68079176f8dd7c37b71f484a2ed4f3be46b4c21d
[WebKit-https.git] / Source / WebKit2 / NetworkProcess / 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/RunLoop.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 final : public ResourceHandleClient {
49     WTF_MAKE_NONCOPYABLE(DownloadClient);
50 public:
51     DownloadClient(Download& download)
52         : m_download(download)
53         , m_handleResponseLater(RunLoop::main(), this, &DownloadClient::handleResponse)
54         , m_allowOverwrite(false)
55     {
56     }
57
58     ~DownloadClient()
59     {
60     }
61
62     void deleteFilesIfNeeded()
63     {
64         if (m_destinationFile)
65             g_file_delete(m_destinationFile.get(), nullptr, nullptr);
66
67         if (m_intermediateFile) {
68             ASSERT(m_destinationFile);
69             g_file_delete(m_intermediateFile.get(), nullptr, nullptr);
70         }
71     }
72
73     void downloadFailed(const ResourceError& error)
74     {
75         deleteFilesIfNeeded();
76         m_download.didFail(error, IPC::DataReference());
77     }
78
79     void didReceiveResponse(ResourceHandle*, ResourceResponse&& response) override
80     {
81         m_response = WTFMove(response);
82         m_download.didReceiveResponse(m_response);
83
84         if (m_response.httpStatusCode() >= 400) {
85             downloadFailed(platformDownloadNetworkError(m_response.httpStatusCode(), m_response.url(), m_response.httpStatusText()));
86             return;
87         }
88
89         String suggestedFilename = m_response.suggestedFilename();
90         if (suggestedFilename.isEmpty()) {
91             URL url = m_response.url();
92             url.setQuery(String());
93             url.removeFragmentIdentifier();
94             suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent());
95         }
96
97         String destinationURI = m_download.decideDestinationWithSuggestedFilename(suggestedFilename, m_allowOverwrite);
98         if (destinationURI.isEmpty()) {
99 #if PLATFORM(GTK)
100             GUniquePtr<char> buffer(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data()));
101             String errorMessage = String::fromUTF8(buffer.get());
102 #else
103             String errorMessage = makeString("Cannot determine destination URI for download with suggested filename ", suggestedFilename);
104 #endif
105             downloadFailed(platformDownloadDestinationError(m_response, errorMessage));
106             return;
107         }
108
109         m_destinationFile = adoptGRef(g_file_new_for_uri(destinationURI.utf8().data()));
110         GRefPtr<GFileOutputStream> outputStream;
111         GUniqueOutPtr<GError> error;
112         if (m_allowOverwrite)
113             outputStream = adoptGRef(g_file_replace(m_destinationFile.get(), nullptr, FALSE, G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
114         else
115             outputStream = adoptGRef(g_file_create(m_destinationFile.get(), G_FILE_CREATE_NONE, nullptr, &error.outPtr()));
116         if (!outputStream) {
117             m_destinationFile.clear();
118             downloadFailed(platformDownloadDestinationError(m_response, error->message));
119             return;
120         }
121
122         String intermediateURI = destinationURI + ".wkdownload";
123         m_intermediateFile = adoptGRef(g_file_new_for_uri(intermediateURI.utf8().data()));
124         m_outputStream = adoptGRef(g_file_replace(m_intermediateFile.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr()));
125         if (!m_outputStream) {
126             downloadFailed(platformDownloadDestinationError(m_response, error->message));
127             return;
128         }
129
130         m_download.didCreateDestination(destinationURI);
131     }
132
133     void didReceiveData(ResourceHandle*, const char* data, unsigned length, int /*encodedDataLength*/) override
134     {
135         if (m_handleResponseLater.isActive()) {
136             m_handleResponseLater.stop();
137             handleResponse();
138         }
139
140         gsize bytesWritten;
141         GUniqueOutPtr<GError> error;
142         g_output_stream_write_all(G_OUTPUT_STREAM(m_outputStream.get()), data, length, &bytesWritten, 0, &error.outPtr());
143         if (error) {
144             downloadFailed(platformDownloadDestinationError(m_response, error->message));
145             return;
146         }
147         m_download.didReceiveData(bytesWritten);
148     }
149
150     void didFinishLoading(ResourceHandle*, double) override
151     {
152         m_outputStream = nullptr;
153
154         ASSERT(m_destinationFile);
155         ASSERT(m_intermediateFile);
156         GUniqueOutPtr<GError> error;
157         if (!g_file_move(m_intermediateFile.get(), m_destinationFile.get(), G_FILE_COPY_OVERWRITE, nullptr, nullptr, nullptr, &error.outPtr())) {
158             downloadFailed(platformDownloadDestinationError(m_response, error->message));
159             return;
160         }
161
162         GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new());
163         CString uri = m_response.url().string().utf8();
164         g_file_info_set_attribute_string(info.get(), "metadata::download-uri", uri.data());
165         g_file_info_set_attribute_string(info.get(), "xattr::xdg.origin.url", uri.data());
166         g_file_set_attributes_async(m_destinationFile.get(), info.get(), G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, nullptr, nullptr, nullptr);
167
168         m_download.didFinish();
169     }
170
171     void didFail(ResourceHandle*, const ResourceError& error) override
172     {
173         downloadFailed(platformDownloadNetworkError(error.errorCode(), error.failingURL(), error.localizedDescription()));
174     }
175
176     void cancel(ResourceHandle* handle)
177     {
178         handle->cancel();
179         deleteFilesIfNeeded();
180         m_download.didCancel(IPC::DataReference());
181     }
182
183     void handleResponse()
184     {
185         didReceiveResponse(nullptr, WTFMove(m_delayedResponse));
186     }
187
188     void handleResponseLater(const ResourceResponse& response)
189     {
190         ASSERT(m_response.isNull());
191         ASSERT(!m_handleResponseLater.isActive());
192
193         m_delayedResponse = response;
194
195         // Call didReceiveResponse in an idle to make sure the download is added
196         // to the DownloadManager downloads map.
197         m_handleResponseLater.startOneShot(0);
198     }
199
200     Download& m_download;
201     GRefPtr<GFileOutputStream> m_outputStream;
202     ResourceResponse m_response;
203     GRefPtr<GFile> m_destinationFile;
204     GRefPtr<GFile> m_intermediateFile;
205     ResourceResponse m_delayedResponse;
206     RunLoop::Timer<DownloadClient> m_handleResponseLater;
207     bool m_allowOverwrite;
208 };
209
210 void Download::startNetworkLoad()
211 {
212     ASSERT(!m_downloadClient);
213     ASSERT(!m_resourceHandle);
214     m_downloadClient = std::make_unique<DownloadClient>(*this);
215     m_resourceHandle = ResourceHandle::create(0, m_request, m_downloadClient.get(), false, true);
216     didStart();
217 }
218
219 void Download::startNetworkLoadWithHandle(ResourceHandle* resourceHandle, const ResourceResponse& response)
220 {
221     ASSERT(!m_downloadClient);
222     ASSERT(!m_resourceHandle);
223     m_downloadClient = std::make_unique<DownloadClient>(*this);
224     m_resourceHandle = resourceHandle->releaseForDownload(m_downloadClient.get());
225     didStart();
226     static_cast<DownloadClient*>(m_downloadClient.get())->handleResponseLater(response);
227 }
228
229 void Download::resume(const IPC::DataReference&, const String&, const SandboxExtension::Handle&)
230 {
231     notImplemented();
232 }
233
234 void Download::cancelNetworkLoad()
235 {
236     if (!m_resourceHandle)
237         return;
238
239     // Cancelling the download will delete it and platformInvalidate() will be called by the destructor.
240     // So, we need to set m_resourceHandle to nullptr before actually cancelling the download to make sure
241     // it won't be cancelled again by platformInvalidate. See https://bugs.webkit.org/show_bug.cgi?id=127650.
242     RefPtr<ResourceHandle> resourceHandle = m_resourceHandle.release();
243     static_cast<DownloadClient*>(m_downloadClient.get())->cancel(resourceHandle.get());
244 }
245
246 void Download::platformInvalidate()
247 {
248 }
249
250 void Download::platformDidFinish()
251 {
252     m_resourceHandle = nullptr;
253 }
254
255 } // namespace WebKit