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/GOwnPtr.h>
36#include <wtf/gobject/GRefPtr.h>
37#include <wtf/text/CString.h>
38
39#if PLATFORM(GTK)
40#include <glib/gi18n-lib.h>
41#endif
42
43using namespace WebCore;
44
45namespace WebKit {
46
47class DownloadClient : public ResourceHandleClient {
48    WTF_MAKE_NONCOPYABLE(DownloadClient);
49public:
50    DownloadClient(Download* download)
51        : m_download(download)
52        , m_handleResponseLaterID(0)
53    {
54    }
55
56    ~DownloadClient()
57    {
58        if (m_handleResponseLaterID)
59            g_source_remove(m_handleResponseLaterID);
60    }
61
62    void downloadFailed(const ResourceError& error)
63    {
64        m_download->didFail(error, CoreIPC::DataReference());
65    }
66
67    void didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
68    {
69        m_response = adoptGRef(response.toSoupMessage());
70        m_download->didReceiveResponse(response);
71
72        if (response.httpStatusCode() >= 400) {
73            downloadFailed(platformDownloadNetworkError(response.httpStatusCode(), response.url().string(), response.httpStatusText()));
74            return;
75        }
76
77        String suggestedFilename = response.suggestedFilename();
78        if (suggestedFilename.isEmpty()) {
79            KURL url = response.url();
80            url.setQuery(String());
81            url.removeFragmentIdentifier();
82            suggestedFilename = decodeURLEscapeSequences(url.lastPathComponent());
83        }
84
85        bool overwrite;
86        String destinationURI = m_download->decideDestinationWithSuggestedFilename(suggestedFilename, overwrite);
87        if (destinationURI.isEmpty()) {
88#if PLATFORM(GTK)
89            GOwnPtr<char> buffer(g_strdup_printf(_("Cannot determine destination URI for download with suggested filename %s"), suggestedFilename.utf8().data()));
90            String errorMessage = String::fromUTF8(buffer.get());
91#else
92            String errorMessage = makeString("Cannot determine destination URI for download with suggested filename ", suggestedFilename);
93#endif
94            downloadFailed(platformDownloadDestinationError(response, errorMessage));
95            return;
96        }
97
98        GRefPtr<GFile> file = adoptGRef(g_file_new_for_uri(destinationURI.utf8().data()));
99        GOwnPtr<GError> error;
100        m_outputStream = adoptGRef(g_file_replace(file.get(), 0, TRUE, G_FILE_CREATE_NONE, 0, &error.outPtr()));
101        if (!m_outputStream) {
102            downloadFailed(platformDownloadDestinationError(response, error->message));
103            return;
104        }
105
106        GRefPtr<GFileInfo> info = adoptGRef(g_file_info_new());
107        g_file_info_set_attribute_string(info.get(), "metadata::download-uri", response.url().string().utf8().data());
108        g_file_set_attributes_async(file.get(), info.get(), G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, 0, 0, 0);
109
110        m_download->didCreateDestination(destinationURI);
111    }
112
113    void didReceiveData(ResourceHandle*, const char* data, int length, int /*encodedDataLength*/)
114    {
115        if (m_handleResponseLaterID) {
116            g_source_remove(m_handleResponseLaterID);
117            handleResponse();
118        }
119
120        gsize bytesWritten;
121        GOwnPtr<GError> error;
122        g_output_stream_write_all(G_OUTPUT_STREAM(m_outputStream.get()), data, length, &bytesWritten, 0, &error.outPtr());
123        if (error) {
124            downloadFailed(platformDownloadDestinationError(ResourceResponse(m_response.get()), error->message));
125            return;
126        }
127        m_download->didReceiveData(bytesWritten);
128    }
129
130    void didFinishLoading(ResourceHandle*, double)
131    {
132        m_outputStream = 0;
133        m_download->didFinish();
134    }
135
136    void didFail(ResourceHandle*, const ResourceError& error)
137    {
138        downloadFailed(platformDownloadNetworkError(error.errorCode(), error.failingURL(), error.localizedDescription()));
139    }
140
141    void wasBlocked(ResourceHandle*)
142    {
143        notImplemented();
144    }
145
146    void cannotShowURL(ResourceHandle*)
147    {
148        notImplemented();
149    }
150
151    void handleResponse()
152    {
153        m_handleResponseLaterID = 0;
154        didReceiveResponse(0, m_delayedResponse);
155    }
156
157    static gboolean handleResponseLaterCallback(DownloadClient* downloadClient)
158    {
159        downloadClient->handleResponse();
160        return FALSE;
161    }
162
163    void handleResponseLater(const ResourceResponse& response)
164    {
165        ASSERT(!m_response);
166        ASSERT(!m_handleResponseLaterID);
167
168        m_delayedResponse = response;
169
170        // Call didReceiveResponse in an idle to make sure the download is added
171        // to the DownloadManager downloads map.
172        m_handleResponseLaterID = g_idle_add_full(G_PRIORITY_DEFAULT, reinterpret_cast<GSourceFunc>(handleResponseLaterCallback), this, 0);
173    }
174
175    Download* m_download;
176    GRefPtr<GFileOutputStream> m_outputStream;
177    GRefPtr<SoupMessage> m_response;
178    ResourceResponse m_delayedResponse;
179    unsigned m_handleResponseLaterID;
180};
181
182void Download::start()
183{
184    ASSERT(!m_downloadClient);
185    ASSERT(!m_resourceHandle);
186    m_downloadClient = adoptPtr(new DownloadClient(this));
187    m_resourceHandle = ResourceHandle::create(0, m_request, m_downloadClient.get(), false, false);
188    didStart();
189}
190
191void Download::startWithHandle(ResourceHandle* resourceHandle, const ResourceResponse& response)
192{
193    ASSERT(!m_downloadClient);
194    ASSERT(!m_resourceHandle);
195    m_downloadClient = adoptPtr(new DownloadClient(this));
196    resourceHandle->setClient(m_downloadClient.get());
197    m_resourceHandle = resourceHandle;
198    didStart();
199    static_cast<DownloadClient*>(m_downloadClient.get())->handleResponseLater(response);
200}
201
202void Download::cancel()
203{
204    if (!m_resourceHandle)
205        return;
206    m_resourceHandle->cancel();
207    didCancel(CoreIPC::DataReference());
208    m_resourceHandle = 0;
209}
210
211void Download::platformInvalidate()
212{
213    if (m_resourceHandle) {
214        m_resourceHandle->setClient(0);
215        m_resourceHandle->cancel();
216        m_resourceHandle = 0;
217    }
218    m_downloadClient.release();
219}
220
221void Download::didDecideDestination(const String& /*destination*/, bool /*allowOverwrite*/)
222{
223    notImplemented();
224}
225
226void Download::platformDidFinish()
227{
228    m_resourceHandle = 0;
229}
230
231void Download::receivedCredential(const AuthenticationChallenge&, const Credential&)
232{
233    notImplemented();
234}
235
236void Download::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge&)
237{
238    notImplemented();
239}
240
241void Download::receivedCancellation(const AuthenticationChallenge&)
242{
243    notImplemented();
244}
245
246void Download::continueWithoutCredential(const AuthenticationChallenge &)
247{
248    notImplemented();
249}
250
251void Download::useCredential(const AuthenticationChallenge&, const Credential&)
252{
253    notImplemented();
254}
255
256void Download::cancelAuthenticationChallenge(const AuthenticationChallenge&)
257{
258    notImplemented();
259}
260
261} // namespace WebKit
262