/***************************************************************************** * * ftpurl.cpp - Creating, encoding, and decoding URLs * *****************************************************************************/ #include "priv.h" #include "ftpurl.h" /////////////////////////////////////////////////////////////////////// // URL Path Functions (Obsolete?) /////////////////////////////////////////////////////////////////////// /*****************************************************************************\ FUNCTION: UrlGetPath DESCRIPTION: pszUrlPath will NOT include the fragment if there is any. \*****************************************************************************/ HRESULT UrlGetDifference(LPCTSTR pszBaseUrl, LPCTSTR pszSuperUrl, LPTSTR pszPathDiff, DWORD cchSize) { HRESULT hr = E_INVALIDARG; pszPathDiff[0] = TEXT('\0'); if ((lstrlen(pszBaseUrl) <= lstrlen(pszSuperUrl)) && !StrCmpN(pszBaseUrl, pszSuperUrl, lstrlen(pszBaseUrl) - 1)) { LPTSTR pszDelta = (LPTSTR) &pszSuperUrl[lstrlen(pszBaseUrl)]; if (TEXT('/') == pszDelta[0]) pszDelta = CharNext(pszDelta); // Skip past this. StrCpyN(pszPathDiff, pszDelta, cchSize); hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: UrlGetPath DESCRIPTION: pszUrlPath will NOT include the fragment if there is any. \*****************************************************************************/ HRESULT UrlPathToFilePath(LPCTSTR pszSourceUrlPath, LPTSTR pszDestFilePath, DWORD cchSize) { HRESULT hr = E_INVALIDARG; LPTSTR pszSeparator; // Is the source and destination the differnt? if (pszSourceUrlPath != pszDestFilePath) { // Yes, so we need to fill the dest before we start modifying it. StrCpyN(pszDestFilePath, pszSourceUrlPath, cchSize); } while (pszSeparator = StrChr(pszDestFilePath, TEXT('/'))) pszSeparator[0] = TEXT('\\'); // Some people use "Test%20File.txt" when "%20" is really in the file name. // ASSERT(!StrChr(pszDestFilePath, TEXT('%'))); // Assert it doesn't contain '%' or it probably has escaped url stuff. return hr; } /*****************************************************************************\ FUNCTION: UrlPathRemoveSlashW DESCRIPTION: \*****************************************************************************/ HRESULT UrlPathRemoveSlashW(LPWSTR pszUrlPath) { LPWSTR pszEndOfPath = &pszUrlPath[lstrlenW(pszUrlPath) - 1]; // Is it missing a backslash? if ((pszEndOfPath >= pszUrlPath) && (CH_URL_URL_SLASHW == pszEndOfPath[0])) pszEndOfPath[0] = 0; // Yes, so remove it. return S_OK; } /*****************************************************************************\ FUNCTION: UrlPathRemoveSlashA DESCRIPTION: \*****************************************************************************/ HRESULT UrlPathRemoveSlashA(LPSTR pszUrlPath) { LPSTR pszEndOfPath = &pszUrlPath[lstrlenA(pszUrlPath) - 1]; // Is it missing a backslash? if ((pszEndOfPath >= pszUrlPath) && (CH_URL_URL_SLASHA == pszEndOfPath[0])) pszEndOfPath[0] = 0; // Yes, so remove it. return S_OK; } /*****************************************************************************\ FUNCTION: UrlPathRemoveFrontSlashW DESCRIPTION: \*****************************************************************************/ HRESULT UrlPathRemoveFrontSlashW(LPWSTR pszUrlPath) { if (pszUrlPath && (CH_URL_URL_SLASHW == pszUrlPath[0])) return CharReplaceWithStrW(pszUrlPath, lstrlen(pszUrlPath), 1, SZ_EMPTYW); else return S_OK; } /*****************************************************************************\ FUNCTION: UrlPathRemoveFrontSlashA DESCRIPTION: \*****************************************************************************/ HRESULT UrlPathRemoveFrontSlashA(LPSTR pszUrlPath) { if (pszUrlPath && (CH_URL_URL_SLASHA == pszUrlPath[0])) return CharReplaceWithStrA(pszUrlPath, lstrlenA(pszUrlPath), 1, SZ_EMPTYA); else return S_OK; } /*****************************************************************************\ FUNCTION: UrlPathToFilePathW DESCRIPTION: \*****************************************************************************/ HRESULT UrlPathToFilePathW(LPWSTR pszPath) { while (pszPath = StrChrW(pszPath, CH_URL_URL_SLASHW)) pszPath[0] = CH_URL_SLASHW; return S_OK; } /*****************************************************************************\ FUNCTION: UrlPathToFilePathA DESCRIPTION: \*****************************************************************************/ HRESULT UrlPathToFilePathA(LPSTR pszPath) { while (pszPath = StrChrA(pszPath, CH_URL_URL_SLASHA)) pszPath[0] = CH_URL_SLASHA; return S_OK; } /*****************************************************************************\ FUNCTION: FilePathToUrlPathW DESCRIPTION: \*****************************************************************************/ HRESULT FilePathToUrlPathW(LPWSTR pszPath) { while (pszPath = StrChrW(pszPath, CH_URL_SLASHW)) pszPath[0] = CH_URL_URL_SLASHW; return S_OK; } /*****************************************************************************\ FUNCTION: FilePathToUrlPathA DESCRIPTION: \*****************************************************************************/ HRESULT FilePathToUrlPathA(LPSTR pszPath) { while (pszPath = StrChrA(pszPath, CH_URL_SLASHA)) pszPath[0] = CH_URL_URL_SLASHA; return S_OK; } /*****************************************************************************\ FUNCTION: UrlPathAdd DESCRIPTION: ... \*****************************************************************************/ HRESULT UrlPathAdd(LPTSTR pszUrl, DWORD cchUrlSize, LPCTSTR pszSegment) { // If the segment starts with a slash, skip it. if (TEXT('/') == pszSegment[0]) pszSegment = CharNext(pszSegment); StrCatBuff(pszUrl, pszSegment, cchUrlSize); return S_OK; } /*****************************************************************************\ StrRetFromFtpPidl \*****************************************************************************/ HRESULT StrRetFromFtpPidl(LPSTRRET pStrRet, DWORD shgno, LPCITEMIDLIST pidl) { HRESULT hr = S_OK; TCHAR szUrl[MAX_URL_STRING]; szUrl[0] = 0; hr = UrlCreateFromPidl(pidl, shgno, szUrl, ARRAYSIZE(szUrl), ICU_ESCAPE | ICU_USERNAME, TRUE); if (SUCCEEDED(hr)) { // Will it fit into STRRET.cStr? if (lstrlen(szUrl) < ARRAYSIZE(pStrRet->cStr)) { // Yes, so there it goes... pStrRet->uType = STRRET_CSTR; SHTCharToAnsi(szUrl, pStrRet->cStr, ARRAYSIZE(pStrRet->cStr)); } else { // No, so we will need to allocate it LPWSTR pwzAllocedStr = NULL; UINT cch = lstrlen(szUrl) + 1; pwzAllocedStr = (LPWSTR) SHAlloc(CbFromCchW(cch)); pStrRet->uType = STRRET_WSTR; pStrRet->pOleStr = pwzAllocedStr; if (pwzAllocedStr) SHTCharToUnicode(szUrl, pwzAllocedStr, cch); else hr = E_OUTOFMEMORY; } } return hr; } /*****************************************************************************\ FUNCTION: GetLastSegment DESCRIPTION: \*****************************************************************************/ LPTSTR GetLastSegment(LPCTSTR pszUrl) { LPTSTR pszLastSeg = (LPTSTR) pszUrl; LPTSTR pszNextPossibleSeg; while (pszNextPossibleSeg = StrChr(pszLastSeg, TEXT('/'))) { if (TEXT('\0') != CharNext(pszNextPossibleSeg)) pszLastSeg = CharNext(pszNextPossibleSeg); else break; // We are done. } if (TEXT('/') == pszLastSeg[0]) pszLastSeg = CharNext(pszLastSeg); return pszLastSeg; } /*****************************************************************************\ FUNCTION: UrlRemoveDownloadType DESCRIPTION: \*****************************************************************************/ HRESULT UrlRemoveDownloadType(LPTSTR pszUrlPath, BOOL * pfTypeSpecified, BOOL * pfType) { HRESULT hr = S_FALSE; // Specified? (Not yet) LPTSTR pszDownloadType; if (pfTypeSpecified) *pfTypeSpecified = TRUE; // Did the user specify a download type. szPath="Dir1/Dir2/file.txt;type=a". // TODO: Search Recursively because each segment in the path can have a // type. // Example Url="ftp://server/Dir1;type=a/Dir2;type=a/File.txt;type=b if (pszDownloadType = StrStrI(pszUrlPath, SZ_FTP_URL_TYPE)) { TCHAR chType; if (pfTypeSpecified) *pfTypeSpecified = TRUE; pszDownloadType[0] = TEXT('\0'); // Terminate pszUrlPath and remove this junk. chType = pszDownloadType[ARRAYSIZE(SZ_FTP_URL_TYPE) - 1]; if (pfType) { if ((TEXT('a') == chType) || (TEXT('A') == chType)) *pfType = TRUE; else *pfType = TRUE; } hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: IsIPAddressStr DESCRIPTION: This function exists to detect an IP Address server name ("124.42.3.53") vs. a DNS domain name ("foobar", or "ftp.foobar.com"). I current accept more than 4 segments because of 6-bit IP address. TODO: To be thurough, I should probably made sure each segment is smaller than 256. \*****************************************************************************/ BOOL IsIPAddressStr(LPTSTR pszServer) { BOOL fIsIPAddressStr = TRUE; LPTSTR pszCurrentChar = pszServer; int nDigits = 0; int nSegments = 1; while (fIsIPAddressStr && pszCurrentChar[0]) { if (TEXT('.') == pszCurrentChar[0]) { nSegments++; if ((0 == nDigits) || (4 < nDigits)) fIsIPAddressStr = FALSE; // it started with a '.', ie, ".xxxxx" nDigits = 0; } nDigits++; if (nDigits > 4) fIsIPAddressStr = FALSE; // To many digits, ie "12345.xxxx" if (((TEXT('0') > pszCurrentChar[0]) || (TEXT('9') < pszCurrentChar[0])) && (TEXT('.') != pszCurrentChar[0])) { fIsIPAddressStr = FALSE; // it's outside of the 0-9 range. } pszCurrentChar++; // Next character. } if (nSegments != 4) fIsIPAddressStr = FALSE; // Needs to have at least 4 segments ("1.2.3.4", "1.2.3.4.5") return fIsIPAddressStr; } /*****************************************************************************\ FUNCTION: PidlGenerateSiteLookupStr DESCRIPTION: Sample Input: "ftp://user:password@ftp.server.com:69/Dir1/Dir2/File.txt" Sample Output: "ftp://user:password@ftp.server.com:69/" This is used to keep track of unique servers for CFtpSite. A CFtpSite needs to be created for each unique site, which includes different users that are logged onto the same site because of rooted directories. \*****************************************************************************/ HRESULT PidlGenerateSiteLookupStr(LPCITEMIDLIST pidl, LPTSTR pszLookupStr, DWORD cchSize) { HRESULT hr = E_FAIL; // Some strange clients pass in non-Server IDs, like comdlg. if (FtpID_IsServerItemID(pidl)) { LPITEMIDLIST pidlServer = FtpCloneServerID(pidl); if (pidlServer) { hr = UrlCreateFromPidlW(pidlServer, SHGDN_FORPARSING, pszLookupStr, cchSize, (ICU_ESCAPE | ICU_USERNAME), FALSE); ILFree(pidlServer); } else hr = E_OUTOFMEMORY; } return hr; } HRESULT PidlReplaceUserPassword(LPCITEMIDLIST pidlIn, LPITEMIDLIST * ppidlOut, IMalloc * pm, LPCTSTR pszUserName, LPCTSTR pszPassword) { TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH]; TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; HRESULT hr = FtpPidl_GetServer(pidlIn, szServer, ARRAYSIZE(szServer)); if (!pszUserName) // May be NULL. { pszUserName = szUserName; EVAL(SUCCEEDED(FtpPidl_GetUserName(pidlIn, szUserName, ARRAYSIZE(szUserName)))); } *ppidlOut = NULL; if (SUCCEEDED(hr)) { LPITEMIDLIST pidlServer; hr = FtpServerID_Create(szServer, pszUserName, pszPassword, FtpServerID_GetTypeID(pidlIn), FtpServerID_GetPortNum(pidlIn), &pidlServer, pm, TRUE); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlFtpPath = _ILNext(pidlIn); *ppidlOut = ILCombine(pidlServer, pidlFtpPath); ILFree(pidlServer); } } return hr; } HRESULT UrlReplaceUserPassword(LPTSTR pszUrlPath, DWORD cchSize, LPCTSTR pszUserName, LPCTSTR pszPassword) { HRESULT hr = E_FAIL; URL_COMPONENTS urlComps = {0}; TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH]; TCHAR szUrlPath[MAX_URL_STRING]; TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; TCHAR szExtraInfo[MAX_PATH]; // Includes Port Number and download type (ASCII, Binary, Detect) BOOL fResult; urlComps.dwStructSize = sizeof(urlComps); urlComps.lpszHostName = szServer; urlComps.dwHostNameLength = ARRAYSIZE(szServer); urlComps.lpszUrlPath = szUrlPath; urlComps.dwUrlPathLength = ARRAYSIZE(szUrlPath); urlComps.lpszExtraInfo = szExtraInfo; urlComps.dwExtraInfoLength = ARRAYSIZE(szExtraInfo); urlComps.lpszUserName = szUserName; urlComps.dwUserNameLength = ARRAYSIZE(szUserName); urlComps.lpszPassword = szPassword; urlComps.dwPasswordLength = ARRAYSIZE(szPassword); fResult = InternetCrackUrl(pszUrlPath, 0, ICU_DECODE, &urlComps); if (fResult) { urlComps.dwStructSize = sizeof(urlComps); urlComps.lpszHostName = szServer; urlComps.lpszUserName = (LPTSTR)(pszUserName ? pszUserName : szUserName); urlComps.dwUserNameLength = (pszUserName ? lstrlen(pszUserName) : lstrlen(szUserName)); urlComps.lpszPassword = (LPTSTR)pszPassword; // It may be valid for caller to pass NULL urlComps.dwPasswordLength = (pszPassword ? lstrlen(pszPassword) : 0); urlComps.lpszExtraInfo = szExtraInfo; urlComps.dwExtraInfoLength = ARRAYSIZE(szExtraInfo); fResult = InternetCreateUrl(&urlComps, (ICU_ESCAPE_AUTHORITY | ICU_ESCAPE | ICU_USERNAME), pszUrlPath, &cchSize); if (fResult) { hr = S_OK; } } return hr; } // InternetCreateUrlW() will write into pszUrl but won't terminate the string. // This is hard to detect because half the time the place where the terminator should // go may coincidentally contain a terminator. This code forces the bug to happen. #define TEST_FOR_INTERNETCREATEURL_BUG 1 #define INTERNETCREATEURL_BUG_WORKAROUND 1 HRESULT UrlCreateEx(LPCTSTR pszServer, LPCTSTR pszUser, LPCTSTR pszPassword, LPCTSTR pszUrlPath, LPCTSTR pszFragment, INTERNET_PORT ipPortNum, LPCTSTR pszDownloadType, LPTSTR pszUrl, DWORD cchSize, DWORD dwFlags) { HRESULT hr = E_FAIL; DWORD cchSizeCopy = cchSize; #if DEBUG && TEST_FOR_INTERNETCREATEURL_BUG LPTSTR pszDebugStr = pszUrl; for (DWORD dwIndex = (cchSize - 2); dwIndex; dwIndex--) { #ifndef INTERNETCREATEURL_BUG_WORKAROUND pszDebugStr[0] = -1; // This will force a buffer w/o terminators. #else // INTERNETCREATEURL_BUG_WORKAROUND pszDebugStr[0] = 0; // This will work around the bug. #endif // INTERNETCREATEURL_BUG_WORKAROUND pszDebugStr++; } #endif // DEBUG && TEST_FOR_INTERNETCREATEURL_BUG URL_COMPONENTS urlComp = {sizeof(URL_COMPONENTS), NULL, 0, INTERNET_SCHEME_FTP, (LPTSTR) pszServer, 0, ipPortNum, (LPTSTR) NULL_FOR_EMPTYSTR(pszUser), 0, (LPTSTR) NULL_FOR_EMPTYSTR(pszPassword), 0, (LPTSTR) pszUrlPath, 0, (LPTSTR) NULL, 0}; if (EVAL(InternetCreateUrl(&urlComp, dwFlags | ICU_ESCAPE_AUTHORITY | ICU_USERNAME, pszUrl, &cchSizeCopy))) { hr = S_OK; if (pszFragment) StrCatBuff(pszUrl, pszFragment, cchSize); } #if DEBUG && TEST_FOR_INTERNETCREATEURL_BUG #ifdef INTERNETCREATEURL_BUG_WORKAROUND // Make sure we hit a terminator and not a -1, which should never happen in URL strings. for (pszDebugStr = pszUrl; pszDebugStr[0]; pszDebugStr++) ASSERT(-1 != pszDebugStr[0]); #endif // INTERNETCREATEURL_BUG_WORKAROUND #endif // DEBUG && TEST_FOR_INTERNETCREATEURL_BUG return hr; } HRESULT UrlCreate(LPCTSTR pszServer, LPCTSTR pszUser, LPCTSTR pszPassword, LPCTSTR pszUrlPath, LPCTSTR pszFragment, INTERNET_PORT ipPortNum, LPCTSTR pszDownloadType, LPTSTR pszUrl, DWORD cchSize) { return UrlCreateEx(pszServer, pszUser, pszPassword, pszUrlPath, pszFragment, ipPortNum, pszDownloadType, pszUrl, cchSize, ICU_ESCAPE); } BOOL IsEmptyUrlPath(LPCTSTR pszUrlPath) { BOOL fResult = FALSE; if (!pszUrlPath || !pszUrlPath[0] || (((TEXT('/') == pszUrlPath[0]) && (!pszUrlPath[1])))) { fResult = TRUE; } return fResult; } /////////////////////////////////////////////////////////////////////// // Wire Path Functions (UTF-8 or DBCS/MBCS) /////////////////////////////////////////////////////////////////////// /*****************************************************************************\ FUNCTION: WirePathAdd DESCRIPTION: ... \*****************************************************************************/ HRESULT WirePathAdd(LPWIRESTR pwWirePath, DWORD cchUrlSize, LPCWIRESTR pwWireSegment) { // If the segment starts with a slash, skip it. if ('/' == pwWireSegment[0]) pwWireSegment = CharNextA(pwWireSegment); StrCatBuffA(pwWirePath, pwWireSegment, cchUrlSize); return S_OK; } /*****************************************************************************\ FUNCTION: WirePathAppendSlash DESCRIPTION: \*****************************************************************************/ HRESULT WirePathAppendSlash(LPWIRESTR pwWirePath, DWORD cchWirePathSize) { HRESULT hr = E_FAIL; DWORD cchSize = lstrlenA(pwWirePath); // Is there enough room? if (cchSize < (cchWirePathSize - 1)) { LPWIRESTR pwEndOfPath = &pwWirePath[cchSize - 1]; // Is it missing a backslash? if ((pwEndOfPath >= pwWirePath) && '/' != pwEndOfPath[0]) StrCatBuffA(pwEndOfPath, SZ_URL_SLASHA, (int)(cchWirePathSize - (pwEndOfPath - pwWirePath))); // Yes, so add it. hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: WirePathAppend DESCRIPTION: ... \*****************************************************************************/ HRESULT WirePathAppend(LPWIRESTR pwWirePath, DWORD cchUrlSize, LPCWIRESTR pwWireSegment) { if (!EVAL(pwWireSegment)) return E_INVALIDARG; WirePathAppendSlash(pwWirePath, cchUrlSize); // Make sure the base url ends in a '/'. Note it may be "ftp://". return WirePathAdd(pwWirePath, cchUrlSize, pwWireSegment); } /*****************************************************************************\ FUNCTION: UrlGetFirstPathSegment PARAMETERS: [IN] pszFullPath - "Dir1\Dir2\Dir3" [OUT] szFirstItem - "Dir1" [OPTIONAL] [OUT] szRemaining - "Dir2\Dir3" [OPTIONAL] \*****************************************************************************/ HRESULT WirePathGetFirstSegment(LPCWIRESTR pwFtpWirePath, LPWIRESTR wFirstItem, DWORD cchFirstItemSize, BOOL * pfWasFragSeparator, LPWIRESTR wRemaining, DWORD cchRemainingSize, BOOL * pfIsDir) { HRESULT hr = S_OK; LPCWIRESTR pwSegEnding = StrChrA(pwFtpWirePath, CH_URL_URL_SLASH); if (pfIsDir) *pfIsDir = FALSE; ASSERT((CH_URL_URL_SLASHA != pwFtpWirePath[0])); // You will probably not get what you want. if (pwSegEnding) { if (wFirstItem) { DWORD cchSize = (DWORD) (pwSegEnding - pwFtpWirePath + 1); StrCpyNA(wFirstItem, pwFtpWirePath, (cchSize <= cchFirstItemSize) ? cchSize : cchFirstItemSize); } if (pfIsDir && (CH_URL_URL_SLASHA == pwSegEnding[0])) *pfIsDir = TRUE; // Tell them that it is a directory. if (wRemaining) StrCpyNA(wRemaining, CharNextA(pwSegEnding), cchRemainingSize); if (0 == pwSegEnding[1]) hr = S_FALSE; // End of the line. } else { if (wFirstItem) StrCpyNA(wFirstItem, pwFtpWirePath, cchFirstItemSize); // pszFullPath contains only one segment if (wRemaining) wRemaining[0] = 0; hr = S_FALSE; // Indicate that there aren't any more directories left. } return hr; } /////////////////////////////////////////////////////////////////////// // Display Path Functions (Unicode) /////////////////////////////////////////////////////////////////////// /*****************************************************************************\ FUNCTION: DisplayPathAdd DESCRIPTION: ... \*****************************************************************************/ HRESULT DisplayPathAdd(LPWSTR pwzUrl, DWORD cchUrlSize, LPCWSTR pwzSegment) { // If the segment starts with a slash, skip it. if (L'/' == pwzSegment[0]) pwzSegment = CharNext(pwzSegment); StrCatBuffW(pwzUrl, pwzSegment, cchUrlSize); return S_OK; } /*****************************************************************************\ FUNCTION: DisplayPathAppendSlash DESCRIPTION: \*****************************************************************************/ HRESULT DisplayPathAppendSlash(LPWSTR pwzDisplayPath, DWORD cchSize) { DWORD cchCurrentSize = lstrlenW(pwzDisplayPath); HRESULT hr = CO_E_PATHTOOLONG; if (cchCurrentSize < (cchSize - 2)) { LPWSTR pwzEndOfPath = &pwzDisplayPath[cchCurrentSize - 1]; // Is it missing a backslash? if ((pwzEndOfPath >= pwzDisplayPath) && TEXT('/') != pwzEndOfPath[0]) StrCatBuff(pwzEndOfPath, SZ_URL_SLASH, (cchSize - cchCurrentSize + 1)); // Yes, so add it. hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: DisplayPathAppend DESCRIPTION: ... \*****************************************************************************/ HRESULT DisplayPathAppend(LPWSTR pwzDisplayPath, DWORD cchUrlSize, LPCWSTR pwzDisplaySegment) { if (!EVAL(pwzDisplaySegment)) return E_INVALIDARG; DisplayPathAppendSlash(pwzDisplayPath, cchUrlSize); // Make sure the base url ends in a '/'. Note it may be "ftp://". return DisplayPathAdd(pwzDisplayPath, cchUrlSize, pwzDisplaySegment); } /*****************************************************************************\ FUNCTION: DisplayPathGetFirstSegment PARAMETERS: [IN] pszFullPath - "Dir1\Dir2\Dir3" [OUT] szFirstItem - "Dir1" [OPTIONAL] [OUT] szRemaining - "Dir2\Dir3" [OPTIONAL] \*****************************************************************************/ HRESULT DisplayPathGetFirstSegment(LPCWSTR pwzFullPath, LPWSTR pwzFirstItem, DWORD cchFirstItemSize, BOOL * pfWasFragSeparator, LPWSTR pwzRemaining, DWORD cchRemainingSize, BOOL * pfIsDir) { HRESULT hr = S_OK; LPWSTR pwzSegEnding = StrChrW(pwzFullPath, CH_URL_URL_SLASH); if (pfIsDir) *pfIsDir = FALSE; // This will happen if the user enters an incorrect URL, like "ftp://wired//" // ASSERT((CH_URL_URL_SLASHW != pwzFullPath[0])); // You will probably not get what you want. if (pwzSegEnding) { if (pwzFirstItem) { DWORD cchSize = (DWORD) (pwzSegEnding - pwzFullPath + 1); StrCpyNW(pwzFirstItem, pwzFullPath, (cchSize <= cchFirstItemSize) ? cchSize : cchFirstItemSize); } if (pfIsDir && (CH_URL_URL_SLASHW == pwzSegEnding[0])) *pfIsDir = TRUE; // Tell them that it is a directory. if (pwzRemaining) StrCpyNW(pwzRemaining, CharNextW(pwzSegEnding), cchRemainingSize); if (0 == pwzSegEnding[1]) hr = S_FALSE; // End of the line. } else { if (pwzFirstItem) StrCpyNW(pwzFirstItem, pwzFullPath, cchFirstItemSize); // pszFullPath contains only one segment if (pwzRemaining) pwzRemaining[0] = 0; hr = S_FALSE; // Indicate that there aren't any more directories left. } return hr; }