569375d24eaaa2156a5406ff96690257c752dd4b
[WebKit-https.git] / Source / WebCore / loader / ios / PreviewLoader.mm
1 /*
2  * Copyright (C) 2009-2017 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 "PreviewLoader.h"
28
29 #if USE(QUICK_LOOK)
30
31 #import "DocumentLoader.h"
32 #import "FrameLoader.h"
33 #import "FrameLoaderClient.h"
34 #import "Logging.h"
35 #import "PreviewConverter.h"
36 #import "PreviewLoaderClient.h"
37 #import "QuickLook.h"
38 #import "QuickLookSPI.h"
39 #import "ResourceLoader.h"
40 #import <wtf/NeverDestroyed.h>
41
42 using namespace WebCore;
43
44 @interface WebPreviewLoader : NSObject {
45     RefPtr<ResourceLoader> _resourceLoader;
46     ResourceResponse _response;
47     RefPtr<PreviewLoaderClient> _client;
48     std::unique_ptr<PreviewConverter> _converter;
49     RetainPtr<NSMutableArray> _bufferedDataArray;
50     BOOL _hasSentDidReceiveResponse;
51 }
52
53 - (instancetype)initWithResourceLoader:(ResourceLoader&)resourceLoader resourceResponse:(const ResourceResponse&)resourceResponse;
54 - (void)appendDataArray:(NSArray<NSData *> *)dataArray;
55 - (void)finishedAppending;
56 - (void)failed;
57
58 @end
59
60 @implementation WebPreviewLoader
61
62 static RefPtr<PreviewLoaderClient>& testingClient()
63 {
64     static NeverDestroyed<RefPtr<PreviewLoaderClient>> testingClient;
65     return testingClient.get();
66 }
67
68 static PreviewLoaderClient& emptyClient()
69 {
70     static NeverDestroyed<PreviewLoaderClient> emptyClient;
71     return emptyClient.get();
72 }
73
74 - (instancetype)initWithResourceLoader:(ResourceLoader&)resourceLoader resourceResponse:(const ResourceResponse&)resourceResponse
75 {
76     self = [super init];
77     if (!self)
78         return nil;
79
80     _resourceLoader = &resourceLoader;
81     _response = resourceResponse;
82     _converter = std::make_unique<PreviewConverter>(self, _response);
83     _bufferedDataArray = adoptNS([[NSMutableArray alloc] init]);
84
85     if (testingClient())
86         _client = testingClient();
87     else if (auto client = resourceLoader.frameLoader()->client().createPreviewLoaderClient(_converter->previewFileName(), _converter->previewUTI()))
88         _client = WTFMove(client);
89     else
90         _client = &emptyClient();
91
92     LOG(Network, "WebPreviewConverter created with preview file name \"%s\".", _converter->previewFileName().utf8().data());
93     return self;
94 }
95
96 - (void)appendDataArray:(NSArray<NSData *> *)dataArray
97 {
98     LOG(Network, "WebPreviewConverter appending data array with count %ld.", dataArray.count);
99     [_converter->platformConverter() appendDataArray:dataArray];
100     [_bufferedDataArray addObjectsFromArray:dataArray];
101     _client->didReceiveDataArray((CFArrayRef)dataArray);
102 }
103
104 - (void)finishedAppending
105 {
106     LOG(Network, "WebPreviewConverter finished appending data.");
107     [_converter->platformConverter() finishedAppendingData];
108     _client->didFinishLoading();
109 }
110
111 - (void)failed
112 {
113     LOG(Network, "WebPreviewConverter failed.");
114     [_converter->platformConverter() finishConverting];
115     _client->didFail();
116 }
117
118 - (void)_sendDidReceiveResponseIfNecessary
119 {
120     ASSERT(!_resourceLoader->reachedTerminalState());
121     if (_hasSentDidReceiveResponse)
122         return;
123
124     [_bufferedDataArray removeAllObjects];
125
126     ResourceResponse response { _converter->previewResponse() };
127     response.setIsQuickLook(true);
128     ASSERT(response.mimeType().length());
129
130     _resourceLoader->documentLoader()->setPreviewConverter(WTFMove(_converter));
131
132     _hasSentDidReceiveResponse = YES;
133     _resourceLoader->didReceiveResponse(response);
134 }
135
136 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
137 {
138     ASSERT_UNUSED(connection, !connection);
139     if (_resourceLoader->reachedTerminalState())
140         return;
141     
142     [self _sendDidReceiveResponseIfNecessary];
143
144     // QuickLook code sends us a nil data at times. The check below is the same as the one in
145     // ResourceHandleMac.cpp added for a different bug.
146     if (auto dataLength = data.length)
147         _resourceLoader->didReceiveData(reinterpret_cast<const char*>(data.bytes), dataLength, lengthReceived, DataPayloadBytes);
148 }
149
150 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
151 {
152     ASSERT_UNUSED(connection, !connection);
153     if (_resourceLoader->reachedTerminalState())
154         return;
155     
156     ASSERT(_hasSentDidReceiveResponse);
157
158     NetworkLoadMetrics emptyMetrics;
159     _resourceLoader->didFinishLoading(emptyMetrics);
160 }
161
162 static inline bool isQuickLookPasswordError(NSError *error)
163 {
164     return error.code == kQLReturnPasswordProtected && [error.domain isEqualToString:@"QuickLookErrorDomain"];
165 }
166
167 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
168 {
169     ASSERT_UNUSED(connection, !connection);
170     if (_resourceLoader->reachedTerminalState())
171         return;
172
173     if (!isQuickLookPasswordError(error)) {
174         [self _sendDidReceiveResponseIfNecessary];
175         _resourceLoader->didFail(error);
176         return;
177     }
178
179     if (!_client->supportsPasswordEntry()) {
180         _resourceLoader->didFail(_resourceLoader->cannotShowURLError());
181         return;
182     }
183
184     _client->didRequestPassword([self, retainedSelf = retainPtr(self)] (const String& password) {
185         _converter = std::make_unique<PreviewConverter>(self, _response, password);
186         [_converter->platformConverter() appendDataArray:_bufferedDataArray.get()];
187         [_converter->platformConverter() finishedAppendingData];
188     });
189 }
190
191 @end
192
193 namespace WebCore {
194
195 PreviewLoader::PreviewLoader(ResourceLoader& loader, const ResourceResponse& response)
196     : m_previewLoader { adoptNS([[WebPreviewLoader alloc] initWithResourceLoader:loader resourceResponse:response]) }
197 {
198 }
199
200 PreviewLoader::~PreviewLoader()
201 {
202 }
203
204 bool PreviewLoader::shouldCreateForMIMEType(const String& mimeType)
205 {
206     return [QLPreviewGetSupportedMIMETypesSet() containsObject:mimeType];
207 }
208
209 std::unique_ptr<PreviewLoader> PreviewLoader::create(ResourceLoader& loader, const ResourceResponse& response)
210 {
211     ASSERT(shouldCreateForMIMEType(response.mimeType()));
212     return std::make_unique<PreviewLoader>(loader, response);
213 }
214
215 bool PreviewLoader::didReceiveData(const char* data, unsigned length)
216 {
217     if (m_finishedLoadingDataIntoConverter)
218         return false;
219
220     [m_previewLoader appendDataArray:@[ [NSData dataWithBytes:data length:length] ]];
221     return true;
222 }
223
224 bool PreviewLoader::didReceiveBuffer(const SharedBuffer& buffer)
225 {
226     if (m_finishedLoadingDataIntoConverter)
227         return false;
228
229     [m_previewLoader appendDataArray:buffer.createNSDataArray().get()];
230     return true;
231 }
232
233 bool PreviewLoader::didFinishLoading()
234 {
235     if (m_finishedLoadingDataIntoConverter)
236         return false;
237
238     m_finishedLoadingDataIntoConverter = true;
239     [m_previewLoader finishedAppending];
240     return true;
241 }
242
243 void PreviewLoader::didFail()
244 {
245     if (m_finishedLoadingDataIntoConverter)
246         return;
247
248     m_finishedLoadingDataIntoConverter = true;
249     [m_previewLoader failed];
250     m_previewLoader = nullptr;
251 }
252
253 void PreviewLoader::setClientForTesting(RefPtr<PreviewLoaderClient>&& client)
254 {
255     testingClient() = WTFMove(client);
256 }
257
258 } // namespace WebCore
259
260 #endif // USE(QUICK_LOOK)