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 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 "WebFrameViewInternal.h"
40#import "WebHTMLRepresentation.h"
41#import "WebKitErrorsPrivate.h"
42#import "WebKitLogging.h"
43#import "WebKitStatisticsPrivate.h"
44#import "WebKitNSStringExtras.h"
45#import "WebNSURLExtras.h"
46#import "WebNSURLRequestExtras.h"
47#import "WebPDFRepresentation.h"
48#import "WebResourceInternal.h"
49#import "WebResourceLoadDelegate.h"
50#import "WebViewInternal.h"
51#import <WebCore/ApplicationCacheStorage.h>
52#import <WebCore/FrameLoader.h>
53#import <WebCore/URL.h>
54#import <WebCore/LegacyWebArchive.h>
55#import <WebCore/MIMETypeRegistry.h>
56#import <WebCore/ResourceBuffer.h>
57#import <WebCore/ResourceRequest.h>
58#import <WebCore/SharedBuffer.h>
59#import <WebCore/WebCoreObjCExtras.h>
60#import <WebCore/WebCoreURLResponse.h>
61#import <WebKitLegacy/DOMHTML.h>
62#import <WebKitLegacy/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#import <wtf/RunLoop.h>
69
70#if PLATFORM(IOS)
71#import "WebPDFViewIOS.h"
72#endif
73
74#if USE(QUICK_LOOK)
75#import <WebCore/QuickLook.h>
76#endif
77
78using namespace WebCore;
79
80class WebDataSourcePrivate
81{
82public:
83    WebDataSourcePrivate(PassRefPtr<WebDocumentLoaderMac> loader)
84        : loader(loader)
85        , representationFinishedLoading(NO)
86        , includedInWebKitStatistics(NO)
87#if PLATFORM(IOS)
88        , _dataSourceDelegate(nil)
89#endif
90    {
91        ASSERT(this->loader);
92    }
93    ~WebDataSourcePrivate()
94    {
95        if (loader) {
96            ASSERT(!loader->isLoading());
97            loader->detachDataSource();
98        }
99    }
100
101    RefPtr<WebDocumentLoaderMac> loader;
102    RetainPtr<id<WebDocumentRepresentation> > representation;
103    BOOL representationFinishedLoading;
104    BOOL includedInWebKitStatistics;
105#if PLATFORM(IOS)
106    NSObject<WebDataSourcePrivateDelegate> *_dataSourceDelegate;
107#endif
108};
109
110static inline WebDataSourcePrivate* toPrivate(void* privateAttribute)
111{
112    return reinterpret_cast<WebDataSourcePrivate*>(privateAttribute);
113}
114
115#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS)
116static void BufferMemoryMapped(PassRefPtr<SharedBuffer> buffer, SharedBuffer::CompletionStatus mapStatus, SharedBuffer::MemoryMappedNotifyCallbackData data)
117{
118    NSObject<WebDataSourcePrivateDelegate> *delegate = [(WebDataSource *)data dataSourceDelegate];
119    if (mapStatus == SharedBuffer::Succeeded)
120        [delegate dataSourceMemoryMapped];
121    else
122        [delegate dataSourceMemoryMapFailed];
123}
124#endif
125
126@interface WebDataSource (WebFileInternal)
127@end
128
129@implementation WebDataSource (WebFileInternal)
130
131- (void)_setRepresentation:(id<WebDocumentRepresentation>)representation
132{
133    toPrivate(_private)->representation = representation;
134    toPrivate(_private)->representationFinishedLoading = NO;
135}
136
137static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
138{
139    NSEnumerator *enumerator = [supportTypes objectEnumerator];
140    ASSERT(enumerator != nil);
141    NSString *mime = nil;
142    while ((mime = [enumerator nextObject]) != nil) {
143        // Don't clobber previously-registered classes.
144        if ([allTypes objectForKey:mime] == nil)
145            [allTypes setObject:objCClass forKey:mime];
146    }
147}
148
149+ (Class)_representationClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins
150{
151    Class repClass;
152    return [WebView _viewClass:nil andRepresentationClass:&repClass forMIMEType:MIMEType allowingPlugins:allowPlugins] ? repClass : nil;
153}
154@end
155
156@implementation WebDataSource (WebPrivate)
157
158+ (void)initialize
159{
160    if (self == [WebDataSource class]) {
161#if !PLATFORM(IOS)
162        JSC::initializeThreading();
163        WTF::initializeMainThreadToProcessMainThread();
164        RunLoop::initializeMainRunLoop();
165#endif
166        WebCoreObjCFinalizeOnMainThread(self);
167    }
168}
169
170- (NSError *)_mainDocumentError
171{
172    return toPrivate(_private)->loader->mainDocumentError();
173}
174
175- (void)_addSubframeArchives:(NSArray *)subframeArchives
176{
177    // FIXME: This SPI is poor, poor design.  Can we come up with another solution for those who need it?
178    NSEnumerator *enumerator = [subframeArchives objectEnumerator];
179    WebArchive *archive;
180    while ((archive = [enumerator nextObject]) != nil)
181        toPrivate(_private)->loader->addAllArchiveResources([archive _coreLegacyWebArchive]);
182}
183
184#if !PLATFORM(IOS)
185- (NSFileWrapper *)_fileWrapperForURL:(NSURL *)URL
186{
187    if ([URL isFileURL])
188        return [[[NSFileWrapper alloc] initWithURL:[URL URLByResolvingSymlinksInPath] options:0 error:nullptr] autorelease];
189
190    WebResource *resource = [self subresourceForURL:URL];
191    if (resource)
192        return [resource _fileWrapperRepresentation];
193
194    NSCachedURLResponse *cachedResponse = [[self _webView] _cachedResponseForURL:URL];
195    if (cachedResponse) {
196        NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease];
197        [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]];
198        return wrapper;
199    }
200
201    return nil;
202}
203#endif
204
205- (NSString *)_responseMIMEType
206{
207    return [[self response] MIMEType];
208}
209
210- (BOOL)_transferApplicationCache:(NSString*)destinationBundleIdentifier
211{
212    if (!toPrivate(_private)->loader)
213        return NO;
214
215    NSString *cacheDir = [NSString _webkit_localCacheDirectoryWithBundleIdentifier:destinationBundleIdentifier];
216
217    return ApplicationCacheStorage::storeCopyOfCache(cacheDir, toPrivate(_private)->loader->applicationCacheHost());
218}
219
220- (void)_setDeferMainResourceDataLoad:(BOOL)flag
221{
222    if (!toPrivate(_private)->loader)
223        return;
224
225    toPrivate(_private)->loader->setDeferMainResourceDataLoad(flag);
226}
227
228#if PLATFORM(IOS)
229- (void)_setOverrideTextEncodingName:(NSString *)encoding
230{
231    toPrivate(_private)->loader->setOverrideEncoding([encoding UTF8String]);
232}
233#endif
234
235- (void)_setAllowToBeMemoryMapped
236{
237#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS)
238    RefPtr<ResourceBuffer> mainResourceBuffer = toPrivate(_private)->loader->mainResourceData();
239    if (!mainResourceBuffer)
240        return;
241
242    RefPtr<SharedBuffer> mainResourceData = mainResourceBuffer->sharedBuffer();
243    if (!mainResourceData)
244        return;
245
246    if (mainResourceData->memoryMappedNotificationCallback() != BufferMemoryMapped) {
247        ASSERT(!mainResourceData->memoryMappedNotificationCallback() && !mainResourceData->memoryMappedNotificationCallbackData());
248        mainResourceData->setMemoryMappedNotificationCallback(BufferMemoryMapped, self);
249    }
250
251    switch (mainResourceData->allowToBeMemoryMapped()) {
252    case SharedBuffer::SuccessAlreadyMapped:
253        [[self dataSourceDelegate] dataSourceMemoryMapped];
254        return;
255    case SharedBuffer::PreviouslyQueuedForMapping:
256    case SharedBuffer::QueuedForMapping:
257        return;
258    case SharedBuffer::FailureCacheFull:
259        [[self dataSourceDelegate] dataSourceMemoryMapFailed];
260        return;
261    }
262    ASSERT_NOT_REACHED();
263#endif
264}
265
266- (void)setDataSourceDelegate:(NSObject<WebDataSourcePrivateDelegate> *)delegate
267{
268#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS)
269    ASSERT(!toPrivate(_private)->_dataSourceDelegate);
270    toPrivate(_private)->_dataSourceDelegate = delegate;
271#endif
272}
273
274- (NSObject<WebDataSourcePrivateDelegate> *)dataSourceDelegate
275{
276#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS)
277    return toPrivate(_private)->_dataSourceDelegate;
278#else
279    return nullptr;
280#endif
281}
282
283@end
284
285@implementation WebDataSource (WebInternal)
286
287- (void)_finishedLoading
288{
289    toPrivate(_private)->representationFinishedLoading = YES;
290    [[self representation] finishedLoadingWithDataSource:self];
291}
292
293- (void)_receivedData:(NSData *)data
294{
295    // protect self temporarily, as the bridge receivedData call could remove our last ref
296    RetainPtr<WebDataSource*> protect(self);
297
298    [[self representation] receivedData:data withDataSource:self];
299    [[[[self webFrame] frameView] documentView] dataSourceUpdated:self];
300}
301
302- (void)_setMainDocumentError:(NSError *)error
303{
304    if (!toPrivate(_private)->representationFinishedLoading) {
305        toPrivate(_private)->representationFinishedLoading = YES;
306        [[self representation] receivedError:error withDataSource:self];
307    }
308}
309
310- (void)_revertToProvisionalState
311{
312    [self _setRepresentation:nil];
313}
314
315+ (NSMutableDictionary *)_repTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
316{
317    static NSMutableDictionary *repTypes = nil;
318    static BOOL addedImageTypes = NO;
319
320    if (!repTypes) {
321        repTypes = [[NSMutableDictionary alloc] init];
322        addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedNonImageMIMETypes]);
323
324        // Since this is a "secret default" we don't both registering it.
325        BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
326        if (!omitPDFSupport)
327#if PLATFORM(IOS)
328#define WebPDFRepresentation ([WebView _getPDFRepresentationClass])
329#endif
330            addTypesFromClass(repTypes, [WebPDFRepresentation class], [WebPDFRepresentation supportedMIMETypes]);
331#if PLATFORM(IOS)
332#undef WebPDFRepresentation
333#endif
334    }
335
336    if (!addedImageTypes && !allowImageTypeOmission) {
337        addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedImageMIMETypes]);
338        addedImageTypes = YES;
339    }
340
341    return repTypes;
342}
343
344- (void)_replaceSelectionWithArchive:(WebArchive *)archive selectReplacement:(BOOL)selectReplacement
345{
346    DOMDocumentFragment *fragment = [self _documentFragmentWithArchive:archive];
347    if (fragment)
348        [[self webFrame] _replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:NO matchStyle:NO];
349}
350
351// FIXME: There are few reasons why this method and many of its related methods can't be pushed entirely into WebCore in the future.
352- (DOMDocumentFragment *)_documentFragmentWithArchive:(WebArchive *)archive
353{
354    ASSERT(archive);
355    WebResource *mainResource = [archive mainResource];
356    if (mainResource) {
357        NSString *MIMEType = [mainResource MIMEType];
358        if ([WebView canShowMIMETypeAsHTML:MIMEType]) {
359            NSString *markupString = [[NSString alloc] initWithData:[mainResource data] encoding:NSUTF8StringEncoding];
360            // FIXME: seems poor form to do this as a side effect of getting a document fragment
361            if (toPrivate(_private)->loader)
362                toPrivate(_private)->loader->addAllArchiveResources([archive _coreLegacyWebArchive]);
363
364            DOMDocumentFragment *fragment = [[self webFrame] _documentFragmentWithMarkupString:markupString baseURLString:[[mainResource URL] _web_originalDataAsString]];
365            [markupString release];
366            return fragment;
367        } else if (MIMETypeRegistry::isSupportedImageMIMEType(MIMEType)) {
368            return [self _documentFragmentWithImageResource:mainResource];
369
370        }
371    }
372    return nil;
373}
374
375- (DOMDocumentFragment *)_documentFragmentWithImageResource:(WebResource *)resource
376{
377    DOMElement *imageElement = [self _imageElementWithImageResource:resource];
378    if (!imageElement)
379        return 0;
380    DOMDocumentFragment *fragment = [[[self webFrame] DOMDocument] createDocumentFragment];
381    [fragment appendChild:imageElement];
382    return fragment;
383}
384
385- (DOMElement *)_imageElementWithImageResource:(WebResource *)resource
386{
387    if (!resource)
388        return 0;
389
390    [self addSubresource:resource];
391
392    DOMElement *imageElement = [[[self webFrame] DOMDocument] createElement:@"img"];
393
394    // FIXME: calling _web_originalDataAsString on a file URL returns an absolute path. Workaround this.
395    NSURL *URL = [resource URL];
396    [imageElement setAttribute:@"src" value:[URL isFileURL] ? [URL absoluteString] : [URL _web_originalDataAsString]];
397
398    return imageElement;
399}
400
401// May return nil if not initialized with a URL.
402- (NSURL *)_URL
403{
404    const URL& url = toPrivate(_private)->loader->url();
405    if (url.isEmpty())
406        return nil;
407    return url;
408}
409
410- (WebView *)_webView
411{
412    return [[self webFrame] webView];
413}
414
415- (BOOL)_isDocumentHTML
416{
417    NSString *MIMEType = [self _responseMIMEType];
418    return [WebView canShowMIMETypeAsHTML:MIMEType];
419}
420
421- (void)_makeRepresentation
422{
423    Class repClass = [[self class] _representationClassForMIMEType:[self _responseMIMEType] allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]];
424
425#if PLATFORM(IOS)
426    if ([repClass respondsToSelector:@selector(_representationClassForWebFrame:)])
427        repClass = [repClass performSelector:@selector(_representationClassForWebFrame:) withObject:[self webFrame]];
428#endif
429
430    // Check if the data source was already bound?
431    if (![[self representation] isKindOfClass:repClass]) {
432        id newRep = repClass != nil ? [[repClass alloc] init] : nil;
433        [self _setRepresentation:(id <WebDocumentRepresentation>)newRep];
434        [newRep release];
435    }
436
437    id<WebDocumentRepresentation> representation = toPrivate(_private)->representation.get();
438    [representation setDataSource:self];
439#if PLATFORM(IOS)
440    toPrivate(_private)->loader->setResponseMIMEType([self _responseMIMEType]);
441#endif
442}
443
444- (DocumentLoader*)_documentLoader
445{
446    return toPrivate(_private)->loader.get();
447}
448
449- (id)_initWithDocumentLoader:(PassRefPtr<WebDocumentLoaderMac>)loader
450{
451    self = [super init];
452    if (!self)
453        return nil;
454
455    ASSERT(loader);
456    _private = static_cast<void*>(new WebDataSourcePrivate(loader));
457
458    LOG(Loading, "creating datasource for %@", static_cast<NSURL *>(toPrivate(_private)->loader->request().url()));
459
460    if ((toPrivate(_private)->includedInWebKitStatistics = [[self webFrame] _isIncludedInWebKitStatistics]))
461        ++WebDataSourceCount;
462
463    return self;
464}
465
466@end
467
468@implementation WebDataSource
469
470- (instancetype)initWithRequest:(NSURLRequest *)request
471{
472    return [self _initWithDocumentLoader:WebDocumentLoaderMac::create(request, SubstituteData())];
473}
474
475- (void)dealloc
476{
477    if (WebCoreObjCScheduleDeallocateOnMainThread([WebDataSource class], self))
478        return;
479
480    if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics)
481        --WebDataSourceCount;
482
483#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS)
484    // The code to remove memory mapped notification is only needed when we are viewing a PDF file.
485    // In such a case, WebPDFViewPlaceholder sets itself as the dataSourceDelegate. Guard the access
486    // to mainResourceData with this nil check so that we avoid assertions due to the resource being
487    // made purgeable.
488    if (_private && [self dataSourceDelegate]) {
489        RefPtr<ResourceBuffer> mainResourceBuffer = toPrivate(_private)->loader->mainResourceData();
490        if (mainResourceBuffer) {
491            RefPtr<SharedBuffer> mainResourceData = mainResourceBuffer->sharedBuffer();
492            if (mainResourceData &&
493                mainResourceData->memoryMappedNotificationCallbackData() == self &&
494                mainResourceData->memoryMappedNotificationCallback() == BufferMemoryMapped) {
495                mainResourceData->setMemoryMappedNotificationCallback(nullptr, nullptr);
496            }
497        }
498    }
499#endif
500
501#if USE(QUICK_LOOK)
502    // Added in -[WebCoreResourceHandleAsDelegate connection:didReceiveResponse:].
503    if (NSURL *url = [[self response] URL])
504        removeQLPreviewConverterForURL(url);
505#endif
506
507    delete toPrivate(_private);
508
509    [super dealloc];
510}
511
512- (void)finalize
513{
514    ASSERT_MAIN_THREAD();
515
516    if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics)
517        --WebDataSourceCount;
518
519    delete toPrivate(_private);
520
521    [super finalize];
522}
523
524- (NSData *)data
525{
526    RefPtr<ResourceBuffer> mainResourceData = toPrivate(_private)->loader->mainResourceData();
527    if (!mainResourceData)
528        return nil;
529    return mainResourceData->createNSData().autorelease();
530}
531
532- (id <WebDocumentRepresentation>)representation
533{
534    return toPrivate(_private)->representation.get();
535}
536
537- (WebFrame *)webFrame
538{
539    if (Frame* frame = toPrivate(_private)->loader->frame())
540        return kit(frame);
541
542    return nil;
543}
544
545- (NSURLRequest *)initialRequest
546{
547    return toPrivate(_private)->loader->originalRequest().nsURLRequest(UpdateHTTPBody);
548}
549
550- (NSMutableURLRequest *)request
551{
552    FrameLoader* frameLoader = toPrivate(_private)->loader->frameLoader();
553    if (!frameLoader || !frameLoader->frameHasLoaded())
554        return nil;
555
556    // FIXME: this cast is dubious
557    return (NSMutableURLRequest *)toPrivate(_private)->loader->request().nsURLRequest(UpdateHTTPBody);
558}
559
560- (NSURLResponse *)response
561{
562    return toPrivate(_private)->loader->response().nsURLResponse();
563}
564
565- (NSString *)textEncodingName
566{
567    NSString *textEncodingName = toPrivate(_private)->loader->overrideEncoding();
568    if (!textEncodingName)
569        textEncodingName = [[self response] textEncodingName];
570    return textEncodingName;
571}
572
573- (BOOL)isLoading
574{
575    return toPrivate(_private)->loader->isLoadingInAPISense();
576}
577
578// Returns nil or the page title.
579- (NSString *)pageTitle
580{
581    return [[self representation] title];
582}
583
584- (NSURL *)unreachableURL
585{
586    const URL& unreachableURL = toPrivate(_private)->loader->unreachableURL();
587    if (unreachableURL.isEmpty())
588        return nil;
589    return unreachableURL;
590}
591
592- (WebArchive *)webArchive
593{
594    // it makes no sense to grab a WebArchive from an uncommitted document.
595    if (!toPrivate(_private)->loader->isCommitted())
596        return nil;
597
598    return [[[WebArchive alloc] _initWithCoreLegacyWebArchive:LegacyWebArchive::create(core([self webFrame]))] autorelease];
599}
600
601- (WebResource *)mainResource
602{
603    RefPtr<ArchiveResource> coreResource = toPrivate(_private)->loader->mainResource();
604    return [[[WebResource alloc] _initWithCoreResource:coreResource.release()] autorelease];
605}
606
607- (NSArray *)subresources
608{
609    auto coreSubresources = toPrivate(_private)->loader->subresources();
610
611    auto subresources = adoptNS([[NSMutableArray alloc] initWithCapacity:coreSubresources.size()]);
612    for (const auto& coreSubresource : coreSubresources) {
613        if (auto resource = adoptNS([[WebResource alloc] _initWithCoreResource:coreSubresource]))
614            [subresources addObject:resource.get()];
615    }
616
617    return subresources.autorelease();
618}
619
620- (WebResource *)subresourceForURL:(NSURL *)URL
621{
622    RefPtr<ArchiveResource> subresource = toPrivate(_private)->loader->subresource(URL);
623
624    return subresource ? [[[WebResource alloc] _initWithCoreResource:subresource.get()] autorelease] : nil;
625}
626
627- (void)addSubresource:(WebResource *)subresource
628{
629    toPrivate(_private)->loader->addArchiveResource([subresource _coreResource]);
630}
631
632@end
633