/*
* Copyright (c) 2005 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:
*
*/
// INCLUDE FILES
#include <e32def.h> // First to avoid NULL redefine warning (no #ifndef NULL).
#include <e32std.h>
#include <string.h>
#include <imcvcodc.h> // Imut.lib, where some decoders reside
#include <EZDecompressor.h> // Ezlib.lib, GZip decoders
#include <Uri8.h>
#include <Uri16.h>
#include <UriCommon.h>
#include <multipartparser.h>
#include "GZipBufMgr.h"
#include "tinternetdate.h"
// CONSTANTS
const char Multipart_Mixed[] = {"multipart/mixed"};
const char Multipart_Related[] = {"multipart/related"};
const char Multipart_Boundary_Text[] = {"boundary="};
const char Multipart_Content_Base_Text[] = {"Content-Base:"};
const char Multipart_Content_Location_Text[] = {"Content-Location:"};
const char Multipart_Content_Type_Text[] = {"Content-Type:"};
const char Multipart_Content_Transfer_Encoding_Text[] = {"Content-Transfer-Encoding:"};
// violate RFC2045; but upon customer request
const char Multipart_Content_Encoding_Text[] = {"Content-Encoding:"};
const char Multipart_Content_ID_Text[] = {"Content-ID:"};
const char Multipart_Hypens_Text[] = {"--"};
const char Multipart_CRLF_Text[] = {"\r\n"};
const char Multipart_LF_Text[] = {"\n"};
const char Multipart_DoubleCRLF_Text[] = {"\r\n\r\n"};
const char Multipart_DoubleLF_Text[] = {"\n\n"};
const char Multipart_Charset_Text[] = {"charset="};
const char Multipart_ContentTypeString_Delimiter_Text[] = {";"};
const char Multipart_ContentTypeString_Quotes_Text[] = {"\""};
const char Multipart_Content_Transfer_Encoding_Base64[] = {"base64"};
const char Multipart_Content_Transfer_Encoding_QuotedPrintable[] = {"quoted-printable"};
const char Multipart_Content_Transfer_Encoding_7bit[] = {"7bit"};
const char Multipart_Content_Transfer_Encoding_8bit[] = {"8bit"};
const char Multipart_Content_Transfer_Encoding_binary[] = {"binary"};
const char Multipart_Content_Encoding_GZip[] = {"gzip"};
const char Multipart_Content_Type_GZip[] = {"application/x-gzip"};
// MACROS
#define MULTIPART_CONTENT_BASE_LENGTH 13
#define MULTIPART_CONTENT_LOCATION_LENGTH 17
#define MULTIPART_CONTENT_TYPE_LENGTH 13
#define MULTIPART_CONTENT_TRANSFER_ENCODING_LENGTH 26
// violates RFC2045; but upon vodafone request
#define MULTIPART_CONTENT_ENCODING_LENGTH 17
#define MULTIPART_CONTENT_LENGTH_LENGTH 15
#define MULTIPART_LAST_MODIFIED_LENGTH 14
#define MULTIPART_CONTENT_ID_LENGTH 11
#define MULTIPART_CONTENT_BASE 1
#define MULTIPART_CONTENT_LOCATION 2
#define MULTIPART_CONTENT_TRANSFER_ENCODING 3
#define MULTIPART_CONTENT_TYPE 4
#define MULTIPART_CONTENT_ID 5
#define MULTIPART_INTERNET_DATE_STRING_LENGTH 29
#define SLASH_CHAR '/'
#define DOT_CHAR '.'
#define AT_CHAR '@'
#define COLON_CHAR ':'
// <scheme>://
#define SCHEME_SEPARATOR_LENGTH 3
// ============================= LOCAL FUNCTIONS ===============================
// ============================ MEMBER FUNCTIONS ===============================
// -------------------------------------------------------------------------
// Parse and put each body part to the body part array
// -------------------------------------------------------------------------
EXPORT_C void MultipartParser::ParseL( const TDesC8& aMultipartBody,
const TDesC8& aContentType,
const TDesC8& aBoundary,
const TDesC16& aUrl,
RPointerArray <CBodyPart>& aBodyPartsArray,
TInt aMaxToParse )
{
// check on required parameters
__ASSERT_ALWAYS( aMultipartBody.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aContentType.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aBoundary.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aUrl.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
const TUint8* multipartBuffer = aMultipartBody.Ptr();
TUint32 multipartLen = aMultipartBody.Length();
__ASSERT_ALWAYS( multipartLen != 0,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
TUint8* bodyPartBuffer = NULL;
TUint32 bodyPartBufferLength = 0;
TUint32 startPosition = 0;
char* singleEolChar = NULL;
char* doubleEolChar = NULL;
CBodyPart* bodyPart = NULL;
// currently only support mixed and related.
char* contentType = (char*)aContentType.Ptr();
if( strncasecmp( contentType, Multipart_Mixed, strlen(Multipart_Mixed) ) != 0 &&
strncasecmp( contentType, Multipart_Related, strlen(Multipart_Related) ) != 0 )
{
User::Leave( KErrNotSupported );
}
// get singleEol and doubleEol
MultipartParser::SetEolCharacters( multipartBuffer,
multipartLen,
aBoundary,
&singleEolChar,
&doubleEolChar );
// get body parts one by one
// null bodyPartBuffer indicates the end of the multipart body
int counter = 0;
do
{
// stop when we get required number of parse done
if( aMaxToParse != -1 && counter >= aMaxToParse )
{
break;
}
// update counter
counter++;
// get the next body part
bodyPartBufferLength = MultipartParser::GetNextBodyPartBuffer( startPosition,
multipartBuffer,
multipartLen,
aBoundary,
singleEolChar,
&bodyPartBuffer );
// break if we are at end
if( bodyPartBuffer == NULL )
{
break;
}
// update start position
startPosition += bodyPartBufferLength;
// create new body part
bodyPart = CBodyPart::NewL();
// parse each body part buffer to fill in body part
MultipartParser::ParseBodyPartL( bodyPartBuffer,
bodyPartBufferLength,
singleEolChar,
doubleEolChar,
aUrl,
bodyPart );
// add the body part to the array
aBodyPartsArray.Append( bodyPart );
}
while( bodyPartBuffer != NULL );
}
// -------------------------------------------------------------------------
// Composes RFC1521 compliant multipart document with given bodyparts
// Actual task of creating the document is delegated to specialized composer
// for each of the subtypes
// -------------------------------------------------------------------------
EXPORT_C HBufC8* MultipartParser::ComposeL( RPointerArray<CBodyPart>& aBodyPartsArray,
const TDesC8& aBoundary,
TMultipartSubtype aSubtype,
TInt aHeaderMask )
{
// check on required parameters
if ( !aBoundary.Ptr() || !aBoundary.Length() )
{
User::Leave( KErrArgument );
}
HBufC8* multipartDoc = NULL;
switch(aSubtype)
{
case EMultipartSubtypeMixed:
{
multipartDoc = ComposeMixedL( aBodyPartsArray,
aBoundary,
aHeaderMask );
}
break;
default:
{
User::Leave( KErrArgument );
}
}
return multipartDoc;
}
// -------------------------------------------------------------------------
// Returns with the next body part buffer from start position (offset)
// -------------------------------------------------------------------------
TUint32
MultipartParser::GetNextBodyPartBuffer( TUint32 aStartPosition,
const TUint8* aMultipartBody,
TUint32 aMultipartLen,
const TDesC8& aBoundary,
char* aSingleEolChar,
TUint8** aBodyPartBuffer )
{
// check on required parameters
__ASSERT_ALWAYS( aMultipartBody != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aBoundary.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aSingleEolChar != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
long startOffset = -1;
long endOffset = -1;
TUint32 length = 0;
TBool badContent = EFalse;
TUint32 offset = 0;
const char* boundaryStr = (const char*) aBoundary.Ptr();
int boundaryLength = aBoundary.Length();
int hypensLength = strlen( Multipart_Hypens_Text );
int singleEolLength = strlen( aSingleEolChar );
TUint32 i = aStartPosition;
// from RFC 1341 7.2
// Overall, the body of a multipart entity may be specified as follows:
// multipart-body := preamble 1*encapsulation close-delimiter epilogue
// encapsulation := delimiter CRLF body-part
// delimiter := CRLF "--" boundary ; taken from Content-Type field.
// when content-type is multipart.
// There must be no space between "--" and boundary.
// close-delimiter := delimiter "--" ; Again, no space before
// "--"
// boundary = 12xy
// body: here comes some text that we ignore
// --12xy
// first body
// --12xy
// second body
// --12xy--
// closing boundary. we ignore this text here
while( i < (aMultipartLen - hypensLength + 1 ) )
{
// get the first two hypens
// using char comparison to compare "--"
// hopefully this is faster
if( (char)aMultipartBody[ i ] == '-' &&
(char)aMultipartBody[ i+1 ] == '-' )
{
char* boundary = (char*)&aMultipartBody[ i + hypensLength ];
// check if the body is long enough first and then check if boundary matches
if( aMultipartLen >= i + hypensLength + boundaryLength )
{
if( strncasecmp( boundary, boundaryStr, boundaryLength ) == 0 )
{
// we've got the boundary
offset = i + hypensLength + boundaryLength;
// Next two chars must be either two hypens (closing boundary - 2 bytes),
// or single Eol characters (new body).
// Eol = CRLF (2 bytes - windows) or LF (1 byte - unix/mac).
char* eolBuf = (char*)&aMultipartBody[ offset ];
// Check if buffer is long enough for hypens [2 bytes], or eol [1 or 2 bytes]
if( aMultipartLen >= offset + hypensLength )
{
if( strncmp( eolBuf, aSingleEolChar, singleEolLength ) == 0||
eolBuf[0] == Multipart_LF_Text[0])
{
// We found Eol, so this is a new multipart body (header and content)
if( startOffset == -1 )
{
// this is the beginning.
startOffset = offset;
// let's looking for the end of this body part which either could
// be closing boundary or an opening boundary for the next body part
// jump over the boundary information
i = startOffset + singleEolLength;
}
else
{
// We found the next boundary marker, so this is the
// beginning of the next body part
endOffset = offset - boundaryLength - hypensLength;
// we've got both start and end offset
break;
}
}
else if( strncmp( eolBuf, Multipart_Hypens_Text, hypensLength ) == 0 )
{
// We found the closing boundary marker
endOffset = offset - boundaryLength - hypensLength;
break;
}
else
{
// it's neither Eol nor two hypens "--"
badContent = ETrue;
break;
}
}
else
{
// the buffer is too short and not closed properly
endOffset = i;
break;
}
}
}
else
{
// the buffer is far too short
endOffset = i;
break;
}
}
i++;
} // end of while loop
// missing closing boundary check
if( endOffset == -1 )
{
// take the end of the body as closing boundary
endOffset = i - 1;
}
if( badContent )
{
endOffset = -1;
startOffset = -1;
}
if( startOffset != -1 && endOffset != -1 )
{
*aBodyPartBuffer = (TUint8*)&aMultipartBody[ startOffset ];
length = endOffset - startOffset;
}
else
{
*aBodyPartBuffer = NULL;
length = 0;
}
return length;
}
// -------------------------------------------------------------------------
// Set End-Of-Line characters. Look at the eol character after the boundary to
// determine if it is a CRLF (2 bytes used by windows), or LF (1 byte used by
// unix/mac).
// NOTE: CR = 0x0D = '\r', LF = 0x0A = '\n'
// Multipart entity is specified as follows:
// --boundary123[eol]
// Content-type: text/html[eol]
// Content-location: http:\\www.nokia.com\index.html[eol]
// [eol]
// <html><body>...nokia rules...</html>[eol]
// --boundary123--[eol]
// -------------------------------------------------------------------------
void
MultipartParser::SetEolCharacters( const TUint8* aMultipartBody,
TUint32 aMultipartLen,
const TDesC8& aBoundary,
char** aSingleEolChar,
char** aDoubleEolChar )
{
// check on required parameters
__ASSERT_ALWAYS( aMultipartBody != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aBoundary.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aSingleEolChar != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aDoubleEolChar != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
TUint32 i = 0;
const char* boundaryStr = (const char*) aBoundary.Ptr();
int boundaryLength = aBoundary.Length();
int hypensLength = strlen( Multipart_Hypens_Text );
int lfLength = strlen( Multipart_LF_Text );
// Set the default eol (CRLF)
*aSingleEolChar = (char *)Multipart_CRLF_Text;
*aDoubleEolChar = (char *)Multipart_DoubleCRLF_Text;
while (i < (aMultipartLen - hypensLength + 1))
{
// Get the first two hypens
char* bodyPart = (char*)&aMultipartBody[ i ];
if (strncmp(bodyPart, Multipart_Hypens_Text, hypensLength) == 0)
{
char* boundary = (char*)&aMultipartBody[ i + hypensLength ];
// Check if the body is long enough first and then check if boundary matches
if (aMultipartLen >= i + hypensLength + boundaryLength )
{
if (strncasecmp(boundary, boundaryStr, boundaryLength) == 0)
{
// We've got the boundary
i = i + hypensLength + boundaryLength;
// Next two chars should be the single Eol characters.
// Eol = CRLF (2 bytes - windows), or LF (1 byte - unix/mac).
char* eolBuf = (char*)&aMultipartBody[ i ];
// Check if buffer is long enough for eol [1 byte LF]
if (aMultipartLen >= i + lfLength)
{
if (strncmp(eolBuf, Multipart_LF_Text, lfLength) == 0)
{
// We found LF Eol (unix/mac)
*aSingleEolChar = (char *)Multipart_LF_Text;
*aDoubleEolChar = (char *)Multipart_DoubleLF_Text;
} // end of if compare eol to LF
} // end of if buffer size ok
// Break in all cases, we will use the default CRLF if we don't
// find eol=LF, or the remaining buffer is too small
break;
} // end of compare/found boundary
}
} // end of looking for first two hypens
++i;
} // end of while
}
// -------------------------------------------------------------------------
// parse body
// The bodyPart parameter can contain the optional headers and response, or
// just the response. In the case of both the (optional) header(s) and the
// response, let's cut off header(s) and return the response body. The
// header is seperated from the response by two End-of-line (Eol) characters,
// i.e. two CRLF's (windows) or two LF's (unix/mac).
// --boundary123 (omitted from bodyPart parameter, starts next line)
// Content-type: text/html[eol]
// Content-location: http:\\www.nokia.com\index.html[eol]
// [eol]
// <html><body>nokia rules</body></html>
//
// In the case of no headers, there may be only one (or more) Eol characters.
// --boundary123 (omitted from bodyPart parameter, starts on next line)
// [eol]
// <html><body>nokia rules</body></html>
// -------------------------------------------------------------------------
void
MultipartParser::ParseBodyPartL( TUint8* aBodyPartBuffer,
TUint32 aBodyPartBufferLength,
char* aSingleEolChar,
char* aDoubleEolChar,
const TDesC16& aResponseUrl,
CBodyPart* aBodyPart )
{
// check on required parameters
__ASSERT_ALWAYS( aBodyPartBuffer != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aSingleEolChar != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aDoubleEolChar != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aBodyPart != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aResponseUrl.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
// headers look something like this
// we need to return "text/html" if the requested header is "Content-type"
// --boundary123
// Content-type: text/html
// Content-location: http:\\www.nokia.com\index.html
//
// <html>
// <body>
// nokia rules.
int contentHeaderValueCharLen = 0;
TPtrC8 contentHeaderValuePtr( NULL, 0 );
int contentHeaderNameLength = 0;
/*lint -e{668} Possibly passing a null pointer to function */
int singleEolLength = strlen( aSingleEolChar );
int doubleEolLength = strlen( aDoubleEolChar );
// start looking for the header name
for( TUint32 i = 0; i < aBodyPartBufferLength ; i++ )
{
int found = 0;
const char* tempBodyPartBuffer = (char*)&aBodyPartBuffer[ i ];
// Did we find the Content Header Value
if (strncasecmp( tempBodyPartBuffer, Multipart_Content_Base_Text, MULTIPART_CONTENT_BASE_LENGTH ) == 0)
{
contentHeaderNameLength = MULTIPART_CONTENT_BASE_LENGTH;
found = MULTIPART_CONTENT_BASE;
}
else if (strncasecmp( tempBodyPartBuffer, Multipart_Content_Location_Text, MULTIPART_CONTENT_LOCATION_LENGTH ) == 0)
{
contentHeaderNameLength = MULTIPART_CONTENT_LOCATION_LENGTH;
found = MULTIPART_CONTENT_LOCATION;
}
else if (strncasecmp( tempBodyPartBuffer, Multipart_Content_Transfer_Encoding_Text, MULTIPART_CONTENT_TRANSFER_ENCODING_LENGTH ) == 0)
{
contentHeaderNameLength = MULTIPART_CONTENT_TRANSFER_ENCODING_LENGTH;
found = MULTIPART_CONTENT_TRANSFER_ENCODING;
}
else if (strncasecmp( tempBodyPartBuffer, Multipart_Content_Encoding_Text, MULTIPART_CONTENT_ENCODING_LENGTH ) == 0)
{
contentHeaderNameLength = MULTIPART_CONTENT_ENCODING_LENGTH;
found = MULTIPART_CONTENT_TRANSFER_ENCODING;
}
else if (strncasecmp( tempBodyPartBuffer, Multipart_Content_Type_Text, MULTIPART_CONTENT_TYPE_LENGTH ) == 0)
{
contentHeaderNameLength = MULTIPART_CONTENT_TYPE_LENGTH;
found = MULTIPART_CONTENT_TYPE;
}
else if (strncasecmp( tempBodyPartBuffer, Multipart_Content_ID_Text, MULTIPART_CONTENT_ID_LENGTH ) == 0)
{
contentHeaderNameLength = MULTIPART_CONTENT_ID_LENGTH;
found = MULTIPART_CONTENT_ID;
}
if (found)
{
// skip spaces
int startPos = i + contentHeaderNameLength;
while ( (char)aBodyPartBuffer[ startPos ] == ' ' )
{
startPos++;
}
// used for finding '<' in Content-ID field
char charFirst = aBodyPartBuffer[ startPos ];
// content headers are closed with End-Of-Line (Eol) character
for( TUint32 j = startPos; j < aBodyPartBufferLength - singleEolLength + 1; j++ )
{
char* tmpContentHeaderValue = (char*)&aBodyPartBuffer[ j ];
if( strncmp( tmpContentHeaderValue, aSingleEolChar, singleEolLength ) == 0
|| tmpContentHeaderValue[0] == Multipart_LF_Text[0])
{
if( found == MULTIPART_CONTENT_ID )
{
if( charFirst == '<' )
{
// length of the value excluding beginging '<' and ending '>'
contentHeaderValueCharLen = j - startPos - 2;
contentHeaderValuePtr.Set( (TUint8*)&aBodyPartBuffer[ startPos+1 ], contentHeaderValueCharLen );
}
}
else
{
// length of the value
contentHeaderValueCharLen = j - startPos;
contentHeaderValuePtr.Set( (TUint8*)&aBodyPartBuffer[ startPos ], contentHeaderValueCharLen );
}
// rewind so the double EOL will be checked against later
i = j - 1;
// break the inner loop
break;
}
} // end of inner for loop
switch( found )
{
case MULTIPART_CONTENT_BASE:
aBodyPart->SetContentBase( contentHeaderValuePtr );
break;
case MULTIPART_CONTENT_LOCATION:
aBodyPart->SetContentLocation( contentHeaderValuePtr );
break;
case MULTIPART_CONTENT_TRANSFER_ENCODING:
aBodyPart->SetContentTransferEncoding( contentHeaderValuePtr );
break;
case MULTIPART_CONTENT_TYPE:
aBodyPart->SetContentType( contentHeaderValuePtr );
break;
case MULTIPART_CONTENT_ID:
aBodyPart->SetContentID( contentHeaderValuePtr );
break;
default:
break;
}
} // end of if (found)
// Did we get to the end of the Content Header. Many of the Content Header Values
// are optional, so we could get to the end of the Content Header (double Eol) and
// not find the Content Header Value we were searching for.
// get the response body
int aEolLength = strlen(Multipart_DoubleLF_Text);
if (strncmp( tempBodyPartBuffer, aDoubleEolChar, doubleEolLength ) == 0 ||
strncmp( tempBodyPartBuffer, Multipart_DoubleLF_Text,aEolLength)== 0)
{
if(strncmp( tempBodyPartBuffer, aDoubleEolChar, doubleEolLength)== 0)
aEolLength = doubleEolLength;
// set body
TUint8* responseBody = (TUint8*) &tempBodyPartBuffer[aEolLength];
int lengthBody = aBodyPartBufferLength - ( i + aEolLength );
TPtrC8 body( responseBody, lengthBody );
aBodyPart->SetBody( body );
// set headers when we have headers
if( i != 0 )
{
// jump over the starting single EOL
TUint8* responseHeaders = (TUint8*) &aBodyPartBuffer[ singleEolLength ];
// // jump over the starting single EOL and the ending double EOL
int lengthHeaders = aBodyPartBufferLength - lengthBody - singleEolLength - aEolLength;
TPtrC8 headers( responseHeaders, lengthHeaders );
aBodyPart->SetHeaders( headers );
}
break;
}
} // end of outter for loop
// prepare more on body part
// Check to see if we have a Content-Transfer-Encoding.
TUint8* contentTransferEncodingValue = (TUint8*) aBodyPart->ContentTransferEncoding().Ptr();
// If we have Content-Transfer-Encoding, prepare to decode
if( MultipartParser::IsEncoded(contentTransferEncodingValue) )
{
// Initialize the encoded body, input
TPtrC8 encodedBody( aBodyPart->Body() );
// This will contain the "decoded" data.
// The memory (decodedBody.Ptr) is owned by this method, but allocated by
// the DecodeContentTransferEncoding method.
TPtr8 decodedBody( NULL, 0, 0 );
// The decoded data will return in decodedBody.Ptr.
// The memory allocated is owned by this method.
TInt err = MultipartParser::DecodeContentTransferEncoding( contentTransferEncodingValue,
encodedBody,
decodedBody );
User::LeaveIfError(err);
// The responseBody pointer is an offset into the response
// buffer, do not delete. Substitute the decodedBody pointer.
aBodyPart->SetBody( decodedBody );
aBodyPart->SetIsDecodedBody( ETrue );
} // end of if (contentTransferEncodingValue)
// Check to see if we have a Content-Type.
TUint8* contentTypeValue = (TUint8*) aBodyPart->ContentType().Ptr();
// parse contentType to get new contentType, charset, boundary info
if( contentTypeValue )
{
MultipartParser::CutOffContentTypeAttributes( aBodyPart );
// updated content type
contentTypeValue = (TUint8*) aBodyPart->ContentType().Ptr();
}
// If we have zipped Content-Type, prepare to unzip
if( MultipartParser::IsZipped(contentTypeValue) )
{
// Initialize the zipped body, input
TPtrC8 zippedBody( aBodyPart->Body() );
// This will contain the "unzipped" data.
// The memory (unzippedBody.Ptr) is owned by this method, but allocated by
// the Unzip method.
TPtr8 unzippedBody( NULL, 0, 0 );
// The unzipped data will return in unzippedBody.Ptr.
// The memory allocated is owned by this method.
TInt err = MultipartParser::Unzip( contentTypeValue,
zippedBody,
unzippedBody );
User::LeaveIfError(err);
if( aBodyPart->IsDecodedBody() )
{
// old body is not the original buffer, delete it
delete (TUint8*) aBodyPart->Body().Ptr();
}
// unzip happend, use unzippedBody; delete decodedBody
else
{
// The responseBody pointer is an offset into the response
// buffer, do not delete. Substitute the decodedBody pointer.
aBodyPart->SetIsDecodedBody( ETrue );
}
aBodyPart->SetBody( unzippedBody );
}
// Get the url of the current body part
HBufC16* responseUrl = MultipartParser::GetBodyPartUrlL( aBodyPart->ContentBase(),
aBodyPart->ContentLocation(),
aResponseUrl );
aBodyPart->SetUrl( responseUrl );
}
// -------------------------------------------------------------------------
// From RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies
// Section 6.2. Content-Transfer-Encodings Semantics
//
// The Content-Transfer-Encoding values "7bit", "8bit", and "binary" all
// mean that the identity (i.e. NO) encoding transformation has been
// performed. As such, they serve simply as indicators of the domain of
// the body data, and provide useful information about the sort of
// encoding that might be needed for transmission in a given transport
// system. The terms "7bit data", "8bit data", and "binary data" are
// all defined in Section 2.
// Returns true if contentTransferEncodingValue is neither NULL nor a domain.
// -------------------------------------------------------------------------
TBool MultipartParser::IsEncoded( TUint8* aContentTransferEncodingValue )
{
if( !aContentTransferEncodingValue )
{
return EFalse;
}
char* encoding = (char*)aContentTransferEncodingValue;
if ( strncasecmp( encoding,
Multipart_Content_Transfer_Encoding_7bit,
strlen(Multipart_Content_Transfer_Encoding_7bit) ) == 0 )
{
return EFalse;
}
if ( strncasecmp( encoding,
Multipart_Content_Transfer_Encoding_8bit,
strlen(Multipart_Content_Transfer_Encoding_8bit) ) == 0 )
{
return EFalse;
}
if ( strncasecmp( encoding,
Multipart_Content_Transfer_Encoding_binary,
strlen(Multipart_Content_Transfer_Encoding_binary) ) == 0 )
{
return EFalse;
}
return ETrue;
}
// ----------------------------------------------------------------------------
// DecodeContentTransferEncoding
//
// Decodes the Content-Transfer-Encoding. The returned length of decodedBody
// is zero if decoding failed.
// NOTES:
// 1. This method should be called with a non-null string, i.e.
// aContentTransferEncodingValue.
// 2. Memory is allocated in this method, but ownership is with the calling method.
// ----------------------------------------------------------------------------
TInt
MultipartParser::DecodeContentTransferEncoding( TUint8* aContentTransferEncodingValue,
const TDesC8& aEncodedBody,
TPtr8& aDecodedBody )
{
// check on required parameters
__ASSERT_ALWAYS( aContentTransferEncodingValue != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aEncodedBody.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
TInt status = KErrNone;
char* contentTransferEncodingString = (char*)aContentTransferEncodingValue;
// Set the decodedBody.Length to zero, length > 0 if successful decode
aDecodedBody.SetLength(0);
// Is the Content-Transfer-Encoding = "base64"
if( strncasecmp( contentTransferEncodingString,
Multipart_Content_Transfer_Encoding_Base64,
strlen(Multipart_Content_Transfer_Encoding_Base64) ) == 0 )
{
// We have base64, create base64 codec
TImCodecB64 base64Codec;
// The decoded length of base64 is about half (use same) encoded length
TUint maxBodyLength = aEncodedBody.Length();
TUint8* decodedDataPtr = new TUint8[maxBodyLength];
if( decodedDataPtr )
{
aDecodedBody.Set(decodedDataPtr, 0, maxBodyLength);
// Decode the base64 Content-Transfer-Encoding
base64Codec.Decode(aEncodedBody, aDecodedBody);
if (aDecodedBody.Length() == 0)
{
status = KErrGeneral;
}
}
else // decodedDataPtr is NULL
{
status = KErrNoMemory;
}
} // end of base64 decoding
// Is the Content-Transfer-Encoding = "quoted-printable"
else if( strncasecmp( contentTransferEncodingString,
Multipart_Content_Transfer_Encoding_QuotedPrintable,
strlen(Multipart_Content_Transfer_Encoding_QuotedPrintable) ) == 0 )
{
// We have quoted-printable, create quoted-printable codec
TImCodecQP qpCodec;
// The decoded length of QP is the same as the encoded length
TUint maxBodyLength = aEncodedBody.Length();
TUint8* decodedDataPtr = new TUint8[maxBodyLength];
if( decodedDataPtr )
{
aDecodedBody.Set(decodedDataPtr, 0, maxBodyLength);
// Decode the quoted-printable Content-Transfer-Encoding
qpCodec.Decode(aEncodedBody, aDecodedBody);
if (aDecodedBody.Length() == 0)
{
status = KErrGeneral;
}
}
else // decodedDataPtr is NULL
{
status = KErrNoMemory;
}
} // end of quoted-printed decoding
// Is the Content-Encoding = "gzip"
else if( strncasecmp( contentTransferEncodingString,
Multipart_Content_Encoding_GZip,
strlen(Multipart_Content_Encoding_GZip) ) == 0 )
{
// Our GZip decoder parts
GZipBufMgr* gZipBufMgr = NULL;
CEZDecompressor* ezDecompressor = NULL;
// We have gzip, lets decompress the encoded data.
// Set up the encoded data into a GZip buffer manager.
TInt err = 0;
TRAP(err, gZipBufMgr = GZipBufMgr::NewL( aEncodedBody ));
// Get the GZip decompressor
if( gZipBufMgr )
{
TRAP(err, ezDecompressor = CEZDecompressor::NewL(*gZipBufMgr, -CEZDecompressor::EMaxWBits));
// Inflate the GZip data
if( ezDecompressor )
{
TRAP(err, ezDecompressor->InflateL());
// Set the finalize flag
if (err == KErrNone)
{
TRAP(err, gZipBufMgr->FinalizeL(*ezDecompressor));
// Get the inflated data, it is much larger then the encoded data
if (err == KErrNone)
{
TPtrC8 output = ezDecompressor->OutputDescriptor();
if (output.Length() != 0)
{
TInt size = output.Size();
TUint8* outBuf = new TUint8[size];
if( outBuf )
{
memcpy(outBuf, output.Ptr(), size);
aDecodedBody.Set((TUint8*)outBuf, size, size);
}
else // outBuf is NULL
{
status = KErrNoMemory;
}
}
else
{
status = KErrGeneral;
}
}
else
{
status = KErrGeneral;
}
}
else
{
status = KErrGeneral;
}
}
else // ezDecompressor is NULL
{
status = KErrNoMemory;
}
}
else // gZipBufMgr is NULL
{
status = KErrNoMemory;
}
// Clean up our memory
delete gZipBufMgr;
delete ezDecompressor;
} // end of gzip
// We can add additional decodings here.
// When adding additional decoding be aware of the decodedBody.Ptr()
// max size. Do the realloc here, AND allow the decodedBody.Ptr()
// ownership to be passed back to calling method.
return status;
}
// -------------------------------------------------------------------------
// only support application/x-gzip
// -------------------------------------------------------------------------
TBool MultipartParser::IsZipped( TUint8* aContentTypeValue )
{
if( !aContentTypeValue )
{
return EFalse;
}
char* contentType = (char*)aContentTypeValue;
if ( strncasecmp( contentType,
Multipart_Content_Type_GZip,
strlen(Multipart_Content_Type_GZip) ) == 0 )
{
return ETrue;
}
return EFalse;
}
// ----------------------------------------------------------------------------
// Unzip
//
// Unzip the .gz. The returned length of unzippedBody
// is zero if unzip failed.
// NOTES:
// 1. This method should be called with a non-null string, i.e.
// aContentType.
// 2. Memory is allocated in this method, but ownership is with the calling method.
// ----------------------------------------------------------------------------
TInt
MultipartParser::Unzip( TUint8* aContentType,
const TDesC8& aZippedBody,
TPtr8& aUnzippedBody )
{
// check on required parameters
__ASSERT_ALWAYS( aContentType != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aZippedBody.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
TInt status = KErrNone;
char* contentTypeStr = (char*)aContentType;
// Set the aUnzippedBody.Length to zero, length > 0 if successful decode
aUnzippedBody.SetLength(0);
// Our GZip decoder parts
GZipBufMgr* gZipBufMgr = NULL;
CEZDecompressor* ezDecompressor = NULL;
// Is the Content-Type = "application/x-gzip"
if( strncasecmp( contentTypeStr,
Multipart_Content_Type_GZip,
strlen(Multipart_Content_Type_GZip) ) == 0 )
{
// We have gzip, lets decompress the encoded data.
// Set up the encoded data into a GZip buffer manager.
TInt err = 0;
TRAP(err, gZipBufMgr = GZipBufMgr::NewL(aZippedBody));
// Get the GZip decompressor
if( gZipBufMgr )
{
TRAP(err, ezDecompressor = CEZDecompressor::NewL(*gZipBufMgr, -CEZDecompressor::EMaxWBits));
// Inflate the GZip data
if( ezDecompressor )
{
TRAP(err, ezDecompressor->InflateL());
// Set the finalize flag
if (err == KErrNone)
{
TRAP(err, gZipBufMgr->FinalizeL(*ezDecompressor));
// Get the inflated data, it is much larger then the encoded data
if (err == KErrNone)
{
TPtrC8 output = ezDecompressor->OutputDescriptor();
if (output.Length() != 0)
{
TInt size = output.Size();
TUint8* outBuf = new TUint8[size];
if( outBuf )
{
memcpy(outBuf, output.Ptr(), size);
aUnzippedBody.Set((TUint8*)outBuf, size, size);
}
else // outBuf is NULL
{
status = KErrNoMemory;
}
}
else
{
status = KErrGeneral;
}
}
else
{
status = KErrGeneral;
}
}
else
{
status = KErrGeneral;
}
}
else // ezDecompressor is NULL
{
status = KErrNoMemory;
}
}
else // gZipBufMgr is NULL
{
status = KErrNoMemory;
}
} // end of gzip
// Clean up our memory
delete gZipBufMgr;
delete ezDecompressor;
return status;
}
// ----------------------------------------------------------------------------
// It cuts off the charset value from the content type header
// content type string looks like as follows:
// text/plain; charset=us-ascii; boundary="abc"
// ----------------------------------------------------------------------------
void
MultipartParser::CutOffContentTypeAttributes( CBodyPart* aBodyPart )
{
// check on required parameters
__ASSERT_ALWAYS( aBodyPart != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
TPtrC8 aContentType( aBodyPart->ContentType() );
// check if there is a delimiter ';'
TInt lenCT = aContentType.Length();
TInt offset = aContentType.Find( (TUint8*)Multipart_ContentTypeString_Delimiter_Text,
strlen(Multipart_ContentTypeString_Delimiter_Text) );
if (offset != KErrNotFound)
{
// ; is meant to be the end of the content type value.
// cut off content type unrelated part
aBodyPart->SetContentType( aContentType.Left( offset ) );
// extract boundary and charset info
if( lenCT > offset )
{
TPtrC8 unrelated = aContentType.Right( lenCT - offset );
TInt lenU = unrelated.Length();
// check the boundary information
TInt offsetB = unrelated.Find( (TUint8*)Multipart_Boundary_Text,
strlen(Multipart_Boundary_Text) );
if (offsetB != KErrNotFound)
{
// now, we are at the beginning of "boundary="abc" string.
// move to the "abc" part
TPtrC8 boundary = unrelated.Right( lenU -
offsetB -
strlen( Multipart_Boundary_Text ) );
TInt lenB = boundary.Length();
// look for where to end
TInt offsetQ = boundary.Find( (TUint8*)Multipart_ContentTypeString_Quotes_Text,
strlen(Multipart_ContentTypeString_Quotes_Text) );
if (offsetQ != KErrNotFound)
{
// skip the quote (") char
boundary.Set( boundary.Right( lenB - offsetQ ) );
}
// look for where to end
// check "
TInt offsetE = boundary.Find( (TUint8*)Multipart_ContentTypeString_Quotes_Text,
strlen(Multipart_ContentTypeString_Quotes_Text) );
if (offsetE == KErrNotFound)
{
// check ;
offsetE = boundary.Find( (TUint8*)Multipart_ContentTypeString_Delimiter_Text,
strlen(Multipart_ContentTypeString_Delimiter_Text) );
}
if (offsetE != KErrNotFound)
{
boundary.Set( boundary.Left( offsetE ) );
}
// set it on to the input parameter
aBodyPart->SetBoundary( boundary );
} // end of if (offsetB != KErrNotFound)
// check the charset information
TInt offsetCh = unrelated.Find( (TUint8*)Multipart_Charset_Text,
strlen(Multipart_Charset_Text) );
if (offsetCh != KErrNotFound)
{
// now, we are at the beginning of "charset=us-ascii" string.
// move to the us-ascii part
TPtrC8 charset = unrelated.Right( lenU -
offsetCh -
strlen( Multipart_Charset_Text ) );
TInt lenCh = charset.Length();
// look for where to end
TInt offsetQ = charset.Find( (TUint8*)Multipart_ContentTypeString_Quotes_Text,
strlen(Multipart_ContentTypeString_Quotes_Text) );
if (offsetQ != KErrNotFound)
{
// skip the quote (") char
charset.Set( charset.Right( lenCh - offsetQ ) );
}
// look for where to end
// check "
TInt offsetE = charset.Find( (TUint8*)Multipart_ContentTypeString_Quotes_Text,
strlen(Multipart_ContentTypeString_Quotes_Text) );
if (offsetE == KErrNotFound)
{
// check ;
offsetE = charset.Find( (TUint8*)Multipart_ContentTypeString_Delimiter_Text,
strlen(Multipart_ContentTypeString_Delimiter_Text) );
}
if (offsetE != KErrNotFound)
{
charset.Set( charset.Left( offsetE ) );
}
// set it on to the input parameter
aBodyPart->SetCharset( charset );
} // end of if (offsetCh != KErrNotFound)
} // end of if( lenCT > offset )
} // end of if (offset != KErrNotFound)
}
// ----------------------------------------------------------------------------
// MultipartParser::GetBodyPartUrl
//
// Builds up the URL which refers to this particular body part
// ----------------------------------------------------------------------------
HBufC16*
MultipartParser::GetBodyPartUrlL( const TDesC8& aContentBase,
const TDesC8& aContentLocation,
const TDesC16& aResponseUrl )
{
// check on required parameters
__ASSERT_ALWAYS( aResponseUrl.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
// Body url builds up as follows:
// Take global (respond header) Content Base header first.
// If local (in the body part) Content Base header is present, then
// take it as default Content Base header (by overruling global Content
// Base header). Check if the Content Location header is either an
// absolute or relative URL. If it is an absolute, then ignore the
// Content Base, otherwise concatenate them.
// Error cases:
// No Content Base header + Content Location is relative URL
// No Content Location header
// Content Base header is relative URL
TBool contentBaseInvalid = EFalse;
HBufC16* url = NULL;
if (aContentBase.Ptr())
{
// Check if it is a relative url
if ( MultipartParser::IsUrlRelativeL( aContentBase ) )
{
// Relative URL is not valid here as base location.
contentBaseInvalid = ETrue;
}
}
else
{
// no content base header
contentBaseInvalid = ETrue;
} // end of if (aContentBase)
if (contentBaseInvalid)
{
if( aResponseUrl.Ptr() )
{
// Copy response url
TInt lenU = aResponseUrl.Length();
url = HBufC::NewLC( lenU + 1 );
url->Des().Copy( aResponseUrl );
url->Des().ZeroTerminate();
}
}
else
{
// Copy global content "base" location
TInt lenCB = aContentBase.Length();
url = HBufC::NewLC( lenCB + 1 );
url->Des().Copy( aContentBase );
url->Des().ZeroTerminate();
} // end of if (contentBaseInvalid)
// Check if Content Localtion is valid
if( aContentLocation.Ptr() )
{
TInt lenCL = aContentLocation.Length();
// If the Content Location is an absolute URL, then Content Base value is ignored,
// otherwise the absolute path is going to be built unless Content Base is missing
if ( !MultipartParser::IsUrlRelativeL( aContentLocation ) )
{
// clean up memory
if( url )
{
CleanupStack::PopAndDestroy(); // url
}
// fill url with content location
url = HBufC::NewL( lenCL + 1 );
url->Des().Copy( aContentLocation );
url->Des().ZeroTerminate();
}
else
{
if( url )
{
HBufC16* urlN = MultipartParser::UrlRelToAbsL( *url, aContentLocation );
CleanupStack::PopAndDestroy(); // url
url = urlN;
}
}
} // end of if( aContentLocation
else
{
if( url )
{
CleanupStack::Pop(); // url
}
}
return url;
}
// ----------------------------------------------------------------------------
// MultipartParser::IsUrlRelativeL
//
// ----------------------------------------------------------------------------
TBool
MultipartParser::IsUrlRelativeL( const TDesC8& aUrl )
{
// check on required parameters
__ASSERT_ALWAYS( aUrl.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
TUriParser8 uriParser;
User::LeaveIfError(uriParser.Parse(aUrl));
if( uriParser.Extract(EUriScheme).Ptr() )
{
return EFalse;
}
else
{
return ETrue;
}
}
// ----------------------------------------------------------------------------
// MultipartParser::UrlRelToAbsL
//
// Absolute path is built as : Base + Relative
// ----------------------------------------------------------------------------
HBufC16*
MultipartParser::UrlRelToAbsL( TDesC16& aBase,
const TDesC8& aRelativeUrl )
{
// check on required parameters
__ASSERT_ALWAYS( aBase.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
__ASSERT_ALWAYS( aRelativeUrl.Ptr() != NULL,
User::Panic(_L("MultipartParser Panic"), KErrArgument) );
// length of absolute url
TInt len = 0;
// length of relative url
TInt lenR = 0;
TBool appendSlash = EFalse;
// path of absolute url
TPtrC16 path( NULL, 0 );
// must to have aRelativeUrl
User::LeaveIfNull( (TUint8*)aRelativeUrl.Ptr() );
TUriParser16 uriParser;
User::LeaveIfError( uriParser.Parse( aBase ) );
// <scheme>://
TPtrC16 scheme( uriParser.Extract( EUriScheme ) );
// must to have scheme
User::LeaveIfNull( (TUint16*)scheme.Ptr() );
len += scheme.Length() + SCHEME_SEPARATOR_LENGTH;
// <user>:<password>@
TPtrC16 user( uriParser.Extract( EUriUserinfo ) );
if( user.Ptr() )
{
len += user.Length() + 1;
}
// <host>
TPtrC16 host( uriParser.Extract( EUriHost ) );
// must to have host
User::LeaveIfNull( (TUint16*)host.Ptr() );
len += host.Length();
// :<port>
TPtrC16 port( uriParser.Extract( EUriPort ) );
if( port.Ptr() )
{
len += port.Length();
}
// If the relative url begins with "./", remove it
TPtrC8 relativeUrl( NULL, 0 );
TInt indexD = aRelativeUrl.Locate( DOT_CHAR );
TInt indexS = aRelativeUrl.Locate( SLASH_CHAR );
if ( indexD == 0 && indexS == 1)
{
// Found a dot-slash at beginning of relative url
relativeUrl.Set( aRelativeUrl.Mid( 2 ) );
}
else
{
relativeUrl.Set( aRelativeUrl );
}
lenR = relativeUrl.Length();
len += lenR;
// If the relative url begins with a slash, then it is an absolute path
// Does relative url start with slash?
indexS = relativeUrl.Locate( SLASH_CHAR );
// no, need to extract path from base url
if( indexS != 0 )
{
// <path>
path.Set( uriParser.Extract( EUriPath ) );
if( path.Ptr() )
{
// cut off the file path
if ( path.LocateReverse( DOT_CHAR ) )
{
// case: dir/index.html
if ( TInt indexS2 = path.LocateReverse( SLASH_CHAR ) )
{
// to keep the slash
path.Set( path.Left( indexS2 + 1 ) );
}
// case: index.html
else
{
path.Set( NULL, 0 );
}
}
// figure out the end slash
if( path.Ptr() )
{
if( path.LocateReverse( SLASH_CHAR ) != (path.Length() - 1) )
{
appendSlash = ETrue;
}
len += path.Length();
}
else
{
appendSlash = ETrue;
}
}
}
// yes, no need to extract path from base url
if( appendSlash )
{
++len;
}
// NULL terminator
// In certain operator cases, need to have an extra space(size of a 2-byte NULL terminator)
// for proper String Termination
len += 2;
// new absolute url
HBufC16* urlAbs = HBufC16::NewL( len );
TPtr16 urlAbsPtr = urlAbs->Des();
// copy base into absolute url
// scheme
urlAbsPtr.Copy( scheme );
urlAbsPtr.Append( COLON_CHAR );
urlAbsPtr.Append( SLASH_CHAR );
urlAbsPtr.Append( SLASH_CHAR );
// user
if( user.Ptr() )
{
urlAbsPtr.Append( user );
urlAbsPtr.Append( AT_CHAR );
}
// host
urlAbsPtr.Append( host );
// port
if( port.Ptr() )
{
urlAbsPtr.Append( COLON_CHAR );
urlAbsPtr.Append( port );
}
// path
if( path.Ptr() )
{
urlAbsPtr.Append( path );
}
// slash between path and relative url
if( appendSlash )
{
urlAbsPtr.Append( SLASH_CHAR );
}
// relative url
TUint16* relUrlInt = new TUint16[ lenR ];
TPtr16 relUrl16( relUrlInt, lenR );
relUrl16.Copy( relativeUrl );
urlAbsPtr.Append( relUrl16 );
delete[] relUrlInt;
// null terminate
urlAbsPtr.ZeroTerminate();
return urlAbs;
}
// -------------------------------------------------------------------------
// Composes multipart/mixed document
// -------------------------------------------------------------------------
HBufC8*
MultipartParser::ComposeMixedL( RPointerArray<CBodyPart>& aBodyArray,
const TDesC8& aBoundary,
TInt aHeaderMask )
{
// --(aBoundary)
_LIT8(KBoundary, "--%S\r\n");
HBufC8* boundary = HBufC8::NewLC( aBoundary.Length() + 4 );
boundary->Des().Format( KBoundary, &aBoundary );
// Calculate the size of this document.
TInt bodySize = 0;
// a. for each CBodyPart
// boundaries + CRLF between headers and body (constant addition)
bodySize += (boundary->Length() + strlen(Multipart_CRLF_Text)) * aBodyArray.Count() ;
for (TInt i = 0; i < aBodyArray.Count(); i++)
{
if (!(aBodyArray[i]->Headers().Length() +
aBodyArray[i]->Body().Length()))
{
// one less boundary
bodySize -= boundary->Length() + strlen(Multipart_CRLF_Text);
// skip empty bodypart
continue;
}
bodySize += aBodyArray[i]->Headers().Length();
// ensure there are only 2 CRLFs between header and body
if (aBodyArray[i]->Headers().Length() > 0)
{
TPtrC8 bodyHeaders(aBodyArray[i]->Headers().Ptr(), aBodyArray[i]->Headers().Length());
TUint newEnd = bodyHeaders.Length() - 1;
while( bodyHeaders[ newEnd ] == '\r' || bodyHeaders[ newEnd ] == '\n' )
{
--newEnd;
--bodySize;
}
// two CRLFs
bodySize += strlen(Multipart_CRLF_Text);
}
bodySize += aBodyArray[i]->Body().Length();
// CRLF (end of body, add one only if there is body AND does not end with CRLF)
TPtrC8 bodyBody(aBodyArray[i]->Body().Ptr(), aBodyArray[i]->Body().Length());
if (bodyBody.Length() > 0
&& bodyBody.Right(2) != TPtrC8((TUint8*)Multipart_CRLF_Text, strlen(Multipart_CRLF_Text)))
{
bodySize += strlen(Multipart_CRLF_Text);
}
}
// end boundary (boundary - '\r\n' + "--")
bodySize += boundary->Length();
TInt docSize = bodySize;
// calculate the size of Headers
_LIT8(KContentType, "Content-Type: multipart/mixed; boundary=\"%S\"\r\n");
if ( aHeaderMask & EMultipartTopLevelHeaderContentType )
{
docSize += MULTIPART_CONTENT_TYPE_LENGTH + 1; // Content-Type: + empty space
docSize += KContentType().Length() - 2 + aBoundary.Length(); // multipart/mixed; boundary="{aBoundary}"
docSize += strlen(Multipart_CRLF_Text); // eol
}
if ( aHeaderMask & EMultipartTopLevelHeaderContentLength )
{
docSize += MULTIPART_CONTENT_LENGTH_LENGTH + 1; // Content-Length: + empty space
// calculate number of chars needed to represent bodySize
HBufC8* bodySizeSize = HBufC8::NewLC( 16 );
bodySizeSize->Des().Num( bodySize );
docSize += bodySizeSize->Length(); // content length (bodySize)
docSize += strlen(Multipart_CRLF_Text); // eol
CleanupStack::PopAndDestroy( bodySizeSize );
}
if ( aHeaderMask & EMultipartTopLevelHeaderLastModified )
{
docSize += MULTIPART_LAST_MODIFIED_LENGTH + 1;
docSize += MULTIPART_INTERNET_DATE_STRING_LENGTH; // timestamp (fixed length)
docSize += strlen(Multipart_CRLF_Text); // eol
}
// extra CRLF for separating header and body
docSize += strlen(Multipart_CRLF_Text);
// CALCULATION COMPLETE
// at this point, bodySize contains the size of bodyparts, i.e. Content-Length:
// and docSize contains the size of the entire document (use it to create HBufC8*
// of appropriate size)
// construct multipart document
HBufC8* document = HBufC8::NewLC(docSize);
TPtr8 docAppend(document->Des());
if ( aHeaderMask & EMultipartTopLevelHeaderContentType )
{
docAppend.Format( KContentType, &aBoundary );
}
if ( aHeaderMask & EMultipartTopLevelHeaderContentLength )
{
_LIT8( KContentLength, "Content-Length: %d\r\n" );
docAppend.AppendFormat( KContentLength, bodySize );
}
if ( aHeaderMask & EMultipartTopLevelHeaderLastModified )
{
_LIT8( KLastModified, "Last-Modified: %S\r\n" );
TTime current;
current.UniversalTime();
TInternetDate modDate(current.DateTime());
HBufC8* dateString = modDate.InternetDateTimeL( TInternetDate::ERfc1123Format );
docAppend.AppendFormat( KLastModified, dateString );
delete dateString;
}
// required CRLF
docAppend.Append((TUint8*)Multipart_CRLF_Text, strlen(Multipart_CRLF_Text));
// BodyParts
for (TInt i = 0; i < aBodyArray.Count(); i++)
{
if (!(aBodyArray[i]->Headers().Length() +
aBodyArray[i]->Body().Length()))
{
// skip empty bodypart
continue;
}
docAppend.Append( *boundary );
TInt headerLength = aBodyArray[i]->Headers().Length() - 1;
while ( headerLength > 0 &&
(aBodyArray[i]->Headers()[headerLength] == '\r'
|| aBodyArray[i]->Headers()[headerLength] == '\n' ))
{
--headerLength;
}
docAppend.Append( aBodyArray[i]->Headers().Ptr(), headerLength + 1 );
if ( headerLength > 0 )
{
docAppend.Append((TUint8*)Multipart_DoubleCRLF_Text, strlen(Multipart_DoubleCRLF_Text));
}
else
{
docAppend.Append((TUint8*)Multipart_CRLF_Text, strlen(Multipart_CRLF_Text));
}
// body
docAppend.Append(aBodyArray[i]->Body());
// CRLF only if body exists and doesn't end with CRLF
TPtrC8 bodyBody(aBodyArray[i]->Body().Ptr(), aBodyArray[i]->Body().Length());
if (bodyBody.Length() > 0
&& bodyBody.Right(2) != TPtrC8((TUint8*)Multipart_CRLF_Text, strlen(Multipart_CRLF_Text)))
{
docAppend.Append((TUint8*)Multipart_CRLF_Text, strlen(Multipart_CRLF_Text));
}
}
// end boundary
_LIT8(KEndBoundary, "--%S--");
docAppend.AppendFormat(KEndBoundary, &aBoundary);
CleanupStack::Pop( document );
CleanupStack::PopAndDestroy( boundary );
return document;
}