/*++ Copyright (c) 1995 Microsoft Corporation Module Name: ftpapiu.cxx Abstract: Common sub-API level FTP functions (created from dll\parseurl.c) Contents: ParseFtpUrl Author: Richard L Firth (rfirth) 31-May-1995 Environment: Win32 user-level DLL Revision History: 31-May-1995 rfirth Created --*/ #include #include "ftpapih.h" // because wininet doesnt know IStream #define NO_SHLWAPI_STREAM #include #include // // functions // DWORD ParseFtpUrl( IN OUT LPHINTERNET lphInternet, IN LPSTR Url, IN DWORD SchemeLength, IN LPSTR Headers, IN DWORD HeadersLength, IN DWORD OpenFlags, IN DWORD_PTR Context ) /*++ Routine Description: URL parser for FTP URLs. Support function for InternetOpenUrl() and ParseUrl(). This is a macro function that just cracks the URL and calls FTP APIs to do the work Arguments: lphInternet - IN: pointer to InternetOpen handle OUT: if successful handle of opened item, else undefined Url - pointer to string containing FTP URL to open SchemeLength - length of the URL scheme, exluding "://" Headers - unused for FTP HeadersLength - unused for FTP OpenFlags - optional flags for opening a file (cache/no-cache, etc.) Context - app-supplied context value for call-backs Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INTERNET_INVALID_URL The URL passed in could not be parsed --*/ { DEBUG_ENTER((DBG_FTP, Dword, "ParseFtpUrl", "%#x [%#x], %q, %d, %#x, %d, %#x, %#x", lphInternet, *lphInternet, Url, SchemeLength, Headers, HeadersLength, OpenFlags, Context )); UNREFERENCED_PARAMETER(Headers); UNREFERENCED_PARAMETER(HeadersLength); // // parse out the name[:password] and host[:port] parts // DWORD urlLength; LPSTR pUserName; DWORD userNameLength; LPSTR pPassword; DWORD passwordLength; LPSTR pHostName; DWORD hostNameLength; INTERNET_PORT port; LPSTR lpszUrl = NULL, lpszBackup = NULL; char firstUrlPathCharacter; HINTERNET hConnect = NULL; DWORD error; // The passed in Url string gets munged during this function. // Make a copy, so the proper URL is set to the mapped connection handle. lpszUrl = NewString((LPCSTR)Url); if (lpszUrl == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } lpszBackup = lpszUrl; lpszUrl += SchemeLength + sizeof("://") - 1; error = GetUrlAddress(&lpszUrl, &urlLength, &pUserName, &userNameLength, &pPassword, &passwordLength, &pHostName, &hostNameLength, &port, NULL ); if (error != ERROR_SUCCESS) { goto quit; } // // we can safely zero-terminate the address parts - the '/' between address // info and url-path is not significant // //if (*Url == '/') { // ++Url; // --urlLength; //} if (pUserName != NULL) { pUserName[userNameLength] = '\0'; } if (pPassword != NULL) { pPassword[passwordLength] = '\0'; } // // now get the FTP file/directory information // BOOL isDirectory; if ((*lpszUrl == '\0') || (*(lpszUrl + 1) == '\0')) { // // if the URL just consisted of ftp://host then by default we are // referencing an FTP directory (the root directory) // isDirectory = TRUE; } else { LPSTR pSemiColon; pSemiColon = strchr(lpszUrl, ';'); if (pSemiColon != NULL) { // // if there's not enough space left in the string after ';' for the // "type=?" substring, then assume this URL is bad // if ((urlLength - (pSemiColon - lpszUrl)) < 6) { error = ERROR_INTERNET_INVALID_URL; goto quit; } if (strnicmp(pSemiColon + 1, "type=", 5) == 0) { switch (tolower(*(pSemiColon + 6))) { case 'a': OpenFlags |= FTP_TRANSFER_TYPE_ASCII; isDirectory = FALSE; *pSemiColon = '\0'; break; case 'i': OpenFlags |= FTP_TRANSFER_TYPE_BINARY; isDirectory = FALSE; *pSemiColon = '\0'; break; case 'd': isDirectory = TRUE; break; default: error = ERROR_INTERNET_INVALID_URL; goto quit; } } else { // // found a ';', but not "type=". Don't understand this URL // error = ERROR_INTERNET_INVALID_URL; goto quit; } urlLength = (DWORD) (pSemiColon - lpszUrl); } else { // // there is no ;type= field to help us out. If the string ends in / // then it is a directory. Further, if the url-path refers to a // file, we don't know which mode to use to transfer it - ASCII or // BINARY. We'll default to binary // if (lpszUrl[urlLength - 1] == '/') { isDirectory = TRUE; } else { OpenFlags |= FTP_TRANSFER_TYPE_BINARY; isDirectory = FALSE; } } // // decode the url-path // if(FAILED(UrlUnescapeInPlace(lpszUrl, 0))){ goto quit; } urlLength = lstrlen(lpszUrl); } // // we potentially need to go round this loop 3 times: // // 1. try to get the item from the cache // 2. try to get the item from the origin server // 3. only if we got an existing connect & the origin server request // failed, reopen the connect handle & try step 2 again // // however, we only need make one attempt if we're in OFFLINE mode - either // we can get the item from the cache, or we can't // HINTERNET hInternetMapped; // // BUGBUG - this function should receive the handle already mapped // error = MapHandleToAddress(*lphInternet, (LPVOID *)&hInternetMapped, FALSE); if (error != ERROR_SUCCESS) { goto quit; } INET_ASSERT(hInternetMapped != NULL); // // if the InternetOpen() handle was created in OFFLINE mode then is an // offline request // DEBUG_PRINT(FTP, INFO, ("ParseFtpUrl: pre-OpenFlags check: OpenFlags = %#x\n", OpenFlags )); OpenFlags |= ((INTERNET_HANDLE_OBJECT *)hInternetMapped)->GetInternetOpenFlags() & INTERNET_FLAG_OFFLINE; DEBUG_PRINT(FTP, INFO, ("ParseFtpUrl: post-OpenFlags check: OpenFlags = %#x\n", OpenFlags )); DereferenceObject((LPVOID)hInternetMapped); DWORD limit; limit = (OpenFlags & INTERNET_FLAG_OFFLINE) ? 1 : 3; // // resynchronize same as reload for FTP // if (OpenFlags & INTERNET_FLAG_RESYNCHRONIZE) { OpenFlags |= INTERNET_FLAG_RELOAD; DEBUG_PRINT(FTP, INFO, ("ParseFtpUrl: INTERNET_FLAG_RESYNCHRONIZE set\n")); } DWORD i; BOOL bFromCache; i = 0; bFromCache = (OpenFlags & INTERNET_FLAG_RELOAD || (GlobalUrlCacheSyncMode == WININET_SYNC_MODE_ALWAYS)) ? FALSE : TRUE; while (i < limit) { // // ok, all parts present and correct; open a handle to the FTP resource // DWORD dwFlags = OpenFlags; if (bFromCache) { // // attempting to get item from cache // dwFlags |= INTERNET_FLAG_OFFLINE; } else { // // performing net request // dwFlags |= INTERNET_FLAG_RELOAD; dwFlags &= ~INTERNET_FLAG_OFFLINE; } // // zero-terminating the host name will wipe out the first '/' of the // URL-path which we must restore before using // firstUrlPathCharacter = *lpszUrl; if (pHostName != NULL) { pHostName[hostNameLength] = '\0'; } // // record current online/offline state // BOOL bOffline = IsOffline(); // // create a connect handle object or find an existing one if using // INTERNET_FLAG_EXISTING_CONNECT // if ( hConnect ) { _InternetCloseHandle(hConnect); // nuke old connect handle, otherwise we leak. } hConnect = InternetConnectA(*lphInternet, pHostName, port, pUserName, pPassword, INTERNET_SERVICE_FTP, dwFlags, // // we are creating a "hidden" handle - don't // tell the app about it // INTERNET_NO_CALLBACK ); // // restore URL-path, but only if its not '\0' - we may have a const // string (we can't write to it - we change to "/" below, which is a // const string) // if (*lpszUrl == '\0') { *(LPSTR)lpszUrl = firstUrlPathCharacter; } HINTERNET hConnectMapped = NULL; if (hConnect != NULL) { // // lock the handle by mapping it // error = MapHandleToAddress(hConnect, (LPVOID *)&hConnectMapped, FALSE ); INET_ASSERT(error == ERROR_SUCCESS); INET_ASSERT(hConnectMapped != NULL); if (error != ERROR_SUCCESS) { break; } INTERNET_CONNECT_HANDLE_OBJECT * pConnectMapped; pConnectMapped = (INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped; // // the ref count should be 2: either we created the connect handle // or we picked up an EXISTING_CONNECT handle which should not be // used by any other requests // INET_ASSERT(pConnectMapped->ReferenceCount() == 2); // // first off, associate the last response info, possibly including // the server welcome message, with the connection // pConnectMapped->AttachLastResponseInfo(); pConnectMapped->SetURL(Url); HINTERNET hRequest; if (isDirectory) { if (*lpszUrl == '\0') { lpszUrl = "/"; } // // if we are reading from cache then set the working directory // locally, else also set the CWD at the server // if (bFromCache) { error = pConnectMapped->SetCurrentWorkingDirectory((LPSTR)lpszUrl); } else if (FtpSetCurrentDirectory(hConnect, lpszUrl)) { error = ERROR_SUCCESS; } else { error = GetLastError(); } if (error == ERROR_SUCCESS) { // if we are not asked to give raw data // then set htmlfind to TRUE if (!(dwFlags & INTERNET_FLAG_RAW_DATA)) { pConnectMapped->SetHtmlFind(TRUE); } hRequest = InternalFtpFindFirstFileA(hConnect, NULL, NULL, dwFlags, Context, bFromCache, pConnectMapped->IsHtmlFind() // allow empty ); } else { hRequest = NULL; } } else /* if (!isDirectory) */ { hRequest = InternalFtpOpenFileA(hConnect, lpszUrl, GENERIC_READ, dwFlags, Context, bFromCache ); // // we may have failed because we're not trying to get a file // after all - we've been given a directory without a trailing // slash // if (hRequest == NULL) { if (!(dwFlags & INTERNET_FLAG_RAW_DATA)) { pConnectMapped->SetHtmlFind(TRUE); } error = pConnectMapped->SetCurrentWorkingDirectory((LPSTR)lpszUrl); INET_ASSERT(error == ERROR_SUCCESS); if (error == ERROR_SUCCESS) { if (!bFromCache) { if (!FtpSetCurrentDirectory(hConnect, lpszUrl)) { error = GetLastError(); } } if (error == ERROR_SUCCESS) { hRequest = InternalFtpFindFirstFileA( hConnect, NULL, NULL, dwFlags, Context, bFromCache, pConnectMapped->IsHtmlFind() ); } } } } // // link the request and connect handles so that the connect handle // object will be deleted when the request handle is closed // if (hRequest != NULL) { HINTERNET hRequestMapped = NULL; error = MapHandleToAddress(hRequest, (LPVOID *)&hRequestMapped, FALSE ); if (error == ERROR_SUCCESS) { RSetParentHandle(hRequestMapped, hConnectMapped, TRUE); } // // dereference the handles referenced by MapHandleToAddress() // if (hRequestMapped != NULL) { DereferenceObject((LPVOID)hRequestMapped); } // // return the request handle // *lphInternet = hRequest; } // // unmap and dereference the connect handle // DereferenceObject((LPVOID)hConnectMapped); // // if we succeeded in opening the item then we're done // if (hRequest != NULL) { break; } else { error = GetLastError(); // // close the handle without modifying the per-thread handle and // context values // //DWORD closeError = _InternetCloseHandleNoContext(hConnect); DWORD closeError = ERROR_SUCCESS; INET_ASSERT(closeError == ERROR_SUCCESS); // // if we failed because we went offline then make a cache // request if we can // if (IsOffline() && !bFromCache) { // // this will be the last chance // bFromCache = TRUE; continue; } } } else { // // InternetConnect() failed. If the offline state didn't change then // its a real error - quit // error = GetLastError(); if (IsOffline() == bOffline) { break; } // // we must have transitioned offline state. If we went offline then // attempt to read from cache only // if (IsOffline() && !bFromCache) { bFromCache = TRUE; continue; } } // // next iteration - second & subsequent not from cache unless we are // offline // bFromCache = FALSE; ++i; } quit: if (lpszBackup) FREE_MEMORY(lpszBackup); DEBUG_LEAVE(error); return error; }