You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
638 lines
18 KiB
638 lines
18 KiB
/*++
|
|
|
|
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;
|
|
}
|