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
42 namespace WebCore {
43
44 static const xmlChar* const archiveTag = BAD_CAST "Archive";
45 static const xmlChar* const archiveResourceTag = BAD_CAST "ArchiveResource";
46 static const xmlChar* const mainResourceTag = BAD_CAST "mainResource";
47 static const xmlChar* const subresourcesTag = BAD_CAST "subresources";
48 static const xmlChar* const subframesTag = BAD_CAST "subframes";
49 static const xmlChar* const urlFieldTag = BAD_CAST "url";
50 static const xmlChar* const mimeFieldTag = BAD_CAST "mimeType";
51 static const xmlChar* const encodingFieldTag = BAD_CAST "textEncoding";
52 static const xmlChar* const frameFieldTag = BAD_CAST "frameName";
53 static const xmlChar* const dataFieldTag = BAD_CAST "data";
54
create(PassRefPtr<ArchiveResource> mainResource,Vector<PassRefPtr<ArchiveResource>> & subresources,Vector<PassRefPtr<Archive>> & subframeArchives)55 PassRefPtr<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
create(Frame * frame)64 PassRefPtr<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
WebArchiveAndroid(PassRefPtr<ArchiveResource> mainResource,Vector<PassRefPtr<ArchiveResource>> & subresources,Vector<PassRefPtr<Archive>> & subframeArchives)79 WebArchiveAndroid::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
loadArchiveResourceField(xmlNodePtr resourceNode,const xmlChar * fieldName,Vector<char> * outputData)98 static 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
loadArchiveResourceFieldBuffer(xmlNodePtr resourceNode,const xmlChar * fieldName)136 static 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
loadArchiveResourceFieldString(xmlNodePtr resourceNode,const xmlChar * fieldName)146 static 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
loadArchiveResourceFieldURL(xmlNodePtr resourceNode,const xmlChar * fieldName)156 static 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
loadArchiveResource(xmlNodePtr resourceNode)166 static 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
loadArchive(xmlNodePtr archiveNode)206 static 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
createArchiveForError()274 static 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
create(SharedBuffer * buffer)286 PassRefPtr<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
saveArchiveResourceField(xmlTextWriterPtr writer,const xmlChar * tag,const char * data,int size)316 static 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
saveArchiveResourceField(xmlTextWriterPtr writer,const xmlChar * tag,SharedBuffer * buffer)348 static bool saveArchiveResourceField(xmlTextWriterPtr writer, const xmlChar* tag, SharedBuffer* buffer)
349 {
350 return saveArchiveResourceField(writer, tag, buffer->data(), buffer->size());
351 }
352
saveArchiveResourceField(xmlTextWriterPtr writer,const xmlChar * tag,const String & string)353 static 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
saveArchiveResource(xmlTextWriterPtr writer,PassRefPtr<ArchiveResource> resource)360 static 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
saveArchive(xmlTextWriterPtr writer,PassRefPtr<Archive> archive)384 static 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
saveWebArchive(xmlTextWriterPtr writer)448 bool 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