1216 lines
38 KiB
C++
1216 lines
38 KiB
C++
/*++
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name:
|
|
gfrapir.cxx
|
|
|
|
Abstract:
|
|
|
|
Contains the remote-side gopher API worker functions. In each case, the API
|
|
proper validates the arguments. The worker functions contained herein just
|
|
perform the requested operation with the supplied arguments.
|
|
|
|
These functions are the remote side of the RPC interface. If the DLL is
|
|
the abstract0 version (no RPC) then the A forms of the functions simply call the w functions
|
|
|
|
Contents:
|
|
wGopherFindFirst
|
|
wGopherFindNext
|
|
wGopherFindClose
|
|
wGopherOpenFile
|
|
wGopherReadFile
|
|
wGopherQueryDataAvailable
|
|
wGopherCloseHandle
|
|
wGopherGetAttribute
|
|
wGopherConnect
|
|
wGopherDisconnect
|
|
(GetView)
|
|
|
|
Author:
|
|
Richard L Firth (rfirth) 14-Oct-1994
|
|
|
|
Environment:
|
|
Win32 DLL
|
|
|
|
Revision History:
|
|
14-Oct-1994 rfirth
|
|
Created
|
|
--*/
|
|
|
|
#include <wininetp.h>
|
|
#include "gfrapih.h"
|
|
|
|
// manifests
|
|
#define DEFAULT_REQUEST_BUFFER_LENGTH (MAX_GOPHER_SELECTOR_TEXT + GOPHER_REQUEST_TERMINATOR_LENGTH + 1)
|
|
|
|
// private data
|
|
char szQuery[] = "query "; // prepend to CSO searches
|
|
|
|
// private prototypes
|
|
PRIVATE
|
|
DWORD
|
|
GetView(
|
|
IN LPSESSION_INFO SessionInfo,
|
|
IN VIEW_TYPE ViewType,
|
|
IN LPSTR Request,
|
|
IN BOOL RequestIsGopherPlus,
|
|
IN DWORD ResponseFlags,
|
|
OUT LPVIEW_INFO* pViewInfo
|
|
);
|
|
|
|
extern
|
|
DWORD
|
|
InbGopherLocalEndCacheWrite(
|
|
IN HINTERNET hGopherFile,
|
|
IN LPSTR lpszFileExtension,
|
|
IN BOOL fNormal
|
|
);
|
|
|
|
// functions
|
|
|
|
|
|
DWORD
|
|
wGopherFindFirst(
|
|
IN LPCSTR lpszLocator,
|
|
IN LPCSTR lpszSearchString OPTIONAL,
|
|
OUT LPGOPHER_FIND_DATA lpBuffer OPTIONAL,
|
|
OUT LPHINTERNET lpHandle
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Connects to the gopher server, sends a request to get directory information,
|
|
gets the response and converts the gopher descriptor strings to
|
|
GOPHER_FIND_DATA structures
|
|
Arguments:
|
|
lpszLocator - pointer to descriptor of information to get
|
|
lpszSearchString - pointer to strings to search for if Locator is search
|
|
server. This argument MUST be present if Locator is
|
|
an search server
|
|
lpBuffer - pointer to user-allocated buffer in which to return
|
|
info
|
|
lpHandle - pointer to returned handle if ERROR_SUCCESS returned
|
|
Return Value:
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
WSA error
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"wGopherFindFirst",
|
|
"%q, %q, %#x, %#x",
|
|
lpszLocator,
|
|
lpszSearchString,
|
|
lpBuffer,
|
|
lpHandle
|
|
));
|
|
|
|
DWORD gopherType;
|
|
LPSTR requestPtr;
|
|
DWORD requestLen;
|
|
char hostName[MAX_GOPHER_HOST_NAME + 1];
|
|
DWORD hostNameLen;
|
|
DWORD port;
|
|
LPSTR gopherPlus;
|
|
LPSESSION_INFO sessionInfo;
|
|
DWORD error;
|
|
HINTERNET findHandle;
|
|
LPVIEW_INFO viewInfo;
|
|
DWORD newRequestLength;
|
|
DWORD searchStringsLength;
|
|
|
|
// initialise variables in case of early exit (via goto)
|
|
sessionInfo = NULL;
|
|
|
|
// grab a buffer for the request string
|
|
requestLen = DEFAULT_REQUEST_BUFFER_LENGTH;
|
|
requestPtr = (LPSTR)ResizeBuffer(NULL, requestLen, FALSE);
|
|
if (requestPtr == NULL) {
|
|
DEBUG_LEAVE(ERROR_NOT_ENOUGH_MEMORY);
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
// pull the individual fields out of the locator. Not interested in display
|
|
// string
|
|
hostNameLen = sizeof(hostName);
|
|
if (!CrackLocator(lpszLocator,
|
|
&gopherType,
|
|
NULL, // DisplayString
|
|
NULL, // DisplayStringLength
|
|
requestPtr, // SelectorString
|
|
&requestLen, // SelectorStringLength
|
|
hostName,
|
|
&hostNameLen,
|
|
&port,
|
|
&gopherPlus
|
|
)) {
|
|
error = ERROR_GOPHER_INVALID_LOCATOR;
|
|
goto quit;
|
|
}
|
|
|
|
// find the session 'object'. If we don't have one describing the requested
|
|
// gopher server then create one
|
|
sessionInfo = FindOrCreateSession(hostName, port, &error);
|
|
if (sessionInfo == NULL) {
|
|
goto quit;
|
|
}
|
|
|
|
// if the request is gopher+ or plain gopher but we know the server is
|
|
// gopher+ then we automatically promote the request to be gopher+. It
|
|
// potentially makes life easier for this DLL (the server could tell us
|
|
// the exact length of the response or be more discerning about errors)
|
|
// and potentially gives more information to the app. Either way, the app
|
|
// doesn't lose by this
|
|
if (gopherPlus || IsGopherPlusSession(sessionInfo)) {
|
|
gopherType |= GOPHER_TYPE_GOPHER_PLUS;
|
|
}
|
|
|
|
// calculate the length of the extra strings we have to add to the selector
|
|
newRequestLength = requestLen;
|
|
|
|
if ( IS_GOPHER_SEARCH_SERVER(gopherType)) {
|
|
INET_ASSERT(lpszSearchString != NULL);
|
|
INET_ASSERT(*lpszSearchString != '\0');
|
|
|
|
// add search strings length
|
|
searchStringsLength = strlen(lpszSearchString);
|
|
newRequestLength += searchStringsLength;
|
|
|
|
if (IS_GOPHER_INDEX_SERVER(gopherType)) {
|
|
newRequestLength++; // for tab
|
|
} else {
|
|
newRequestLength += sizeof(szQuery) - 1;
|
|
}
|
|
} else {
|
|
searchStringsLength = 0;
|
|
}
|
|
|
|
// gopher+ requests have "\t+" or "\t$" at the end of the request
|
|
if (IS_GOPHER_PLUS(gopherType)) {
|
|
newRequestLength += 2;
|
|
}
|
|
|
|
// all requests terminated by "\r\n". Add 1 for string terminator
|
|
newRequestLength += sizeof(GOPHER_REQUEST_TERMINATOR);
|
|
|
|
// grow the buffer if necessary
|
|
if (newRequestLength > DEFAULT_REQUEST_BUFFER_LENGTH) {
|
|
requestPtr = (LPSTR)ResizeBuffer((HLOCAL)requestPtr, newRequestLength, FALSE);
|
|
if (requestPtr == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
// add the additional strings
|
|
if (searchStringsLength != 0) {
|
|
if (IS_GOPHER_INDEX_SERVER(gopherType)) {
|
|
requestPtr[requestLen++] = GOPHER_FIELD_SEPARATOR;
|
|
} else {
|
|
memcpy (requestPtr + requestLen, szQuery, sizeof(szQuery) - 1);
|
|
requestLen += sizeof(szQuery) - 1;
|
|
}
|
|
memcpy(&requestPtr[requestLen], lpszSearchString, searchStringsLength);
|
|
requestLen += searchStringsLength;
|
|
}
|
|
if (IS_GOPHER_PLUS(gopherType)) {
|
|
requestPtr[requestLen++] = GOPHER_FIELD_SEPARATOR;
|
|
requestPtr[requestLen++] =
|
|
IS_GOPHER_SEARCH_SERVER(gopherType)? '+' : '$';
|
|
}
|
|
memcpy(&requestPtr[requestLen],
|
|
GOPHER_REQUEST_TERMINATOR,
|
|
sizeof(GOPHER_REQUEST_TERMINATOR) // don't scrub the '\0' in this case
|
|
);
|
|
|
|
// selector munged; get the directory listing
|
|
error = GetView(sessionInfo,
|
|
ViewTypeFind,
|
|
requestPtr,
|
|
IS_GOPHER_PLUS(gopherType) ? TRUE : FALSE,
|
|
BI_DOT_AT_END,
|
|
&viewInfo
|
|
);
|
|
|
|
// if no error was reported then we can return a directory entry
|
|
if (error == ERROR_SUCCESS) {
|
|
// if the caller supplied an output buffer then convert the first
|
|
// directory entry to the API buffer format, else the caller wants
|
|
// all gopher directory information returned by InternetFindNextFile()
|
|
if (ARGUMENT_PRESENT(lpBuffer)) {
|
|
error = GetDirEntry(viewInfo, lpBuffer);
|
|
}
|
|
if (error == ERROR_SUCCESS) {
|
|
findHandle = viewInfo->Handle;
|
|
} else {
|
|
DereferenceView(viewInfo);
|
|
}
|
|
}
|
|
|
|
quit:
|
|
|
|
// dereference the session. If we have an active VIEW_INFO then the
|
|
// reference count will still be > 0
|
|
if (sessionInfo != NULL) {
|
|
DereferenceSession(sessionInfo);
|
|
}
|
|
|
|
// if we allocated a new request buffer for a large search request then
|
|
// free it
|
|
if (requestPtr != NULL) {
|
|
requestPtr = (LPSTR)ResizeBuffer((HLOCAL)requestPtr, 0, FALSE);
|
|
INET_ASSERT(requestPtr == NULL);
|
|
}
|
|
|
|
// set the handle value - ignored by caller if we don't return ERROR_SUCCESS
|
|
*lpHandle = findHandle;
|
|
DEBUG_LEAVE(error);
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
wGopherFindNext(
|
|
IN HINTERNET hFind,
|
|
OUT LPGOPHER_FIND_DATA lpBuffer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Remote side of GopherFindNext API. All parameters have been validated by the
|
|
time this function is called, so we know that Buffer is large enough to
|
|
hold all the returned data
|
|
Arguments:
|
|
hFind - handle of FIND_DATA, created by GopherFindFirstFile()
|
|
lpBuffer - pointer to user-allocated buffer for GOPHER_FIND_DATA
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
Buffer contains next GOPHER_FIND_DATA structure
|
|
|
|
Failure - ERROR_INVALID_HANDLE
|
|
Can't find the VIEW_INFO corresponding to hFind
|
|
|
|
ERROR_NO_MORE_FILES
|
|
We have reached the end of the directory info
|
|
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"wGopherFindNext",
|
|
"%#x, %#x",
|
|
hFind,
|
|
lpBuffer
|
|
));
|
|
|
|
LPVIEW_INFO viewInfo;
|
|
DWORD error;
|
|
|
|
// locate the VIEW_INFO corresponding to hFind and the SESSION_INFO
|
|
// that owns it
|
|
|
|
viewInfo = FindViewByHandle(hFind, ViewTypeFind);
|
|
if (viewInfo != NULL) {
|
|
// just read out the next directory entry
|
|
error = GetDirEntry(viewInfo, lpBuffer);
|
|
|
|
// if the Find has been closed or this was the last entry, the following
|
|
// dereference will cause the VIEW_INFO to be deleted, and if there are no
|
|
// more requests outstanding on the SESSION_INFO then it too will be
|
|
// deleted (via a dereference)
|
|
DereferenceView(viewInfo);
|
|
} else {
|
|
error = ERROR_INVALID_HANDLE;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
wGopherFindClose(
|
|
IN HINTERNET hFind
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Causes the VIEW_INFO described by hFind to be removed from the SESSION_INFO
|
|
and freed. If there are no other links to the data buffer then it too is
|
|
deallocated
|
|
|
|
Arguments:
|
|
hFind - handle describing the VIEW_INFO to terminate
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INVALID_HANDLE
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"wGopherFindClose",
|
|
"%#x",
|
|
hFind
|
|
));
|
|
|
|
DWORD error;
|
|
|
|
// atomically find and dereference the VIEW_INFO given the handle
|
|
error = DereferenceViewByHandle(hFind, ViewTypeFind);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
wGopherOpenFile(
|
|
IN LPCSTR lpszLocator,
|
|
IN LPCSTR lpszView OPTIONAL,
|
|
OUT LPHINTERNET lpHandle
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
'Opens' a gopher file by copying it locally and returning a handle to the
|
|
buffer
|
|
|
|
Arguments:
|
|
lpszLocator - pointer to locator describing file to open
|
|
lpszView - pointer to view name - identifies type of file to retrieve
|
|
lpHandle - pointer to returned handle if ERROR_SUCCESS returned
|
|
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
WSA error
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"wGopherOpenFile",
|
|
"%q, %q, %#x",
|
|
lpszLocator,
|
|
lpszView,
|
|
lpHandle
|
|
));
|
|
|
|
DWORD error;
|
|
DWORD gopherType;
|
|
LPSTR requestPtr;
|
|
DWORD requestLen;
|
|
char hostName[MAX_GOPHER_HOST_NAME + 1];
|
|
DWORD hostNameLen;
|
|
DWORD port;
|
|
LPSTR gopherPlus;
|
|
LPSESSION_INFO sessionInfo;
|
|
LPVIEW_INFO viewInfo;
|
|
HINTERNET fileHandle;
|
|
DWORD viewLen;
|
|
|
|
// initialise variables in case of early exit (via goto)
|
|
sessionInfo = NULL;
|
|
fileHandle = NULL;
|
|
|
|
// grab a buffer for the request string
|
|
requestLen = MAX_GOPHER_SELECTOR_TEXT + 1;
|
|
requestPtr = (LPSTR)ResizeBuffer((HLOCAL)NULL, requestLen, FALSE);
|
|
if (requestPtr == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
// pull the individual fields out of the locator. Not interested in display
|
|
// string
|
|
hostNameLen = sizeof(hostName);
|
|
if (!CrackLocator(lpszLocator,
|
|
&gopherType,
|
|
NULL, // DisplayString
|
|
NULL, // DisplayStringLength
|
|
requestPtr, // SelectorString
|
|
&requestLen, // SelectorStringLength
|
|
hostName,
|
|
&hostNameLen,
|
|
&port,
|
|
&gopherPlus
|
|
)) {
|
|
error = ERROR_GOPHER_INVALID_LOCATOR;
|
|
goto quit;
|
|
}
|
|
|
|
// find the session 'object'. If we don't have one describing the requested
|
|
// gopher server then create one
|
|
sessionInfo = FindOrCreateSession(hostName, port, &error);
|
|
if (sessionInfo == NULL) {
|
|
goto quit;
|
|
}
|
|
|
|
// (see GopherFindFirstFile()). If gopher+ is requested or available then
|
|
// make this a gopher+ request
|
|
if (gopherPlus || IsGopherPlusSession(sessionInfo)) {
|
|
gopherType |= GOPHER_TYPE_GOPHER_PLUS;
|
|
|
|
// we at least need some space for "\t+"
|
|
viewLen = sizeof(GOPHER_PLUS_INDICATOR) - 1;
|
|
|
|
// get the amount of space required for the alternate view, if supplied
|
|
if (ARGUMENT_PRESENT(lpszView)) {
|
|
// the extra +1 here is for the '+' between the selector and the
|
|
// alternate view string: the '\0' is handled by
|
|
// sizeof(GOPHER_REQUEST_TERMINATOR)
|
|
viewLen += strlen(lpszView);
|
|
}
|
|
} else {
|
|
// the caller may have supplied an alternate view for a gopher0 even
|
|
// though it is meaningless. Ensure that it is not used
|
|
lpszView = NULL;
|
|
viewLen = 0;
|
|
}
|
|
|
|
// grow the buffer if it is not large enough to hold the view etc. Note, if
|
|
// this is true, then we have bust one of our internal limits, which is
|
|
// unexpected to say the least. But this way, we can allow apps to present
|
|
// completely bogus (so we think) parameters, and at least give them a try
|
|
if ((requestLen + viewLen + sizeof(GOPHER_REQUEST_TERMINATOR))
|
|
> (MAX_GOPHER_SELECTOR_TEXT + 1)) {
|
|
requestPtr = (LPSTR)ResizeBuffer((HLOCAL)requestPtr,
|
|
requestLen
|
|
+ viewLen
|
|
+ sizeof(GOPHER_REQUEST_TERMINATOR),
|
|
FALSE
|
|
);
|
|
if (requestPtr == NULL) {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto quit;
|
|
}
|
|
}
|
|
|
|
// if this is a gopher plus request, then add the "\t+". If there is an
|
|
// alternate view then it will be appended to the plus, else we just add
|
|
// the line terminator
|
|
if (IS_GOPHER_PLUS(gopherType)) {
|
|
memcpy(&requestPtr[requestLen],
|
|
GOPHER_PLUS_INDICATOR,
|
|
sizeof(GOPHER_PLUS_INDICATOR) - 1
|
|
);
|
|
requestLen += sizeof(GOPHER_PLUS_INDICATOR) - 1;
|
|
}
|
|
|
|
// add the alternate view information, if any was supplied
|
|
if (ARGUMENT_PRESENT(lpszView)) {
|
|
memcpy(&requestPtr[requestLen], lpszView, viewLen);
|
|
requestLen += viewLen;
|
|
}
|
|
|
|
// in gopher0 and gopher+ cases we must terminate the selector by CR-LF
|
|
memcpy(&requestPtr[requestLen],
|
|
GOPHER_REQUEST_TERMINATOR,
|
|
sizeof(GOPHER_REQUEST_TERMINATOR)
|
|
);
|
|
|
|
// selector munged; get the file
|
|
error = GetView(sessionInfo,
|
|
ViewTypeFile,
|
|
requestPtr,
|
|
(gopherPlus != NULL)
|
|
? TRUE
|
|
: FALSE,
|
|
IS_DOT_TERMINATED_REQUEST(gopherType)
|
|
? BI_DOT_AT_END
|
|
: 0,
|
|
&viewInfo
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
fileHandle = viewInfo->Handle;
|
|
}
|
|
|
|
quit:
|
|
|
|
// free the request buffer
|
|
if (requestPtr != NULL) {
|
|
requestPtr = (LPSTR)ResizeBuffer((HLOCAL)requestPtr, 0, FALSE);
|
|
INET_ASSERT(requestPtr == NULL);
|
|
}
|
|
|
|
// dereference the session - this may cause it to be deleted
|
|
if (sessionInfo != NULL) {
|
|
DereferenceSession(sessionInfo);
|
|
}
|
|
|
|
// set the handle value - ignored by caller if we don't return ERROR_SUCCESS
|
|
*lpHandle = fileHandle;
|
|
|
|
DEBUG_LEAVE(error);
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD wGopherReadFile(IN HINTERNET hFile, OUT LPBYTE lpBuffer, IN DWORD dwBufferLength, OUT LPDWORD lpdwBytesReturned)
|
|
/*++
|
|
Routine Description:
|
|
Reads the next dwBufferLength bytes (or as much as is remaining) from the
|
|
file identified by hFile and writes to lpBuffer
|
|
|
|
Arguments:
|
|
hFile - identifies file
|
|
lpBuffer - place to return file data
|
|
dwBufferLength - length of Buffer
|
|
lpdwBytesReturned - amount of data written to Buffer
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
*lpdwBytesReturned written to lpBuffer
|
|
|
|
Failure - ERROR_INVALID_HANDLE
|
|
Couldn't find the VIEW_INFO corresponding to hFile
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER, Dword, "wGopherReadFile", "%#x, %#x, %d, %#x", hFile, lpBuffer, dwBufferLength, lpdwBytesReturned));
|
|
|
|
LPVIEW_INFO viewInfo;
|
|
DWORD error;
|
|
|
|
viewInfo = FindViewByHandle(hFile, ViewTypeFile);
|
|
if (viewInfo != NULL) {
|
|
LPBUFFER_INFO bufferInfo;
|
|
|
|
INET_ASSERT(viewInfo->BufferInfo != NULL);
|
|
|
|
bufferInfo = viewInfo->BufferInfo;
|
|
|
|
bufferInfo->Buffer = lpBuffer;
|
|
bufferInfo->BufferLength = dwBufferLength;
|
|
error = ReadData(viewInfo, lpdwBytesReturned);
|
|
DereferenceView(viewInfo);
|
|
} else {
|
|
error = ERROR_INVALID_HANDLE;
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
wGopherQueryDataAvailable(
|
|
IN HINTERNET hFile,
|
|
OUT LPDWORD lpdwNumberOfBytesAvailable
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Determines the amount of data available to be read on the socket
|
|
Arguments:
|
|
hFile - identifies gopher file
|
|
lpdwNumberOfBytesAvailable - where number of available bytes returned
|
|
Return Value:
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INVALID_HANDLE
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER, Dword, "wGopherQueryDataAvailable", "%#x, %#x", hFile, lpdwNumberOfBytesAvailable));
|
|
|
|
DWORD bytesAvailable = 0;
|
|
LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo();
|
|
DWORD error;
|
|
|
|
HINTERNET_HANDLE_TYPE handleType;
|
|
|
|
error = RGetHandleType(lpThreadInfo->hObjectMapped, &handleType);
|
|
if (error != ERROR_SUCCESS) {
|
|
return (error);
|
|
}
|
|
|
|
if (lpThreadInfo == NULL) {
|
|
INET_ASSERT(FALSE);
|
|
error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
goto quit;
|
|
}
|
|
|
|
LPVIEW_INFO viewInfo;
|
|
VIEW_TYPE viewType;
|
|
|
|
// assume this is a directory list first
|
|
if ((viewInfo = FindViewByHandle(hFile, ViewTypeFind)) != NULL) {
|
|
viewType = ViewTypeFind;
|
|
error = ERROR_SUCCESS;
|
|
if (viewInfo->ViewOffset < viewInfo->BufferInfo->BufferLength) {
|
|
bytesAvailable = sizeof(GOPHER_FIND_DATA);
|
|
}
|
|
} else if ((viewInfo = FindViewByHandle(hFile, ViewTypeFile)) != NULL) {
|
|
viewType = ViewTypeFile;
|
|
error = ERROR_SUCCESS;
|
|
} else {
|
|
error = ERROR_INVALID_HANDLE;
|
|
}
|
|
if ((error == ERROR_SUCCESS) && (bytesAvailable == 0)) {
|
|
INET_ASSERT(viewInfo->BufferInfo != NULL);
|
|
|
|
ICSocket *socket = viewInfo->BufferInfo->Socket;
|
|
if (socket->IsValid()) {
|
|
error = socket->DataAvailable(&bytesAvailable);
|
|
}
|
|
if ((error == ERROR_SUCCESS) && (viewType == ViewTypeFind) && (bytesAvailable != 0)) {
|
|
bytesAvailable = sizeof(GOPHER_FIND_DATA);
|
|
}
|
|
} else {
|
|
error = ERROR_SUCCESS;
|
|
}
|
|
|
|
if (viewInfo != NULL) {
|
|
DereferenceView(viewInfo);
|
|
}
|
|
|
|
*lpdwNumberOfBytesAvailable = bytesAvailable;
|
|
|
|
quit:
|
|
if ((error == ERROR_SUCCESS) && !bytesAvailable) {
|
|
InbGopherLocalEndCacheWrite(lpThreadInfo->hObjectMapped,
|
|
((handleType==TypeFtpFindHandleHtml)
|
|
?"htm":NULL),
|
|
TRUE);
|
|
}
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
DWORD
|
|
wGopherCloseHandle(
|
|
IN HINTERNET hFile
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Causes the VIEW_INFO described by hFile to be removed from the
|
|
SESSION_INFO and freed. If there are no other links to the data buffer
|
|
then it too is deallocated
|
|
Arguments:
|
|
hFile - handle describing the VIEW_INFO to terminate
|
|
Return Value:
|
|
BOOL
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_INVALID_HANDLE
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"wGopherCloseHandle",
|
|
"%#x",
|
|
hFile
|
|
));
|
|
|
|
DWORD error;
|
|
|
|
// atomically find and dereference the VIEW_INFO given the handle
|
|
error = DereferenceViewByHandle(hFile, ViewTypeFile);
|
|
|
|
DEBUG_LEAVE(error);
|
|
|
|
return error;
|
|
}
|
|
|
|
#if defined(GOPHER_ATTRIBUTE_SUPPORT)
|
|
|
|
|
|
DWORD
|
|
wGopherGetAttribute(
|
|
IN LPCSTR lpszLocator,
|
|
IN LPCSTR lpszAttribute,
|
|
OUT LPBYTE lpBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Retrieves the requested attribute
|
|
Arguments:
|
|
lpszLocator - descriptor of item for which attribute information will
|
|
be retrieved
|
|
lpszAttribute - the attribute name, e.g. +VIEWS
|
|
lpBuffer - to receive attributes
|
|
lpdwBufferLength - IN: length of lpBuffer
|
|
OUT: number of bytes returned in lpBuffer
|
|
Return Value:
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_GOPHER_ATTRIBUTE_NOT_FOUND
|
|
ERROR_GOPHER_NOT_GOPHER_PLUS
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"wGopherGetAttribute",
|
|
"%q, %q, %#x, %#x [%d]",
|
|
lpszLocator,
|
|
lpszAttribute,
|
|
lpBuffer,
|
|
lpdwBufferLength
|
|
));
|
|
|
|
DWORD error;
|
|
|
|
// the locator we are requested to get attributes for may have come from a
|
|
// server other than that identified in the locator. We will search any
|
|
// VIEW_INFO buffers we have looking for the locator. In the worst case
|
|
// we won't find it and will have to send a request to the server
|
|
error = SearchSessionsForAttribute((LPSTR)lpszLocator,
|
|
(LPSTR)lpszAttribute,
|
|
lpBuffer,
|
|
lpdwBufferLength
|
|
);
|
|
if (error == ERROR_GOPHER_ATTRIBUTE_NOT_FOUND) {
|
|
char request[MAX_GOPHER_SELECTOR_TEXT + 1];
|
|
DWORD requestLen;
|
|
char hostName[MAX_GOPHER_HOST_NAME + 1];
|
|
DWORD hostNameLen;
|
|
DWORD port;
|
|
LPSTR gopherPlus;
|
|
LPSESSION_INFO sessionInfo;
|
|
DWORD requestType;
|
|
|
|
// its the worst case - we don't (or no longer) have the requested
|
|
// locator/attributes. We must request them again from the server
|
|
|
|
// pull the individual fields out of the locator. Not interested in display
|
|
// string
|
|
requestLen = sizeof(request);
|
|
hostNameLen = sizeof(hostName);
|
|
if (!CrackLocator(lpszLocator,
|
|
&requestType,
|
|
NULL, // DisplayString
|
|
NULL, // DisplayStringLength
|
|
request, // SelectorString
|
|
&requestLen, // SelectorStringLength
|
|
hostName,
|
|
&hostNameLen,
|
|
&port,
|
|
&gopherPlus
|
|
)) {
|
|
error = ERROR_GOPHER_INVALID_LOCATOR;
|
|
goto quit;
|
|
}
|
|
|
|
// if we already have a session to the server identified in the locator
|
|
// then we will check if we already have the information stored in a
|
|
// VIEW_INFO. If not then we must send the request for the attribute info
|
|
sessionInfo = FindOrCreateSession(hostName, port, &error);
|
|
if (sessionInfo != NULL) {
|
|
// BUGBUG - IsGopherPlusSession needs to perform discovery if
|
|
// unknown
|
|
|
|
// if (IsGopherPlusSession(sessionInfo)) {
|
|
if (TRUE) {
|
|
LPSTR attributeRequest;
|
|
|
|
// convert the request to a request for attributes that the
|
|
// gopher server understands
|
|
attributeRequest = MakeAttributeRequest(request,
|
|
(LPSTR)lpszAttribute
|
|
);
|
|
if (attributeRequest != NULL) {
|
|
LPVIEW_INFO viewInfo;
|
|
|
|
error = GetView(sessionInfo,
|
|
ViewTypeFind,
|
|
attributeRequest,
|
|
TRUE, // RequestIsGopherPlus
|
|
0,
|
|
&viewInfo
|
|
);
|
|
|
|
// done with attribute request buffer (created by
|
|
// MakeAttributeRequest)
|
|
DEL(attributeRequest);
|
|
|
|
// copy everything that came back to the caller's buffer
|
|
// if there's enough space
|
|
if (error == ERROR_SUCCESS) {
|
|
DWORD amountToCopy;
|
|
|
|
INET_ASSERT(viewInfo->BufferInfo->Flags & BI_RECEIVE_COMPLETE);
|
|
|
|
AcquireBufferLock(viewInfo->BufferInfo);
|
|
amountToCopy = viewInfo->BufferInfo->BufferLength;
|
|
|
|
// if the buffer contains dot-terminated info, then
|
|
// account for the dot
|
|
if ((viewInfo->BufferInfo->Flags & BI_DOT_AT_END)
|
|
|
|
// this *SHOULD* always be true, but just in case we
|
|
// have an anomalous situation, we don't want to
|
|
// return a negative value (i.e. a large DWORD value)
|
|
&& (amountToCopy > GOPHER_DOT_TERMINATOR_LENGTH)) {
|
|
amountToCopy -= GOPHER_DOT_TERMINATOR_LENGTH;
|
|
}
|
|
if (amountToCopy <= *lpdwBufferLength) {
|
|
LPBYTE attributeBuffer;
|
|
|
|
attributeBuffer = viewInfo->BufferInfo->Buffer;
|
|
|
|
INET_ASSERT(attributeBuffer != NULL);
|
|
|
|
memcpy(lpBuffer, attributeBuffer, amountToCopy);
|
|
}
|
|
|
|
// whether we copied the data or not, indicate to the
|
|
// caller how much data is available
|
|
*lpdwBufferLength = amountToCopy;
|
|
|
|
// we are done with the buffer. Unlock the BUFFER_INFO
|
|
// and the VIEW_INFO. Both will probably be destroyed
|
|
ReleaseBufferLock(viewInfo->BufferInfo);
|
|
DereferenceView(viewInfo);
|
|
}
|
|
} else {
|
|
error = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
} else {
|
|
error = ERROR_GOPHER_NOT_GOPHER_PLUS;
|
|
}
|
|
|
|
// dereference the session, possibly destroying it
|
|
DereferenceSession(sessionInfo);
|
|
}
|
|
}
|
|
|
|
quit:
|
|
DEBUG_LEAVE(error);
|
|
return error;
|
|
}
|
|
|
|
#endif // defined(GOPHER_ATTRIBUTE_SUPPORT)
|
|
|
|
|
|
//DWORD
|
|
//wGopherConnect(
|
|
// IN LPCSTR lpszServerName,
|
|
// IN INTERNET_PORT nServerPort,
|
|
// IN LPCSTR lpszUsername,
|
|
// IN LPCSTR lpszPassword,
|
|
// IN DWORD dwService,
|
|
// IN DWORD dwFlags,
|
|
// OUT LPHINTERNET lpConnectHandle
|
|
// )
|
|
|
|
///*++
|
|
|
|
//Routine Description:
|
|
// Creates a default gopher connection information 'object' from the
|
|
// parameters supplied in InternetConnect()
|
|
//Arguments:
|
|
// lpszServerName - pointer to default gopher server
|
|
// nServerPort - default configured gopher port (0 for use default of 70)
|
|
// lpszUsername - our user's name
|
|
// lpszPassword - and password
|
|
// dwService - INTERNET_SERVICE_GOPHER
|
|
// dwFlags - unused
|
|
// lpConnectHandle - where we return the pointer to the 'object'
|
|
//Return Value:
|
|
|
|
// DWORD
|
|
// Success - ERROR_SUCCESS
|
|
|
|
// Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
//--*/
|
|
//{
|
|
// LPGOPHER_DEFAULT_CONNECT_INFO info;
|
|
// DWORD error;
|
|
|
|
// DEBUG_ENTER((DBG_GOPHER,
|
|
// Dword,
|
|
// "wGopherConnect",
|
|
// "%q, %d, %q, %q, %d, %#x, %#x",
|
|
// lpszServerName,
|
|
// nServerPort,
|
|
// lpszUsername,
|
|
// lpszPassword,
|
|
// dwService,
|
|
// dwFlags,
|
|
// lpConnectHandle
|
|
// ));
|
|
|
|
// UNREFERENCED_PARAMETER(dwService);
|
|
// UNREFERENCED_PARAMETER(dwFlags);
|
|
|
|
// info = NEW(GOPHER_DEFAULT_CONNECT_INFO);
|
|
// if (info != NULL) {
|
|
// error = ERROR_SUCCESS;
|
|
// if (lpszServerName != NULL) {
|
|
// info->HostName = NEW_STRING((LPSTR)lpszServerName);
|
|
// if (info->HostName == NULL) {
|
|
// error = ERROR_NOT_ENOUGH_MEMORY;
|
|
// }
|
|
// }
|
|
// info->Port = nServerPort;
|
|
// if ((lpszUsername != NULL) && (error == ERROR_SUCCESS)) {
|
|
// info->UserName = NEW_STRING((LPSTR)lpszUsername);
|
|
// if (info->UserName == NULL) {
|
|
// error = ERROR_NOT_ENOUGH_MEMORY;
|
|
// }
|
|
// }
|
|
// if ((lpszPassword != NULL) && (error == ERROR_NOT_ENOUGH_MEMORY)) {
|
|
// info->Password = NEW_STRING((LPSTR)lpszPassword);
|
|
// if (info->Password == NULL) {
|
|
// error = ERROR_NOT_ENOUGH_MEMORY;
|
|
// }
|
|
// }
|
|
// } else {
|
|
// error = ERROR_NOT_ENOUGH_MEMORY;
|
|
// }
|
|
// if (error == ERROR_SUCCESS) {
|
|
// *lpConnectHandle = (HINTERNET)info;
|
|
// } else {
|
|
// if (info != NULL) {
|
|
// if (info->HostName != NULL) {
|
|
// DEL_STRING(info->HostName);
|
|
// }
|
|
// if (info->UserName != NULL) {
|
|
// DEL_STRING(info->UserName);
|
|
// }
|
|
// if (info->Password != NULL) {
|
|
// DEL_STRING(info->Password);
|
|
// }
|
|
// DEL(info);
|
|
// }
|
|
// }
|
|
|
|
// DEBUG_LEAVE(error);
|
|
|
|
// return error;
|
|
//}
|
|
|
|
|
|
//DWORD
|
|
//wGopherDisconnect(
|
|
// IN HINTERNET hInternet
|
|
// )
|
|
///*++
|
|
//Routine Description:
|
|
// Undoes the work of wGopherConnect
|
|
//Arguments:
|
|
// hInternet - handle to object created by wGopherConnect. Actually just a
|
|
// pointer to GOPHER_DEFAULT_CONNECT_INFO structure
|
|
|
|
//Return Value:
|
|
|
|
// DWORD
|
|
// Success - ERROR_SUCCESS
|
|
|
|
// Failure - ERROR_INTERNET_INTERNAL_ERROR
|
|
// hInternet does not identify a GOPHER_DEFAULT_CONNECT_INFO
|
|
|
|
//--*/
|
|
//{
|
|
// LPGOPHER_DEFAULT_CONNECT_INFO info;
|
|
// DWORD error;
|
|
|
|
// DEBUG_ENTER((DBG_GOPHER,
|
|
// Dword,
|
|
// "wGopherDisconnect",
|
|
// "%#x",
|
|
// hInternet
|
|
// ));
|
|
|
|
// // BUGBUG - not expecting bogus pointer?!
|
|
// info = (LPGOPHER_DEFAULT_CONNECT_INFO)hInternet;
|
|
// if (info != NULL) {
|
|
// if (info->HostName != NULL) {
|
|
// DEL_STRING(info->HostName);
|
|
// }
|
|
// if (info->UserName != NULL) {
|
|
// DEL_STRING(info->UserName);
|
|
// }
|
|
// if (info->Password != NULL) {
|
|
// DEL_STRING(info->Password);
|
|
// }
|
|
// DEL(info);
|
|
// error = ERROR_SUCCESS;
|
|
// } else {
|
|
// error = ERROR_INTERNET_INTERNAL_ERROR;
|
|
// }
|
|
|
|
// DEBUG_LEAVE(error);
|
|
|
|
// return error;
|
|
//}
|
|
|
|
|
|
// private functions
|
|
|
|
|
|
|
|
PRIVATE
|
|
DWORD
|
|
GetView(
|
|
IN LPSESSION_INFO SessionInfo,
|
|
IN VIEW_TYPE ViewType,
|
|
IN LPSTR Request,
|
|
IN BOOL RequestIsGopherPlus,
|
|
IN DWORD ResponseFlags,
|
|
OUT LPVIEW_INFO* pViewInfo
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Creates a 'view object'. Sends a request to the gopher server and receives
|
|
the response. The response data is the view. Multiple simultaneous
|
|
requests for the same data from the same server are serialized. It is
|
|
possible that when this function terminates, the entire response may not
|
|
have been received, but we have enough to return to the caller. In this
|
|
case, a background thread may be actively receiving the remainder of the
|
|
response
|
|
Arguments:
|
|
SessionInfo - pointer to SESSION_INFO describing the gopher server
|
|
ViewType - type of view being requested, File or Find
|
|
Request - gopher request string
|
|
RequestIsGopherPlus - TRUE if this is a gopher+ request
|
|
ResponseFlags - bit-mask describing expected response buffer
|
|
pViewInfo - returned view info
|
|
Return Value:
|
|
|
|
DWORD
|
|
Success - ERROR_SUCCESS
|
|
|
|
Failure - ERROR_NOT_ENOUGH_MEMORY
|
|
--*/
|
|
{
|
|
DEBUG_ENTER((DBG_GOPHER,
|
|
Dword,
|
|
"GetView",
|
|
"%#x, %s, %q, %B, %#x, %#x",
|
|
SessionInfo,
|
|
(ViewType == ViewTypeFile) ? "File" : "Find",
|
|
Request,
|
|
RequestIsGopherPlus,
|
|
ResponseFlags,
|
|
pViewInfo
|
|
));
|
|
|
|
DWORD error;
|
|
LPVIEW_INFO viewInfo;
|
|
BOOL viewCloned;
|
|
|
|
// find the view info that contains the results of sending Request to
|
|
// the server identified by the session info. If this succeeds then we
|
|
// will have added another reference to the session info
|
|
viewInfo = CreateView(SessionInfo, ViewType, Request, &error, &viewCloned);
|
|
if (viewInfo != NULL) {
|
|
LPBUFFER_INFO bufferInfo;
|
|
|
|
// if this is a gopher+ request then mark it in the VIEW_INFO
|
|
if (RequestIsGopherPlus) {
|
|
viewInfo->Flags |= VI_GOPHER_PLUS;
|
|
}
|
|
|
|
bufferInfo = viewInfo->BufferInfo;
|
|
|
|
INET_ASSERT(bufferInfo != NULL);
|
|
|
|
// set the expected response type (dot-terminated or not.) Only really
|
|
// useful if request is gopher0 file
|
|
bufferInfo->Flags |= ResponseFlags;
|
|
|
|
if (!viewCloned) {
|
|
BOOL receiveComplete;
|
|
|
|
// if this is a directory view then we need to communicate the fact
|
|
// that ReceiveResponse needs to allocate a buffer
|
|
if (ViewType == ViewTypeFind) {
|
|
bufferInfo->Flags |= BI_BUFFER_RESPONSE;
|
|
}
|
|
|
|
// the view was created. We must make the request to the gopher
|
|
// server
|
|
error = GopherTransaction(viewInfo);
|
|
|
|
// if there were multiple simultaneous requests for the same data
|
|
// then we must signal the request event to restart those other
|
|
// threads. If no other threads are waiting then we can dispense
|
|
// with the request event
|
|
|
|
// AcquireViewLock(SessionInfo, ViewType);
|
|
|
|
// if (bufferInfo->RequestWaiters != 0) {
|
|
|
|
// the request has completed, maybe with an error. In both
|
|
// cases, we signal the event in the BUFFER_INFO to allow
|
|
// any concurrent requesters of the same information to
|
|
// continue
|
|
// SetEvent(bufferInfo->RequestEvent);
|
|
// } else {
|
|
// no other concurrent waiters. The system can have an event
|
|
// back
|
|
|
|
// CloseHandle(bufferInfo->RequestEvent);
|
|
// bufferInfo->RequestEvent = NULL;
|
|
// }
|
|
|
|
// ReleaseViewLock(SessionInfo, ViewType);
|
|
} else {
|
|
// we no longer allow file views to be cloned - each request for a
|
|
// file must now make a separate connection to the server
|
|
INET_ASSERT(ViewType != ViewTypeFile);
|
|
|
|
// the view was cloned. If we made the request at the same time
|
|
// another thread was making the same request to the server then
|
|
// wait for that other thread to signal the request event. If the
|
|
// request event handle is NULL then we don't have to wait
|
|
|
|
// if (bufferInfo->RequestEvent != NULL) {
|
|
// WAIT_FOR_SINGLE_OBJECT(bufferInfo->RequestEvent, error);
|
|
// } else {
|
|
// error = WAIT_OBJECT_0;
|
|
// }
|
|
|
|
// if RequestEvent existed when this clone was generated then we
|
|
// decrement the number of waiters, now that we have access to the
|
|
// data.
|
|
// If the number of requesters goes to zero, we grab the view lock
|
|
// for this list. If the number of requesters is still zero then
|
|
// we close the event handle.
|
|
// The event handle was only required for a special purpose - to
|
|
// hold off multiple simultaneous requesters of the same data from
|
|
// the same server. Once the data has been retrieved, there is no
|
|
// need to keep the event
|
|
|
|
// if (bufferInfo->RequestWaiters != 0) {
|
|
// if (InterlockedDecrement(&bufferInfo->RequestWaiters) == 0) {
|
|
|
|
// AcquireViewLock(SessionInfo, ViewType);
|
|
|
|
// if (bufferInfo->RequestWaiters == 0) {
|
|
// CloseHandle(bufferInfo->RequestEvent);
|
|
// bufferInfo->RequestEvent = NULL;
|
|
// }
|
|
|
|
// ReleaseViewLock(SessionInfo, ViewType);
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// if an error occurred then dereferencing the view should destroy it
|
|
if (error != ERROR_SUCCESS) {
|
|
viewInfo = DereferenceView(viewInfo);
|
|
}
|
|
}
|
|
|
|
*pViewInfo = viewInfo;
|
|
DEBUG_LEAVE(error);
|
|
return error;
|
|
} |