/*++ 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 #include "gfrapih.h" // because wininet doesnt know IStream #define NO_SHLWAPI_STREAM #include #include #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 // 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; }