1/*
2 * Copyright (C) 2005, 2006, 2007, 2008, 2012 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 "WebDataSource.h"
30
31#import "WebArchive.h"
32#import "WebArchiveInternal.h"
33#import "WebDataSourceInternal.h"
34#import "WebDocument.h"
35#import "WebDocumentLoaderMac.h"
36#import "WebFrameInternal.h"
37#import "WebFrameLoadDelegate.h"
38#import "WebFrameLoaderClient.h"
39#import "WebHTMLRepresentation.h"
40#import "WebKitErrorsPrivate.h"
41#import "WebKitLogging.h"
42#import "WebKitStatisticsPrivate.h"
43#import "WebKitNSStringExtras.h"
44#import "WebNSURLExtras.h"
45#import "WebNSURLRequestExtras.h"
46#import "WebPDFRepresentation.h"
47#import "WebResourceInternal.h"
48#import "WebResourceLoadDelegate.h"
49#import "WebViewInternal.h"
50#import <WebCore/ApplicationCacheStorage.h>
51#import <WebCore/FrameLoader.h>
52#import <WebCore/KURL.h>
53#import <WebCore/LegacyWebArchive.h>
54#import <WebCore/MIMETypeRegistry.h>
55#import <WebCore/ResourceBuffer.h>
56#import <WebCore/ResourceRequest.h>
57#import <WebCore/RunLoop.h>
58#import <WebCore/SharedBuffer.h>
59#import <WebCore/WebCoreObjCExtras.h>
60#import <WebCore/WebCoreURLResponse.h>
61#import <WebKit/DOMHTML.h>
62#import <WebKit/DOMPrivate.h>
63#import <runtime/InitializeThreading.h>
64#import <wtf/Assertions.h>
65#import <wtf/MainThread.h>
66#import <wtf/RefPtr.h>
67#import <wtf/RetainPtr.h>
68
69using namespace WebCore;
70
71class WebDataSourcePrivate
72{
73public:
74    WebDataSourcePrivate(PassRefPtr<WebDocumentLoaderMac> loader)
75        : loader(loader)
76        , representationFinishedLoading(NO)
77        , includedInWebKitStatistics(NO)
78    {
79        ASSERT(this->loader);
80    }
81    ~WebDataSourcePrivate()
82    {
83        if (loader) {
84            ASSERT(!loader->isLoading());
85            loader->detachDataSource();
86        }
87    }
88
89    RefPtr<WebDocumentLoaderMac> loader;
90    RetainPtr<id<WebDocumentRepresentation> > representation;
91    BOOL representationFinishedLoading;
92    BOOL includedInWebKitStatistics;
93};
94
95static inline WebDataSourcePrivate* toPrivate(void* privateAttribute)
96{
97    return reinterpret_cast<WebDataSourcePrivate*>(privateAttribute);
98}
99
100@interface WebDataSource (WebFileInternal)
101@end
102
103@implementation WebDataSource (WebFileInternal)
104
105- (void)_setRepresentation:(id<WebDocumentRepresentation>)representation
106{
107    toPrivate(_private)->representation = representation;
108    toPrivate(_private)->representationFinishedLoading = NO;
109}
110
111static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
112{
113    NSEnumerator *enumerator = [supportTypes objectEnumerator];
114    ASSERT(enumerator != nil);
115    NSString *mime = nil;
116    while ((mime = [enumerator nextObject]) != nil) {
117        // Don't clobber previously-registered classes.
118        if ([allTypes objectForKey:mime] == nil)
119            [allTypes setObject:objCClass forKey:mime];
120    }
121}
122
123+ (Class)_representationClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins
124{
125    Class repClass;
126    return [WebView _viewClass:nil andRepresentationClass:&repClass forMIMEType:MIMEType allowingPlugins:allowPlugins] ? repClass : nil;
127}
128@end
129
130@implementation WebDataSource (WebPrivate)
131
132+ (void)initialize
133{
134    if (self == [WebDataSource class]) {
135        JSC::initializeThreading();
136        WTF::initializeMainThreadToProcessMainThread();
137        WebCore::RunLoop::initializeMainRunLoop();
138        WebCoreObjCFinalizeOnMainThread(self);
139    }
140}
141
142- (NSError *)_mainDocumentError
143{
144    return toPrivate(_private)->loader->mainDocumentError();
145}
146
147- (void)_addSubframeArchives:(NSArray *)subframeArchives
148{
149    // FIXME: This SPI is poor, poor design.  Can we come up with another solution for those who need it?
150    NSEnumerator *enumerator = [subframeArchives objectEnumerator];
151    WebArchive *archive;
152    while ((archive = [enumerator nextObject]) != nil)
153        toPrivate(_private)->loader->addAllArchiveResources([archive _coreLegacyWebArchive]);
154}
155
156- (NSFileWrapper *)_fileWrapperForURL:(NSURL *)URL
157{
158    if ([URL isFileURL]) {
159        NSString *path = [[URL path] stringByResolvingSymlinksInPath];
160        return [[[NSFileWrapper alloc] initWithPath:path] autorelease];
161    }
162
163    WebResource *resource = [self subresourceForURL:URL];
164    if (resource)
165        return [resource _fileWrapperRepresentation];
166
167    NSCachedURLResponse *cachedResponse = [[self _webView] _cachedResponseForURL:URL];
168    if (cachedResponse) {
169        NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease];
170        [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]];
171        return wrapper;
172    }
173
174    return nil;
175}
176
177- (NSString *)_responseMIMEType
178{
179    return [[self response] MIMEType];
180}
181
182- (BOOL)_transferApplicationCache:(NSString*)destinationBundleIdentifier
183{
184    if (!toPrivate(_private)->loader)
185        return NO;
186
187    NSString *cacheDir = [NSString _webkit_localCacheDirectoryWithBundleIdentifier:destinationBundleIdentifier];
188
189    return ApplicationCacheStorage::storeCopyOfCache(cacheDir, toPrivate(_private)->loader->applicationCacheHost());
190}
191
192- (void)_setDeferMainResourceDataLoad:(BOOL)flag
193{
194    if (!toPrivate(_private)->loader)
195        return;
196
197    toPrivate(_private)->loader->setDeferMainResourceDataLoad(flag);
198}
199
200@end
201
202@implementation WebDataSource (WebInternal)
203
204- (void)_finishedLoading
205{
206    toPrivate(_private)->representationFinishedLoading = YES;
207    [[self representation] finishedLoadingWithDataSource:self];
208}
209
210- (void)_receivedData:(NSData *)data
211{
212    // protect self temporarily, as the bridge receivedData call could remove our last ref
213    RetainPtr<WebDataSource*> protect(self);
214
215    [[self representation] receivedData:data withDataSource:self];
216    [[[[self webFrame] frameView] documentView] dataSourceUpdated:self];
217}
218
219- (void)_setMainDocumentError:(NSError *)error
220{
221    if (!toPrivate(_private)->representationFinishedLoading) {
222        toPrivate(_private)->representationFinishedLoading = YES;
223        [[self representation] receivedError:error withDataSource:self];
224    }
225}
226
227- (void)_revertToProvisionalState
228{
229    [self _setRepresentation:nil];
230}
231
232+ (NSMutableDictionary *)_repTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
233{
234    static NSMutableDictionary *repTypes = nil;
235    static BOOL addedImageTypes = NO;
236
237    if (!repTypes) {
238        repTypes = [[NSMutableDictionary alloc] init];
239        addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedNonImageMIMETypes]);
240
241        // Since this is a "secret default" we don't both registering it.
242        BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
243        if (!omitPDFSupport)
244            addTypesFromClass(repTypes, [WebPDFRepresentation class], [WebPDFRepresentation supportedMIMETypes]);
245    }
246
247    if (!addedImageTypes && !allowImageTypeOmission) {
248        addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedImageMIMETypes]);
249        addedImageTypes = YES;
250    }
251
252    return repTypes;
253}
254
255- (void)_replaceSelectionWithArchive:(WebArchive *)archive selectReplacement:(BOOL)selectReplacement
256{
257    DOMDocumentFragment *fragment = [self _documentFragmentWithArchive:archive];
258    if (fragment)
259        [[self webFrame] _replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:NO matchStyle:NO];
260}
261
262// FIXME: There are few reasons why this method and many of its related methods can't be pushed entirely into WebCore in the future.
263- (DOMDocumentFragment *)_documentFragmentWithArchive:(WebArchive *)archive
264{
265    ASSERT(archive);
266    WebResource *mainResource = [archive mainResource];
267    if (mainResource) {
268        NSString *MIMEType = [mainResource MIMEType];
269        if ([WebView canShowMIMETypeAsHTML:MIMEType]) {
270            NSString *markupString = [[NSString alloc] initWithData:[mainResource data] encoding:NSUTF8StringEncoding];
271            // FIXME: seems poor form to do this as a side effect of getting a document fragment
272            if (toPrivate(_private)->loader)
273                toPrivate(_private)->loader->addAllArchiveResources([archive _coreLegacyWebArchive]);
274
275            DOMDocumentFragment *fragment = [[self webFrame] _documentFragmentWithMarkupString:markupString baseURLString:[[mainResource URL] _web_originalDataAsString]];
276            [markupString release];
277            return fragment;
278        } else if (MIMETypeRegistry::isSupportedImageMIMEType(MIMEType)) {
279            return [self _documentFragmentWithImageResource:mainResource];
280
281        }
282    }
283    return nil;
284}
285
286- (DOMDocumentFragment *)_documentFragmentWithImageResource:(WebResource *)resource
287{
288    DOMElement *imageElement = [self _imageElementWithImageResource:resource];
289    if (!imageElement)
290        return 0;
291    DOMDocumentFragment *fragment = [[[self webFrame] DOMDocument] createDocumentFragment];
292    [fragment appendChild:imageElement];
293    return fragment;
294}
295
296- (DOMElement *)_imageElementWithImageResource:(WebResource *)resource
297{
298    if (!resource)
299        return 0;
300
301    [self addSubresource:resource];
302
303    DOMElement *imageElement = [[[self webFrame] DOMDocument] createElement:@"img"];
304
305    // FIXME: calling _web_originalDataAsString on a file URL returns an absolute path. Workaround this.
306    NSURL *URL = [resource URL];
307    [imageElement setAttribute:@"src" value:[URL isFileURL] ? [URL absoluteString] : [URL _web_originalDataAsString]];
308
309    return imageElement;
310}
311
312// May return nil if not initialized with a URL.
313- (NSURL *)_URL
314{
315    const KURL& url = toPrivate(_private)->loader->url();
316    if (url.isEmpty())
317        return nil;
318    return url;
319}
320
321- (WebView *)_webView
322{
323    return [[self webFrame] webView];
324}
325
326- (BOOL)_isDocumentHTML
327{
328    NSString *MIMEType = [self _responseMIMEType];
329    return [WebView canShowMIMETypeAsHTML:MIMEType];
330}
331
332- (void)_makeRepresentation
333{
334    Class repClass = [[self class] _representationClassForMIMEType:[self _responseMIMEType] allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]];
335
336    // Check if the data source was already bound?
337    if (![[self representation] isKindOfClass:repClass]) {
338        id newRep = repClass != nil ? [[repClass alloc] init] : nil;
339        [self _setRepresentation:(id <WebDocumentRepresentation>)newRep];
340        [newRep release];
341    }
342
343    id<WebDocumentRepresentation> representation = toPrivate(_private)->representation.get();
344    [representation setDataSource:self];
345}
346
347- (DocumentLoader*)_documentLoader
348{
349    return toPrivate(_private)->loader.get();
350}
351
352- (id)_initWithDocumentLoader:(PassRefPtr<WebDocumentLoaderMac>)loader
353{
354    self = [super init];
355    if (!self)
356        return nil;
357
358    ASSERT(loader);
359    _private = static_cast<void*>(new WebDataSourcePrivate(loader));
360
361    LOG(Loading, "creating datasource for %@", static_cast<NSURL *>(toPrivate(_private)->loader->request().url()));
362
363    if ((toPrivate(_private)->includedInWebKitStatistics = [[self webFrame] _isIncludedInWebKitStatistics]))
364        ++WebDataSourceCount;
365
366    return self;
367}
368
369@end
370
371@implementation WebDataSource
372
373- (id)initWithRequest:(NSURLRequest *)request
374{
375    return [self _initWithDocumentLoader:WebDocumentLoaderMac::create(request, SubstituteData())];
376}
377
378- (void)dealloc
379{
380    if (WebCoreObjCScheduleDeallocateOnMainThread([WebDataSource class], self))
381        return;
382
383    if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics)
384        --WebDataSourceCount;
385
386    delete toPrivate(_private);
387
388    [super dealloc];
389}
390
391- (void)finalize
392{
393    ASSERT_MAIN_THREAD();
394
395    if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics)
396        --WebDataSourceCount;
397
398    delete toPrivate(_private);
399
400    [super finalize];
401}
402
403- (NSData *)data
404{
405    RefPtr<ResourceBuffer> mainResourceData = toPrivate(_private)->loader->mainResourceData();
406    if (!mainResourceData)
407        return nil;
408    return [mainResourceData->createNSData() autorelease];
409}
410
411- (id <WebDocumentRepresentation>)representation
412{
413    return toPrivate(_private)->representation.get();
414}
415
416- (WebFrame *)webFrame
417{
418    if (Frame* frame = toPrivate(_private)->loader->frame())
419        return kit(frame);
420
421    return nil;
422}
423
424- (NSURLRequest *)initialRequest
425{
426    return toPrivate(_private)->loader->originalRequest().nsURLRequest(UpdateHTTPBody);
427}
428
429- (NSMutableURLRequest *)request
430{
431    FrameLoader* frameLoader = toPrivate(_private)->loader->frameLoader();
432    if (!frameLoader || !frameLoader->frameHasLoaded())
433        return nil;
434
435    // FIXME: this cast is dubious
436    return (NSMutableURLRequest *)toPrivate(_private)->loader->request().nsURLRequest(UpdateHTTPBody);
437}
438
439- (NSURLResponse *)response
440{
441    return toPrivate(_private)->loader->response().nsURLResponse();
442}
443
444- (NSString *)textEncodingName
445{
446    NSString *textEncodingName = toPrivate(_private)->loader->overrideEncoding();
447    if (!textEncodingName)
448        textEncodingName = [[self response] textEncodingName];
449    return textEncodingName;
450}
451
452- (BOOL)isLoading
453{
454    return toPrivate(_private)->loader->isLoadingInAPISense();
455}
456
457// Returns nil or the page title.
458- (NSString *)pageTitle
459{
460    return [[self representation] title];
461}
462
463- (NSURL *)unreachableURL
464{
465    const KURL& unreachableURL = toPrivate(_private)->loader->unreachableURL();
466    if (unreachableURL.isEmpty())
467        return nil;
468    return unreachableURL;
469}
470
471- (WebArchive *)webArchive
472{
473    // it makes no sense to grab a WebArchive from an uncommitted document.
474    if (!toPrivate(_private)->loader->isCommitted())
475        return nil;
476
477    return [[[WebArchive alloc] _initWithCoreLegacyWebArchive:LegacyWebArchive::create(core([self webFrame]))] autorelease];
478}
479
480- (WebResource *)mainResource
481{
482    RefPtr<ArchiveResource> coreResource = toPrivate(_private)->loader->mainResource();
483    return [[[WebResource alloc] _initWithCoreResource:coreResource.release()] autorelease];
484}
485
486- (NSArray *)subresources
487{
488    Vector<PassRefPtr<ArchiveResource> > coreSubresources;
489    toPrivate(_private)->loader->getSubresources(coreSubresources);
490
491    NSMutableArray *subresources = [[NSMutableArray alloc] initWithCapacity:coreSubresources.size()];
492    for (unsigned i = 0; i < coreSubresources.size(); ++i) {
493        WebResource *resource = [[WebResource alloc] _initWithCoreResource:coreSubresources[i]];
494        if (resource) {
495            [subresources addObject:resource];
496            [resource release];
497        }
498    }
499
500    return [subresources autorelease];
501}
502
503- (WebResource *)subresourceForURL:(NSURL *)URL
504{
505    RefPtr<ArchiveResource> subresource = toPrivate(_private)->loader->subresource(URL);
506
507    return subresource ? [[[WebResource alloc] _initWithCoreResource:subresource.get()] autorelease] : nil;
508}
509
510- (void)addSubresource:(WebResource *)subresource
511{
512    toPrivate(_private)->loader->addArchiveResource([subresource _coreResource]);
513}
514
515@end
516