blob: 1a41b1372ff876c3a615d19e924a43b8591faa3e [file] [log] [blame]
/*
* Copyright (C) 2007, 2014-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ResourceLoadDelegate.h"
#include "DumpRenderTree.h"
#include "TestRunner.h"
#include <WebKitLegacy/WebKitCOMAPI.h>
#include <comutil.h>
#include <sstream>
#include <tchar.h>
#include <wtf/RetainPtr.h>
#include <wtf/Vector.h>
using namespace std;
static inline wstring wstringFromBSTR(BSTR str)
{
return wstring(str, ::SysStringLen(str));
}
static inline wstring wstringFromInt(int i)
{
wostringstream ss;
ss << i;
return ss.str();
}
wstring ResourceLoadDelegate::descriptionSuitableForTestResult(unsigned long identifier) const
{
IdentifierMap::const_iterator it = m_urlMap.find(identifier);
if (it == m_urlMap.end())
return L"<unknown>";
return urlSuitableForTestResult(it->value);
}
wstring ResourceLoadDelegate::descriptionSuitableForTestResult(IWebURLRequest* request)
{
if (!request)
return L"(null)";
_bstr_t urlBSTR;
if (FAILED(request->URL(&urlBSTR.GetBSTR())))
return wstring();
wstring url = urlSuitableForTestResult(wstringFromBSTR(urlBSTR));
_bstr_t mainDocumentURLBSTR;
if (FAILED(request->mainDocumentURL(&mainDocumentURLBSTR.GetBSTR())))
return wstring();
wstring mainDocumentURL = urlSuitableForTestResult(wstringFromBSTR(mainDocumentURLBSTR));
_bstr_t httpMethodBSTR;
if (FAILED(request->HTTPMethod(&httpMethodBSTR.GetBSTR())))
return wstring();
return L"<NSURLRequest URL " + url + L", main document URL " + mainDocumentURL + L", http method " + static_cast<wchar_t*>(httpMethodBSTR) + L">";
}
wstring ResourceLoadDelegate::descriptionSuitableForTestResult(IWebURLResponse* response)
{
if (!response)
return L"(null)";
_bstr_t urlBSTR;
if (FAILED(response->URL(&urlBSTR.GetBSTR())))
return wstring();
wstring url = urlSuitableForTestResult(wstringFromBSTR(urlBSTR));
int statusCode = 0;
COMPtr<IWebHTTPURLResponse> httpResponse;
if (response && SUCCEEDED(response->QueryInterface(&httpResponse)))
httpResponse->statusCode(&statusCode);
return L"<NSURLResponse " + url + L", http status code " + wstringFromInt(statusCode) + L">";
}
wstring ResourceLoadDelegate::descriptionSuitableForTestResult(IWebError* error, unsigned long identifier) const
{
wstring result = L"<NSError ";
_bstr_t domainSTR;
if (FAILED(error->domain(&domainSTR.GetBSTR())))
return wstring();
wstring domain = wstringFromBSTR(domainSTR);
int code;
if (FAILED(error->code(&code)))
return wstring();
if (domain == L"CFURLErrorDomain") {
domain = L"NSURLErrorDomain";
// Convert kCFURLErrorUnknown to NSURLErrorUnknown
if (code == -998)
code = -1;
} else if (domain == L"kCFErrorDomainWinSock") {
domain = L"NSURLErrorDomain";
// Convert the winsock error code to an NSURLError code.
if (code == WSAEADDRNOTAVAIL)
code = -1004; // NSURLErrorCannotConnectToHose;
}
result += L"domain " + domain;
result += L", code " + wstringFromInt(code);
_bstr_t failingURLSTR;
if (FAILED(error->failingURL(&failingURLSTR.GetBSTR())))
return wstring();
if (failingURLSTR.length())
result += L", failing URL \"" + urlSuitableForTestResult(wstringFromBSTR(failingURLSTR)) + L"\"";
result += L">";
return result;
}
ResourceLoadDelegate::ResourceLoadDelegate()
{
}
ResourceLoadDelegate::~ResourceLoadDelegate()
{
}
HRESULT ResourceLoadDelegate::QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppvObject)
{
if (!ppvObject)
return E_POINTER;
*ppvObject = nullptr;
if (IsEqualGUID(riid, IID_IUnknown))
*ppvObject = static_cast<IWebResourceLoadDelegate*>(this);
else if (IsEqualGUID(riid, IID_IWebResourceLoadDelegate))
*ppvObject = static_cast<IWebResourceLoadDelegate*>(this);
else if (IsEqualGUID(riid, IID_IWebResourceLoadDelegatePrivate2))
*ppvObject = static_cast<IWebResourceLoadDelegatePrivate2*>(this);
else
return E_NOINTERFACE;
AddRef();
return S_OK;
}
ULONG ResourceLoadDelegate::AddRef()
{
return ++m_refCount;
}
ULONG ResourceLoadDelegate::Release()
{
ULONG newRef = --m_refCount;
if (!newRef)
delete(this);
return newRef;
}
HRESULT ResourceLoadDelegate::identifierForInitialRequest(_In_opt_ IWebView* webView, _In_opt_ IWebURLRequest* request,
_In_opt_ IWebDataSource* dataSource, unsigned long identifier)
{
_bstr_t urlStr;
if (FAILED(request->URL(&urlStr.GetBSTR())))
return E_FAIL;
ASSERT(!urlMap().contains(identifier));
urlMap().set(identifier, wstringFromBSTR(urlStr));
return S_OK;
}
HRESULT ResourceLoadDelegate::removeIdentifierForRequest(_In_opt_ IWebView* webView, unsigned long identifier)
{
urlMap().remove(identifier);
return S_OK;
}
static bool isLocalhost(CFStringRef host)
{
return kCFCompareEqualTo == CFStringCompare(host, CFSTR("127.0.0.1"), 0)
|| kCFCompareEqualTo == CFStringCompare(host, CFSTR("localhost"), 0);
}
static bool hostIsUsedBySomeTestsToGenerateError(CFStringRef host)
{
return kCFCompareEqualTo == CFStringCompare(host, CFSTR("255.255.255.255"), 0);
}
HRESULT ResourceLoadDelegate::willSendRequest(_In_opt_ IWebView* webView, unsigned long identifier, _In_opt_ IWebURLRequest* request,
_In_opt_ IWebURLResponse* redirectResponse, _In_opt_ IWebDataSource* dataSource, _COM_Outptr_opt_ IWebURLRequest** newRequest)
{
if (!newRequest)
return E_POINTER;
*newRequest = nullptr;
if (!done && gTestRunner->dumpResourceLoadCallbacks()) {
fprintf(testResult, "%S - willSendRequest %S redirectResponse %S\n",
descriptionSuitableForTestResult(identifier).c_str(),
descriptionSuitableForTestResult(request).c_str(),
descriptionSuitableForTestResult(redirectResponse).c_str());
}
if (!done && gTestRunner->willSendRequestReturnsNull())
return S_OK;
if (!done && gTestRunner->willSendRequestReturnsNullOnRedirect() && redirectResponse) {
fprintf(testResult, "Returning null for this redirect\n");
return S_OK;
}
_bstr_t urlBstr;
if (FAILED(request->URL(&urlBstr.GetBSTR()))) {
fprintf(testResult, "Request has no URL\n");
return E_FAIL;
}
RetainPtr<CFStringRef> str = adoptCF(CFStringCreateWithCString(0, static_cast<const char*>(urlBstr), kCFStringEncodingWindowsLatin1));
RetainPtr<CFURLRef> url = adoptCF(CFURLCreateWithString(kCFAllocatorDefault, str.get(), nullptr));
if (url) {
RetainPtr<CFStringRef> host = adoptCF(CFURLCopyHostName(url.get()));
RetainPtr<CFStringRef> scheme = adoptCF(CFURLCopyScheme(url.get()));
if (host && ((kCFCompareEqualTo == CFStringCompare(scheme.get(), CFSTR("http"), kCFCompareCaseInsensitive))
|| (kCFCompareEqualTo == CFStringCompare(scheme.get(), CFSTR("https"), kCFCompareCaseInsensitive)))) {
RetainPtr<CFStringRef> testURL = adoptCF(CFStringCreateWithCString(kCFAllocatorDefault, gTestRunner->testURL().c_str(), kCFStringEncodingWindowsLatin1));
RetainPtr<CFMutableStringRef> lowercaseTestURL = adoptCF(CFStringCreateMutableCopy(kCFAllocatorDefault, CFStringGetLength(testURL.get()), testURL.get()));
RetainPtr<CFLocaleRef> locale = CFLocaleCopyCurrent();
CFStringLowercase(lowercaseTestURL.get(), locale.get());
RetainPtr<CFStringRef> testHost;
if (CFStringHasPrefix(lowercaseTestURL.get(), CFSTR("http:")) || CFStringHasPrefix(lowercaseTestURL.get(), CFSTR("https:"))) {
RetainPtr<CFURLRef> testPathURL = adoptCF(CFURLCreateWithString(kCFAllocatorDefault, lowercaseTestURL.get(), nullptr));
testHost = adoptCF(CFURLCopyHostName(testPathURL.get()));
}
if (!isLocalhost(host.get()) && !hostIsUsedBySomeTestsToGenerateError(host.get()) && (!testHost || isLocalhost(testHost.get()))) {
fprintf(testResult, "Blocked access to external URL %s\n", static_cast<const char*>(urlBstr));
return S_OK;
}
}
}
IWebMutableURLRequest* requestCopy = 0;
request->mutableCopy(&requestCopy);
const set<string>& clearHeaders = gTestRunner->willSendRequestClearHeaders();
for (set<string>::const_iterator header = clearHeaders.begin(); header != clearHeaders.end(); ++header) {
_bstr_t bstrHeader(header->data());
requestCopy->setValue(0, bstrHeader);
}
*newRequest = requestCopy;
return S_OK;
}
HRESULT ResourceLoadDelegate::didReceiveAuthenticationChallenge(_In_opt_ IWebView* webView, unsigned long identifier,
_In_opt_ IWebURLAuthenticationChallenge* challenge, _In_opt_ IWebDataSource* dataSource)
{
COMPtr<IWebURLAuthenticationChallengeSender> sender;
if (!challenge || FAILED(challenge->sender(&sender)))
return E_FAIL;
if (!gTestRunner->handlesAuthenticationChallenges()) {
fprintf(testResult, "%S - didReceiveAuthenticationChallenge - Simulating cancelled authentication sheet\n", descriptionSuitableForTestResult(identifier).c_str());
sender->continueWithoutCredentialForAuthenticationChallenge(challenge);
return S_OK;
}
const char* user = gTestRunner->authenticationUsername().c_str();
const char* password = gTestRunner->authenticationPassword().c_str();
fprintf(testResult, "%S - didReceiveAuthenticationChallenge - Responding with %s:%s\n", descriptionSuitableForTestResult(identifier).c_str(), user, password);
COMPtr<IWebURLCredential> credential;
if (FAILED(WebKitCreateInstance(CLSID_WebURLCredential, 0, IID_IWebURLCredential, (void**)&credential)))
return E_FAIL;
credential->initWithUser(_bstr_t(user), _bstr_t(password), WebURLCredentialPersistenceForSession);
sender->useCredential(credential.get(), challenge);
return S_OK;
}
HRESULT ResourceLoadDelegate::didReceiveResponse(_In_opt_ IWebView* webView, unsigned long identifier,
_In_opt_ IWebURLResponse* response, _In_opt_ IWebDataSource* dataSource)
{
if (!done && gTestRunner->dumpResourceLoadCallbacks()) {
fprintf(testResult, "%S - didReceiveResponse %S\n",
descriptionSuitableForTestResult(identifier).c_str(),
descriptionSuitableForTestResult(response).c_str());
}
if (!done && gTestRunner->dumpResourceResponseMIMETypes()) {
_bstr_t mimeTypeBSTR;
if (FAILED(response->MIMEType(&mimeTypeBSTR.GetBSTR())))
E_FAIL;
_bstr_t urlBSTR;
if (FAILED(response->URL(&urlBSTR.GetBSTR())))
E_FAIL;
wstring url = wstringFromBSTR(urlBSTR);
fprintf(testResult, "%S has MIME type %S\n", lastPathComponent(url).c_str(), static_cast<wchar_t*>(mimeTypeBSTR));
}
return S_OK;
}
HRESULT ResourceLoadDelegate::didFinishLoadingFromDataSource(_In_opt_ IWebView* webView, unsigned long identifier, _In_opt_ IWebDataSource* dataSource)
{
if (!done && gTestRunner->dumpResourceLoadCallbacks()) {
fprintf(testResult, "%S - didFinishLoading\n",
descriptionSuitableForTestResult(identifier).c_str());
}
removeIdentifierForRequest(webView, identifier);
return S_OK;
}
HRESULT ResourceLoadDelegate::didFailLoadingWithError(_In_opt_ IWebView* webView, unsigned long identifier, _In_opt_ IWebError* error, _In_opt_ IWebDataSource* dataSource)
{
if (!done && gTestRunner->dumpResourceLoadCallbacks()) {
fprintf(testResult, "%S - didFailLoadingWithError: %S\n",
descriptionSuitableForTestResult(identifier).c_str(),
descriptionSuitableForTestResult(error, identifier).c_str());
}
removeIdentifierForRequest(webView, identifier);
return S_OK;
}