1/*
2 * Copyright (C) 2007 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
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 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "ResourceLoadDelegate.h"
31
32#include "DumpRenderTree.h"
33#include "LayoutTestController.h"
34#include <WebKit/WebKitCOMAPI.h>
35#include <comutil.h>
36#include <sstream>
37#include <tchar.h>
38#include <wtf/Vector.h>
39
40using namespace std;
41
42static inline wstring wstringFromBSTR(BSTR str)
43{
44    return wstring(str, ::SysStringLen(str));
45}
46
47static inline wstring wstringFromInt(int i)
48{
49    wostringstream ss;
50    ss << i;
51    return ss.str();
52}
53
54static inline BSTR BSTRFromString(const string& str)
55{
56    int length = ::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), 0, 0);
57    BSTR result = ::SysAllocStringLen(0, length);
58    ::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), result, length);
59    return result;
60}
61
62wstring ResourceLoadDelegate::descriptionSuitableForTestResult(unsigned long identifier) const
63{
64    IdentifierMap::const_iterator it = m_urlMap.find(identifier);
65
66    if (it == m_urlMap.end())
67        return L"<unknown>";
68
69    return urlSuitableForTestResult(it->second);
70}
71
72wstring ResourceLoadDelegate::descriptionSuitableForTestResult(IWebURLRequest* request)
73{
74    if (!request)
75        return L"(null)";
76
77    BSTR urlBSTR;
78    if (FAILED(request->URL(&urlBSTR)))
79        return wstring();
80
81    wstring url = urlSuitableForTestResult(wstringFromBSTR(urlBSTR));
82    ::SysFreeString(urlBSTR);
83
84    BSTR mainDocumentURLBSTR;
85    if (FAILED(request->mainDocumentURL(&mainDocumentURLBSTR)))
86        return wstring();
87
88    wstring mainDocumentURL = urlSuitableForTestResult(wstringFromBSTR(mainDocumentURLBSTR));
89    ::SysFreeString(mainDocumentURLBSTR);
90
91    BSTR httpMethodBSTR;
92    if (FAILED(request->HTTPMethod(&httpMethodBSTR)))
93        return wstring();
94
95    wstring httpMethod = wstringFromBSTR(httpMethodBSTR);
96    ::SysFreeString(httpMethodBSTR);
97
98    return L"<NSURLRequest URL " + url + L", main document URL " + mainDocumentURL + L", http method " + httpMethod + L">";
99}
100
101wstring ResourceLoadDelegate::descriptionSuitableForTestResult(IWebURLResponse* response)
102{
103    if (!response)
104        return L"(null)";
105
106    BSTR urlBSTR;
107    if (FAILED(response->URL(&urlBSTR)))
108        return wstring();
109
110    wstring url = urlSuitableForTestResult(wstringFromBSTR(urlBSTR));
111    ::SysFreeString(urlBSTR);
112
113    int statusCode = 0;
114    COMPtr<IWebHTTPURLResponse> httpResponse;
115    if (response && SUCCEEDED(response->QueryInterface(&httpResponse)))
116        httpResponse->statusCode(&statusCode);
117
118    return L"<NSURLResponse " + url + L", http status code " + wstringFromInt(statusCode) + L">";
119}
120
121wstring ResourceLoadDelegate::descriptionSuitableForTestResult(IWebError* error, unsigned long identifier) const
122{
123    wstring result = L"<NSError ";
124
125    BSTR domainSTR;
126    if (FAILED(error->domain(&domainSTR)))
127        return wstring();
128
129    wstring domain = wstringFromBSTR(domainSTR);
130    ::SysFreeString(domainSTR);
131
132    int code;
133    if (FAILED(error->code(&code)))
134        return wstring();
135
136    if (domain == L"CFURLErrorDomain") {
137        domain = L"NSURLErrorDomain";
138
139        // Convert kCFURLErrorUnknown to NSURLErrorUnknown
140        if (code == -998)
141            code = -1;
142    } else if (domain == L"kCFErrorDomainWinSock") {
143        domain = L"NSURLErrorDomain";
144
145        // Convert the winsock error code to an NSURLError code.
146        if (code == WSAEADDRNOTAVAIL)
147            code = -1004; // NSURLErrorCannotConnectToHose;
148    }
149
150    result += L"domain " + domain;
151    result += L", code " + wstringFromInt(code);
152
153    BSTR failingURLSTR;
154    if (FAILED(error->failingURL(&failingURLSTR)))
155        return wstring();
156
157    wstring failingURL;
158
159    // If the error doesn't have a failing URL, we fake one by using the URL the resource had
160    // at creation time. This seems to work fine for now.
161    // See <rdar://problem/5064234> CFErrors should have failingURL key.
162    if (failingURLSTR)
163        failingURL = wstringFromBSTR(failingURLSTR);
164    else
165        failingURL = descriptionSuitableForTestResult(identifier);
166
167    ::SysFreeString(failingURLSTR);
168
169    result += L", failing URL \"" + urlSuitableForTestResult(failingURL) + L"\">";
170
171    return result;
172}
173
174ResourceLoadDelegate::ResourceLoadDelegate()
175    : m_refCount(1)
176{
177}
178
179ResourceLoadDelegate::~ResourceLoadDelegate()
180{
181}
182
183HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::QueryInterface(REFIID riid, void** ppvObject)
184{
185    *ppvObject = 0;
186    if (IsEqualGUID(riid, IID_IUnknown))
187        *ppvObject = static_cast<IWebResourceLoadDelegate*>(this);
188    else if (IsEqualGUID(riid, IID_IWebResourceLoadDelegate))
189        *ppvObject = static_cast<IWebResourceLoadDelegate*>(this);
190    else if (IsEqualGUID(riid, IID_IWebResourceLoadDelegatePrivate2))
191        *ppvObject = static_cast<IWebResourceLoadDelegatePrivate2*>(this);
192    else
193        return E_NOINTERFACE;
194
195    AddRef();
196    return S_OK;
197}
198
199ULONG STDMETHODCALLTYPE ResourceLoadDelegate::AddRef(void)
200{
201    return ++m_refCount;
202}
203
204ULONG STDMETHODCALLTYPE ResourceLoadDelegate::Release(void)
205{
206    ULONG newRef = --m_refCount;
207    if (!newRef)
208        delete(this);
209
210    return newRef;
211}
212
213HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::identifierForInitialRequest(
214    /* [in] */ IWebView* webView,
215    /* [in] */ IWebURLRequest* request,
216    /* [in] */ IWebDataSource* dataSource,
217    /* [in] */ unsigned long identifier)
218{
219    if (!done && gLayoutTestController->dumpResourceLoadCallbacks()) {
220        BSTR urlStr;
221        if (FAILED(request->URL(&urlStr)))
222            return E_FAIL;
223
224        ASSERT(!urlMap().contains(identifier));
225        urlMap().set(identifier, wstringFromBSTR(urlStr));
226    }
227
228    return S_OK;
229}
230
231HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::removeIdentifierForRequest(
232    /* [in] */ IWebView* webView,
233    /* [in] */ unsigned long identifier)
234{
235    urlMap().remove(identifier);
236
237    return S_OK;
238}
239
240HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::willSendRequest(
241    /* [in] */ IWebView* webView,
242    /* [in] */ unsigned long identifier,
243    /* [in] */ IWebURLRequest* request,
244    /* [in] */ IWebURLResponse* redirectResponse,
245    /* [in] */ IWebDataSource* dataSource,
246    /* [retval][out] */ IWebURLRequest **newRequest)
247{
248    if (!done && gLayoutTestController->dumpResourceLoadCallbacks()) {
249        printf("%S - willSendRequest %S redirectResponse %S\n",
250            descriptionSuitableForTestResult(identifier).c_str(),
251            descriptionSuitableForTestResult(request).c_str(),
252            descriptionSuitableForTestResult(redirectResponse).c_str());
253    }
254
255    if (!done && !gLayoutTestController->deferMainResourceDataLoad()) {
256        COMPtr<IWebDataSourcePrivate> dataSourcePrivate(Query, dataSource);
257        if (!dataSourcePrivate)
258            return E_FAIL;
259        dataSourcePrivate->setDeferMainResourceDataLoad(FALSE);
260    }
261
262    if (!done && gLayoutTestController->willSendRequestReturnsNull()) {
263        *newRequest = 0;
264        return S_OK;
265    }
266
267    if (!done && gLayoutTestController->willSendRequestReturnsNullOnRedirect() && redirectResponse) {
268        printf("Returning null for this redirect\n");
269        *newRequest = 0;
270        return S_OK;
271    }
272
273    IWebMutableURLRequest* requestCopy = 0;
274    request->mutableCopy(&requestCopy);
275    const set<string>& clearHeaders = gLayoutTestController->willSendRequestClearHeaders();
276    for (set<string>::const_iterator header = clearHeaders.begin(); header != clearHeaders.end(); ++header) {
277      BSTR bstrHeader = BSTRFromString(*header);
278      requestCopy->setValue(0, bstrHeader);
279      SysFreeString(bstrHeader);
280    }
281
282    *newRequest = requestCopy;
283    return S_OK;
284}
285
286HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::didReceiveAuthenticationChallenge(
287    /* [in] */ IWebView *webView,
288    /* [in] */ unsigned long identifier,
289    /* [in] */ IWebURLAuthenticationChallenge *challenge,
290    /* [in] */ IWebDataSource *dataSource)
291{
292    COMPtr<IWebURLAuthenticationChallengeSender> sender;
293    if (!challenge || FAILED(challenge->sender(&sender)))
294        return E_FAIL;
295
296    if (!gLayoutTestController->handlesAuthenticationChallenges()) {
297        printf("%S - didReceiveAuthenticationChallenge - Simulating cancelled authentication sheet\n", descriptionSuitableForTestResult(identifier).c_str());
298        sender->continueWithoutCredentialForAuthenticationChallenge(challenge);
299        return S_OK;
300    }
301
302    const char* user = gLayoutTestController->authenticationUsername().c_str();
303    const char* password = gLayoutTestController->authenticationPassword().c_str();
304
305    printf("%S - didReceiveAuthenticationChallenge - Responding with %s:%s\n", descriptionSuitableForTestResult(identifier).c_str(), user, password);
306
307    COMPtr<IWebURLCredential> credential;
308    if (FAILED(WebKitCreateInstance(CLSID_WebURLCredential, 0, IID_IWebURLCredential, (void**)&credential)))
309        return E_FAIL;
310    credential->initWithUser(_bstr_t(user), _bstr_t(password), WebURLCredentialPersistenceForSession);
311
312    sender->useCredential(credential.get(), challenge);
313    return S_OK;
314}
315
316HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::didReceiveResponse(
317    /* [in] */ IWebView* webView,
318    /* [in] */ unsigned long identifier,
319    /* [in] */ IWebURLResponse* response,
320    /* [in] */ IWebDataSource* dataSource)
321{
322    if (!done && gLayoutTestController->dumpResourceLoadCallbacks()) {
323        printf("%S - didReceiveResponse %S\n",
324            descriptionSuitableForTestResult(identifier).c_str(),
325            descriptionSuitableForTestResult(response).c_str());
326    }
327    if (!done && gLayoutTestController->dumpResourceResponseMIMETypes()) {
328        BSTR mimeTypeBSTR;
329        if (FAILED(response->MIMEType(&mimeTypeBSTR)))
330            E_FAIL;
331
332        wstring mimeType = wstringFromBSTR(mimeTypeBSTR);
333        ::SysFreeString(mimeTypeBSTR);
334
335        BSTR urlBSTR;
336        if (FAILED(response->URL(&urlBSTR)))
337            E_FAIL;
338
339        wstring url = wstringFromBSTR(urlBSTR);
340        ::SysFreeString(urlBSTR);
341
342        printf("%S has MIME type %S\n", lastPathComponent(url).c_str(), mimeType.c_str());
343    }
344
345    return S_OK;
346}
347
348
349HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::didFinishLoadingFromDataSource(
350    /* [in] */ IWebView* webView,
351    /* [in] */ unsigned long identifier,
352    /* [in] */ IWebDataSource* dataSource)
353{
354    if (!done && gLayoutTestController->dumpResourceLoadCallbacks()) {
355        printf("%S - didFinishLoading\n",
356            descriptionSuitableForTestResult(identifier).c_str());
357    }
358
359    removeIdentifierForRequest(webView, identifier);
360
361    return S_OK;
362}
363
364HRESULT STDMETHODCALLTYPE ResourceLoadDelegate::didFailLoadingWithError(
365    /* [in] */ IWebView* webView,
366    /* [in] */ unsigned long identifier,
367    /* [in] */ IWebError* error,
368    /* [in] */ IWebDataSource* dataSource)
369{
370    if (!done && gLayoutTestController->dumpResourceLoadCallbacks()) {
371        printf("%S - didFailLoadingWithError: %S\n",
372            descriptionSuitableForTestResult(identifier).c_str(),
373            descriptionSuitableForTestResult(error, identifier).c_str());
374    }
375
376    removeIdentifierForRequest(webView, identifier);
377
378    return S_OK;
379}
380