|
|
/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
sockets.cxx
Abstract:
Contains functions to interface between gopher APIs and Winsock
Contents: GopherConnect GopherDisconnect GopherSendRequest GopherReceiveResponse
Author:
Richard L Firth (rfirth) 11-Oct-1994
Environment:
Win32(s) user-mode DLL
Revision History:
11-Oct-1994 rfirth Created
--*/
#include <wininetp.h>
#include "gfrapih.h"
//
// manifests
//
#define DEFAULT_RESPONSE_BUFFER_LENGTH (4 K)
//
// functions
//
DWORD GopherConnect( IN LPVIEW_INFO ViewInfo )
/*++
Routine Description:
Makes a connection to a (gopher) server. Sets a receive timeout on the connected socket
Arguments:
ViewInfo - pointer to VIEW_INFO containing pointer to SESSION_INFO which describes server to connect to
Return Value:
DWORD Success - ERROR_SUCCESS
Failure - WSA error
--*/
{ DEBUG_ENTER((DBG_SOCKETS, Dword, "GopherConnect", "%#x", ViewInfo ));
DWORD error; BOOL fSuccess; INTERNET_CONNECT_HANDLE_OBJECT *pConnect; PROXY_STATE *pProxyState = NULL;
INET_ASSERT(ViewInfo->BufferInfo != NULL); INET_ASSERT(ViewInfo->SessionInfo != NULL);
//
// determine sync or async
//
LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo();
if (lpThreadInfo == NULL) {
INET_ASSERT(FALSE);
error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; }
DWORD asyncFlags;
asyncFlags = 0; //asyncFlags = lpThreadInfo->IsAsyncWorkerThread ? SF_NON_BLOCKING : 0;
//
// Set the port we're using on the socket object.
//
ViewInfo->BufferInfo->Socket->SetPort((INTERNET_PORT) ViewInfo->SessionInfo->Port);
//
// Using the object handle, check to see if we have a socks proxy.
// If so, use it to do our connections.
//
INTERNET_HANDLE_OBJECT * pInternet; HINTERNET hConnectMapped;
INET_ASSERT(lpThreadInfo != NULL); INET_ASSERT(lpThreadInfo->hObjectMapped != NULL); INET_ASSERT(ViewInfo->SessionInfo->Host != NULL);
//
// Get the Mapped Connect Handle Object...
//
INET_ASSERT( (ViewInfo->ViewType == ViewTypeFile) || (ViewInfo->ViewType == ViewTypeFind) );
hConnectMapped = ((HANDLE_OBJECT *)lpThreadInfo->hObjectMapped)->GetParent();
INET_ASSERT(hConnectMapped);
//
// Finally get the Internet Object, so we can query proxy information
// out of it.
//
pConnect = (INTERNET_CONNECT_HANDLE_OBJECT *) hConnectMapped;
pInternet = (INTERNET_HANDLE_OBJECT *) ((INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped)->GetParent();
INET_ASSERT(pInternet);
{
AUTO_PROXY_ASYNC_MSG proxyInfoQuery( INTERNET_SCHEME_GOPHER, pConnect->GetURL(), lstrlen(pConnect->GetURL()), ViewInfo->SessionInfo->Host, lstrlen(ViewInfo->SessionInfo->Host), (INTERNET_PORT) ViewInfo->SessionInfo->Port );
AUTO_PROXY_ASYNC_MSG *pProxyInfoQuery;
proxyInfoQuery.SetBlockUntilCompletetion(TRUE);
pProxyInfoQuery = &proxyInfoQuery;
error = pInternet->GetProxyInfo( &pProxyInfoQuery );
if ( error != ERROR_SUCCESS ) { goto quit; }
if (pProxyInfoQuery->IsUseProxy() && pProxyInfoQuery->GetProxyScheme() == INTERNET_SCHEME_SOCKS && pProxyInfoQuery->_lpszProxyHostName ) { //
// If there is Socks enabled, then turned it on.
//
error = ViewInfo->BufferInfo->Socket->EnableSocks( pProxyInfoQuery->_lpszProxyHostName, pProxyInfoQuery->_nProxyHostPort ); }
if ( pProxyInfoQuery && pProxyInfoQuery->IsAlloced() ) { delete pProxyInfoQuery; pProxyInfoQuery = NULL; }
if ( error != ERROR_SUCCESS ) { goto quit; }
}
error = ViewInfo->BufferInfo->Socket->Connect( GetTimeoutValue(INTERNET_OPTION_CONNECT_TIMEOUT), GetTimeoutValue(INTERNET_OPTION_CONNECT_RETRIES), SF_INDICATE | asyncFlags );
if (error == ERROR_SUCCESS) {
DEBUG_PRINT(SOCKETS, INFO, ("GopherConnect(): ConnectSocket() returns socket %#x\n", //ViewInfo->BufferInfo->ConnectedSocket.Socket
ViewInfo->BufferInfo->Socket->GetSocket() ));
//
// we have made a connection with the server. Set the receive timeout.
// If this fails for any reason, ignore it (although the socket is
// probably bad if this is true)
//
ViewInfo->BufferInfo->Socket->SetTimeout( RECEIVE_TIMEOUT, GetTimeoutValue(INTERNET_OPTION_RECEIVE_TIMEOUT) ); } else {
DEBUG_PRINT(SOCKETS, ERROR, ("GopherConnect(): ConnectSocket() returns %d\n", error ));
}
quit:
DEBUG_LEAVE(error);
return error; }
DWORD GopherDisconnect( IN LPVIEW_INFO ViewInfo, IN BOOL AbortConnection )
/*++
Routine Description:
Disconnects from the gopher server if the session is not flagged as persistent. The socket is closed
Arguments:
ViewInfo - pointer to VIEW_INFO containing pointer to SESSION_INFO which describes connection to gopher server
AbortConnection - TRUE if the connection is to be terminated, even if it is a persistent connection
Return Value:
DWORD Success - ERROR_SUCCESS
Failure - WSA error
--*/
{ DEBUG_ENTER((DBG_SOCKETS, Dword, "GopherDisconnect", "%#x, %B", ViewInfo, AbortConnection ));
DWORD error; LPSESSION_INFO sessionInfo;
INET_ASSERT(ViewInfo->SessionInfo != NULL);
sessionInfo = ViewInfo->SessionInfo; if (!(sessionInfo->Flags & SI_PERSISTENT) || AbortConnection) {
LPBUFFER_INFO bufferInfo;
INET_ASSERT(ViewInfo->BufferInfo != NULL);
bufferInfo = ViewInfo->BufferInfo;
DEBUG_PRINT(SOCKETS, INFO, ("GopherDisconnect(): closing socket %#x\n", //bufferInfo->ConnectedSocket.Socket
bufferInfo->Socket->GetSocket() ));
error = bufferInfo->Socket->Disconnect();
if (error != ERROR_SUCCESS) {
DEBUG_PRINT(SOCKETS, ERROR, ("GopherDisconnect(): Disconnect(%#x) returns %d\n", ViewInfo->BufferInfo->Socket->GetSocket(), error ));
} } else { error = ERROR_SUCCESS; }
DEBUG_LEAVE(error);
return error; }
DWORD GopherSendRequest( IN LPVIEW_INFO ViewInfo )
/*++
Routine Description:
Sends a gopher request to the server we are currently connected to. The request is the selector string used to tell the gopher server what to return
Arguments:
ViewInfo - pointer to VIEW_INFO describing request. Contains pointer to CR/LF terminated text string gopher request
Return Value:
DWORD Success - ERROR_SUCCESS
Failure - WSA error
--*/
{ DEBUG_ENTER((DBG_SOCKETS, Dword, "GopherSendRequest", "%#x", ViewInfo ));
DWORD error;
//
// determine sync or async
//
LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo();
if (lpThreadInfo == NULL) {
INET_ASSERT(FALSE);
error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; }
DWORD asyncFlags;
asyncFlags = 0; //asyncFlags = lpThreadInfo->IsAsyncWorkerThread ? SF_NON_BLOCKING : 0;
error = ViewInfo->BufferInfo->Socket->Send(ViewInfo->Request, ViewInfo->RequestLength, SF_INDICATE );
quit:
DEBUG_LEAVE(error);
return error; }
DWORD GopherReceiveResponse( IN OUT LPVIEW_INFO ViewInfo, OUT LPDWORD BytesReceived )
/*++
Routine Description:
This function is called iteratively to receive the gopher response data. The first time this function is called, we determine the type of response - success or failure, gopher0 or gopher+.
The buffer information passed in can refer to a buffer that we maintain internally (for directories), or to a buffer that the caller supplies to the GopherReadFile() function. In the latter case, the buffer address and length may be 0.
This function assumes that directory responses will be received into a buffer which we allocate in this function, and that file requests are received into caller-supplied (user-supplied) buffers
Arguments:
ViewInfo - pointer to VIEW_INFO structure (including BUFFER_INFO) describing the gopher request and the response from the server
BytesReceived - number of bytes received in this response
Return Value:
DWORD Success - ERROR_SUCCESS
Failure - ERROR_INTERNET_EXTENDED_ERROR The server sent us some form of error text. The app should use InternetGetLastResponseInfo() to get the text
--*/
{ DEBUG_ENTER((DBG_SOCKETS, Dword, "GopherReceiveResponse", "%#x, %#x", ViewInfo, BytesReceived ));
LPBUFFER_INFO bufferInfo; DWORD error; int bufferLength; LPBYTE buffer; LPBYTE responseBuffer; int bytesReceived; BOOL discardBuffer; BOOL terminateConnection; BOOL checkResponse;
*BytesReceived = 0;
bytesReceived = 0; terminateConnection = FALSE; checkResponse = FALSE;
//
// variables for SocketReceive()
//
LPVOID psrBuffer; DWORD srLength; DWORD srLeft; DWORD srReceived; BOOL eof;
bufferInfo = ViewInfo->BufferInfo;
INET_ASSERT(bufferInfo != NULL);
//
// determine sync or async
//
LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo();
if (lpThreadInfo == NULL) {
INET_ASSERT(FALSE);
error = ERROR_INTERNET_INTERNAL_ERROR; goto cleanup; }
DWORD asyncFlags;
asyncFlags = 0; //asyncFlags = lpThreadInfo->IsAsyncWorkerThread ? SF_NON_BLOCKING : 0;
//
// if this is the first receive for this buffer then figure out what we
// received. At this point we may have no buffer (GopherOpenFile()) so we
// set up the BUFFER_INFO with the required information for the next
// GopherReadFile()
//
if (bufferInfo->Flags & BI_FIRST_RECEIVE) {
//
// determine what we have received
//
psrBuffer = (LPVOID)bufferInfo->ResponseInfo; srLength = sizeof(bufferInfo->ResponseInfo); srLeft = sizeof(bufferInfo->ResponseInfo); srReceived = 0;
error = bufferInfo->Socket->Receive(&psrBuffer, &srLength, &srLeft, &srReceived, 0, // dwExtraSpace
SF_INDICATE, &eof ); if (error != ERROR_SUCCESS) { goto cleanup; } else if (srReceived == 0) {
INET_ASSERT(eof);
//
// the server has closed the connection already. We either end up
// with a zero-length file, or return ERROR_NO_MORE_FILES from a
// directory listing
//
DEBUG_PRINT(SOCKETS, ERROR, ("SocketsReceive() returns 0 bytes on initial read\n" ));
bufferInfo->Flags |= BI_RECEIVE_COMPLETE; goto cleanup; }
//
// BytesRemaining is the number of data bytes in ResponseInfo
//
bufferInfo->BytesRemaining = srReceived; bufferInfo->DataBytes = (LPBYTE)bufferInfo->ResponseInfo;
if (ViewInfo->Flags & VI_GOPHER_PLUS) {
//
// gopher+ request: expecting gopher+ response: +/-#\r\n
//
if (bufferInfo->ResponseInfo[0] == GOPHER_PLUS_ERROR_INDICATOR) { bufferInfo->Flags |= BI_ERROR_RESPONSE; } else if (bufferInfo->ResponseInfo[0] != GOPHER_PLUS_SUCCESS_INDICATOR) {
//
// buffer does not start with + or -; assume we received a
// gopher0 response, and down-grade the request
//
ViewInfo->Flags &= ~VI_GOPHER_PLUS;
DEBUG_PRINT(SOCKETS, WARNING, ("Gopher+ request resulted in gopher0 response!\n" ));
} } else if ((ViewInfo->ViewType != ViewTypeFile) && (bufferInfo->ResponseInfo[0] == GOPHER_PLUS_ERROR_INDICATOR)) {
//
// if this is a gopher+ error response then we promote the request/
// response to gopher+ and handle the error below. If it actually
// turns out that someone has invented a locator that starts '-'
// then we will again down-grade the request
//
bufferInfo->Flags |= BI_ERROR_RESPONSE; ViewInfo->Flags |= VI_GOPHER_PLUS;
DEBUG_PRINT(SOCKETS, WARNING, ("Gopher0 request resulted in gopher+ response!\n" ));
}
//
// if we still think the request/response is gopher+ then extract the
// response length
//
if (ViewInfo->Flags & VI_GOPHER_PLUS) {
BOOL ok; LPSTR pNumber;
pNumber = &bufferInfo->ResponseInfo[1]; ok = ExtractInt(&pNumber, 0, &bufferInfo->ResponseLength ); if (!ok || (*pNumber != '\r') || (*(pNumber + 1) != '\n')) { ViewInfo->Flags &= ~VI_GOPHER_PLUS;
DEBUG_PRINT(SOCKETS, WARNING, ("Gopher+ request resulted in gopher0 response!\n" ));
//
// probably isn't an error either
//
bufferInfo->Flags &= ~BI_ERROR_RESPONSE; } else {
int numberLength;
//
// this is the number of bytes we copied to the ResponseInfo
// buffer that belong in the response proper
//
numberLength = (int) (pNumber - bufferInfo->ResponseInfo) + 2; bufferInfo->DataBytes = (LPBYTE)&bufferInfo->ResponseInfo[numberLength]; bufferInfo->BytesRemaining -= numberLength; } }
//
// if we are receiving a gopher0 (directory) response then we need to
// check below if we really received an error
//
if (!(ViewInfo->Flags & VI_GOPHER_PLUS)) { checkResponse = TRUE; }
//
// no longer the first time receive
//
bufferInfo->Flags &= ~BI_FIRST_RECEIVE; }
//
// either get the user buffer pointer, or allocate/resize a moveable buffer
// (for directory listings and errors)
//
// N.B. A special case here is an error received in response to a gopher0
// directory request: we don't know at this point if a gopher0 directory
// request has resulted in an error, but we have to allocate a buffer
// anyway, so we will determine error status after we have received the
// data
//
discardBuffer = FALSE; if (!(bufferInfo->Flags & (BI_BUFFER_RESPONSE | BI_ERROR_RESPONSE))) { buffer = bufferInfo->Buffer; bufferLength = bufferInfo->BufferLength;
//
// if we are given a zero length user buffer then quit now; the bytes
// received parameter has already been zeroed
//
if (bufferLength == 0) { goto quit; } } else {
//
// if we know how much data is in the response AND it is less than the
// default length, then allocate a buffer that will exactly fit the
// response. Otherwise allocate a buffer large enough to fit the default
// response length
//
if (bufferInfo->ResponseLength > 0) { bufferLength = min(bufferInfo->ResponseLength, DEFAULT_RESPONSE_BUFFER_LENGTH ); } else {
//
// the response length is -1 or -2, or its a gopher0 response: we
// don't know how large it is
//
bufferLength = DEFAULT_RESPONSE_BUFFER_LENGTH; }
//
// if this is the first time we have called this function, allocate the
// buffer, else grow it to the new size
//
bufferInfo->Buffer = (LPBYTE)ResizeBuffer(bufferInfo->Buffer, bufferInfo->BufferLength + bufferLength, FALSE ); if (bufferInfo->Buffer == NULL) { terminateConnection = TRUE;
INET_ASSERT(FALSE);
goto last_error_exit; }
bufferInfo->Flags |= BI_OWN_BUFFER;
//
// start receiving the next chunk at the end of the previous one
//
buffer = bufferInfo->Buffer + bufferInfo->BufferLength; }
//
// if we haven't already received all the data (in DetermineGopherResponse)
// then receive the next chunk
//
responseBuffer = buffer; bytesReceived = bufferLength;
//
// receive the data. N.B. Don't change this loop without first examining
// the checks below
//
int n;
for (n = 0; bufferLength > 0; ) {
//
// if there is still data to copy in ResponseInfo then copy that
//
if (bufferInfo->BytesRemaining) { n = min(bufferInfo->BytesRemaining, bufferLength); memcpy(buffer, bufferInfo->DataBytes, n); bufferInfo->BytesRemaining -= n; bufferInfo->DataBytes += n; } else {
//
// if there is data left to copy in the look-ahead buffer that we
// allocated in DetermineGopherResponse() then copy that data to
// the response buffer
//
psrBuffer = (LPVOID)buffer; srLength = bufferLength; srLeft = bufferLength; srReceived = 0;
error = bufferInfo->Socket->Receive(&psrBuffer, &srLength, &srLeft, &srReceived, 0, // dwExtraSpace
SF_INDICATE, &eof ); if (error != ERROR_SUCCESS) { discardBuffer = TRUE; terminateConnection = TRUE; goto cleanup; } n = srReceived; if (n == 0) { break; } } bufferLength -= n; buffer += n; }
//
// at this point we have one of the following:
//
// * we reached the end of the response (n == 0)
// * we reached the end of the buffer (n != 0 && bufferLength == 0)
//
//
// get the actual number of bytes received for this iteration
//
bytesReceived -= bufferLength;
//
// if we are receiving a gopher0 directory response then we need to check
// whether we actually received an error
//
if (checkResponse) {
//
// allow for long locators (!)
//
char locator[2 * MAX_GOPHER_LOCATOR_LENGTH + 1]; LPSTR destination; LPSTR source; DWORD destinationLength; DWORD sourceLength;
//
// we have a gopher0 error if the response starts with a '3' but there
// is only one locator, or the response data is unformatted
//
//
// N.B. We are making an assumption here that the locator(s) in the
// response is(are) not larger than our buffer above. This should be a
// safe assumption, but this test could fail if we get a large locator
// but then a large locator will probably break all other gopher
// clients? The reason we copy the locator is so that we get a zero-
// terminated string for IsValidLocator(), and CopyToEol() compresses
// multiple carriage-returns which some servers are uninteligent to
// return
//
source = (LPSTR)responseBuffer; sourceLength = bytesReceived; destination = locator; destinationLength = sizeof(locator); if (CopyToEol(&destination, &destinationLength, &source, &sourceLength)) {
if (IsValidLocator(locator, sizeof(locator))) {
//
// response contains at least one valid locator. If it starts
// with the gopher0 error indicator ('3') and its the only one
// then this is an error response (although it *could* be the
// one and only locator describing the directory, and the admin
// decided to make it an error for some reason. We can't
// differentiate in this case)
//
if (locator[0] == GOPHER_CHAR_ERROR) { destination = locator; destinationLength = sizeof(locator); if (!CopyToEol(&destination, &destinationLength, &source, &sourceLength)) { bufferInfo->Flags |= BI_ERROR_RESPONSE; } else if (!IsValidLocator(locator, sizeof(locator))) { bufferInfo->Flags |= BI_ERROR_RESPONSE; } } } else {
//
// response doesn't contain a valid locator: must be an error
//
bufferInfo->Flags |= BI_ERROR_RESPONSE; } } else {
//
// we are receiving 4K but couldn't find an end-of-line? Must be an
// error
//
bufferInfo->Flags |= BI_ERROR_RESPONSE; } }
//
// if we finished receivig the response then we can set the error code or
// shrink the buffer
//
if ((n == 0) || (bytesReceived == bufferInfo->ResponseLength) || (bufferInfo->Flags & BI_ERROR_RESPONSE)) {
//
// no more data in the response. We have completed the transfer
//
bufferInfo->Flags |= BI_RECEIVE_COMPLETE;
//
// if this response type is terminated with a line containing only a dot
// then remove the dot
//
if (bufferInfo->Flags & BI_DOT_AT_END) {
//
// BUGBUG - this is insufficient - need to check for different line
// termination schemes ('\n', '\r\r\n', '\r\n', etc.)
//
if ((bytesReceived >= 3) && (memcmp(buffer - 3, ".\r\n", 3) == 0)) { bytesReceived -= 3; } bufferInfo->Flags &= ~BI_DOT_AT_END; }
//
// if we received an error response then we must set the last error
// response data
//
if (bufferInfo->Flags & BI_ERROR_RESPONSE) { if (bytesReceived > 0) { InternetSetLastError(0, (LPSTR)responseBuffer, bytesReceived, SLE_ZERO_TERMINATE );
//
// let the app know that it needs to call InternetGetLastResponseInfo()
// to retrieve the error text
//
error = ERROR_INTERNET_EXTENDED_ERROR; } else { error = ERROR_SUCCESS; }
//
// we have copied the data in the error response, no more need for
// the buffer
//
discardBuffer = TRUE;
//
// need to indicate to the caller that they need to get the last
// error response
//
} else { error = ERROR_SUCCESS; } } else {
//
// this chunk received OK, more to go
//
error = ERROR_SUCCESS; }
cleanup:
//
// if we no longer need the buffer then discard it, else if we have completed
// receiving the response, shrink the buffer to free up any unused space
//
if (bufferInfo->Flags & BI_OWN_BUFFER) {
DWORD newBufferLength; BOOL resize;
if (discardBuffer) { newBufferLength = 0; resize = TRUE; } else {
//
// update the amount of data received - i.e. the number of bytes in
// the buffer (excluding header info)
//
bufferInfo->BufferLength += bytesReceived; if (bufferInfo->Flags & BI_RECEIVE_COMPLETE) { newBufferLength = bufferInfo->BufferLength; resize = TRUE;
DEBUG_PRINT(SOCKETS, ERROR, ("received 0 bytes in response\n" ));
} else { resize = FALSE; } }
//
// if resize is TRUE then we are either shrinking the data buffer to
// exclude any unused space, or we are shrinking it to nothing because
// we have no data or it is being discarded
//
if (resize) { bufferInfo->Buffer = (LPBYTE)ResizeBuffer(bufferInfo->Buffer, newBufferLength, FALSE );
if (newBufferLength == 0) {
//
// we no longer own the buffer
//
bufferInfo->Flags &= ~BI_OWN_BUFFER;
//
// DEBUG version: ensure that the buffer has really been freed
//
INET_ASSERT(bufferInfo->Buffer == NULL);
} } }
//
// if this is a gopher+ response and we know the length of the response then
// reduce the outstanding size of the response by the amount we received
//
if (bufferInfo->ResponseLength > 0) { bufferInfo->ResponseLength -= bytesReceived; }
//
// if we completed the response and the connection is not persistent, or an
// error occurred, close the connection
//
if ((bufferInfo->Flags & BI_RECEIVE_COMPLETE) || (error != ERROR_SUCCESS)) {
//
// let the app know we have finished receiving data
//
InternetIndicateStatus(INTERNET_STATUS_RESPONSE_RECEIVED, &bytesReceived, sizeof(bytesReceived) );
GopherDisconnect(ViewInfo, terminateConnection); }
//
// if we didn't receive error text from the server then we return the amount
// of data received. This is only interesting to ReadData()
//
if (!(bufferInfo->Flags & BI_ERROR_RESPONSE)) { *BytesReceived = bytesReceived; }
quit:
DEBUG_LEAVE(error);
return error;
last_error_exit:
error = GetLastError();
INET_ASSERT(error != ERROR_SUCCESS);
goto cleanup; }
|