639 lines
18 KiB
C++
639 lines
18 KiB
C++
/*++
|
||
|
||
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 <wininetp.h>
|
||
#include "ftpapih.h"
|
||
|
||
// because wininet doesnt know IStream
|
||
#define NO_SHLWAPI_STREAM
|
||
#include <shlwapi.h>
|
||
#include <shlwapip.h>
|
||
|
||
//
|
||
// 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;
|
||
}
|