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/gobject/GMainLoopSource.h> 36#include <wtf/gobject/GRefPtr.h> 37#include <wtf/gobject/GUniquePtr.h> 38#include <wtf/text/CString.h> 39 40#if PLATFORM(GTK) 41#include <glib/gi18n-lib.h> 42#endif 43 44using namespace WebCore; 45 46namespace WebKit { 47 48class DownloadClient : public ResourceHandleClient { 49 WTF_MAKE_NONCOPYABLE(DownloadClient); 50public: 51 DownloadClient(Download* download) 52 : m_download(download) 53 { 54 } 55 56 ~DownloadClient() 57 { 58 } 59 60 void deleteIntermediateFileInNeeded() 61 { 62 if (!m_intermediateFile) 63 return; 64 g_file_delete(m_intermediateFile.get(), nullptr, nullptr); 65 } 66 67 void downloadFailed(const ResourceError& error) 68 { 69 deleteIntermediateFileInNeeded(); 70 m_download->didFail(error, IPC::DataReference()); 71 } 72 73 void didReceiveResponse(ResourceHandle*, const ResourceResponse& response) 74 { 75 m_response = response; 76 m_download->didReceiveResponse(response); 77 78 if (response.httpStatusCode() >= 400) { 79 downloadFailed(platformDownloadNetworkError(response.httpStatusCode(), response.url().string(), response.httpStatusText())); 80 return; 81 } 82 83 String suggestedFilename = response.suggestedFilename(); 84 if (suggestedFilename.isEmpty()) { 85 URL url = response.url(); 86 url.setQuery(String()); 87 url.removeFragmentIdentifier(); 88 suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent()); 89 } 90 91 bool overwrite; 92 m_destinationURI = m_download->decideDestinationWithSuggestedFilename(suggestedFilename, overwrite); 93 if (m_destinationURI.isEmpty()) { 94#if PLATFORM(GTK) 95 GUniquePtr<char> buffer(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data())); 96 String errorMessage = String::fromUTF8(buffer.get()); 97#else 98 String errorMessage = makeString("Cannot determine destination URI for download with suggested filename ", suggestedFilename); 99#endif 100 downloadFailed(platformDownloadDestinationError(response, errorMessage)); 101 return; 102 } 103 104 String intermediateURI = m_destinationURI + ".wkdownload"; 105 m_intermediateFile = adoptGRef(g_file_new_for_uri(intermediateURI.utf8().data())); 106 GUniqueOutPtr<GError> error; 107 m_outputStream = adoptGRef(g_file_replace(m_intermediateFile.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr())); 108 if (!m_outputStream) { 109 downloadFailed(platformDownloadDestinationError(response, error->message)); 110 return; 111 } 112 113 m_download->didCreateDestination(m_destinationURI); 114 } 115 116 void didReceiveData(ResourceHandle*, const char* data, unsigned length, int /*encodedDataLength*/) 117 { 118 if (m_handleResponseLater.isScheduled()) { 119 m_handleResponseLater.cancel(); 120 handleResponse(); 121 } 122 123 gsize bytesWritten; 124 GUniqueOutPtr<GError> error; 125 g_output_stream_write_all(G_OUTPUT_STREAM(m_outputStream.get()), data, length, &bytesWritten, 0, &error.outPtr()); 126 if (error) { 127 downloadFailed(platformDownloadDestinationError(m_response, error->message)); 128 return; 129 } 130 m_download->didReceiveData(bytesWritten); 131 } 132 133 void didFinishLoading(ResourceHandle*, double) 134 { 135 m_outputStream = 0; 136 137 ASSERT(m_intermediateFile); 138 GRefPtr<GFile> destinationFile = adoptGRef(g_file_new_for_uri(m_destinationURI.utf8().data())); 139 GUniqueOutPtr<GError> error; 140 if (!g_file_move(m_intermediateFile.get(), destinationFile.get(), G_FILE_COPY_NONE, nullptr, nullptr, nullptr, &error.outPtr())) { 141 downloadFailed(platformDownloadDestinationError(m_response, error->message)); 142 return; 143 } 144 145 GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new()); 146 CString uri = m_response.url().string().utf8(); 147 g_file_info_set_attribute_string(info.get(), "metadata::download-uri", uri.data()); 148 g_file_info_set_attribute_string(info.get(), "xattr::xdg.origin.url", uri.data()); 149 g_file_set_attributes_async(destinationFile.get(), info.get(), G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, nullptr, nullptr, nullptr); 150 151 m_download->didFinish(); 152 } 153 154 void didFail(ResourceHandle*, const ResourceError& error) 155 { 156 downloadFailed(platformDownloadNetworkError(error.errorCode(), error.failingURL(), error.localizedDescription())); 157 } 158 159 void wasBlocked(ResourceHandle*) 160 { 161 notImplemented(); 162 } 163 164 void cannotShowURL(ResourceHandle*) 165 { 166 notImplemented(); 167 } 168 169 void cancel(ResourceHandle* handle) 170 { 171 handle->cancel(); 172 deleteIntermediateFileInNeeded(); 173 m_download->didCancel(IPC::DataReference()); 174 } 175 176 void handleResponse() 177 { 178 didReceiveResponse(nullptr, m_delayedResponse); 179 } 180 181 void handleResponseLater(const ResourceResponse& response) 182 { 183 ASSERT(m_response.isNull()); 184 ASSERT(!m_handleResponseLater.isScheduled()); 185 186 m_delayedResponse = response; 187 188 // Call didReceiveResponse in an idle to make sure the download is added 189 // to the DownloadManager downloads map. 190 m_handleResponseLater.schedule("[WebKit] DownloadHandleResponseLater", std::function<void()>(std::bind(&DownloadClient::handleResponse, this))); 191 } 192 193 Download* m_download; 194 GRefPtr<GFileOutputStream> m_outputStream; 195 ResourceResponse m_response; 196 String m_destinationURI; 197 GRefPtr<GFile> m_intermediateFile; 198 ResourceResponse m_delayedResponse; 199 GMainLoopSource m_handleResponseLater; 200}; 201 202void Download::start() 203{ 204 ASSERT(!m_downloadClient); 205 ASSERT(!m_resourceHandle); 206 m_downloadClient = std::make_unique<DownloadClient>(this); 207 m_resourceHandle = ResourceHandle::create(0, m_request, m_downloadClient.get(), false, false); 208 didStart(); 209} 210 211void Download::startWithHandle(ResourceHandle* resourceHandle, const ResourceResponse& response) 212{ 213 ASSERT(!m_downloadClient); 214 ASSERT(!m_resourceHandle); 215 m_downloadClient = std::make_unique<DownloadClient>(this); 216 resourceHandle->setClient(m_downloadClient.get()); 217 m_resourceHandle = resourceHandle; 218 didStart(); 219 static_cast<DownloadClient*>(m_downloadClient.get())->handleResponseLater(response); 220} 221 222void Download::cancel() 223{ 224 if (!m_resourceHandle) 225 return; 226 227 // Cancelling the download will delete it and platformInvalidate() will be called by the destructor. 228 // So, we need to set m_resourceHandle to nullptr before actually cancelling the download to make sure 229 // it won't be cancelled again by platformInvalidate. See https://bugs.webkit.org/show_bug.cgi?id=127650. 230 RefPtr<ResourceHandle> resourceHandle = m_resourceHandle.release(); 231 static_cast<DownloadClient*>(m_downloadClient.get())->cancel(resourceHandle.get()); 232} 233 234void Download::platformInvalidate() 235{ 236 if (m_resourceHandle) { 237 m_resourceHandle->setClient(0); 238 m_resourceHandle->cancel(); 239 m_resourceHandle = 0; 240 } 241 242 m_downloadClient = nullptr; 243} 244 245void Download::didDecideDestination(const String& /*destination*/, bool /*allowOverwrite*/) 246{ 247 notImplemented(); 248} 249 250void Download::platformDidFinish() 251{ 252 m_resourceHandle = 0; 253} 254 255void Download::receivedCredential(const AuthenticationChallenge&, const Credential&) 256{ 257 notImplemented(); 258} 259 260void Download::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&) 261{ 262 notImplemented(); 263} 264 265void Download::receivedCancellation(const AuthenticationChallenge&) 266{ 267 notImplemented(); 268} 269 270void Download::continueWithoutCredential(const AuthenticationChallenge &) 271{ 272 notImplemented(); 273} 274 275void Download::useCredential(const AuthenticationChallenge&, const Credential&) 276{ 277 notImplemented(); 278} 279 280void Download::cancelAuthenticationChallenge(const AuthenticationChallenge&) 281{ 282 notImplemented(); 283} 284 285void Download::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge&) 286{ 287 notImplemented(); 288} 289 290void Download::receivedChallengeRejection(const AuthenticationChallenge&) 291{ 292 notImplemented(); 293} 294 295} // namespace WebKit 296