Windows2003-3790/inetcore/wininet/ftp/ftpapiu.cxx
2020-09-30 16:53:55 +02:00

639 lines
18 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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;
}