1/*
2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "WebArchive.h"
30#import "WebArchiveInternal.h"
31
32#import "WebKitLogging.h"
33#import "WebNSObjectExtras.h"
34#import "WebResourceInternal.h"
35#import "WebTypesInternal.h"
36#import <JavaScriptCore/InitializeThreading.h>
37#import <WebCore/ArchiveResource.h>
38#import <WebCore/LegacyWebArchive.h>
39#import <WebCore/ThreadCheck.h>
40#import <WebCore/WebCoreObjCExtras.h>
41#import <wtf/Threading.h>
42
43using namespace WebCore;
44
45NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type";
46
47static NSString * const WebMainResourceKey = @"WebMainResource";
48static NSString * const WebSubresourcesKey = @"WebSubresources";
49static NSString * const WebSubframeArchivesKey = @"WebSubframeArchives";
50
51@interface WebArchivePrivate : NSObject {
52@public
53    WebResource *cachedMainResource;
54    NSArray *cachedSubresources;
55    NSArray *cachedSubframeArchives;
56@private
57    RefPtr<LegacyWebArchive> coreArchive;
58}
59
60- (id)initWithCoreArchive:(PassRefPtr<LegacyWebArchive>)coreArchive;
61- (LegacyWebArchive*)coreArchive;
62- (void)setCoreArchive:(PassRefPtr<LegacyWebArchive>)newCoreArchive;
63@end
64
65@implementation WebArchivePrivate
66
67+ (void)initialize
68{
69    JSC::initializeThreading();
70    WTF::initializeMainThreadToProcessMainThread();
71#ifndef BUILDING_ON_TIGER
72    WebCoreObjCFinalizeOnMainThread(self);
73#endif
74}
75
76- (id)init
77{
78    self = [super init];
79    if (!self)
80        return nil;
81    coreArchive = LegacyWebArchive::create();
82    return self;
83}
84
85- (id)initWithCoreArchive:(PassRefPtr<LegacyWebArchive>)_coreArchive
86{
87    self = [super init];
88    if (!self || !_coreArchive) {
89        [self release];
90        return nil;
91    }
92    coreArchive = _coreArchive;
93    return self;
94}
95
96- (LegacyWebArchive*)coreArchive
97{
98    return coreArchive.get();
99}
100
101- (void)setCoreArchive:(PassRefPtr<LegacyWebArchive>)newCoreArchive
102{
103    ASSERT(coreArchive);
104    ASSERT(newCoreArchive);
105    coreArchive = newCoreArchive;
106}
107
108- (void)dealloc
109{
110    if (WebCoreObjCScheduleDeallocateOnMainThread([WebArchivePrivate class], self))
111        return;
112
113    [cachedMainResource release];
114    [cachedSubresources release];
115    [cachedSubframeArchives release];
116
117    [super dealloc];
118}
119
120@end
121
122@implementation WebArchive
123
124- (id)init
125{
126    WebCoreThreadViolationCheckRoundTwo();
127
128    self = [super init];
129    if (!self)
130        return nil;
131    _private = [[WebArchivePrivate alloc] init];
132    return self;
133}
134
135static BOOL isArrayOfClass(id object, Class elementClass)
136{
137    if (![object isKindOfClass:[NSArray class]])
138        return NO;
139    NSArray *array = (NSArray *)object;
140    NSUInteger count = [array count];
141    for (NSUInteger i = 0; i < count; ++i)
142        if (![[array objectAtIndex:i] isKindOfClass:elementClass])
143            return NO;
144    return YES;
145}
146
147- (id)initWithMainResource:(WebResource *)mainResource subresources:(NSArray *)subresources subframeArchives:(NSArray *)subframeArchives
148{
149#ifdef MAIL_THREAD_WORKAROUND
150    if (needMailThreadWorkaround())
151        return [[self _webkit_invokeOnMainThread] initWithMainResource:mainResource subresources:subresources subframeArchives:subframeArchives];
152#endif
153
154    WebCoreThreadViolationCheckRoundTwo();
155
156    self = [super init];
157    if (!self)
158        return nil;
159
160    _private = [[WebArchivePrivate alloc] init];
161
162    _private->cachedMainResource = [mainResource retain];
163    if (!_private->cachedMainResource) {
164        [self release];
165        return nil;
166    }
167
168    if (!subresources || isArrayOfClass(subresources, [WebResource class]))
169        _private->cachedSubresources = [subresources retain];
170    else {
171        [self release];
172        return nil;
173    }
174
175    if (!subframeArchives || isArrayOfClass(subframeArchives, [WebArchive class]))
176        _private->cachedSubframeArchives = [subframeArchives retain];
177    else {
178        [self release];
179        return nil;
180    }
181
182    RefPtr<ArchiveResource> coreMainResource = mainResource ? [mainResource _coreResource] : 0;
183
184    Vector<PassRefPtr<ArchiveResource> > coreResources;
185    NSEnumerator *enumerator = [subresources objectEnumerator];
186    WebResource *subresource;
187    while ((subresource = [enumerator nextObject]) != nil)
188        coreResources.append([subresource _coreResource]);
189
190    Vector<PassRefPtr<LegacyWebArchive> > coreArchives;
191    enumerator = [subframeArchives objectEnumerator];
192    WebArchive *subframeArchive;
193    while ((subframeArchive = [enumerator nextObject]) != nil)
194        coreArchives.append([subframeArchive->_private coreArchive]);
195
196    [_private setCoreArchive:LegacyWebArchive::create(coreMainResource.release(), coreResources, coreArchives)];
197    if (![_private coreArchive]) {
198        [self release];
199        return nil;
200    }
201
202    return self;
203}
204
205- (id)initWithData:(NSData *)data
206{
207    WebCoreThreadViolationCheckRoundTwo();
208
209    self = [super init];
210    if (!self)
211        return nil;
212
213#if !LOG_DISABLED
214    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
215#endif
216
217    _private = [[WebArchivePrivate alloc] init];
218    RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::create(SharedBuffer::wrapNSData(data).get());
219    if (!coreArchive) {
220        [self release];
221        return nil;
222    }
223
224    [_private setCoreArchive:coreArchive.release()];
225
226#if !LOG_DISABLED
227    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
228    CFAbsoluteTime duration = end - start;
229#endif
230    LOG(Timing, "Parsing web archive with [NSPropertyListSerialization propertyListFromData::::] took %f seconds", duration);
231
232    return self;
233}
234
235- (id)initWithCoder:(NSCoder *)decoder
236{
237    WebResource *mainResource = nil;
238    NSArray *subresources = nil;
239    NSArray *subframeArchives = nil;
240
241    @try {
242        id object = [decoder decodeObjectForKey:WebMainResourceKey];
243        if ([object isKindOfClass:[WebResource class]])
244            mainResource = object;
245        object = [decoder decodeObjectForKey:WebSubresourcesKey];
246        if (isArrayOfClass(object, [WebResource class]))
247            subresources = object;
248        object = [decoder decodeObjectForKey:WebSubframeArchivesKey];
249        if (isArrayOfClass(object, [WebArchive class]))
250            subframeArchives = object;
251    } @catch(id) {
252        [self release];
253        return nil;
254    }
255
256    return [self initWithMainResource:mainResource subresources:subresources subframeArchives:subframeArchives];
257}
258
259- (void)encodeWithCoder:(NSCoder *)encoder
260{
261    [encoder encodeObject:[self mainResource] forKey:WebMainResourceKey];
262    [encoder encodeObject:[self subresources] forKey:WebSubresourcesKey];
263    [encoder encodeObject:[self subframeArchives] forKey:WebSubframeArchivesKey];
264}
265
266- (void)dealloc
267{
268    [_private release];
269    [super dealloc];
270}
271
272- (id)copyWithZone:(NSZone *)zone
273{
274    return [self retain];
275}
276
277- (WebResource *)mainResource
278{
279#ifdef MAIL_THREAD_WORKAROUND
280    if (needMailThreadWorkaround())
281        return [[self _webkit_invokeOnMainThread] mainResource];
282#endif
283
284    WebCoreThreadViolationCheckRoundTwo();
285
286    // Currently from WebKit API perspective, WebArchives are entirely immutable once created
287    // If they ever become mutable, we'll need to rethink this.
288    if (!_private->cachedMainResource) {
289        LegacyWebArchive* coreArchive = [_private coreArchive];
290        if (coreArchive)
291            _private->cachedMainResource = [[WebResource alloc] _initWithCoreResource:coreArchive->mainResource()];
292    }
293
294    return [[_private->cachedMainResource retain] autorelease];
295}
296
297- (NSArray *)subresources
298{
299#ifdef MAIL_THREAD_WORKAROUND
300    if (needMailThreadWorkaround())
301        return [[self _webkit_invokeOnMainThread] subresources];
302#endif
303
304    WebCoreThreadViolationCheckRoundTwo();
305
306    // Currently from WebKit API perspective, WebArchives are entirely immutable once created
307    // If they ever become mutable, we'll need to rethink this.
308    if (!_private->cachedSubresources) {
309        LegacyWebArchive* coreArchive = [_private coreArchive];
310        if (!coreArchive)
311            _private->cachedSubresources = [[NSArray alloc] init];
312        else {
313            const Vector<RefPtr<ArchiveResource> >& subresources(coreArchive->subresources());
314            NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:subresources.size()];
315            _private->cachedSubresources = mutableArray;
316            for (unsigned i = 0; i < subresources.size(); ++i) {
317                WebResource *resource = [[WebResource alloc] _initWithCoreResource:subresources[i].get()];
318                if (resource) {
319                    [mutableArray addObject:resource];
320                    [resource release];
321                }
322            }
323        }
324    }
325    // Maintain the WebKit 3 behavior of this API, which is documented and
326    // relied upon by some clients, of returning nil if there are no subresources.
327    return [_private->cachedSubresources count] ? [[_private->cachedSubresources retain] autorelease] : nil;
328}
329
330- (NSArray *)subframeArchives
331{
332#ifdef MAIL_THREAD_WORKAROUND
333    if (needMailThreadWorkaround())
334        return [[self _webkit_invokeOnMainThread] subframeArchives];
335#endif
336
337    WebCoreThreadViolationCheckRoundTwo();
338
339    // Currently from WebKit API perspective, WebArchives are entirely immutable once created
340    // If they ever become mutable, we'll need to rethink this.
341    if (!_private->cachedSubframeArchives) {
342        LegacyWebArchive* coreArchive = [_private coreArchive];
343        if (!coreArchive)
344            _private->cachedSubframeArchives = [[NSArray alloc] init];
345        else {
346            const Vector<RefPtr<Archive> >& subframeArchives(coreArchive->subframeArchives());
347            NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:subframeArchives.size()];
348            _private->cachedSubframeArchives = mutableArray;
349            for (unsigned i = 0; i < subframeArchives.size(); ++i) {
350                WebArchive *archive = [[WebArchive alloc] _initWithCoreLegacyWebArchive:(LegacyWebArchive *)subframeArchives[i].get()];
351                [mutableArray addObject:archive];
352                [archive release];
353            }
354        }
355    }
356
357    return [[_private->cachedSubframeArchives retain] autorelease];
358}
359
360- (NSData *)data
361{
362    WebCoreThreadViolationCheckRoundTwo();
363
364#if !LOG_DISABLED
365    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
366#endif
367
368    RetainPtr<CFDataRef> data = [_private coreArchive]->rawDataRepresentation();
369
370#if !LOG_DISABLED
371    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
372    CFAbsoluteTime duration = end - start;
373#endif
374    LOG(Timing, "Serializing web archive to raw CFPropertyList data took %f seconds", duration);
375
376    return [[(NSData *)data.get() retain] autorelease];
377}
378
379@end
380
381@implementation WebArchive (WebInternal)
382
383- (id)_initWithCoreLegacyWebArchive:(PassRefPtr<WebCore::LegacyWebArchive>)coreLegacyWebArchive
384{
385    WebCoreThreadViolationCheckRoundTwo();
386
387    self = [super init];
388    if (!self)
389        return nil;
390
391    _private = [[WebArchivePrivate alloc] initWithCoreArchive:coreLegacyWebArchive];
392    if (!_private) {
393        [self release];
394        return nil;
395    }
396
397    return self;
398}
399
400- (WebCore::LegacyWebArchive *)_coreLegacyWebArchive
401{
402    WebCoreThreadViolationCheckRoundTwo();
403
404    return [_private coreArchive];
405}
406
407@end
408