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

1066 lines
29 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

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

/*++
Copyright (c) 1994 Microsoft Corporation
Module Name:
gfrapiu.cxx
Abstract:
Common sub-API level functions
Contents:
TestLocatorType
GetAttributes
MakeAttributeRequest
ParseGopherUrl
GopherLocatorToUrl
Author:
Richard L Firth (rfirth) 19-Nov-1994
Environment:
Win32 user-level DLL
Revision History:
19-Nov-1994
Created
--*/
#include <wininetp.h>
#include "gfrapih.h"
// because wininet doesnt know IStream
#define NO_SHLWAPI_STREAM
#include <shlwapi.h>
#include <shlwapip.h>
#define INTERNET_DEFAULT_CSO_PORT 105
#define INTERNET_MAX_WELL_KNOWN_PORT 1023
//
// functions
//
BOOL IsInappropriateGopherPort (INTERNET_PORT port)
/*++
Routine Description:
Gopher URLs can encode arbitrary data to arbitrary ports. This characteristic
enables malicious web pages to redirect IE to exploit security holes, for
example, to spoof a mailer daemon inside a firewall. Based on experimentation,
Netscape apparently disables gopher on ports 1 and 7 though 25 odd. That range
covers many of the well-known ports catalogued by IANA but misses many others
like 137 through 139, assigned for netbios over tcp/ip . Since gopher is
becoming increasingly irrelevant, we prefer to be stricter . IE3 now disables
gopher on ports less than 1024, except for 70, the standard gopher port, and
105, typically used for CSO name searches.
Arguments: Port number
Return Value: TRUE for success, FALSE for failure
--*/
{
if (port > INTERNET_MAX_WELL_KNOWN_PORT)
return FALSE;
switch (port) {
case INTERNET_INVALID_PORT_NUMBER:
case INTERNET_DEFAULT_GOPHER_PORT:
case INTERNET_DEFAULT_CSO_PORT:
return FALSE;
default:
return TRUE;
}
}
DWORD
TestLocatorType(
IN LPCSTR Locator,
IN DWORD TypeMask
)
/*++
Routine Description:
Checks that Locator is valid and checks if it is of the specified type.
This function is mainly for use by GfrIsXxxx APIs
Arguments:
Locator - pointer to app-supplied locator string
TypeMask - gopher type mask to check for
Return Value:
DWORD
Success - ERROR_SUCCESS
Locator is good and of the specified type
Failure - ERROR_INVALID_PARAMETER
Locator is bad
ERROR_INVALID_FUNCTION
Locator is good, but not of the specified type
--*/
{
DWORD error;
BOOL success = FALSE;
//
// BUGBUG - 1. Do we really want to test this parameter?
// 2. If so, is the length sufficient?
//
if (IsBadStringPtr(Locator, MAX_GOPHER_LOCATOR_LENGTH)) {
error = ERROR_INVALID_PARAMETER;
} else {
DWORD gopherType;
gopherType = GopherCharToType(*Locator);
if (gopherType == INVALID_GOPHER_TYPE) {
//
// not a recognizable type - Locator is bogus
//
error = ERROR_INVALID_PARAMETER;
} else if (gopherType & TypeMask) {
error = ERROR_SUCCESS;
} else {
//
// slight bogosity - need an error code to differentiate matched
// vs. not-matched: INVALID_FUNCTION will do
//
error = ERROR_INVALID_FUNCTION;
}
}
return error;
}
#if defined(GOPHER_ATTRIBUTE_SUPPORT)
DWORD
GetAttributes(
IN GOPHER_ATTRIBUTE_ENUMERATOR Enumerator,
IN DWORD CategoryId,
IN DWORD AttributeId,
IN LPCSTR AttributeName,
IN LPSTR InBuffer,
IN DWORD InBufferLength,
OUT LPBYTE OutBuffer,
IN DWORD OutBufferLength,
OUT LPDWORD CharactersReturned
)
/*++
Routine Description:
Pulls attributes out of a buffer and puts them in the caller's buffer or
enumerates them (if Enumerator supplied)
Arguments:
Enumerator - address of caller's enumerator function
CategoryId - category of attribute(s)
AttributeId - the attribute to return
AttributeName - name of the attribute if not a known attribute
InBuffer - pointer to buffer containing gopher+ attributes
InBufferLength - length of attribute buffer
OutBuffer - pointer to caller's buffer where attributes returned
OutBufferLength - length of caller's buffer
CharactersReturned - pointer to returned buffer length
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_GOPHER_ATTRIBUTE_NOT_FOUND
We couldn't find the requested attribute/category
ERROR_INSUFFICIENT_BUFFER
The caller's buffer isn't large enough to contain the
attributes. *lpdwCharactersReturned will contain the
required length
ERROR_GOPHER_DATA_ERROR
We couldn't parse the attributes for some reason
--*/
{
DWORD error;
*CharactersReturned = 0;
//
// the buffer starts with the "+INFO:" attribute describing the locator. We
// don't return this as an attribute
//
if (SkipLine(&InBuffer, &InBufferLength)) {
LPSTR endSection;
BOOL done;
BOOL found;
BOOL more;
if (CategoryId != GOPHER_CATEGORY_ID_ALL) {
//
// advance InBuffer to the line that contains the requested
// attribute
//
found = FindAttribute(CategoryId,
AttributeId,
AttributeName,
&InBuffer,
&InBufferLength
);
if (found) {
//
// if the caller requested that we return all attributes in a
// section, then skip the line containing the category name
//
if (AttributeId == GOPHER_ATTRIBUTE_ID_ALL) {
found = SkipLine(&InBuffer, &InBufferLength);
}
if (found) {
DWORD bufferLeft;
//
// get the end of the section or line in endSection
//
endSection = InBuffer;
bufferLeft = InBufferLength;
FindNextAttribute(CategoryId,
AttributeId,
&endSection,
&bufferLeft
);
}
}
error = found ? ERROR_SUCCESS : ERROR_GOPHER_ATTRIBUTE_NOT_FOUND;
} else {
endSection = InBuffer + InBufferLength;
}
more = TRUE;
done = FALSE;
while ((error == ERROR_SUCCESS) && (InBuffer < endSection) && more) {
LPSTR linePtr;
char lineBuffer[256]; // arbitrary
DWORD lineLength;
BOOL ok;
linePtr = lineBuffer;
lineLength = sizeof(lineBuffer);
ok = CopyToEol(&linePtr,
&lineLength,
&InBuffer,
&InBufferLength
);
if (ok) {
if (Enumerator != NULL) {
//
// if the line starts with a '+' then (we assume) we are
// enumerating all attributes, in which case this line
// just serves to identify the next attribute section. We
// don't return any info
//
if (*linePtr == '+') {
char newCategory[32]; // arbitrary
int i;
for (i = 0; i < sizeof(newCategory); ++i) {
char ch;
ch = linePtr[i];
if ((ch == '\r') || (ch == '\n') || (ch == ' ') || (ch == ':')) {
break;
}
newCategory[i] = ch;
}
newCategory[i] = '\0';
MapAttributeToIds((LPCSTR)newCategory,
&CategoryId,
&AttributeId
);
if (CategoryId == GOPHER_CATEGORY_ID_ABSTRACT) {
//
// BUGBUG - the remainder of this line may contain
// a locator identifying the location of
// a file containing the abstract
//
}
} else {
error = EnumerateAttribute(Enumerator,
linePtr,
lineLength,
OutBuffer,
OutBufferLength,
&more
);
done = TRUE;
}
} else {
//
// get the length of the line in lineLength. N.B. We have
// to subtract an extra 1 because CopyToEol adds a '\0'
//
lineLength = sizeof(lineBuffer) - lineLength - 1;
if (OutBufferLength >= lineLength) {
memcpy(OutBuffer, lineBuffer, lineLength);
OutBuffer += lineLength;
OutBufferLength -= lineLength;
done = TRUE;
} else {
error = ERROR_INSUFFICIENT_BUFFER;
}
//
// always update the characters copied/required parameter
//
*CharactersReturned += lineLength;
}
} else {
error = ERROR_GOPHER_DATA_ERROR;
}
}
//
// if nothing was copied or enumerated then the attribute was not found
//
if (!done && (error == ERROR_SUCCESS)) {
error = ERROR_GOPHER_ATTRIBUTE_NOT_FOUND;
}
} else {
error = ERROR_GOPHER_DATA_ERROR;
}
return error;
}
LPSTR
MakeAttributeRequest(
IN LPSTR Selector,
IN LPSTR Attribute
)
/*++
Routine Description:
Converts a gopher+ request into a request for attributes. E.g. turns
"0Foo" into "0Foo\t!+ADMIN"
Arguments:
Selector - pointer to identifier of gopher+ item to get attributes for
Attribute - pointer to name of attribute(s) to retrieve
Return Value:
LPSTR
Success - pointer to allocated memory containing attribute requester
Failure - NULL
--*/
{
INT selectorLength;
INT attributeLength;
LPSTR request;
selectorLength = (Selector != NULL) ? strlen(Selector) : 0;
attributeLength = (Attribute != NULL) ? strlen(Attribute) : 0;
request = NEW_MEMORY(selectorLength
//
// sizeof(GOPHER_PLUS_INFO_REQUEST) includes 2 for
// <CR><LF> and 1 for terminator
//
+ sizeof(GOPHER_PLUS_INFO_REQUEST)
+ attributeLength,
CHAR
);
if (request != NULL) {
if (Selector != NULL) {
memcpy(request, Selector, selectorLength);
}
memcpy(&request[selectorLength],
GOPHER_PLUS_ITEM_INFO,
sizeof(GOPHER_PLUS_ITEM_INFO) - 1
);
selectorLength += sizeof(GOPHER_PLUS_ITEM_INFO) - 1;
if (Attribute != NULL) {
memcpy(&request[selectorLength], Attribute, attributeLength);
selectorLength += attributeLength;
}
memcpy(&request[selectorLength],
GOPHER_REQUEST_TERMINATOR,
sizeof(GOPHER_REQUEST_TERMINATOR)
);
}
return request;
}
#endif // defined(GOPHER_ATTRIBUTE_SUPPORT)
DWORD
ParseGopherUrl(
IN OUT LPHINTERNET hInternet,
IN LPSTR Url,
IN DWORD SchemeLength,
IN LPSTR Headers,
IN DWORD HeadersLength,
IN DWORD OpenFlags,
IN DWORD_PTR Context
)
/*++
Routine Description:
URL parser for gopher URLs. Support function for InternetOpenUrl() and
ParseUrl().
This is a macro function that just cracks the URL and calls gopher APIs to
do the work
Arguments:
hInternet - IN: Internet gateway handle
OUT: if successful handle of opened item, else undefined
Url - pointer to string containing gopher URL to open
SchemeLength - length of the URL scheme, exluding "://"
Headers - unused for gopher
HeadersLength - unused for gopher
OpenFlags - optional flags for opening a file (cache/no-cache, etc.)
Context - app-supplied context value for call-backs
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INTERNET_INVALID_URL
The URL passed in could not be parsed
--*/
{
DEBUG_ENTER((DBG_GOPHER,
Dword,
"ParseGopherUrl",
"%#x [%#x], %q, %d, %#x, %d, %#x, %#x",
hInternet,
*hInternet,
Url,
SchemeLength,
Headers,
HeadersLength,
OpenFlags,
Context
));
DWORD error;
DWORD gopherType;
LPSTR selector;
LPSTR searchString;
HINTERNET hMapped = NULL;
UNREFERENCED_PARAMETER(Headers);
UNREFERENCED_PARAMETER(HeadersLength);
//
// extract the address information - no user name or password
//
DWORD urlLength;
LPSTR pHostName;
DWORD hostNameLength;
INTERNET_PORT port;
LPSTR lpszUrl = Url;
Url += SchemeLength + sizeof("://") - 1;
error = GetUrlAddress(&Url,
&urlLength,
NULL,
NULL,
NULL,
NULL,
&pHostName,
&hostNameLength,
&port,
NULL
);
if (error != ERROR_SUCCESS) {
goto quit;
}
if (IsInappropriateGopherPort(port)) {
error = ERROR_INTERNET_INVALID_URL;
goto quit;
}
//
// a '/' between address and url-path is not significant to gopher
//
if (*Url == '/') {
++Url;
--urlLength;
//
// the fact that we can ignore the '/' between address and url-path
// means that it is okay to write a '\0' at the end of the host name
//
pHostName[hostNameLength] = '\0';
}
//
// if the URL just consisted of gopher://host[:port] then by default we are
// referencing the root gopher directory
//
if (*Url != '\0') {
//
// Before decoding, convert '?' to tab and thereafter any '+' to ' '
//
LPSTR lpszScan = strchr (Url, '?');
if (lpszScan)
{
*lpszScan++ = '\t';
while (*lpszScan)
{
INET_ASSERT (*lpszScan != '?'); // should be encoded
if (*lpszScan == '+')
*lpszScan = ' ';
lpszScan++;
}
}
//
// we need to convert the url-path before checking it for the search
// string and gopher+ fields because we need to search for '\t' which
// is currently encoded
//
if(FAILED(UrlUnescapeInPlace(Url, 0))){
goto quit;
}
urlLength = lstrlen(Url);
//
// find the type of the gopher resource; if unknown, treat as a file
//
gopherType = GopherCharToType(Url[0]);
selector = &Url[1];
//
// urlLength is now the length of the converted selector
//
--urlLength;
searchString = (LPSTR)memchr((LPVOID)selector, '\t', urlLength);
if (searchString != NULL) {
LPSTR plusString;
//
// zero-terminate the search string, then check if for a gopher+
// component
//
*searchString++ = '\0';
plusString = (LPSTR)memchr((LPVOID)searchString,
'\t',
urlLength - (DWORD) (searchString - selector)
);
if (plusString != NULL) {
*plusString++ = '\0';
gopherType |= GOPHER_TYPE_GOPHER_PLUS;
//
// if the URL defines a file then we may have a view type
//
//
// BUGBUG - need to handle:
//
// - alternate file views
// - attribute requests (?)
// - ASK forms
//
}
}
} else {
gopherType = GOPHER_TYPE_DIRECTORY;
selector = NULL;
searchString = NULL;
}
HINTERNET hConnect;
//
// initialize in case of error
//
hConnect = NULL;
//
// get the offline state
//
BOOL bOffline;
DWORD dwFlags;
bOffline = IsOffline();
if (bOffline || (OpenFlags & INTERNET_FLAG_OFFLINE)) {
dwFlags = INTERNET_FLAG_OFFLINE;
} else {
dwFlags = 0;
}
//
// try to create a locator from the various parts
//
char locator[MAX_GOPHER_LOCATOR_LENGTH + 1];
DWORD locatorLength;
locatorLength = sizeof(locator);
if (GopherCreateLocator(pHostName,
port,
NULL,
selector,
gopherType,
locator,
&locatorLength
)) {
//
// ok, all parts present and correct; open a handle to the gopher
// resource
//
hConnect = InternetConnect(*hInternet,
pHostName,
port,
NULL, // lpszUserName
NULL, // lpszPassword
INTERNET_SERVICE_GOPHER,
dwFlags,
//
// we are creating a "hidden" handle - don't
// tell the app about it
//
INTERNET_NO_CALLBACK
);
try_again:
if (hConnect != NULL) {
HINTERNET handle;
if ( hMapped == NULL )
{
error = MapHandleToAddress(hConnect, (LPVOID *)&hMapped, FALSE);
if ( (error != ERROR_SUCCESS) && (hMapped == NULL) )
{
goto error_quit;
}
error = ERROR_SUCCESS;
}
INET_ASSERT(hMapped != NULL);
((INTERNET_CONNECT_HANDLE_OBJECT *)hMapped)->SetURL(lpszUrl);
if ( IS_GOPHER_DIRECTORY(gopherType)
|| IS_GOPHER_SEARCH_SERVER(gopherType)) {
// set htmlfind only if RAW is not asked
if (!(OpenFlags & INTERNET_FLAG_RAW_DATA)) {
((INTERNET_CONNECT_HANDLE_OBJECT *)hMapped)->SetHtmlFind(TRUE);
//
// BUGBUG: we don't have time to handle CSO searches
//
if (IS_GOPHER_PHONE_SERVER (gopherType))
goto cso_hack;
if ( IS_GOPHER_SEARCH_SERVER(gopherType)
&& (!searchString || !searchString[0])) {
cso_hack:
handle = NULL;
if (ERROR_SUCCESS == RMakeGfrFixedObjectHandle
(hMapped, &handle, gopherType)) {
handle = ((HANDLE_OBJECT *)handle)->GetPseudoHandle();
}
DereferenceObject((LPVOID)hMapped);
goto got_handle;
}
}
handle = GopherFindFirstFile(hConnect,
locator,
searchString,
NULL,
OpenFlags | dwFlags,
Context
);
} else {
handle = GopherOpenFile(hConnect,
locator,
NULL,
OpenFlags | dwFlags,
Context
);
}
got_handle:
if (handle != NULL) {
//
// map the handles
//
HINTERNET hRequestMapped;
error = MapHandleToAddress(handle, (LPVOID *)&hRequestMapped, FALSE);
INET_ASSERT(error == ERROR_SUCCESS);
HINTERNET hConnectMapped;
error = MapHandleToAddress(hConnect, (LPVOID *)&hConnectMapped, FALSE);
INET_ASSERT(error == ERROR_SUCCESS);
//
// link the request and connect handles so that the connect handle
// object will be deleted when the request handle is closed
//
RSetParentHandle(hRequestMapped, hConnectMapped, TRUE);
//
// reduce the reference counts incremented by MapHandleToAddress()
//
if (hRequestMapped != NULL) {
DereferenceObject((LPVOID)hRequestMapped);
}
if (hConnectMapped != NULL) {
DereferenceObject((LPVOID)hConnectMapped);
}
//
// return the request handle to the caller
//
*hInternet = handle;
error = ERROR_SUCCESS;
goto quit;
} else if (!bOffline && IsOffline() && !(dwFlags & INTERNET_FLAG_OFFLINE)) {
//
// we went offline during the request. Try again, this time
// from cache
//
dwFlags = INTERNET_FLAG_OFFLINE;
goto try_again;
}
}
}
error_quit:
if ( hMapped != NULL )
{
DereferenceObject((LPVOID)hMapped);
hMapped = NULL;
}
error = GetLastError();
if (hConnect != NULL) {
//
// BUGBUG - this should close the item handle also (if open)
//
_InternetCloseHandle(hConnect);
}
INET_ASSERT(error != ERROR_SUCCESS);
quit:
DEBUG_LEAVE(error);
return error;
}
DWORD
GopherLocatorToUrl(
IN LPSTR Locator,
OUT LPSTR Buffer,
IN DWORD BufferLength,
OUT LPDWORD UrlLength
)
/*++
Routine Description:
Converts a gopher locator to a gopher URL. E.g. converts:
1foo\tFoo Directory\tfoo.host\t77\t+
to the URL:
gopher://foo.host:77/1Foo%20Directory%09%09%2B
Arguments:
Locator - pointer to gopher locator to convert
Buffer - pointer to buffer where URL is written
BufferLength - size of Buffer in bytes
UrlLength - number of bytes written to Buffer
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INTERNET_INTERNAL_ERROR
We blew an internal buffer limit
ERROR_INSUFFICIENT_BUFFER
Buffer is not large enough to hold the converted URL
--*/
{
DWORD gopherType;
char selector[MAX_GOPHER_SELECTOR_TEXT + 1];
DWORD selectorLength;
char hostName[MAX_GOPHER_HOST_NAME + 1];
DWORD hostNameLength;
DWORD gopherPort;
LPSTR gopherPlus;
char urlBuf[INTERNET_MAX_URL_LENGTH];
DWORD urlBufferLength;
LPSTR urlBuffer;
DWORD error;
DWORD bufLen;
urlBufferLength = sizeof(urlBuf);
urlBuffer = urlBuf;
bufLen = BufferLength;
//
// start with the gopher protocol specifier
//
if (bufLen > sizeof("gopher://")) {
memcpy(Buffer, "gopher://", sizeof("gopher://") - 1);
Buffer += sizeof("gopher://") - 1;
bufLen -= sizeof("gopher://") - 1;
} else {
return ERROR_INSUFFICIENT_BUFFER;
}
//
// use CrackLocator() to get the individual parts of the locator
//
selectorLength = sizeof(selector);
hostNameLength = sizeof(hostName);
if (!CrackLocator(Locator,
&gopherType,
NULL, // DisplayString - we don't care about this in the URL
NULL, // DisplayStringLength
selector,
&selectorLength,
hostName,
&hostNameLength,
&gopherPort,
&gopherPlus
)) {
//
// most likely we bust a limit!
//
return ERROR_INTERNET_INTERNAL_ERROR;
}
//
// add in the host name
//
if (bufLen > hostNameLength) {
memcpy(Buffer, hostName, hostNameLength);
Buffer += hostNameLength;
bufLen -= hostNameLength;
} else {
return ERROR_INSUFFICIENT_BUFFER;
}
//
// add the port, but only if it is not the default (70)
//
if (gopherPort != INTERNET_DEFAULT_GOPHER_PORT) {
if (bufLen > 1 + INTERNET_MAX_PORT_NUMBER_LENGTH) {
int n;
n = wsprintf(Buffer, ":%u", gopherPort);
Buffer += n;
bufLen -= (DWORD)n;
} else {
return ERROR_INSUFFICIENT_BUFFER;
}
}
//
// add the URL-path separator and the locator type character
//
if (bufLen > 2) {
*Buffer++ = '/';
*Buffer++ = *Locator;
bufLen -= 2;
}
//
// copy the selector string, and any gopher+ addenda to a separater buffer
//
if (urlBufferLength > selectorLength) {
memcpy(urlBuffer, selector, selectorLength);
urlBuffer += selectorLength;
urlBufferLength -= selectorLength;
}
//
// if the locator specifies a gopher+ item then add the gopher+ indicator
//
if (gopherPlus != NULL) {
if (urlBufferLength > 3) {
memcpy(urlBuffer, "\t\t+", 3);
urlBufferLength -= 3;
urlBuffer += 3;
}
}
//
// finally terminate the URL
//
if (urlBufferLength >= 1) {
*urlBuffer++ = '\0';
--urlBufferLength;
} else {
return ERROR_INSUFFICIENT_BUFFER;
}
//
// now escape any special characters (e.g. space, tab, etc.) in the url-path
//
*UrlLength = bufLen;
error = EncodeUrlPath(NO_ENCODE_PATH_SEP,
SCHEME_GOPHER,
urlBuf,
sizeof(urlBuf) - urlBufferLength - 1,
Buffer,
UrlLength
);
if (error == ERROR_SUCCESS) {
*UrlLength += BufferLength - bufLen;
}
return error;
}