Clean up usesAsyncCallbacks handling in ResourceHandle
[WebKit-https.git] / Source / WebKit2 / Shared / Downloads / soup / DownloadSoup.cpp
1 /*
2  * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 Brent Fulgham <bfulgham@webkit.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "Download.h"
29
30 #include "DataReference.h"
31 #include "DownloadSoupErrors.h"
32 #include <WebCore/NotImplemented.h>
33 #include <WebCore/ResourceHandleInternal.h>
34 #include <gio/gio.h>
35 #include <wtf/glib/GMainLoopSource.h>
36 #include <wtf/glib/GRefPtr.h>
37 #include <wtf/glib/GUniquePtr.h>
38 #include <wtf/text/CString.h>
39
40 #if PLATFORM(GTK)
41 #include <glib/gi18n-lib.h>
42 #endif
43
44 using namespace WebCore;
45
46 namespace WebKit {
47
48 class DownloadClient : public ResourceHandleClient {
49     WTF_MAKE_NONCOPYABLE(DownloadClient);
50 public:
51     DownloadClient(Download* download)
52         : m_download(download)
53         , m_allowOverwrite(false)
54     {
55     }
56
57     ~DownloadClient()
58     {
59     }
60
61     void deleteFilesIfNeeded()
62     {
63         if (m_destinationFile)
64             g_file_delete(m_destinationFile.get(), nullptr, nullptr);
65
66         if (m_intermediateFile) {
67             ASSERT(m_destinationFile);
68             g_file_delete(m_intermediateFile.get(), nullptr, nullptr);
69         }
70     }
71
72     void downloadFailed(const ResourceError& error)
73     {
74         deleteFilesIfNeeded();
75         m_download->didFail(error, IPC::DataReference());
76     }
77
78     void didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
79     {
80         m_response = response;
81         m_download->didReceiveResponse(response);
82
83         if (response.httpStatusCode() >= 400) {
84             downloadFailed(platformDownloadNetworkError(response.httpStatusCode(), response.url().string(), response.httpStatusText()));
85             return;
86         }
87
88         String suggestedFilename = response.suggestedFilename();
89         if (suggestedFilename.isEmpty()) {
90             URL url = response.url();
91             url.setQuery(String());
92             url.removeFragmentIdentifier();
93             suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent());
94         }
95
96         String destinationURI = m_download->decideDestinationWithSuggestedFilename(suggestedFilename, m_allowOverwrite);
97         if (destinationURI.isEmpty()) {
98 #if PLATFORM(GTK)
99             GUniquePtr<char> buffer(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data()));
100             String errorMessage = String::fromUTF8(buffer.get());
101 #else
102             String errorMessage = makeString("Cannot determine destination URI for download with suggested filename ", suggestedFilename);
103 #endif
104             downloadFailed(platformDownloadDestinationError(response, errorMessage));
105             return;
106         }
107
108         m_destinationFile = adoptGRef(g_file_new_for_uri(destinationURI.utf8().data()));
109         GRefPtr<GFileOutputStream> outputStream;
110         GUniqueOutPtr<GError> error;
111         if (m_allowOverwrite)
112             outputStream = adoptGRef(g_file_replace(m_destinationFile.get(), nullptr, FALSE, G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
113         else
114             outputStream = adoptGRef(g_file_create(m_destinationFile.get(), G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
115         if (!outputStream) {
116             m_destinationFile.clear();
117             downloadFailed(platformDownloadDestinationError(response, error->message));
118             return;
119         }
120
121         String intermediateURI = destinationURI + ".wkdownload";
122         m_intermediateFile = adoptGRef(g_file_new_for_uri(intermediateURI.utf8().data()));
123         m_outputStream = adoptGRef(g_file_replace(m_intermediateFile.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr()));
124         if (!m_outputStream) {
125             downloadFailed(platformDownloadDestinationError(response, error->message));
126             return;
127         }
128
129         m_download->didCreateDestination(destinationURI);
130     }
131
132     void didReceiveData(ResourceHandle*, const char* data, unsigned length, int /*encodedDataLength*/)
133     {
134         if (m_handleResponseLater.isScheduled()) {
135             m_handleResponseLater.cancel();
136             handleResponse();
137         }
138
139         gsize bytesWritten;
140         GUniqueOutPtr<GError> error;
141         g_output_stream_write_all(G_OUTPUT_STREAM(m_outputStream.get()), data, length, &bytesWritten, 0, &error.outPtr());
142         if (error) {
143             downloadFailed(platformDownloadDestinationError(m_response, error->message));
144             return;
145         }
146         m_download->didReceiveData(bytesWritten);
147     }
148
149     void didFinishLoading(ResourceHandle*, double)
150     {
151         m_outputStream = 0;
152
153         ASSERT(m_destinationFile);
154         ASSERT(m_intermediateFile);
155         GUniqueOutPtr<GError> error;
156         if (!g_file_move(m_intermediateFile.get(), m_destinationFile.get(), G_FILE_COPY_OVERWRITE, nullptr, nullptr, nullptr, &error.outPtr())) {
157             downloadFailed(platformDownloadDestinationError(m_response, error->message));
158             return;
159         }
160
161         GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new());
162         CString uri = m_response.url().string().utf8();
163         g_file_info_set_attribute_string(info.get(), "metadata::download-uri", uri.data());
164         g_file_info_set_attribute_string(info.get(), "xattr::xdg.origin.url", uri.data());
165         g_file_set_attributes_async(m_destinationFile.get(), info.get(), G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, nullptr, nullptr, nullptr);
166
167         m_download->didFinish();
168     }
169
170     void didFail(ResourceHandle*, const ResourceError& error)
171     {
172         downloadFailed(platformDownloadNetworkError(error.errorCode(), error.failingURL(), error.localizedDescription()));
173     }
174
175     void wasBlocked(ResourceHandle*)
176     {
177         notImplemented();
178     }
179
180     void cannotShowURL(ResourceHandle*)
181     {
182         notImplemented();
183     }
184
185     void cancel(ResourceHandle* handle)
186     {
187         handle->cancel();
188         deleteFilesIfNeeded();
189         m_download->didCancel(IPC::DataReference());
190     }
191
192     void handleResponse()
193     {
194         didReceiveResponse(nullptr, m_delayedResponse);
195     }
196
197     void handleResponseLater(const ResourceResponse& response)
198     {
199         ASSERT(m_response.isNull());
200         ASSERT(!m_handleResponseLater.isScheduled());
201
202         m_delayedResponse = response;
203
204         // Call didReceiveResponse in an idle to make sure the download is added
205         // to the DownloadManager downloads map.
206         m_handleResponseLater.schedule("[WebKit] DownloadHandleResponseLater", std::function<void()>(std::bind(&DownloadClient::handleResponse, this)));
207     }
208
209     Download* m_download;
210     GRefPtr<GFileOutputStream> m_outputStream;
211     ResourceResponse m_response;
212     GRefPtr<GFile> m_destinationFile;
213     GRefPtr<GFile> m_intermediateFile;
214     ResourceResponse m_delayedResponse;
215     GMainLoopSource m_handleResponseLater;
216     bool m_allowOverwrite;
217 };
218
219 void Download::start()
220 {
221     ASSERT(!m_downloadClient);
222     ASSERT(!m_resourceHandle);
223     m_downloadClient = std::make_unique<DownloadClient>(this);
224     m_resourceHandle = ResourceHandle::create(0, m_request, m_downloadClient.get(), false, false);
225     didStart();
226 }
227
228 void Download::startWithHandle(ResourceHandle* resourceHandle, const ResourceResponse& response)
229 {
230     ASSERT(!m_downloadClient);
231     ASSERT(!m_resourceHandle);
232     m_downloadClient = std::make_unique<DownloadClient>(this);
233     m_resourceHandle = resourceHandle->releaseForDownload(m_downloadClient.get());
234     didStart();
235     static_cast<DownloadClient*>(m_downloadClient.get())->handleResponseLater(response);
236 }
237
238 void Download::resume(const IPC::DataReference&, const String&, const SandboxExtension::Handle&)
239 {
240     notImplemented();
241 }
242
243 void Download::cancel()
244 {
245     if (!m_resourceHandle)
246         return;
247
248     // Cancelling the download will delete it and platformInvalidate() will be called by the destructor.
249     // So, we need to set m_resourceHandle to nullptr before actually cancelling the download to make sure
250     // it won't be cancelled again by platformInvalidate. See https://bugs.webkit.org/show_bug.cgi?id=127650.
251     RefPtr<ResourceHandle> resourceHandle = m_resourceHandle.release();
252     static_cast<DownloadClient*>(m_downloadClient.get())->cancel(resourceHandle.get());
253 }
254
255 void Download::platformInvalidate()
256 {
257     if (m_resourceHandle) {
258         m_resourceHandle->clearClient();
259         m_resourceHandle->cancel();
260         m_resourceHandle = nullptr;
261     }
262
263     m_downloadClient = nullptr;
264 }
265
266 void Download::platformDidFinish()
267 {
268     m_resourceHandle = nullptr;
269 }
270
271 void Download::receivedCredential(const AuthenticationChallenge&, const Credential&)
272 {
273     notImplemented();
274 }
275
276 void Download::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
277 {
278     notImplemented();
279 }
280
281 void Download::receivedCancellation(const AuthenticationChallenge&)
282 {
283     notImplemented();
284 }
285
286 void Download::continueWithoutCredential(const AuthenticationChallenge &)
287 {
288     notImplemented();
289 }
290
291 void Download::useCredential(const AuthenticationChallenge&, const Credential&)
292 {
293     notImplemented();
294 }
295
296 void Download::cancelAuthenticationChallenge(const AuthenticationChallenge&)
297 {
298     notImplemented();
299 }
300
301 void Download::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge&)
302 {
303     notImplemented();
304 }
305
306 void Download::receivedChallengeRejection(const AuthenticationChallenge&)
307 {
308     notImplemented();
309 }
310
311 } // namespace WebKit