/*
* Copyright (c) 1998-2004 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/


/*
**-------------------------------------------------------------------------
**  Component Generic Include
**-------------------------------------------------------------------------
*/
#include <apgcli.h>
#include <apmstd.h>
#include <f32file.h>

#include "MVCShell.h"
#include "nw_object_exceptions.h"
#include "nwx_mem.h"
#include "nwx_string.h"
#include "nwx_url_utils.h"
#include "nw_nvpair.h"
#include "nw_wae_reader.h"
#include "nw_string_string.h"
#include "nwx_http_defs.h"
#include "nwx_settings.h"
#include "nwx_statuscodeconvert.h"

/*
**-------------------------------------------------------------------------
**  Module Specific Includes
**-------------------------------------------------------------------------
*/
#include "nw_loadreq.h"
#include "urlloader_urlloaderint.h"
#include "BrsrStatusCodes.h"
#include "nwx_http_defs.h"

/*
**-------------------------------------------------------------------------
**  Constants
**-------------------------------------------------------------------------
*/

//TODO STREAMING: Need to track down an API for getting these.  Initial inquiry
//didn't find one and found other apps are hard-coding these as well.  These,
//however, will not be needed when we switch to the streaming implementation.
static const NW_Ucs2 KCDrivePrefix[] = {'c', ':', '\\', '\0'};
static const NW_Ucs2 KEDrivePrefix[] = {'e', ':', '\\', '\0'};

// The boundary string generated will be done using random numbers and
// converting them to ASCII hex strings. The length should be fairly long so as
// to reduce the probability of the boundary matching any data being sent. As
// of this writing, Microsoft I.E. uses a length of 41, of which 14 chars are
// random ASCII hex chars and the rest are all dashes. In our case, all 41
// chars will be random providing a very low probability of a match. RFC2046
// states that the max length is 70.
static const int KBoundaryLen = 41;

static const NW_Uint8 KDefaultContentType[] =
{'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'o', 'c', 't', 'e', 't', '-', 's', 't', 'r', 'e', 'a', 'm'};

/*
**-------------------------------------------------------------------------
**  Internal Types
**-------------------------------------------------------------------------
*/

/*
**-------------------------------------------------------------------------
**  Macros
**-------------------------------------------------------------------------
*/

#define NW_URLENCODED_TYPE_STR (const char*)"application/x-www-form-urlencoded"
#define NW_URLENCODED_CHARSET_UTF8_TYPE_STR (const char*)"application/x-www-form-urlencoded; charset=utf-8"
#define NW_MULTIPART_TYPE_STR  (const char*)"multipart/form-data"

// The HTTP Post header is created elsewhere.  It inserts enough initial CRLFs
// already in preparation for the body; thus, the first boundary string needs
// one fewer CRLF. Inserting one too many CRLF will cause some sites to not
// work on multipart post (e.g., www.gifworks.com). Therefore, there are two
// boundary start strings, one with CRLF and one without. Note, the CRLF does
// belong in the front of and as part of the boundary string.  See RFC 1521:
// "...initial CRLF is considered to be attached to encpsulation boundary
// rather than part of the preceding part."
#define NW_BOUNDARY_FIRST_START_STR (const char*)"--"
#define NW_BOUNDARY_START_STR (const char*)"\r\n--"
#define NW_BOUNDARY_END_STR (const char*)"--\r\n"
#define NW_CONTENTTYPE_TEXTPLAIN_CHARSET_STR (const char*)"\r\nContent-Type: text/plain; charset="
#define NW_CONTENTTYPE_STR (const char*)"\r\nContent-Type: "
#define NW_CONTENTDISPOSITION_STR (const char*)"\r\nContent-Disposition: form-data; name="
#define NW_FILENAME_STR (const char*)" filename="
#define NW_HEADEREND_STR (const char*)"\r\n\r\n"


/*
**-------------------------------------------------------------------------
**  Internal Prototypes
**-------------------------------------------------------------------------
*/

/*
**-------------------------------------------------------------------------
**  File Scoped Static Variables
**-------------------------------------------------------------------------
*/

/*
**-------------------------------------------------------------------------
**  Global Variable Definitions
**-------------------------------------------------------------------------
*/

/*
**-------------------------------------------------------------------------
**  Internal Prototypes
**-------------------------------------------------------------------------
*/
TBrowserStatusCode Transcode( NW_Byte *p, const NW_Ucs2 *s, 
    NW_Http_CharSet_t http_charset, NW_Uint32 *byteLen );
NW_Uint32 TranscodedLength( const NW_Ucs2 *s,
    NW_Http_CharSet_t http_charset);
NW_Byte* TranscodeTo10646_ucs2( const NW_Ucs2 *aSrc, NW_Byte *aDst );

static TBrowserStatusCode getEscapedPostdataL(NW_NVPair_t *postfields,
                                      NW_Ucs2 ** escapedPostdata,
                                      NW_HED_AppServices_t* appServices,
                                      void * appCtx);
/*
**-------------------------------------------------------------------------
**  Internal Functions
**-------------------------------------------------------------------------
*/

//-------------------------------------------------------------------------
// getConvertedStringL():
//    aString:          A NW_Ucs2* that needs url-escaping and/or foreign charset conversion.
//    aConvertedString: Gets filled in with a (NW_Ucs2*) pointing to the result of those operations.
//                      *aConvertedString is NULL-terminated.
//    aConvertedLen:    Set to the length of aConvertedString, not including the NULL-terminator
//
static TBrowserStatusCode getConvertedStringL(const NW_Ucs2* aString,
                                               NW_HED_AppServices_t* appServices,
                                               void * appCtx,
                                               NW_Ucs2** convertedString,
                                               NW_Uint32* convertedLen)
{
NW_Ucs2*  escapedString = NULL;
NW_Uint32 escapedLen = 0;
NW_Uint32 originalStringLen = NW_Str_Strlen(aString);
NW_Uint32 copyPos = 0;
NW_Uint32 convertPos = 0;
CBufFlat* buffer = CBufFlat::NewL(20);  // arbitrary granularity.

*convertedString = NULL;
*convertedLen = 0;

NW_TRY( status )
    {
    escapedLen = (3 * originalStringLen + 1);
    escapedString = NW_Str_New(escapedLen);
    NW_THROW_OOM_ON_NULL(escapedString, status);
    
    (void)NW_Mem_memset(escapedString, 0, escapedLen * sizeof(NW_Ucs2));
    
    // protect the URL from thinking field-data is a delimiter character.
    NW_Url_EscapeString(aString, escapedString);
    
    /* convert the value to foreign charset if necessary */
    if (appServices && appCtx)
        {
        NW_String_t text;
        NW_Uint32 origEscapedLen = NW_Str_Strlen(escapedString);
        
        NW_Uint32 i = 0;
        while (i < origEscapedLen)
            {
            // Each interation copies 1 ascii sub-string and/or 
            // converts 1 non-ascii sub string.
            
            // Copy ascii chars
            copyPos = i;
            while (i < origEscapedLen && escapedString[i] <= 127)
                {
                i++;
                }

            if (i != copyPos)
                {
                buffer->InsertL(buffer->Size(), escapedString + copyPos, (i - copyPos) * sizeof(NW_Ucs2));
                }
            
            convertPos = i;
            while (i < origEscapedLen && escapedString[i] > 127)
                {
                // gather non-ascii chars for conversion.
                i++;
                }
            
            if (i != convertPos)
                {
                // Need to convert foreign char string.
                NW_Uint32 foreignLen = i - convertPos ;
                NW_Ucs2*  foreignString = NW_Str_New( foreignLen);
                NW_Ucs2*  transmitString = NULL;

                NW_Mem_memcpy( foreignString, escapedString + convertPos, foreignLen * sizeof(NW_Ucs2));
                foreignString[ foreignLen ] = 0;
                
                // convert the ucs2 into byte* from charset conversion process*/
                status = StatusCodeConvert(NW_String_ucs2CharToString (&text, foreignString, HTTP_iso_10646_ucs_2));
                NW_Mem_Free(foreignString);
                NW_THROW_ON_ERROR(status);
                
                // convert to the foreign charset */
                status = appServices->characterConversionAPI.convertToForeignCharSet(appCtx, &text);
                NW_THROW_ON_ERROR(status);

                // TODO: Apply Utf8 encoding here if appropriate.

                // Apply %HH encoding to the foreign charset string.
                status = NW_Url_HHEscape((const char*)text.storage, &transmitString);
                NW_String_deleteStorage(&text);
                NW_THROW_ON_ERROR(status);

                buffer->InsertL(buffer->Size(), transmitString, NW_Str_Strsize(transmitString) - sizeof(NW_Ucs2));
                NW_Mem_Free(transmitString);
                }
            }
        
        buffer->Compress();
        *convertedLen = buffer->Size();
        *convertedString = NW_Str_New( *convertedLen );
        NW_THROW_OOM_ON_NULL( *convertedString, status );
        
        NW_Mem_memcpy( *convertedString, buffer->Ptr(0).Ptr(), *convertedLen );        
        }
    else 
        {
        NW_Uint32 charCount = NW_Str_Strlen(escapedString);
        *convertedLen =  charCount * sizeof(NW_Ucs2);
        *convertedString = NW_Str_New(charCount);
        NW_THROW_OOM_ON_NULL( *convertedString, status );        
        NW_Str_Strcpy( *convertedString, escapedString);
        }    
    }

NW_CATCH( status )
    {
    }

NW_FINALLY
    {
    buffer->Reset();
    delete buffer;
    NW_Str_Delete(escapedString);
    return status;
    }

NW_END_TRY
}

//TODO CHARSET: These character-handling functions should be in a common
//character translation module.  Currently, this type of functionality is
//spread throughout the code rather than centralized where it can be more
//easily maintained and controlled.

// -----------------------------------------------------------------------------
// GetMaxTranscodedByteLength
// Gets the max number of bytes required to hold the transcoded version of the
// specified source string. This is not precise. It is an upper bounds. The
// actual transcoded string length may end up being less than this max length.
// @param aSrc Pointer to source string, for which the max transcoded length is
//  determined. The source string must be HTTP_iso_10646_ucs_2.
// @param aHttpCharSet Char set to use for transcoding.
// @return Max number of bytes in the transcoded string.
// -----------------------------------------------------------------------------
//
NW_Uint32 GetMaxTranscodedByteLength( const NW_Ucs2 *aSrc,
    NW_Http_CharSet_t aHttpCharSet )
    {
    NW_Uint32 len = 0;

    // Return 0 length for NULL pointer or empty string.
    if ( ( aSrc == NULL ) || ( aSrc[0] == 0 ) )
        {
        return len;
        }

    // Get the length (in characters) of the UCS2 source string.
    len = NW_Str_Strlen( aSrc );

    // For performance reasons, the exact byte length is returned for a few
    // simple and quick cases. For all the rest, an upper bounds is calculated
    // as being 3 byte-sized chars per every UCS2 char.
    switch ( aHttpCharSet )
        {
        case HTTP_us_ascii:         // 1 byte per char; length already set
        case HTTP_iso_8859_1:
            break;
        case HTTP_iso_10646_ucs_2:  // 2 bytes per char
            len *= sizeof(NW_Ucs2);
            break;
        default:                    // upper bounds is 3 byte-sized chars per char
            len *= 3;
            break;
        }
    return len;
    }

// -----------------------------------------------------------------------------
// TranscodeTo10646_ucs2
// Transcodes from a UCS2 string to a UCS2 byte stream for HTTP posts, which
// results in changing the two-byte chars from little endian to big endian.
// @param aSrc Pointer to source string, which is to be transcoded.
// @param aDst Pointer to destination for the transcoded results.
// @return Pointer to the next available byte within the destination buffer.
// -----------------------------------------------------------------------------
//
NW_Byte* TranscodeTo10646_ucs2( const NW_Ucs2* aSrc, NW_Byte* aDst )
{
    while ( *aSrc )
        {
        aDst += NW_Reader_WriteUcs2CharBuff( *aSrc, aDst);
        aSrc++;
        }
    return aDst;
}

// -----------------------------------------------------------------------------
// Transcode
// Translates string from NW_Ucs2 to specified charset (does not append a
// null-terminator).
// @param aDst Pointer to destination into which transcoded string is returned.
//  The size of the destination buffer should be large enough to contain the
//  transcoded string and as such the buffer needs to be allocated large enough
//  when it is created.
// @param aSrc Pointer to source string, which is to be transcoded into the
//  destination. The source string must be HTTP_iso_10646_ucs_2.
// @param aHttpCharSet Pointer to charset name (e.g. "utf-8").
// @param aBufLen Length of destination buffer (amount of space available for
//  transcoded string).
// @param aByteLen Pointer at which the byte length of the transcoded string is
//  returned.
// @return  KBrsrSuccess: if success.
//          KBrsrOutOfMemory: if out of memory.
//          KBrsrFailure: any other failure.
// -----------------------------------------------------------------------------
//
TBrowserStatusCode Transcode( NW_Byte *aDst, const NW_Ucs2 *aSrc, 
    NW_Http_CharSet_t aHttpCharSet, NW_Uint32 aBufLen, NW_Uint32* aByteLen )
    {
    NW_String_t text;
    NW_Uint32 srcByteLen;

    // Parameter assertion block.
    NW_ASSERT( aDst );
    NW_ASSERT( aByteLen );

    text.storage = NULL;

    // Initialize returned length to zero for now, in case an error occurs.
    *aByteLen = 0;

    NW_TRY( status )
        {

        // If there is no source, just return.
        if ( aSrc == NULL )
            {
            NW_THROW( KBrsrSuccess );
            }

        srcByteLen = NW_Str_Strlen( aSrc ) * sizeof( NW_Ucs2 );

        // Special case UCS2 for performance reasons - if both source and
        // destination are UCS2 then no intermediate buffer needed.
        if ( aHttpCharSet == HTTP_iso_10646_ucs_2 )
            {
            // Verify that there is sufficient space for the text.
            if ( srcByteLen > aBufLen )
                {
                NW_THROW( KBrsrFailure );
                }
            aDst = TranscodeTo10646_ucs2( aSrc, aDst );
            // Return the length of transcoded text in destination.
            *aByteLen = srcByteLen;
            NW_THROW( KBrsrSuccess );
            }

        // Allocate an intermediate buffer. All other char sets go through the 
        // transcoding methods which unfortunately require an intermediate
        // buffer.
        text.storage = (NW_Byte*)NW_Mem_Malloc( srcByteLen );
        NW_THROW_OOM_ON_NULL( text.storage, status );

        // Copy the source into the intermediate buffer.
        NW_Mem_memcpy( text.storage, aSrc, srcByteLen );
        text.length = srcByteLen;

        // Tanscode. Note, this assumes transcoding to original character set.
//TODO CHARSET: Make the API more flexible to take the charset as a parameter.
        status = CShell::ConvertToForeignCharSet( &text );
        _NW_THROW_ON_ERROR( status );
        // Verify that there is sufficient space for the text.
        if ( text.length > aBufLen )
            {
            NW_THROW( KBrsrFailure );
        }
        // Copy the transcoded text into the destination buffer. Note, the
        // transcoded length always includes 2 null bytes.
//TODO CHARSET: Fix ConvertToForeignCharSet and any code that uses it to not
//use 2 extra null bytes. Not as easy as it sounds. That will lead into a
//tangled web.
	    NW_Mem_memcpy( aDst, text.storage, text.length - 2 );
        // Return the length of transcoded text in destination.
        *aByteLen = text.length - 2;
        }
    // status = KBrsrSuccess; // set by NW_CATCH
    NW_CATCH( status )
        {
        }
    NW_FINALLY
        {
        NW_String_deleteStorage( &text );
        return status;
        }
    NW_END_TRY
    }

/************************************************************************
 
  Function: GetHttpCharsetAsAsciiText

  Description:  Returns the string of the character set.

  Parameters:   httpCharset - in: HTTP charset.
    
  Return Value: Pointer to the charset string.
                  
**************************************************************************/
static const char* GetHttpCharsetAsAsciiText(NW_Http_CharSet_t httpCharset)
{
    switch (httpCharset)
    {
        case HTTP_utf_8:
            return NW_Utf8_CharsetStr;
        case HTTP_us_ascii:
            return NW_UsAscii_CharsetStr;
        case HTTP_iso_10646_ucs_2:
            return NW_Iso10646Ucs2_CharsetStr;
        case HTTP_windows_1255:
            return NW_Windows1255_CharsetStr;
        case HTTP_windows_1256:
            return NW_Windows1256_CharsetStr;
        case HTTP_windows_1250:
            return NW_Windows1250_CharsetStr;
        case HTTP_windows_1251:
            return NW_Windows1251_CharsetStr;
        case HTTP_windows_1253:
            return NW_Windows1253_CharsetStr;
        case HTTP_windows_1254:
            return NW_Windows1254_CharsetStr;
        case HTTP_windows_1257:
            return NW_Windows1257_CharsetStr;
        case HTTP_big5:
            return NW_Big5_CharsetStr;
        case HTTP_gb2312:
            return NW_Gb2312_CharsetStr;
        case HTTP_iso_8859_1:
            return NW_Iso88591_CharsetStr;
        case HTTP_iso_8859_2:
            return NW_Iso88592_CharsetStr;
        case HTTP_iso_8859_3:
            return NW_Iso88593_CharsetStr;
        case HTTP_iso_8859_4:
            return NW_Iso88594_CharsetStr;
        case HTTP_iso_8859_5:
            return NW_Iso88595_CharsetStr;
        case HTTP_iso_8859_6:
            return NW_Iso88596_CharsetStr;
        case HTTP_iso_8859_7:
            return NW_Iso88597_CharsetStr;
        case HTTP_iso_8859_8:
            return NW_Iso88598_CharsetStr;
        case HTTP_iso_8859_9:
            return NW_Iso88599_CharsetStr;
        case HTTP_tis_620:  // not flagged because we will not get here unless tis_620 is enabled
            return NW_Tis_620_CharsetStr;
        case HTTP_shift_JIS:
            return NW_ShiftJis_CharsetStr;
        case HTTP_jis_x0201_1997:
            return NW_JisX0201_1997_CharsetStr;
        case HTTP_jis_x0208_1997:
            return NW_JisX0208_1997_CharsetStr;
        case HTTP_euc_jp:
            return NW_EucJp_CharsetStr;
        case HTTP_iso_2022_jp:
            return NW_Iso2022Jp_CharsetStr;
        case HTTP_windows_874:
            return NW_Windows874_CharsetStr;
        case HTTP_Koi8_r:
            return NW_Koi8r_CharsetStr;
        case HTTP_Koi8_u:
            return NW_Koi8u_CharsetStr;
    }
    NW_ASSERT(NW_FALSE);
    return NULL; /* Make compiler happy */
}

// -----------------------------------------------------------------------------
// getPostDataCharset
// Gets the charset to be used for posting data. This is currently the encoding
// of the original document.
// @return Charset to be used or HTTP_iso_8859_1 if no known value.
//  HTTP_iso_8859_1 was chosen as the "default" since it is a very common
//  charset.
// -----------------------------------------------------------------------------
//
static NW_Http_CharSet_t getPostdataCharset()
    {
    NW_Http_CharSet_t charset;

    charset = (NW_Http_CharSet_t)NW_Settings_GetOriginalEncoding();
    return ((charset == HTTP_unknown) || (charset == 0))
        ? (NW_Http_CharSet_t)HTTP_iso_8859_1 : charset;
    }



/************************************************************************
 
  Function: getEscapedPostdata 

  Description:  Give a single url-encoded string containing all
                the postfield data, in the form of

                "name1=value1&name2=value2&...&namen=valuen"

  Parameters:   *postfields        - pointer to the postfield data.
                **escaped_postdata - pointer to the encoded string.
    
  Return Value: KBrsrSuccess
                KBrsrOutOfMemory
                  
**************************************************************************/
//??? RFD: This section needs to be corrected along with the rest of the URL-encoding fixes underway.
static TBrowserStatusCode getEscapedPostdata(NW_NVPair_t *postfields,
                                      NW_Ucs2 ** escapedPostdata,
                                      NW_HED_AppServices_t* appServices,
                                      void * appCtx)
{
  TBrowserStatusCode status = KBrsrFailure;

  TInt err;
  TRAP(err, status = getEscapedPostdataL(postfields, escapedPostdata, appServices, appCtx));

  return status;
}
                                     
static TBrowserStatusCode getEscapedPostdataL(NW_NVPair_t *postfields,
                                      NW_Ucs2 ** escapedPostdata,
                                      NW_HED_AppServices_t* appServices,
                                      void * appCtx)
{

  NW_Ucs2   *name;
  NW_Ucs2   *value;
  NW_Ucs2*  convertedName = NULL;
  NW_Uint32 convertedNameLen = 0;
  NW_Ucs2*  convertedValue = NULL;
  NW_Uint32 convertedValueLen = 0;
  NW_Uint32 parameterLen = 0;
  
  NW_TRY( status )
      {


		NW_ASSERT(escapedPostdata != NULL);

		*escapedPostdata = NULL;

		if ( NW_NVPair_IsEmpty(postfields) )
  	        {
            // No postdata fields.  Return an empty string for
		    // escaped_postdata.
  	        *escapedPostdata = NW_Str_NewcpyConst( "" );
		    return KBrsrSuccess;
		    }

  	    status = KBrsrFailure;
		CBufFlat* buffer = CBufFlat::NewL(20);  // arbitrary granularity.
		NW_Bool   needSep = NW_FALSE;

		(void) NW_NVPair_ResetIter( postfields );
		while ( KBrsrSuccess == (status = NW_NVPair_GetNext( postfields, &name, &value ))) 
          {

          if ( needSep ) 
              {
			  // append "&" delimiter, as Ucs2
              buffer->InsertL(buffer->Size(), WAE_URL_POSTFIELD_SEP, 2);
              parameterLen++;
              }

          // Each name and value pair needs to be escaped and/or converted to foreign charset.

          status = getConvertedStringL(name, appServices, appCtx, &convertedName, &convertedNameLen);
          _NW_THROW_ON_ERROR( status );
          buffer->InsertL(buffer->Size(), convertedName, convertedNameLen);
          NW_Mem_Free(convertedName);
          convertedName = NULL;

          buffer->InsertL(buffer->Size(), "=", 2); // seperator is UCS2
          
          status = getConvertedStringL(value, appServices, appCtx, &convertedValue, &convertedValueLen);
          _NW_THROW_ON_ERROR( status );
          buffer->InsertL(buffer->Size(), convertedValue, convertedValueLen);
          NW_Mem_Free(convertedValue);
          convertedValue = NULL;

          needSep = NW_TRUE;
          }

      parameterLen = buffer->Size();
      *escapedPostdata = (NW_Ucs2*) NW_Mem_Malloc(parameterLen + 2);
      NW_Mem_memcpy( *escapedPostdata, buffer->Ptr(0).Ptr(), parameterLen);
      (void)NW_Mem_memset( ((NW_Byte*)(*escapedPostdata)) + parameterLen, 0, 2 );
      buffer->Reset();
      delete buffer;
            
      }

  NW_CATCH( status )
      {
      NW_Mem_Free(convertedName);
      NW_Mem_Free(convertedValue);

      }

  NW_FINALLY
      {
      return status;      
      }

  NW_END_TRY
}


// -----------------------------------------------------------------------------
// CreateBoundary
// Creates the boundary string that separates each part in the multipart post.
// The boundary string generated is fairly long and has some degree of
// randomness (by using a random number generator). This is to reduce the
// chance that the string generated could actually match any of the data being
// posted. Although highly unlikely, if it does match then the post would fail.
// If the user attempts to re-transmit, a new string will be generated and the
// post is highly likely to succeed.
// @param aBoundary Ptr to address into which the allocated boundary string
//      is stored.  The caller takes ownership of the allocated memory.
// @return KBrsrSuccess or KBrsrOutOfMemory.
// -----------------------------------------------------------------------------
//
static TBrowserStatusCode CreateBoundary( NW_Uint8** aBoundary )
    {
    // Allocate memory for the boundary string.
    NW_Uint8* boundary = (NW_Uint8*)NW_Mem_Malloc( KBoundaryLen + 1 );
    *aBoundary = boundary;
    if ( !boundary )
        {
        return KBrsrOutOfMemory;
        }
    // Fill the boundary string with random numbers, converting the numbers to
    // hex chars and inserting each char into the string.
    NW_Uint8 n;
    int random = 0;
    for ( int i = 0; i < KBoundaryLen; i++ )
        {
        if ( !random )
            {
            random = rand();
            }
        // Extract lower nibble.
        n = (NW_Uint8)(random & 0x0F);
        // Convert to ASCII hex.
        n = (NW_Uint8)((n < 10) ? (n + '0') : ((n - 10) + 'A'));
        // Shift right by a nibble.
        random = (random >> 4) & 0x0FFFFFFF;
        // Insert char.
        *boundary++ = n;
        }
    // Null terminate.
    *boundary = 0;

    return KBrsrSuccess;
    }

// -----------------------------------------------------------------------------
// GetMimeType
// Gets the mime-type string for a given file (E.g., "image/gif", etc.).
// @param aFileName Ptr to name of file for which to get the mime-type.
// @param aMimeTypeString Ptr to desination into which the newly allocated
//      mime-type string is stored.  The caller takes ownership of the allocated
//      memory.
// @return  KBrsrSuccess: if success.
//          KBrsrOutOfMemory: if out of memory.
//          KBrsrFailure: any other failure.
// -----------------------------------------------------------------------------
//
static TBrowserStatusCode GetMimeType(
    const NW_Ucs2* aFileName, TUint8** aMimeTypeString )
    {
    TInt err;
    TUid uid;
    TDataType dataType;
    RApaLsSession apaSession;
    TBool apaSessionConnected = EFalse;

    // Parameter assertion block.
    NW_ASSERT( aFileName != NULL );
    NW_ASSERT( aMimeTypeString != NULL );

    *aMimeTypeString = NULL;

    NW_TRY( status )
        {
        err = apaSession.Connect();
        if ( err )
            {
            NW_THROW( (err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure );
            }
        apaSessionConnected = ETrue;
            
        // Ask the application architecture to find the file type.
        TPtrC fileNamePtr( (const TUint16*)aFileName,
            (TInt)NW_Str_Strlen( aFileName ) );

        err = apaSession.AppForDocument( fileNamePtr, uid, dataType );
        if ( err )
            {
            NW_THROW( (err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure );
            }

        TPtrC8 dataTypePtr = dataType.Des8();
        TInt len = dataTypePtr.Length();

        // If no mime-type yet, then use the default.
        if ( !len )
            {
            len = sizeof( KDefaultContentType );
            dataTypePtr.Set( KDefaultContentType, len );
            }

        // Allocate a buffer and return the mime-type in it.
        len++; // +1 for NULL terminator
        *aMimeTypeString = new TUint8 [len]; 
        if ( *aMimeTypeString == NULL )
            {
            NW_THROW( KBrsrOutOfMemory );
            }
        TPtr8 mimeTypeStringPtr( *aMimeTypeString, len );
        mimeTypeStringPtr.Copy( dataTypePtr );
        mimeTypeStringPtr.ZeroTerminate();
        }
    // status = KBrsrSuccess; // set by NW_CATCH
    NW_CATCH( status )
        {
        }
    NW_FINALLY
        {
        if ( apaSessionConnected )
            {
            apaSession.Close();
            }
        return status;
        }
    NW_END_TRY
    }

// -----------------------------------------------------------------------------
// GetMimeTypeSize
// Gets the number of characters in the mime-type string.
// @param aFileName Ptr to name of file for which to get the mime-type size.
// @param aSize Ptr to destination into which to return the size.
// @return  KBrsrSuccess: if success.
//          KBrsrOutOfMemory: if out of memory.
//          KBrsrFailure: any other failure.
// -----------------------------------------------------------------------------
//
static TBrowserStatusCode GetMimeTypeSize(
    const NW_Ucs2* aFileName, NW_Uint32* aSize )
    {
    TBrowserStatusCode status;
    TUint8* mimeType;

    // Parameter assertion block.
    NW_ASSERT( aFileName != NULL );
    NW_ASSERT( aSize != NULL );

    *aSize = 0; // default
    status = GetMimeType( aFileName, &mimeType );
    if ( status == KBrsrSuccess )
        {
        *aSize = NW_Asc_strlen( (const char*)mimeType );
        delete mimeType;
        }
    return status;
    }

// -----------------------------------------------------------------------------
// GetMultipartEntrySize
// Gets the upper bounds on the size of the multipart entry.  The size returned
//  may not be the exact size but it will be >= exact size.
//
//  Each multipart entry is in one of two formats. One is for name/values and 
//  the other is for files:
//
//  name/value:
//    <cr><lf>--boundary<cr><lf>
//    Content-Type: text/plain; charset=charsetString<cr><lf>
//    Content-Disposition: form-data; name="name"<cr><lf>
//    Content-Length: contentLength<cr><lf><cr><lf>
//    {value here without the curly braces}
//
//  files:
//    <cr><lf>--boundary<cr><lf>
//    Content-Disposition: form-data; name="name"; filename="d:\dir\filename.ext"<cr><lf><cr><lf>
//    {contents of file without the curly braces}    
//
// @param aName
// @param aValue
// @param aHttpCharSet
// @param aBoundary
// @param aFirst
// @param aSize
// @return
// -----------------------------------------------------------------------------
//
static TBrowserStatusCode GetMultipartEntrySize(
    const NW_Ucs2* aName, 
    const NW_Ucs2* aValue, 
    NW_Http_CharSet_t aHttpCharSet,
    NW_Uint8* aBoundary,
    TBool aFirst,
    NW_Uint32* aSize )
    {
    TBrowserStatusCode ret = KBrsrSuccess;
    NW_Uint32 size = 0;

    // Parameter assertion block.
    NW_ASSERT( aName != NULL );
    NW_ASSERT( aValue != NULL );
    NW_ASSERT( aBoundary != NULL );
    NW_ASSERT( aSize != NULL );

    // Note, content length is not included in mime part header.  Some sites
    // do not work properly if included.  Microsoft I.E. does not include one.

    // Get size of the boundary, common to all types of mime parts.
    size = ( aFirst ) ? NW_Asc_strlen( NW_BOUNDARY_FIRST_START_STR ) :
        NW_Asc_strlen( NW_BOUNDARY_START_STR );
    size += NW_Asc_strlen( (const char*)aBoundary );

    // Add in the rest of the size; depends on type of mime part.
    if ( NW_LoadReq_IsUploadFile( aValue ) )
        {
        TInt err = KErrNone;
        RFs rfs;
        TBool rfsConnected = EFalse;
        RFile file;
        TBool fileOpened = EFalse;
        TPtrC fileNamePtr( (TUint16*)aValue );
        TInt fileSize;

        NW_TRY( status )
            {
//TODO STREAMING: This is very in-efficient to connect every time and then to
//reconnect again later to read the file. This will be addressed in next phase
//with streaming implementation.
            err = rfs.Connect();
            if ( err )
                {
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }
            rfsConnected = ETrue;

            err = file.Open( rfs, fileNamePtr, EFileRead | EFileShareExclusive | EFileStream );
            if ( err )
                {
//TODO STREAMING: Also look for missing file error returned. This too will
//change with streaming implementation.
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }
            fileOpened = ETrue;

            err = file.Size( fileSize );
            if ( err )
                {
//TODO STREAMING: What other errors should be checked? This too will
//change with streaming implementation.
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }

            NW_Uint32 mimeTypeSize = 0;
            status = GetMimeTypeSize( aValue, &mimeTypeSize );
            _NW_THROW_ON_ERROR( status );

            NW_Uint32 mimeContentTypeSize = (mimeTypeSize)
                ? mimeTypeSize + NW_Asc_strlen( NW_CONTENTTYPE_STR ) : 0;

            size += NW_Asc_strlen( NW_CONTENTDISPOSITION_STR )
                + NW_Str_Strlen( aName ) + 3 //+3 for quotes around name and a semicolon at end
                + NW_Asc_strlen( NW_FILENAME_STR )
                + NW_Str_Strlen( aValue ) + 2 //+2 for quotes around filename
                + mimeContentTypeSize
                + NW_Asc_strlen( NW_HEADEREND_STR )
                + fileSize;
            }
        // status = KBrsrSuccess; // set by NW_CATCH
        NW_CATCH( status )
            {
            }
        NW_FINALLY
            {
            if ( fileOpened )
                {
                file.Close();
                }
            if ( rfsConnected )
                {
                rfs.Close();
                }
            ret = status;
            }
        NW_END_TRY
        }
    else
        {
        NW_Uint32 transcodedValueLength = GetMaxTranscodedByteLength( aValue, aHttpCharSet );
        size += NW_Asc_strlen( NW_CONTENTTYPE_TEXTPLAIN_CHARSET_STR )
            + NW_Asc_strlen( GetHttpCharsetAsAsciiText( aHttpCharSet ) )
            + NW_Asc_strlen( NW_CONTENTDISPOSITION_STR )
            + NW_Str_Strlen( aName ) + 2/*quotes around name*/
            + NW_Asc_strlen( NW_HEADEREND_STR )
            + transcodedValueLength;
        }

    *aSize = (ret == KBrsrSuccess) ? size : 0;
    return ret;
    }

// -----------------------------------------------------------------------------
// GetMultipartSize
// Gets the upper bounds on the size of the multipart data.  The size returned
//  may not be the exact size but it will be >= exact size.
// @param aPostFields
// @param aHttpCharSet
// @param aBoundary
// @param aSize
// @return
// -----------------------------------------------------------------------------
//
static TBrowserStatusCode GetMultipartSize(
    NW_NVPair_t *aPostFields,
    NW_Http_CharSet_t aHttpCharSet,
    NW_Uint8* aBoundary,
    NW_Uint32* aSize )
    {
    NW_Ucs2 *name;
    NW_Ucs2 *value;
    TBrowserStatusCode status;
    NW_Uint32 size;
    NW_Uint32 partSize;
    TBool first = ETrue;

    // Parameter assertion block.
    NW_ASSERT( aPostFields != NULL );
    NW_ASSERT( aBoundary != NULL );
    NW_ASSERT( aSize != NULL );

    size = 0;
    NW_NVPair_ResetIter( aPostFields );
    while ( (status = NW_NVPair_GetNext( aPostFields, &name, &value )) == KBrsrSuccess )
        {
        status = GetMultipartEntrySize( name, value, aHttpCharSet, aBoundary,
            first, &partSize );
        if ( status != KBrsrSuccess )
            {
            return status;
            }
        size += partSize;
        first = EFalse;
        }

    // Add the size of the ending boundary marker, plus one for null termination
    size += NW_Asc_strlen(NW_BOUNDARY_START_STR) +
    NW_Asc_strlen((const char*)aBoundary) +
    NW_Asc_strlen(NW_BOUNDARY_END_STR) + 1;

    *aSize = size;
    return KBrsrSuccess;
    }

static TBrowserStatusCode StorePart( NW_Uint8* aDataBuffer, NW_Uint32 len,
    NW_Uint8* aBoundary, NW_Ucs2* aName, NW_Ucs2* aValue,
    NW_Http_CharSet_t aHttpCharSet, TBool aFirst,
    NW_Uint8** retPtr )
    {
    TBrowserStatusCode ret = KBrsrSuccess;
    char* curr = (char*)aDataBuffer;
    NW_Ucs2* ptr;
    NW_Uint32 neededLen;
    NW_Uint32 byteLen = 0;

    // Store the boundary, common to all types of mime parts.
    neededLen = NW_Asc_strlen(
        ((aFirst) ? NW_BOUNDARY_FIRST_START_STR : NW_BOUNDARY_START_STR) )
        + NW_Asc_strlen( (char*)aBoundary );
    if ( neededLen >= len )
        {
        return KBrsrFailure;
        }
    NW_Asc_strcpy( curr, 
        ((aFirst) ? NW_BOUNDARY_FIRST_START_STR : NW_BOUNDARY_START_STR) );
    NW_Asc_strcat( curr, (char*)aBoundary );
    curr += neededLen;
    len -= neededLen;

    // Store the rest of the part; depends on type of mime part.
    if ( NW_LoadReq_IsUploadFile( aValue ) )
        {
        TInt err = KErrNone;
        RFs rfs;
        TBool rfsConnected = EFalse;
        RFile file;
        TBool fileOpened = EFalse;
        TPtrC fileNamePtr( (TUint16*)aValue );
        TInt fileSize;
        TUint8* mimeType = NULL;

        NW_TRY( status )
            {
            // Append the content disposition.
            neededLen = NW_Asc_strlen( NW_CONTENTDISPOSITION_STR );
            if ( neededLen >= len )
                {
                return KBrsrFailure;
                }
            NW_Asc_strcpy( curr, NW_CONTENTDISPOSITION_STR );
            curr += neededLen;

            // Append the variable name, within quotes.
            neededLen = NW_Str_Strlen( aName ) + 3; // +3 for two quotes and semicolon
            if ( neededLen >= len )
                {
                return KBrsrFailure;
                }
            *curr++ = '\"';
            ptr = aName;
            while (*ptr != 0)
                {
                *curr++ = (char)*ptr;
                ptr++;
                }
            *curr++ = '\"';
            *curr++ = ';';  // end it with a semicolon
            len -= neededLen;

            // Append the file name tag.
            neededLen = NW_Asc_strlen( NW_FILENAME_STR );
            if ( neededLen >= len )
                {
                return KBrsrFailure;
                }
            NW_Asc_strcpy( curr, NW_FILENAME_STR );
            curr += neededLen;
            len -= neededLen;

            // Append the actual file name, within quotes.
            neededLen = NW_Str_Strlen( aValue ) + 2; // +2 for quotes
            if ( neededLen >= len )
                {
                return KBrsrFailure;
                }
            *curr++ = '\"';
            ptr = aValue;
            while (*ptr != 0)
            {
                *curr++ = (char)*ptr;
                ptr++;
            }
            *curr++ = '\"';
            len -= neededLen;

            // Append the ContentType if the mime-type is known.
            status = GetMimeType( aValue, &mimeType );
            _NW_THROW_ON_ERROR( status );

            if ( mimeType )
                {
                neededLen = NW_Asc_strlen( NW_CONTENTTYPE_STR )
                    + NW_Asc_strlen( (const char*)mimeType );
                if ( neededLen >= len )
                    {
                    return KBrsrFailure;
                    }
                NW_Asc_strcpy( curr, NW_CONTENTTYPE_STR );
                NW_Asc_strcat( curr, (const char*)mimeType );
                curr += neededLen;
                len -= neededLen;
                }

            // Append the header end.
            neededLen = NW_Asc_strlen( NW_HEADEREND_STR );
            if ( neededLen >= len )
                {
                return KBrsrFailure;
                }
            NW_Asc_strcpy( curr, NW_HEADEREND_STR );
            curr += neededLen;
            len -= neededLen;

            // Append the contents of the file.
            err = rfs.Connect();
            if ( err )
                {
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }
            rfsConnected = ETrue;

            err = file.Open( rfs, fileNamePtr, EFileRead | EFileShareExclusive );
            if ( err )
                {
//TODO STREAMING: Also look for missing file error returned. This too will
//change with streaming implementation.
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }
            fileOpened = ETrue;

            err = file.Size( fileSize );
            if ( err )
                {
//STREAMING: What other errors should be checked? This too will
//change with streaming implementation.
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }

            neededLen = (NW_Uint32)fileSize;
            if ( neededLen >= len )
                {
                return KBrsrFailure;
                }

            TPtr8 tmpPtr( (TUint8*)(curr), 0, fileSize );
            err = file.Read( tmpPtr );
            if ( err )
                {
                NW_THROW_STATUS( status, ((err == KErrNoMemory) ? KBrsrOutOfMemory : KBrsrFailure ) );
                }

            curr += neededLen;
            len -= neededLen;
            }
        // status = KBrsrSuccess; // set by NW_CATCH
        NW_CATCH( status )
            {
            }
        NW_FINALLY
            {
            delete mimeType;
            if ( fileOpened )
                {
                file.Close();
                }
            if ( rfsConnected )
                {
                rfs.Close();
                }
            }
        ret = status;
        NW_END_TRY
        }
    else
        {
        char* charSetAsText = (char*)GetHttpCharsetAsAsciiText( aHttpCharSet );

        // Verify the length will not exceed remaining buffer length (not
        // including transcoded data yet).
        neededLen = NW_Asc_strlen( NW_CONTENTTYPE_TEXTPLAIN_CHARSET_STR )
            + NW_Asc_strlen( charSetAsText )
            + NW_Asc_strlen( NW_CONTENTDISPOSITION_STR )
            + NW_Str_Strlen( aName ) + 2 // +2 for quotes
            + NW_Asc_strlen( NW_HEADEREND_STR );
        if ( neededLen >= len )
            {
            return KBrsrFailure;
            }

        // Append content type and disposition text.
        NW_Asc_strcpy( curr, NW_CONTENTTYPE_TEXTPLAIN_CHARSET_STR );
        NW_Asc_strcat( curr, charSetAsText );
        NW_Asc_strcat( curr, NW_CONTENTDISPOSITION_STR );
        curr = curr + NW_Asc_strlen( curr );
        // Append variable name to end of content disposition, within quotes.
        *curr++ = '\"';
        ptr = aName;
        while (*ptr != 0)
        {
            *curr++ = (char)*ptr;
            ptr++;
        }
        *curr++ = '\"';
        // Append the header end.
        NW_Asc_strcpy( curr, NW_HEADEREND_STR );
        curr = curr + NW_Asc_strlen( curr );

        // Adjust available buffer length.
        len -= neededLen;

        // Append the data.
        ret = Transcode( (NW_Uint8*)curr, aValue, aHttpCharSet, len, &byteLen );
        if ( ret != KBrsrSuccess )
            {
            if ( ret != KBrsrOutOfMemory )
                {
                ret = KBrsrFailure;
                }
            }
        else
            {
            curr += byteLen;
            }
        }

    *retPtr = (NW_Uint8*)curr;
    return ret;
    }

/*****************************************************************

  Name: BuildMultipartBody

  Description:
    Allocate and create a multipart/form-data encoded body for the 
    postfields.

  Parameters:
    aBuffer      - out: multipart/form-data encoded postfield body
    aPostFields  - in: postfields to encode.
    aCharset     - in: character set to encode the postfield values.
    aBoundary    - out: boundary value that separates each part; caller
                    must deallocate regardless of status returned!

  Return Value:
     KBrsrSuccess
     KBrsrOutOfMemory
     KBrsrFailure

******************************************************************/
static TBrowserStatusCode BuildMultipartBody(
    NW_Buffer_t** aBuffer,
    NW_NVPair_t* aPostFields,
    const NW_Http_CharSet_t aCharset,
    NW_Uint8** aBoundary)
    {
    NW_Uint32 len;
    NW_Byte* p;
    NW_Byte* newP;
    NW_Ucs2* name;
    NW_Ucs2* value;
    TBrowserStatusCode status;

    NW_ASSERT( aBuffer != NULL );
    NW_ASSERT( aBoundary != NULL );

    *aBoundary = NULL;

    status = CreateBoundary( aBoundary );
    if ( status != KBrsrSuccess )
        {
        return status;
        }

    // Calculate the length of the multipart data. This will be used to
    // allocate a buffer to contain the actual data. Note, the length
    // is an upper bounds and not necessarily the exact length.
    status = GetMultipartSize( aPostFields, aCharset, *aBoundary, &len );
    if ( status != KBrsrSuccess )
        {
        return status;
        }
    NW_ASSERT( len != 0 );

    // Allocate the buffer for the multipart data.
    *aBuffer = NW_Buffer_New( len );
    if ( *aBuffer == NULL)
        {
        return KBrsrOutOfMemory;
        }

    // Insert the multipart content into the buffer. The length was
    // determined above; however, remaining length is tracked to make sure
    // nothing has changed (like the length of the files) since the time it
    // was determined.  Tracking the length also provides a sanity check.
    p = (*aBuffer)->data;
    TBool first = ETrue;
    (void)NW_NVPair_ResetIter( aPostFields );
    while ( (status = NW_NVPair_GetNext( aPostFields, &name, &value )) == KBrsrSuccess )
        {
        status = StorePart( p, len, *aBoundary, name, value, aCharset, first, &newP );
        if ( status != KBrsrSuccess )
            {
            return status;
            }
        len -= (newP - p);
        p = newP;
        first = EFalse;
        }

    // Make sure there is enough space for ending boundary marker and a null.
    NW_Uint32 endingLen = NW_Asc_strlen( NW_BOUNDARY_START_STR )
        + NW_Asc_strlen( (char*)*aBoundary )
        + NW_Asc_strlen( NW_BOUNDARY_END_STR );
    // Add one to make sure there is enough space for the null terminator.
    if ( (endingLen + 1) > len )
        {
        return KBrsrFailure;
        }

    // Add the ending boundary marker and final null terminator.
    NW_Asc_strcpy( (char*)p, NW_BOUNDARY_START_STR );
    NW_Asc_strcat( (char*)p, (char*)*aBoundary );
    NW_Asc_strcat( (char*)p, NW_BOUNDARY_END_STR );

    p += endingLen;

    // Set length in the destination buffer to actual amount of data stored.
    (*aBuffer)->length = p - (*aBuffer)->data;

    return KBrsrSuccess;
    }

/*
**-------------------------------------------------------------------------
**  External Public (Exported) Functions
**-------------------------------------------------------------------------
*/

TBrowserStatusCode NW_LoadReq_Create ( const NW_Ucs2 *url,
                                const NW_Bool postMethodFlag, 
                                const NW_Ucs2 *referer, 
                                const NW_Ucs2 *acceptCharset,
                                NW_NVPair_t *postfields,
                                const NW_Ucs2 *enctype,
                                const NW_Http_CharSet_t docCharset,
                                NW_Http_Header_t **header,
                                NW_Ucs2** resultUrl,
                                NW_Uint8* method,
                                NW_Buffer_t** body,
                                NW_HED_AppServices_t* appServices,
                                void * appCtx)
{
  TBrowserStatusCode status = KBrsrSuccess;
  char*                 content = NULL;
  NW_Http_CharSet_t     httpCharset;
  NW_Ucs2*              escapedPostdata = NULL;
  NW_Ucs2*              convertedPostdata = NULL;
  NW_Uint8*             boundary = NULL;

  NW_REQUIRED_PARAM(acceptCharset);
  NW_REQUIRED_PARAM(docCharset);

  NW_ASSERT(url != NULL);

  /* Start with empty return values. */
  *resultUrl = NULL;
  *method = 0;
  *body = NULL;

  /* validate enctype */
  if ( enctype != NULL ) {
    if ( postMethodFlag ) {
      if ( NW_Str_StricmpConst(enctype, NW_URLENCODED_TYPE_STR)
        && NW_Str_StricmpConst(enctype, NW_MULTIPART_TYPE_STR) )
        return KBrsrWmlbrowserBadContent;
    } else {
      if ( NW_Str_StricmpConst(enctype, NW_URLENCODED_TYPE_STR) )
        return KBrsrWmlbrowserBadContent;
    }
  }

  /* Get charset to use for the postdata. */
  httpCharset = getPostdataCharset();
  if (httpCharset == HTTP_unknown) {
    return KBrsrUnsupportedFormCharset;
  }

  if ( postMethodFlag ) {
    *resultUrl = NW_Str_Newcpy(url);
    if (*resultUrl == NULL) {
      goto OutOfMemory;
    }

    if ( NW_LoadReq_IsMultipart( enctype ) ) {

      /* post method and multipart/form-data body */
      status = BuildMultipartBody(body, postfields, httpCharset, &boundary);
      if (status != KBrsrSuccess) {
        goto OutOfMemory;
      }
      if (*header == NULL) {
        *header = UrlLoader_HeadersNew(NULL, 
            (const unsigned char *)NW_MULTIPART_TYPE_STR, 
            referer, boundary, (*body)->length, NULL);
      }

      if (*header == NULL) {
        goto OutOfMemory;
      }

    } else {    
//??? RFD: This section needs to be corrected along with the rest of the URL-encoding fixes underway.
      /* post method and url-encoded body */
      status = getEscapedPostdata(postfields, &escapedPostdata, appServices, appCtx);
      if (status != KBrsrSuccess) 
        goto OutOfMemory;
      /* first process the post data to corrected formatted and escaped */
      status = NW_Url_ConvertUnicode( escapedPostdata, &convertedPostdata );
      if (status != KBrsrSuccess) 
        goto OutOfMemory;

      /* convert the post data from NW_Ucs2 to ascii */
      content = NW_Str_CvtToAscii(convertedPostdata);
      if (content == NULL) {
        goto OutOfMemory;
      }
  
      /* Create a buffer and insert the content */
      *body = NW_Buffer_New(0);
      if (*body == NULL) {
        goto OutOfMemory;
      }
      NW_Buffer_SetData(*body, content);

      /* Remove the null-terminator. */
      (*body)->length -= 1;
      if (*header == NULL) {
            *header = UrlLoader_HeadersNew(NULL, (const unsigned char *)NW_URLENCODED_TYPE_STR, 
				referer, boundary, (*body)->length, NULL);
      }

      if (*header == NULL) {
        goto OutOfMemory;
      }
    }
  } else {   
    /* GET method */
    /* Don't add Content-Type to GET methods - there is no content */
    /* Create header because the caller expects a header to be created here */
    if (*header == NULL) {
      *header = UrlLoader_HeadersNew(NULL, NULL, referer, NULL, 0, NULL);
    }

    if (*header == NULL) {
      goto OutOfMemory;
    }
    status = NW_LoadReq_BuildQuery(url, postfields, resultUrl, appServices, appCtx);
    if (status != KBrsrSuccess) {
      goto OutOfMemory;
    }
  }

  /* no longer add accept-charsets as headers */
  /* Do the load */
  if ( postMethodFlag ) {
    *method = NW_UINT8_CAST(NW_URL_METHOD_POST);
  } else {
    *method = NW_UINT8_CAST(NW_URL_METHOD_GET);
  }

  /* cleanup and return normally */
  NW_Str_Delete(escapedPostdata);
  NW_Str_Delete(convertedPostdata);
  NW_Mem_Free(boundary);
  return status;

  /* 
   * clean up and return from KBrsrOutOfMemory errors 
   * Note: NW_Http_Header_Free(), NW_Mem_Free(), NW_Buffer_Free(), 
   * and NW_Str_Delete() all handle NULL pointers 
   */
OutOfMemory:

  NW_Mem_Free(boundary);
  NW_Str_Delete(escapedPostdata);
  NW_Str_Delete(convertedPostdata);
  NW_Mem_Free(content);
  UrlLoader_HeadersFree(*header);
  *header = NULL;
  NW_Str_Delete(*resultUrl);
  *resultUrl = NULL;
  NW_Buffer_Free(*body);
  *body = NULL;

  return KBrsrOutOfMemory;
}

TBrowserStatusCode NW_LoadReq_BuildQuery(const NW_Ucs2 *url,
                                  NW_NVPair_t *postfields, 
                                  NW_Ucs2 **resultUrl,
                                  NW_HED_AppServices_t * appServices,
                                  void * appCtx)
{
  NW_Uint32   len;
  NW_Ucs2     *reqUrl = NULL;
  NW_Ucs2     *frag    = NULL;
  NW_Ucs2     *base    = NULL;
  NW_Ucs2     *pQuery  = NULL;
  NW_Ucs2     *escapedPostdata = NULL;
  TBrowserStatusCode status;
  TBrowserStatusCode getQueryStatus;

  NW_ASSERT(url != NULL);
  NW_ASSERT(resultUrl != NULL);

  len = NW_Str_Strlen(url) + 1;
  status = getEscapedPostdata(postfields, &escapedPostdata, appServices, appCtx);
  if (status != KBrsrSuccess)
    return status;
  
  if ((status = NW_Url_GetBase(url, &base)) != KBrsrSuccess)
  {
    NW_Str_Delete(escapedPostdata);
    return status;
  }
  /* At this point, getQueryStatus cannot be KBrsrMalformedUrl.
     If it was the case it would be detected earlier by NW_Url_GetBase()
  */ 
  getQueryStatus = NW_Url_GetQuery(url, &pQuery);

  /* We should free the pQuery - nobody uses it */
  NW_Str_Delete(pQuery);
  pQuery = NULL;

  status = NW_Url_GetFragId(url, &frag, NW_FALSE);
  if (status != KBrsrSuccess && status != KBrsrFailure)
    /* FAILURE means url had no fragment - not a fatal error */
  {
    NW_Str_Delete(escapedPostdata);
    return status;
  }

  len += NW_Str_Strlen(escapedPostdata) + 1;
  if (frag) 
    len += NW_Str_Strlen(frag);
  
  reqUrl = NW_Str_New(len);
  if (reqUrl == NULL)
  {
    NW_Str_Delete(escapedPostdata);
   return KBrsrOutOfMemory;
  }

  NW_Str_Strcpy(reqUrl, base);

  /* Prevent a separator to be added to the end of string.
     Prevent the WAE_URL_QUERY_SEP to be applied when Url
     has the query field: http://www.foo.com/mywml.wml?pf=ONE
  */
  if ( ! NW_NVPair_IsEmpty(postfields) )
  {
    if (getQueryStatus == KBrsrSuccess) {
      NW_Str_StrcatConst(reqUrl, WAE_URL_POSTFIELD_SEP);
    } else {
      NW_Str_StrcatConst(reqUrl, WAE_URL_QUERY_SEP);
    }
  }

  NW_Str_Strcat(reqUrl, escapedPostdata);

  if (frag)
  {
    NW_Str_StrcatConst(reqUrl, WAE_URL_FRAG_SEP);
    NW_Str_Strcat(reqUrl, frag);
    NW_Str_Delete(frag);
  }

  NW_Str_Delete(base);
  NW_Str_Delete(escapedPostdata);

  *resultUrl = reqUrl;
  return KBrsrSuccess;
}

// -----------------------------------------------------------------------------
// NW_LoadReq::IsMultipart
// Checks the encoding type to see if it is for a multipart request.
// (Other items are commented in header.)
// -----------------------------------------------------------------------------
//
NW_Bool NW_LoadReq_IsMultipart( const NW_Ucs2 *aEncodingType )
    {
    NW_Bool ret = NW_FALSE;
    if ( aEncodingType )
        {
        if ( NW_Str_StricmpConst( aEncodingType, NW_MULTIPART_TYPE_STR ) == 0 )
            {
            ret = NW_TRUE;
            }
        }
    return ret;
    }

//TODO STREAMING: Modify the way in which filenames are handled. The current
//implementation is a quick, short-term solution.  This is going to change
//with streaming implementation.
// -----------------------------------------------------------------------------
// NW_LoadReq::IsUploadFile
// Determines whether or not the current part of the multipart post is for a
// file to be uploaded or not.
// (Other items are commented in header.)
// -----------------------------------------------------------------------------
//
NW_Bool NW_LoadReq_IsUploadFile( const NW_Ucs2 *aValue )
    {
    // Checks file name to see if it starts with "C:\" or E:\".
    if ( aValue )
        {
        if ( (NW_Str_Strnicmp( aValue, KCDrivePrefix, NW_Str_Strlen( KCDrivePrefix ) ) == 0)
            || (NW_Str_Strnicmp( aValue, KEDrivePrefix, NW_Str_Strlen( KEDrivePrefix ) ) == 0) )
            {
            return NW_TRUE;
            }
        }
    return NW_FALSE;
    }
