/* * Copyright 2010, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "webarchive" #include "config.h" #include "WebArchiveAndroid.h" #include "Base64.h" #include #include #include #include #include #include namespace WebCore { static const xmlChar* const archiveTag = BAD_CAST "Archive"; static const xmlChar* const archiveResourceTag = BAD_CAST "ArchiveResource"; static const xmlChar* const mainResourceTag = BAD_CAST "mainResource"; static const xmlChar* const subresourcesTag = BAD_CAST "subresources"; static const xmlChar* const subframesTag = BAD_CAST "subframes"; static const xmlChar* const urlFieldTag = BAD_CAST "url"; static const xmlChar* const mimeFieldTag = BAD_CAST "mimeType"; static const xmlChar* const encodingFieldTag = BAD_CAST "textEncoding"; static const xmlChar* const frameFieldTag = BAD_CAST "frameName"; static const xmlChar* const dataFieldTag = BAD_CAST "data"; PassRefPtr WebArchiveAndroid::create(PassRefPtr mainResource, Vector >& subresources, Vector >& subframeArchives) { if (mainResource) return adoptRef(new WebArchiveAndroid(mainResource, subresources, subframeArchives)); return 0; } PassRefPtr WebArchiveAndroid::create(Frame* frame) { PassRefPtr mainResource = frame->loader()->documentLoader()->mainResource(); Vector > subresources; Vector > subframes; int children = frame->tree()->childCount(); frame->loader()->documentLoader()->getSubresources(subresources); for (int child = 0; child < children; child++) subframes.append(create(frame->tree()->child(child))); return create(mainResource, subresources, subframes); } WebArchiveAndroid::WebArchiveAndroid(PassRefPtr mainResource, Vector >& subresources, Vector >& subframeArchives) { setMainResource(mainResource); for (Vector >::iterator subresourcesIterator = subresources.begin(); subresourcesIterator != subresources.end(); subresourcesIterator++) { addSubresource(*subresourcesIterator); } for (Vector >::iterator subframesIterator = subframeArchives.begin(); subframesIterator != subframeArchives.end(); subframesIterator++) { addSubframeArchive(*subframesIterator); } } static bool loadArchiveResourceField(xmlNodePtr resourceNode, const xmlChar* fieldName, Vector* outputData) { if (!outputData) return false; outputData->clear(); const char* base64Data = 0; for (xmlNodePtr fieldNode = resourceNode->xmlChildrenNode; fieldNode; fieldNode = fieldNode->next) { if (xmlStrEqual(fieldNode->name, fieldName)) { base64Data = (const char*)xmlNodeGetContent(fieldNode->xmlChildrenNode); if (!base64Data) { /* Empty fields seem to break if they aren't null terminated. */ outputData->append('\0'); return true; } break; } } if (!base64Data) { LOGD("loadWebArchive: Failed to load field."); return false; } const int base64Size = xmlStrlen(BAD_CAST base64Data); const int result = base64Decode(base64Data, base64Size, *outputData); if (!result) { LOGD("loadWebArchive: Failed to decode field."); return false; } return true; } static PassRefPtr loadArchiveResourceFieldBuffer(xmlNodePtr resourceNode, const xmlChar* fieldName) { Vector fieldData; if (loadArchiveResourceField(resourceNode, fieldName, &fieldData)) return SharedBuffer::create(fieldData.data(), fieldData.size()); return 0; } static String loadArchiveResourceFieldString(xmlNodePtr resourceNode, const xmlChar* fieldName) { Vector fieldData; if (loadArchiveResourceField(resourceNode, fieldName, &fieldData)) return String::fromUTF8(fieldData.data(), fieldData.size()); return String(); } static KURL loadArchiveResourceFieldURL(xmlNodePtr resourceNode, const xmlChar* fieldName) { Vector fieldData; if (loadArchiveResourceField(resourceNode, fieldName, &fieldData)) return KURL(ParsedURLString, String::fromUTF8(fieldData.data(), fieldData.size())); return KURL(); } static PassRefPtr loadArchiveResource(xmlNodePtr resourceNode) { if (!xmlStrEqual(resourceNode->name, archiveResourceTag)) { LOGD("loadWebArchive: Malformed resource."); return 0; } KURL url = loadArchiveResourceFieldURL(resourceNode, urlFieldTag); if (url.isNull()) { LOGD("loadWebArchive: Failed to load resource."); return 0; } String mimeType = loadArchiveResourceFieldString(resourceNode, mimeFieldTag); if (mimeType.isNull()) { LOGD("loadWebArchive: Failed to load resource."); return 0; } String textEncoding = loadArchiveResourceFieldString(resourceNode, encodingFieldTag); if (textEncoding.isNull()) { LOGD("loadWebArchive: Failed to load resource."); return 0; } String frameName = loadArchiveResourceFieldString(resourceNode, frameFieldTag); if (frameName.isNull()) { LOGD("loadWebArchive: Failed to load resource."); return 0; } PassRefPtr data = loadArchiveResourceFieldBuffer(resourceNode, dataFieldTag); if (!data) { LOGD("loadWebArchive: Failed to load resource."); return 0; } return ArchiveResource::create(data, url, mimeType, textEncoding, frameName); } static PassRefPtr loadArchive(xmlNodePtr archiveNode) { xmlNodePtr resourceNode = 0; PassRefPtr mainResource; Vector > subresources; Vector > subframes; if (!xmlStrEqual(archiveNode->name, archiveTag)) { LOGD("loadWebArchive: Malformed archive."); return 0; } for (resourceNode = archiveNode->xmlChildrenNode; resourceNode; resourceNode = resourceNode->next) { if (xmlStrEqual(resourceNode->name, mainResourceTag)) { resourceNode = resourceNode->xmlChildrenNode; if (!resourceNode) break; mainResource = loadArchiveResource(resourceNode); break; } } if (!mainResource) { LOGD("saveWebArchive: Failed to load main resource."); return 0; } for (resourceNode = archiveNode->xmlChildrenNode; resourceNode; resourceNode = resourceNode->next) { if (xmlStrEqual(resourceNode->name, subresourcesTag)) { for (resourceNode = resourceNode->xmlChildrenNode; resourceNode; resourceNode = resourceNode->next) { PassRefPtr subresource = loadArchiveResource(resourceNode); if (!subresource) { LOGD("saveWebArchive: Failed to load subresource."); break; } subresources.append(subresource); } break; } } for (resourceNode = archiveNode->xmlChildrenNode; resourceNode; resourceNode = resourceNode->next) { if (xmlStrEqual(resourceNode->name, subframesTag)) { for (resourceNode = resourceNode->xmlChildrenNode; resourceNode; resourceNode = resourceNode->next) { PassRefPtr subframe = loadArchive(resourceNode); if (!subframe) { LOGD("saveWebArchive: Failed to load subframe."); break; } subframes.append(subframe); } break; } } return WebArchiveAndroid::create(mainResource, subresources, subframes); } static PassRefPtr createArchiveForError() { /* When an archive cannot be loaded, we return an empty archive instead. */ PassRefPtr mainResource = ArchiveResource::create( SharedBuffer::create(), KURL(ParsedURLString, String::fromUTF8("file:///dummy")), String::fromUTF8("text/plain"), String(""), String("")); Vector > subresources; Vector > subframes; return WebArchiveAndroid::create(mainResource, subresources, subframes); } PassRefPtr WebArchiveAndroid::create(SharedBuffer* buffer) { const char* const noBaseUrl = ""; const char* const defaultEncoding = 0; const int noParserOptions = 0; xmlDocPtr doc = xmlReadMemory(buffer->data(), buffer->size(), noBaseUrl, defaultEncoding, noParserOptions); if (!doc) { LOGD("loadWebArchive: Failed to parse document."); return createArchiveForError(); } xmlNodePtr root = xmlDocGetRootElement(doc); if (!root) { LOGD("loadWebArchive: Empty document."); xmlFreeDoc(doc); return createArchiveForError(); } RefPtr archive = loadArchive(root); if (!archive) { LOGD("loadWebArchive: Failed to load archive."); xmlFreeDoc(doc); return createArchiveForError(); } xmlFreeDoc(doc); return archive.release(); } static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, const char* data, int size) { int result = xmlTextWriterStartElement(writer, tag); if (result < 0) { LOGD("saveWebArchive: Failed to start element."); return false; } if (size > 0) { Vector base64Data; base64Encode(data, size, base64Data, false); if (base64Data.isEmpty()) { LOGD("saveWebArchive: Failed to base64 encode data."); return false; } result = xmlTextWriterWriteRawLen(writer, BAD_CAST base64Data.data(), base64Data.size()); if (result < 0) { LOGD("saveWebArchive: Failed to write data."); return false; } } result = xmlTextWriterEndElement(writer); if (result < 0) { LOGD("saveWebArchive: Failed to end element."); return false; } return true; } static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, SharedBuffer* buffer) { return saveArchiveResourceField(writer, tag, buffer->data(), buffer->size()); } static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, const String& string) { CString utf8String = string.utf8(); return saveArchiveResourceField(writer, tag, utf8String.data(), utf8String.length()); } static bool saveArchiveResource(xmlTextWriterPtr writer, PassRefPtr resource) { int result = xmlTextWriterStartElement(writer, archiveResourceTag); if (result < 0) { LOGD("saveWebArchive: Failed to start element."); return false; } if (!saveArchiveResourceField(writer, urlFieldTag, resource->url().string()) || !saveArchiveResourceField(writer, mimeFieldTag, resource->mimeType()) || !saveArchiveResourceField(writer, encodingFieldTag, resource->textEncoding()) || !saveArchiveResourceField(writer, frameFieldTag, resource->frameName()) || !saveArchiveResourceField(writer, dataFieldTag, resource->data())) return false; result = xmlTextWriterEndElement(writer); if (result < 0) { LOGD("saveWebArchive: Failed to end element."); return false; } return true; } static bool saveArchive(xmlTextWriterPtr writer, PassRefPtr archive) { int result = xmlTextWriterStartElement(writer, archiveTag); if (result < 0) { LOGD("saveWebArchive: Failed to start element."); return false; } result = xmlTextWriterStartElement(writer, mainResourceTag); if (result < 0) { LOGD("saveWebArchive: Failed to start element."); return false; } if (!saveArchiveResource(writer, archive->mainResource())) return false; result = xmlTextWriterEndElement(writer); if (result < 0) { LOGD("saveWebArchive: Failed to end element."); return false; } result = xmlTextWriterStartElement(writer, subresourcesTag); if (result < 0) { LOGD("saveWebArchive: Failed to start element."); return false; } for (Vector >::iterator subresource = archive->subresources().begin(); subresource != archive->subresources().end(); subresource++) { if (!saveArchiveResource(writer, *subresource)) return false; } result = xmlTextWriterEndElement(writer); if (result < 0) { LOGD("saveWebArchive: Failed to end element."); return false; } result = xmlTextWriterStartElement(writer, subframesTag); if (result < 0) { LOGD("saveWebArchive: Failed to start element."); return false; } for (Vector >::iterator subframe = archive->subframeArchives().begin(); subframe != archive->subframeArchives().end(); subframe++) { if (!saveArchive(writer, *subframe)) return false; } result = xmlTextWriterEndElement(writer); if (result < 0) { LOGD("saveWebArchive: Failed to end element."); return true; } return true; } bool WebArchiveAndroid::saveWebArchive(xmlTextWriterPtr writer) { const char* const defaultXmlVersion = 0; const char* const defaultEncoding = 0; const char* const defaultStandalone = 0; int result = xmlTextWriterStartDocument(writer, defaultXmlVersion, defaultEncoding, defaultStandalone); if (result < 0) { LOGD("saveWebArchive: Failed to start document."); return false; } if (!saveArchive(writer, this)) return false; result = xmlTextWriterEndDocument(writer); if (result< 0) { LOGD("saveWebArchive: Failed to end document."); return false; } return true; } }