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.
6969 lines
195 KiB
6969 lines
195 KiB
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
inetapia.cxx
|
|
|
|
Abstract:
|
|
|
|
Contains the ANSI and character-mode-independent Internet APIs
|
|
|
|
Contents:
|
|
InternetCrackUrlA
|
|
InternetCreateUrlA
|
|
InternetCanonicalizeUrlA
|
|
InternetCombineUrlA
|
|
InternetOpenA
|
|
InternetCloseHandle
|
|
_InternetCloseHandle
|
|
_InternetCloseHandleNoContext
|
|
InternetConnectA
|
|
InternetOpenUrlA
|
|
InternetReadFile
|
|
ReadFile_End
|
|
InternetReadFileExA
|
|
InternetWriteFile
|
|
InternetWriteFileExA
|
|
InternetSetFilePointer
|
|
InternetQueryDataAvailable
|
|
InternetFindNextFileA
|
|
InternetQueryOptionA
|
|
InternetSetOptionA
|
|
InternetSetOptionExA
|
|
InternetGetLastResponseInfoA
|
|
InternetSetStatusCallbackA
|
|
//InternetCancelAsyncRequest
|
|
(wInternetCloseConnectA)
|
|
(GetEmailNameAndPassword)
|
|
InternetAttemptConnect
|
|
(CreateDeleteSocket)
|
|
InternetLockRequestFile
|
|
InternetUnlockRequestFile
|
|
InternetCheckConnectionA
|
|
|
|
Author:
|
|
|
|
Richard L Firth (rfirth) 02-Mar-1995
|
|
|
|
Environment:
|
|
|
|
Win32 user-mode DLL
|
|
|
|
Revision History:
|
|
|
|
02-Mar-1995 rfirth
|
|
Created
|
|
|
|
07-Mar-1995 madana
|
|
|
|
|
|
--*/
|
|
|
|
|
|
#include <wininetp.h>
|
|
#include <perfdiag.hxx>
|
|
#include "inetapiu.h"
|
|
|
|
// because wininet doesnt know IStream
|
|
#define NO_SHLWAPI_STREAM
|
|
#include <shlwapi.h>
|
|
#include <shlwapip.h>
|
|
|
|
#include "autodial.h"
|
|
|
|
//
|
|
// public ..?
|
|
//
|
|
|
|
extern "C" {
|
|
|
|
INTERNETAPI_(BOOL) InternetGetCertByURLA(
|
|
IN LPSTR lpszURL,
|
|
IN OUT LPSTR lpszCertText,
|
|
OUT DWORD dwcbCertText
|
|
);
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// private manifests
|
|
//
|
|
|
|
//
|
|
// private prototypes
|
|
//
|
|
|
|
PRIVATE
|
|
DWORD
|
|
ReadFile_Fsm(
|
|
IN CFsm_ReadFile * Fsm
|
|
);
|
|
|
|
PRIVATE
|
|
DWORD
|
|
ReadFileEx_Fsm(
|
|
IN CFsm_ReadFileEx * Fsm
|
|
);
|
|
|
|
PRIVATE
|
|
VOID
|
|
ReadFile_End(
|
|
IN BOOL bDeref,
|
|
IN BOOL bSuccess,
|
|
IN HINTERNET hFileMapped,
|
|
IN DWORD dwBytesRead,
|
|
IN LPVOID lpBuffer OPTIONAL,
|
|
IN DWORD dwNumberOfBytesToRead,
|
|
OUT LPDWORD lpdwNumberOfBytesRead OPTIONAL
|
|
);
|
|
|
|
PRIVATE
|
|
DWORD
|
|
QueryAvailable_Fsm(
|
|
IN CFsm_QueryAvailable * Fsm
|
|
);
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
wInternetCloseConnectA(
|
|
IN HINTERNET lpConnectHandle,
|
|
IN DWORD ServiceType
|
|
);
|
|
|
|
PRIVATE
|
|
DWORD
|
|
GetEmailNameAndPassword(
|
|
IN OUT LPSTR* lplpszUserName,
|
|
IN OUT LPSTR* lplpszPassword,
|
|
OUT LPSTR lpszEmailName,
|
|
IN DWORD dwEmailNameLength
|
|
);
|
|
|
|
PRIVATE
|
|
BOOL
|
|
InternetParseCommon(
|
|
IN LPCTSTR lpszBaseUrl,
|
|
IN LPCTSTR lpszRelativeUrl,
|
|
OUT LPTSTR lpszBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN DWORD dwFlags
|
|
);
|
|
|
|
//PRIVATE
|
|
//DWORD
|
|
//CreateDeleteSocket(
|
|
// VOID
|
|
// );
|
|
|
|
BOOL
|
|
GetWininetUserName(
|
|
VOID
|
|
);
|
|
|
|
|
|
|
|
//
|
|
// functions
|
|
//
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetCrackUrlA(
|
|
IN LPCSTR lpszUrl,
|
|
IN DWORD dwUrlLength,
|
|
IN DWORD dwFlags,
|
|
IN LPURL_COMPONENTSA lpUrlComponents
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cracks an URL into its constituent parts. Optionally escapes the url-path.
|
|
We assume that the user has supplied large enough buffers for the various
|
|
URL parts
|
|
|
|
Arguments:
|
|
|
|
lpszUrl - pointer to URL to crack
|
|
|
|
dwUrlLength - 0 if lpszUrl is ASCIIZ string, else length of lpszUrl
|
|
|
|
dwFlags - flags controlling operation
|
|
|
|
lpUrlComponents - pointer to URL_COMPONENTS
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetCrackUrlA",
|
|
"%q, %#x, %#x, %#x",
|
|
lpszUrl,
|
|
dwUrlLength,
|
|
dwFlags,
|
|
lpUrlComponents
|
|
));
|
|
|
|
DWORD error;
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(lpszUrl)) {
|
|
if (dwUrlLength == 0) {
|
|
error = ProbeString((LPSTR)lpszUrl, &dwUrlLength);
|
|
} else {
|
|
error = ProbeReadBuffer((LPVOID)lpszUrl, dwUrlLength);
|
|
}
|
|
} else {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
if ((lpUrlComponents == NULL)
|
|
|| (lpUrlComponents->dwStructSize != sizeof(*lpUrlComponents))) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
} else {
|
|
error = ProbeWriteBuffer((LPVOID)lpUrlComponents,
|
|
sizeof(*lpUrlComponents)
|
|
);
|
|
}
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// we only allow two flags for this API
|
|
//
|
|
|
|
if (dwFlags & ~(ICU_ESCAPE | ICU_DECODE)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
if (!GlobalDataInitialized)
|
|
{
|
|
error = GlobalDataInitialize();
|
|
if (error != ERROR_SUCCESS)
|
|
{
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the individual components to return. If they reference a buffer then
|
|
// check it for writeability
|
|
//
|
|
|
|
LPSTR lpUrl;
|
|
LPSTR urlCopy;
|
|
INTERNET_SCHEME schemeType;
|
|
LPSTR schemeName;
|
|
DWORD schemeNameLength;
|
|
LPSTR hostName;
|
|
DWORD hostNameLength;
|
|
INTERNET_PORT nPort;
|
|
LPSTR userName;
|
|
DWORD userNameLength;
|
|
LPSTR password;
|
|
DWORD passwordLength;
|
|
LPSTR urlPath;
|
|
DWORD urlPathLength;
|
|
LPSTR extraInfo;
|
|
DWORD extraInfoLength;
|
|
BOOL copyComponent;
|
|
BOOL havePort;
|
|
|
|
copyComponent = FALSE;
|
|
|
|
schemeName = lpUrlComponents->lpszScheme;
|
|
schemeNameLength = lpUrlComponents->dwSchemeLength;
|
|
if ((schemeName != NULL) && (schemeNameLength != 0)) {
|
|
error = ProbeWriteBuffer((LPVOID)schemeName, schemeNameLength);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
*schemeName = '\0';
|
|
copyComponent = TRUE;
|
|
}
|
|
|
|
hostName = lpUrlComponents->lpszHostName;
|
|
hostNameLength = lpUrlComponents->dwHostNameLength;
|
|
if ((hostName != NULL) && (hostNameLength != 0)) {
|
|
error = ProbeWriteBuffer((LPVOID)hostName, hostNameLength);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
*hostName = '\0';
|
|
copyComponent = TRUE;
|
|
}
|
|
|
|
userName = lpUrlComponents->lpszUserName;
|
|
userNameLength = lpUrlComponents->dwUserNameLength;
|
|
if ((userName != NULL) && (userNameLength != 0)) {
|
|
error = ProbeWriteBuffer((LPVOID)userName, userNameLength);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
*userName = '\0';
|
|
copyComponent = TRUE;
|
|
}
|
|
|
|
password = lpUrlComponents->lpszPassword;
|
|
passwordLength = lpUrlComponents->dwPasswordLength;
|
|
if ((password != NULL) && (passwordLength != 0)) {
|
|
error = ProbeWriteBuffer((LPVOID)password, passwordLength);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
*password = '\0';
|
|
copyComponent = TRUE;
|
|
}
|
|
|
|
urlPath = lpUrlComponents->lpszUrlPath;
|
|
urlPathLength = lpUrlComponents->dwUrlPathLength;
|
|
if ((urlPath != NULL) && (urlPathLength != 0)) {
|
|
error = ProbeWriteBuffer((LPVOID)urlPath, urlPathLength);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
*urlPath = '\0';
|
|
copyComponent = TRUE;
|
|
}
|
|
|
|
extraInfo = lpUrlComponents->lpszExtraInfo;
|
|
extraInfoLength = lpUrlComponents->dwExtraInfoLength;
|
|
if ((extraInfo != NULL) && (extraInfoLength != 0)) {
|
|
error = ProbeWriteBuffer((LPVOID)extraInfo, extraInfoLength);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
*extraInfo = '\0';
|
|
copyComponent = TRUE;
|
|
}
|
|
|
|
//
|
|
// we can only escape or decode the URL if the caller has provided us with
|
|
// buffers to write the escaped strings into
|
|
//
|
|
|
|
if (dwFlags & (ICU_ESCAPE | ICU_DECODE)) {
|
|
if (!copyComponent) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// create a copy of the URL. CrackUrl() will modify this in situ. We
|
|
// need to copy the results back to the user's buffer(s)
|
|
//
|
|
|
|
urlCopy = NewString((LPSTR)lpszUrl, dwUrlLength);
|
|
if (urlCopy == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
lpUrl = urlCopy;
|
|
} else {
|
|
lpUrl = (LPSTR)lpszUrl;
|
|
urlCopy = NULL;
|
|
}
|
|
|
|
//
|
|
// crack the URL into its constituent parts
|
|
//
|
|
|
|
error = CrackUrl(lpUrl,
|
|
dwUrlLength,
|
|
(dwFlags & ICU_ESCAPE) ? TRUE : FALSE,
|
|
&schemeType,
|
|
&schemeName,
|
|
&schemeNameLength,
|
|
&hostName,
|
|
&hostNameLength,
|
|
&nPort,
|
|
&userName,
|
|
&userNameLength,
|
|
&password,
|
|
&passwordLength,
|
|
&urlPath,
|
|
&urlPathLength,
|
|
extraInfoLength ? &extraInfo : NULL,
|
|
extraInfoLength ? &extraInfoLength : 0,
|
|
&havePort
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto crack_error;
|
|
}
|
|
|
|
BOOL copyFailure;
|
|
|
|
copyFailure = FALSE;
|
|
|
|
//
|
|
// update the URL_COMPONENTS structure based on the results, and what was
|
|
// asked for
|
|
//
|
|
|
|
if (lpUrlComponents->lpszScheme != NULL) {
|
|
if (lpUrlComponents->dwSchemeLength > schemeNameLength) {
|
|
memcpy((LPVOID)lpUrlComponents->lpszScheme,
|
|
(LPVOID)schemeName,
|
|
schemeNameLength
|
|
);
|
|
lpUrlComponents->lpszScheme[schemeNameLength] = '\0';
|
|
// Windows Bug 757146
|
|
// No longer unescaping the scheme. It's just wrong to do so.
|
|
// if (dwFlags & ICU_DECODE) {
|
|
// UrlUnescapeInPlace(lpUrlComponents->lpszScheme, 0);
|
|
// }
|
|
} else {
|
|
++schemeNameLength;
|
|
copyFailure = TRUE;
|
|
}
|
|
lpUrlComponents->dwSchemeLength = schemeNameLength;
|
|
} else if (lpUrlComponents->dwSchemeLength != 0) {
|
|
lpUrlComponents->lpszScheme = schemeName;
|
|
lpUrlComponents->dwSchemeLength = schemeNameLength;
|
|
}
|
|
|
|
if (lpUrlComponents->lpszHostName != NULL) {
|
|
if (lpUrlComponents->dwHostNameLength > hostNameLength) {
|
|
memcpy((LPVOID)lpUrlComponents->lpszHostName,
|
|
(LPVOID)hostName,
|
|
hostNameLength
|
|
);
|
|
lpUrlComponents->lpszHostName[hostNameLength] = '\0';
|
|
// Windows Bug 757146
|
|
// No longer unescaping the host name if ICU_DECODE set.
|
|
// It already has been unescaped once in CrackUrl above.
|
|
// if (dwFlags & ICU_DECODE) {
|
|
// UrlUnescapeInPlace(lpUrlComponents->lpszHostName, 0);
|
|
// }
|
|
} else {
|
|
++hostNameLength;
|
|
copyFailure = TRUE;
|
|
}
|
|
lpUrlComponents->dwHostNameLength = hostNameLength;
|
|
} else if (lpUrlComponents->dwHostNameLength != 0) {
|
|
lpUrlComponents->lpszHostName = hostName;
|
|
lpUrlComponents->dwHostNameLength = hostNameLength;
|
|
}
|
|
|
|
if (lpUrlComponents->lpszUserName != NULL) {
|
|
if (lpUrlComponents->dwUserNameLength > userNameLength) {
|
|
memcpy((LPVOID)lpUrlComponents->lpszUserName,
|
|
(LPVOID)userName,
|
|
userNameLength
|
|
);
|
|
lpUrlComponents->lpszUserName[userNameLength] = '\0';
|
|
// Windows Bug 757146
|
|
// No longer unescaping the user name if ICU_DECODE set.
|
|
// It already has been unescaped once in CrackUrl above.
|
|
// if (dwFlags & ICU_DECODE) {
|
|
// UrlUnescapeInPlace(lpUrlComponents->lpszUserName, 0);
|
|
// }
|
|
} else {
|
|
++userNameLength;
|
|
copyFailure = TRUE;
|
|
}
|
|
lpUrlComponents->dwUserNameLength = userNameLength;
|
|
} else if (lpUrlComponents->dwUserNameLength != 0) {
|
|
lpUrlComponents->lpszUserName = userName;
|
|
lpUrlComponents->dwUserNameLength = userNameLength;
|
|
}
|
|
|
|
if (lpUrlComponents->lpszPassword != NULL) {
|
|
if (lpUrlComponents->dwPasswordLength > passwordLength) {
|
|
memcpy((LPVOID)lpUrlComponents->lpszPassword,
|
|
(LPVOID)password,
|
|
passwordLength
|
|
);
|
|
lpUrlComponents->lpszPassword[passwordLength] = '\0';
|
|
// Windows Bug 757146
|
|
// No longer unescaping the password if ICU_DECODE set.
|
|
// It already has been unescaped once in CrackUrl above.
|
|
// if (dwFlags & ICU_DECODE) {
|
|
// UrlUnescapeInPlace(lpUrlComponents->lpszPassword, 0);
|
|
// }
|
|
} else {
|
|
++passwordLength;
|
|
copyFailure = TRUE;
|
|
}
|
|
lpUrlComponents->dwPasswordLength = passwordLength;
|
|
} else if (lpUrlComponents->dwPasswordLength != 0) {
|
|
lpUrlComponents->lpszPassword = password;
|
|
lpUrlComponents->dwPasswordLength = passwordLength;
|
|
}
|
|
|
|
if (lpUrlComponents->lpszUrlPath != NULL) {
|
|
if(schemeType == INTERNET_SCHEME_FILE)
|
|
{
|
|
//
|
|
// for file: urls we return the path component
|
|
// as a valid dos path.
|
|
//
|
|
|
|
copyFailure = FAILED(PathCreateFromUrl(lpUrl, lpUrlComponents->lpszUrlPath, &(lpUrlComponents->dwUrlPathLength), 0));
|
|
}
|
|
else if (lpUrlComponents->dwUrlPathLength > urlPathLength) {
|
|
memcpy((LPVOID)lpUrlComponents->lpszUrlPath,
|
|
(LPVOID)urlPath,
|
|
urlPathLength
|
|
);
|
|
lpUrlComponents->lpszUrlPath[urlPathLength] = '\0';
|
|
// Windows Bug 757146
|
|
// Unescape the path if only ICU_DECODE is set.
|
|
// ICU_ESCAPE causes the path to be unescaped in
|
|
// CrackUrl above.
|
|
if ((dwFlags & (ICU_DECODE | ICU_ESCAPE)) == ICU_DECODE) {
|
|
UrlUnescapeInPlace(lpUrlComponents->lpszUrlPath, 0);
|
|
}
|
|
lpUrlComponents->dwUrlPathLength = urlPathLength;
|
|
} else {
|
|
++urlPathLength;
|
|
copyFailure = TRUE;
|
|
lpUrlComponents->dwUrlPathLength = urlPathLength;
|
|
}
|
|
} else if (lpUrlComponents->dwUrlPathLength != 0) {
|
|
lpUrlComponents->lpszUrlPath = urlPath;
|
|
lpUrlComponents->dwUrlPathLength = urlPathLength;
|
|
}
|
|
|
|
if (lpUrlComponents->lpszExtraInfo != NULL) {
|
|
if (lpUrlComponents->dwExtraInfoLength > extraInfoLength) {
|
|
memcpy((LPVOID)lpUrlComponents->lpszExtraInfo,
|
|
(LPVOID)extraInfo,
|
|
extraInfoLength
|
|
);
|
|
lpUrlComponents->lpszExtraInfo[extraInfoLength] = '\0';
|
|
// Windows Bug 757146
|
|
// Unescape the extrainfo if only ICU_DECODE is set.
|
|
// ICU_ESCAPE causes the extrainfo to be unescaped in
|
|
// CrackUrl above.
|
|
if ((dwFlags & (ICU_DECODE | ICU_ESCAPE)) == ICU_DECODE) {
|
|
UrlUnescapeInPlace(lpUrlComponents->lpszExtraInfo, 0);
|
|
}
|
|
} else {
|
|
++extraInfoLength;
|
|
copyFailure = TRUE;
|
|
}
|
|
lpUrlComponents->dwExtraInfoLength = extraInfoLength;
|
|
} else if (lpUrlComponents->dwExtraInfoLength != 0) {
|
|
lpUrlComponents->lpszExtraInfo = extraInfo;
|
|
lpUrlComponents->dwExtraInfoLength = extraInfoLength;
|
|
}
|
|
|
|
//
|
|
// we may have failed to copy one or more components because we didn't have
|
|
// enough buffer space.
|
|
//
|
|
// N.B. Don't change error below here. If need be, move this test lower
|
|
//
|
|
|
|
if (copyFailure) {
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
//
|
|
// copy the scheme type
|
|
//
|
|
|
|
lpUrlComponents->nScheme = schemeType;
|
|
|
|
//
|
|
// convert 0 port (not in URL) to default value for scheme
|
|
//
|
|
|
|
if (nPort == INTERNET_INVALID_PORT_NUMBER && !havePort) {
|
|
switch (schemeType) {
|
|
case INTERNET_SCHEME_FTP:
|
|
nPort = INTERNET_DEFAULT_FTP_PORT;
|
|
break;
|
|
|
|
case INTERNET_SCHEME_GOPHER:
|
|
nPort = INTERNET_DEFAULT_GOPHER_PORT;
|
|
break;
|
|
|
|
case INTERNET_SCHEME_HTTP:
|
|
nPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
break;
|
|
|
|
case INTERNET_SCHEME_HTTPS:
|
|
nPort = INTERNET_DEFAULT_HTTPS_PORT;
|
|
break;
|
|
}
|
|
}
|
|
lpUrlComponents->nPort = nPort;
|
|
|
|
crack_error:
|
|
|
|
if (urlCopy != NULL) {
|
|
DEL_STRING(urlCopy);
|
|
}
|
|
|
|
quit:
|
|
BOOL success = (error==ERROR_SUCCESS);
|
|
|
|
if (!success) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
return success;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetCreateUrlA(
|
|
IN LPURL_COMPONENTSA lpUrlComponents,
|
|
IN DWORD dwFlags,
|
|
OUT LPSTR lpszUrl OPTIONAL,
|
|
IN OUT LPDWORD lpdwUrlLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates an URL from its constituent parts
|
|
|
|
Arguments:
|
|
|
|
lpUrlComponents - pointer to URL_COMPONENTS structure containing pointers
|
|
and lengths of components of interest
|
|
|
|
dwFlags - flags controlling function:
|
|
|
|
ICU_ESCAPE - the components contain characters that
|
|
must be escaped in the output URL
|
|
|
|
lpszUrl - pointer to buffer where output URL will be written
|
|
|
|
lpdwUrlLength - IN: number of bytes in lpszUrl buffer
|
|
OUT: if success, number of characters in lpszUrl, else
|
|
number of bytes required for buffer
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - URL written to lpszUrl
|
|
|
|
Failure - call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetCreateUrlA",
|
|
"%#x, %#x, %#x, %#x",
|
|
lpUrlComponents,
|
|
dwFlags,
|
|
lpszUrl,
|
|
lpdwUrlLength
|
|
));
|
|
|
|
#if INET_DEBUG
|
|
|
|
LPSTR lpszUrlOriginal = lpszUrl;
|
|
|
|
#endif
|
|
|
|
DWORD error = ERROR_SUCCESS;
|
|
LPSTR encodedUserName = NULL;
|
|
LPSTR encodedPassword = NULL;
|
|
LPSTR encodedHostName = NULL;
|
|
LPSTR encodedUrlPath = NULL;
|
|
LPSTR encodedExtraInfo = NULL;
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
if ((lpUrlComponents == NULL)
|
|
|| (lpUrlComponents->dwStructSize != sizeof(*lpUrlComponents))
|
|
|| (dwFlags & ~(ICU_ESCAPE | ICU_USERNAME | ICU_ESCAPE_AUTHORITY))
|
|
|| (lpdwUrlLength == NULL)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(lpszUrl)) {
|
|
*lpdwUrlLength = 0;
|
|
}
|
|
|
|
//
|
|
// allocate large buffers from heap
|
|
//
|
|
|
|
encodedHostName = (LPSTR)ALLOCATE_MEMORY(LMEM_FIXED, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
encodedUserName = (LPSTR)ALLOCATE_MEMORY(LMEM_FIXED, INTERNET_MAX_USER_NAME_LENGTH + 1);
|
|
encodedPassword = (LPSTR)ALLOCATE_MEMORY(LMEM_FIXED, INTERNET_MAX_PASSWORD_LENGTH + 1);
|
|
encodedUrlPath = (LPSTR)ALLOCATE_MEMORY(LMEM_FIXED, INTERNET_MAX_URL_LENGTH + 1);
|
|
encodedExtraInfo = (LPSTR)ALLOCATE_MEMORY(LMEM_FIXED, INTERNET_MAX_URL_LENGTH + 1);
|
|
if ((encodedHostName == NULL) ||
|
|
(encodedUserName == NULL) ||
|
|
(encodedPassword == NULL) ||
|
|
(encodedUrlPath == NULL) ||
|
|
(encodedExtraInfo == NULL)
|
|
) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// if we get an exception, we return ERROR_INVALID_PARAMETER
|
|
//
|
|
|
|
__try {
|
|
|
|
//
|
|
// get the individual components to copy
|
|
//
|
|
|
|
LPSTR schemeName;
|
|
DWORD schemeNameLength;
|
|
DWORD schemeFlags;
|
|
LPSTR hostName;
|
|
DWORD hostNameLength;
|
|
DWORD encodedHostNameLength;
|
|
INTERNET_PORT nPort;
|
|
DWORD portLength;
|
|
LPSTR userName;
|
|
DWORD userNameLength;
|
|
DWORD encodedUserNameLength;
|
|
LPSTR password;
|
|
DWORD passwordLength;
|
|
DWORD encodedPasswordLength;
|
|
LPSTR urlPath;
|
|
DWORD urlPathLength;
|
|
DWORD extraLength;
|
|
DWORD encodedUrlPathLength;
|
|
LPSTR extraInfo;
|
|
DWORD extraInfoLength;
|
|
DWORD encodedExtraInfoLength;
|
|
LPSTR schemeSep;
|
|
DWORD schemeSepLength;
|
|
INTERNET_SCHEME schemeType;
|
|
INTERNET_PORT defaultPort;
|
|
|
|
//
|
|
// if the scheme name is absent then we use the default
|
|
//
|
|
|
|
schemeName = lpUrlComponents->lpszScheme;
|
|
schemeType = lpUrlComponents->nScheme;
|
|
|
|
if (schemeName == NULL) {
|
|
if (schemeType == INTERNET_SCHEME_DEFAULT){
|
|
schemeName = DEFAULT_URL_SCHEME_NAME;
|
|
schemeNameLength = sizeof(DEFAULT_URL_SCHEME_NAME) - 1;
|
|
}
|
|
else {
|
|
schemeName = MapUrlScheme(schemeType, &schemeNameLength);
|
|
}
|
|
} else {
|
|
schemeNameLength = lpUrlComponents->dwSchemeLength;
|
|
if (schemeNameLength == 0) {
|
|
schemeNameLength = lstrlen(schemeName);
|
|
}
|
|
}
|
|
|
|
//
|
|
// determine the schemeFlags for possible use in encoding below
|
|
//
|
|
|
|
schemeFlags = 0;
|
|
if (strnicmp(schemeName, "http", schemeNameLength) == 0) {
|
|
schemeFlags = SCHEME_HTTP;
|
|
} else if (strnicmp(schemeName, "ftp", schemeNameLength) == 0) {
|
|
schemeFlags = SCHEME_FTP;
|
|
} else if (strnicmp(schemeName, "gopher", schemeNameLength) == 0) {
|
|
schemeFlags = SCHEME_GOPHER;
|
|
}
|
|
|
|
//
|
|
// doesn't have to be a host name
|
|
//
|
|
|
|
hostName = lpUrlComponents->lpszHostName;
|
|
portLength = 0;
|
|
if (hostName != NULL) {
|
|
hostNameLength = lpUrlComponents->dwHostNameLength;
|
|
if (hostNameLength == 0) {
|
|
hostNameLength = lstrlen(hostName);
|
|
}
|
|
// Windows Bugs 625684
|
|
// encode hostname if ICU_ESCAPE_AUTHORITY is set
|
|
if (dwFlags & ICU_ESCAPE_AUTHORITY) {
|
|
encodedHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
error = EncodeAuthorityComponent(
|
|
hostName,
|
|
hostNameLength,
|
|
encodedHostName,
|
|
&encodedHostNameLength
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
hostName = encodedHostName;
|
|
hostNameLength = encodedHostNameLength;
|
|
}
|
|
}
|
|
//
|
|
// if the port is default then we don't add it to the URL, else we need to
|
|
// copy it as a string
|
|
//
|
|
// there won't be a port unless there's host.
|
|
|
|
schemeType = MapUrlSchemeName(schemeName, schemeNameLength ? schemeNameLength : -1);
|
|
switch (schemeType) {
|
|
case INTERNET_SCHEME_FTP:
|
|
defaultPort = INTERNET_DEFAULT_FTP_PORT;
|
|
break;
|
|
|
|
case INTERNET_SCHEME_GOPHER:
|
|
defaultPort = INTERNET_DEFAULT_GOPHER_PORT;
|
|
break;
|
|
|
|
case INTERNET_SCHEME_HTTP:
|
|
defaultPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
break;
|
|
|
|
case INTERNET_SCHEME_HTTPS:
|
|
defaultPort = INTERNET_DEFAULT_HTTPS_PORT;
|
|
break;
|
|
|
|
default:
|
|
defaultPort = INTERNET_INVALID_PORT_NUMBER;
|
|
break;
|
|
}
|
|
|
|
if (lpUrlComponents->nPort != defaultPort) {
|
|
|
|
INTERNET_PORT divisor;
|
|
|
|
nPort = lpUrlComponents->nPort;
|
|
if (nPort) {
|
|
divisor = 10000;
|
|
portLength = 6; // max is 5 characters, plus 1 for ':'
|
|
while ((nPort / divisor) == 0) {
|
|
--portLength;
|
|
divisor /= 10;
|
|
}
|
|
} else {
|
|
portLength = 2; // port is ":0"
|
|
}
|
|
}
|
|
} else {
|
|
hostNameLength = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// doesn't have to be a user name
|
|
//
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
|
|
userName = lpUrlComponents->lpszUserName;
|
|
if (userName != NULL) {
|
|
userNameLength = lpUrlComponents->dwUserNameLength;
|
|
if (userNameLength == 0) {
|
|
userNameLength = lstrlen(userName);
|
|
}
|
|
// Windows Bugs 625684
|
|
// encode hostname if ICU_ESCAPE_AUTHORITY is set
|
|
if (dwFlags & ICU_ESCAPE_AUTHORITY) {
|
|
encodedUserNameLength = INTERNET_MAX_USER_NAME_LENGTH + 1;
|
|
error = EncodeAuthorityComponent(
|
|
userName,
|
|
userNameLength,
|
|
encodedUserName,
|
|
&encodedUserNameLength
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
userName = encodedUserName;
|
|
userNameLength = encodedUserNameLength;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// BUGBUG - if ICU_USERNAME then we get the value from the registry
|
|
//
|
|
|
|
userNameLength = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// doesn't have to be a password
|
|
//
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
password = lpUrlComponents->lpszPassword;
|
|
if (password != NULL) {
|
|
passwordLength = lpUrlComponents->dwPasswordLength;
|
|
if (passwordLength == 0) {
|
|
passwordLength = lstrlen(password);
|
|
}
|
|
// Windows Bugs 625684
|
|
// encode hostname if ICU_ESCAPE_AUTHORITY is set
|
|
if (dwFlags & ICU_ESCAPE_AUTHORITY) {
|
|
encodedPasswordLength = INTERNET_MAX_PASSWORD_LENGTH + 1;
|
|
error = EncodeAuthorityComponent(
|
|
password,
|
|
passwordLength,
|
|
encodedPassword,
|
|
&encodedPasswordLength
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
password = encodedPassword;
|
|
passwordLength = encodedPasswordLength;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// BUGBUG - if ICU_USERNAME then we get the value from the registry
|
|
//
|
|
|
|
passwordLength = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// but if there's a password without a user name, then its an error
|
|
//
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
|
|
if (password && !userName) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
} else {
|
|
|
|
|
|
//
|
|
// doesn't have to be an URL-path. Empty string is default
|
|
//
|
|
|
|
urlPath = lpUrlComponents->lpszUrlPath;
|
|
if (urlPath != NULL) {
|
|
urlPathLength = lpUrlComponents->dwUrlPathLength;
|
|
if (urlPathLength == 0) {
|
|
urlPathLength = lstrlen(urlPath);
|
|
}
|
|
if ((*urlPath != '/') && (*urlPath != '\\')) {
|
|
extraLength = 1;
|
|
} else {
|
|
extraLength = 0;
|
|
}
|
|
|
|
//
|
|
// if requested, we will encode the URL-path
|
|
//
|
|
|
|
if (dwFlags & ICU_ESCAPE) {
|
|
|
|
//
|
|
// only encode the URL-path if it's a recognized scheme
|
|
//
|
|
|
|
if (schemeFlags != 0) {
|
|
encodedUrlPathLength = INTERNET_MAX_URL_LENGTH + 1;
|
|
error = EncodeUrlPath(NO_ENCODE_PATH_SEP,
|
|
schemeFlags,
|
|
urlPath,
|
|
urlPathLength,
|
|
encodedUrlPath,
|
|
&encodedUrlPathLength
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
urlPath = encodedUrlPath;
|
|
urlPathLength = encodedUrlPathLength;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
urlPathLength = 0;
|
|
extraLength = 0;
|
|
}
|
|
|
|
//
|
|
// handle extra info if present
|
|
//
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
extraInfo = lpUrlComponents->lpszExtraInfo;
|
|
if (extraInfo != NULL) {
|
|
extraInfoLength = lpUrlComponents->dwExtraInfoLength;
|
|
if (extraInfoLength == 0) {
|
|
extraInfoLength = lstrlen(extraInfo);
|
|
}
|
|
|
|
//
|
|
// if requested, we will encode the extra info
|
|
//
|
|
|
|
if (dwFlags & ICU_ESCAPE) {
|
|
|
|
//
|
|
// only encode the extra info if it's a recognized scheme
|
|
//
|
|
|
|
if (schemeFlags != 0) {
|
|
encodedExtraInfoLength = INTERNET_MAX_URL_LENGTH + 1;
|
|
error = EncodeUrlPath(0,
|
|
schemeFlags,
|
|
extraInfo,
|
|
extraInfoLength,
|
|
encodedExtraInfo,
|
|
&encodedExtraInfoLength
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
extraInfo = encodedExtraInfo;
|
|
extraInfoLength = encodedExtraInfoLength;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
extraInfoLength = 0;
|
|
}
|
|
}
|
|
|
|
DWORD requiredSize;
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Determine if we have a protocol scheme that requires slashes
|
|
//
|
|
|
|
if (DoesSchemeRequireSlashes(schemeName, schemeNameLength, (hostName != NULL))
|
|
|| ((schemeType == INTERNET_SCHEME_NEWS)
|
|
&& urlPath
|
|
&& (strchr(urlPath, '/') || strchr(urlPath, '\\')))) {
|
|
schemeSep = "://";
|
|
schemeSepLength = sizeof("://") - 1;
|
|
} else {
|
|
schemeSep = ":";
|
|
schemeSepLength = sizeof(":") - 1;
|
|
}
|
|
|
|
//
|
|
// ensure we have enough buffer space
|
|
//
|
|
|
|
requiredSize = schemeNameLength
|
|
+ schemeSepLength
|
|
+ hostNameLength
|
|
+ portLength
|
|
+ (userName ? userNameLength + 1 : 0) // +1 for '@'
|
|
+ (password ? passwordLength + 1 : 0) // +1 for ':'
|
|
+ urlPathLength
|
|
+ extraLength
|
|
+ extraInfoLength
|
|
+ 1 // +1 for '\0'
|
|
;
|
|
|
|
//
|
|
// if there is enough buffer, copy the URL
|
|
//
|
|
|
|
if (*lpdwUrlLength >= requiredSize) {
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)schemeName, schemeNameLength);
|
|
lpszUrl += schemeNameLength;
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)schemeSep, schemeSepLength);
|
|
lpszUrl += schemeSepLength;
|
|
if (userName) {
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)userName, userNameLength);
|
|
lpszUrl += userNameLength;
|
|
if (password) {
|
|
*lpszUrl++ = ':';
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)password, passwordLength);
|
|
lpszUrl += passwordLength;
|
|
}
|
|
*lpszUrl++ = '@';
|
|
}
|
|
if (hostName) {
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)hostName, hostNameLength);
|
|
lpszUrl += hostNameLength;
|
|
|
|
// We won't attach a port unless there's a host to go with it.
|
|
if (portLength) {
|
|
lpszUrl += wsprintf(lpszUrl, ":%d", nPort & 0xffff);
|
|
}
|
|
|
|
}
|
|
if (urlPath) {
|
|
|
|
//
|
|
// Only do extraLength if we've actually copied something
|
|
// after the scheme. Also, don't copy slash if it's
|
|
// mailto:
|
|
//
|
|
|
|
if (extraLength != 0 && (userName || hostName || portLength) &&
|
|
schemeType != INTERNET_SCHEME_MAILTO) {
|
|
*lpszUrl++ = '/';
|
|
} else if (extraLength != 0) {
|
|
--requiredSize;
|
|
}
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)urlPath, urlPathLength);
|
|
lpszUrl += urlPathLength;
|
|
} else if (extraLength != 0) {
|
|
--requiredSize;
|
|
}
|
|
if (extraInfo) {
|
|
memcpy((LPVOID)lpszUrl, (LPVOID)extraInfo, extraInfoLength);
|
|
lpszUrl += extraInfoLength;
|
|
}
|
|
|
|
//
|
|
// terminate string
|
|
//
|
|
|
|
*lpszUrl = '\0';
|
|
|
|
//
|
|
// -1 for terminating '\0'
|
|
//
|
|
|
|
--requiredSize;
|
|
} else {
|
|
|
|
//
|
|
// not enough buffer space - just return the required buffer length
|
|
//
|
|
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// update returned parameters
|
|
//
|
|
|
|
*lpdwUrlLength = requiredSize;
|
|
}
|
|
}
|
|
|
|
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
ENDEXCEPT
|
|
quit:
|
|
|
|
//
|
|
// clear up the buffers we allocated
|
|
//
|
|
if (encodedUserName != NULL) {
|
|
FREE_MEMORY(encodedUserName);
|
|
}
|
|
if (encodedPassword != NULL) {
|
|
FREE_MEMORY(encodedPassword);
|
|
}
|
|
if (encodedHostName != NULL) {
|
|
FREE_MEMORY(encodedHostName);
|
|
}
|
|
if (encodedUrlPath != NULL) {
|
|
FREE_MEMORY(encodedUrlPath);
|
|
}
|
|
if (encodedExtraInfo != NULL) {
|
|
FREE_MEMORY(encodedExtraInfo);
|
|
}
|
|
|
|
BOOL success = (error==ERROR_SUCCESS);
|
|
|
|
if (success) {
|
|
|
|
DEBUG_PRINT_API(API,
|
|
INFO,
|
|
("URL = %q\n",
|
|
lpszUrlOriginal
|
|
));
|
|
} else {
|
|
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
return success;
|
|
}
|
|
|
|
//
|
|
// ICUHrToWin32Error() is specifically for converting the return codes for
|
|
// Url* APIs in shlwapi into win32 errors.
|
|
// WARNING: it should not be used for any other purpose.
|
|
//
|
|
DWORD
|
|
ICUHrToWin32Error(HRESULT hr)
|
|
{
|
|
DWORD err = ERROR_INVALID_PARAMETER;
|
|
switch(hr)
|
|
{
|
|
case E_OUTOFMEMORY:
|
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
|
|
case E_POINTER:
|
|
err = ERROR_INSUFFICIENT_BUFFER;
|
|
break;
|
|
|
|
case S_OK:
|
|
err = ERROR_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetCanonicalizeUrlA(
|
|
IN LPCSTR lpszUrl,
|
|
OUT LPSTR lpszBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Combines a relative URL with a base URL to form a new full URL.
|
|
|
|
Arguments:
|
|
|
|
lpszUrl - pointer to URL to be canonicalize
|
|
lpszBuffer - pointer to buffer where new URL is written
|
|
lpdwBufferLength - size of buffer on entry, length of new URL on exit
|
|
dwFlags - flags controlling operation
|
|
|
|
Return Value:
|
|
|
|
BOOL - TRUE if successful, FALSE if not
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetCanonicalizeUrlA",
|
|
"%q, %#x, %#x [%d], %#x",
|
|
lpszUrl,
|
|
lpszBuffer,
|
|
lpdwBufferLength,
|
|
lpdwBufferLength ? *lpdwBufferLength : 0,
|
|
dwFlags
|
|
));
|
|
|
|
HRESULT hr ;
|
|
BOOL bRet = TRUE;;
|
|
|
|
INET_ASSERT(lpszUrl);
|
|
INET_ASSERT(lpszBuffer);
|
|
INET_ASSERT(lpdwBufferLength && (*lpdwBufferLength > 0));
|
|
|
|
//
|
|
// the flags for the Url* APIs in shlwapi should be the same
|
|
// except that NO_ENCODE is on by default. so we need to flip it
|
|
//
|
|
dwFlags ^= ICU_NO_ENCODE;
|
|
|
|
// Check for invalid parameters
|
|
|
|
if (!lpszUrl || !lpszBuffer || !lpdwBufferLength || *lpdwBufferLength == 0 || IsBadWritePtr(lpszBuffer, *lpdwBufferLength*sizeof(CHAR)))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
hr = UrlCanonicalizeA(lpszUrl, lpszBuffer,
|
|
lpdwBufferLength, dwFlags | URL_WININET_COMPATIBILITY);
|
|
}
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
DWORD dw = ICUHrToWin32Error(hr);
|
|
|
|
bRet = FALSE;
|
|
|
|
DEBUG_ERROR(API, dw);
|
|
|
|
SetLastError(dw);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetCombineUrlA(
|
|
IN LPCSTR lpszBaseUrl,
|
|
IN LPCSTR lpszRelativeUrl,
|
|
OUT LPSTR lpszBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Combines a relative URL with a base URL to form a new full URL.
|
|
|
|
Arguments:
|
|
|
|
lpszBaseUrl - pointer to base URL
|
|
lpszRelativeUrl - pointer to relative URL
|
|
lpszBuffer - pointer to buffer where new URL is written
|
|
lpdwBufferLength - size of buffer on entry, length of new URL on exit
|
|
dwFlags - flags controlling operation
|
|
|
|
Return Value:
|
|
|
|
BOOL - TRUE if successful, FALSE if not
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetCombineUrlA",
|
|
"%q, %q, %#x, %#x [%d], %#x",
|
|
lpszBaseUrl,
|
|
lpszRelativeUrl,
|
|
lpszBuffer,
|
|
lpdwBufferLength,
|
|
lpdwBufferLength ? *lpdwBufferLength : 0,
|
|
dwFlags
|
|
));
|
|
|
|
HRESULT hr ;
|
|
BOOL bRet;
|
|
|
|
INET_ASSERT(lpszBaseUrl);
|
|
INET_ASSERT(lpszRelativeUrl);
|
|
INET_ASSERT(lpdwBufferLength);
|
|
|
|
//
|
|
// the flags for the Url* APIs in shlwapi should be the same
|
|
// except that NO_ENCODE is on by default. so we need to flip it
|
|
//
|
|
dwFlags ^= ICU_NO_ENCODE;
|
|
|
|
// Check for invalid parameters
|
|
|
|
if (!lpszBaseUrl || !lpszRelativeUrl || !lpdwBufferLength || (lpszBuffer && IsBadWritePtr(lpszBuffer, *lpdwBufferLength*sizeof(CHAR))))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
hr = UrlCombineA(lpszBaseUrl, lpszRelativeUrl, lpszBuffer,
|
|
lpdwBufferLength, dwFlags | URL_WININET_COMPATIBILITY);
|
|
}
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
DWORD dw = ICUHrToWin32Error(hr);
|
|
|
|
bRet = FALSE;
|
|
|
|
DEBUG_ERROR(API, dw);
|
|
|
|
SetLastError(dw);
|
|
}
|
|
else
|
|
bRet = TRUE;
|
|
|
|
IF_DEBUG_CODE() {
|
|
if (bRet) {
|
|
DEBUG_PRINT_API(API,
|
|
INFO,
|
|
("URL = %q\n",
|
|
lpszBuffer
|
|
));
|
|
}
|
|
}
|
|
|
|
DEBUG_LEAVE_API(bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
|
|
INTERNETAPI_(HINTERNET) InternetOpenA(
|
|
IN LPCSTR lpszAgent,
|
|
IN DWORD dwAccessType,
|
|
IN LPCSTR lpszProxy OPTIONAL,
|
|
IN LPCSTR lpszProxyBypass OPTIONAL,
|
|
IN DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a root Internet handle from which all HINTERNET objects are derived
|
|
|
|
Arguments:
|
|
|
|
lpszAgent - name of the application making the request (arbitrary
|
|
identifying string). Used in "User-Agent" header when
|
|
communicating with HTTP servers, if the application does
|
|
not add a User-Agent header of its own
|
|
|
|
dwAccessType - type of access required. Can be
|
|
|
|
INTERNET_OPEN_TYPE_PRECONFIG
|
|
- Gets the configuration from the registry
|
|
|
|
INTERNET_OPEN_TYPE_DIRECT
|
|
- Requests are made directly to the nominated server
|
|
|
|
INTERNET_OPEN_TYPE_PROXY
|
|
- Requests are made via the nominated proxy
|
|
|
|
INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY
|
|
- Like Pre-Config, but prevents JavaScript, INS
|
|
and other auto-proxy types from being used.
|
|
|
|
lpszProxy - if INTERNET_OPEN_TYPE_PROXY, a list of proxy servers to
|
|
use
|
|
|
|
lpszProxyBypass - if INTERNET_OPEN_TYPE_PROXY, a list of servers which we
|
|
will communicate with directly
|
|
|
|
dwFlags - flags to control the operation of this API or potentially
|
|
all APIs called on the handle generated by this API.
|
|
Currently supported are:
|
|
|
|
INTERNET_FLAG_ASYNC
|
|
- if specified then all subsequent API calls made
|
|
against the handle returned from this API, or
|
|
handles descended from the handle returned by
|
|
this API, have the opportunity to complete
|
|
asynchronously, depending on other factors
|
|
relevant at the time the API is called
|
|
|
|
Return Value:
|
|
|
|
HINTERNET
|
|
Success - handle of Internet object
|
|
|
|
Failure - NULL. For more information, call GetLastError()
|
|
|
|
--*/
|
|
|
|
{
|
|
PERF_INIT();
|
|
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Handle,
|
|
"InternetOpenA",
|
|
"%q, %s (%d), %q, %q, %#x",
|
|
lpszAgent,
|
|
InternetMapOpenType(dwAccessType),
|
|
dwAccessType,
|
|
lpszProxy,
|
|
lpszProxyBypass,
|
|
dwFlags
|
|
));
|
|
|
|
DWORD error;
|
|
HINTERNET hInternet = NULL;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = GlobalDataInitialize();
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// we are doing GetUserName here instead of in DLL_PROCESS_ATTACH
|
|
// As every caller of wininet has to do this first, we ensure
|
|
// that the username is initialized when they get to actually doing
|
|
// any real operation
|
|
//
|
|
|
|
GetWininetUserName();
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
if (!
|
|
(
|
|
(dwAccessType == INTERNET_OPEN_TYPE_DIRECT)
|
|
|| (dwAccessType == INTERNET_OPEN_TYPE_PROXY)
|
|
|| (dwAccessType == INTERNET_OPEN_TYPE_PRECONFIG)
|
|
|| (dwAccessType == INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY)
|
|
|| (
|
|
(dwAccessType == INTERNET_OPEN_TYPE_PROXY)
|
|
&&
|
|
(
|
|
!ARGUMENT_PRESENT(lpszProxy)
|
|
|| (*lpszProxy == '\0')
|
|
|
|
)
|
|
)
|
|
|| (dwFlags & ~INTERNET_FLAGS_MASK)
|
|
)
|
|
)
|
|
{
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
|
|
GlobalHaveInternetOpened = TRUE;
|
|
|
|
//
|
|
// Initalize an auto proxy dll if needed,
|
|
// as long as the caller is allowing us free rein to do this
|
|
// by calling us with INTERNET_OPEN_TYPE_PRECONFIG.
|
|
//
|
|
|
|
//if ( dwAccessType == INTERNET_OPEN_TYPE_PRECONFIG )
|
|
//{
|
|
// if ( ! InitalizeAutoConfigDllIfNeeded() )
|
|
// {
|
|
// error = GetLastError();
|
|
//
|
|
// INET_ASSERT(error != ERROR_SUCCESS);
|
|
//
|
|
// goto quit;
|
|
// }
|
|
//
|
|
//
|
|
|
|
|
|
INTERNET_HANDLE_OBJECT * lpInternet;
|
|
|
|
lpInternet = new INTERNET_HANDLE_OBJECT(lpszAgent,
|
|
dwAccessType,
|
|
(LPSTR)lpszProxy,
|
|
(LPSTR)lpszProxyBypass,
|
|
dwFlags
|
|
);
|
|
if (lpInternet == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
error = lpInternet->GetStatus();
|
|
if (error == ERROR_SUCCESS) {
|
|
hInternet = (HINTERNET)lpInternet;
|
|
|
|
//
|
|
// success - don't return the object address, return the pseudo-handle
|
|
// value we generated
|
|
//
|
|
|
|
hInternet = ((HANDLE_OBJECT *)hInternet)->GetPseudoHandle();
|
|
|
|
//
|
|
// start async support now if required. If we can't start it, we'll get
|
|
// another chance the next time we create an async request
|
|
//
|
|
|
|
if (dwFlags & INTERNET_FLAG_ASYNC) {
|
|
InitializeAsyncSupport();
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// hack fix to stop InternetIndicateStatus (called from the handle
|
|
// object destructor) blowing up if there is no handle object in the
|
|
// thread info block. We can't call back anyway
|
|
//
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo();
|
|
|
|
if (lpThreadInfo) {
|
|
|
|
//
|
|
// BUGBUG - incorrect handle value
|
|
//
|
|
|
|
_InternetSetObjectHandle(lpThreadInfo, lpInternet, lpInternet);
|
|
}
|
|
|
|
//
|
|
// we failed during initialization. Kill the handle using Dereference()
|
|
// (in order to stop the debug version complaining about the reference
|
|
// count not being 0. Invalidate for same reason)
|
|
//
|
|
|
|
lpInternet->Invalidate();
|
|
lpInternet->Dereference();
|
|
|
|
INET_ASSERT(hInternet == NULL);
|
|
|
|
}
|
|
|
|
quit:
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
DEBUG_ERROR(API, error);
|
|
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(hInternet);
|
|
|
|
return hInternet;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetCloseHandle(
|
|
IN HINTERNET hInternet
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Closes any open internet handle object
|
|
|
|
Arguments:
|
|
|
|
hInternet - handle of internet object to close
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. For more information call GetLastError()
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetCloseHandle",
|
|
"%#x",
|
|
hInternet
|
|
));
|
|
|
|
PERF_ENTER(InternetCloseHandle);
|
|
|
|
DWORD error;
|
|
BOOL success = FALSE;
|
|
HINTERNET hInternetMapped = NULL;
|
|
static DWORD ticks = GetTickCountWrap();
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = GlobalDataInitialize();
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// map the handle. Don't invalidate it (_InternetCloseHandle() does this)
|
|
//
|
|
|
|
error = MapHandleToAddress(hInternet, (LPVOID *)&hInternetMapped, FALSE);
|
|
if (error != ERROR_SUCCESS) {
|
|
if (hInternetMapped == NULL) {
|
|
|
|
//
|
|
// the handle never existed or has been completely destroyed
|
|
//
|
|
|
|
DEBUG_PRINT(API,
|
|
ERROR,
|
|
("Handle %#x is invalid\n",
|
|
hInternet
|
|
));
|
|
|
|
//
|
|
// catch invalid handles - may help caller
|
|
//
|
|
|
|
DEBUG_BREAK(INVALID_HANDLES);
|
|
|
|
} else {
|
|
|
|
//
|
|
// this handle is already being closed (it's invalidated). We only
|
|
// need one InternetCloseHandle() operation to invalidate the handle.
|
|
// All other threads will simply dereference the handle, and
|
|
// eventually it will be destroyed
|
|
//
|
|
|
|
DereferenceObject((LPVOID)hInternetMapped);
|
|
}
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// the handle is not invalidated
|
|
//
|
|
|
|
HANDLE_OBJECT * pHandle;
|
|
|
|
pHandle = (HANDLE_OBJECT *)hInternetMapped;
|
|
|
|
if ( ! ((INTERNET_HANDLE_OBJECT *)hInternetMapped)->IsAsyncHandle() )
|
|
{
|
|
if ((GetTickCountWrap() - ticks) >= 5000)
|
|
{
|
|
PurgeServerInfoList(FALSE);
|
|
|
|
InterlockedExchange((LPLONG) &ticks, (LONG) GetTickCountWrap());
|
|
}
|
|
}
|
|
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("handle %#x == %#x == %s\n",
|
|
hInternet,
|
|
hInternetMapped,
|
|
InternetMapHandleType(pHandle->GetHandleType())
|
|
));
|
|
|
|
//
|
|
// if this is an http request handle, notify all filters.
|
|
//
|
|
|
|
if (pHandle->GetHandleType() == TypeHttpRequestHandle) {
|
|
HttpFiltOnTransactionComplete (hInternet);
|
|
}
|
|
|
|
//
|
|
// if this is a delete-parent-with-child subtree then find the root node
|
|
//
|
|
|
|
while (pHandle->GetDeleteWithChild()) {
|
|
|
|
HINTERNET handleObject;
|
|
|
|
handleObject = pHandle->GetParent();
|
|
|
|
INET_ASSERT(handleObject != NULL);
|
|
|
|
//
|
|
// remove the delete-parent-with-child indication, or we'll get stuck
|
|
// in a loop
|
|
//
|
|
|
|
pHandle->SetParent(handleObject, FALSE);
|
|
|
|
//
|
|
// if the parent handle is an EXISTING_CONNECT connect handle then we
|
|
// just mark it unused & close the current handle
|
|
//
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
|
|
handleType = ((HANDLE_OBJECT *)handleObject)->GetHandleType();
|
|
if ((handleType == TypeFtpConnectHandle)
|
|
|| (handleType == TypeGopherConnectHandle)
|
|
|| (handleType == TypeHttpConnectHandle)) {
|
|
|
|
INTERNET_CONNECT_HANDLE_OBJECT * pConnect;
|
|
|
|
pConnect = (INTERNET_CONNECT_HANDLE_OBJECT *)handleObject;
|
|
|
|
//
|
|
// SetUnused() will only operate on a connect handle object that
|
|
// has been created with INTERNET_FLAG_EXISTING_CONNECT
|
|
//
|
|
|
|
if (pConnect->SetUnused()) {
|
|
|
|
//
|
|
// only handle type should be FTP connect handle for now
|
|
//
|
|
|
|
INET_ASSERT(handleType == TypeFtpConnectHandle);
|
|
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("caching unused %s connect handle object %#x. RefCount = %d\n",
|
|
(handleType == TypeFtpConnectHandle)
|
|
? "FTP"
|
|
: (handleType == TypeGopherConnectHandle)
|
|
? "Gopher"
|
|
: "HTTP",
|
|
((HANDLE_OBJECT *)handleObject)->GetPseudoHandle(),
|
|
((HANDLE_OBJECT *)handleObject)->ReferenceCount()
|
|
));
|
|
break;
|
|
}
|
|
}
|
|
pHandle = (HANDLE_OBJECT *)handleObject;
|
|
hInternet = pHandle->GetPseudoHandle();
|
|
}
|
|
|
|
//
|
|
// close all child handles first
|
|
//
|
|
|
|
while (pHandle->HaveChildren()) {
|
|
|
|
//
|
|
// we'll fall out at the first error we get. It *should* mean that this
|
|
// handle (and its descendents) is already being closed
|
|
//
|
|
|
|
if (!InternetCloseHandle(pHandle->NextChild())) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// clear the handle object last error variables
|
|
//
|
|
|
|
InternetClearLastError();
|
|
|
|
|
|
//
|
|
// remove the reference added by MapHandleToAddress(), or the handle won't
|
|
// be destroyed by _InternetCloseHandle()
|
|
//
|
|
|
|
DereferenceObject((LPVOID)hInternetMapped);
|
|
|
|
//
|
|
// use _InternetCloseHandle() to do the work
|
|
//
|
|
|
|
success = _InternetCloseHandle(hInternet);
|
|
|
|
quit:
|
|
|
|
// SetLastError must be called after PERF_LEAVE !!!
|
|
PERF_LEAVE(InternetCloseHandle);
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
BOOL
|
|
_InternetCloseHandle(
|
|
IN HINTERNET hInternet
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Same as InternetCloseHandle() except does not clear out the last error text.
|
|
Mainly for FTP
|
|
|
|
Arguments:
|
|
|
|
hInternet - handle of internet object to close
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. For more information call GetLastError()
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
Bool,
|
|
"_InternetCloseHandle",
|
|
"%#x",
|
|
hInternet
|
|
));
|
|
|
|
DWORD error;
|
|
BOOL success;
|
|
HINTERNET hInternetMapped = NULL;
|
|
LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo();
|
|
|
|
if (lpThreadInfo == NULL) {
|
|
if (InDllCleanup) {
|
|
error = ERROR_INTERNET_SHUTDOWN;
|
|
} else {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
}
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// map the handle and invalidate it. This will cause any new requests with
|
|
// the handle as a parameter to fail
|
|
//
|
|
|
|
error = MapHandleToAddress(hInternet, (LPVOID *)&hInternetMapped, TRUE);
|
|
if (error != ERROR_SUCCESS) {
|
|
if (hInternetMapped != NULL) {
|
|
|
|
//
|
|
// the handle is already being closed, or is already deleted
|
|
//
|
|
|
|
DereferenceObject((LPVOID)hInternetMapped);
|
|
}
|
|
|
|
//
|
|
// since this is the only function that can invalidate a handle, if we
|
|
// are here then the handle is just waiting for its refcount to go to
|
|
// zero. We already removed the refcount we added above, so we're in
|
|
// the clear
|
|
//
|
|
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// there may be an active socket operation. We close the socket to abort the
|
|
// operation
|
|
//
|
|
|
|
((INTERNET_HANDLE_OBJECT *)hInternetMapped)->AbortSocket();
|
|
|
|
//
|
|
// we need the parent handle - we will set this as the handle object being
|
|
// processed by this thread. This is required for async worker threads (see
|
|
// below)
|
|
//
|
|
|
|
HINTERNET hParent;
|
|
HINTERNET hParentMapped;
|
|
DWORD_PTR dwParentContext;
|
|
|
|
hParentMapped = ((HANDLE_OBJECT *)hInternetMapped)->GetParent();
|
|
if (hParentMapped != NULL) {
|
|
hParent = ((HANDLE_OBJECT *)hParentMapped)->GetPseudoHandle();
|
|
dwParentContext = ((HANDLE_OBJECT *)hParentMapped)->GetContext();
|
|
}
|
|
|
|
//
|
|
// set the object handle and context in the per-thread data structure
|
|
//
|
|
|
|
_InternetSetObjectHandle(lpThreadInfo, hInternet, hInternetMapped);
|
|
_InternetSetContext(lpThreadInfo,
|
|
((INTERNET_HANDLE_OBJECT *)hInternetMapped)->GetContext()
|
|
);
|
|
|
|
//
|
|
// at this point, there should *always* be at least 2 references on the
|
|
// handle - one added when the object was created, and one added by
|
|
// MapHandleToAddress() above. If the object is still alive after the 2
|
|
// dereferences, then it will be destroyed when the current owning thread
|
|
// dereferences it
|
|
//
|
|
|
|
(void)DereferenceObject((LPVOID)hInternetMapped);
|
|
error = DereferenceObject((LPVOID)hInternetMapped);
|
|
|
|
//
|
|
// now set the object to be the parent. This is necessary for e.g.
|
|
// FtpGetFile() and async requests (where the async worker thread will make
|
|
// an extra callback to deliver the results of the async request)
|
|
//
|
|
|
|
if (hParentMapped != NULL) {
|
|
_InternetSetObjectHandle(lpThreadInfo, hParent, hParentMapped);
|
|
if (dwParentContext != 0) {
|
|
_InternetSetContext(lpThreadInfo, dwParentContext);
|
|
}
|
|
}
|
|
|
|
if (g_bHibernating)
|
|
{
|
|
InterruptSelect();
|
|
}
|
|
//
|
|
// if the handle was still alive after dereferencing it then we will inform
|
|
// the app that the close is pending
|
|
//
|
|
|
|
quit:
|
|
|
|
//
|
|
// if the handle is still alive then we return success - it is invalidated
|
|
// and will be deleted as soon as possible
|
|
//
|
|
|
|
if (error == ERROR_INTERNET_HANDLE_EXISTS) {
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
success = (error==ERROR_SUCCESS);
|
|
if (!success) {
|
|
SetLastError(error);
|
|
DEBUG_ERROR(INET, error);
|
|
}
|
|
DEBUG_LEAVE(success);
|
|
return success;
|
|
}
|
|
|
|
|
|
DWORD
|
|
_InternetCloseHandleNoContext(
|
|
IN HINTERNET hInternet
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Same as _InternetCloseHandle() except does not change the per-thread info
|
|
structure handle/context values
|
|
|
|
BUGBUG - This should be handled via a parameter to _InternetCloseHandle(),
|
|
but its close to shipping...
|
|
|
|
Arguments:
|
|
|
|
hInternet - handle of internet object to close
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INVALID_HANDLE
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
Bool,
|
|
"_InternetCloseHandleNoContext",
|
|
"%#x",
|
|
hInternet
|
|
));
|
|
|
|
DWORD error;
|
|
HINTERNET hInternetMapped = NULL;
|
|
|
|
//
|
|
// map the handle and invalidate it. This will cause any new requests with
|
|
// the handle as a parameter to fail
|
|
//
|
|
|
|
error = MapHandleToAddress(hInternet, (LPVOID *)&hInternetMapped, TRUE);
|
|
if (error != ERROR_SUCCESS) {
|
|
if (hInternetMapped != NULL) {
|
|
|
|
//
|
|
// the handle is already being closed, or is already deleted
|
|
//
|
|
|
|
DereferenceObject((LPVOID)hInternetMapped);
|
|
}
|
|
|
|
//
|
|
// since this is the only function that can invalidate a handle, if we
|
|
// are here then the handle is just waiting for its refcount to go to
|
|
// zero. We already removed the refcount we added above, so we're in
|
|
// the clear
|
|
//
|
|
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// there may be an active socket operation. We close the socket to abort the
|
|
// operation
|
|
//
|
|
|
|
((INTERNET_HANDLE_OBJECT *)hInternetMapped)->AbortSocket();
|
|
|
|
//
|
|
// at this point, there should *always* be at least 2 references on the
|
|
// handle - one added when the object was created, and one added by
|
|
// MapHandleToAddress() above. If the object is still alive after the 2
|
|
// dereferences, then it will be destroyed when the current owning thread
|
|
// dereferences it
|
|
//
|
|
|
|
(void)DereferenceObject((LPVOID)hInternetMapped);
|
|
error = DereferenceObject((LPVOID)hInternetMapped);
|
|
|
|
quit:
|
|
|
|
//
|
|
// if the handle is still alive then we return success - it is invalidated
|
|
// and will be deleted as soon as possible
|
|
//
|
|
|
|
if (error == ERROR_INTERNET_HANDLE_EXISTS) {
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetGetCertByURLA(
|
|
IN LPSTR lpszURL,
|
|
IN OUT LPSTR lpszCertText,
|
|
OUT DWORD dwcbCertText
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a high-level lookup against the Certificate Cache.
|
|
Searches by URL (broken down into hostname) for the Certificate,
|
|
and returns it in a formatted (&localized) string.
|
|
|
|
Arguments:
|
|
|
|
lpszUrl - pointer to URL to crack
|
|
|
|
lpszCertText - Output of formatted certifcate
|
|
|
|
dwcbCertText - Size of lpszCertText
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
BOOL fSuccess = FALSE;
|
|
|
|
/* LPSTR lpszHostName;
|
|
DWORD dwcbHostName;
|
|
INTERNET_CERTIFICATE_INFO cInfo;
|
|
CHAR chBackup;
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
ZeroMemory(&cInfo, sizeof(INTERNET_CERTIFICATE_INFO));
|
|
|
|
error = CrackUrl(lpszURL,
|
|
lstrlen(lpszURL),
|
|
FALSE,
|
|
NULL, // Scheme Type
|
|
NULL, // Scheme Name
|
|
NULL, // Scheme Length
|
|
&lpszHostName, // Host Name
|
|
&dwcbHostName, // Host Length
|
|
NULL, // Internet Port
|
|
NULL, // UserName
|
|
NULL, // UserName Length
|
|
NULL, // Password
|
|
NULL, // Password Lenth
|
|
NULL, // Path
|
|
NULL, // Path Length
|
|
NULL, // Extra Info
|
|
NULL, // Extra Info Length
|
|
NULL
|
|
);
|
|
|
|
|
|
if ( error != ERROR_SUCCESS)
|
|
goto quit;
|
|
|
|
chBackup = lpszHostName[dwcbHostName];
|
|
lpszHostName[dwcbHostName] = '\0';
|
|
|
|
fSuccess = GlobalCertCache.GetCert(
|
|
lpszHostName,
|
|
&cInfo
|
|
);
|
|
|
|
|
|
lpszHostName[dwcbHostName] = chBackup;
|
|
|
|
if ( ! fSuccess )
|
|
{
|
|
error = ERROR_INTERNET_INVALID_OPERATION;
|
|
goto quit;
|
|
}
|
|
|
|
LPSTR szResult;
|
|
|
|
szResult = FormatCertInfo(&cInfo);
|
|
|
|
if ( ! szResult )
|
|
{
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
DWORD dwcbResult;
|
|
|
|
dwcbResult = lstrlen(szResult);
|
|
|
|
if ( dwcbCertText < (dwcbResult+1) )
|
|
{
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
goto quit;
|
|
}
|
|
|
|
memcpy(
|
|
lpszCertText,
|
|
szResult,
|
|
(dwcbResult + 1) * sizeof(TCHAR));
|
|
|
|
|
|
quit:
|
|
|
|
if (NULL != szResult) {
|
|
FREE_MEMORY(szResult);
|
|
}
|
|
if (NULL != cInfo.lpszSubjectInfo) {
|
|
FREE_MEMORY(cInfo.lpszSubjectInfo);
|
|
}
|
|
if (NULL != cInfo.lpszIssuerInfo) {
|
|
FREE_MEMORY(cInfo.lpszIssuerInfo);
|
|
}
|
|
if (NULL != cInfo.lpszSignatureAlgName) {
|
|
FREE_MEMORY(cInfo.lpszSignatureAlgName);
|
|
}
|
|
if (NULL != cInfo.lpszEncryptionAlgName) {
|
|
FREE_MEMORY(cInfo.lpszEncryptionAlgName);
|
|
}
|
|
if (NULL != cInfo.lpszProtocolName) {
|
|
FREE_MEMORY(cInfo.lpszProtocolName);
|
|
}
|
|
|
|
fSuccess = TRUE;
|
|
|
|
if ( error != ERROR_SUCCESS )
|
|
{
|
|
fSuccess = FALSE;
|
|
SetLastError(error);
|
|
}*/
|
|
|
|
return fSuccess;
|
|
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetShowSecurityInfoByURLA(
|
|
IN LPSTR lpszURL,
|
|
IN HWND hwndRootWindow
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a high-level lookup against the Certificate Cache.
|
|
Searches by URL (broken down into hostname) for the Certificate,
|
|
and returns it in a formatted (&localized) string.
|
|
|
|
Arguments:
|
|
|
|
lpszUrl - pointer to URL to crack
|
|
|
|
lpszCertText - Output of formatted certifcate
|
|
|
|
dwcbCertText - Size of lpszCertText
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_INET,
|
|
Bool,
|
|
"InternetShowSecurityInfoA",
|
|
"%q %#x",
|
|
lpszURL,
|
|
hwndRootWindow
|
|
));
|
|
|
|
LPSTR lpszHostName;
|
|
DWORD dwcbHostName;
|
|
INTERNET_SECURITY_INFO cInfo;
|
|
CHAR chBackup;
|
|
DWORD dwFlags;
|
|
DWORD error = ERROR_SUCCESS;
|
|
WCHAR szTitle[MAX_PATH];
|
|
WCHAR szMessage[MAX_PATH];
|
|
INTERNET_SCHEME ustSchemeType;
|
|
BOOL fResult = FALSE;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
if (GlobalDataInitialize() != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
ZeroMemory(&cInfo, sizeof(INTERNET_SECURITY_INFO));
|
|
|
|
error = CrackUrl(lpszURL,
|
|
lstrlen(lpszURL),
|
|
FALSE,
|
|
&ustSchemeType,
|
|
NULL, // Scheme Name
|
|
NULL, // Scheme Length
|
|
&lpszHostName, // Host Name
|
|
&dwcbHostName, // Host Length
|
|
NULL, // Internet Port
|
|
NULL, // UserName
|
|
NULL, // UserName Length
|
|
NULL, // Password
|
|
NULL, // Password Lenth
|
|
NULL, // Path
|
|
NULL, // Path Length
|
|
NULL, // Extra Info
|
|
NULL, // Extra Info Length
|
|
NULL
|
|
);
|
|
|
|
|
|
if ( error != ERROR_SUCCESS)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( ustSchemeType != INTERNET_SCHEME_HTTPS )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( lpszHostName == NULL || dwcbHostName == 0 )
|
|
{
|
|
fResult = TRUE;
|
|
goto done;
|
|
}
|
|
|
|
|
|
chBackup = lpszHostName[dwcbHostName];
|
|
lpszHostName[dwcbHostName] = '\0';
|
|
SECURITY_CACHE_LIST_ENTRY *pEntry;
|
|
pEntry = GlobalCertCache.Find(lpszHostName);
|
|
|
|
lpszHostName[dwcbHostName] = chBackup;
|
|
|
|
if(pEntry)
|
|
{
|
|
pEntry->CopyOut(cInfo);
|
|
pEntry->Release();
|
|
ShowSecurityInfo(hwndRootWindow,
|
|
&cInfo);
|
|
CertFreeCertificateContext(cInfo.pCertificate);
|
|
fResult = TRUE;
|
|
goto done;
|
|
}
|
|
|
|
Cleanup:
|
|
// No certificate info, display messagebox.
|
|
LoadStringWrapW(
|
|
GlobalDllHandle,
|
|
IDS_NOCERT_TITLE,
|
|
szTitle,
|
|
sizeof(szTitle) / sizeof(szTitle[0]));
|
|
|
|
LoadStringWrapW(
|
|
GlobalDllHandle,
|
|
IDS_NOCERT,
|
|
szMessage,
|
|
sizeof(szMessage) / sizeof(szMessage[0]));
|
|
|
|
MessageBoxWrapW(hwndRootWindow,
|
|
szMessage,
|
|
szTitle,
|
|
MB_ICONINFORMATION | MB_OK);
|
|
|
|
done:
|
|
DEBUG_LEAVE_API(fResult);
|
|
return fResult;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(HINTERNET) InternetConnectA(
|
|
IN HINTERNET hInternet,
|
|
IN LPCSTR lpszServerName,
|
|
IN INTERNET_PORT nServerPort,
|
|
IN LPCSTR lpszUserName OPTIONAL,
|
|
IN LPCSTR lpszPassword OPTIONAL,
|
|
IN DWORD dwService,
|
|
IN DWORD dwFlags,
|
|
IN DWORD_PTR dwContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a connection with a server, logging-on the user in the process.
|
|
|
|
Arguments:
|
|
|
|
hInternet - Internet handle, returned by InternetOpen()
|
|
|
|
lpszServerName - name of server with which to connect
|
|
|
|
nServerPort - port at which server listens
|
|
|
|
lpszUserName - name of current user
|
|
|
|
lpszPassword - password of current user
|
|
|
|
dwService - service required. Controls type of handle generated.
|
|
May be one of:
|
|
- INTERNET_SERVICE_FTP
|
|
- INTERNET_SERVICE_GOPHER
|
|
- INTERNET_SERVICE_HTTP
|
|
|
|
dwFlags - protocol-specific flags. The following are defined:
|
|
- INTERNET_FLAG_PASSIVE (FTP)
|
|
- INTERNET_FLAG_KEEP_CONNECTION (HTTP)
|
|
- INTERNET_FLAG_SECURE (HTTP)
|
|
|
|
dwContext - application-supplied value used to identify this
|
|
request in callbacks
|
|
|
|
Return Value:
|
|
|
|
HINTERNET
|
|
Success - address of a new handle object
|
|
|
|
Failure - NULL. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Handle,
|
|
"InternetConnectA",
|
|
"%#x, %q, %d, %q, %q, %s (%d), %#08x, %#x",
|
|
hInternet,
|
|
lpszServerName,
|
|
nServerPort,
|
|
lpszUserName,
|
|
lpszPassword,
|
|
InternetMapService(dwService),
|
|
dwService,
|
|
dwFlags,
|
|
dwContext
|
|
));
|
|
|
|
char emailName[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
char proxyBuf[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
|
|
HINTERNET connectHandle = NULL;
|
|
HINTERNET hInternetMapped = NULL;
|
|
HINTERNET hObject;
|
|
HINTERNET hObjectMapped = NULL;
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
|
|
BOOL fUseProxy = FALSE;
|
|
|
|
LPSTR serverName = NULL;
|
|
LPSTR userName = (LPSTR)lpszUserName;
|
|
LPSTR password = (LPSTR)lpszPassword;
|
|
LPSTR realServerName = (LPSTR)lpszServerName;
|
|
LPSTR realUserName = (LPSTR)lpszUserName;
|
|
|
|
BOOL existingConnection = FALSE;
|
|
BOOL viaProxy = FALSE;
|
|
|
|
INTERNET_CONNECT_HANDLE_OBJECT * pConnect = NULL;
|
|
//CServerInfo * lpServerInfo;
|
|
|
|
BOOL bProtocolLevel = !(dwFlags & INTERNET_FLAG_OFFLINE);
|
|
BOOL bIsWorker = FALSE;
|
|
BOOL bNonNestedAsync = FALSE;
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
BOOL bFTPSetPerUserItem = FALSE;
|
|
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// get the per-thread info block
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
_InternetIncNestingCount();
|
|
bIsWorker = lpThreadInfo->IsAsyncWorkerThread;
|
|
bNonNestedAsync = bIsWorker && (lpThreadInfo->NestedRequests == 1);
|
|
|
|
//
|
|
// handle any global proxy settings changes first
|
|
//
|
|
|
|
if (InternetSettingsChanged()) {
|
|
ChangeGlobalSettings();
|
|
}
|
|
|
|
//
|
|
// handle/refcount munging:
|
|
//
|
|
// sync:
|
|
// map hInternet on input (+1 ref)
|
|
// generate connect handle (1 ref)
|
|
// if failure && !connect handle
|
|
// close connect handle (0 refs: delete)
|
|
// if success
|
|
// deref hInternet (-1 ref)
|
|
// else if going async
|
|
// ref connect handle (2 refs)
|
|
//
|
|
// async:
|
|
// hInternet is mapped connect handle (2 refs)
|
|
// get real hInternet from connect handle parent (2 refs (e.g.))
|
|
// deref connect handle (1 ref)
|
|
// if failure
|
|
// close connect handle (0 refs: delete)
|
|
// deref open handle (-1 ref)
|
|
//
|
|
// N.B. the final deref of the *indicated* handle on async callback will
|
|
// happen in the async code
|
|
//
|
|
|
|
if (bNonNestedAsync) {
|
|
connectHandle = hInternet;
|
|
hInternetMapped = ((HANDLE_OBJECT *)connectHandle)->GetParent();
|
|
hInternet = ((HANDLE_OBJECT *)hInternetMapped)->GetPseudoHandle();
|
|
} else {
|
|
error = MapHandleToAddress(hInternet, (LPVOID *)&hInternetMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hInternetMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// set the info context and clear the last error info
|
|
//
|
|
|
|
_InternetSetObjectHandle(lpThreadInfo, hInternet, hInternetMapped);
|
|
_InternetClearLastError(lpThreadInfo);
|
|
_InternetSetContext(lpThreadInfo, dwContext);
|
|
|
|
//
|
|
// quit now if the handle object is invalidated
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate the handle & discover local/remote & sync/async
|
|
//
|
|
|
|
error = RIsHandleLocal(hInternetMapped,
|
|
&isLocal,
|
|
&isAsync,
|
|
TypeInternetHandle
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// we allow all valid flags to be passed in
|
|
//
|
|
|
|
if ((dwFlags & ~INTERNET_FLAGS_MASK)
|
|
|| (lpszServerName == NULL)
|
|
|| (*lpszServerName == '\0')) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
INTERNET_SCHEME schemeType;
|
|
|
|
switch (dwService) {
|
|
case INTERNET_SERVICE_FTP:
|
|
schemeType = INTERNET_SCHEME_FTP;
|
|
break;
|
|
|
|
case INTERNET_SERVICE_HTTP:
|
|
schemeType = (dwFlags & INTERNET_FLAG_SECURE)
|
|
? INTERNET_SCHEME_HTTPS
|
|
: INTERNET_SCHEME_HTTP;
|
|
break;
|
|
|
|
case INTERNET_SERVICE_GOPHER: // disable gopher by default
|
|
if (GlobalEnableGopher) {
|
|
schemeType = INTERNET_SCHEME_GOPHER;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate arguments if we're not in the async thread context, in which
|
|
// case we did this when the original request was made
|
|
//
|
|
|
|
if (bNonNestedAsync) {
|
|
pConnect = (INTERNET_CONNECT_HANDLE_OBJECT *)connectHandle;
|
|
goto sync_path;
|
|
}
|
|
|
|
//
|
|
// app thread or in async worker thread but being called from another
|
|
// async API, such as InternetOpenUrl()
|
|
//
|
|
|
|
//
|
|
// special case: if the server name is the NULL pointer or empty string, and
|
|
// the port is 0 AND we have a proxy configured for this protocol then the
|
|
// app is asking to connect directly to the proxy (the proxy server itself
|
|
// had better be in the bypass list!)
|
|
//
|
|
// BUGBUG - not sure if this is really where we want to do this
|
|
//
|
|
|
|
INTERNET_HANDLE_OBJECT * lpInternet;
|
|
|
|
lpInternet = (INTERNET_HANDLE_OBJECT * )hInternetMapped;
|
|
|
|
//
|
|
// if the port value is 0 convert it to the default port for the
|
|
// protocol
|
|
//
|
|
|
|
if (nServerPort == INTERNET_INVALID_PORT_NUMBER) {
|
|
switch (dwService) {
|
|
case INTERNET_SERVICE_FTP:
|
|
nServerPort = INTERNET_DEFAULT_FTP_PORT;
|
|
break;
|
|
|
|
case INTERNET_SERVICE_GOPHER:
|
|
nServerPort = INTERNET_DEFAULT_GOPHER_PORT;
|
|
break;
|
|
|
|
case INTERNET_SERVICE_HTTP:
|
|
if (dwFlags & INTERNET_FLAG_SECURE) {
|
|
nServerPort = INTERNET_DEFAULT_HTTPS_PORT;
|
|
} else {
|
|
nServerPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we have been given a net (i.e. IP) address, try to convert it to the
|
|
// corresponding host name
|
|
//
|
|
|
|
//if (IsNetAddress((LPSTR)lpszServerName)) {
|
|
//lpszServerName = (LPCSTR)MapNetAddressToName((LPSTR)lpszServerName);
|
|
realServerName = (LPSTR)lpszServerName;
|
|
//}
|
|
|
|
//
|
|
// we need to get the username and password for the current user before we
|
|
// make the connection proper. The reason for this is that if we leave it
|
|
// to the server, it will end up with a username of "SYSTEM" for all
|
|
// anonymous FTP connects
|
|
//
|
|
|
|
if (dwService == INTERNET_SERVICE_FTP) {
|
|
|
|
//
|
|
// Make sure we have the correct Proxy-Network Settings, at this point.
|
|
//
|
|
|
|
InternetAutodialIfNotLocalHost(NULL, (LPSTR) lpszServerName);
|
|
|
|
//
|
|
// check the user name & password. If NULLs were supplied, use the values
|
|
// from the registry
|
|
//
|
|
|
|
//
|
|
// Do we need to set this item as per user? If a username was supplied (
|
|
// either cracked from the URL or set on the handle, userName will be
|
|
// non-null. Currently, this is the only criteria for when we set pu.
|
|
//
|
|
// Need to check this BEFORE calling GetEmailNameAndPassword which will
|
|
// plugin "anonymous" if no username provided.
|
|
//
|
|
|
|
bFTPSetPerUserItem = userName ? TRUE : FALSE;
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("InternetConnectA:FTP: bFTPSetPerUserItem = %d\n",
|
|
bFTPSetPerUserItem
|
|
));
|
|
|
|
error = GetEmailNameAndPassword(&userName,
|
|
&password,
|
|
emailName,
|
|
sizeof(emailName)
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// this is the user name we will use for the object, i.e. either the
|
|
// name supplied, or "anonymous" as mapped above
|
|
//
|
|
|
|
realUserName = userName;
|
|
|
|
//
|
|
// if this request is going via an FTP proxy, then convert the parameters
|
|
// now. We convert the username to <username>@<servername>, the password
|
|
// remains the same, and the server name & port become the proxy server
|
|
// name and port
|
|
//
|
|
// N.B. We ONLY do this once on the initial (synchronous) path
|
|
//
|
|
|
|
AUTO_PROXY_ASYNC_MSG proxyInfoQuery(
|
|
INTERNET_SCHEME_FTP,
|
|
(LPSTR)lpszServerName,
|
|
lstrlen((LPSTR)lpszServerName)
|
|
);
|
|
|
|
AUTO_PROXY_ASYNC_MSG *pProxyInfoQuery;
|
|
|
|
pProxyInfoQuery = &proxyInfoQuery;
|
|
|
|
|
|
proxyInfoQuery.SetAvoidAsyncCall(TRUE);
|
|
|
|
error = lpInternet->GetProxyInfo(
|
|
&pProxyInfoQuery
|
|
);
|
|
|
|
if ( error != ERROR_SUCCESS )
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
if ( proxyInfoQuery.IsUseProxy() )
|
|
{
|
|
if (proxyInfoQuery.GetProxyScheme() == INTERNET_SCHEME_FTP)
|
|
{
|
|
|
|
int ulen = lstrlen(userName);
|
|
int slen = lstrlen(lpszServerName);
|
|
|
|
INET_ASSERT((ulen + slen) < (sizeof(proxyBuf) - 1));
|
|
|
|
if ((ulen + slen) < (sizeof(proxyBuf) - 1))
|
|
{
|
|
memcpy(proxyBuf, userName, ulen);
|
|
proxyBuf[ulen++] = '@';
|
|
memcpy(&proxyBuf[ulen], lpszServerName, slen + 1);
|
|
|
|
//
|
|
// keep a pointer to the real user name for when we
|
|
// create the object
|
|
//
|
|
|
|
realUserName = userName;
|
|
userName = proxyBuf;
|
|
|
|
//
|
|
// create a copy of the proxy name. We have to do
|
|
// this in case the current proxy list is replaced
|
|
// while we are using this string
|
|
//
|
|
|
|
//
|
|
// N.B. we can't be here if we determined that the
|
|
// proxy server was the destination (i.e. we mapped2
|
|
// the empty server name above)
|
|
//
|
|
|
|
INET_ASSERT(serverName == NULL);
|
|
|
|
serverName = NewString((LPCSTR)proxyInfoQuery._lpszProxyHostName);
|
|
|
|
if ( serverName == NULL )
|
|
{
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// keep a pointer to the real (origin) server name
|
|
// for when we create the object
|
|
//
|
|
|
|
realServerName = (LPSTR)lpszServerName;
|
|
lpszServerName = serverName;
|
|
|
|
//
|
|
// BUGBUG - what if proxyPort != nServerPort? Where
|
|
// should the port go (user@server:port?)
|
|
//
|
|
|
|
nServerPort = proxyInfoQuery._nProxyHostPort;
|
|
|
|
//
|
|
// this request will go via proxy
|
|
//
|
|
|
|
viaProxy = TRUE;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// blew internal limit
|
|
//
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto quit;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (userName != NULL) {
|
|
if (IsBadStringPtr(userName, INTERNET_MAX_USER_NAME_LENGTH)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
} else if (*userName == '\0') {
|
|
userName = NULL;
|
|
}
|
|
}
|
|
if (password != NULL) {
|
|
if (IsBadStringPtr(password, INTERNET_MAX_PASSWORD_LENGTH)) {
|
|
error = ERROR_INVALID_PASSWORD;
|
|
goto quit;
|
|
} else if (*password == '\0') {
|
|
password = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// find the handle object if EXISTING_CONNECT AND we are creating protocol-
|
|
// level connections, else create it
|
|
//
|
|
|
|
INET_ASSERT(connectHandle == NULL);
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
if ((dwFlags & INTERNET_FLAG_EXISTING_CONNECT) && bProtocolLevel) {
|
|
connectHandle = FindExistingConnectObject(hInternet,
|
|
realServerName,
|
|
nServerPort,
|
|
realUserName,
|
|
password,
|
|
dwService,
|
|
dwFlags,
|
|
dwContext
|
|
);
|
|
}
|
|
if (connectHandle != NULL) {
|
|
existingConnection = TRUE;
|
|
} else {
|
|
|
|
//
|
|
// turn off INTERNET_FLAG_EXISTING_CONNECT if we are creating a cache
|
|
// handle - we don't want the handle to hang around after we delete
|
|
// the request handle (i.e. be set unused by InternetCloseHandle()).
|
|
// N.B. We don't need this flag after this operation, so its safe to
|
|
// remove it from dwFlags
|
|
//
|
|
|
|
if (!bProtocolLevel) {
|
|
dwFlags &= ~INTERNET_FLAG_EXISTING_CONNECT;
|
|
}
|
|
error = RMakeInternetConnectObjectHandle(
|
|
hInternetMapped,
|
|
&connectHandle,
|
|
(CONNECT_CLOSE_HANDLE_FUNC)wInternetCloseConnectA,
|
|
realServerName, // origin server, not proxy
|
|
nServerPort,
|
|
realUserName, // just user name, not user@server
|
|
password,
|
|
dwService,
|
|
dwFlags,
|
|
dwContext
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// this new handle will be used in callbacks
|
|
//
|
|
|
|
_InternetSetObjectHandle(lpThreadInfo,
|
|
((HANDLE_OBJECT *)connectHandle)->GetPseudoHandle(),
|
|
connectHandle
|
|
);
|
|
|
|
//
|
|
// based on whether we have been asked to perform async I/O AND we are not
|
|
// in an async worker thread context AND the request is to connect with an
|
|
// FTP service (currently only FTP because this request performs network
|
|
// I/O - gopher and HTTP just allocate & fill in memory) AND there is a
|
|
// valid context value, we will queue the async request, or execute the
|
|
// request synchronously
|
|
//
|
|
|
|
//
|
|
// BUGBUG - GetFlags()
|
|
//
|
|
|
|
if ((lpInternet->GetInternetOpenFlags() | dwFlags) & INTERNET_FLAG_OFFLINE) {
|
|
error = ERROR_SUCCESS;
|
|
goto quit;
|
|
}
|
|
|
|
pConnect = (INTERNET_CONNECT_HANDLE_OBJECT *)connectHandle;
|
|
//lpServerInfo = pConnect->GetServerInfo();
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("bIsWorker = %d isAsync = %d dwContext %s INC dwService %s ISF bProtocolLevel = %d\n",
|
|
bIsWorker,
|
|
isAsync,
|
|
(dwContext == INTERNET_NO_CALLBACK) ? "==":"!=",
|
|
(dwService == INTERNET_SERVICE_FTP) ? "==":"!=",
|
|
bProtocolLevel));
|
|
|
|
if (!bIsWorker
|
|
&& isAsync
|
|
&& (dwContext != INTERNET_NO_CALLBACK)
|
|
&& ((dwService == INTERNET_SERVICE_FTP) ? bProtocolLevel : FALSE)) {
|
|
|
|
// If we determined item should be set pu, do so now
|
|
pConnect->SetPerUserItem(bFTPSetPerUserItem);
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("InternetConnectA:Async Path: SetPerUserItem to %\r\n\
|
|
<pConnect = 0x%x> <connectHandle = 0x%x>\r\n",
|
|
pConnect->IsPerUserItem(), pConnect, connectHandle));
|
|
CFsm_FtpConnect * pFsm;
|
|
|
|
pFsm = new CFsm_FtpConnect(lpszServerName,
|
|
userName,
|
|
password,
|
|
nServerPort,
|
|
dwService,
|
|
dwFlags,
|
|
dwContext
|
|
);
|
|
if (pFsm != NULL &&
|
|
pFsm->GetError() == ERROR_SUCCESS)
|
|
{
|
|
BOOL bDerefConnect = TRUE;
|
|
|
|
error = pConnect->Reference();
|
|
|
|
if (error == ERROR_ACCESS_DENIED)
|
|
{
|
|
bDerefConnect = FALSE;
|
|
}
|
|
else if (error == ERROR_SUCCESS)
|
|
{
|
|
error = pFsm->QueueWorkItem();
|
|
if (error == ERROR_IO_PENDING) {
|
|
hInternetMapped = NULL;
|
|
bDerefConnect = FALSE;
|
|
}
|
|
}
|
|
if (bDerefConnect) {
|
|
pConnect->Dereference();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
if ( pFsm )
|
|
{
|
|
error = pFsm->GetError();
|
|
delete pFsm;
|
|
pFsm = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we're here then ERROR_SUCCESS cannot have been returned from
|
|
// the above calls
|
|
//
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("processing request asynchronously: error = %d\n",
|
|
error
|
|
));
|
|
|
|
goto quit;
|
|
}
|
|
|
|
sync_path:
|
|
|
|
if (bProtocolLevel && !existingConnection) {
|
|
|
|
//
|
|
// generate the protocol-level connect 'object' if required (for FTP).
|
|
// This simply creates a memory object
|
|
//
|
|
|
|
HINTERNET protocolConnectHandle = NULL;
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
if (dwService == INTERNET_SERVICE_FTP) {
|
|
error = wFtpConnect(lpszServerName,
|
|
nServerPort,
|
|
userName,
|
|
password,
|
|
dwService,
|
|
dwFlags,
|
|
&protocolConnectHandle
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// associate the protocol-level handle and INTERNET_CONNECT_HANDLE_OBJECT
|
|
//
|
|
|
|
pConnect->SetConnectHandle(protocolConnectHandle);
|
|
|
|
// If we determined item should be set pu, do so now
|
|
pConnect->SetPerUserItem(bFTPSetPerUserItem);
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("InternetConnectA:Sync Path:SetPerUserItem to %d\r\n\
|
|
<pConnect = 0x%x> <protocolConnectHandle = 0x%x> <connectHandle = 0x%x>\r\n",
|
|
pConnect->IsPerUserItem(), pConnect, protocolConnectHandle, connectHandle));
|
|
|
|
|
|
// for all connect types, get the server info and resolve the server
|
|
// name. If we can't resolve the name then we fail this request
|
|
//
|
|
|
|
//lpServerInfo = pConnect->GetServerInfo();
|
|
//if ((lpServerInfo != NULL) && !lpServerInfo->IsNameResolved()) {
|
|
// error = pConnect->SetServerInfo(schemeType, FALSE, FALSE);
|
|
// if (error != ERROR_SUCCESS) {
|
|
// goto quit;
|
|
// }
|
|
//}
|
|
|
|
//
|
|
// if we succeeded in creating the connect object and this is an FTP
|
|
// request then we will now attempt to connect to the server proper.
|
|
//
|
|
// We don't need to do this for gopher and HTTP because (currently) they
|
|
// don't make server connections until we perform some other action,
|
|
// like find file, e.g.
|
|
//
|
|
|
|
if (dwService == INTERNET_SERVICE_FTP) {
|
|
error = wFtpMakeConnection(protocolConnectHandle,
|
|
userName,
|
|
password
|
|
);
|
|
}
|
|
}
|
|
|
|
quit:
|
|
|
|
_InternetDecNestingCount(1);
|
|
|
|
//
|
|
// free the buffer we used to hold the proxy name if we had to map the
|
|
// empty string to a proxy name
|
|
//
|
|
|
|
if (serverName != NULL) {
|
|
DEL_STRING(serverName);
|
|
}
|
|
|
|
done:
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// set the via proxy flag
|
|
//
|
|
|
|
((INTERNET_CONNECT_HANDLE_OBJECT *)connectHandle)->SetViaProxy(viaProxy);
|
|
|
|
//
|
|
// success - return generated pseudo-handle
|
|
//
|
|
|
|
connectHandle = ((HANDLE_OBJECT *)connectHandle)->GetPseudoHandle();
|
|
|
|
//
|
|
// created a handle. If we are generating protocol-level connections
|
|
// then flush the existing connection cache
|
|
//
|
|
|
|
if (bProtocolLevel) {
|
|
FlushExistingConnectObjects(hInternet);
|
|
}
|
|
} else {
|
|
if (bNonNestedAsync
|
|
&& (/*((HANDLE_OBJECT *)connectHandle)->Dereference()
|
|
||*/ ((HANDLE_OBJECT *)connectHandle)->IsInvalidated())) {
|
|
error = ERROR_INTERNET_OPERATION_CANCELLED;
|
|
}
|
|
|
|
//
|
|
// if we are not pending an async request but we created a handle object
|
|
// then close it
|
|
//
|
|
|
|
if ((error != ERROR_IO_PENDING) && (connectHandle != NULL)) {
|
|
|
|
//
|
|
// use _InternetCloseHandle() to close the handle: it doesn't clear
|
|
// out the last error text, so that an app can find out what the
|
|
// server sent us in the event of an FTP login failure
|
|
//
|
|
|
|
|
|
if (bNonNestedAsync) {
|
|
|
|
//
|
|
// this handle deref'd at async completion
|
|
//
|
|
|
|
hInternetMapped = NULL;
|
|
}
|
|
else
|
|
{
|
|
_InternetCloseHandle(((HANDLE_OBJECT *)connectHandle)->GetPseudoHandle());
|
|
}
|
|
}
|
|
connectHandle = NULL;
|
|
}
|
|
if (hInternetMapped != NULL) {
|
|
DereferenceObject((LPVOID)hInternetMapped);
|
|
}
|
|
if (error != ERROR_SUCCESS) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
DEBUG_LEAVE_API(connectHandle);
|
|
return connectHandle;
|
|
}
|
|
|
|
|
|
|
|
INTERNETAPI_(HINTERNET) InternetOpenUrlA(
|
|
IN HINTERNET hInternet,
|
|
IN LPCSTR lpszUrl,
|
|
IN LPCSTR lpszHeaders OPTIONAL,
|
|
IN DWORD dwHeadersLength,
|
|
IN DWORD dwFlags,
|
|
IN DWORD_PTR dwContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens an URL. This consists of creating a handle to the type of item
|
|
identified by the URL - directory or file
|
|
|
|
Arguments:
|
|
|
|
hInternet - root Internet handle
|
|
|
|
lpszUrl - pointer to the URL to use to open the item
|
|
|
|
lpszHeaders - headers to send to HTTP server. May be NULL
|
|
|
|
dwHeadersLength - length of lpszHeaders. May be -1 if the app wants us to
|
|
perform the strlen()
|
|
|
|
dwFlags - open flags (cache/nocache, etc.)
|
|
|
|
dwContext - app-supplied context value for call-backs
|
|
|
|
Return Value:
|
|
|
|
HINTERNET
|
|
Success - open handle to item described by URL
|
|
|
|
Failure - NULL. Use GetLastError() to get more information about why the
|
|
call failed. There may be error text returned from the server
|
|
(in the case of a gopher or FTP URL)
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Handle,
|
|
"InternetOpenUrlA",
|
|
"%#x, %q, %.80q, %d, %#08x, %#x",
|
|
hInternet,
|
|
lpszUrl,
|
|
lpszHeaders,
|
|
dwHeadersLength,
|
|
dwFlags,
|
|
dwContext
|
|
));
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo = NULL;
|
|
DWORD error;
|
|
DWORD nestingLevel = 0;
|
|
HINTERNET hInternetMapped = NULL;
|
|
HINTERNET hUrlMapped = NULL;
|
|
HINTERNET hUrl = NULL;
|
|
|
|
PROXY_STATE * pProxyState = NULL;
|
|
BOOL bDeref = TRUE;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// get the thread info block
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto quit;
|
|
}
|
|
|
|
_InternetIncNestingCount();
|
|
nestingLevel = 1;
|
|
|
|
//
|
|
// map the handle
|
|
//
|
|
|
|
error = MapHandleToAddress(hInternet, (LPVOID *)&hInternetMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hInternetMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// set the info context and clear the last error info
|
|
//
|
|
|
|
_InternetSetObjectHandle(lpThreadInfo, hInternet, hInternetMapped);
|
|
_InternetClearLastError(lpThreadInfo);
|
|
_InternetSetContext(lpThreadInfo, dwContext);
|
|
|
|
//
|
|
// quit now if the handle object is invalidated
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate the handle & discover async/sync & local/remote
|
|
//
|
|
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
|
|
error = RIsHandleLocal(hInternetMapped,
|
|
&isLocal,
|
|
&isAsync,
|
|
TypeInternetHandle
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate parameters if we're not in the async worker thread context
|
|
//
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread) {
|
|
|
|
//
|
|
// ensure we have good values for the headers pointer and length
|
|
//
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
if (ARGUMENT_PRESENT(lpszHeaders) && (dwHeadersLength == -1)) {
|
|
__try {
|
|
dwHeadersLength = lstrlen(lpszHeaders);
|
|
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
ENDEXCEPT
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
} else if (!ARGUMENT_PRESENT(lpszHeaders) || (dwHeadersLength == 0)) {
|
|
lpszHeaders = NULL;
|
|
dwHeadersLength = 0;
|
|
}
|
|
if (!ARGUMENT_PRESENT(lpszUrl)
|
|
|| (*lpszUrl == '\0')
|
|
// || !IsValidUrl(lpszUrl)
|
|
|| (dwFlags & ~INTERNET_FLAGS_MASK)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// determine if this is a http request or FTP request - either http URL, or via http proxy.
|
|
// or via another protocol.
|
|
// For any Async request, we must go async in order to determine proxy information
|
|
//
|
|
|
|
bDeref = FALSE;
|
|
hUrl = hInternet;
|
|
|
|
if (isAsync
|
|
&& !lpThreadInfo->IsAsyncWorkerThread
|
|
&& (dwContext != INTERNET_NO_CALLBACK))
|
|
{
|
|
CFsm_ParseUrlForHttp *pFsm;
|
|
|
|
pFsm = new CFsm_ParseUrlForHttp(&hUrl,
|
|
(INTERNET_HANDLE_OBJECT *)hInternetMapped,
|
|
lpszUrl,
|
|
lpszHeaders,
|
|
dwHeadersLength,
|
|
dwFlags,
|
|
dwContext
|
|
);
|
|
|
|
if (pFsm != NULL) {
|
|
// IE6 BUG #27905
|
|
// check if ctor of CFsm_ParseUrlForHttp encountered an error
|
|
error = pFsm->GetError();
|
|
if (error == ERROR_SUCCESS) {
|
|
// first call will not be on this API's thread context
|
|
pFsm->ClearOnApiCall();
|
|
error = pFsm->QueueWorkItem();
|
|
} else {
|
|
delete pFsm;
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
} else {
|
|
bDeref = TRUE;
|
|
CFsm_ParseUrlForHttp *pFsm;
|
|
|
|
pFsm = new CFsm_ParseUrlForHttp(&hUrl,
|
|
(INTERNET_HANDLE_OBJECT *)hInternetMapped,
|
|
lpszUrl,
|
|
lpszHeaders,
|
|
dwHeadersLength,
|
|
dwFlags,
|
|
dwContext
|
|
);
|
|
if (pFsm != NULL) {
|
|
// IE6 BUG #27905
|
|
// check if ctor of CFsm_ParseUrlForHttp encountered an error
|
|
error = pFsm->GetError();
|
|
if (error == ERROR_SUCCESS) {
|
|
error = DoFsm(pFsm);
|
|
} else {
|
|
delete pFsm;
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
if ( error == ERROR_IO_PENDING )
|
|
{
|
|
bDeref = FALSE;
|
|
}
|
|
|
|
quit:
|
|
|
|
if (bDeref && hInternetMapped != NULL) {
|
|
DereferenceObject((LPVOID)hInternetMapped);
|
|
}
|
|
|
|
if ( lpThreadInfo != NULL ) {
|
|
_InternetDecNestingCount(nestingLevel);
|
|
}
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
DEBUG_ERROR(API, error);
|
|
|
|
SetLastError(error);
|
|
hUrl = NULL;
|
|
}
|
|
|
|
DEBUG_LEAVE_API(hUrl);
|
|
|
|
return hUrl;
|
|
}
|
|
|
|
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetReadFile(
|
|
IN HINTERNET hFile,
|
|
IN LPVOID lpBuffer,
|
|
IN DWORD dwNumberOfBytesToRead,
|
|
OUT LPDWORD lpdwNumberOfBytesRead
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This functions reads the next block of data from the file object. The
|
|
following handle/data types are supported:
|
|
|
|
TypeGopherFileHandle - raw gopher file data
|
|
TypeGopherFileHandleHtml - HTML-encapsulated gopher file data
|
|
TypeGopherFindHandleHtml - HTML-encapsulated gopher directory data
|
|
TypeFtpFileHandle - raw FTP file data
|
|
TypeFtpFileHandleHtml - HTML-encapsulated FTP file data
|
|
TypeFtpFindHandleHtml - HTML-encapsulated FTP directory data
|
|
|
|
Arguments:
|
|
|
|
hFile - handle returned from Open function
|
|
|
|
lpBuffer - pointer to caller's buffer
|
|
|
|
dwNumberOfBytesToRead - size of lpBuffer in BYTEs
|
|
|
|
lpdwNumberOfBytesRead - returned number of bytes read into lpBuffer
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetReadFile",
|
|
"%#x, %#x, %d, %#x",
|
|
hFile,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
lpdwNumberOfBytesRead
|
|
));
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
DWORD nestingLevel = 0;
|
|
DWORD error;
|
|
BOOL success = FALSE;
|
|
HINTERNET hFileMapped = NULL;
|
|
DWORD bytesRead = 0;
|
|
BOOL bEndRead = TRUE;
|
|
HINTERNET_HANDLE_TYPE handleType = TypeWildHandle;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// we need the thread info block
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
//INET_ASSERT(lpThreadInfo->Fsm == NULL);
|
|
|
|
_InternetIncNestingCount();
|
|
nestingLevel = 1;
|
|
|
|
//
|
|
// map the handle
|
|
//
|
|
|
|
error = MapHandleToAddress(hFile, (LPVOID *)&hFileMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hFileMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// set the context, handle, and last-error info in the per-thread data block
|
|
// before we go any further. This allows us to return a status in the async
|
|
// case, even if the handle has been closed
|
|
//
|
|
|
|
DWORD_PTR context;
|
|
|
|
RGetContext(hFileMapped, &context);
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread) {
|
|
|
|
PERF_LOG(PE_CLIENT_REQUEST_START,
|
|
AR_INTERNET_READ_FILE,
|
|
lpThreadInfo->ThreadId,
|
|
hFile
|
|
);
|
|
|
|
}
|
|
|
|
_InternetSetContext(lpThreadInfo, context);
|
|
_InternetSetObjectHandle(lpThreadInfo, hFile, hFileMapped);
|
|
_InternetClearLastError(lpThreadInfo);
|
|
|
|
//
|
|
// if MapHandleToAddress() returned a non-NULL object address, but also an
|
|
// error status, then the handle is being closed - quit
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate handle and retrieve its type
|
|
//
|
|
|
|
error = RGetHandleType(hFileMapped, &handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
|
|
error = RIsHandleLocal(hFileMapped, &isLocal, &isAsync, handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// we should not get an error - we already believe the handle object
|
|
// is valid and of the type just retrieved!
|
|
//
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// ensure correct handle type
|
|
//
|
|
|
|
if ((handleType != TypeHttpRequestHandle)
|
|
&& (handleType != TypeFtpFileHandle)
|
|
&& (handleType != TypeGopherFileHandle)
|
|
&& (handleType != TypeFtpFindHandleHtml)
|
|
&& (handleType != TypeGopherFindHandleHtml)
|
|
&& (handleType != TypeFtpFileHandleHtml)
|
|
&& (handleType != TypeGopherFileHandleHtml)
|
|
&& (handleType != TypeFileRequestHandle)) {
|
|
error = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread) {
|
|
error = ProbeAndSetDword(lpdwNumberOfBytesRead, 0);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
error = ProbeWriteBuffer(lpBuffer, dwNumberOfBytesToRead);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
*lpdwNumberOfBytesRead = 0;
|
|
|
|
if (((handleType == TypeFtpFindHandleHtml) ||
|
|
(handleType == TypeGopherFindHandleHtml)) &&
|
|
((INTERNET_CONNECT_HANDLE_OBJECT *)hFileMapped)->
|
|
IsCacheReadInProgress())
|
|
{
|
|
error = ((INTERNET_CONNECT_HANDLE_OBJECT *)hFileMapped)->
|
|
ReadCache((LPBYTE)lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead);
|
|
success = (error == ERROR_SUCCESS);
|
|
goto quit;
|
|
}
|
|
|
|
if (handleType == TypeHttpRequestHandle) {
|
|
|
|
HTTP_REQUEST_HANDLE_OBJECT *lpRequest =
|
|
(HTTP_REQUEST_HANDLE_OBJECT *) hFileMapped;
|
|
|
|
// See if request can be fulfilled from file system.
|
|
if (lpRequest->AttemptReadFromFile
|
|
(lpBuffer, dwNumberOfBytesToRead, &bytesRead)) {
|
|
success = TRUE;
|
|
goto quit;
|
|
}
|
|
|
|
} // end if (handleType == TypeHttpRequestHandle)
|
|
|
|
else
|
|
{
|
|
//
|
|
// trap a zero-length buffer before we go to the trouble of going async.
|
|
// Maintain compatibility with base ReadFile(), although this is
|
|
// POTENTIALLY A BUG. ReadFile() is *supposed* to return TRUE and number
|
|
// of bytes read equal to zero to indicate end-of-file, but it will also
|
|
// return TRUE and zero if a read of zero bytes is requested. According
|
|
// to MarkL, that's the way it is. Good enough for me...
|
|
//
|
|
// For http, AttemptToReadFromFile traps zero-length reads
|
|
|
|
if (dwNumberOfBytesToRead == 0) {
|
|
|
|
//
|
|
// *lpdwNumberOfBytesRead and error should have been correctly set
|
|
// during parameter validation
|
|
//
|
|
|
|
INET_ASSERT(*lpdwNumberOfBytesRead == 0);
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
success = TRUE;
|
|
goto quit;
|
|
}
|
|
} // end else (handleType != TypeHttpRequestHandle)
|
|
|
|
} // end if (!lpThreadInfo->IsAsyncWorkerThread)
|
|
|
|
//
|
|
// the request will only be made asynchronously if more data is requested
|
|
// than is immediately available AND we haven't reached end of file
|
|
//
|
|
|
|
DWORD available;
|
|
|
|
available = ((INTERNET_HANDLE_OBJECT *)hFileMapped)->AvailableDataLength();
|
|
|
|
BOOL eof;
|
|
|
|
eof = ((INTERNET_HANDLE_OBJECT *)hFileMapped)->IsEndOfFile();
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread
|
|
&& isAsync
|
|
&& (context != INTERNET_NO_CALLBACK)
|
|
&& (dwNumberOfBytesToRead > available)
|
|
&& !eof
|
|
&& (handleType != TypeHttpRequestHandle)
|
|
&& (handleType != TypeFileRequestHandle)) {
|
|
|
|
// MakeAsyncRequest
|
|
CFsm_InternetReadFile * pFsm;
|
|
|
|
pFsm = new CFsm_InternetReadFile(hFile, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead);
|
|
if (pFsm != NULL) {
|
|
error = pFsm->QueueWorkItem();
|
|
if ( error == ERROR_IO_PENDING ) {
|
|
bEndRead = FALSE;
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// if we're here then ERROR_SUCCESS cannot have been returned from
|
|
// the above calls
|
|
//
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("processing request asynchronously: error = %d\n",
|
|
error
|
|
));
|
|
|
|
goto quit;
|
|
|
|
//
|
|
// we're going synchronous - set the error so we do the right thing when
|
|
// we exit
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
} else if ((available >= dwNumberOfBytesToRead) || eof) {
|
|
|
|
DEBUG_PRINT(API,
|
|
INFO,
|
|
("immediate read: %d requested, %d available. EOF = %B\n",
|
|
dwNumberOfBytesToRead,
|
|
available,
|
|
eof
|
|
));
|
|
|
|
}
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
//
|
|
// just call the underlying API: return whatever it returns, and let it
|
|
// handle setting the last error
|
|
//
|
|
|
|
switch (handleType) {
|
|
case TypeFtpFileHandle:
|
|
success = FtpReadFile(hFileMapped,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead
|
|
);
|
|
break;
|
|
|
|
case TypeGopherFileHandle:
|
|
success = GopherReadFile(hFileMapped,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead
|
|
);
|
|
break;
|
|
|
|
case TypeFtpFindHandleHtml:
|
|
case TypeGopherFindHandleHtml:
|
|
|
|
//
|
|
// HTML handle types - convert underlying data to HTML document
|
|
//
|
|
|
|
success = ReadHtmlUrlData(hFileMapped,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead
|
|
);
|
|
|
|
if (((INTERNET_CONNECT_HANDLE_OBJECT *)hFileMapped)->IsCacheWriteInProgress())
|
|
{
|
|
DWORD errorCache;
|
|
|
|
if (success) {
|
|
|
|
|
|
if (bytesRead) {
|
|
|
|
errorCache = ((INTERNET_CONNECT_HANDLE_OBJECT *)hFileMapped)->
|
|
WriteCache(
|
|
(LPBYTE)lpBuffer,
|
|
bytesRead
|
|
);
|
|
}
|
|
else {
|
|
errorCache = ERROR_NO_MORE_FILES;
|
|
}
|
|
|
|
}
|
|
else {
|
|
errorCache = GetLastError();
|
|
}
|
|
|
|
// if the thing failed because the caller passed in
|
|
// insufficient buffer for internetreadfile
|
|
// then we should do nothing
|
|
|
|
if ((errorCache != ERROR_SUCCESS)&&
|
|
(errorCache != ERROR_INSUFFICIENT_BUFFER)) {
|
|
if (handleType == TypeFtpFindHandleHtml) {
|
|
|
|
// we save extension in the index file
|
|
// this is used to differentiate between html directory
|
|
// entry from the non-html one for the same url
|
|
|
|
InbLocalEndCacheWrite( hFileMapped,
|
|
"htm", // save extension in index file
|
|
(errorCache == ERROR_NO_MORE_FILES)
|
|
);
|
|
}
|
|
else {
|
|
InbGopherLocalEndCacheWrite( hFileMapped,
|
|
"htm",
|
|
(errorCache == ERROR_NO_MORE_FILES)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case TypeFtpFileHandleHtml:
|
|
case TypeGopherFileHandleHtml:
|
|
|
|
//
|
|
// HTML handle types - convert underlying data to HTML document
|
|
//
|
|
|
|
success = ReadHtmlUrlData(hFileMapped,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead
|
|
);
|
|
break;
|
|
|
|
case TypeHttpRequestHandle:
|
|
|
|
{
|
|
//HTTP_REQUEST_HANDLE_OBJECT * lpRequest;
|
|
//
|
|
//lpRequest = (HTTP_REQUEST_HANDLE_OBJECT *)hFileMapped;
|
|
//
|
|
//error = lpRequest->QuickSyncRead(
|
|
// lpBuffer,
|
|
// dwNumberOfBytesToRead,
|
|
// lpdwNumberOfBytesRead,
|
|
// 0
|
|
// );
|
|
//
|
|
//if ( error == ERROR_IO_PENDING )
|
|
//{
|
|
error = DoFsm(new CFsm_ReadFile(lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
lpdwNumberOfBytesRead
|
|
));
|
|
//}
|
|
|
|
success = (error == ERROR_SUCCESS) ? TRUE : FALSE;
|
|
bEndRead = FALSE;
|
|
break;
|
|
}
|
|
|
|
case TypeFileRequestHandle:
|
|
|
|
success = ReadFile(
|
|
((INTERNET_FILE_HANDLE_OBJECT *) hFileMapped)->GetFileHandle(),
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead,
|
|
NULL // overlapped I/O
|
|
);
|
|
|
|
if (!success)
|
|
{
|
|
error = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
|
|
case TypeFtpFindHandle:
|
|
case TypeGopherFindHandle:
|
|
|
|
//
|
|
// you cannot receive RAW directory data using this API. You have
|
|
// to call InternetFindNextFile()
|
|
//
|
|
|
|
default:
|
|
|
|
//
|
|
// the handle is a valid handle (or else RGetHandleType() would
|
|
// have returned ERROR_INVALID_HANDLE), but this operation is
|
|
// inconsistent with the handle type. Return a more prosaic error
|
|
// code
|
|
//
|
|
|
|
error = ERROR_INTERNET_INVALID_OPERATION;
|
|
break;
|
|
}
|
|
|
|
quit:
|
|
|
|
_InternetDecNestingCount(nestingLevel);;
|
|
|
|
if (bEndRead) {
|
|
|
|
//
|
|
// if handleType is not HttpRequest or File then we are making this
|
|
// request in the context of an uninterruptable async worker thread.
|
|
// HTTP and file requests use the normal mechanism. In the case of non-
|
|
// HTTP and file requests, we need to treat the request as if it were
|
|
// sync and deref the handle
|
|
//
|
|
|
|
ReadFile_End(!lpThreadInfo->IsAsyncWorkerThread
|
|
|| !((handleType == TypeHttpRequestHandle)
|
|
|| (handleType == TypeFileRequestHandle)),
|
|
success,
|
|
hFileMapped,
|
|
bytesRead,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
lpdwNumberOfBytesRead
|
|
);
|
|
}
|
|
|
|
if (lpThreadInfo && !lpThreadInfo->IsAsyncWorkerThread) {
|
|
|
|
PERF_LOG(PE_CLIENT_REQUEST_END,
|
|
AR_INTERNET_READ_FILE,
|
|
bytesRead,
|
|
lpThreadInfo->ThreadId,
|
|
hFile
|
|
);
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
//
|
|
// if error is not ERROR_SUCCESS then this function returning the error,
|
|
// otherwise the error has already been set by the API we called,
|
|
// irrespective of the value of success
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
DEBUG_ERROR(API, error);
|
|
|
|
SetLastError(error);
|
|
success = FALSE;
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
PRIVATE
|
|
VOID
|
|
ReadFile_End(
|
|
IN BOOL bDeref,
|
|
IN BOOL bSuccess,
|
|
IN HINTERNET hFileMapped,
|
|
IN DWORD dwBytesRead,
|
|
IN LPVOID lpBuffer OPTIONAL,
|
|
IN DWORD dwNumberOfBytesToRead,
|
|
OUT LPDWORD lpdwNumberOfBytesRead OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Common end-of-read processing:
|
|
|
|
- update bytes read parameter
|
|
- dump data if logging & API data requested
|
|
- dereference handle if not async request
|
|
|
|
Arguments:
|
|
|
|
bDeref - TRUE if handle should be dereferenced (should be
|
|
FALSE for async request)
|
|
|
|
bSuccess - TRUE if Read completed successfully
|
|
|
|
hFileMapped - mapped file handle
|
|
|
|
dwBytesRead - number of bytes read
|
|
|
|
lpBuffer - into this buffer
|
|
|
|
dwNumberOfBytesToRead - originally requested bytes to read
|
|
|
|
lpdwNumberOfBytesRead - where bytes read is stored
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
None,
|
|
"ReadFile_End",
|
|
"%B, %B, %#x, %d, %#x, %d, %#x",
|
|
bDeref,
|
|
bSuccess,
|
|
hFileMapped,
|
|
dwBytesRead,
|
|
lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
lpdwNumberOfBytesRead
|
|
));
|
|
|
|
if (bSuccess) {
|
|
|
|
//
|
|
// update the amount of immediate data available only if we succeeded
|
|
//
|
|
|
|
((INTERNET_HANDLE_OBJECT *)hFileMapped)->ReduceAvailableDataLength(dwBytesRead);
|
|
|
|
if (lpdwNumberOfBytesRead != NULL) {
|
|
*lpdwNumberOfBytesRead = dwBytesRead;
|
|
|
|
DEBUG_PRINT(API,
|
|
INFO,
|
|
("*lpdwNumberOfBytesRead = %d\n",
|
|
*lpdwNumberOfBytesRead
|
|
));
|
|
|
|
//
|
|
// dump API data only if requested
|
|
//
|
|
|
|
IF_DEBUG_CONTROL(DUMP_API_DATA) {
|
|
DEBUG_DUMP_API(API,
|
|
"Received data:\n",
|
|
lpBuffer,
|
|
*lpdwNumberOfBytesRead
|
|
);
|
|
}
|
|
}
|
|
if (dwBytesRead < dwNumberOfBytesToRead) {
|
|
|
|
DEBUG_PRINT(API,
|
|
INFO,
|
|
("(!) bytes read (%d) < bytes requested (%d)\n",
|
|
dwBytesRead,
|
|
dwNumberOfBytesToRead
|
|
));
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// if async request, handle will be deref'd after REQUEST_COMPLETE callback
|
|
// is delivered
|
|
//
|
|
|
|
if (bDeref && (hFileMapped != NULL)) {
|
|
DereferenceObject((LPVOID)hFileMapped);
|
|
}
|
|
|
|
PERF_LOG(PE_CLIENT_REQUEST_END,
|
|
AR_INTERNET_READ_FILE,
|
|
dwBytesRead,
|
|
0,
|
|
(!bDeref && hFileMapped) ? ((INTERNET_HANDLE_OBJECT *)hFileMapped)->GetPseudoHandle() : NULL
|
|
);
|
|
|
|
DEBUG_LEAVE(0);
|
|
}
|
|
|
|
|
|
DWORD
|
|
CFsm_ReadFile::RunSM(
|
|
IN CFsm * Fsm
|
|
)
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"CFsm_ReadFile::RunSM",
|
|
"%#x",
|
|
Fsm
|
|
));
|
|
|
|
DWORD error;
|
|
CFsm_ReadFile * stateMachine = (CFsm_ReadFile *)Fsm;
|
|
|
|
switch (Fsm->GetState()) {
|
|
case FSM_STATE_INIT:
|
|
case FSM_STATE_CONTINUE:
|
|
error = ReadFile_Fsm(stateMachine);
|
|
break;
|
|
|
|
default:
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
break;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
ReadFile_Fsm(
|
|
IN CFsm_ReadFile * Fsm
|
|
)
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
Dword,
|
|
"ReadFile_Fsm",
|
|
"%#x",
|
|
Fsm
|
|
));
|
|
|
|
CFsm_ReadFile & fsm = *Fsm;
|
|
DWORD error = fsm.GetError();
|
|
|
|
if ((error == ERROR_SUCCESS) && (fsm.GetState() == FSM_STATE_INIT)) {
|
|
error = HttpReadData(fsm.GetMappedHandle(),
|
|
fsm.m_lpBuffer,
|
|
fsm.m_dwNumberOfBytesToRead,
|
|
&fsm.m_dwBytesRead,
|
|
0
|
|
);
|
|
if (error == ERROR_IO_PENDING) {
|
|
goto quit;
|
|
}
|
|
}
|
|
ReadFile_End(!fsm.GetThreadInfo()->IsAsyncWorkerThread,
|
|
(error == ERROR_SUCCESS) ? TRUE : FALSE,
|
|
fsm.GetMappedHandle(),
|
|
fsm.m_dwBytesRead,
|
|
fsm.m_lpBuffer,
|
|
fsm.m_dwNumberOfBytesToRead,
|
|
fsm.m_lpdwNumberOfBytesRead
|
|
);
|
|
fsm.SetDone();
|
|
|
|
quit:
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetReadFileExA(
|
|
IN HINTERNET hFile,
|
|
OUT LPINTERNET_BUFFERSA lpBuffersOut,
|
|
IN DWORD dwFlags,
|
|
IN DWORD_PTR dwContext
|
|
)
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetReadFileExA",
|
|
"%#x, %#x [%#x, %d], %#x, %#x",
|
|
hFile,
|
|
lpBuffersOut,
|
|
(lpBuffersOut ? lpBuffersOut->lpvBuffer : NULL),
|
|
(lpBuffersOut ? lpBuffersOut->dwBufferLength : 0),
|
|
dwFlags,
|
|
dwContext
|
|
));
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
DWORD nestingLevel = 0;
|
|
DWORD error;
|
|
HINTERNET hFileMapped = NULL;
|
|
DWORD bytesRead = 0;
|
|
LPVOID lpBuffer = NULL;
|
|
DWORD dwNumberOfBytesToRead;
|
|
BOOL bEndRead = TRUE;
|
|
BOOL success = TRUE;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// we need the thread info block
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
_InternetIncNestingCount();
|
|
nestingLevel = 1;
|
|
|
|
//
|
|
// map the handle
|
|
//
|
|
|
|
error = MapHandleToAddress(hFile, (LPVOID *)&hFileMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hFileMapped == NULL)) {
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// set the context, handle, and last-error info in the per-thread data block
|
|
// before we go any further. This allows us to return a status in the async
|
|
// case, even if the handle has been closed
|
|
//
|
|
|
|
DWORD_PTR context;
|
|
|
|
RGetContext(hFileMapped, &context);
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread) {
|
|
|
|
PERF_LOG(PE_CLIENT_REQUEST_START,
|
|
AR_INTERNET_READ_FILE,
|
|
lpThreadInfo->ThreadId,
|
|
hFile
|
|
);
|
|
|
|
}
|
|
|
|
_InternetSetContext(lpThreadInfo, context);
|
|
_InternetSetObjectHandle(lpThreadInfo, hFile, hFileMapped);
|
|
_InternetClearLastError(lpThreadInfo);
|
|
|
|
//
|
|
// if MapHandleToAddress() returned a non-NULL object address, but also an
|
|
// error status, then the handle is being closed - quit
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// validate handle and retrieve its type
|
|
//
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
|
|
error = RGetHandleType(hFileMapped, &handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto done;
|
|
}
|
|
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
|
|
error = RIsHandleLocal(hFileMapped, &isLocal, &isAsync, handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// we should not get an error - we already believe the handle object
|
|
// is valid and of the type just retrieved!
|
|
//
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// only accepting HTTP handles currently
|
|
//
|
|
|
|
if (handleType != TypeHttpRequestHandle) {
|
|
error = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
|
|
goto done;
|
|
}
|
|
|
|
HTTP_REQUEST_HANDLE_OBJECT * lpRequest;
|
|
|
|
lpRequest = (HTTP_REQUEST_HANDLE_OBJECT *)hFileMapped;
|
|
|
|
//
|
|
// validate params
|
|
//
|
|
|
|
if (lpBuffersOut->dwStructSize != sizeof(INTERNET_BUFFERS)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
lpBuffer = lpBuffersOut->lpvBuffer;
|
|
dwNumberOfBytesToRead = lpBuffersOut->dwBufferLength;
|
|
|
|
INET_ASSERT(dwNumberOfBytesToRead > 0);
|
|
|
|
//
|
|
// See if request can be fulfilled from file system.
|
|
//
|
|
|
|
if (lpRequest->AttemptReadFromFile(lpBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&bytesRead)) {
|
|
error = ERROR_SUCCESS;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// trap a zero-length buffer before we go to the trouble of going async.
|
|
// Maintain compatibility with base ReadFile(), although this is
|
|
// POTENTIALLY A BUG. ReadFile() is *supposed* to return TRUE and number
|
|
// of bytes read equal to zero to indicate end-of-file, but it will also
|
|
// return TRUE and zero if a read of zero bytes is requested. According
|
|
// to MarkL, that's the way it is. Good enough for me...
|
|
//
|
|
// For http, AttemptToReadFromFile traps zero-length reads
|
|
|
|
if (dwNumberOfBytesToRead == 0) {
|
|
|
|
//
|
|
// *lpdwNumberOfBytesRead and error should have been correctly set
|
|
// during parameter validation
|
|
//
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
goto quit;
|
|
}
|
|
|
|
//error = lpRequest->QuickSyncRead(
|
|
// lpBuffer,
|
|
// dwNumberOfBytesToRead,
|
|
// &bytesRead,
|
|
// SF_NO_WAIT
|
|
// );
|
|
//
|
|
//if ( error == ERROR_IO_PENDING )
|
|
//{
|
|
error = DoFsm(new CFsm_ReadFileEx(lpBuffersOut,
|
|
dwFlags,
|
|
dwContext
|
|
));
|
|
//}
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
bytesRead = lpBuffersOut->dwBufferLength;
|
|
}
|
|
bEndRead = FALSE;
|
|
|
|
quit:
|
|
|
|
_InternetDecNestingCount(nestingLevel);;
|
|
|
|
if (bEndRead) {
|
|
ReadFile_End(TRUE,
|
|
(error == ERROR_SUCCESS),
|
|
hFileMapped,
|
|
bytesRead,
|
|
lpBuffersOut->lpvBuffer,
|
|
dwNumberOfBytesToRead,
|
|
&lpBuffersOut->dwBufferLength
|
|
);
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
DEBUG_ERROR(API, error);
|
|
|
|
SetLastError(error);
|
|
success = FALSE;
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
DWORD
|
|
CFsm_ReadFileEx::RunSM(
|
|
IN CFsm * Fsm
|
|
)
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"CFsm_ReadFileEx::RunSM",
|
|
"%#x",
|
|
Fsm
|
|
));
|
|
|
|
DWORD error;
|
|
CFsm_ReadFileEx * stateMachine = (CFsm_ReadFileEx *)Fsm;
|
|
|
|
switch (Fsm->GetState()) {
|
|
case FSM_STATE_INIT:
|
|
case FSM_STATE_CONTINUE:
|
|
error = ReadFileEx_Fsm(stateMachine);
|
|
break;
|
|
|
|
default:
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
break;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
ReadFileEx_Fsm(
|
|
IN CFsm_ReadFileEx * Fsm
|
|
)
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
Dword,
|
|
"ReadFileEx_Fsm",
|
|
"%#x",
|
|
Fsm
|
|
));
|
|
|
|
CFsm_ReadFileEx & fsm = *Fsm;
|
|
DWORD error = fsm.GetError();
|
|
|
|
if ((error == ERROR_SUCCESS) && (fsm.GetState() == FSM_STATE_INIT)) {
|
|
fsm.m_dwNumberOfBytesToRead = fsm.m_lpBuffersOut->dwBufferLength;
|
|
error = HttpReadData(fsm.GetMappedHandle(),
|
|
fsm.m_lpBuffersOut->lpvBuffer,
|
|
fsm.m_dwNumberOfBytesToRead,
|
|
&fsm.m_dwBytesRead,
|
|
(fsm.m_dwFlags & IRF_NO_WAIT)
|
|
? SF_NO_WAIT
|
|
: 0
|
|
);
|
|
if (error == ERROR_IO_PENDING) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we are asynchronously completing a no-wait read then we don't update
|
|
// any app parameters - we simply return the indication that we completed.
|
|
// The app will then make another no-wait read to get the data
|
|
//
|
|
|
|
BOOL bNoOutput;
|
|
|
|
bNoOutput = ((fsm.m_dwFlags & IRF_NO_WAIT)
|
|
&& fsm.GetThreadInfo()->IsAsyncWorkerThread)
|
|
? TRUE
|
|
: FALSE;
|
|
|
|
ReadFile_End(!fsm.GetThreadInfo()->IsAsyncWorkerThread,
|
|
(error == ERROR_SUCCESS) ? TRUE : FALSE,
|
|
fsm.GetMappedHandle(),
|
|
bNoOutput ? 0 : fsm.m_dwBytesRead,
|
|
bNoOutput ? NULL : fsm.m_lpBuffersOut->lpvBuffer,
|
|
bNoOutput ? 0 : fsm.m_dwNumberOfBytesToRead,
|
|
bNoOutput ? NULL : &fsm.m_lpBuffersOut->dwBufferLength
|
|
);
|
|
fsm.SetDone();
|
|
|
|
quit:
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetWriteFile(
|
|
IN HINTERNET hFile,
|
|
IN LPCVOID lpBuffer,
|
|
IN DWORD dwNumberOfBytesToWrite,
|
|
OUT LPDWORD lpdwNumberOfBytesWritten
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function write next block of data to the internet file. Currently it
|
|
supports the following protocol data:
|
|
|
|
FtpWriteFile
|
|
HttpWriteFile
|
|
FileWriteFile
|
|
|
|
Arguments:
|
|
|
|
hFile - handle that was obtained by OpenFile Call
|
|
|
|
lpBuffer - pointer to the data buffer
|
|
|
|
dwNumberOfBytesToWrite - number of bytes in the above buffer
|
|
|
|
lpdwNumberOfBytesWritten - pointer to a DWORD where the number of bytes
|
|
of data actually written is returned
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetWriteFile",
|
|
"%#x, %#x, %d, %#x",
|
|
hFile,
|
|
lpBuffer,
|
|
dwNumberOfBytesToWrite,
|
|
lpdwNumberOfBytesWritten
|
|
));
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
DWORD nestingLevel = 0;
|
|
DWORD error;
|
|
BOOL success = FALSE;
|
|
BOOL fNeedDeref = TRUE;
|
|
HINTERNET hFileMapped = NULL;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// get the per-thread info block
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
_InternetIncNestingCount();
|
|
nestingLevel = 1;
|
|
|
|
//
|
|
// map the handle
|
|
//
|
|
|
|
error = MapHandleToAddress(hFile, (LPVOID *)&hFileMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hFileMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// set the context, handle, and last-error info in the per-thread data block
|
|
// before we go any further. This allows us to return a status in the async
|
|
// case, even if the handle has been closed
|
|
//
|
|
|
|
DWORD_PTR context;
|
|
|
|
RGetContext(hFileMapped, &context);
|
|
_InternetSetContext(lpThreadInfo, context);
|
|
_InternetSetObjectHandle(lpThreadInfo, hFile, hFileMapped);
|
|
_InternetClearLastError(lpThreadInfo);
|
|
|
|
//
|
|
// if MapHandleToAddress() returned a non-NULL object address, but also an
|
|
// error status, then the handle is being closed - quit
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate handle and retrieve its type
|
|
//
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
|
|
error = RGetHandleType(hFileMapped, &handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
|
|
error = RIsHandleLocal(hFileMapped, &isLocal, &isAsync, handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// we should not get an error - we already believe the handle object
|
|
// is valid and of the type just retrieved!
|
|
//
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate parameters - write length cannot be 0
|
|
//
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread) {
|
|
if (dwNumberOfBytesToWrite != 0) {
|
|
error = ProbeReadBuffer((LPVOID)lpBuffer, dwNumberOfBytesToWrite);
|
|
if (error == ERROR_SUCCESS) {
|
|
error = ProbeAndSetDword(lpdwNumberOfBytesWritten, 0);
|
|
}
|
|
} else {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
|
|
// # 62953
|
|
// If the authentication state of the handle is Negotiate,
|
|
// don't submit data to the server but return success.
|
|
// ** Added test for NTLM or Negotiate - Adriaanc.
|
|
if (handleType == TypeHttpRequestHandle)
|
|
{
|
|
HTTP_REQUEST_HANDLE_OBJECT *pRequest;
|
|
pRequest = (HTTP_REQUEST_HANDLE_OBJECT*) hFileMapped;
|
|
|
|
if (pRequest->GetAuthState() == AUTHSTATE_NEGOTIATE
|
|
&& !((PLUG_CTX*) (pRequest->GetAuthCtx()))->_fNTLMProxyAuth
|
|
&& !(pRequest->GetAuthCtx()->GetSchemeType() == AUTHCTX::SCHEME_DPA))
|
|
{
|
|
*lpdwNumberOfBytesWritten = dwNumberOfBytesToWrite;
|
|
error = ERROR_SUCCESS;
|
|
success = TRUE;
|
|
goto quit;
|
|
}
|
|
}
|
|
//
|
|
// we have to do some work. If the file object handle was created with
|
|
// async I/O capability then we will queue an async request, otherwise
|
|
// we will process the request synchronously
|
|
//
|
|
|
|
if (isAsync
|
|
&& !lpThreadInfo->IsAsyncWorkerThread
|
|
&& (handleType != TypeHttpRequestHandle)
|
|
&& (handleType != TypeFileRequestHandle)) {
|
|
|
|
// MakeAsyncRequest
|
|
CFsm_InternetWriteFile * pFsm;
|
|
|
|
pFsm = new CFsm_InternetWriteFile(hFile, lpBuffer, dwNumberOfBytesToWrite, lpdwNumberOfBytesWritten);
|
|
if (pFsm != NULL) {
|
|
error = pFsm->QueueWorkItem();
|
|
if ( error == ERROR_IO_PENDING ) {
|
|
fNeedDeref = FALSE;
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// if we're here then ERROR_SUCCESS cannot have been returned from
|
|
// the above calls
|
|
//
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("processing request asynchronously: error = %d\n",
|
|
error
|
|
));
|
|
|
|
goto quit;
|
|
|
|
//
|
|
// we're going synchronous. Change error to ERROR_SUCCESS so that we do
|
|
// the right thing at quit
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
switch (handleType) {
|
|
case TypeFtpFileHandle:
|
|
success = FtpWriteFile(hFileMapped,
|
|
(LPVOID)lpBuffer,
|
|
dwNumberOfBytesToWrite,
|
|
lpdwNumberOfBytesWritten
|
|
);
|
|
break;
|
|
|
|
case TypeHttpRequestHandle:
|
|
error = HttpWriteData(hFileMapped,
|
|
(LPVOID)lpBuffer,
|
|
dwNumberOfBytesToWrite,
|
|
lpdwNumberOfBytesWritten,
|
|
0
|
|
);
|
|
//
|
|
// Don't Derefrence if we're going pending cause the FSM will do
|
|
// it for us.
|
|
//
|
|
|
|
if ( error == ERROR_IO_PENDING )
|
|
{
|
|
fNeedDeref = FALSE;
|
|
}
|
|
success = (error == ERROR_SUCCESS) ? TRUE : FALSE;
|
|
//bEndRead = FALSE;
|
|
break;
|
|
|
|
case TypeFileRequestHandle:
|
|
|
|
success = WriteFile(((INTERNET_FILE_HANDLE_OBJECT *) hFileMapped)->GetFileHandle(),
|
|
(LPVOID)lpBuffer,
|
|
dwNumberOfBytesToWrite,
|
|
lpdwNumberOfBytesWritten,
|
|
NULL // overlapped I/O
|
|
);
|
|
|
|
if ( !success )
|
|
{
|
|
error = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
error = ERROR_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
|
|
quit:
|
|
|
|
if (hFileMapped != NULL && fNeedDeref) {
|
|
DereferenceObject((LPVOID)hFileMapped);
|
|
}
|
|
|
|
_InternetDecNestingCount(nestingLevel);;
|
|
|
|
done:
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetWriteFileExA(
|
|
IN HINTERNET hFile,
|
|
IN LPINTERNET_BUFFERSA lpBuffersIn,
|
|
IN DWORD dwFlags,
|
|
IN DWORD_PTR dwContext
|
|
)
|
|
{
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(DWORD) InternetSetFilePointer(
|
|
IN HINTERNET hFile,
|
|
IN LONG lDistanceToMove,
|
|
IN PVOID pReserved,
|
|
IN DWORD dwMoveMethod,
|
|
IN DWORD_PTR dwContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets a file position for InternetReadFile. It is a synchronous call,
|
|
however subsequent calls to InternetReadFile may block or return
|
|
pending if the data is not available from the cache and the server
|
|
does not support random access.
|
|
|
|
Arguments:
|
|
|
|
hFile
|
|
A valid handle returned from a previous call to InternetOpenUrl
|
|
or a handle returned from HttpOpenRequest for a GET or HEAD method
|
|
and passed to HttpSendRequest. The handle must have been created
|
|
without INTERNET_FLAG_DONT_CACHE.
|
|
|
|
lDistanceToMove
|
|
|
|
Specifies the number of bytes to move the file pointer. A positive
|
|
value moves the pointer forward in the file and a negative value
|
|
moves it backward.
|
|
|
|
pReserved
|
|
Reserved, pass NULL.
|
|
|
|
dwMoveMethod
|
|
Specifies the starting point for the file pointer move. This
|
|
parameter can be one of the following values:
|
|
|
|
Value Meaning
|
|
FILE_BEGIN The starting point is zero or the beginning of the file.
|
|
If FILE_BEGIN is specified, DistanceToMove is interpreted
|
|
as an unsigned location for the new file pointer.
|
|
FILE_CURRENT The current value of the file pointer is the starting point.
|
|
FILE_END The current end-of-file position is the starting point.
|
|
This method will fail if the content length is unknown.
|
|
|
|
Return Value:
|
|
|
|
-1 on failure, else the current file position.
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Int,
|
|
"InternetSetFilePointer",
|
|
"%#x, %#x, %#x, %#x %#x",
|
|
hFile,
|
|
lDistanceToMove,
|
|
pReserved,
|
|
dwMoveMethod,
|
|
dwContext
|
|
));
|
|
|
|
DWORD dwNewPosition = (DWORD) -1L;
|
|
DWORD error;
|
|
HINTERNET hFileMapped = NULL;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto done;
|
|
}
|
|
|
|
// Validate parameters...
|
|
|
|
error = MapHandleToAddress(hFile, &hFileMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hFileMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
error = RGetHandleType(hFileMapped, &handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
switch (handleType) {
|
|
// case TypeFtpFileHandle:
|
|
// case TypeGopherFileHandle:
|
|
case TypeHttpRequestHandle:
|
|
break;
|
|
default:
|
|
error = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
|
|
goto quit;
|
|
}
|
|
|
|
HTTP_REQUEST_HANDLE_OBJECT *lpRequest;
|
|
lpRequest = (HTTP_REQUEST_HANDLE_OBJECT *) hFileMapped;
|
|
dwNewPosition = lpRequest->SetStreamPointer (lDistanceToMove, dwMoveMethod);
|
|
|
|
quit:
|
|
|
|
if (hFileMapped != NULL) {
|
|
DereferenceObject((LPVOID)hFileMapped);
|
|
}
|
|
|
|
done:
|
|
|
|
DEBUG_LEAVE_API(dwNewPosition);
|
|
|
|
return dwNewPosition;
|
|
}
|
|
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetQueryDataAvailable(
|
|
IN HINTERNET hFile,
|
|
OUT LPDWORD lpdwNumberOfBytesAvailable,
|
|
IN DWORD dwFlags,
|
|
IN DWORD_PTR dwContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines the amount of data currently available to be read on the handle
|
|
|
|
Arguments:
|
|
|
|
hFile - handle of internet object
|
|
|
|
lpdwNumberOfBytesAvailable - pointer to returned bytes available
|
|
|
|
dwFlags - flags controlling operation - FUTURE
|
|
|
|
dwContext - used to differentiate multiple requests - FUTURE
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetQueryDataAvailable",
|
|
"%#x, %#x, %#x, %#x",
|
|
hFile,
|
|
lpdwNumberOfBytesAvailable,
|
|
dwFlags,
|
|
dwContext
|
|
));
|
|
|
|
BOOL success;
|
|
DWORD error;
|
|
LPINTERNET_THREAD_INFO lpThreadInfo = NULL;
|
|
HINTERNET hFileMapped = NULL;
|
|
BOOL bDeref = TRUE;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
bDeref = FALSE;
|
|
goto quit;
|
|
}
|
|
|
|
INET_ASSERT(hFile);
|
|
|
|
//
|
|
// get the per-thread info block
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto quit;
|
|
}
|
|
|
|
//INET_ASSERT(lpThreadInfo->Fsm == NULL);
|
|
|
|
PERF_LOG(PE_CLIENT_REQUEST_START,
|
|
AR_INTERNET_QUERY_DATA_AVAILABLE,
|
|
lpThreadInfo->ThreadId,
|
|
hFile
|
|
);
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
error = MapHandleToAddress(hFile, &hFileMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hFileMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
INET_ASSERT(hFileMapped);
|
|
|
|
//
|
|
// set the context and handle values in the per-thread info block (this API
|
|
// can't return extended error info, so we don't care about it)
|
|
//
|
|
|
|
_InternetSetContext(lpThreadInfo,
|
|
((INTERNET_HANDLE_OBJECT *)hFileMapped)->GetContext()
|
|
);
|
|
_InternetSetObjectHandle(lpThreadInfo, hFile, hFileMapped);
|
|
|
|
//
|
|
// if the handle is invalid, quit now
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// validate rest of parameters
|
|
//
|
|
|
|
error = ProbeAndSetDword(lpdwNumberOfBytesAvailable, 0);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// get the handle type
|
|
//
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
|
|
handleType = ((HANDLE_OBJECT *)hFileMapped)->GetHandleType();
|
|
|
|
//
|
|
// find out if we're sync or async
|
|
//
|
|
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
|
|
error = RIsHandleLocal(hFileMapped, &isLocal, &isAsync, TypeWildHandle);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
// WinSE 4998. If there's no context on the handle, force the request to be synchronous.
|
|
//
|
|
if (isAsync && lpThreadInfo->Context == INTERNET_NO_CALLBACK)
|
|
{
|
|
DEBUG_PRINT(API,
|
|
ERROR,
|
|
("Zero context: Call is Synchronous\n"
|
|
));
|
|
isAsync = FALSE;
|
|
}
|
|
|
|
//
|
|
// since the async worker thread doesn't come back through this API, the
|
|
// following test is sufficient. Note that we only go async if there is
|
|
// no data currently available on the handle
|
|
//
|
|
|
|
BOOL dataAvailable;
|
|
|
|
dataAvailable = ((INTERNET_HANDLE_OBJECT *)hFileMapped)->IsDataAvailable();
|
|
|
|
BOOL eof;
|
|
|
|
eof = ((INTERNET_HANDLE_OBJECT *)hFileMapped)->IsEndOfFile();
|
|
|
|
if ((handleType != TypeHttpRequestHandle)
|
|
&& isAsync
|
|
&& !dataAvailable
|
|
&& !eof) {
|
|
|
|
INET_ASSERT(hFileMapped);
|
|
|
|
// MakeAsyncRequest
|
|
CFsm_InternetQueryDataAvailable * pFsm;
|
|
|
|
pFsm = new CFsm_InternetQueryDataAvailable(hFileMapped, lpdwNumberOfBytesAvailable, dwFlags, dwContext);
|
|
if (pFsm != NULL) {
|
|
error = pFsm->QueueWorkItem();
|
|
if (error == ERROR_IO_PENDING) {
|
|
bDeref = FALSE;
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// if we're here then ERROR_SUCCESS cannot have been returned from
|
|
// the above calls
|
|
//
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("processing request asynchronously: error = %d\n",
|
|
error
|
|
));
|
|
|
|
goto quit;
|
|
|
|
//
|
|
// we will continue along the synchronous path, in which case we
|
|
// need to set error back to ERROR_SUCCESS so that our exit
|
|
// processing (at quit) does the right thing
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
} else if (dataAvailable || eof) {
|
|
|
|
DWORD available;
|
|
|
|
available = ((INTERNET_HANDLE_OBJECT *)hFileMapped)->AvailableDataLength();
|
|
|
|
//
|
|
// we have immediate data; if the handle type is FTP or gopher find and
|
|
// the data is coming from cache, then we only want to indicate that a
|
|
// single (fixed-length) find structure is available
|
|
//
|
|
|
|
switch (((HANDLE_OBJECT *)hFileMapped)->GetHandleType()) {
|
|
case TypeFtpFindHandle:
|
|
available = min(available, sizeof(WIN32_FIND_DATA));
|
|
break;
|
|
|
|
case TypeGopherFindHandle:
|
|
available = min(available, sizeof(GOPHER_FIND_DATA));
|
|
break;
|
|
}
|
|
|
|
DEBUG_PRINT(API,
|
|
INFO,
|
|
("%d bytes are immediately available\n",
|
|
available
|
|
));
|
|
|
|
*lpdwNumberOfBytesAvailable = available;
|
|
success = TRUE;
|
|
goto finish;
|
|
}
|
|
|
|
INET_ASSERT(hFileMapped);
|
|
|
|
//
|
|
// sync path. wInternetQueryDataAvailable will set the last error code
|
|
// if it fails
|
|
//
|
|
|
|
if (handleType == TypeHttpRequestHandle) {
|
|
error = DoFsm(new CFsm_QueryAvailable(lpdwNumberOfBytesAvailable,
|
|
dwFlags,
|
|
dwContext
|
|
));
|
|
if (error == ERROR_SUCCESS) {
|
|
success = TRUE;
|
|
} else {
|
|
if (error == ERROR_IO_PENDING) {
|
|
bDeref = FALSE;
|
|
}
|
|
goto quit;
|
|
}
|
|
} else {
|
|
success = wInternetQueryDataAvailable(hFileMapped,
|
|
lpdwNumberOfBytesAvailable,
|
|
dwFlags,
|
|
dwContext
|
|
);
|
|
}
|
|
|
|
finish:
|
|
|
|
DEBUG_PRINT_API(API,
|
|
INFO,
|
|
("*lpdwNumberOfBytesAvailable (%#x) = %d\n",
|
|
lpdwNumberOfBytesAvailable,
|
|
*lpdwNumberOfBytesAvailable
|
|
));
|
|
|
|
if (bDeref && (hFileMapped != NULL)) {
|
|
DereferenceObject((LPVOID)hFileMapped);
|
|
}
|
|
|
|
if (lpThreadInfo) {
|
|
|
|
PERF_LOG(PE_CLIENT_REQUEST_END,
|
|
AR_INTERNET_QUERY_DATA_AVAILABLE,
|
|
*lpdwNumberOfBytesAvailable,
|
|
lpThreadInfo->ThreadId,
|
|
hFile
|
|
);
|
|
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
|
|
return success;
|
|
|
|
//
|
|
// we only come here if we are returning an error before calling
|
|
// wInternetQueryDataAvailable
|
|
//
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
quit:
|
|
|
|
DEBUG_ERROR(API, error);
|
|
|
|
SetLastError(error);
|
|
success = FALSE;
|
|
|
|
goto finish;
|
|
}
|
|
|
|
|
|
DWORD
|
|
CFsm_QueryAvailable::RunSM(
|
|
IN CFsm * Fsm
|
|
)
|
|
{
|
|
DEBUG_ENTER((DBG_HTTP,
|
|
Dword,
|
|
"CFsm_QueryAvailable::RunSM",
|
|
"%#x",
|
|
Fsm
|
|
));
|
|
|
|
DWORD error;
|
|
CFsm_QueryAvailable * stateMachine = (CFsm_QueryAvailable *)Fsm;
|
|
|
|
switch (Fsm->GetState()) {
|
|
case FSM_STATE_INIT:
|
|
case FSM_STATE_CONTINUE:
|
|
error = QueryAvailable_Fsm(stateMachine);
|
|
break;
|
|
|
|
default:
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
break;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
QueryAvailable_Fsm(
|
|
IN CFsm_QueryAvailable * Fsm
|
|
)
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
Dword,
|
|
"QueryAvailable_Fsm",
|
|
"%#x",
|
|
Fsm
|
|
));
|
|
|
|
CFsm_QueryAvailable & fsm = *Fsm;
|
|
DWORD error = fsm.GetError();
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
HTTP_REQUEST_HANDLE_OBJECT * pRequest;
|
|
|
|
pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)fsm.GetMappedHandle();
|
|
|
|
if (fsm.GetState() == FSM_STATE_INIT) {
|
|
error = pRequest->QueryDataAvailable(fsm.m_lpdwNumberOfBytesAvailable);
|
|
}
|
|
if (error == ERROR_SUCCESS) {
|
|
pRequest->SetAvailableDataLength(*fsm.m_lpdwNumberOfBytesAvailable);
|
|
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("%d bytes available\n",
|
|
*fsm.m_lpdwNumberOfBytesAvailable
|
|
));
|
|
|
|
fsm.SetApiData(*fsm.m_lpdwNumberOfBytesAvailable);
|
|
}
|
|
|
|
quit:
|
|
|
|
if (error != ERROR_IO_PENDING) {
|
|
fsm.SetDone();
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetFindNextFileA(
|
|
IN HINTERNET hFind,
|
|
OUT LPVOID lpBuffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the next directory entry in the listing identified by the handle
|
|
|
|
Arguments:
|
|
|
|
hFind - find handle, as returned by e.g. FtpFindFirstFile()
|
|
|
|
lpBuffer - pointer to buffer where next directory entry information will
|
|
be written. Contents of buffer may be different depending on
|
|
type of directory request, and protocol used (FTP/gopher/etc.)
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
|
|
Failure - FALSE. Call GetLastError() for more info
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetFindNextFileA",
|
|
"%#x, %#x",
|
|
hFind,
|
|
lpBuffer
|
|
));
|
|
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
DWORD nestingLevel = 0;
|
|
DWORD error;
|
|
BOOL success = FALSE;
|
|
BOOL fDeref = TRUE;
|
|
HINTERNET hFindMapped = NULL;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = ERROR_INTERNET_NOT_INITIALIZED;
|
|
goto done;
|
|
}
|
|
|
|
//
|
|
// we need the per-thread info block on all paths
|
|
//
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
_InternetIncNestingCount();
|
|
nestingLevel = 1;
|
|
|
|
//
|
|
// map the handle
|
|
//
|
|
|
|
error = MapHandleToAddress(hFind, (LPVOID *)&hFindMapped, FALSE);
|
|
if ((error != ERROR_SUCCESS) && (hFindMapped == NULL)) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// set the context, handle, and last-error info in the per-thread data block
|
|
// before we go any further. This allows us to return a status in the async
|
|
// case, even if the handle has been closed
|
|
//
|
|
|
|
DWORD_PTR context;
|
|
|
|
RGetContext(hFindMapped, &context);
|
|
_InternetSetContext(lpThreadInfo, context);
|
|
_InternetSetObjectHandle(lpThreadInfo, hFind, hFindMapped);
|
|
_InternetClearLastError(lpThreadInfo);
|
|
|
|
//
|
|
// if MapHandleToAddress() returned a non-NULL object address, but also an
|
|
// error status, then the handle is being closed - quit
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// retrieve handle type, and validate in the process
|
|
//
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
|
|
error = RGetHandleType(hFindMapped, &handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// get async/sync and local/remote
|
|
//
|
|
|
|
BOOL isLocal;
|
|
BOOL isAsync;
|
|
|
|
error = RIsHandleLocal(hFindMapped, &isLocal, &isAsync, handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// this should never happen - we just successfully called
|
|
// RGetHandleType(), so RIsHandleLocal() should have worked too
|
|
//
|
|
|
|
DEBUG_PRINT(INET,
|
|
ERROR,
|
|
("RIsHandleLocal() returns %d\n",
|
|
error
|
|
));
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// make sure the handle type is valid for this request. We only support
|
|
// FTP find handle and gopher find handle (both raw data)
|
|
//
|
|
|
|
if (!((handleType == TypeFtpFindHandle)
|
|
|| (handleType == TypeGopherFindHandle))) {
|
|
error = ERROR_INTERNET_INVALID_OPERATION;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// if we're not in an async worker thread context then probe the buffer. If
|
|
// we are in the async worker thread context, then we've already validated
|
|
// the buffer. If is has since become invalid, then the app will fail
|
|
//
|
|
|
|
if (!lpThreadInfo->IsAsyncWorkerThread) {
|
|
error = ProbeWriteBuffer(lpBuffer,
|
|
(handleType == TypeFtpFindHandle)
|
|
? sizeof(WIN32_FIND_DATA)
|
|
: sizeof(GOPHER_FIND_DATA)
|
|
);
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// if this is an async request and we are not an async worker thread
|
|
// then queue the request
|
|
//
|
|
|
|
if (isAsync) {
|
|
|
|
// MakeAsyncRequest
|
|
CFsm_InternetFindNextFile * pFsm;
|
|
|
|
pFsm = new CFsm_InternetFindNextFile(hFind, lpBuffer);
|
|
if (pFsm != NULL) {
|
|
error = pFsm->QueueWorkItem();
|
|
if ( error == ERROR_IO_PENDING ) {
|
|
fDeref = FALSE;
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// if we're here then ERROR_SUCCESS cannot have been returned from
|
|
// the above calls
|
|
//
|
|
|
|
INET_ASSERT(error != ERROR_SUCCESS);
|
|
|
|
|
|
DEBUG_PRINT(FTP,
|
|
INFO,
|
|
("processing request asynchronously: error = %d\n",
|
|
error
|
|
));
|
|
|
|
goto quit;
|
|
|
|
//
|
|
// we will continue along the synchronous path, in which case we
|
|
// need to set error back to ERROR_SUCCESS so that our exit
|
|
// processing (at quit) does the right thing
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// dispatch to the underlying API. Return what the API returns, and let
|
|
// the API SetLastError()
|
|
//
|
|
// N.B. We have already checked the handle type above, and we know at
|
|
// this stage that we have a correct handle type
|
|
//
|
|
|
|
INET_ASSERT(error == ERROR_SUCCESS);
|
|
|
|
switch (handleType) {
|
|
case TypeFtpFindHandle:
|
|
success = FtpFindNextFileA(hFindMapped,
|
|
(LPWIN32_FIND_DATA)lpBuffer
|
|
);
|
|
break;
|
|
|
|
case TypeGopherFindHandle:
|
|
success = GopherFindNextA(hFindMapped,
|
|
(LPGOPHER_FIND_DATA)lpBuffer
|
|
);
|
|
break;
|
|
}
|
|
|
|
quit:
|
|
|
|
if (hFindMapped != NULL && fDeref) {
|
|
DereferenceObject((LPVOID)hFindMapped);
|
|
}
|
|
|
|
_InternetDecNestingCount(nestingLevel);;
|
|
|
|
done:
|
|
|
|
//
|
|
// if error is not ERROR_SUCCESS then this function returning the error,
|
|
// otherwise the error has already been set by the API we called,
|
|
// irrespective of the value of success
|
|
//
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
DEBUG_LEAVE_API(success);
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetGetLastResponseInfoA(
|
|
OUT LPDWORD lpdwErrorCategory,
|
|
IN LPSTR lpszBuffer OPTIONAL,
|
|
IN OUT LPDWORD lpdwBufferLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function returns the per-thread last internet error description text
|
|
or server response.
|
|
|
|
If this function is successful, *lpdwBufferLength contains the string length
|
|
of lpszBuffer.
|
|
|
|
If this function returns a failure indication, *lpdwBufferLength contains
|
|
the number of BYTEs required to hold the response text
|
|
|
|
Arguments:
|
|
|
|
lpdwErrorCategory - pointer to DWORD location where the error catagory is
|
|
returned
|
|
|
|
lpszBuffer - pointer to buffer where the error text is returned
|
|
|
|
lpdwBufferLength - IN: length of lpszBuffer
|
|
OUT: number of characters in lpszBuffer if successful
|
|
else size of buffer required to hold response text
|
|
|
|
Return Value:
|
|
|
|
BOOL
|
|
Success - TRUE
|
|
lpszBuffer contains the error text. The caller must check
|
|
*lpdwBufferLength: if 0 then there was no text to return
|
|
|
|
Failure - FALSE
|
|
Call GetLastError() for more information
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetGetLastResponseInfoA",
|
|
"%#x, %#x, %#x [%d]",
|
|
lpdwErrorCategory,
|
|
lpszBuffer,
|
|
lpdwBufferLength,
|
|
lpdwBufferLength ? *lpdwBufferLength : 0
|
|
));
|
|
|
|
DWORD error;
|
|
BOOL success;
|
|
DWORD textLength;
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
|
|
//
|
|
// validate parameters
|
|
//
|
|
|
|
if (IsBadWritePtr(lpdwErrorCategory, sizeof(*lpdwErrorCategory))
|
|
|| IsBadWritePtr(lpdwBufferLength, sizeof(*lpdwBufferLength))
|
|
|| (ARGUMENT_PRESENT(lpszBuffer)
|
|
? IsBadWritePtr(lpszBuffer, *lpdwBufferLength)
|
|
: FALSE)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// if the buffer pointer is NULL then its the same as a zero-length buffer
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(lpszBuffer)) {
|
|
*lpdwBufferLength = 0;
|
|
} else if (*lpdwBufferLength != 0) {
|
|
*lpszBuffer = '\0';
|
|
}
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
DEBUG_PRINT(INET,
|
|
ERROR,
|
|
("failed to get INTERNET_THREAD_INFO\n"
|
|
));
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto quit;
|
|
}
|
|
|
|
//
|
|
// there may not be any error text for this thread - either no server
|
|
// error/response has been received, or the error text has been cleared by
|
|
// an intervening API
|
|
//
|
|
|
|
if (lpThreadInfo->hErrorText != NULL) {
|
|
|
|
//
|
|
// copy as much as we can fit in the user supplied buffer
|
|
//
|
|
|
|
textLength = lpThreadInfo->ErrorTextLength;
|
|
if (*lpdwBufferLength) {
|
|
|
|
LPBYTE errorText;
|
|
|
|
errorText = (LPBYTE)LOCK_MEMORY(lpThreadInfo->hErrorText);
|
|
if (errorText != NULL) {
|
|
textLength = min(textLength, *lpdwBufferLength) - 1;
|
|
memcpy(lpszBuffer, errorText, textLength);
|
|
|
|
//
|
|
// the error text should always be zero terminated, so the
|
|
// calling app can treat it as a string
|
|
//
|
|
|
|
lpszBuffer[textLength] = '\0';
|
|
|
|
UNLOCK_MEMORY(lpThreadInfo->hErrorText);
|
|
|
|
if (textLength == lpThreadInfo->ErrorTextLength - 1) {
|
|
error = ERROR_SUCCESS;
|
|
} else {
|
|
|
|
//
|
|
// returned length is amount of buffer required
|
|
//
|
|
|
|
textLength = lpThreadInfo->ErrorTextLength;
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
} else {
|
|
|
|
DEBUG_PRINT(INET,
|
|
ERROR,
|
|
("failed to lock hErrorText (%#x): %d\n",
|
|
lpThreadInfo->hErrorText,
|
|
GetLastError()
|
|
));
|
|
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// user's buffer is not large enough to hold the info. We'll
|
|
// let them know the required length
|
|
//
|
|
|
|
error = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
} else {
|
|
|
|
INET_ASSERT(lpThreadInfo->ErrorTextLength == 0);
|
|
|
|
textLength = 0;
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
*lpdwErrorCategory = lpThreadInfo->ErrorNumber;
|
|
*lpdwBufferLength = textLength;
|
|
|
|
IF_DEBUG(ANY) {
|
|
if ((error == ERROR_SUCCESS)
|
|
|| ((textLength != 0) && (lpszBuffer != NULL))) {
|
|
|
|
DEBUG_DUMP_API(API,
|
|
"Last Response Info:\n",
|
|
lpszBuffer,
|
|
textLength
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
quit:
|
|
success = (error == ERROR_SUCCESS);
|
|
if (!success) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(success);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(INTERNET_STATUS_CALLBACK) InternetSetStatusCallbackCore(
|
|
IN HINTERNET hInternet,
|
|
IN INTERNET_STATUS_CALLBACK lpfnInternetCallback,
|
|
IN BOOL fType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the status callback function for the DLL or the handle object
|
|
|
|
Arguments:
|
|
|
|
hInternet - handle of the object for which we wish to set the
|
|
status callback
|
|
|
|
lpfnInternetCallback - pointer to caller-supplied status function
|
|
|
|
Return Value:
|
|
|
|
FARPROC
|
|
Success - previous status callback function address
|
|
|
|
Failure - INTERNET_INVALID_STATUS_CALLBACK. Call GetLastErrorInfo() for
|
|
more information:
|
|
|
|
ERROR_INVALID_PARAMETER
|
|
The callback function is invalid
|
|
|
|
ERROR_INTERNET_INCORRECT_HANDLE_TYPE
|
|
Cannot set the callback on the supplied handle (probably
|
|
a NULL handle - per-process callbacks no longer
|
|
supported)
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD dwErr = ERROR_SUCCESS;
|
|
INTERNET_STATUS_CALLBACK previousCallback = INTERNET_INVALID_STATUS_CALLBACK;
|
|
HINTERNET hObjectMapped = NULL;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
dwErr = GlobalDataInitialize();
|
|
if (dwErr != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if ((lpfnInternetCallback != NULL) && IsBadCodePtr((FARPROC)lpfnInternetCallback))
|
|
{
|
|
dwErr = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
if (!hInternet)
|
|
{
|
|
dwErr = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// map the handle
|
|
//
|
|
|
|
dwErr = MapHandleToAddress(hInternet, (LPVOID *)&hObjectMapped, FALSE);
|
|
if (dwErr == ERROR_SUCCESS)
|
|
{
|
|
//
|
|
// swap the new and previous handle object status callbacks, ONLY
|
|
// if there are no pending requests on this handle
|
|
//
|
|
previousCallback = lpfnInternetCallback;
|
|
dwErr = RExchangeStatusCallback(hObjectMapped, &previousCallback, fType);
|
|
}
|
|
|
|
if (hObjectMapped != NULL) {
|
|
DereferenceObject((LPVOID)hObjectMapped);
|
|
}
|
|
|
|
cleanup:
|
|
if (dwErr!=ERROR_SUCCESS)
|
|
{
|
|
SetLastError(dwErr);
|
|
DEBUG_ERROR(API, dwErr);
|
|
}
|
|
return previousCallback;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(INTERNET_STATUS_CALLBACK) InternetSetStatusCallbackA(
|
|
IN HINTERNET hInternet,
|
|
IN INTERNET_STATUS_CALLBACK lpfnInternetCallback
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the status callback function for the DLL or the handle object
|
|
|
|
Arguments:
|
|
|
|
hInternet - handle of the object for which we wish to set the
|
|
status callback
|
|
|
|
lpfnInternetCallback - pointer to caller-supplied status function
|
|
|
|
Return Value:
|
|
|
|
FARPROC
|
|
Success - previous status callback function address
|
|
|
|
Failure - INTERNET_INVALID_STATUS_CALLBACK. Call GetLastErrorInfo() for
|
|
more information:
|
|
|
|
ERROR_INVALID_PARAMETER
|
|
The callback function is invalid
|
|
|
|
ERROR_INTERNET_INCORRECT_HANDLE_TYPE
|
|
Cannot set the callback on the supplied handle (probably
|
|
a NULL handle - per-process callbacks no longer
|
|
supported)
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_INET,
|
|
Pointer,
|
|
"InternetSetStatusCallbackA",
|
|
"%#x, %#x",
|
|
hInternet,
|
|
lpfnInternetCallback
|
|
));
|
|
|
|
INTERNET_STATUS_CALLBACK previousCallback = InternetSetStatusCallbackCore(
|
|
hInternet,
|
|
lpfnInternetCallback,
|
|
FALSE
|
|
);
|
|
|
|
DEBUG_LEAVE_API(previousCallback);
|
|
return previousCallback;
|
|
}
|
|
|
|
|
|
//
|
|
//INTERNETAPI_(BOOL) InternetCancelAsyncRequest(
|
|
// IN DWORD dwAsyncId
|
|
// )
|
|
//
|
|
///*++
|
|
//
|
|
//Routine Description:
|
|
//
|
|
// Cancels an outstanding async request
|
|
//
|
|
//Arguments:
|
|
//
|
|
// dwAsyncId - identifier of the async I/O request
|
|
//
|
|
//Return Value:
|
|
//
|
|
// BOOL
|
|
// Success - TRUE
|
|
// Request was cancelled
|
|
//
|
|
// Failure - FALSE
|
|
// Call GetLastError() for more information
|
|
//
|
|
//--*/
|
|
//
|
|
//{
|
|
// DEBUG_ENTER((DBG_INET,
|
|
// Bool,
|
|
// "InternetCancelAsyncRequest",
|
|
// "%d",
|
|
// dwAsyncId
|
|
// ));
|
|
//
|
|
// DWORD error;
|
|
// BOOL success;
|
|
//
|
|
// error = CancelAsyncRequest(dwAsyncId);
|
|
// if (error != ERROR_SUCCESS) {
|
|
//
|
|
// DEBUG_ERROR(INET, error);
|
|
//
|
|
// SetLastError(error);
|
|
// success = FALSE;
|
|
// } else {
|
|
// success = TRUE;
|
|
// }
|
|
//
|
|
// DEBUG_LEAVE(success);
|
|
//
|
|
// return success;
|
|
//}
|
|
|
|
//
|
|
// private functions
|
|
//
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
wInternetCloseConnectA(
|
|
IN HINTERNET hConnect,
|
|
IN DWORD dwService
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The obverse of InternetConnect(). Closes the handle created in
|
|
InternetConnect()
|
|
|
|
Arguments:
|
|
|
|
hConnect - protocol handle created in InternetConnect()
|
|
|
|
dwService - service required. Controls type of handle generated.
|
|
May be one of:
|
|
- INTERNET_SERVICE_FTP
|
|
- INTERNET_SERVICE_GOPHER
|
|
- INTERNET_SERVICE_HTTP
|
|
|
|
Return Value:
|
|
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INVALID_PARAMETER
|
|
Incorrect dwService parameter (*never* expect this)
|
|
|
|
Windows error
|
|
Wininet error
|
|
WSA error
|
|
Error from protocol-specific disconnect function
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER((DBG_INET,
|
|
Dword,
|
|
"wInternetCloseConnectA",
|
|
"%#x, %d",
|
|
hConnect,
|
|
dwService
|
|
));
|
|
|
|
DWORD error;
|
|
|
|
switch (dwService) {
|
|
case INTERNET_SERVICE_FTP :
|
|
error = wFtpDisconnect(hConnect, CF_EXPEDITED_CLOSE);
|
|
break;
|
|
|
|
case INTERNET_SERVICE_GOPHER :
|
|
//error = wGopherDisconnect(hConnect);
|
|
error = ERROR_SUCCESS;
|
|
break;
|
|
|
|
case INTERNET_SERVICE_HTTP:
|
|
//error = wHttpConnectClose((LPHINTERNET)hConnect);
|
|
error = ERROR_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
error = ERROR_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
GetEmailNameAndPassword(
|
|
IN OUT LPSTR* lplpszUserName,
|
|
IN OUT LPSTR* lplpszPassword,
|
|
OUT LPSTR EmailName,
|
|
IN DWORD EmailNameLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the login name and password for the FTP server (but can be used for any
|
|
other protocol)
|
|
|
|
Arguments:
|
|
|
|
lplpszUserName - IN: pointer to pointer to user name
|
|
OUT: pointer to pointer to user name; may be modified
|
|
|
|
lplpszPassword - IN: pointer to pointer to password
|
|
OUT: pointer to pointer to password; may be modified
|
|
|
|
EmailName - pointer to buffer in which to store password if
|
|
"anonymous" returned for login name
|
|
|
|
EmailNameLength - length of EmailName
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INVALID_PARAMETER
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD error;
|
|
LPSTR lpszUserName;
|
|
LPSTR lpszPassword;
|
|
|
|
lpszUserName = *lplpszUserName;
|
|
lpszPassword = *lplpszPassword;
|
|
|
|
//
|
|
// validate username and password arguments. Valid combinations are:
|
|
// (N.B. NULL means NULL pointer or NUL string)
|
|
//
|
|
// lpszUsername lpszPassword Result
|
|
//
|
|
// NULL NULL "anonymous", "emailname@domain"
|
|
// !NULL NULL lpszUserName, NULL
|
|
// NULL !NULL ERROR
|
|
// !NULL !NULL lpszUserName, lpszPassword
|
|
//
|
|
|
|
error = ERROR_SUCCESS;
|
|
if (lpszUserName != NULL) {
|
|
if (IsBadStringPtr(lpszUserName, INTERNET_MAX_USER_NAME_LENGTH)) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
} else if (*lpszUserName == '\0') {
|
|
lpszUserName = NULL;
|
|
}
|
|
}
|
|
if (error == ERROR_SUCCESS) {
|
|
if (lpszPassword != NULL) {
|
|
if (IsBadStringPtr(lpszPassword, INTERNET_MAX_PASSWORD_LENGTH)) {
|
|
error = ERROR_INVALID_PASSWORD;
|
|
} else if (*lpszPassword == '\0') {
|
|
lpszPassword = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (error == ERROR_SUCCESS) {
|
|
if (lpszPassword == NULL) {
|
|
if (lpszUserName == NULL) {
|
|
|
|
DWORD length;
|
|
|
|
//
|
|
// both name and password are null pointers. We will convert to
|
|
// "anonymous", "EmailName@DomainName"
|
|
//
|
|
|
|
//
|
|
// because we don't require a client to be running TCP/IP, we
|
|
// may be unable to get a domain name. Hence we now require the
|
|
// EmailName entry in the registry to contain the entire
|
|
// EmailName@DomainName string, including the '@'. If this is
|
|
// not available, then we will just return an error
|
|
//
|
|
|
|
lpszUserName = "anonymous";
|
|
length = EmailNameLength;
|
|
error = GetMyEmailName(EmailName, &EmailNameLength);
|
|
if (error == ERROR_SUCCESS) {
|
|
lpszPassword = EmailName;
|
|
}
|
|
} else {
|
|
lpszPassword = "";
|
|
}
|
|
} else if (lpszUserName == NULL) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
*lplpszUserName = lpszUserName;
|
|
*lplpszPassword = lpszPassword;
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(DWORD) InternetAttemptConnect(
|
|
IN DWORD dwReserved
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine attempts to make a loopback socket
|
|
Clients call this to either invoke the dialdialog or to see whether
|
|
they are connected to the net (??).
|
|
|
|
4/29/97 (darrenmi) This function now calls InternetAutodial to see if a
|
|
connection needs to be made.
|
|
|
|
This function
|
|
Arguments:
|
|
|
|
|
|
dwReserved - ?
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
|
|
Windows error code, or sockets error code
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Dword,
|
|
"InternetAttemptConnect",
|
|
"%d",
|
|
dwReserved
|
|
));
|
|
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = GlobalDataInitialize();
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
if(!InternetAutodial(0, 0)) {
|
|
error = ERROR_GEN_FAILURE;
|
|
}
|
|
|
|
quit:
|
|
DEBUG_LEAVE_API(error);
|
|
return error;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetLockRequestFile(
|
|
IN HINTERNET hInternet,
|
|
OUT HANDLE *lphLockReqHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allows the caller to place a lock on the file that he is
|
|
using by doing a CreateFile. This ensures that if this file is associated
|
|
with this url, and another download on this url tries to commit another
|
|
file, then this file won't vanish because the cache does a safe delete
|
|
when updating or deleting the cache entry.
|
|
The caller can then call the InternetUnlockRequestFile to give wininet
|
|
the permission to delete this file if not committed to the cache.
|
|
|
|
Arguments:
|
|
hInternet request object which is doing the download
|
|
lphLocReqHandle place to return LockRequestHandle
|
|
|
|
Return Value:
|
|
TRUE - Success
|
|
FALSE - failure, GetLastError returns the error code
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetLockRequest",
|
|
"%#x, %#x",
|
|
hInternet,
|
|
lphLockReqHandle
|
|
));
|
|
|
|
DWORD error, dwSize, dwUrlLenPlus1, dwFileLenPlus1;
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
HINTERNET hObjectMapped = NULL;
|
|
INTERNET_CONNECT_HANDLE_OBJECT * pConnect;
|
|
LPLOCK_REQUEST_INFO lpLockReqInfo = NULL;
|
|
LPSTR lpSource;
|
|
BOOL locked = FALSE;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = GlobalDataInitialize();
|
|
if (error != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
error = MapHandleToAddress(hInternet, (LPVOID *)&hInternet, FALSE);
|
|
if (error == ERROR_SUCCESS) {
|
|
hObjectMapped = hInternet;
|
|
pConnect = (INTERNET_CONNECT_HANDLE_OBJECT *)hInternet;
|
|
error = RGetHandleType(hInternet, &handleType);
|
|
}
|
|
if (error != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((handleType == TypeGenericHandle)||
|
|
(handleType == TypeInternetHandle)||
|
|
(handleType == TypeFtpConnectHandle)||
|
|
(handleType == TypeGopherConnectHandle)||
|
|
(handleType == TypeHttpConnectHandle) ||
|
|
(handleType == TypeFileRequestHandle)) {
|
|
|
|
error = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
EnterCriticalSection(&LockRequestFileCritSec);
|
|
locked = TRUE;
|
|
|
|
//
|
|
// If a lock handle was already created, simply increment the refcount.
|
|
//
|
|
|
|
if(lpLockReqInfo = (LPLOCK_REQUEST_INFO)(pConnect->GetLockRequestHandle())) {
|
|
|
|
lpLockReqInfo->dwCount++;
|
|
*lphLockReqHandle = (HANDLE)lpLockReqInfo;
|
|
error = ERROR_SUCCESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Record the URL and associated filename in the lock handle.
|
|
//
|
|
|
|
lpSource = pConnect->GetDataFileName();
|
|
if (!lpSource) {
|
|
error = ERROR_FILE_NOT_FOUND;
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwSize = sizeof(LOCK_REQUEST_INFO)
|
|
+(dwUrlLenPlus1 = lstrlen(pConnect->GetCacheKey())+1)
|
|
+(dwFileLenPlus1 = lstrlen(lpSource)+1)+3; // atmost 3 bytes slop
|
|
|
|
lpLockReqInfo = (LPLOCK_REQUEST_INFO)ALLOCATE_MEMORY(LPTR, dwSize);
|
|
if (!lpLockReqInfo) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
lpLockReqInfo->dwSignature = LOCK_REQUEST_SIGNATURE;
|
|
lpLockReqInfo->dwSize = dwSize;
|
|
lpLockReqInfo->fNoCacheLookup = FALSE;
|
|
|
|
memcpy(lpLockReqInfo->rgBuff, pConnect->GetCacheKey(), dwUrlLenPlus1);
|
|
lpLockReqInfo->UrlName = lpLockReqInfo->rgBuff;
|
|
|
|
// align filename to DWORD
|
|
lpLockReqInfo->FileName = &(lpLockReqInfo->rgBuff[((dwUrlLenPlus1+sizeof(DWORD)) & ~(3))]);
|
|
memcpy(lpLockReqInfo->FileName, lpSource, dwFileLenPlus1);
|
|
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("Url==%s, File== %s\n",
|
|
lpLockReqInfo->UrlName,
|
|
lpLockReqInfo->FileName
|
|
));
|
|
|
|
//
|
|
// Open the file so it will not be deleted upon cache entry delete/update.
|
|
//
|
|
|
|
lpLockReqInfo->hFile = CreateFile (
|
|
lpSource,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL );
|
|
if (lpLockReqInfo->hFile == INVALID_HANDLE_VALUE) {
|
|
error = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Set refcount to 2, one for connect handle and the other for lock handle.
|
|
// Whichever one is closed last will perform cleanup.
|
|
//
|
|
|
|
lpLockReqInfo->dwCount = 2;
|
|
*lphLockReqHandle = (HANDLE)lpLockReqInfo;
|
|
pConnect->SetLockRequestHandle((HANDLE)lpLockReqInfo);
|
|
|
|
// Check to see if this file corresponds to an installed cache
|
|
// entry. If so, set fNoDelete so unlocking cannot delete the file.
|
|
// Note - because installed cache entries are generally not downloaded
|
|
// by wininet this is ok to do - it is only necessary to check if there
|
|
// is a cache entry in the request object and if it is an installed type.
|
|
if (handleType == TypeHttpRequestHandle)
|
|
{
|
|
LPCACHE_ENTRY_INFO pcei;
|
|
pcei = ((HTTP_REQUEST_HANDLE_OBJECT*) pConnect)->GetCacheEntryInfo();
|
|
if (pcei)
|
|
{
|
|
if (pcei->CacheEntryType & INSTALLED_CACHE_ENTRY)
|
|
lpLockReqInfo->fNoDelete = TRUE;
|
|
}
|
|
}
|
|
|
|
error = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
BOOL fRet = (error==ERROR_SUCCESS);
|
|
|
|
if (!fRet) {
|
|
|
|
if (lpLockReqInfo) {
|
|
FREE_MEMORY(lpLockReqInfo);
|
|
}
|
|
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
else {
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("Url==%s, File== %s RefCount=%d, Handle = %#x\n",
|
|
lpLockReqInfo->UrlName,
|
|
lpLockReqInfo->FileName,
|
|
lpLockReqInfo->dwCount,
|
|
*lphLockReqHandle
|
|
));
|
|
|
|
}
|
|
|
|
if (locked) {
|
|
LeaveCriticalSection(&LockRequestFileCritSec);
|
|
}
|
|
|
|
if (hObjectMapped) {
|
|
DereferenceObject((LPVOID)hObjectMapped);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(fRet);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetUnlockRequestFile(
|
|
IN HANDLE hLockHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allows the caller to unlock a request file that was locked
|
|
using the InternetLockRequestFile routine. This allows the file
|
|
to be deleted after the request object is long gone.
|
|
|
|
Arguments:
|
|
|
|
hLockHandle Lock Request Handle that was returned in InternetLockRequestFile
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success
|
|
FALSE - failure, GetLastError returns the error code
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetUnlockRequest",
|
|
"%#x",
|
|
hLockHandle
|
|
));
|
|
|
|
DWORD error, dwUrlLen, dwFileNameLen;
|
|
LPLOCK_REQUEST_INFO lpLockReqInfo;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
error = GlobalDataInitialize();
|
|
if (error != ERROR_SUCCESS) {
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
EnterCriticalSection(&LockRequestFileCritSec);
|
|
|
|
lpLockReqInfo = (LPLOCK_REQUEST_INFO)hLockHandle;
|
|
|
|
__try {
|
|
if (lpLockReqInfo->dwSignature == LOCK_REQUEST_SIGNATURE) {
|
|
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("Url==%s, File== %s, refcount=%d\n",
|
|
lpLockReqInfo->UrlName,
|
|
lpLockReqInfo->FileName,
|
|
lpLockReqInfo->dwCount
|
|
));
|
|
|
|
if (--lpLockReqInfo->dwCount == 0) {
|
|
|
|
if (!CloseHandle(lpLockReqInfo->hFile)) {
|
|
|
|
DEBUG_PRINT(INET,
|
|
ERROR,
|
|
("Error=%d while Closing OpenHandle for file=%s for url=%s\n",
|
|
GetLastError(),
|
|
lpLockReqInfo->FileName,
|
|
lpLockReqInfo->UrlName
|
|
));
|
|
|
|
}
|
|
|
|
if (!lpLockReqInfo->fNoDelete) {
|
|
|
|
//
|
|
// Validate URL and filename.
|
|
//
|
|
|
|
dwUrlLen = lstrlen(lpLockReqInfo->UrlName);
|
|
dwFileNameLen = lstrlen(lpLockReqInfo->FileName);
|
|
|
|
//
|
|
// Check if there is a cache entry for the URL.
|
|
//
|
|
|
|
DWORD dwSize;
|
|
LPINTERNET_CACHE_ENTRY_INFO pCEI;
|
|
char buf[sizeof(INTERNET_CACHE_ENTRY_INFO)+MAX_PATH+1];
|
|
|
|
pCEI = (LPINTERNET_CACHE_ENTRY_INFO)buf;
|
|
dwSize = sizeof(buf);
|
|
|
|
if (lpLockReqInfo->fNoCacheLookup) {
|
|
|
|
error = ERROR_FILE_NOT_FOUND;
|
|
|
|
} else {
|
|
// Grab info and
|
|
// Check if the filename actually matches.
|
|
error = GetUrlCacheEntryInfoEx(lpLockReqInfo->UrlName,
|
|
pCEI,
|
|
&dwSize,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
INTERNET_CACHE_FLAG_ADD_FILENAME_ONLY) ?
|
|
(lstrcmpi(lpLockReqInfo->FileName, pCEI->lpszLocalFileName) ?
|
|
ERROR_FILE_NOT_FOUND
|
|
: ERROR_SUCCESS)
|
|
: GetLastError();
|
|
} // end else if (!lpLockReqInfo->fNoCacheLookup)
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// The file was not committed to cache, so attempt to delete it.
|
|
//
|
|
|
|
DEBUG_PRINT(INET, INFO,("deleting %q\n",lpLockReqInfo->FileName));
|
|
|
|
if (!DeleteFile(lpLockReqInfo->FileName)) {
|
|
|
|
DEBUG_PRINT(INET,
|
|
ERROR,
|
|
("Error=%d while deleting file=%s for url=%s\n",
|
|
GetLastError(),
|
|
lpLockReqInfo->FileName,
|
|
lpLockReqInfo->UrlName
|
|
));
|
|
|
|
if (lpLockReqInfo->fNoCacheLookup) {
|
|
|
|
switch (GetLastError()) {
|
|
case ERROR_SHARING_VIOLATION:
|
|
case ERROR_ACCESS_DENIED:
|
|
UrlCacheAddLeakFile (lpLockReqInfo->FileName);
|
|
}
|
|
}
|
|
|
|
} // end if (!DeleteFile(...))
|
|
}
|
|
} // end if (!lpLockReqInfo->fNoDelete)
|
|
|
|
FREE_MEMORY(lpLockReqInfo);
|
|
error = ERROR_SUCCESS;
|
|
|
|
} else {
|
|
|
|
DEBUG_PRINT(INET,
|
|
INFO,
|
|
("Quitting after decrementing refcount, new refcount=%d\n",
|
|
lpLockReqInfo->dwCount
|
|
));
|
|
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
} else {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
|
error = ERROR_INVALID_PARAMETER;
|
|
}
|
|
ENDEXCEPT
|
|
|
|
LeaveCriticalSection(&LockRequestFileCritSec);
|
|
|
|
quit:
|
|
BOOL fRet = (error==ERROR_SUCCESS);
|
|
|
|
if (fRet) {
|
|
DEBUG_ERROR(API, error);
|
|
SetLastError(error);
|
|
}
|
|
|
|
DEBUG_LEAVE_API(fRet);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
INTERNETAPI_(BOOL) InternetCheckConnectionA(
|
|
IN LPCSTR lpszUrl,
|
|
IN DWORD dwFlags,
|
|
IN DWORD dwReserved
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine tells the caller whether he can establish a connection to the
|
|
network. If no URL is specified and dwFlags are set to NULL then wininet
|
|
a) checks whether it has an outstanding socket connection and if so
|
|
then the API returns TRUE.
|
|
b) If there are no outstanding socket connections then a check
|
|
is made in the wininet serverdatabase for servers which were
|
|
connected to in the recent past. If one is found then the API returns TRUE.
|
|
|
|
If neither a) or b) succeeds the API returns FALSE.
|
|
|
|
Arguments:
|
|
|
|
lpszUrl this parameter is an indication to the API to attempt
|
|
a specific host. The use of this parameter is based on the
|
|
flags set in the dwFlags parameter
|
|
|
|
dwFlags a bitwise OR of the following flags
|
|
|
|
INTERNET_FLAG_ICC_FORCE_CONNECTION - force a connection
|
|
A sockets connection is attempted in the following order
|
|
1) If lpszUrl is non-NULL then a host value is extracted
|
|
fromt it used that to ping the specific host
|
|
2) If the lpszUrl parameter is NULL then if there is an
|
|
entry in the wininet's internal server database for
|
|
the nearest server, it is used to do the pinging
|
|
|
|
If neither of these are available then ERROR_NOT_CONNECTED is
|
|
returned in GetLastError()
|
|
|
|
dwReserved reserved
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success
|
|
FALSE - failure, GetLastError returns the error code
|
|
|
|
--*/
|
|
|
|
{
|
|
DEBUG_ENTER_API((DBG_API,
|
|
Bool,
|
|
"InternetCheckConnectionA",
|
|
"%s %x",
|
|
lpszUrl,
|
|
dwFlags
|
|
));
|
|
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
LPSTR lpszHostName;
|
|
DWORD dwHostNameLen;
|
|
INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
INTERNET_SCHEME ustScheme = INTERNET_SCHEME_HTTP;
|
|
ICSocket *pSocket = NULL;
|
|
char buff[INTERNET_MAX_HOST_NAME_LENGTH+1];
|
|
LPINTERNET_THREAD_INFO lpThreadInfo;
|
|
HINTERNET hInternet = NULL, hConnect = NULL, hConnectMapped = NULL;
|
|
CServerInfo * lpServerInfo = NULL;
|
|
|
|
if (!GlobalDataInitialized) {
|
|
dwError = GlobalDataInitialize();
|
|
if (dwError != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// if the main sockets database thinks we are
|
|
// unconditionally offline, then let us give that to the user
|
|
//if(vSocketsDatabase.IsOffline())
|
|
//{
|
|
//
|
|
// dwError = ERROR_NOT_CONNECTED;
|
|
//}
|
|
//else
|
|
{
|
|
|
|
// We are not explicitly in offline mode
|
|
|
|
lpServerInfo = FindNearestServer();
|
|
|
|
if (dwFlags & FLAG_ICC_FORCE_CONNECTION) {
|
|
|
|
buff[0] = 0;
|
|
|
|
if (lpszUrl) {
|
|
|
|
if (((dwError = CrackUrl(
|
|
(LPSTR)lpszUrl, // url
|
|
lstrlen(lpszUrl), // url length
|
|
FALSE, // escape the URL ?
|
|
&ustScheme, // scheme type
|
|
NULL, // scheme name
|
|
NULL, // scheme length
|
|
&lpszHostName, // hostname pointer
|
|
&dwHostNameLen, // hostname length
|
|
&ServerPort, // port
|
|
NULL, // UserName
|
|
NULL, // UserName Length
|
|
NULL, // Password
|
|
NULL, // Password Length
|
|
NULL, // Path
|
|
NULL, // Path Length
|
|
NULL, // Extra
|
|
NULL, // Extra Length
|
|
NULL // have port?
|
|
)) == ERROR_SUCCESS)&&
|
|
(dwHostNameLen<=INTERNET_MAX_HOST_NAME_LENGTH))
|
|
{
|
|
memcpy(buff, lpszHostName, dwHostNameLen);
|
|
buff[dwHostNameLen] = 0;
|
|
|
|
// make sure we have a valid scheme
|
|
if(INTERNET_SCHEME_UNKNOWN == ustScheme)
|
|
{
|
|
ustScheme = INTERNET_SCHEME_HTTP;
|
|
}
|
|
|
|
// make sure we have a valid port
|
|
if(0 == ServerPort)
|
|
{
|
|
switch(ustScheme)
|
|
{
|
|
case INTERNET_SCHEME_FTP:
|
|
ServerPort = INTERNET_DEFAULT_FTP_PORT;
|
|
break;
|
|
case INTERNET_SCHEME_HTTPS:
|
|
ServerPort = INTERNET_DEFAULT_HTTPS_PORT;
|
|
break;
|
|
default:
|
|
ServerPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// for our purposes, HTTPS == HTTP
|
|
if(INTERNET_SCHEME_HTTPS == ustScheme)
|
|
{
|
|
ustScheme = INTERNET_SCHEME_HTTP;
|
|
}
|
|
|
|
// we need the serverinfo struct for this server, not
|
|
// the nearest one that we already found
|
|
if(lpServerInfo)
|
|
{
|
|
lpServerInfo->Dereference();
|
|
}
|
|
|
|
lpServerInfo = FindServerInfo(buff);
|
|
if(!lpServerInfo)
|
|
{
|
|
// new CServerInfo has ref count 1 already
|
|
// we need to raise it so another thread won't go ahead and destroy this.
|
|
LockSerializedList(&GlobalServerInfoList);
|
|
lpServerInfo = new CServerInfo(buff, &dwError, INTERNET_SERVICE_HTTP, 0);
|
|
if(NULL == lpServerInfo)
|
|
{
|
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
else if (dwError != ERROR_SUCCESS)
|
|
{
|
|
delete lpServerInfo;
|
|
lpServerInfo = NULL;
|
|
}
|
|
else
|
|
{
|
|
lpServerInfo->Reference();
|
|
}
|
|
UnlockSerializedList(&GlobalServerInfoList);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (lpServerInfo) {
|
|
|
|
buff[sizeof(buff)-1] = 0;
|
|
|
|
strncpy(buff, lpServerInfo->GetHostName(), sizeof(buff)-1);
|
|
}
|
|
else
|
|
{
|
|
// FORCE but no server to try - set error
|
|
dwError = ERROR_INTERNET_INVALID_OPERATION;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (buff[0] && lpServerInfo) {
|
|
|
|
// we have a host name, ping it.
|
|
|
|
// This threadinfo/InternetOpen stuff is being done
|
|
// because the ICSocket class is intertwined with
|
|
// an internet handle, so we are just getting round that
|
|
// difficulty. Ideally, ICSocket should have been a
|
|
// standalone sockets class
|
|
|
|
lpThreadInfo = InternetGetThreadInfo();
|
|
|
|
if (lpThreadInfo == NULL) {
|
|
|
|
INET_ASSERT(FALSE);
|
|
|
|
dwError = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
hInternet = InternetOpen("Internal",
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
0
|
|
);
|
|
if (!hInternet) {
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
hConnect = InternetConnect(hInternet,
|
|
buff,
|
|
ServerPort,
|
|
NULL,
|
|
NULL,
|
|
ustScheme,
|
|
0,
|
|
0);
|
|
if (!hConnect) {
|
|
dwError = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwError = MapHandleToAddress(hConnect, (LPVOID *)&hConnectMapped, FALSE);
|
|
|
|
if (dwError != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
_InternetSetObjectHandle(lpThreadInfo, hConnect, hConnectMapped);
|
|
|
|
// Ping the server
|
|
if (pSocket = new ICSocket()) {
|
|
|
|
pSocket->SetPort(ServerPort);
|
|
|
|
dwError = pSocket->SocketConnect(
|
|
GetTimeoutValue(INTERNET_OPTION_CONNECT_TIMEOUT),
|
|
GetTimeoutValue(INTERNET_OPTION_CONNECT_RETRIES),
|
|
0,
|
|
lpServerInfo
|
|
);
|
|
|
|
if (dwError == ERROR_SUCCESS) {
|
|
pSocket->Disconnect();
|
|
}
|
|
}
|
|
else {
|
|
|
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else{
|
|
// caller doesn't ask us to force a connection
|
|
// do the best we can and tell him
|
|
|
|
dwError = (/*vSocketsDatabase.GetSocketCount() ||*/ lpServerInfo)?
|
|
ERROR_SUCCESS:
|
|
ERROR_NOT_CONNECTED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (lpServerInfo)
|
|
{
|
|
lpServerInfo->Dereference();
|
|
}
|
|
if (pSocket) {
|
|
|
|
pSocket->Dereference();
|
|
|
|
}
|
|
if (hConnectMapped) {
|
|
|
|
DereferenceObject((LPVOID)hConnectMapped);
|
|
|
|
}
|
|
if (hConnect) {
|
|
|
|
InternetCloseHandle(hConnect);
|
|
|
|
}
|
|
if (hInternet) {
|
|
|
|
InternetCloseHandle(hInternet);
|
|
|
|
}
|
|
|
|
BOOL fRet = (dwError==ERROR_SUCCESS);
|
|
if (FALSE == fRet) {
|
|
SetLastError(dwError);
|
|
DEBUG_ERROR(API, dwError);
|
|
}
|
|
DEBUG_LEAVE_API(fRet);
|
|
|
|
return (fRet);
|
|
}
|