Unreviewed, roll out http://trac.webkit.org/changeset/187972.
[WebKit-https.git] / Source / WebCore / platform / network / ios / QuickLook.mm
1 /*
2  * Copyright (C) 2009 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. 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 APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "QuickLook.h"
28
29 #if USE(QUICK_LOOK)
30
31 #import "DocumentLoader.h"
32 #import "FileSystemIOS.h"
33 #import "Logging.h"
34 #import "NSFileManagerSPI.h"
35 #import "QuickLookSoftLink.h"
36 #import "ResourceError.h"
37 #import "ResourceHandle.h"
38 #import "ResourceLoader.h"
39 #import "RuntimeApplicationChecksIOS.h"
40 #import "SynchronousResourceHandleCFURLConnectionDelegate.h"
41 #import "WebCoreURLResponseIOS.h"
42 #import <Foundation/Foundation.h>
43 #import <wtf/NeverDestroyed.h>
44 #import <wtf/StdLibExtras.h>
45 #import <wtf/Threading.h>
46 #import <wtf/Vector.h>
47 #import <wtf/text/WTFString.h>
48
49 #if USE(CFNETWORK)
50 #import <CFNetwork/CFURLConnection.h>
51
52 @interface NSURLResponse (QuickLookDetails)
53 +(NSURLResponse *)_responseWithCFURLResponse:(CFURLResponseRef)response;
54 -(CFURLResponseRef)_CFURLResponse;
55 @end
56 #endif
57
58 using namespace WebCore;
59
60 NSSet *WebCore::QLPreviewGetSupportedMIMETypesSet()
61 {
62     static NeverDestroyed<RetainPtr<NSSet>> set = QLPreviewGetSupportedMIMETypes();
63     return set.get().get();
64 }
65
66 NSDictionary *WebCore::QLFileAttributes()
67 {
68     // Set file perms to owner read/write only
69     NSNumber *filePOSIXPermissions = [NSNumber numberWithInteger:(WEB_UREAD | WEB_UWRITE)];
70     static NSDictionary *dictionary = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:
71                                         NSUserName(), NSFileOwnerAccountName,
72                                         filePOSIXPermissions, NSFilePosixPermissions,
73                                         nullptr]).leakRef();
74     return dictionary;
75 }
76
77 NSDictionary *WebCore::QLDirectoryAttributes()
78 {
79     // Set file perms to owner read/write/execute only
80     NSNumber *directoryPOSIXPermissions = [NSNumber numberWithInteger:(WEB_UREAD | WEB_UWRITE | WEB_UEXEC)];
81     static NSDictionary *dictionary = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:
82                                                 NSUserName(), NSFileOwnerAccountName,
83                                                 directoryPOSIXPermissions, NSFilePosixPermissions,
84                                                 nullptr]).leakRef();
85     return dictionary;
86 }
87
88 static Mutex& qlPreviewConverterDictionaryMutex()
89 {
90     static NeverDestroyed<Mutex> mutex;
91     return mutex;
92 }
93
94 static NSMutableDictionary *QLPreviewConverterDictionary()
95 {
96     static NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
97     return dictionary;
98 }
99
100 static NSMutableDictionary *QLContentDictionary()
101 {
102     static NSMutableDictionary *contentDictionary = [[NSMutableDictionary alloc] init];
103     return contentDictionary;
104 }
105
106 void WebCore::addQLPreviewConverterWithFileForURL(NSURL *url, id converter, NSString *fileName)
107 {
108     ASSERT(url);
109     ASSERT(converter);
110     MutexLocker lock(qlPreviewConverterDictionaryMutex());
111     [QLPreviewConverterDictionary() setObject:converter forKey:url];
112     [QLContentDictionary() setObject:(fileName ? fileName : @"") forKey:url];
113 }
114
115 NSString *WebCore::qlPreviewConverterFileNameForURL(NSURL *url)
116 {
117     return [QLContentDictionary() objectForKey:url];
118 }
119
120 NSString *WebCore::qlPreviewConverterUTIForURL(NSURL *url)
121 {
122     id converter = nil;
123     {
124         MutexLocker lock(qlPreviewConverterDictionaryMutex());
125         converter = [QLPreviewConverterDictionary() objectForKey:url];
126     }
127     if (!converter)
128         return nil;
129     return [converter previewUTI];
130 }
131
132 void WebCore::removeQLPreviewConverterForURL(NSURL *url)
133 {
134     MutexLocker lock(qlPreviewConverterDictionaryMutex());
135     [QLPreviewConverterDictionary() removeObjectForKey:url];
136
137     // Delete the file when we remove the preview converter
138     NSString *filename = qlPreviewConverterFileNameForURL(url);
139     if ([filename length])
140         [[NSFileManager defaultManager] _web_removeFileOnlyAtPath:filename];
141     [QLContentDictionary() removeObjectForKey:url];
142 }
143
144 RetainPtr<NSURLRequest> WebCore::registerQLPreviewConverterIfNeeded(NSURL *url, NSString *mimeType, NSData *data)
145 {
146     RetainPtr<NSString> updatedMIMEType = adoptNS(QLTypeCopyBestMimeTypeForURLAndMimeType(url, mimeType));
147
148     if ([QLPreviewGetSupportedMIMETypesSet() containsObject:updatedMIMEType.get()]) {
149         RetainPtr<NSString> uti = adoptNS(QLTypeCopyUTIForURLAndMimeType(url, updatedMIMEType.get()));
150
151         RetainPtr<QLPreviewConverter> converter = adoptNS([allocQLPreviewConverterInstance() initWithData:data name:nil uti:uti.get() options:nil]);
152         NSURLRequest *request = [converter previewRequest];
153
154         // We use [request URL] here instead of url since it will be
155         // the URL that the WebDataSource will see during -dealloc.
156         addQLPreviewConverterWithFileForURL([request URL], converter.get(), nil);
157
158         return request;
159     }
160
161     return nil;
162 }
163
164 const URL WebCore::safeQLURLForDocumentURLAndResourceURL(const URL& documentURL, const String& resourceURL)
165 {
166     id converter = nil;
167     NSURL *nsDocumentURL = documentURL;
168     {
169         MutexLocker lock(qlPreviewConverterDictionaryMutex());
170         converter = [QLPreviewConverterDictionary() objectForKey:nsDocumentURL];
171     }
172
173     if (!converter)
174         return URL(ParsedURLString, resourceURL);
175
176     RetainPtr<NSURLRequest> request = adoptNS([[NSURLRequest alloc] initWithURL:[NSURL URLWithString:resourceURL]]);
177     NSURLRequest *safeRequest = [converter safeRequestForRequest:request.get()];
178     return [safeRequest URL];
179 }
180
181 static Vector<char> createQLPreviewProtocol()
182 {
183     Vector<char> previewProtocol;
184     const char* qlPreviewScheme = [QLPreviewScheme UTF8String];
185     previewProtocol.append(qlPreviewScheme, strlen(qlPreviewScheme) + 1);
186     return previewProtocol;
187 }
188
189 const char* WebCore::QLPreviewProtocol()
190 {
191     static NeverDestroyed<Vector<char>> previewProtocol(createQLPreviewProtocol());
192     return previewProtocol.get().data();
193 }
194
195 #if USE(CFNETWORK)
196 // The way QuickLook works is we pass it an NSURLConnectionDelegate callback object at creation
197 // time. Then we pass it all the data as we receive it. Once we've downloaded the full URL,
198 // QuickLook turns around and send us, through this delegate, the HTML version of the file which we
199 // pass on to WebCore. The flag m_finishedLoadingDataIntoConverter in QuickLookHandle decides
200 // whether to pass the data to QuickLook or WebCore.
201 //
202 // This works fine when using NS APIs, but when using CFNetwork, we don't have a NSURLConnectionDelegate.
203 // So we create WebQuickLookHandleAsDelegate as an intermediate delegate object and pass it to
204 // QLPreviewConverter. The proxy delegate then forwards the messages on to the CFNetwork code.
205 @interface WebQuickLookHandleAsDelegate : NSObject <NSURLConnectionDelegate> {
206     RefPtr<SynchronousResourceHandleCFURLConnectionDelegate> m_connectionDelegate;
207 }
208
209 - (id)initWithConnectionDelegate:(SynchronousResourceHandleCFURLConnectionDelegate*)connectionDelegate;
210 - (void)clearHandle;
211 @end
212
213 @implementation WebQuickLookHandleAsDelegate
214 - (id)initWithConnectionDelegate:(SynchronousResourceHandleCFURLConnectionDelegate*)connectionDelegate
215 {
216     self = [super init];
217     if (!self)
218         return nil;
219     m_connectionDelegate = connectionDelegate;
220     return self;
221 }
222
223 - (void)connection:(NSURLConnection *)connection didReceiveDataArray:(NSArray *)dataArray
224 {
225     UNUSED_PARAM(connection);
226     if (!m_connectionDelegate)
227         return;
228     LOG(Network, "WebQuickLookHandleAsDelegate::didReceiveDataArray()");
229     m_connectionDelegate->didReceiveDataArray(reinterpret_cast<CFArrayRef>(dataArray));
230 }
231
232 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
233 {
234     UNUSED_PARAM(connection);
235     if (!m_connectionDelegate)
236         return;
237     LOG(Network, "WebQuickLookHandleAsDelegate::didReceiveData() - data length = %ld", (long)[data length]);
238
239     // QuickLook code sends us a nil data at times. The check below is the same as the one in
240     // ResourceHandleMac.cpp added for a different bug.
241     if (![data length])
242         return;
243     m_connectionDelegate->didReceiveData(reinterpret_cast<CFDataRef>(data), static_cast<int>(lengthReceived));
244 }
245
246 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
247 {
248     UNUSED_PARAM(connection);
249     if (!m_connectionDelegate)
250         return;
251     LOG(Network, "WebQuickLookHandleAsDelegate::didFinishLoading()");
252     m_connectionDelegate->didFinishLoading();
253 }
254
255 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
256 {
257     UNUSED_PARAM(connection);
258     if (!m_connectionDelegate)
259         return;
260     LOG(Network, "WebQuickLookHandleAsDelegate::didFail()");
261     m_connectionDelegate->didFail(reinterpret_cast<CFErrorRef>(error));
262 }
263
264 - (void)clearHandle
265 {
266     m_connectionDelegate = nullptr;
267 }
268 @end
269 #endif
270
271 @interface WebResourceLoaderQuickLookDelegate : NSObject <NSURLConnectionDelegate> {
272     RefPtr<ResourceLoader> _resourceLoader;
273     BOOL _hasSentDidReceiveResponse;
274     BOOL _hasFailed;
275 }
276 @property (nonatomic) QuickLookHandle* quickLookHandle;
277 @end
278
279 @implementation WebResourceLoaderQuickLookDelegate
280
281 - (id)initWithResourceLoader:(PassRefPtr<ResourceLoader>)resourceLoader
282 {
283     self = [super init];
284     if (!self)
285         return nil;
286
287     _resourceLoader = resourceLoader;
288     return self;
289 }
290
291 - (void)_sendDidReceiveResponseIfNecessary
292 {
293     if (_hasSentDidReceiveResponse || _hasFailed || !_quickLookHandle)
294         return;
295
296     // QuickLook might fail to convert a document without calling connection:didFailWithError: (see <rdar://problem/17927972>).
297     // A nil MIME type is an indication of such a failure, so stop loading the resource and ignore subsequent delegate messages.
298     NSURLResponse *previewResponse = _quickLookHandle->nsResponse();
299     if (![previewResponse MIMEType]) {
300         _hasFailed = YES;
301         _resourceLoader->didFail(_resourceLoader->cannotShowURLError());
302         return;
303     }
304
305     _hasSentDidReceiveResponse = YES;
306     _resourceLoader->didReceiveResponse(previewResponse);
307 }
308
309 #if USE(NETWORK_CFDATA_ARRAY_CALLBACK)
310 - (void)connection:(NSURLConnection *)connection didReceiveDataArray:(NSArray *)dataArray
311 {
312     UNUSED_PARAM(connection);
313     if (!_resourceLoader)
314         return;
315
316     [self _sendDidReceiveResponseIfNecessary];
317     if (_hasFailed)
318         return;
319
320     _resourceLoader->didReceiveDataArray(reinterpret_cast<CFArrayRef>(dataArray));
321 }
322 #endif
323
324 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
325 {
326     UNUSED_PARAM(connection);
327     if (!_resourceLoader)
328         return;
329
330     [self _sendDidReceiveResponseIfNecessary];
331     if (_hasFailed)
332         return;
333
334     // QuickLook code sends us a nil data at times. The check below is the same as the one in
335     // ResourceHandleMac.cpp added for a different bug.
336     if (![data length])
337         return;
338     _resourceLoader->didReceiveData(reinterpret_cast<const char*>([data bytes]), [data length], lengthReceived, DataPayloadBytes);
339 }
340
341 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
342 {
343     UNUSED_PARAM(connection);
344     if (!_resourceLoader || _hasFailed)
345         return;
346
347     ASSERT(_hasSentDidReceiveResponse);
348     _resourceLoader->didFinishLoading(0);
349 }
350
351 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
352 {
353     UNUSED_PARAM(connection);
354
355     [self _sendDidReceiveResponseIfNecessary];
356     if (_hasFailed)
357         return;
358
359     _resourceLoader->didFail(ResourceError(error));
360 }
361
362 - (void)clearHandle
363 {
364     _resourceLoader = nullptr;
365     _quickLookHandle = nullptr;
366 }
367
368 @end
369
370 namespace WebCore {
371
372 NSString *createTemporaryFileForQuickLook(NSString *fileName)
373 {
374     NSString *downloadDirectory = createTemporaryDirectory(@"QuickLookContent");
375     if (!downloadDirectory)
376         return nil;
377
378     NSString *contentPath = [downloadDirectory stringByAppendingPathComponent:fileName];
379     NSFileManager *fileManager = [NSFileManager defaultManager];
380     NSString *uniqueContentPath = [fileManager _web_pathWithUniqueFilenameForPath:contentPath];
381
382     BOOL success = [fileManager _web_createFileAtPathWithIntermediateDirectories:uniqueContentPath
383                                                                         contents:nil
384                                                                       attributes:QLFileAttributes()
385                                                              directoryAttributes:QLDirectoryAttributes()];
386
387     return success ? uniqueContentPath : nil;
388 }
389
390 static inline QuickLookHandleClient* emptyClient()
391 {
392     static NeverDestroyed<QuickLookHandleClient> emptyClient;
393     return &emptyClient.get();
394 }
395
396 QuickLookHandle::QuickLookHandle(NSURL *firstRequestURL, NSURLConnection *connection, NSURLResponse *nsResponse, id delegate)
397     : m_firstRequestURL(firstRequestURL)
398     , m_converter(adoptNS([allocQLPreviewConverterInstance() initWithConnection:connection delegate:delegate response:nsResponse options:nil]))
399     , m_delegate(delegate)
400     , m_finishedLoadingDataIntoConverter(false)
401     , m_nsResponse([m_converter previewResponse])
402     , m_client(emptyClient())
403 {
404     LOG(Network, "QuickLookHandle::QuickLookHandle() - previewFileName: %s", [m_converter previewFileName]);
405 }
406
407 std::unique_ptr<QuickLookHandle> QuickLookHandle::create(ResourceHandle* handle, NSURLConnection *connection, NSURLResponse *nsResponse, id delegate)
408 {
409     ASSERT_ARG(handle, handle);
410     if (handle->firstRequest().requester() != ResourceRequest::Requester::Main || ![QLPreviewGetSupportedMIMETypesSet() containsObject:[nsResponse MIMEType]])
411         return nullptr;
412
413     std::unique_ptr<QuickLookHandle> quickLookHandle(new QuickLookHandle([handle->firstRequest().nsURLRequest(DoNotUpdateHTTPBody) URL], connection, nsResponse, delegate));
414     handle->client()->didCreateQuickLookHandle(*quickLookHandle);
415     return WTF::move(quickLookHandle);
416 }
417
418 #if USE(CFNETWORK)
419 std::unique_ptr<QuickLookHandle> QuickLookHandle::create(ResourceHandle* handle, SynchronousResourceHandleCFURLConnectionDelegate* connectionDelegate, CFURLResponseRef cfResponse)
420 {
421     ASSERT_ARG(handle, handle);
422     if (handle->firstRequest().requester() != ResourceRequest::Requester::Main || ![QLPreviewGetSupportedMIMETypesSet() containsObject:(NSString *)CFURLResponseGetMIMEType(cfResponse)])
423         return nullptr;
424
425     NSURLResponse *nsResponse = [NSURLResponse _responseWithCFURLResponse:cfResponse];
426     WebQuickLookHandleAsDelegate *delegate = [[[WebQuickLookHandleAsDelegate alloc] initWithConnectionDelegate:connectionDelegate] autorelease];
427     std::unique_ptr<QuickLookHandle> quickLookHandle(new QuickLookHandle([handle->firstRequest().nsURLRequest(DoNotUpdateHTTPBody) URL], nil, nsResponse, delegate));
428     handle->client()->didCreateQuickLookHandle(*quickLookHandle);
429     return WTF::move(quickLookHandle);
430 }
431
432 CFURLResponseRef QuickLookHandle::cfResponse()
433 {
434     return [m_nsResponse _CFURLResponse];
435 }
436 #endif
437
438 bool QuickLookHandle::shouldCreateForMIMEType(const String& mimeType)
439 {
440     return [QLPreviewGetSupportedMIMETypesSet() containsObject:mimeType];
441 }
442
443 std::unique_ptr<QuickLookHandle> QuickLookHandle::create(ResourceLoader& loader, const ResourceResponse& response)
444 {
445     ASSERT(shouldCreateForMIMEType(response.mimeType()));
446
447     RetainPtr<WebResourceLoaderQuickLookDelegate> delegate = adoptNS([[WebResourceLoaderQuickLookDelegate alloc] initWithResourceLoader:&loader]);
448     std::unique_ptr<QuickLookHandle> quickLookHandle(new QuickLookHandle([loader.originalRequest().nsURLRequest(DoNotUpdateHTTPBody) URL], nil, response.nsURLResponse(), delegate.get()));
449     [delegate setQuickLookHandle:quickLookHandle.get()];
450     loader.didCreateQuickLookHandle(*quickLookHandle);
451     return WTF::move(quickLookHandle);
452 }
453
454 NSURLResponse *QuickLookHandle::nsResponse()
455 {
456     return m_nsResponse.get();
457 }
458
459 bool QuickLookHandle::didReceiveDataArray(CFArrayRef cfDataArray)
460 {
461     if (m_finishedLoadingDataIntoConverter)
462         return false;
463
464     LOG(Network, "QuickLookHandle::didReceiveDataArray()");
465     [m_converter appendDataArray:(NSArray *)cfDataArray];
466     m_client->didReceiveDataArray(cfDataArray);
467     return true;
468 }
469
470 bool QuickLookHandle::didReceiveData(CFDataRef cfData)
471 {
472     if (m_finishedLoadingDataIntoConverter)
473         return false;
474     
475     return didReceiveDataArray(adoptCF(CFArrayCreate(kCFAllocatorDefault, (const void**)&cfData, 1, &kCFTypeArrayCallBacks)).get());
476 }
477
478 bool QuickLookHandle::didFinishLoading()
479 {
480     if (m_finishedLoadingDataIntoConverter)
481         return false;
482
483     LOG(Network, "QuickLookHandle::didFinishLoading()");
484     m_finishedLoadingDataIntoConverter = YES;
485     [m_converter finishedAppendingData];
486     m_client->didFinishLoading();
487     return true;
488 }
489
490 void QuickLookHandle::didFail()
491 {
492     LOG(Network, "QuickLookHandle::didFail()");
493     m_client->didFail();
494     [m_converter finishConverting];
495     m_converter = nullptr;
496 }
497
498 QuickLookHandle::~QuickLookHandle()
499 {
500     LOG(Network, "QuickLookHandle::~QuickLookHandle()");
501     m_converter = nullptr;
502
503     [m_delegate clearHandle];
504 }
505
506 String QuickLookHandle::previewFileName() const
507 {
508     return [m_converter previewFileName];
509 }
510
511 String QuickLookHandle::previewUTI() const
512 {
513     return [m_converter previewUTI];
514 }
515
516 NSURL *QuickLookHandle::previewRequestURL() const
517 {
518     return [[m_converter previewRequest] URL];
519 }
520
521 }
522
523 #endif // USE(QUICK_LOOK)