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 "WebResourceInternal.h"
30
31#import "WebFrameInternal.h"
32#import "WebKitLogging.h"
33#import "WebKitVersionChecks.h"
34#import "WebNSDictionaryExtras.h"
35#import "WebNSObjectExtras.h"
36#import "WebNSURLExtras.h"
37#import <JavaScriptCore/InitializeThreading.h>
38#import <JavaScriptCore/PassRefPtr.h>
39#import <WebCore/ArchiveResource.h>
40#import <WebCore/LegacyWebArchive.h>
41#import <WebCore/RuntimeApplicationChecks.h>
42#import <WebCore/TextEncoding.h>
43#import <WebCore/ThreadCheck.h>
44#import <WebCore/WebCoreObjCExtras.h>
45#import <WebCore/WebCoreURLResponse.h>
46#import <wtf/Threading.h>
47
48using namespace WebCore;
49
50static NSString * const WebResourceDataKey =              @"WebResourceData";
51static NSString * const WebResourceFrameNameKey =         @"WebResourceFrameName";
52static NSString * const WebResourceMIMETypeKey =          @"WebResourceMIMEType";
53static NSString * const WebResourceURLKey =               @"WebResourceURL";
54static NSString * const WebResourceTextEncodingNameKey =  @"WebResourceTextEncodingName";
55static NSString * const WebResourceResponseKey =          @"WebResourceResponse";
56
57@interface WebResourcePrivate : NSObject {
58@public
59    ArchiveResource* coreResource;
60}
61- (id)initWithCoreResource:(PassRefPtr<ArchiveResource>)coreResource;
62@end
63
64@implementation WebResourcePrivate
65
66+ (void)initialize
67{
68    JSC::initializeThreading();
69    WTF::initializeMainThreadToProcessMainThread();
70#ifndef BUILDING_ON_TIGER
71    WebCoreObjCFinalizeOnMainThread(self);
72#endif
73}
74
75- (id)init
76{
77    return [super init];
78}
79
80- (id)initWithCoreResource:(PassRefPtr<ArchiveResource>)passedResource
81{
82    self = [super init];
83    if (!self)
84        return nil;
85    // Acquire the PassRefPtr<>'s ref as our own manual ref
86    coreResource = passedResource.releaseRef();
87    return self;
88}
89
90- (void)dealloc
91{
92    if (WebCoreObjCScheduleDeallocateOnMainThread([WebResourcePrivate class], self))
93        return;
94
95    if (coreResource)
96        coreResource->deref();
97    [super dealloc];
98}
99
100- (void)finalize
101{
102    if (coreResource)
103        coreResource->deref();
104    [super finalize];
105}
106
107@end
108
109@implementation WebResource
110
111- (id)init
112{
113    self = [super init];
114    if (!self)
115        return nil;
116    _private = [[WebResourcePrivate alloc] init];
117    return self;
118}
119
120- (id)initWithData:(NSData *)data URL:(NSURL *)URL MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName frameName:(NSString *)frameName
121{
122    return [self _initWithData:data URL:URL MIMEType:MIMEType textEncodingName:textEncodingName frameName:frameName response:nil copyData:YES];
123}
124
125- (id)initWithCoder:(NSCoder *)decoder
126{
127    WebCoreThreadViolationCheckRoundTwo();
128
129    self = [super init];
130    if (!self)
131        return nil;
132
133    NSData *data = nil;
134    NSURL *url = nil;
135    NSString *mimeType = nil, *textEncoding = nil, *frameName = nil;
136    NSURLResponse *response = nil;
137
138    @try {
139        id object = [decoder decodeObjectForKey:WebResourceDataKey];
140        if ([object isKindOfClass:[NSData class]])
141            data = object;
142        object = [decoder decodeObjectForKey:WebResourceURLKey];
143        if ([object isKindOfClass:[NSURL class]])
144            url = object;
145        object = [decoder decodeObjectForKey:WebResourceMIMETypeKey];
146        if ([object isKindOfClass:[NSString class]])
147            mimeType = object;
148        object = [decoder decodeObjectForKey:WebResourceTextEncodingNameKey];
149        if ([object isKindOfClass:[NSString class]])
150            textEncoding = object;
151        object = [decoder decodeObjectForKey:WebResourceFrameNameKey];
152        if ([object isKindOfClass:[NSString class]])
153            frameName = object;
154        object = [decoder decodeObjectForKey:WebResourceResponseKey];
155        if ([object isKindOfClass:[NSURLResponse class]])
156            response = object;
157    } @catch(id) {
158        [self release];
159        return nil;
160    }
161
162    _private = [[WebResourcePrivate alloc] initWithCoreResource:ArchiveResource::create(SharedBuffer::wrapNSData(data), url, mimeType, textEncoding, frameName, response)];
163
164    return self;
165}
166
167- (void)encodeWithCoder:(NSCoder *)encoder
168{
169    ArchiveResource *resource = _private->coreResource;
170
171    NSData *data = nil;
172    NSURL *url = nil;
173    NSString *mimeType = nil, *textEncoding = nil, *frameName = nil;
174    NSURLResponse *response = nil;
175
176    if (resource) {
177        if (resource->data())
178            data = [resource->data()->createNSData() autorelease];
179        url = resource->url();
180        mimeType = resource->mimeType();
181        textEncoding = resource->textEncoding();
182        frameName = resource->frameName();
183        response = resource->response().nsURLResponse();
184    }
185    [encoder encodeObject:data forKey:WebResourceDataKey];
186    [encoder encodeObject:url forKey:WebResourceURLKey];
187    [encoder encodeObject:mimeType forKey:WebResourceMIMETypeKey];
188    [encoder encodeObject:textEncoding forKey:WebResourceTextEncodingNameKey];
189    [encoder encodeObject:frameName forKey:WebResourceFrameNameKey];
190    [encoder encodeObject:response forKey:WebResourceResponseKey];
191}
192
193- (void)dealloc
194{
195    [_private release];
196    [super dealloc];
197}
198
199- (id)copyWithZone:(NSZone *)zone
200{
201    return [self retain];
202}
203
204- (NSData *)data
205{
206#ifdef MAIL_THREAD_WORKAROUND
207    if (needMailThreadWorkaround())
208        return [[self _webkit_invokeOnMainThread] data];
209#endif
210
211    WebCoreThreadViolationCheckRoundTwo();
212
213    if (!_private->coreResource)
214        return nil;
215    if (!_private->coreResource->data())
216        return nil;
217    return [_private->coreResource->data()->createNSData() autorelease];
218}
219
220- (NSURL *)URL
221{
222#ifdef MAIL_THREAD_WORKAROUND
223    if (needMailThreadWorkaround())
224        return [[self _webkit_invokeOnMainThread] URL];
225#endif
226
227    WebCoreThreadViolationCheckRoundTwo();
228
229    if (!_private->coreResource)
230        return nil;
231    NSURL *url = _private->coreResource->url();
232    return url;
233}
234
235- (NSString *)MIMEType
236{
237#ifdef MAIL_THREAD_WORKAROUND
238    if (needMailThreadWorkaround())
239        return [[self _webkit_invokeOnMainThread] MIMEType];
240#endif
241
242    WebCoreThreadViolationCheckRoundTwo();
243
244    if (!_private->coreResource)
245        return nil;
246    NSString *mimeType = _private->coreResource->mimeType();
247    return mimeType;
248}
249
250- (NSString *)textEncodingName
251{
252#ifdef MAIL_THREAD_WORKAROUND
253    if (needMailThreadWorkaround())
254        return [[self _webkit_invokeOnMainThread] textEncodingName];
255#endif
256
257    WebCoreThreadViolationCheckRoundTwo();
258
259    if (!_private->coreResource)
260        return nil;
261    NSString *textEncodingName = _private->coreResource->textEncoding();
262    return textEncodingName;
263}
264
265- (NSString *)frameName
266{
267#ifdef MAIL_THREAD_WORKAROUND
268    if (needMailThreadWorkaround())
269        return [[self _webkit_invokeOnMainThread] frameName];
270#endif
271
272    WebCoreThreadViolationCheckRoundTwo();
273
274    if (!_private->coreResource)
275        return nil;
276    NSString *frameName = _private->coreResource->frameName();
277    return frameName;
278}
279
280- (NSString *)description
281{
282    return [NSString stringWithFormat:@"<%@ %@>", [self className], [self URL]];
283}
284
285@end
286
287@implementation WebResource (WebResourceInternal)
288
289- (id)_initWithCoreResource:(PassRefPtr<ArchiveResource>)coreResource
290{
291    self = [super init];
292    if (!self)
293        return nil;
294
295    ASSERT(coreResource);
296
297    // WebResources should not be init'ed with nil data, and doing so breaks certain uses of NSHTMLReader
298    // See <rdar://problem/5820157> for more info
299    if (!coreResource->data()) {
300        [self release];
301        return nil;
302    }
303
304    _private = [[WebResourcePrivate alloc] initWithCoreResource:coreResource];
305
306    return self;
307}
308
309- (WebCore::ArchiveResource *)_coreResource
310{
311    return _private->coreResource;
312}
313
314@end
315
316@implementation WebResource (WebResourcePrivate)
317
318// SPI for Mail (5066325)
319// FIXME: This "ignoreWhenUnarchiving" concept is an ugly one - can we find a cleaner solution for those who need this SPI?
320- (void)_ignoreWhenUnarchiving
321{
322#ifdef MAIL_THREAD_WORKAROUND
323    if (needMailThreadWorkaround()) {
324        [[self _webkit_invokeOnMainThread] _ignoreWhenUnarchiving];
325        return;
326    }
327#endif
328
329    WebCoreThreadViolationCheckRoundTwo();
330
331    if (!_private->coreResource)
332        return;
333    _private->coreResource->ignoreWhenUnarchiving();
334}
335
336- (id)_initWithData:(NSData *)data
337                URL:(NSURL *)URL
338           MIMEType:(NSString *)MIMEType
339   textEncodingName:(NSString *)textEncodingName
340          frameName:(NSString *)frameName
341           response:(NSURLResponse *)response
342           copyData:(BOOL)copyData
343{
344#ifdef MAIL_THREAD_WORKAROUND
345    if (needMailThreadWorkaround())
346        return [[self _webkit_invokeOnMainThread] _initWithData:data URL:URL MIMEType:MIMEType textEncodingName:textEncodingName frameName:frameName response:response copyData:copyData];
347#endif
348
349    WebCoreThreadViolationCheckRoundTwo();
350
351    self = [super init];
352    if (!self)
353        return nil;
354
355    if (!data || !URL || !MIMEType) {
356        [self release];
357        return nil;
358    }
359
360    _private = [[WebResourcePrivate alloc] initWithCoreResource:ArchiveResource::create(SharedBuffer::wrapNSData(copyData ? [[data copy] autorelease] : data), URL, MIMEType, textEncodingName, frameName, response)];
361
362    return self;
363}
364
365- (id)_initWithData:(NSData *)data URL:(NSURL *)URL response:(NSURLResponse *)response
366{
367    // Pass NO for copyData since the data doesn't need to be copied since we know that callers will no longer modify it.
368    // Copying it will also cause a performance regression.
369    return [self _initWithData:data
370                           URL:URL
371                      MIMEType:[response MIMEType]
372              textEncodingName:[response textEncodingName]
373                     frameName:nil
374                      response:response
375                      copyData:NO];
376}
377
378- (NSString *)_suggestedFilename
379{
380#ifdef MAIL_THREAD_WORKAROUND
381    if (needMailThreadWorkaround())
382        return [[self _webkit_invokeOnMainThread] _suggestedFilename];
383#endif
384
385    WebCoreThreadViolationCheckRoundTwo();
386
387    if (!_private->coreResource)
388        return nil;
389    NSString *suggestedFilename = _private->coreResource->response().suggestedFilename();
390    return suggestedFilename;
391}
392
393- (NSFileWrapper *)_fileWrapperRepresentation
394{
395    NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[self data]] autorelease];
396    NSString *filename = [self _suggestedFilename];
397    if (!filename || ![filename length])
398        filename = [[self URL] _webkit_suggestedFilenameWithMIMEType:[self MIMEType]];
399    [wrapper setPreferredFilename:filename];
400    return wrapper;
401}
402
403- (NSURLResponse *)_response
404{
405#ifdef MAIL_THREAD_WORKAROUND
406    if (needMailThreadWorkaround())
407        return [[self _webkit_invokeOnMainThread] _response];
408#endif
409
410    WebCoreThreadViolationCheckRoundTwo();
411
412    NSURLResponse *response = nil;
413    if (_private->coreResource)
414        response = _private->coreResource->response().nsURLResponse();
415    return response ? response : [[[NSURLResponse alloc] init] autorelease];
416}
417
418- (NSString *)_stringValue
419{
420#ifdef MAIL_THREAD_WORKAROUND
421    if (needMailThreadWorkaround())
422        return [[self _webkit_invokeOnMainThread] _stringValue];
423#endif
424
425    WebCoreThreadViolationCheckRoundTwo();
426
427    WebCore::TextEncoding encoding;
428    if (_private->coreResource)
429        encoding = _private->coreResource->textEncoding();
430    if (!encoding.isValid())
431        encoding = WindowsLatin1Encoding();
432
433    SharedBuffer* coreData = _private->coreResource ? _private->coreResource->data() : 0;
434    return encoding.decode(reinterpret_cast<const char*>(coreData ? coreData->data() : 0), coreData ? coreData->size() : 0);
435}
436
437@end
438
439#ifdef MAIL_THREAD_WORKAROUND
440
441static const double newMailBundleVersion = 1050.0;
442
443@implementation WebResource (WebMailThreadWorkaround)
444
445+ (BOOL)_needMailThreadWorkaroundIfCalledOffMainThread
446{
447    static BOOL isOldMail = applicationIsAppleMail() && [[[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey] doubleValue] < newMailBundleVersion;
448    return isOldMail;
449}
450
451@end
452
453#endif
454