/*++ 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 #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); }