2637 lines
71 KiB
C++
2637 lines
71 KiB
C++
/*++
|
||
|
||
Copyright (c) 1994 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
gfrapia.cxx
|
||
|
||
Abstract:
|
||
|
||
ANSI versions of Windows Internet Extensions Gopher Protocol APIs
|
||
|
||
Contents:
|
||
GopherCreateLocatorA
|
||
GopherGetLocatorTypeA
|
||
GopherFindFirstFileA
|
||
GopherFindNextA
|
||
GopherOpenFileA
|
||
GopherReadFile
|
||
GopherGetAttributeA
|
||
GopherSendDataA
|
||
pGfrGetUrlInfo
|
||
pGopherGetUrlString
|
||
|
||
Author:
|
||
|
||
Richard L Firth (rfirth) 11-Oct-1994
|
||
|
||
Environment:
|
||
|
||
Win32 user-level DLL
|
||
|
||
Revision History:
|
||
|
||
11-Oct-1994 rfirth
|
||
Created
|
||
|
||
--*/
|
||
|
||
#include <wininetp.h>
|
||
#include "gfrapih.h"
|
||
|
||
//
|
||
// manifests
|
||
//
|
||
|
||
#define GOPHER_ATTRIBUTE_BUFFER_LENGTH (4 K) // arbitrary
|
||
#define MAX_GOPHER_SEARCH_STRING_LENGTH (1 K) // arbitrary
|
||
|
||
// used as delimiter in creating a unique cache name to append
|
||
// searchstring or viewtype to a url
|
||
#define GOPHER_EXTENSION_DELIMITER_SZ "<>"
|
||
|
||
DWORD
|
||
pGopherGetUrlString(
|
||
IN INTERNET_SCHEME SchemeType,
|
||
IN LPSTR lpszTargetHost,
|
||
IN LPSTR lpszCWD,
|
||
IN LPSTR lpszObjectLocator,
|
||
IN LPSTR lpszExtension,
|
||
IN DWORD dwPort,
|
||
OUT LPSTR *lplpUrlName,
|
||
OUT LPDWORD lpdwUrlLen
|
||
);
|
||
|
||
//
|
||
// private functions
|
||
//
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherBeginCacheReadProcessing(
|
||
IN HINTERNET hGopherSession,
|
||
IN LPCSTR lpszFileName,
|
||
IN LPCSTR lpszViewType,
|
||
IN DWORD dwFlags,
|
||
IN DWORD_PTR dwContext,
|
||
BOOL fIsHtmlFind
|
||
);
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherCanReadFromCache(
|
||
HINTERNET hGopherSession
|
||
);
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherBeginCacheWriteProcessing(
|
||
IN HINTERNET hGopherSession,
|
||
IN LPCSTR lpszFileName,
|
||
IN LPCSTR lpszViewType,
|
||
IN DWORD dwFlags,
|
||
IN DWORD_PTR dwContext,
|
||
BOOL fIsHtmlFind
|
||
);
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherCanWriteToCache(
|
||
HINTERNET hGopherSession
|
||
);
|
||
|
||
DWORD
|
||
InbGopherLocalEndCacheWrite(
|
||
IN HINTERNET hGopherFile,
|
||
IN LPSTR lpszFileExtension,
|
||
IN BOOL fNormal
|
||
);
|
||
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FIsGopherExpired(
|
||
HINTERNET hGopher,
|
||
LPCACHE_ENTRY_INFO lpCEI
|
||
);
|
||
|
||
//
|
||
// functions
|
||
//
|
||
|
||
|
||
INTERNETAPI_(BOOL) GopherCreateLocatorA(
|
||
IN LPCSTR lpszHost,
|
||
IN INTERNET_PORT nServerPort,
|
||
IN LPCSTR lpszDisplayString OPTIONAL,
|
||
IN LPCSTR lpszSelectorString OPTIONAL,
|
||
IN DWORD dwGopherType,
|
||
OUT LPSTR lpszLocator OPTIONAL,
|
||
IN OUT LPDWORD lpdwBufferLength
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Creates a gopher locator string. The string should be an opaque type so far
|
||
as the app is concerned.
|
||
|
||
This API mainly exists for situations where the app may want to request
|
||
explicit information, without first having contacted a server and asked for
|
||
a list of available information
|
||
|
||
Arguments:
|
||
|
||
lpszHost - Name of the host where the gopher server lives
|
||
|
||
nServerPort - Port at which the gopher server listens. The default
|
||
value 70 will be substituted if 0 is passed in
|
||
|
||
lpszDisplayString - Optional display string. Mainly a place holder. Can be
|
||
NULL, or NUL string, in which case a default string
|
||
will be substituted (just \t)
|
||
|
||
lpszSelectorString - The string used to select the item at the gopher
|
||
server. Can be NULL
|
||
|
||
dwGopherType - Tells us that the item to return is a file or
|
||
directory, graphics image, audio file, etc...
|
||
If 0, the default GOPHER_TYPE_DIRECTORY is used
|
||
|
||
lpszLocator - Place where the locator is returned
|
||
|
||
lpdwBufferLength - IN: Length of Buffer
|
||
OUT: Required length of Buffer only if
|
||
ERROR_INSUFFICIENT_BUFFER returned
|
||
|
||
Return Value:
|
||
|
||
BOOL
|
||
Success - TRUE
|
||
|
||
Failure - FALSE. Call GetLastError()/InternetGetLastResponseInfo() to
|
||
get more info
|
||
|
||
--*/
|
||
|
||
{
|
||
DEBUG_ENTER_API((DBG_API,
|
||
Bool,
|
||
"GopherCreateLocatorA",
|
||
"%q, %d, %q, %q, %#x, %#x, %#x [%d]",
|
||
lpszHost,
|
||
nServerPort,
|
||
lpszDisplayString,
|
||
lpszSelectorString,
|
||
dwGopherType,
|
||
lpszLocator,
|
||
lpdwBufferLength,
|
||
lpdwBufferLength ? *lpdwBufferLength : 0
|
||
));
|
||
|
||
DWORD requiredLength;
|
||
char portBuffer[INTERNET_MAX_PORT_NUMBER_LENGTH + 1];
|
||
char gopherChar;
|
||
DWORD displayStringLength;
|
||
DWORD selectorStringLength;
|
||
DWORD hostNameLength;
|
||
DWORD portLength;
|
||
BOOL gopherPlus;
|
||
BOOL success;
|
||
|
||
//
|
||
// default is directory, ordinary gopher (i.e. not gopher+)
|
||
//
|
||
|
||
if (dwGopherType == 0) {
|
||
dwGopherType = GOPHER_TYPE_DIRECTORY;
|
||
}
|
||
|
||
gopherChar = GopherTypeToChar(dwGopherType);
|
||
|
||
//
|
||
// validate parameters
|
||
//
|
||
|
||
if (IsBadStringPtr(lpszHost, MAX_GOPHER_HOST_NAME)
|
||
|| (*lpszHost == '\0')
|
||
|| (ARGUMENT_PRESENT(lpszDisplayString)
|
||
&& IsBadStringPtr(lpszDisplayString, MAX_GOPHER_DISPLAY_TEXT))
|
||
|| (ARGUMENT_PRESENT(lpszSelectorString)
|
||
&& IsBadStringPtr(lpszSelectorString, MAX_GOPHER_SELECTOR_TEXT))
|
||
|| IsBadWritePtr(lpdwBufferLength, sizeof(*lpdwBufferLength))
|
||
|| (ARGUMENT_PRESENT(lpszLocator)
|
||
&& IsBadWritePtr(lpszLocator, *lpdwBufferLength))
|
||
|| (gopherChar == INVALID_GOPHER_TYPE)) {
|
||
|
||
DEBUG_ERROR(API, ERROR_INVALID_PARAMETER);
|
||
|
||
DEBUG_LEAVE(FALSE);
|
||
|
||
SetLastError(ERROR_INVALID_PARAMETER);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// ensure that if the caller passed in a NULL locator pointer then the size
|
||
// of the buffer is 0
|
||
//
|
||
|
||
if (!ARGUMENT_PRESENT(lpszLocator)) {
|
||
*lpdwBufferLength = 0;
|
||
}
|
||
|
||
if (nServerPort == 0) {
|
||
nServerPort = INTERNET_DEFAULT_GOPHER_PORT;
|
||
}
|
||
|
||
//ULTOA(nServerPort, portBuffer, 10);
|
||
wsprintf (portBuffer, "%u", nServerPort);
|
||
|
||
|
||
if (!(ARGUMENT_PRESENT(lpszDisplayString) && (*lpszDisplayString != '\0'))) {
|
||
lpszDisplayString = DEFAULT_GOPHER_DISPLAY_STRING;
|
||
}
|
||
|
||
if (!(ARGUMENT_PRESENT(lpszSelectorString) && (*lpszSelectorString != '\0'))) {
|
||
lpszSelectorString = DEFAULT_GOPHER_SELECTOR_STRING;
|
||
}
|
||
|
||
gopherPlus = IS_GOPHER_PLUS(dwGopherType);
|
||
|
||
displayStringLength = lstrlen(lpszDisplayString);
|
||
selectorStringLength = lstrlen(lpszSelectorString);
|
||
hostNameLength = lstrlen(lpszHost);
|
||
portLength = lstrlen(portBuffer);
|
||
|
||
//
|
||
// calculate how many bytes of buffer required for the locator
|
||
//
|
||
|
||
requiredLength = sizeof(char) // descriptor character
|
||
+ displayStringLength
|
||
+ sizeof(char) // TAB
|
||
+ selectorStringLength
|
||
+ sizeof(char) // TAB
|
||
+ hostNameLength
|
||
+ sizeof(char) // TAB
|
||
+ portLength
|
||
+ (gopherPlus ? 2 : 0) // TAB, '+'
|
||
+ sizeof(char) // CR
|
||
+ sizeof(char) // LF
|
||
+ sizeof(char) // EOS
|
||
;
|
||
|
||
//
|
||
// and if the caller supplied at least that much, then create the locator,
|
||
// else just return the size of buffer needed
|
||
//
|
||
|
||
if (*lpdwBufferLength >= requiredLength) {
|
||
*lpszLocator++ = gopherChar;
|
||
|
||
CopyMemory(lpszLocator, lpszDisplayString, displayStringLength);
|
||
lpszLocator += displayStringLength;
|
||
*lpszLocator++ = '\t';
|
||
|
||
CopyMemory(lpszLocator, lpszSelectorString, selectorStringLength);
|
||
lpszLocator += selectorStringLength;
|
||
*lpszLocator++ = '\t';
|
||
|
||
CopyMemory(lpszLocator, lpszHost, hostNameLength);
|
||
lpszLocator += hostNameLength;
|
||
*lpszLocator++ = '\t';
|
||
|
||
CopyMemory(lpszLocator, portBuffer, portLength);
|
||
lpszLocator += portLength;
|
||
|
||
if (gopherPlus) {
|
||
*lpszLocator++ = '\t';
|
||
*lpszLocator++ = '+';
|
||
}
|
||
|
||
*lpszLocator++ = '\r';
|
||
*lpszLocator++ = '\n';
|
||
*lpszLocator = '\0';
|
||
|
||
//
|
||
// in the case of a successful copy, we return *lpdwBufferLength as the
|
||
// number of characters in the string, as if returned by lstrlen()
|
||
//
|
||
|
||
--requiredLength;
|
||
success = TRUE;
|
||
} else {
|
||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||
|
||
DEBUG_ERROR(API, ERROR_INSUFFICIENT_BUFFER);
|
||
|
||
success = FALSE;
|
||
}
|
||
*lpdwBufferLength = requiredLength;
|
||
|
||
DEBUG_LEAVE_API(success);
|
||
|
||
return success;
|
||
}
|
||
|
||
|
||
INTERNETAPI_(BOOL) GopherGetLocatorTypeA(
|
||
IN LPCSTR lpszLocator,
|
||
OUT LPDWORD lpdwGopherType
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Returns the type of the locator
|
||
|
||
Arguments:
|
||
|
||
lpszLocator - pointer to locator to return type of
|
||
|
||
lpdwGopherType - pointer to DWORD where type is returned
|
||
|
||
Return Value:
|
||
|
||
BOOL
|
||
Success - TRUE
|
||
|
||
Failure - FALSE. Check GetLastError() for more information:
|
||
ERROR_INVALID_PARAMETER
|
||
ERROR_GOPHER_UNKNOWN_LOCATOR
|
||
|
||
--*/
|
||
|
||
{
|
||
DEBUG_ENTER_API((DBG_API,
|
||
Bool,
|
||
"GopherGetLocatorTypeA",
|
||
"%q, %#x",
|
||
lpszLocator,
|
||
lpdwGopherType
|
||
));
|
||
|
||
DWORD error;
|
||
|
||
if (IsValidLocator(lpszLocator, MAX_GOPHER_LOCATOR_LENGTH)
|
||
&& !IsBadWritePtr(lpdwGopherType, sizeof(*lpdwGopherType))) {
|
||
|
||
DWORD gopherType;
|
||
|
||
gopherType = GopherCharToType(*lpszLocator);
|
||
if (gopherType != INVALID_GOPHER_CHAR) {
|
||
gopherType |= IsGopherPlus(lpszLocator)
|
||
? GOPHER_TYPE_GOPHER_PLUS
|
||
: 0
|
||
;
|
||
*lpdwGopherType = gopherType;
|
||
error = ERROR_SUCCESS;
|
||
} else {
|
||
error = ERROR_GOPHER_UNKNOWN_LOCATOR;
|
||
}
|
||
} else {
|
||
error = ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
DWORD success;
|
||
|
||
if (error == ERROR_SUCCESS) {
|
||
success = TRUE;
|
||
} else {
|
||
|
||
DEBUG_ERROR(API, error);
|
||
|
||
SetLastError(error);
|
||
success = FALSE;
|
||
}
|
||
|
||
DEBUG_LEAVE_API(success);
|
||
|
||
return success;
|
||
}
|
||
|
||
|
||
INTERNETAPI_(HINTERNET) GopherFindFirstFileA(
|
||
IN HINTERNET hGopherSession,
|
||
IN LPCSTR lpszLocator OPTIONAL,
|
||
IN LPCSTR lpszSearchString OPTIONAL,
|
||
OUT LPGOPHER_FIND_DATAA lpBuffer OPTIONAL,
|
||
IN DWORD dwFlags,
|
||
IN DWORD_PTR dwContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Used to return directory/hierarchy information from the gopher server
|
||
|
||
Arguments:
|
||
|
||
hGopherSession - identifies gopher server context to use - either via
|
||
gateway, or straight to internet
|
||
|
||
lpszLocator - descriptor of information to get. If NULL then we get
|
||
the default directory at the server
|
||
|
||
lpszSearchString - if this request is to an search server, this
|
||
parameter specifies the string(s) to search for. If
|
||
Locator does not specify a search server then
|
||
the paraneter is not used, but it IS validated
|
||
|
||
lpBuffer - pointer to user-allocated buffer in which to return
|
||
info. This parameter may be NULL, in which case if
|
||
this function returns success, then the results of
|
||
the request will be returned via InternetFindNextFile()
|
||
|
||
dwFlags - controlling caching, etc.
|
||
|
||
dwContext - app-supplied context value for use in call-backs
|
||
|
||
Return Value:
|
||
|
||
HINTERNET
|
||
Success - valid handle value
|
||
If lpBuffer was not NULL, a GOPHER_FIND_DATA structure has
|
||
been returned in lpBuffer. Use InternetFindNextFile() to
|
||
retrieve the remainder of the directory entries.
|
||
If lpBuffer was NULL, then this call is still successful,
|
||
but all directory entries (including the first) will be
|
||
returned via InternetFindNextFile()
|
||
|
||
Failure - NULL
|
||
Use GetLastError()/InternetGetLastResponseInfo() to get
|
||
more info
|
||
|
||
--*/
|
||
|
||
{
|
||
DEBUG_ENTER_API((DBG_API,
|
||
Handle,
|
||
"GopherFindFirstFileA",
|
||
"%#x, %q, %q, %#x, %#x, %$x",
|
||
hGopherSession,
|
||
lpszLocator,
|
||
lpszSearchString,
|
||
lpBuffer,
|
||
dwFlags,
|
||
dwContext
|
||
));
|
||
|
||
HINTERNET findHandle;
|
||
HINTERNET hConnectMapped = NULL;
|
||
HINTERNET hObject;
|
||
HINTERNET hObjectMapped = NULL;
|
||
|
||
DWORD error;
|
||
LPINTERNET_THREAD_INFO lpThreadInfo;
|
||
DWORD nestingLevel = 0;
|
||
BOOL fDeref = TRUE;
|
||
|
||
if (!GlobalDataInitialized) {
|
||
error = ERROR_INTERNET_NOT_INITIALIZED;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// get the per-thread info block
|
||
//
|
||
|
||
lpThreadInfo = InternetGetThreadInfo();
|
||
if (lpThreadInfo == NULL) {
|
||
|
||
INET_ASSERT(FALSE);
|
||
|
||
error = ERROR_INTERNET_INTERNAL_ERROR;
|
||
goto done;
|
||
}
|
||
|
||
_InternetIncNestingCount();
|
||
nestingLevel = 1;
|
||
|
||
//
|
||
// if this is the async worker thread then what we think is hGopherSession
|
||
// is really the find handle object. Get the handles in the right variables
|
||
//
|
||
|
||
if (lpThreadInfo->IsAsyncWorkerThread
|
||
&& (lpThreadInfo->NestedRequests == 1)) {
|
||
hObject = hGopherSession;
|
||
error = MapHandleToAddress(hObject, (LPVOID *)&hObjectMapped, FALSE);
|
||
if ((error != ERROR_SUCCESS) && (hObjectMapped == NULL)) {
|
||
goto quit;
|
||
}
|
||
findHandle = hObjectMapped;
|
||
hConnectMapped = ((FTP_FIND_HANDLE_OBJECT *)findHandle)->GetParent();
|
||
} else {
|
||
|
||
//
|
||
// map the handle
|
||
//
|
||
|
||
hObject = hGopherSession;
|
||
error = MapHandleToAddress(hGopherSession, (LPVOID *)&hObjectMapped, FALSE);
|
||
if ((error != ERROR_SUCCESS) && (hObjectMapped == NULL)) {
|
||
goto quit;
|
||
}
|
||
hConnectMapped = hObjectMapped;
|
||
findHandle = NULL;
|
||
}
|
||
|
||
//
|
||
// set the context info and clear the error variables
|
||
//
|
||
|
||
_InternetSetObjectHandle(lpThreadInfo, hObject, hObjectMapped);
|
||
_InternetSetContext(lpThreadInfo, dwContext);
|
||
_InternetClearLastError(lpThreadInfo);
|
||
|
||
//
|
||
// quit now if the handle is invalid
|
||
//
|
||
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// validate parameters - check handle and discover sync/async and
|
||
// local/remote
|
||
//
|
||
|
||
BOOL isLocal, isAsync;
|
||
|
||
error = RIsHandleLocal(hConnectMapped,
|
||
&isLocal,
|
||
&isAsync,
|
||
TypeGopherConnectHandle
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// skip rest of validation if we're in the async worker thread context -
|
||
// we already did this
|
||
//
|
||
|
||
if (!lpThreadInfo->IsAsyncWorkerThread
|
||
|| (lpThreadInfo->NestedRequests > 1)) {
|
||
if ((ARGUMENT_PRESENT(lpBuffer)
|
||
&& IsBadWritePtr(lpBuffer, sizeof(GOPHER_FIND_DATA)))
|
||
|| (ARGUMENT_PRESENT(lpszSearchString)
|
||
|
||
//
|
||
// BUGBUG - limit on search string?
|
||
//
|
||
|
||
&& IsBadStringPtr(lpszSearchString,
|
||
MAX_GOPHER_SEARCH_STRING_LENGTH))) {
|
||
|
||
error = ERROR_INVALID_PARAMETER;
|
||
goto quit;
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT(lpszLocator)) {
|
||
|
||
//
|
||
// BUGBUG - limit on locator?
|
||
//
|
||
|
||
if (IsValidLocator(lpszLocator, MAX_GOPHER_LOCATOR_LENGTH)) {
|
||
|
||
DWORD locatorType;
|
||
|
||
locatorType = GopherCharToType(*lpszLocator);
|
||
|
||
//
|
||
// we only allow directories and index/cso servers to be searched
|
||
//
|
||
|
||
if ( !IS_GOPHER_DIRECTORY(locatorType)
|
||
&& !IS_GOPHER_SEARCH_SERVER(locatorType)) {
|
||
error = ERROR_GOPHER_INCORRECT_LOCATOR_TYPE;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// if this is an search server then there must be search strings
|
||
// - the server will only tell us that we need to supply them,
|
||
// so no point in going to the expense of sending a request just
|
||
// to get this response
|
||
//
|
||
|
||
if ( IS_GOPHER_SEARCH_SERVER (locatorType)
|
||
&& ( !ARGUMENT_PRESENT(lpszSearchString)
|
||
|| (*lpszSearchString == '\0'))) {
|
||
|
||
error = ERROR_INVALID_PARAMETER;
|
||
goto quit;
|
||
}
|
||
} else {
|
||
error = ERROR_GOPHER_INVALID_LOCATOR;
|
||
goto quit;
|
||
}
|
||
}
|
||
|
||
//
|
||
// create the handle object now. This can be used to cancel the async
|
||
// operation, or the sync operation if InternetCloseHandle() is called
|
||
// from a different thread
|
||
//
|
||
|
||
INET_ASSERT(findHandle == NULL);
|
||
|
||
error = RMakeGfrFindObjectHandle(hConnectMapped,
|
||
&findHandle,
|
||
(CLOSE_HANDLE_FUNC)wGopherFindClose,
|
||
dwContext
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// this new handle will be used in callbacks
|
||
//
|
||
|
||
_InternetSetObjectHandle(lpThreadInfo,
|
||
((HANDLE_OBJECT *)findHandle)->GetPseudoHandle(),
|
||
findHandle
|
||
);
|
||
|
||
//
|
||
// get the OFFLINE flag whether set globally (@ InternetOpen() level) or
|
||
// locally (for this function)
|
||
//
|
||
|
||
if ((((INTERNET_CONNECT_HANDLE_OBJECT *)findHandle)->GetInternetOpenFlags()
|
||
| dwFlags) & INTERNET_FLAG_OFFLINE) {
|
||
dwFlags |= INTERNET_FLAG_OFFLINE;
|
||
}
|
||
|
||
try_again:
|
||
|
||
//
|
||
// check to see if the data is in the cache. Do it here so that we don't
|
||
// waste any time going async if we already have the data locally
|
||
//
|
||
|
||
//
|
||
// can't do it for default locator
|
||
//
|
||
|
||
if ((lpszLocator != NULL)
|
||
&& FGopherBeginCacheReadProcessing(findHandle,
|
||
lpszLocator,
|
||
lpszSearchString,
|
||
dwFlags,
|
||
dwContext,
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped)->IsHtmlFind()
|
||
)) {
|
||
|
||
error = ERROR_SUCCESS;
|
||
|
||
if (lpBuffer) {
|
||
|
||
DWORD dwBytes = sizeof(GOPHER_FIND_DATA);
|
||
|
||
error = ((INTERNET_CONNECT_HANDLE_OBJECT *)findHandle)->ReadCache(
|
||
(LPBYTE)lpBuffer,
|
||
sizeof(GOPHER_FIND_DATA),
|
||
&dwBytes
|
||
);
|
||
}
|
||
if (error == ERROR_SUCCESS) {
|
||
goto quit;
|
||
} else {
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)findHandle)->EndCacheRetrieval();
|
||
}
|
||
}
|
||
|
||
if (dwFlags & INTERNET_FLAG_OFFLINE) {
|
||
|
||
//
|
||
// we are supposed to be in offline mode
|
||
// if we are not reading from the cache, let us bailout
|
||
//
|
||
|
||
if (!((INTERNET_CONNECT_HANDLE_OBJECT *)findHandle)->IsCacheReadInProgress()) {
|
||
error = ERROR_PATH_NOT_FOUND;
|
||
goto quit;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// if the handle was created for async I/O AND there is a non-zero context
|
||
// value AND we are not in the context of an async worker thread then queue
|
||
// the request
|
||
//
|
||
|
||
if (isAsync
|
||
&& (dwContext != INTERNET_NO_CALLBACK)
|
||
&& !lpThreadInfo->IsAsyncWorkerThread) {
|
||
|
||
// MakeAsyncRequest
|
||
CFsm_GopherFindFirstFile * pFsm;
|
||
|
||
pFsm = new CFsm_GopherFindFirstFile(((HANDLE_OBJECT *)findHandle)->GetPseudoHandle(),
|
||
dwContext,
|
||
lpszLocator,
|
||
lpszSearchString,
|
||
lpBuffer,
|
||
dwFlags
|
||
);
|
||
|
||
if (pFsm != NULL &&
|
||
pFsm->GetError() == ERROR_SUCCESS)
|
||
{
|
||
error = pFsm->QueueWorkItem();
|
||
if ( error == ERROR_IO_PENDING ) {
|
||
fDeref = FALSE;
|
||
}
|
||
}
|
||
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;
|
||
}
|
||
|
||
//
|
||
// make the request
|
||
//
|
||
|
||
char defaultLocator[MAX_GOPHER_LOCATOR_LENGTH * 2];
|
||
|
||
//
|
||
// if we were given a NULL locator then create a locator to access
|
||
// the default directory at the configured default server.
|
||
//
|
||
// N.B. This only gives us gopher access (i.e. not gopher+)
|
||
//
|
||
|
||
if (!ARGUMENT_PRESENT(lpszLocator)) {
|
||
|
||
BOOL ok;
|
||
DWORD defaultLocatorLength;
|
||
|
||
defaultLocatorLength = sizeof(defaultLocator);
|
||
ok = GopherCreateLocator(
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped)->GetHostName(),
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped)->GetHostPort(),
|
||
NULL,
|
||
NULL,
|
||
GOPHER_TYPE_DIRECTORY,
|
||
defaultLocator,
|
||
&defaultLocatorLength
|
||
);
|
||
if (ok) {
|
||
lpszLocator = defaultLocator;
|
||
} else {
|
||
|
||
//
|
||
// set error to ERROR_SUCCESS so that cleanup doesn't set it
|
||
// again (already set by GopherCreateLocator)
|
||
//
|
||
|
||
error = ERROR_SUCCESS;
|
||
goto quit;
|
||
}
|
||
}
|
||
|
||
HINTERNET protocolFindHandle;
|
||
|
||
error = wGopherFindFirst(lpszLocator,
|
||
lpszSearchString,
|
||
lpBuffer,
|
||
&protocolFindHandle
|
||
);
|
||
if (error == ERROR_SUCCESS) {
|
||
((GOPHER_FIND_HANDLE_OBJECT *)findHandle)->SetFindHandle(protocolFindHandle);
|
||
|
||
//
|
||
// if we succeeded in getting the data, add it to the cache. Don't worry
|
||
// about errors if cache write fails
|
||
//
|
||
|
||
if (FGopherBeginCacheWriteProcessing(
|
||
findHandle,
|
||
lpszLocator,
|
||
lpszSearchString,
|
||
0,
|
||
dwContext,
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped)->IsHtmlFind())) {
|
||
|
||
if (lpBuffer != NULL) {
|
||
|
||
DWORD dwBytes = sizeof(GOPHER_FIND_DATA);
|
||
DWORD errorCache;
|
||
|
||
errorCache = ((INTERNET_CONNECT_HANDLE_OBJECT *)findHandle)->WriteCache(
|
||
(LPBYTE)lpBuffer,
|
||
sizeof(GOPHER_FIND_DATA)
|
||
);
|
||
if (errorCache != ERROR_SUCCESS) {
|
||
InbGopherLocalEndCacheWrite(findHandle,
|
||
NULL,
|
||
(errorCache == ERROR_NO_MORE_FILES)
|
||
);
|
||
}
|
||
}
|
||
}
|
||
} else if (IsOffline() && !(dwFlags & INTERNET_FLAG_OFFLINE)) {
|
||
|
||
//
|
||
// if we failed because we went offline before retrieving it from the
|
||
// cache, then try from the cache again
|
||
//
|
||
|
||
dwFlags |= INTERNET_FLAG_OFFLINE;
|
||
goto try_again;
|
||
}
|
||
|
||
quit:
|
||
|
||
_InternetDecNestingCount(nestingLevel);
|
||
|
||
done:
|
||
|
||
//
|
||
// if we got an error then set this thread's error variable and return
|
||
// NULL. The app must call GetLastError()
|
||
//
|
||
|
||
if (error != ERROR_SUCCESS) {
|
||
|
||
DEBUG_ERROR(API, error);
|
||
|
||
//
|
||
// if we are not pending an async request but we created a handle object
|
||
// then close it
|
||
//
|
||
|
||
if ((error != ERROR_IO_PENDING) && (findHandle != NULL)) {
|
||
InternetCloseHandle(((HANDLE_OBJECT *)findHandle)->GetPseudoHandle());
|
||
}
|
||
|
||
//
|
||
// error situation, or request is being processed asynchronously: return
|
||
// a NULL handle
|
||
//
|
||
|
||
findHandle = NULL;
|
||
} else {
|
||
|
||
//
|
||
// success - return generated pseudo-handle
|
||
//
|
||
|
||
findHandle = ((HANDLE_OBJECT *)findHandle)->GetPseudoHandle();
|
||
}
|
||
|
||
if ((hConnectMapped != NULL) && fDeref) {
|
||
DereferenceObject((LPVOID)hConnectMapped);
|
||
}
|
||
|
||
if ( error != ERROR_SUCCESS ) {
|
||
SetLastError(error);
|
||
}
|
||
|
||
DEBUG_LEAVE_API(findHandle);
|
||
|
||
return findHandle;
|
||
}
|
||
|
||
|
||
BOOL
|
||
GopherFindNextA(
|
||
IN HINTERNET hFind,
|
||
OUT LPGOPHER_FIND_DATA lpBuffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Continues a search created by GopherFindFirstFile(). The same search
|
||
criteria as specfied in GopherFindFirstFile() will be applied
|
||
|
||
Assumes: 1. We are being called from InternetFindNextFile() which has
|
||
already validated the parameters, set the thread variables,
|
||
and cleared the object last error info
|
||
|
||
Arguments:
|
||
|
||
hFind - search handle created by call to GopherFindFirstFile()
|
||
|
||
lpBuffer - pointer to user-allocated buffer in which to return info
|
||
|
||
Return Value:
|
||
|
||
BOOL
|
||
TRUE - Information has been returned in lpBuffer
|
||
|
||
FALSE - Use GetLastError()/InternetGetLastResponseInfo() to get nore
|
||
information
|
||
|
||
--*/
|
||
|
||
{
|
||
DEBUG_ENTER((DBG_GOPHER,
|
||
Bool,
|
||
"GopherFindNextA",
|
||
"%#x, %#x",
|
||
hFind,
|
||
lpBuffer
|
||
));
|
||
|
||
INET_ASSERT(GlobalDataInitialized);
|
||
|
||
DWORD error;
|
||
|
||
//
|
||
// find path from internet handle
|
||
//
|
||
|
||
BOOL isLocal;
|
||
BOOL isAsync, fIsHtml = FALSE;
|
||
|
||
error = RIsHandleLocal(hFind,
|
||
&isLocal,
|
||
&isAsync,
|
||
TypeGopherFindHandle
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
|
||
//
|
||
// if the handle is actually a HTML gopher find handle, then we allow
|
||
// the operation. Note: we can do this because GopherFindNext() is not
|
||
// exported, so a rogue app cannot call this function after opening the
|
||
// handle via InternetOpenUrl()
|
||
//
|
||
|
||
error = RIsHandleLocal(hFind,
|
||
&isLocal,
|
||
&isAsync,
|
||
TypeGopherFindHandleHtml
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
fIsHtml = TRUE;
|
||
}
|
||
|
||
INET_ASSERT(error == ERROR_SUCCESS);
|
||
|
||
if (((INTERNET_CONNECT_HANDLE_OBJECT *)hFind)->IsCacheReadInProgress()) {
|
||
DWORD dwLen = sizeof(GOPHER_FIND_DATA);
|
||
error = ((INTERNET_CONNECT_HANDLE_OBJECT *)hFind)->ReadCache((LPBYTE)lpBuffer,
|
||
dwLen,
|
||
&dwLen);
|
||
if ((error == ERROR_SUCCESS) && !dwLen) {
|
||
error = ERROR_NO_MORE_FILES;
|
||
}
|
||
goto quit;
|
||
}
|
||
|
||
HINTERNET localHandle;
|
||
|
||
error = RGetLocalHandle(hFind, &localHandle);
|
||
if (error == ERROR_SUCCESS) {
|
||
error = wGopherFindNext(localHandle, lpBuffer);
|
||
}
|
||
|
||
DWORD errorCache;
|
||
|
||
errorCache = error;
|
||
|
||
if (error == ERROR_SUCCESS) {
|
||
if (((INTERNET_CONNECT_HANDLE_OBJECT *)hFind)->IsCacheWriteInProgress()) {
|
||
if (!fIsHtml) {
|
||
errorCache = ((INTERNET_CONNECT_HANDLE_OBJECT *)hFind)->WriteCache((LPBYTE)lpBuffer,
|
||
sizeof(GOPHER_FIND_DATA));
|
||
}
|
||
}
|
||
|
||
}
|
||
if (errorCache != ERROR_SUCCESS) {
|
||
if (!fIsHtml) {
|
||
InbGopherLocalEndCacheWrite(hFind,
|
||
NULL,
|
||
(errorCache == ERROR_NO_MORE_FILES)
|
||
);
|
||
}
|
||
}
|
||
|
||
quit:
|
||
|
||
BOOL success;
|
||
|
||
if (error == ERROR_SUCCESS) {
|
||
success = TRUE;
|
||
} else {
|
||
|
||
DEBUG_ERROR(API, error);
|
||
|
||
success = FALSE;
|
||
}
|
||
|
||
DEBUG_LEAVE(success);
|
||
|
||
SetLastError(error);
|
||
|
||
return success;
|
||
}
|
||
|
||
|
||
INTERNETAPI_(HINTERNET) GopherOpenFileA(
|
||
IN HINTERNET hGopherSession,
|
||
IN LPCSTR lpszLocator,
|
||
IN LPCSTR lpszView OPTIONAL,
|
||
IN DWORD dwFlags,
|
||
IN DWORD_PTR dwContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
'Opens' a file at a gopher server. Right now this means transferring the
|
||
file locally, keeping it in a buffer
|
||
|
||
Arguments:
|
||
|
||
hGopherSession - defines where to go for the file - gateway or internet
|
||
|
||
lpszLocator - descriptor of file to get
|
||
|
||
lpszView - optional type of file to read as MIME content-type
|
||
|
||
dwFlags - open options
|
||
|
||
dwContext - app-supplied context value for use in call-backs
|
||
|
||
Return Value:
|
||
|
||
HINTERNET
|
||
Success - valid handle value
|
||
|
||
Failure - NULL
|
||
Use GetLastError()/InternetGetLastResponseInfo() to get
|
||
more information
|
||
|
||
--*/
|
||
|
||
{
|
||
DEBUG_ENTER_API((DBG_API,
|
||
Handle,
|
||
"GopherOpenFileA",
|
||
"%#x, %q, %q, %#x, %#x",
|
||
hGopherSession,
|
||
lpszLocator,
|
||
lpszView,
|
||
dwFlags,
|
||
dwContext
|
||
));
|
||
|
||
HINTERNET fileHandle = NULL;
|
||
HINTERNET hConnectMapped = NULL;
|
||
HINTERNET hObject;
|
||
HINTERNET hObjectMapped = NULL;
|
||
|
||
LPINTERNET_THREAD_INFO lpThreadInfo;
|
||
DWORD error;
|
||
DWORD nestingLevel = 0;
|
||
BOOL fDeref = TRUE;
|
||
|
||
if (!GlobalDataInitialized) {
|
||
error = ERROR_INTERNET_NOT_INITIALIZED;
|
||
goto done;
|
||
}
|
||
|
||
//
|
||
// need the per-thread info block
|
||
//
|
||
|
||
lpThreadInfo = InternetGetThreadInfo();
|
||
if (lpThreadInfo == NULL) {
|
||
|
||
INET_ASSERT(FALSE);
|
||
|
||
error = ERROR_INTERNET_INTERNAL_ERROR;
|
||
goto done;
|
||
}
|
||
|
||
_InternetIncNestingCount();
|
||
nestingLevel = 1;
|
||
|
||
//
|
||
// if this is the async worker thread then what we think is hGopherSession
|
||
// is really the file handle object. Get the handles in the right variables
|
||
//
|
||
|
||
if (lpThreadInfo->IsAsyncWorkerThread
|
||
&& (lpThreadInfo->NestedRequests == 1)) {
|
||
hObject = hGopherSession;
|
||
error = MapHandleToAddress(hObject, (LPVOID *)&hObjectMapped, FALSE);
|
||
if ((error != ERROR_SUCCESS) && (hObjectMapped == NULL)) {
|
||
goto quit;
|
||
}
|
||
fileHandle = hObjectMapped;
|
||
hConnectMapped = ((FTP_FIND_HANDLE_OBJECT *)fileHandle)->GetParent();
|
||
} else {
|
||
|
||
//
|
||
// map the handle
|
||
//
|
||
|
||
hObject = hGopherSession;
|
||
error = MapHandleToAddress(hObject, (LPVOID *)&hObjectMapped, FALSE);
|
||
if ((error != ERROR_SUCCESS) && (hObjectMapped == NULL)) {
|
||
goto quit;
|
||
}
|
||
hConnectMapped = hObjectMapped;
|
||
}
|
||
|
||
//
|
||
// handle must be valid type
|
||
//
|
||
|
||
BOOL isLocal;
|
||
BOOL isAsync;
|
||
|
||
error = RIsHandleLocal(hConnectMapped,
|
||
&isLocal,
|
||
&isAsync,
|
||
TypeGopherConnectHandle
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// set the context info & clear the error variables
|
||
//
|
||
|
||
_InternetSetObjectHandle(lpThreadInfo, hObject, hObjectMapped);
|
||
_InternetSetContext(lpThreadInfo, dwContext);
|
||
_InternetClearLastError(lpThreadInfo);
|
||
|
||
//
|
||
// if this is an async request and we're in the context of an async worker
|
||
// thread then skip the rest of parameter validation - it was already done
|
||
// when the request was originally queued
|
||
//
|
||
|
||
if (isAsync
|
||
&& lpThreadInfo->IsAsyncWorkerThread
|
||
&& (lpThreadInfo->NestedRequests == 1)) {
|
||
goto synchronous_path;
|
||
}
|
||
|
||
//
|
||
// validate parameters - locator must identify a file (and be a valid
|
||
// locator), and the flags parameter cannot contain any undefined flags.
|
||
// lpszView must be NULL or valid string
|
||
//
|
||
|
||
if (dwFlags & ~INTERNET_FLAGS_MASK) {
|
||
error = ERROR_INVALID_PARAMETER;
|
||
goto quit;
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT(lpszView)) {
|
||
|
||
int len;
|
||
|
||
__try {
|
||
len = lstrlen(lpszView);
|
||
error = ERROR_SUCCESS;
|
||
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
||
error = ERROR_INVALID_PARAMETER;
|
||
}
|
||
ENDEXCEPT
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
}
|
||
|
||
error = TestLocatorType(lpszLocator, GOPHER_FILE_MASK);
|
||
if (error != ERROR_SUCCESS) {
|
||
|
||
//
|
||
// TestLocatorType uses ERROR_INVALID_FUNCTION to mean Locator is not
|
||
// the specified type. Map this to ERROR_SUCCESS and NOT SUCCESS
|
||
//
|
||
|
||
if (error == ERROR_INVALID_FUNCTION) {
|
||
error = ERROR_GOPHER_NOT_FILE;
|
||
}
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// create the handle object now. This can be used to cancel the async
|
||
// operation, or the sync operation if InternetCloseHandle() is called
|
||
// from a different thread
|
||
//
|
||
|
||
fileHandle = NULL;
|
||
error = RMakeGfrFileObjectHandle(hConnectMapped,
|
||
&fileHandle,
|
||
(CLOSE_HANDLE_FUNC)wGopherCloseHandle,
|
||
dwContext
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// this new handle will be used in callbacks
|
||
//
|
||
|
||
_InternetSetObjectHandle(lpThreadInfo,
|
||
((HANDLE_OBJECT *)fileHandle)->GetPseudoHandle(),
|
||
fileHandle
|
||
);
|
||
|
||
//
|
||
// get the OFFLINE flag whether set globally (@ InternetOpen() level) or
|
||
// locally (for this function)
|
||
//
|
||
|
||
if ((((GOPHER_FILE_HANDLE_OBJECT *)fileHandle)->GetInternetOpenFlags()
|
||
| dwFlags) & INTERNET_FLAG_OFFLINE) {
|
||
dwFlags |= INTERNET_FLAG_OFFLINE;
|
||
}
|
||
|
||
try_again:
|
||
|
||
//
|
||
// check to see if the data is in the cache
|
||
//
|
||
|
||
if (FGopherBeginCacheReadProcessing(fileHandle,
|
||
lpszLocator,
|
||
lpszView,
|
||
dwFlags,
|
||
dwContext,
|
||
FALSE)) {
|
||
error = ERROR_SUCCESS;
|
||
goto quit;
|
||
} else if (dwFlags & INTERNET_FLAG_OFFLINE) {
|
||
|
||
//
|
||
// we are supposed to be in offline mode, let us bailout
|
||
//
|
||
|
||
error = ERROR_FILE_NOT_FOUND;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// if we're here then we know we're not in the context of an async worker
|
||
// thread, so if the request is async, we queue it and get out
|
||
//
|
||
|
||
if (!lpThreadInfo->IsAsyncWorkerThread
|
||
&& isAsync
|
||
&& (dwContext != INTERNET_NO_CALLBACK))
|
||
{
|
||
// MakeAsyncRequest
|
||
CFsm_GopherOpenFile * pFsm;
|
||
|
||
pFsm = new CFsm_GopherOpenFile(((HANDLE_OBJECT *)fileHandle)->GetPseudoHandle(),
|
||
dwContext,
|
||
lpszLocator,
|
||
lpszView,
|
||
dwFlags
|
||
);
|
||
|
||
if (pFsm != NULL &&
|
||
pFsm->GetError() == ERROR_SUCCESS)
|
||
{
|
||
error = pFsm->QueueWorkItem();
|
||
if ( error == ERROR_IO_PENDING ) {
|
||
fDeref = FALSE;
|
||
}
|
||
}
|
||
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;
|
||
}
|
||
|
||
synchronous_path:
|
||
|
||
//
|
||
// local call, call the worker directly.
|
||
//
|
||
|
||
HINTERNET protocolFileHandle;
|
||
|
||
error = wGopherOpenFile(lpszLocator,
|
||
lpszView,
|
||
&protocolFileHandle
|
||
);
|
||
if (error == ERROR_SUCCESS) {
|
||
((GOPHER_FILE_HANDLE_OBJECT *)fileHandle)->SetFileHandle(protocolFileHandle);
|
||
|
||
//
|
||
// don't worry about errors if cache write does not begin
|
||
//
|
||
|
||
FGopherBeginCacheWriteProcessing(fileHandle,
|
||
lpszLocator,
|
||
lpszView,
|
||
dwFlags,
|
||
dwContext,
|
||
FALSE
|
||
);
|
||
} else if (IsOffline() && !(dwFlags & INTERNET_FLAG_OFFLINE)) {
|
||
|
||
//
|
||
// if we failed because we went offline before retrieving it from the
|
||
// cache, then try from the cache again
|
||
//
|
||
|
||
dwFlags |= INTERNET_FLAG_OFFLINE;
|
||
goto try_again;
|
||
}
|
||
|
||
quit:
|
||
|
||
_InternetDecNestingCount(nestingLevel);
|
||
|
||
done:
|
||
|
||
if (error != ERROR_SUCCESS) {
|
||
|
||
DEBUG_ERROR(API, error);
|
||
|
||
SetLastError(error);
|
||
|
||
//
|
||
// if we are not pending an async request but we created a handle object
|
||
// then close it
|
||
//
|
||
|
||
if ((error != ERROR_IO_PENDING) && (fileHandle != NULL)) {
|
||
InternetCloseHandle(((HANDLE_OBJECT *)fileHandle)->GetPseudoHandle());
|
||
}
|
||
|
||
//
|
||
// error situation, or request is being processed asynchronously: return
|
||
// a NULL handle
|
||
//
|
||
|
||
fileHandle = NULL;
|
||
} else {
|
||
|
||
//
|
||
// success - return generated pseudo-handle
|
||
//
|
||
|
||
fileHandle = ((HANDLE_OBJECT *)fileHandle)->GetPseudoHandle();
|
||
}
|
||
|
||
if ((hConnectMapped != NULL) && fDeref ) {
|
||
DereferenceObject((LPVOID)hConnectMapped);
|
||
}
|
||
|
||
DEBUG_LEAVE_API(fileHandle);
|
||
|
||
return fileHandle;
|
||
}
|
||
|
||
|
||
BOOL
|
||
GopherReadFile(
|
||
IN HINTERNET hFile,
|
||
IN LPVOID lpBuffer,
|
||
IN DWORD dwNumberOfBytesToRead,
|
||
OUT LPDWORD lpdwNumberOfBytesRead
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Reads from a file opened by GopherOpenFile into the caller's buffer. The
|
||
number of bytes returned is the smaller of dwNumberOfBytesToRead and the
|
||
number of bytes between the current file pointer and the end of the file
|
||
|
||
Assumes: 1. We are being called from InternetReadFile() which has
|
||
already validated the parameters, handled the zero byte
|
||
read case, set the thread variables, and cleared the object
|
||
last error info
|
||
|
||
Arguments:
|
||
|
||
hFile - file handle created by GopherOpenFile
|
||
|
||
lpBuffer - pointer to caller's buffer
|
||
|
||
dwNumberOfBytesToRead - pointer to length of Buffer
|
||
|
||
lpdwNumberOfBytesRead - number of bytes copied into Buffer
|
||
|
||
Return Value:
|
||
|
||
BOOL
|
||
TRUE - lpdwNumberOfBytesRead contains amount of data written to
|
||
lpBuffer
|
||
|
||
FALSE - use GetLastError()/InternetGetLastResponseInfo() to get more
|
||
info
|
||
|
||
--*/
|
||
|
||
{
|
||
DEBUG_ENTER((DBG_GOPHER,
|
||
Bool,
|
||
"GopherReadFile",
|
||
"%#x, %#x, %d, %#x",
|
||
hFile,
|
||
lpBuffer,
|
||
dwNumberOfBytesToRead,
|
||
lpdwNumberOfBytesRead
|
||
));
|
||
|
||
INET_ASSERT(GlobalDataInitialized);
|
||
|
||
DWORD error;
|
||
|
||
//
|
||
// find path from file handle
|
||
//
|
||
|
||
BOOL isLocal;
|
||
BOOL isAsync;
|
||
|
||
error = RIsHandleLocal(hFile,
|
||
&isLocal,
|
||
&isAsync,
|
||
TypeGopherFileHandle
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
if (((GOPHER_FILE_HANDLE_OBJECT *)hFile)->IsCacheReadInProgress()) {
|
||
error = ((GOPHER_FILE_HANDLE_OBJECT *)hFile)->ReadCache((LPBYTE)lpBuffer,
|
||
dwNumberOfBytesToRead,
|
||
lpdwNumberOfBytesRead
|
||
);
|
||
if (!*lpdwNumberOfBytesRead || (error != ERROR_SUCCESS)) {
|
||
// Don't end cache retrieval. So any extraneous reads
|
||
// continue down this path. bug #9086
|
||
// ((GOPHER_FILE_HANDLE_OBJECT *)hFile)->EndCacheRetrieval();
|
||
}
|
||
|
||
// quit whether we succeed or we fail
|
||
|
||
goto quit;
|
||
}
|
||
|
||
HINTERNET localHandle;
|
||
|
||
error = RGetLocalHandle(hFile, &localHandle);
|
||
if (error == ERROR_SUCCESS) {
|
||
error = wGopherReadFile(localHandle,
|
||
(LPBYTE)lpBuffer,
|
||
dwNumberOfBytesToRead,
|
||
lpdwNumberOfBytesRead
|
||
);
|
||
}
|
||
|
||
if (error == ERROR_SUCCESS) {
|
||
DWORD errorCache;
|
||
if (((GOPHER_FILE_HANDLE_OBJECT *)hFile)->IsCacheWriteInProgress()) {
|
||
|
||
if(!*lpdwNumberOfBytesRead) {
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("Cache write complete\r\n"
|
||
));
|
||
|
||
errorCache = InbGopherLocalEndCacheWrite(hFile, NULL, TRUE);
|
||
|
||
INET_ASSERT(error == ERROR_SUCCESS);
|
||
|
||
goto quit;
|
||
}
|
||
|
||
INET_ASSERT(((GOPHER_FILE_HANDLE_OBJECT *)hFile)->IsCacheReadInProgress()==FALSE);
|
||
|
||
if (((GOPHER_FILE_HANDLE_OBJECT *)hFile)->WriteCache((LPBYTE)lpBuffer,
|
||
*lpdwNumberOfBytesRead
|
||
) != ERROR_SUCCESS) {
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
ERROR,
|
||
("Error in Cache write\n"
|
||
));
|
||
|
||
errorCache = InbGopherLocalEndCacheWrite(hFile, NULL, FALSE);
|
||
|
||
INET_ASSERT(error == ERROR_SUCCESS);
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
quit:
|
||
|
||
BOOL success;
|
||
|
||
if (error == ERROR_SUCCESS) {
|
||
success = TRUE;
|
||
} else {
|
||
|
||
DEBUG_ERROR(API, error);
|
||
|
||
SetLastError(error);
|
||
success = FALSE;
|
||
}
|
||
|
||
DEBUG_LEAVE(success);
|
||
|
||
return success;
|
||
}
|
||
|
||
|
||
INTERNETAPI_(BOOL) GopherGetAttributeA(
|
||
IN HINTERNET hGopherSession,
|
||
IN LPCSTR lpszLocator,
|
||
IN LPCSTR lpszAttributeName OPTIONAL,
|
||
OUT LPBYTE lpBuffer,
|
||
IN DWORD dwBufferLength,
|
||
OUT LPDWORD lpdwCharactersReturned,
|
||
IN GOPHER_ATTRIBUTE_ENUMERATOR lpfnEnumerator OPTIONAL,
|
||
IN DWORD_PTR dwContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Gets an attribute from a server
|
||
|
||
BUGBUG - it should be possible for a caller to specify e.g. "+VIEWS+ABSTRACT"
|
||
(according to gopher plus documentation) and get both types back
|
||
|
||
Arguments:
|
||
|
||
hGopherSession - identifies the gopher session object
|
||
|
||
lpszLocator - pointer to locator identifying item to get
|
||
attribute for
|
||
|
||
lpszAttributeName - name of attribute to return. May be NULL,
|
||
meaning return everything
|
||
|
||
lpBuffer - pointer to buffer where attributes are to be
|
||
returned
|
||
|
||
dwBufferLength - length of buffer
|
||
|
||
lpdwCharactersReturned - pointer to variable which will receive the
|
||
number of bytes in lpBuffer on output (if no
|
||
error occurs)
|
||
|
||
lpfnEnumerator - optional enumerator. If supplied, we return an
|
||
enumerated series of GOPHER_ATTRIBUTE_TYPE
|
||
items, else we just return the info in the
|
||
caller's buffer
|
||
|
||
dwContext - app-supplied context value for use in call-backs
|
||
|
||
Return Value:
|
||
|
||
BOOL
|
||
Success - TRUE
|
||
|
||
Failure - FALSE. Use GetLastError()/InternetGetLastResponseInfo() for
|
||
more information
|
||
|
||
--*/
|
||
|
||
{
|
||
#if !defined(GOPHER_ATTRIBUTE_SUPPORT)
|
||
|
||
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
||
return FALSE;
|
||
|
||
#else
|
||
|
||
DEBUG_ENTER_API((DBG_API,
|
||
Bool,
|
||
"GopherGetAttributeA",
|
||
"%#x, %q, %q, %#x, %d, %#x, %#x, %#x",
|
||
hGopherSession,
|
||
lpszLocator,
|
||
lpszAttributeName,
|
||
lpBuffer,
|
||
dwBufferLength,
|
||
lpdwCharactersReturned,
|
||
lpfnEnumerator,
|
||
dwContext
|
||
));
|
||
|
||
DWORD error;
|
||
DWORD gopherType;
|
||
LPSTR gopherPlusInfo;
|
||
DWORD categoryId;
|
||
DWORD attributeId;
|
||
DWORD attributeLength;
|
||
LPBYTE attributeBuffer = NULL;
|
||
LPINTERNET_THREAD_INFO lpThreadInfo;
|
||
DWORD bufferSize;
|
||
HINTERNET hMapped = NULL;
|
||
BOOL fDeref = TRUE;
|
||
|
||
if (!GlobalDataInitialized) {
|
||
error = ERROR_INTERNET_NOT_INITIALIZED;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// get the per-thread info block
|
||
//
|
||
|
||
lpThreadInfo = InternetGetThreadInfo();
|
||
if (lpThreadInfo == NULL) {
|
||
|
||
INET_ASSERT(FALSE);
|
||
|
||
error = ERROR_INTERNET_INTERNAL_ERROR;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// map the handle
|
||
//
|
||
|
||
error = MapHandleToAddress(hGopherSession, (LPVOID *)&hMapped, FALSE);
|
||
if ((error != ERROR_SUCCESS) && (hMapped == NULL)) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// set the call context info & clear the error variables
|
||
//
|
||
|
||
_InternetSetObjectHandle(lpThreadInfo, hGopherSession, hMapped);
|
||
_InternetSetContext(lpThreadInfo, dwContext);
|
||
_InternetClearLastError(lpThreadInfo);
|
||
|
||
//
|
||
// quit now if the handle is invalid
|
||
//
|
||
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// validate the handle on all paths
|
||
//
|
||
|
||
BOOL isLocal;
|
||
BOOL isAsync;
|
||
|
||
error = RIsHandleLocal(hMapped,
|
||
&isLocal,
|
||
&isAsync,
|
||
TypeGopherConnectHandle
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// if we're in the async worker thread context then we've already validated
|
||
// the parameters - skip validation and go straight to local/remote path
|
||
//
|
||
|
||
if (lpThreadInfo->IsAsyncWorkerThread) {
|
||
goto synchronous_path;
|
||
}
|
||
|
||
//
|
||
// validate parameters
|
||
//
|
||
|
||
if (!IsValidLocator(lpszLocator, MAX_GOPHER_LOCATOR_LENGTH)
|
||
|
||
//
|
||
// we say Buffer must be >= MIN_GOPHER_ATTRIBUTE_LENGTH to give us a chance
|
||
// of returning enumerated attributes
|
||
//
|
||
|
||
|| (dwBufferLength < MIN_GOPHER_ATTRIBUTE_LENGTH)
|
||
|| IsBadWritePtr(lpBuffer, dwBufferLength)
|
||
|| IsBadWritePtr(lpdwCharactersReturned, sizeof(*lpdwCharactersReturned))
|
||
|| ARGUMENT_PRESENT(lpfnEnumerator) && IsBadCodePtr((FARPROC)lpfnEnumerator)) {
|
||
error = ERROR_INVALID_PARAMETER;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// crack open the locator. We want the type and to know if its a gopher+
|
||
// locator. Note that we could *really* accept a gopher0 locator if the
|
||
// server identified therein was actually a gopher+ server, but that
|
||
// would require more work just to identify whether we could proceed or
|
||
// not. If the caller doesn't supply a gopher+ locator then 1) the caller
|
||
// supplied their own locator and it won't be painful to recreate a
|
||
// locator of the correct type, or 2) the locator was returned from a
|
||
// previous find first/find next call and therefore we are justified in
|
||
// refusing the request
|
||
//
|
||
|
||
CrackLocator(lpszLocator,
|
||
&gopherType,
|
||
NULL, // DisplayString
|
||
NULL, // DisplayStringLength
|
||
NULL, // SelectorString
|
||
NULL, // SelectorStringLength
|
||
NULL, // HostName
|
||
NULL, // HostNameLength
|
||
NULL, // GopherPort
|
||
&gopherPlusInfo
|
||
);
|
||
|
||
//
|
||
// the locator must identify a gopher+ item - i.e. there must at least be a
|
||
// "\t+" after the gopher server port number in the locator
|
||
//
|
||
|
||
if (!gopherPlusInfo) {
|
||
error = ERROR_GOPHER_NOT_GOPHER_PLUS;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// and must be a file or directory
|
||
//
|
||
// BUGBUG - I can't find a gopher+ index server. It may be wrong to preclude
|
||
// this type from getting attributes. Likewise for telnet sessions,
|
||
// TN3270 sessions, etc. etc.?
|
||
//
|
||
|
||
if (!(IS_GOPHER_FILE(gopherType) || IS_GOPHER_DIRECTORY(gopherType))) {
|
||
error = ERROR_GOPHER_INCORRECT_LOCATOR_TYPE;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// convert a NULL string to a NULL pointer, indicating that the caller wants
|
||
// all available attributes for the locator
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(lpszAttributeName) && (*lpszAttributeName == '\0')) {
|
||
lpszAttributeName = NULL;
|
||
}
|
||
|
||
//
|
||
// we don't allow the app to request the +INFO attribute - we would have to
|
||
// return it as a GOPHER_FIND_DATA which is already catered for by
|
||
// GopherFindFirstFile()/GopherFindNext()
|
||
//
|
||
|
||
MapAttributeToIds(lpszAttributeName, &categoryId, &attributeId);
|
||
if (categoryId == GOPHER_CATEGORY_ID_INFO) {
|
||
error = ERROR_INVALID_PARAMETER;
|
||
goto quit;
|
||
}
|
||
|
||
//
|
||
// queue async request if that's what we're asked to do. We know we're not
|
||
// in the async worker thread context at this point
|
||
//
|
||
|
||
INET_ASSERT(!lpThreadInfo->IsAsyncWorkerThread);
|
||
|
||
if (isAsync && (dwContext != INTERNET_NO_CALLBACK))
|
||
{
|
||
// MakeAsyncRequest
|
||
CFsm_GopherGetAttribute * pFsm;
|
||
|
||
pFsm = new CFsm_GopherGetAttribute(hGopherSession, dwContext, lpszLocator, lpszAttributeName, lpBuffer,
|
||
dwBufferLength, lpdwCharactersReturned, lpfnEnumerator );
|
||
|
||
if (pFsm != NULL &&
|
||
pFsm->GetError() == ERROR_SUCCESS)
|
||
{
|
||
error = pFsm->QueueWorkItem();
|
||
if ( error == ERROR_IO_PENDING ) {
|
||
fDeref = FALSE;
|
||
}
|
||
}
|
||
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;
|
||
}
|
||
|
||
synchronous_path:
|
||
|
||
//
|
||
// we will allocate a buffer to receive the attributes into. Since we
|
||
// won't be growing the buffer, we have to loop until we receive all
|
||
// attribute information, or until an error occurs
|
||
//
|
||
|
||
bufferSize = GOPHER_ATTRIBUTE_BUFFER_LENGTH;
|
||
error = ERROR_INSUFFICIENT_BUFFER;
|
||
while (error == ERROR_INSUFFICIENT_BUFFER) {
|
||
|
||
//
|
||
// we need to allocate an intermediate buffer to receive the
|
||
// attributes into, for two reasons: 1) we don't return the +INFO
|
||
// attribute, 2) we probably need to filter the returned attributes
|
||
// before returning the requested attributes to the caller
|
||
//
|
||
|
||
attributeBuffer = NEW_MEMORY(bufferSize, BYTE);
|
||
if (attributeBuffer != NULL) {
|
||
attributeLength = bufferSize;
|
||
error = wGopherGetAttribute(lpszLocator,
|
||
lpszAttributeName,
|
||
attributeBuffer,
|
||
&attributeLength
|
||
);
|
||
|
||
} else {
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
ERROR,
|
||
("failed to allocate %d byte attribute buffer\n",
|
||
GOPHER_ATTRIBUTE_BUFFER_LENGTH
|
||
));
|
||
|
||
error = ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
//
|
||
// if we have tried up to 64K then something serious has gone wrong.
|
||
// Return an internal error (BUGBUG?)
|
||
//
|
||
|
||
if (error == ERROR_INSUFFICIENT_BUFFER) {
|
||
if (bufferSize >= 64 K) {
|
||
error = ERROR_INTERNET_INTERNAL_ERROR;
|
||
} else {
|
||
DEL(attributeBuffer);
|
||
bufferSize += GOPHER_ATTRIBUTE_BUFFER_LENGTH;
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
WARNING,
|
||
("attribute buffer too small: trying %d\n",
|
||
bufferSize
|
||
));
|
||
|
||
}
|
||
}
|
||
}
|
||
if (error == ERROR_SUCCESS) {
|
||
|
||
//
|
||
// we successfully got a buffer of attributes. Return the attributes
|
||
// requested, either in the user buffer or via the enumerator
|
||
//
|
||
|
||
error = GetAttributes(lpfnEnumerator,
|
||
categoryId,
|
||
attributeId,
|
||
lpszAttributeName,
|
||
(LPSTR)attributeBuffer,
|
||
attributeLength,
|
||
lpBuffer,
|
||
dwBufferLength,
|
||
lpdwCharactersReturned
|
||
);
|
||
}
|
||
if (attributeBuffer != NULL) {
|
||
DEL(attributeBuffer);
|
||
}
|
||
|
||
quit:
|
||
|
||
BOOL success;
|
||
|
||
if (error != ERROR_SUCCESS) {
|
||
|
||
DEBUG_ERROR(API, error);
|
||
|
||
SetLastError(error);
|
||
success = FALSE;
|
||
} else {
|
||
success = TRUE;
|
||
}
|
||
|
||
if ((hMapped != NULL) && fDeref) {
|
||
DereferenceObject((LPVOID)hMapped);
|
||
}
|
||
|
||
DEBUG_LEAVE_API(success);
|
||
|
||
return success;
|
||
|
||
#endif
|
||
}
|
||
|
||
//
|
||
//INTERNETAPI_(BOOL) GopherSendDataA(
|
||
// IN HINTERNET hGopherSession,
|
||
// IN LPCSTR lpszLocator,
|
||
// IN LPCSTR lpszBuffer,
|
||
// IN DWORD dwNumberOfCharactersToSend,
|
||
// OUT LPDWORD lpdwNumberOfCharactersSent,
|
||
// IN DWORD dwContext
|
||
// )
|
||
//
|
||
///*++
|
||
//
|
||
//Routine Description:
|
||
//
|
||
// Sends arbitrary text to a gopher server. This function is used primarily
|
||
// for returning the results of a gopher+ ASK item
|
||
//
|
||
//Arguments:
|
||
//
|
||
// hGopherSession - identifies the gopher session object
|
||
//
|
||
// lpszLocator - pointer to locator identifying item to send
|
||
// data for
|
||
//
|
||
// lpszBuffer - pointer to buffer containing data to send
|
||
//
|
||
// dwNumberOfCharactersToSend - number of bytes in lpszBuffer
|
||
//
|
||
// lpdwNumberOfCharactersSent - pointer to returned number of bytes sent
|
||
//
|
||
// dwContext - app-supplied context value for use in call-backs
|
||
//
|
||
//Return Value:
|
||
//
|
||
// BOOL
|
||
// Success - TRUE
|
||
//
|
||
// Failure - FALSE. Call GetLastError()/InternetGetLastResponseInfo() for
|
||
// more information
|
||
//
|
||
//--*/
|
||
//
|
||
//{
|
||
// DEBUG_ENTER_API((DBG_API,
|
||
// Bool,
|
||
// "GopherSendDataA",
|
||
// "%#x, %q, %#x, %d, %#x, %#x",
|
||
// hGopherSession,
|
||
// lpszLocator,
|
||
// lpszBuffer,
|
||
// dwNumberOfCharactersToSend,
|
||
// lpdwNumberOfCharactersSent,
|
||
// dwContext
|
||
// ));
|
||
//
|
||
// DEBUG_ERROR(API, ERROR_CALL_NOT_IMPLEMENTED);
|
||
//
|
||
// //
|
||
// // this is the handle we are currently working on
|
||
// //
|
||
//
|
||
// InternetSetObjectHandle(hGopherSession, hGopherSession);
|
||
//
|
||
// //
|
||
// // clear the per-handle object last error variables
|
||
// //
|
||
//
|
||
// InternetClearLastError();
|
||
//
|
||
// SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
||
//
|
||
// DEBUG_LEAVE_API(FALSE);
|
||
//
|
||
// return FALSE;
|
||
//}
|
||
|
||
//
|
||
//DWORD
|
||
//pGfrGetUrlInfo(
|
||
// IN HANDLE InternetConnectHandle,
|
||
// OUT LPSTR Url
|
||
// )
|
||
//
|
||
///*++
|
||
//
|
||
//Routine Description:
|
||
//
|
||
// description-of-function.
|
||
//
|
||
//Arguments:
|
||
//
|
||
// InternetConnectHandle -
|
||
// Url -
|
||
//
|
||
//Return Value:
|
||
//
|
||
// DWORD
|
||
//
|
||
//--*/
|
||
//
|
||
//{
|
||
// return( ERROR_CALL_NOT_IMPLEMENTED );
|
||
//}
|
||
|
||
|
||
DWORD
|
||
pGopherGetUrlString(
|
||
IN INTERNET_SCHEME SchemeType,
|
||
IN LPSTR lpszTargetHost,
|
||
IN LPSTR lpszCWD,
|
||
IN LPSTR lpszObjectLocator,
|
||
IN LPSTR lpszExtension,
|
||
IN DWORD dwPort,
|
||
OUT LPSTR *lplpUrlName,
|
||
OUT LPDWORD lpdwUrlLen
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Creates an URL for a gopher entity
|
||
|
||
Arguments:
|
||
|
||
SchemeType - protocol scheme (INTERNET_SCHEME_GOPHER)
|
||
|
||
lpszTargetHost - server
|
||
|
||
lpszCWD - current directory at server
|
||
|
||
lpszObjectLocator - gopher locator string
|
||
|
||
lpszExtension - file extension
|
||
|
||
dwPort - port at server
|
||
|
||
lplpUrlName - pointer to returned URL
|
||
|
||
lpdwUrlLen - pointer to returned URL length
|
||
|
||
Return Value:
|
||
|
||
DWORD
|
||
Success - ERROR_SUCCESS
|
||
|
||
Failure - ERROR_INVALID_PARAMETER
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY
|
||
|
||
--*/
|
||
|
||
{
|
||
DWORD error, dwUrlLen, dwExtensionLength;
|
||
BOOL fDoneOnce = FALSE;
|
||
|
||
if (lpszExtension) {
|
||
dwExtensionLength = lstrlen(lpszExtension)
|
||
+ lstrlen(GOPHER_EXTENSION_DELIMITER_SZ);
|
||
} else {
|
||
dwExtensionLength = 0;
|
||
}
|
||
*lplpUrlName = NULL;
|
||
*lpdwUrlLen = 128 + dwExtensionLength; // let us try with less first
|
||
|
||
BOOL isDir;
|
||
|
||
error = TestLocatorType(lpszObjectLocator, GOPHER_TYPE_DIRECTORY);
|
||
isDir = error == ERROR_SUCCESS;
|
||
|
||
error = ERROR_INVALID_PARAMETER;
|
||
|
||
do {
|
||
|
||
INET_ASSERT(*lplpUrlName == NULL);
|
||
|
||
//
|
||
// BUGBUG - LPTR!
|
||
//
|
||
|
||
//*lplpUrlName = (LPSTR)ALLOCATE_MEMORY(LPTR, *lpdwUrlLen);
|
||
|
||
*lplpUrlName = (LPSTR)ALLOCATE_FIXED_MEMORY(*lpdwUrlLen);
|
||
if (!*lplpUrlName) {
|
||
error = GetLastError();
|
||
goto Cleanup;
|
||
}
|
||
dwUrlLen = *lpdwUrlLen;
|
||
#ifdef MAYBE
|
||
if (isDir) {
|
||
**lplpUrlName = '~';
|
||
}
|
||
#endif //MAYBE
|
||
error = GopherLocatorToUrl(lpszObjectLocator,
|
||
#ifdef MAYBE
|
||
isDir ? (*lplpUrlName + 1) : *lplpUrlName,
|
||
isDir ? (*lpdwUrlLen - 1) : *lpdwUrlLen,
|
||
#endif //MAYBE
|
||
*lplpUrlName,
|
||
*lpdwUrlLen,
|
||
&dwUrlLen
|
||
);
|
||
if (error != ERROR_SUCCESS) {
|
||
|
||
//
|
||
// BUGBUG - *lplpUrlName cannot be non-NULL?
|
||
//
|
||
|
||
if (*lplpUrlName) {
|
||
FREE_MEMORY(*lplpUrlName);
|
||
*lplpUrlName = NULL;
|
||
}
|
||
}
|
||
if (!fDoneOnce) {
|
||
if (error != ERROR_INSUFFICIENT_BUFFER) {
|
||
break;
|
||
} else {
|
||
*lpdwUrlLen = INTERNET_MAX_URL_LENGTH + dwExtensionLength;
|
||
fDoneOnce = TRUE;
|
||
}
|
||
} else {
|
||
break;
|
||
}
|
||
} while (TRUE);
|
||
|
||
if (error == ERROR_SUCCESS) {
|
||
if (lpszExtension) {
|
||
|
||
INET_ASSERT(dwExtensionLength);
|
||
|
||
lstrcat(*lplpUrlName, GOPHER_EXTENSION_DELIMITER_SZ);
|
||
lstrcat(*lplpUrlName, lpszExtension);
|
||
}
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
return error;
|
||
}
|
||
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherBeginCacheReadProcessing(
|
||
IN HINTERNET hGopher,
|
||
IN LPCSTR lpszFileName,
|
||
IN LPCSTR lpszViewType,
|
||
IN DWORD dwFlags,
|
||
IN DWORD_PTR dwContext,
|
||
IN BOOL fIsHtmlFind
|
||
)
|
||
{
|
||
DEBUG_ENTER((DBG_GOPHER,
|
||
Bool,
|
||
"FGopherBeginCacheReadProcessing",
|
||
"%#x, %q, %q, %#x, %#x, %B",
|
||
hGopher,
|
||
lpszFileName,
|
||
lpszViewType,
|
||
dwFlags,
|
||
dwContext,
|
||
fIsHtmlFind
|
||
));
|
||
|
||
DWORD dwError = ERROR_SUCCESS;
|
||
URLGEN_FUNC fn = pGopherGetUrlString;
|
||
LPCACHE_ENTRY_INFO lpCEI = NULL;
|
||
BOOL ok;
|
||
|
||
if (((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->IsCacheReadInProgress()) {
|
||
goto quit;
|
||
}
|
||
|
||
if (!(dwFlags & INTERNET_FLAG_NO_CACHE_WRITE)) {
|
||
|
||
//
|
||
// if the object name is not set then all cache methods fail
|
||
//
|
||
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->SetObjectName(
|
||
(LPSTR)lpszFileName,
|
||
(LPSTR)lpszViewType,
|
||
&fn
|
||
);
|
||
|
||
//
|
||
// set the cache flags like RELOAD etc.
|
||
//
|
||
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->SetCacheFlags(dwFlags);
|
||
} else {
|
||
|
||
//
|
||
// set flags to disable both read and write
|
||
//
|
||
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->SetCacheFlags(
|
||
INTERNET_FLAG_DONT_CACHE
|
||
| INTERNET_FLAG_RELOAD
|
||
);
|
||
}
|
||
|
||
if (!FGopherCanReadFromCache(hGopher)) {
|
||
dwError = !ERROR_SUCCESS;
|
||
goto quit;
|
||
}
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("Checking in the cache\n"
|
||
));
|
||
|
||
dwError = ((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->BeginCacheRetrieval(&lpCEI);
|
||
if (dwError == ERROR_SUCCESS) {
|
||
|
||
//
|
||
// found it in the cache
|
||
//
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("Found in the cache\n"
|
||
));
|
||
|
||
if (IsOffline()
|
||
|| (!FIsGopherExpired(hGopher, lpCEI)
|
||
&& ((fIsHtmlFind && lpCEI->lpszFileExtension)
|
||
|| (!fIsHtmlFind && !lpCEI->lpszFileExtension)))) {
|
||
|
||
dwError = ERROR_SUCCESS;
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->SetFromCache();
|
||
} else {
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("Expired or invalid datatype\n"
|
||
));
|
||
|
||
dwError = ((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->EndCacheRetrieval();
|
||
|
||
INET_ASSERT(dwError == ERROR_SUCCESS);
|
||
|
||
dwError = ERROR_FILE_NOT_FOUND;
|
||
}
|
||
}
|
||
|
||
if (lpCEI != NULL) {
|
||
lpCEI = (LPCACHE_ENTRY_INFO)FREE_MEMORY(lpCEI);
|
||
|
||
INET_ASSERT(lpCEI == NULL);
|
||
|
||
}
|
||
|
||
quit:
|
||
|
||
ok = (dwError == ERROR_SUCCESS);
|
||
|
||
DEBUG_LEAVE(ok);
|
||
|
||
return ok;
|
||
}
|
||
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherCanReadFromCache(
|
||
HINTERNET hGopher
|
||
)
|
||
{
|
||
DEBUG_ENTER((DBG_GOPHER,
|
||
Bool,
|
||
"FGopherCanReadFromCache",
|
||
"%#x",
|
||
hGopher
|
||
));
|
||
|
||
DWORD dwOpenFlags = ((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->GetInternetOpenFlags();
|
||
DWORD dwCacheFlags = ((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->GetCacheFlags();
|
||
BOOL ok = TRUE;
|
||
|
||
//
|
||
// in disconnected state client always reads
|
||
//
|
||
|
||
if (((dwOpenFlags | dwCacheFlags) & INTERNET_FLAG_OFFLINE) || IsOffline()) {
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("open flags=%#x, cache flags=%#x, offline=%B\n",
|
||
dwOpenFlags,
|
||
dwCacheFlags,
|
||
IsOffline()
|
||
));
|
||
|
||
} else if (dwCacheFlags & (INTERNET_FLAG_RELOAD | INTERNET_FLAG_RESYNCHRONIZE)) {
|
||
|
||
//
|
||
// if we are asked to reload data, it is not OK to read from cache
|
||
//
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("no cache option\n"
|
||
));
|
||
|
||
ok = FALSE;
|
||
}
|
||
|
||
DEBUG_LEAVE(ok);
|
||
|
||
return ok;
|
||
}
|
||
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherBeginCacheWriteProcessing(
|
||
IN HINTERNET hGopher,
|
||
IN LPCSTR lpszFileName,
|
||
IN LPCSTR lpszViewType,
|
||
IN DWORD dwFlags,
|
||
IN DWORD_PTR dwContext,
|
||
IN BOOL fIsHtmlFind
|
||
)
|
||
{
|
||
DWORD dwError = ERROR_INVALID_FUNCTION;
|
||
URLGEN_FUNC fn = pGopherGetUrlString;
|
||
LPSTR lpszFileExtension, lpTemp;
|
||
char cExt[DEFAULT_MAX_EXTENSION_LENGTH + sizeof("%09%09%2B") + 1];
|
||
DWORD dwBuffLen;
|
||
|
||
|
||
if (!((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->IsCacheReadInProgress()) {
|
||
|
||
//
|
||
// we are not reading from the cache
|
||
// Let us ask a routine whether we should cache this
|
||
// stuff or not
|
||
//
|
||
|
||
if (FGopherCanWriteToCache(hGopher)) {
|
||
|
||
//
|
||
// if the object name is not set then all cache methods fail
|
||
//
|
||
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->SetObjectName(
|
||
(LPSTR)lpszFileName,
|
||
(LPSTR)lpszViewType,
|
||
&fn
|
||
);
|
||
|
||
//
|
||
// set the cache flags
|
||
//
|
||
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->SetCacheFlags(dwFlags);
|
||
|
||
//
|
||
// he says we can cache it.
|
||
//
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("Starting cache write\n"
|
||
));
|
||
|
||
if (!fIsHtmlFind) {
|
||
dwBuffLen = sizeof(cExt);
|
||
|
||
lpszFileExtension = GetFileExtensionFromUrl(
|
||
((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->GetURL(),
|
||
&dwBuffLen
|
||
);
|
||
|
||
if (lpszFileExtension != NULL) {
|
||
|
||
//
|
||
// strip off gopher+ strings
|
||
//
|
||
|
||
for (lpTemp = lpszFileExtension;
|
||
*lpTemp != 0 && * lpTemp != '%';
|
||
++lpTemp) {
|
||
|
||
//
|
||
// EMPTY LOOP
|
||
//
|
||
|
||
}
|
||
|
||
dwBuffLen = (DWORD) PtrDifference(lpTemp, lpszFileExtension);
|
||
memcpy(cExt, lpszFileExtension, dwBuffLen);
|
||
cExt[dwBuffLen] = '\0';
|
||
lpszFileExtension = cExt;
|
||
}
|
||
}
|
||
else {
|
||
//generate htm extension
|
||
strcpy(cExt, "htm");
|
||
lpszFileExtension = cExt;
|
||
}
|
||
|
||
dwError = ((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->BeginCacheWrite(
|
||
0,
|
||
lpszFileExtension
|
||
);
|
||
|
||
if (dwError != ERROR_SUCCESS) {
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
ERROR,
|
||
("Error in BeginCacheWrite %ld\n",
|
||
dwError
|
||
));
|
||
|
||
}
|
||
}
|
||
}
|
||
return (dwError == ERROR_SUCCESS);
|
||
}
|
||
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FGopherCanWriteToCache(
|
||
HINTERNET hGopher
|
||
)
|
||
{
|
||
if (((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->
|
||
GetCacheFlags() & INTERNET_FLAG_DONT_CACHE) {
|
||
return (FALSE);
|
||
}
|
||
|
||
return (TRUE);
|
||
}
|
||
|
||
|
||
DWORD
|
||
InbGopherLocalEndCacheWrite(
|
||
IN HINTERNET hGopher,
|
||
IN LPSTR lpszFileExtension,
|
||
IN BOOL fNormal
|
||
)
|
||
{
|
||
|
||
FILETIME ftLastModTime, ftExpiryTime, ftPostCheck;
|
||
DWORD dwEntryType;
|
||
|
||
if (((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->IsCacheWriteInProgress()) {
|
||
|
||
ftLastModTime.dwLowDateTime =
|
||
ftLastModTime.dwHighDateTime = 0;
|
||
|
||
ftExpiryTime.dwLowDateTime =
|
||
ftExpiryTime.dwHighDateTime = 0;
|
||
|
||
ftPostCheck.dwLowDateTime =
|
||
ftPostCheck.dwHighDateTime = 0;
|
||
|
||
|
||
dwEntryType = (!fNormal)?0xffffffff:
|
||
((((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->
|
||
|
||
GetCacheFlags() & INTERNET_FLAG_MAKE_PERSISTENT)
|
||
? STICKY_CACHE_ENTRY:0
|
||
);
|
||
|
||
DEBUG_PRINT(GOPHER,
|
||
INFO,
|
||
("Cache write EntryType = %x\r\n", dwEntryType
|
||
));
|
||
|
||
return (((INTERNET_CONNECT_HANDLE_OBJECT *)hGopher)->EndCacheWrite(
|
||
&ftExpiryTime,
|
||
&ftLastModTime,
|
||
&ftPostCheck,
|
||
dwEntryType,
|
||
0,
|
||
NULL,
|
||
lpszFileExtension
|
||
));
|
||
}
|
||
return (ERROR_SUCCESS);
|
||
}
|
||
|
||
|
||
PRIVATE
|
||
BOOL
|
||
FIsGopherExpired(
|
||
HINTERNET hGopher,
|
||
LPCACHE_ENTRY_INFO lpCEI
|
||
)
|
||
{
|
||
DEBUG_ENTER((DBG_GOPHER,
|
||
Bool,
|
||
"FIsGopherExpired",
|
||
"%#x, %#x",
|
||
hGopher,
|
||
lpCEI
|
||
));
|
||
|
||
FILETIME ft;
|
||
BOOL fExpired = TRUE;
|
||
|
||
GetCurrentGmtTime(&ft);
|
||
if (CheckExpired( hGopher,
|
||
&fExpired,
|
||
lpCEI,
|
||
dwdwGopherDefaultExpiryDelta) != ERROR_SUCCESS) {
|
||
fExpired = TRUE;
|
||
}
|
||
|
||
DEBUG_LEAVE(fExpired);
|
||
|
||
return (fExpired);
|
||
}
|