webengine/osswebengine/WebCore/platform/network/symbian/MultipartContentHandler.cpp
changeset 0 dd21522fd290
child 36 0ed94ceaa377
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebCore/platform/network/symbian/MultipartContentHandler.cpp	Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,503 @@
+/*
+* Copyright (c) 2007 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 "config.h"
+#include "MultipartContentHandler.h"
+#include "MultipartParser.h"
+#include "BodyPart.h"
+#include "HttpSessionManager.h"
+#include "HttpCacheManager.h"
+#include <http/rhttptransaction.h>
+#include <Uri16.h>
+#include "HttpFilterCommonStringsExt.h"
+#include "WebCharsetData.h"
+#include "StaticObjectsContainer.h"
+#include "ResourceLoaderDelegate.h"
+#include "HttpConnUtils.h"
+
+// CONSTANTS
+_LIT ( KContentTypeMultipartMixed,  "multipart/mixed");
+_LIT8( KContentTypeMultipartMixed8, "multipart/mixed");
+_LIT8( KContentTypeTextHtml8, "text/html");
+_LIT8( KContentTypeAppXhtml8, "application/xhtml+xml");
+_LIT8( KContentTypeAppWapXhtml8, "application/vnd.wap.xhtml+xml");
+_LIT8( KContentTypeAppOctetStream, "application/octet-stream");
+_LIT8( KContentTypeTextWml8, "text/vnd.wap.wml");
+_LIT8( KContentTypeImages8, "image/");
+_LIT8( KContentTypeScript8, "application/x-javascript");
+_LIT8( KContentTypeCss8, "text/css");
+_LIT8( KErrPage, "<html><body><h1>ERROR:</h1><h2>Invalid Content</h2><br/></body></html>");
+_LIT8( KBoundaryEndTag, "--");
+_LIT8( KContentTypeStr, "Content-Type: ");
+_LIT8( KCharsetStr, "; charset=");
+_LIT8( KCrLf, "\r\n");
+_LIT8( KEmptyStr, "");
+_LIT ( KExpiresFormat,"Expires: %F%*E, %D %*N %Y, %H:%T:%S GMT\r\n");
+_LIT8( KCacheControl, "Cache-Control: ");
+_LIT8( KMaxAge, "max-age=");
+_LIT8( KExpires, "Expires: ");
+//_LIT ( KExtHtml, ".html");
+//_LIT ( KExtHtm, ".htm");
+//_LIT ( KExtAsp, ".asp");
+//_LIT ( KExtPhp, ".php");
+//_LIT ( KExtJsp, ".jsp");
+
+const TInt KDefaultRespBufferSize = 20 * 1024; // if no content-length header, alloc buffer this size
+const TInt KRespBufferExtraMargin = 5 * 1024;  // extra length of buffer to avoid realloc()
+const TInt KExpiresHeaderLength = 1024;        // default length for Expires header
+const TInt KExpiresInterval = 5;               // num minutes for expiration of multipart content parts
+const TInt KBoundaryEndMarkerLen = 2;          // length of the the "--" on last multipart part
+const TUint KCharsetUidDef = KCharacterSetIdentifierIso88591;
+
+using namespace WebCore;
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::MultipartContentHandler
+// C++ default constructor can NOT contain any code, that
+// might leave.
+// -----------------------------------------------------------------------------
+MultipartContentHandler::MultipartContentHandler()
+    : m_ResponseUrl(NULL),
+      m_TopLevelBoundary(NULL),
+      m_TopLevelBoundaryEnd(NULL),
+      m_ParsingCompleted(false),
+      m_CacheExpirationHeader(NULL)
+{
+    m_MarkupContent.Set(KEmptyStr);
+    m_MarkupCharset.Set(KEmptyStr);
+    m_MarkupContentType.Set(KEmptyStr);
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::ConstructL
+// Symbian 2nd phase constructor can leave.
+// -----------------------------------------------------------------------------
+void MultipartContentHandler::ConstructL()
+{
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::NewL
+// Two-phased constructor.
+// -----------------------------------------------------------------------------
+//
+MultipartContentHandler* MultipartContentHandler::NewL()
+{
+    MultipartContentHandler* self = new(ELeave)MultipartContentHandler();
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    CleanupStack::Pop(self);
+    return self;
+}
+
+// -----------------------------------------------------------------------------
+// Destructor
+// -----------------------------------------------------------------------------
+MultipartContentHandler::~MultipartContentHandler()
+{
+    for(TInt n = 1; n<m_BodyPartsArray.Count(); n++) {
+        delete m_BodyPartsArray[n];
+    }
+    m_BodyPartsArray.Reset();
+    delete m_ResponseBuffer;
+    delete m_ResponseUrl;
+    delete m_TopLevelBoundary;
+    delete m_TopLevelBoundaryEnd;
+    delete m_CacheExpirationHeader;
+}
+
+// -----------------------------------------------------------------------------
+// SelfDownloadContentHandler::IsSupported
+// -----------------------------------------------------------------------------
+bool MultipartContentHandler::IsSupported(
+    const ResourceResponse& response)
+{
+    TInt pos = response.mimeType().des().Find(KContentTypeMultipartMixed);
+    //
+    return (pos != KErrNotFound);
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::HandleResponseHeadersL
+// -----------------------------------------------------------------------------
+TInt MultipartContentHandler::HandleResponseHeadersL(
+    const ResourceResponse& response,
+    RHTTPTransaction& httpTransaction)
+{
+    // initialize response body buffer
+    TUint contentLength = response.expectedContentLength();
+    contentLength = contentLength > 0 ? contentLength : KDefaultRespBufferSize;
+    // add extra size to buffer to avoid realloc calls
+    m_ResponseBuffer = HBufC8::NewL(contentLength + KRespBufferExtraMargin) ;
+    TPtrC8 respUrl = response.url().des();
+    delete m_ResponseUrl;
+    m_ResponseUrl = HBufC::NewL(respUrl.Length());
+    m_ResponseUrl->Des().Copy(respUrl);
+
+    TPtrC8 boundPtr;
+    THTTPHdrVal hdrVal;
+    // save multipart boundary string
+    RHTTPHeaders hdrs = httpTransaction.Response().GetHeaderCollection();
+    RHTTPSession session = httpTransaction.Session();
+    RStringPool stringPool = session.StringPool();
+    const TStringTable& stringTable = session.GetTable();
+    if(hdrs.GetParam(stringPool.StringF( HTTP::EContentType, stringTable ),
+        stringPool.StringF( HttpFilterCommonStringsExt::EBoundary,
+        HttpFilterCommonStringsExt::GetTable()), hdrVal) == KErrNone) {
+        boundPtr.Set(hdrVal.StrF().DesC());
+    }
+    // locate any cache control headers in top-level response
+    TPtrC8 expiresPtr;
+    TPtrC8 cacheControlPtr;
+    hdrs.GetRawField(stringPool.StringF(HTTP::EExpires, stringTable), expiresPtr);
+    hdrs.GetRawField(stringPool.StringF(HTTP::ECacheControl, stringTable), cacheControlPtr);
+    if(expiresPtr.Length() > 0) {
+        // found header matching "Expires: . . ."
+        delete m_CacheExpirationHeader;
+        m_CacheExpirationHeader = HBufC8::NewL(KExpires().Length() +
+            expiresPtr.Length() + KCrLf().Length());
+        TPtr8 expPtr = m_CacheExpirationHeader->Des();
+        expPtr.Copy(KExpires);
+        expPtr.Append(expiresPtr);
+        expPtr.Append(KCrLf);
+    } else if(cacheControlPtr.FindF(KMaxAge) != KErrNotFound) {
+        // found header matching "Cache-Control: max-age= . . ."
+        delete m_CacheExpirationHeader;
+        m_CacheExpirationHeader = HBufC8::NewL(KCacheControl().Length() +
+            KMaxAge().Length() + cacheControlPtr.Length() + KCrLf().Length());
+        TPtr8 expPtr = m_CacheExpirationHeader->Des();
+        expPtr.Copy(KCacheControl);
+        expPtr.Append(cacheControlPtr);
+        expPtr.Append(KCrLf);
+    } else {
+        // generate Expires header with default expiration
+        m_CacheExpirationHeader = GenerateExpiresHeaderL();
+    }
+    // save boundary strings to member variable buffers
+    m_TopLevelBoundary = HBufC8::NewL(boundPtr.Length());
+    m_TopLevelBoundary->Des().Copy(boundPtr);
+    //
+    m_TopLevelBoundaryEnd = HBufC8::NewL(boundPtr.Length() + KBoundaryEndMarkerLen );
+    m_TopLevelBoundaryEnd->Des().Copy( boundPtr);
+    m_TopLevelBoundaryEnd->Des().Append(KBoundaryEndTag);
+    //
+    return KErrNone;
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::HandleResponseBodyL
+// -----------------------------------------------------------------------------
+TInt MultipartContentHandler::HandleResponseBodyL(
+    TDesC8& aRespData)
+{
+    TInt status(KErrNone);
+    if (aRespData.Length()) {
+        // do we have enough capacity left in response buffer to handle this chunk?
+        TInt sizeNeeded = m_ResponseBuffer->Length() + aRespData.Length();
+        if (sizeNeeded > m_ResponseBuffer->Des().MaxLength()) {
+            // add extra length to avoid future realloc's
+            m_ResponseBuffer = m_ResponseBuffer->ReAllocL( sizeNeeded +
+            KRespBufferExtraMargin);
+        }
+        m_ResponseBuffer->Des().Append(aRespData);
+    }
+    return status;
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::ResponseCompleteL
+// -----------------------------------------------------------------------------
+TInt MultipartContentHandler::ResponseComplete()
+{
+    TRAPD(ret, HandleMultipartMixedL());
+    return ret;
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::MarkupContent
+// -----------------------------------------------------------------------------
+const TDesC8& MultipartContentHandler::MarkupContent()
+{
+    __ASSERT_DEBUG(m_ParsingCompleted, User::Panic(_L("MultipartContentHandler"), KErrNotReady));
+    return m_MarkupContent;
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::HandleMultipartMixedL
+// -----------------------------------------------------------------------------
+TInt MultipartContentHandler::HandleMultipartMixedL()
+{
+    TInt status(KErrNone);
+    // parse complete response
+    MultipartParser::ParseL(*m_ResponseBuffer, KContentTypeMultipartMixed8,
+                            *m_TopLevelBoundary, *m_ResponseUrl,
+                            m_BodyPartsArray, -1);
+    TInt  partCount(ValidateMultipartArrayL());
+    //
+    if (partCount > 0) {
+        TUint charsetId(KCharsetUidDef);
+        m_MarkupCharset.Set(m_BodyPartsArray[0]->Charset());
+        m_MarkupContentType.Set(m_BodyPartsArray[0]->ContentType());
+        CHttpCacheManager* cacheMgr = StaticObjectsContainer::instance()->
+                                      resourceLoaderDelegate()->
+                                      httpSessionManager()->cacheManager(); 
+        // push non-markup parts to the two caches
+        // this assumes that response follows convention for first
+        // part to be the markup and following parts to be the 
+        // resources referenced by the markup
+        for(TInt n = 1; n<m_BodyPartsArray.Count(); n++) {
+            PushToHttpCacheL(*m_BodyPartsArray[n], cacheMgr);
+            // PushToMemoryCacheL(*m_BodyPartsArray[n]);
+            delete m_BodyPartsArray[n];
+            m_BodyPartsArray[n] = NULL;
+        }
+        m_MarkupContent.Set(m_BodyPartsArray[0]->Body());
+    } else {
+        __DEBUGGER();  // fixme: remove once all testing complete                
+        m_MarkupContentType.Set(KContentTypeTextHtml8);        
+        m_MarkupContent.Set(KErrPage);
+        status = KErrGeneral;
+    }
+    m_ParsingCompleted = true;
+    return status;
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::PushToMemoryCache
+// -----------------------------------------------------------------------------
+/*void MultipartContentHandler::PushToMemoryCacheL( CBodyPart& aPart )
+{
+    // not implemented
+}*/
+
+// -----------------------------------------------------------------------------
+// CHttpLoaderUtils::PushToHttpCacheL
+// -----------------------------------------------------------------------------
+void MultipartContentHandler::PushToHttpCacheL(
+    CBodyPart& aPart,
+    CHttpCacheManager* aCacheMgr)
+{
+    TBool status(EFalse);
+    __ASSERT_DEBUG(aCacheMgr, THttpConnUtils::PanicLoader(KErrGeneral));
+
+    // convert the part's url to ascii
+    TPtrC8 asciiUrl;
+    TUint8* retPtr = NULL;
+    THttpConnUtils::ConvertPtrUsc2ToAscii( aPart.Url(), asciiUrl, &retPtr );
+    if(aCacheMgr->Find(asciiUrl)) {
+        aCacheMgr->RemoveL(asciiUrl);
+    }
+    // generate content-type header
+    TInt contTypeLen = aPart.ContentType().Length();
+    TInt charsetLen = aPart.Charset().Length();
+    TInt hdrsLen(0);
+    if(contTypeLen > 0) {
+        hdrsLen += KContentTypeStr().Length() + contTypeLen + KCrLf().Length();
+        if(charsetLen > 0) {
+            hdrsLen += KCharsetStr().Length() + charsetLen;
+        }
+    }
+    // generate Expires header
+    HBufC8* expirationBuf(NULL);
+    TPtrC8 expPtrActual;
+    TInt expStart(aPart.Headers().FindF(KExpires));
+    TInt cacheControlStart(aPart.Headers().FindF(KCacheControl));
+    TInt cleanupCount(0);
+    //
+    if(expStart != KErrNotFound) {
+        // found part header matching "Expires: . . ."
+        const TPtrC8 expPtr = aPart.Headers().Mid(expStart);
+        TInt hdrEnd = expPtr.FindF(KCrLf);
+        hdrEnd = (hdrEnd == KErrNotFound) ? expPtr.Length() : hdrEnd;
+        expirationBuf = HBufC8::NewLC(hdrEnd + KCrLf().Length());
+        cleanupCount++;
+        expirationBuf->Des().Copy(expPtr.Left(hdrEnd));
+        expirationBuf->Des().Append(KCrLf);
+        expPtrActual.Set(*expirationBuf);
+    } else if((cacheControlStart != KErrNotFound) && 
+            (aPart.Headers().Find(KMaxAge) != KErrNotFound)) {
+        // found part header matching "Cache-Control: max-age= . . ."
+        const TPtrC8 expPtr = aPart.Headers().Mid(cacheControlStart);
+        TInt hdrEnd = expPtr.FindF(KCrLf);
+        hdrEnd = (hdrEnd == KErrNotFound) ? expPtr.Length() : hdrEnd;
+        expirationBuf = HBufC8::NewLC(hdrEnd + KCrLf().Length());
+        cleanupCount++;
+        expirationBuf->Des().Copy(expPtr.Left(hdrEnd));
+        expirationBuf->Des().Append(KCrLf);
+        expPtrActual.Set(*expirationBuf);
+    } else {
+        // generate Expires header with default expiration
+        expPtrActual.Set(*m_CacheExpirationHeader);
+    }
+    hdrsLen += expPtrActual.Length();
+    HBufC8* headers = HBufC8::NewLC(hdrsLen);
+    cleanupCount++;
+    TPtr8 headersPtr = headers->Des();
+    headersPtr.Copy(expPtrActual);
+    //
+    if(contTypeLen > 0) {
+        headersPtr.Append(KContentTypeStr);
+        headersPtr.Append(aPart.ContentType());
+        if( charsetLen > 0 ) {
+            headersPtr.Append(KCharsetStr);
+            headersPtr.Append(aPart.Charset());
+        }
+        headersPtr.Append( KCrLf );
+    }
+    //
+    status = aCacheMgr->SaveL(asciiUrl, *headers, aPart.Body(), aPart.Headers());
+    User::Free( retPtr );
+    CleanupStack::PopAndDestroy(cleanupCount);  //headers, expirationBuf
+}
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::IsExtensionTextType
+// -----------------------------------------------------------------------------
+/*TInt MultipartContentHandler::IsExtensionTypeText(const TDesC16& aResponseUrl)
+{
+
+    // special case for application/octet-stream. some web servers
+    // are miscofigured and send application/octet-stream even for html
+    // page so we need to pass it to webcore as if it was text/html. however
+    // some binary content also comes as application/octet-stream (such as
+    // sisx files ). so here we che(ha)ck if the response url has .html .htm .asp
+    // extension. though luck for the rest. feel free to extend the list.
+    TUriParser parser;
+    if( parser.Parse( aResponseUrl ) == KErrNone )
+        {
+        //
+        TPtrC path = parser.Extract( EUriPath );
+        // path == 1 means only / (no filename)
+        if( path.Length() > 1 )
+            {
+
+              if (!( path.Find( KExtHtml ) != -1 ||
+                  path.Find( KExtHtm ) != -1 ||
+                  path.Find( KExtAsp ) != -1 ||
+                  path.Find( KExtPhp ) != -1 ||
+                  path.Find( KExtJsp ) != -1 ))
+                  {
+                return KErrGeneral;
+                  }
+            }
+        }
+
+  return KErrNone;
+}*/
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::ValidateMultipartArrayL
+// -----------------------------------------------------------------------------
+TInt MultipartContentHandler::ValidateMultipartArrayL()
+{
+    // set the validation rules mask for multipart/mixed
+    TInt rulesMask = KRulePartUrlMatchesRespUrl |
+                     KRuleUrlMatchingOnHostname |
+                     KRuleFirstPartIsMarkup |
+                     KRuleOnlyWebContentAllowed;
+    //
+    TUriParser respUriParser;
+    User::LeaveIfError(respUriParser.Parse(*m_ResponseUrl));
+    //    
+    if (rulesMask & KRulePartUrlMatchesRespUrl) {
+        // compare specific portions of part url with multipart response url
+        TUriParser partUri;
+        //
+        if(rulesMask & KRuleUrlMatchingOnScheme) {
+            // not yet implemented
+            __ASSERT_DEBUG(EFalse, User::Panic(_L("MultipartContentHandler"), KErrArgument));
+        }
+        //
+        if(rulesMask & KRuleUrlMatchingOnHostname) {
+            TInt m(0);
+            while(m < m_BodyPartsArray.Count()) {
+                if((partUri.Parse(m_BodyPartsArray[m]->Url()) != KErrNone)
+                    || (respUriParser.Compare(partUri, EUriHost ) != 0)) {
+                    // invalid part url
+                    delete m_BodyPartsArray[m];
+                    m_BodyPartsArray[m] = NULL;
+                    m_BodyPartsArray.Remove(m);
+                } else {
+                    ++m;
+                }
+            }
+        }
+        if(rulesMask & KRuleUrlMatchingOnPort) {
+            // not yet implemented
+            __ASSERT_DEBUG(EFalse, User::Panic(_L("MultipartContentHandler"), KErrArgument));
+        }
+    }
+    if((rulesMask & KRuleFirstPartIsMarkup) && (m_BodyPartsArray.Count() > 0)) {
+        // ensure that content-type of first part is either HTML, XHTML or WML
+        const TDesC8& contType = m_BodyPartsArray[0]->ContentType();
+        if((contType.Find(KContentTypeTextHtml8) == KErrNotFound) &&        // html
+           (contType.Find(KContentTypeAppXhtml8) == KErrNotFound) &&      // xhtml
+           (contType.Find(KContentTypeAppWapXhtml8) == KErrNotFound) &&   // wap??
+           (contType.Find(KContentTypeTextWml8) == KErrNotFound) &&        // wml
+           (contType.Find(KContentTypeAppOctetStream) == KErrNotFound) /*||
+           IsExtensionTypeText(m_BodyPartsArray[0]->Url()) == KErrGeneral)*/) {
+            m_BodyPartsArray.ResetAndDestroy();
+            return 0;
+        }
+    }
+    if((rulesMask & KRuleOnlyWebContentAllowed) && (m_BodyPartsArray.Count() > 0)) {
+        // remove all unrecognized body parts
+        // ensure that content-type of all parts are from the following
+        // list: markup, image, script, css, or sound types
+        TInt m(0);
+        while(m < m_BodyPartsArray.Count()) {
+            const TDesC8& contType = m_BodyPartsArray[m]->ContentType();
+            if((contType.Find(KContentTypeTextHtml8) == KErrNotFound ) &&     // html
+               (contType.Find(KContentTypeImages8 ) == KErrNotFound ) &&       // images
+               ( contType.Find( KContentTypeCss8 ) == KErrNotFound ) &&          // css
+               ( contType.Find( KContentTypeScript8 ) == KErrNotFound ) &&       // javascript
+               ( contType.Find( KContentTypeAppXhtml8 ) == KErrNotFound ) &&     // xhtml
+               ( contType.Find( KContentTypeTextWml8 ) == KErrNotFound ) &&      // wml
+               ( contType.Find( KContentTypeAppWapXhtml8 ) == KErrNotFound ) &&  // wap??
+               ( contType.Find( KContentTypeAppOctetStream ) == KErrNotFound )) { // octet-stream??
+                delete m_BodyPartsArray[m];
+                m_BodyPartsArray[m] = NULL;
+                m_BodyPartsArray.Remove(m);
+            } else {
+                ++m;
+            }
+        }
+    }
+    return m_BodyPartsArray.Count();
+}
+
+
+// -----------------------------------------------------------------------------
+// MultipartContentHandler::GenerateExpiresHeader
+// -----------------------------------------------------------------------------
+HBufC8* MultipartContentHandler::GenerateExpiresHeaderL()
+{
+    HBufC* expiresBuf = HBufC::NewLC(KExpiresHeaderLength);
+    TPtr expiresPtr = expiresBuf->Des();
+    TTime expireTime;
+    expireTime.UniversalTime();
+    expireTime += TTimeIntervalMinutes(KExpiresInterval);
+    //
+    expireTime.FormatL(expiresPtr, KExpiresFormat);
+    // now convert to 8-bit
+    HBufC8* retBuf = HBufC8::NewL(expiresBuf->Length());
+    retBuf->Des().Copy(*expiresBuf);
+    CleanupStack::PopAndDestroy(expiresBuf);
+    return retBuf;
+}
+//  End of File