/*++

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;
}