1/*
2 * Copyright 2010, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#define LOG_TAG "webarchive"
27
28#include "config.h"
29#include "WebArchiveAndroid.h"
30
31#if ENABLE(WEB_ARCHIVE)
32
33#include "Base64.h"
34#include <libxml/encoding.h>
35#include <libxml/parser.h>
36#include <libxml/tree.h>
37#include <libxml/xmlstring.h>
38#include <libxml/xmlwriter.h>
39#include <utils/Log.h>
40#include <wtf/text/CString.h>
41
42namespace WebCore {
43
44static const xmlChar* const archiveTag = BAD_CAST "Archive";
45static const xmlChar* const archiveResourceTag = BAD_CAST "ArchiveResource";
46static const xmlChar* const mainResourceTag = BAD_CAST "mainResource";
47static const xmlChar* const subresourcesTag = BAD_CAST "subresources";
48static const xmlChar* const subframesTag = BAD_CAST "subframes";
49static const xmlChar* const urlFieldTag = BAD_CAST "url";
50static const xmlChar* const mimeFieldTag = BAD_CAST "mimeType";
51static const xmlChar* const encodingFieldTag = BAD_CAST "textEncoding";
52static const xmlChar* const frameFieldTag = BAD_CAST "frameName";
53static const xmlChar* const dataFieldTag = BAD_CAST "data";
54
55PassRefPtr<WebArchiveAndroid> WebArchiveAndroid::create(PassRefPtr<ArchiveResource> mainResource,
56        Vector<PassRefPtr<ArchiveResource> >& subresources,
57        Vector<PassRefPtr<Archive> >& subframeArchives)
58{
59    if (mainResource)
60        return adoptRef(new WebArchiveAndroid(mainResource, subresources, subframeArchives));
61    return 0;
62}
63
64PassRefPtr<WebArchiveAndroid> WebArchiveAndroid::create(Frame* frame)
65{
66    PassRefPtr<ArchiveResource> mainResource = frame->loader()->documentLoader()->mainResource();
67    Vector<PassRefPtr<ArchiveResource> > subresources;
68    Vector<PassRefPtr<Archive> > subframes;
69    int children = frame->tree()->childCount();
70
71    frame->loader()->documentLoader()->getSubresources(subresources);
72
73    for (int child = 0; child < children; child++)
74        subframes.append(create(frame->tree()->child(child)));
75
76    return create(mainResource, subresources, subframes);
77}
78
79WebArchiveAndroid::WebArchiveAndroid(PassRefPtr<ArchiveResource> mainResource,
80        Vector<PassRefPtr<ArchiveResource> >& subresources,
81        Vector<PassRefPtr<Archive> >& subframeArchives)
82{
83    setMainResource(mainResource);
84
85    for (Vector<PassRefPtr<ArchiveResource> >::iterator subresourcesIterator = subresources.begin();
86         subresourcesIterator != subresources.end();
87         subresourcesIterator++) {
88        addSubresource(*subresourcesIterator);
89    }
90
91    for (Vector<PassRefPtr<Archive> >::iterator subframesIterator = subframeArchives.begin();
92         subframesIterator != subframeArchives.end();
93         subframesIterator++) {
94        addSubframeArchive(*subframesIterator);
95    }
96}
97
98static bool loadArchiveResourceField(xmlNodePtr resourceNode, const xmlChar* fieldName, Vector<char>* outputData)
99{
100    if (!outputData)
101        return false;
102
103    outputData->clear();
104
105    const char* base64Data = 0;
106
107    for (xmlNodePtr fieldNode = resourceNode->xmlChildrenNode;
108         fieldNode;
109         fieldNode = fieldNode->next) {
110        if (xmlStrEqual(fieldNode->name, fieldName)) {
111            base64Data = (const char*)xmlNodeGetContent(fieldNode->xmlChildrenNode);
112            if (!base64Data) {
113                /* Empty fields seem to break if they aren't null terminated. */
114                outputData->append('\0');
115                return true;
116            }
117            break;
118        }
119    }
120    if (!base64Data) {
121        ALOGD("loadArchiveResourceField: Failed to load field.");
122        return false;
123    }
124
125    const int base64Size = xmlStrlen(BAD_CAST base64Data);
126
127    const int result = base64Decode(base64Data, base64Size, *outputData);
128    if (!result) {
129        ALOGD("loadArchiveResourceField: Failed to decode field.");
130        return false;
131    }
132
133    return true;
134}
135
136static PassRefPtr<SharedBuffer> loadArchiveResourceFieldBuffer(xmlNodePtr resourceNode, const xmlChar* fieldName)
137{
138    Vector<char> fieldData;
139
140    if (loadArchiveResourceField(resourceNode, fieldName, &fieldData))
141        return SharedBuffer::create(fieldData.data(), fieldData.size());
142
143    return 0;
144}
145
146static String loadArchiveResourceFieldString(xmlNodePtr resourceNode, const xmlChar* fieldName)
147{
148    Vector<char> fieldData;
149
150    if (loadArchiveResourceField(resourceNode, fieldName, &fieldData))
151        return String::fromUTF8(fieldData.data(), fieldData.size());
152
153    return String();
154}
155
156static KURL loadArchiveResourceFieldURL(xmlNodePtr resourceNode, const xmlChar* fieldName)
157{
158    Vector<char> fieldData;
159
160    if (loadArchiveResourceField(resourceNode, fieldName, &fieldData))
161        return KURL(ParsedURLString, String::fromUTF8(fieldData.data(), fieldData.size()));
162
163    return KURL();
164}
165
166static PassRefPtr<ArchiveResource> loadArchiveResource(xmlNodePtr resourceNode)
167{
168    if (!xmlStrEqual(resourceNode->name, archiveResourceTag)) {
169        ALOGD("loadArchiveResource: Malformed resource.");
170        return 0;
171    }
172
173    KURL url = loadArchiveResourceFieldURL(resourceNode, urlFieldTag);
174    if (url.isNull()) {
175        ALOGD("loadArchiveResource: Failed to load resource.");
176        return 0;
177    }
178
179    String mimeType = loadArchiveResourceFieldString(resourceNode, mimeFieldTag);
180    if (mimeType.isNull()) {
181        ALOGD("loadArchiveResource: Failed to load resource.");
182        return 0;
183    }
184
185    String textEncoding = loadArchiveResourceFieldString(resourceNode, encodingFieldTag);
186    if (textEncoding.isNull()) {
187        ALOGD("loadArchiveResource: Failed to load resource.");
188        return 0;
189    }
190
191    String frameName = loadArchiveResourceFieldString(resourceNode, frameFieldTag);
192    if (frameName.isNull()) {
193        ALOGD("loadArchiveResource: Failed to load resource.");
194        return 0;
195    }
196
197    PassRefPtr<SharedBuffer> data = loadArchiveResourceFieldBuffer(resourceNode, dataFieldTag);
198    if (!data) {
199        ALOGD("loadArchiveResource: Failed to load resource.");
200        return 0;
201    }
202
203    return ArchiveResource::create(data, url, mimeType, textEncoding, frameName);
204}
205
206static PassRefPtr<WebArchiveAndroid> loadArchive(xmlNodePtr archiveNode)
207{
208    xmlNodePtr resourceNode = 0;
209
210    PassRefPtr<ArchiveResource> mainResource;
211    Vector<PassRefPtr<ArchiveResource> > subresources;
212    Vector<PassRefPtr<Archive> > subframes;
213
214    if (!xmlStrEqual(archiveNode->name, archiveTag)) {
215        ALOGD("loadArchive: Malformed archive.");
216        return 0;
217    }
218
219    for (resourceNode = archiveNode->xmlChildrenNode;
220         resourceNode;
221         resourceNode = resourceNode->next) {
222        if (xmlStrEqual(resourceNode->name, mainResourceTag)) {
223            resourceNode = resourceNode->xmlChildrenNode;
224            if (!resourceNode)
225                break;
226            mainResource = loadArchiveResource(resourceNode);
227            break;
228        }
229    }
230    if (!mainResource) {
231        ALOGD("loadArchive: Failed to load main resource.");
232        return 0;
233    }
234
235    for (resourceNode = archiveNode->xmlChildrenNode;
236         resourceNode;
237         resourceNode = resourceNode->next) {
238        if (xmlStrEqual(resourceNode->name, subresourcesTag)) {
239            for (resourceNode = resourceNode->xmlChildrenNode;
240                 resourceNode;
241                 resourceNode = resourceNode->next) {
242                PassRefPtr<ArchiveResource> subresource = loadArchiveResource(resourceNode);
243                if (!subresource) {
244                    ALOGD("loadArchive: Failed to load subresource.");
245                    break;
246                }
247                subresources.append(subresource);
248            }
249            break;
250        }
251    }
252
253    for (resourceNode = archiveNode->xmlChildrenNode;
254         resourceNode;
255         resourceNode = resourceNode->next) {
256        if (xmlStrEqual(resourceNode->name, subframesTag)) {
257            for (resourceNode = resourceNode->xmlChildrenNode;
258                 resourceNode;
259                 resourceNode = resourceNode->next) {
260                PassRefPtr<WebArchiveAndroid> subframe = loadArchive(resourceNode);
261                if (!subframe) {
262                    ALOGD("loadArchive: Failed to load subframe.");
263                    break;
264                }
265                subframes.append(subframe);
266            }
267            break;
268        }
269    }
270
271    return WebArchiveAndroid::create(mainResource, subresources, subframes);
272}
273
274static PassRefPtr<WebArchiveAndroid> createArchiveForError()
275{
276    /* When an archive cannot be loaded, we return an empty archive instead. */
277    PassRefPtr<ArchiveResource> mainResource = ArchiveResource::create(
278        SharedBuffer::create(), KURL(ParsedURLString, String::fromUTF8("file:///dummy")),
279        String::fromUTF8("text/plain"), String(""), String(""));
280    Vector<PassRefPtr<ArchiveResource> > subresources;
281    Vector<PassRefPtr<Archive> > subframes;
282
283    return WebArchiveAndroid::create(mainResource, subresources, subframes);
284}
285
286PassRefPtr<WebArchiveAndroid> WebArchiveAndroid::create(SharedBuffer* buffer)
287{
288    const char* const noBaseUrl = "";
289    const char* const defaultEncoding = 0;
290    const int noParserOptions = 0;
291
292    xmlDocPtr doc = xmlReadMemory(buffer->data(), buffer->size(), noBaseUrl, defaultEncoding, noParserOptions);
293    if (!doc) {
294        ALOGD("create: Failed to parse document.");
295        return createArchiveForError();
296    }
297
298    xmlNodePtr root = xmlDocGetRootElement(doc);
299    if (!root) {
300        ALOGD("create: Empty document.");
301        xmlFreeDoc(doc);
302        return createArchiveForError();
303    }
304
305    RefPtr<WebArchiveAndroid> archive = loadArchive(root);
306    if (!archive) {
307        ALOGD("create: Failed to load archive.");
308        xmlFreeDoc(doc);
309        return createArchiveForError();
310    }
311
312    xmlFreeDoc(doc);
313    return archive.release();
314}
315
316static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, const char* data, int size)
317{
318    int result = xmlTextWriterStartElement(writer, tag);
319    if (result < 0) {
320        ALOGD("saveArchiveResourceField: Failed to start element.");
321        return false;
322    }
323
324    if (size > 0) {
325        Vector<char> base64Data;
326        base64Encode(data, size, base64Data, false);
327        if (base64Data.isEmpty()) {
328            ALOGD("saveArchiveResourceField: Failed to base64 encode data.");
329            return false;
330        }
331
332        result = xmlTextWriterWriteRawLen(writer, BAD_CAST base64Data.data(), base64Data.size());
333        if (result < 0) {
334            ALOGD("saveArchiveResourceField: Failed to write data.");
335            return false;
336        }
337    }
338
339    result = xmlTextWriterEndElement(writer);
340    if (result < 0) {
341        ALOGD("saveArchiveResourceField: Failed to end element.");
342        return false;
343    }
344
345    return true;
346}
347
348static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, SharedBuffer* buffer)
349{
350    return saveArchiveResourceField(writer, tag, buffer->data(), buffer->size());
351}
352
353static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, const String& string)
354{
355    CString utf8String = string.utf8();
356
357    return saveArchiveResourceField(writer, tag, utf8String.data(), utf8String.length());
358}
359
360static bool saveArchiveResource(xmlTextWriterPtr writer, PassRefPtr<ArchiveResource> resource)
361{
362    int result = xmlTextWriterStartElement(writer, archiveResourceTag);
363    if (result < 0) {
364        ALOGD("saveArchiveResource: Failed to start element.");
365        return false;
366    }
367
368    if (!saveArchiveResourceField(writer, urlFieldTag, resource->url().string())
369        || !saveArchiveResourceField(writer, mimeFieldTag, resource->mimeType())
370        || !saveArchiveResourceField(writer, encodingFieldTag, resource->textEncoding())
371        || !saveArchiveResourceField(writer, frameFieldTag, resource->frameName())
372        || !saveArchiveResourceField(writer, dataFieldTag, resource->data()))
373        return false;
374
375    result = xmlTextWriterEndElement(writer);
376    if (result < 0) {
377        ALOGD("saveArchiveResource: Failed to end element.");
378        return false;
379    }
380
381    return true;
382}
383
384static bool saveArchive(xmlTextWriterPtr writer, PassRefPtr<Archive> archive)
385{
386    int result = xmlTextWriterStartElement(writer, archiveTag);
387    if (result < 0) {
388        ALOGD("saveArchive: Failed to start element.");
389        return false;
390    }
391
392    result = xmlTextWriterStartElement(writer, mainResourceTag);
393    if (result < 0) {
394        ALOGD("saveArchive: Failed to start element.");
395        return false;
396    }
397
398    if (!saveArchiveResource(writer, archive->mainResource()))
399        return false;
400
401    result = xmlTextWriterEndElement(writer);
402    if (result < 0) {
403        ALOGD("saveArchive: Failed to end element.");
404        return false;
405    }
406
407    result = xmlTextWriterStartElement(writer, subresourcesTag);
408    if (result < 0) {
409        ALOGD("saveArchive: Failed to start element.");
410        return false;
411    }
412
413    for (Vector<const RefPtr<ArchiveResource> >::iterator subresource = archive->subresources().begin();
414         subresource != archive->subresources().end();
415         subresource++) {
416        if (!saveArchiveResource(writer, *subresource))
417            return false;
418    }
419
420    result = xmlTextWriterEndElement(writer);
421    if (result < 0) {
422        ALOGD("saveArchive: Failed to end element.");
423        return false;
424    }
425
426    result = xmlTextWriterStartElement(writer, subframesTag);
427    if (result < 0) {
428        ALOGD("saveArchive: Failed to start element.");
429        return false;
430    }
431
432    for (Vector<const RefPtr<Archive> >::iterator subframe = archive->subframeArchives().begin();
433            subframe != archive->subframeArchives().end();
434            subframe++) {
435        if (!saveArchive(writer, *subframe))
436            return false;
437    }
438
439    result = xmlTextWriterEndElement(writer);
440    if (result < 0) {
441        ALOGD("saveArchive: Failed to end element.");
442        return true;
443    }
444
445    return true;
446}
447
448bool WebArchiveAndroid::saveWebArchive(xmlTextWriterPtr writer)
449{
450    const char* const defaultXmlVersion = 0;
451    const char* const defaultEncoding = 0;
452    const char* const defaultStandalone = 0;
453
454    int result = xmlTextWriterStartDocument(writer, defaultXmlVersion, defaultEncoding, defaultStandalone);
455    if (result < 0) {
456        ALOGD("saveWebArchive: Failed to start document.");
457        return false;
458    }
459
460    if (!saveArchive(writer, this))
461        return false;
462
463    result = xmlTextWriterEndDocument(writer);
464    if (result< 0) {
465        ALOGD("saveWebArchive: Failed to end document.");
466        return false;
467    }
468
469    return true;
470}
471
472}
473
474#endif // ENABLE(WEB_ARCHIVE)
475