1/*
2 * Copyright (C) 2004, 2006 Apple Inc.  All rights reserved.
3 * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.com>
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. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "ResourceHandle.h"
29
30#include "DataURL.h"
31#include "HTTPParsers.h"
32#include "MIMETypeRegistry.h"
33#include "NetworkingContext.h"
34#include "NotImplemented.h"
35#include "ResourceError.h"
36#include "ResourceHandleClient.h"
37#include "ResourceHandleInternal.h"
38#include "SharedBuffer.h"
39#include "Timer.h"
40
41#include <wtf/MainThread.h>
42#include <wtf/text/CString.h>
43
44#include <windows.h>
45#include <wininet.h>
46
47namespace WebCore {
48
49static inline HINTERNET createInternetHandle(const String& userAgent, bool asynchronous)
50{
51    String userAgentString = userAgent;
52    HINTERNET internetHandle = InternetOpenW(userAgentString.charactersWithNullTermination().data(), INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, asynchronous ? INTERNET_FLAG_ASYNC : 0);
53
54    if (asynchronous)
55        InternetSetStatusCallback(internetHandle, &ResourceHandle::internetStatusCallback);
56
57    return internetHandle;
58}
59
60static HINTERNET asynchronousInternetHandle(const String& userAgent)
61{
62    static HINTERNET internetHandle = createInternetHandle(userAgent, true);
63    return internetHandle;
64}
65
66static String queryHTTPHeader(HINTERNET requestHandle, DWORD infoLevel)
67{
68    DWORD bufferSize = 0;
69    HttpQueryInfoW(requestHandle, infoLevel, 0, &bufferSize, 0);
70
71    Vector<UChar> characters(bufferSize / sizeof(UChar));
72
73    if (!HttpQueryInfoW(requestHandle, infoLevel, characters.data(), &bufferSize, 0))
74        return String();
75
76    characters.removeLast(); // Remove NullTermination.
77    return String::adopt(characters);
78}
79
80
81class WebCoreSynchronousLoader : public ResourceHandleClient {
82    WTF_MAKE_NONCOPYABLE(WebCoreSynchronousLoader);
83public:
84    WebCoreSynchronousLoader(ResourceError&, ResourceResponse&, Vector<char>&, const String& userAgent);
85    ~WebCoreSynchronousLoader();
86
87    HINTERNET internetHandle() const { return m_internetHandle; }
88
89    virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
90    virtual void didReceiveData(ResourceHandle*, const char*, unsigned, int encodedDataLength);
91    virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
92    virtual void didFail(ResourceHandle*, const ResourceError&);
93
94private:
95    ResourceError& m_error;
96    ResourceResponse& m_response;
97    Vector<char>& m_data;
98    HINTERNET m_internetHandle;
99};
100
101WebCoreSynchronousLoader::WebCoreSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector<char>& data, const String& userAgent)
102    : m_error(error)
103    , m_response(response)
104    , m_data(data)
105    , m_internetHandle(createInternetHandle(userAgent, false))
106{
107}
108
109WebCoreSynchronousLoader::~WebCoreSynchronousLoader()
110{
111    InternetCloseHandle(m_internetHandle);
112}
113
114void WebCoreSynchronousLoader::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
115{
116    m_response = response;
117}
118
119void WebCoreSynchronousLoader::didReceiveData(ResourceHandle*, const char* data, unsigned length, int)
120{
121    m_data.append(data, length);
122}
123
124void WebCoreSynchronousLoader::didFinishLoading(ResourceHandle*, double)
125{
126}
127
128void WebCoreSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
129{
130    m_error = error;
131}
132
133
134ResourceHandleInternal::~ResourceHandleInternal()
135{
136}
137
138ResourceHandle::~ResourceHandle()
139{
140}
141
142static void callOnRedirect(void* context)
143{
144    ResourceHandle* handle = static_cast<ResourceHandle*>(context);
145    handle->onRedirect();
146}
147
148static void callOnRequestComplete(void* context)
149{
150    ResourceHandle* handle = static_cast<ResourceHandle*>(context);
151    handle->onRequestComplete();
152}
153
154void ResourceHandle::internetStatusCallback(HINTERNET internetHandle, DWORD_PTR context, DWORD internetStatus,
155                                                     LPVOID statusInformation, DWORD statusInformationLength)
156{
157    ResourceHandle* handle = reinterpret_cast<ResourceHandle*>(context);
158
159    switch (internetStatus) {
160    case INTERNET_STATUS_REDIRECT:
161        handle->d->m_redirectUrl = String(static_cast<UChar*>(statusInformation), statusInformationLength);
162        callOnMainThread(callOnRedirect, handle);
163        break;
164
165    case INTERNET_STATUS_REQUEST_COMPLETE:
166        callOnMainThread(callOnRequestComplete, handle);
167        break;
168
169    default:
170        break;
171    }
172}
173
174void ResourceHandle::onRedirect()
175{
176    ResourceRequest newRequest = firstRequest();
177    newRequest.setURL(URL(ParsedURLString, d->m_redirectUrl));
178
179    ResourceResponse response(firstRequest().url(), String(), 0, String(), String());
180
181    if (ResourceHandleClient* resourceHandleClient = client())
182        resourceHandleClient->willSendRequest(this, newRequest, response);
183}
184
185bool ResourceHandle::onRequestComplete()
186{
187    if (!d->m_internetHandle) { // 0 if canceled.
188        deref(); // balances ref in start
189        return false;
190    }
191
192    if (d->m_bytesRemainingToWrite) {
193        DWORD bytesWritten;
194        InternetWriteFile(d->m_requestHandle,
195                          d->m_formData.data() + (d->m_formData.size() - d->m_bytesRemainingToWrite),
196                          d->m_bytesRemainingToWrite,
197                          &bytesWritten);
198        d->m_bytesRemainingToWrite -= bytesWritten;
199        if (d->m_bytesRemainingToWrite)
200            return true;
201        d->m_formData.clear();
202    }
203
204    if (!d->m_sentEndRequest) {
205        HttpEndRequestW(d->m_requestHandle, 0, 0, reinterpret_cast<DWORD_PTR>(this));
206        d->m_sentEndRequest = true;
207        return true;
208    }
209
210    static const int bufferSize = 32768;
211    char buffer[bufferSize];
212    INTERNET_BUFFERSA buffers;
213    buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
214    buffers.lpvBuffer = buffer;
215    buffers.dwBufferLength = bufferSize;
216
217    BOOL ok = FALSE;
218    while ((ok = InternetReadFileExA(d->m_requestHandle, &buffers, d->m_loadSynchronously ? 0 : IRF_NO_WAIT, reinterpret_cast<DWORD_PTR>(this))) && buffers.dwBufferLength) {
219        if (!d->m_hasReceivedResponse) {
220            d->m_hasReceivedResponse = true;
221
222            ResourceResponse response;
223            response.setURL(firstRequest().url());
224
225            String httpStatusText = queryHTTPHeader(d->m_requestHandle, HTTP_QUERY_STATUS_TEXT);
226            if (!httpStatusText.isNull())
227                response.setHTTPStatusText(httpStatusText);
228
229            String httpStatusCode = queryHTTPHeader(d->m_requestHandle, HTTP_QUERY_STATUS_CODE);
230            if (!httpStatusCode.isNull())
231                response.setHTTPStatusCode(httpStatusCode.toInt());
232
233            String httpContentLength = queryHTTPHeader(d->m_requestHandle, HTTP_QUERY_CONTENT_LENGTH);
234            if (!httpContentLength.isNull())
235                response.setExpectedContentLength(httpContentLength.toInt());
236
237            String httpContentType = queryHTTPHeader(d->m_requestHandle, HTTP_QUERY_CONTENT_TYPE);
238            if (!httpContentType.isNull()) {
239                response.setMimeType(extractMIMETypeFromMediaType(httpContentType));
240                response.setTextEncodingName(extractCharsetFromMediaType(httpContentType));
241            }
242
243            if (ResourceHandleClient* resourceHandleClient = client())
244                resourceHandleClient->didReceiveResponse(this, response);
245        }
246
247        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
248        // -1 means we do not provide any data about transfer size to inspector so it would use
249        // Content-Length headers or content size to show transfer size.
250        if (ResourceHandleClient* resourceHandleClient = client())
251            resourceHandleClient->didReceiveData(this, buffer, buffers.dwBufferLength, -1);
252        buffers.dwBufferLength = bufferSize;
253    }
254
255    if (!ok && GetLastError() == ERROR_IO_PENDING)
256        return true;
257
258    if (ResourceHandleClient* resourceHandleClient = client())
259        resourceHandleClient->didFinishLoading(this, 0);
260
261    InternetCloseHandle(d->m_requestHandle);
262    InternetCloseHandle(d->m_connectHandle);
263    deref(); // balances ref in start
264    return false;
265}
266
267bool ResourceHandle::start()
268{
269    if (firstRequest().url().isLocalFile() || firstRequest().url().protocolIsData()) {
270        ref(); // balanced by deref in fileLoadTimer
271        if (d->m_loadSynchronously)
272            fileLoadTimer(0);
273        else
274            d->m_fileLoadTimer.startOneShot(0.0);
275        return true;
276    }
277
278    if (!d->m_internetHandle)
279        d->m_internetHandle = asynchronousInternetHandle(d->m_context->userAgent());
280
281    if (!d->m_internetHandle)
282        return false;
283
284    DWORD flags = INTERNET_FLAG_KEEP_CONNECTION
285        | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
286        | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
287        | INTERNET_FLAG_DONT_CACHE
288        | INTERNET_FLAG_RELOAD;
289
290    d->m_connectHandle = InternetConnectW(d->m_internetHandle, firstRequest().url().host().charactersWithNullTermination().data(), firstRequest().url().port(),
291                                          0, 0, INTERNET_SERVICE_HTTP, flags, reinterpret_cast<DWORD_PTR>(this));
292
293    if (!d->m_connectHandle)
294        return false;
295
296    String urlStr = firstRequest().url().path();
297    String urlQuery = firstRequest().url().query();
298
299    if (!urlQuery.isEmpty()) {
300        urlStr.append('?');
301        urlStr.append(urlQuery);
302    }
303
304    String httpMethod = firstRequest().httpMethod();
305    String httpReferrer = firstRequest().httpReferrer();
306
307    LPCWSTR httpAccept[] = { L"*/*", 0 };
308
309    d->m_requestHandle = HttpOpenRequestW(d->m_connectHandle, httpMethod.charactersWithNullTermination().data(), urlStr.charactersWithNullTermination().data(),
310                                          0, httpReferrer.charactersWithNullTermination().data(), httpAccept, flags, reinterpret_cast<DWORD_PTR>(this));
311
312    if (!d->m_requestHandle) {
313        InternetCloseHandle(d->m_connectHandle);
314        return false;
315    }
316
317    if (firstRequest().httpBody()) {
318        firstRequest().httpBody()->flatten(d->m_formData);
319        d->m_bytesRemainingToWrite = d->m_formData.size();
320    }
321
322    Vector<UChar> httpHeaders;
323    const HTTPHeaderMap& httpHeaderFields = firstRequest().httpHeaderFields();
324
325    for (HTTPHeaderMap::const_iterator it = httpHeaderFields.begin(); it != httpHeaderFields.end(); ++it) {
326        if (equalIgnoringCase(it->key, "Accept") || equalIgnoringCase(it->key, "Referer") || equalIgnoringCase(it->key, "User-Agent"))
327            continue;
328
329        if (!httpHeaders.isEmpty())
330            httpHeaders.append('\n');
331
332        httpHeaders.append(it->key.characters(), it->key.length());
333        httpHeaders.append(':');
334        httpHeaders.append(it->value.characters(), it->value.length());
335    }
336
337    INTERNET_BUFFERSW internetBuffers;
338    ZeroMemory(&internetBuffers, sizeof(internetBuffers));
339    internetBuffers.dwStructSize = sizeof(internetBuffers);
340    internetBuffers.lpcszHeader = httpHeaders.data();
341    internetBuffers.dwHeadersLength = httpHeaders.size();
342    internetBuffers.dwBufferTotal = d->m_bytesRemainingToWrite;
343
344    HttpSendRequestExW(d->m_requestHandle, &internetBuffers, 0, 0, reinterpret_cast<DWORD_PTR>(this));
345
346    ref(); // balanced by deref in onRequestComplete
347
348    if (d->m_loadSynchronously)
349        while (onRequestComplete()) {
350            // Loop until finished.
351        }
352
353    return true;
354}
355
356void ResourceHandle::fileLoadTimer(Timer<ResourceHandle>*)
357{
358    RefPtr<ResourceHandle> protector(this);
359    deref(); // balances ref in start
360
361    if (firstRequest().url().protocolIsData()) {
362        handleDataURL(this);
363        return;
364    }
365
366    String fileName = firstRequest().url().fileSystemPath();
367    HANDLE fileHandle = CreateFileW(fileName.charactersWithNullTermination().data(), GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
368
369    if (fileHandle == INVALID_HANDLE_VALUE) {
370        client()->didFail(this, ResourceError());
371        return;
372    }
373
374    ResourceResponse response;
375
376    int dotPos = fileName.reverseFind('.');
377    int slashPos = fileName.reverseFind('/');
378
379    if (slashPos < dotPos && dotPos != -1) {
380        String ext = fileName.substring(dotPos + 1);
381        response.setMimeType(MIMETypeRegistry::getMIMETypeForExtension(ext));
382    }
383
384    client()->didReceiveResponse(this, response);
385
386    bool result = false;
387    DWORD bytesRead = 0;
388
389    do {
390        const int bufferSize = 8192;
391        char buffer[bufferSize];
392        result = ReadFile(fileHandle, &buffer, bufferSize, &bytesRead, 0);
393        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
394        // -1 means we do not provide any data about transfer size to inspector so it would use
395        // Content-Length headers or content size to show transfer size.
396        if (result && bytesRead)
397            client()->didReceiveData(this, buffer, bytesRead, -1);
398        // Check for end of file.
399    } while (result && bytesRead);
400
401    CloseHandle(fileHandle);
402
403    client()->didFinishLoading(this, 0);
404}
405
406void ResourceHandle::cancel()
407{
408    if (d->m_requestHandle) {
409        d->m_internetHandle = 0;
410        InternetCloseHandle(d->m_requestHandle);
411        InternetCloseHandle(d->m_connectHandle);
412    } else
413        d->m_fileLoadTimer.stop();
414}
415
416void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
417{
418    UNUSED_PARAM(storedCredentials);
419
420    WebCoreSynchronousLoader syncLoader(error, response, data, request.httpUserAgent());
421    ResourceHandle handle(context, request, &syncLoader, true, false);
422
423    handle.setSynchronousInternetHandle(syncLoader.internetHandle());
424    handle.start();
425}
426
427void ResourceHandle::setSynchronousInternetHandle(HINTERNET internetHandle)
428{
429    d->m_internetHandle = internetHandle;
430    d->m_loadSynchronously = true;
431}
432
433void prefetchDNS(const String&)
434{
435    notImplemented();
436}
437
438bool ResourceHandle::loadsBlocked()
439{
440    return false;
441}
442
443void ResourceHandle::platformSetDefersLoading(bool)
444{
445    notImplemented();
446}
447
448} // namespace WebCore
449