blob: 337b752e68c1dca51f25588ef3ab1c929116529a [file] [log] [blame]
/*
* Copyright (C) 2004, 2006 Apple Computer, 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 COMPUTER, INC. OR
* 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 "ResourceHandle.h"
#include "ResourceHandleClient.h"
#include "ResourceHandleInternal.h"
#include "ResourceHandleWin.h"
#include "CString.h"
#include "DocLoader.h"
#include "Document.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "Page.h"
#include "ResourceError.h"
#include "Timer.h"
#include <windows.h>
#include <wininet.h>
namespace WebCore {
static unsigned transferJobId = 0;
static HashMap<int, ResourceHandle*>* jobIdMap = 0;
static HWND transferJobWindowHandle = 0;
const LPCWSTR kResourceHandleWindowClassName = L"ResourceHandleWindowClass";
// Message types for internal use (keep in sync with kMessageHandlers)
enum {
handleCreatedMessage = WM_USER,
requestRedirectedMessage,
requestCompleteMessage
};
typedef void (ResourceHandle:: *ResourceHandleEventHandler)(LPARAM);
static const ResourceHandleEventHandler messageHandlers[] = {
&ResourceHandle::onHandleCreated,
&ResourceHandle::onRequestRedirected,
&ResourceHandle::onRequestComplete
};
static int addToOutstandingJobs(ResourceHandle* job)
{
if (!jobIdMap)
jobIdMap = new HashMap<int, ResourceHandle*>;
transferJobId++;
jobIdMap->set(transferJobId, job);
return transferJobId;
}
static void removeFromOutstandingJobs(int jobId)
{
if (!jobIdMap)
return;
jobIdMap->remove(jobId);
}
static ResourceHandle* lookupResourceHandle(int jobId)
{
if (!jobIdMap)
return 0;
return jobIdMap->get(jobId);
}
static LRESULT CALLBACK ResourceHandleWndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
if (message >= handleCreatedMessage) {
UINT index = message - handleCreatedMessage;
if (index < _countof(messageHandlers)) {
unsigned jobId = (unsigned) wParam;
ResourceHandle* job = lookupResourceHandle(jobId);
if (job) {
ASSERT(job->d->m_jobId == jobId);
ASSERT(job->d->m_threadId == GetCurrentThreadId());
(job->*(messageHandlers[index]))(lParam);
}
return 0;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
static void initializeOffScreenResourceHandleWindow()
{
if (transferJobWindowHandle)
return;
WNDCLASSEX wcex;
memset(&wcex, 0, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = ResourceHandleWndProc;
wcex.hInstance = Page::instanceHandle();
wcex.lpszClassName = kResourceHandleWindowClassName;
RegisterClassEx(&wcex);
transferJobWindowHandle = CreateWindow(kResourceHandleWindowClassName, 0, 0, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
HWND_MESSAGE, 0, Page::instanceHandle(), 0);
}
ResourceHandleInternal::~ResourceHandleInternal()
{
if (m_fileHandle != INVALID_HANDLE_VALUE)
CloseHandle(m_fileHandle);
}
ResourceHandle::~ResourceHandle()
{
if (d->m_jobId)
removeFromOutstandingJobs(d->m_jobId);
}
void ResourceHandle::onHandleCreated(LPARAM lParam)
{
if (!d->m_resourceHandle) {
d->m_resourceHandle = HINTERNET(lParam);
if (d->status != 0) {
// We were canceled before Windows actually created a handle for us, close and delete now.
InternetCloseHandle(d->m_resourceHandle);
delete this;
return;
}
if (method() == "POST") {
// FIXME: Too late to set referrer properly.
String urlStr = url().path();
int fragmentIndex = urlStr.find('#');
if (fragmentIndex != -1)
urlStr = urlStr.left(fragmentIndex);
static LPCSTR accept[2]={"*/*", NULL};
HINTERNET urlHandle = HttpOpenRequestA(d->m_resourceHandle,
"POST", urlStr.latin1().data(), 0, 0, accept,
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_FORMS_SUBMIT |
INTERNET_FLAG_RELOAD |
INTERNET_FLAG_NO_CACHE_WRITE |
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS |
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP,
(DWORD_PTR)d->m_jobId);
if (urlHandle == INVALID_HANDLE_VALUE) {
InternetCloseHandle(d->m_resourceHandle);
delete this;
}
}
} else if (!d->m_secondaryHandle) {
assert(method() == "POST");
d->m_secondaryHandle = HINTERNET(lParam);
// Need to actually send the request now.
String headers = "Content-Type: application/x-www-form-urlencoded\n";
headers += "Referer: ";
headers += d->m_postReferrer;
headers += "\n";
const CString& headersLatin1 = headers.latin1();
String formData = postData()->flattenToString();
INTERNET_BUFFERSA buffers;
memset(&buffers, 0, sizeof(buffers));
buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
buffers.lpcszHeader = headersLatin1;
buffers.dwHeadersLength = headers.length();
buffers.dwBufferTotal = formData.length();
d->m_bytesRemainingToWrite = formData.length();
d->m_formDataString = (char*)malloc(formData.length());
d->m_formDataLength = formData.length();
strncpy(d->m_formDataString, formData.latin1(), formData.length());
d->m_writing = true;
HttpSendRequestExA(d->m_secondaryHandle, &buffers, 0, 0, (DWORD_PTR)d->m_jobId);
// FIXME: add proper error handling
}
}
void ResourceHandle::onRequestRedirected(LPARAM lParam)
{
// If already canceled, then ignore this event.
if (d->status != 0)
return;
ResourceRequest request((StringImpl*) lParam);
ResourceResponse redirectResponse;
client()->willSendRequest(this, request, redirectResponse);
}
void ResourceHandle::onRequestComplete(LPARAM lParam)
{
if (d->m_writing) {
DWORD bytesWritten;
InternetWriteFile(d->m_secondaryHandle,
d->m_formDataString + (d->m_formDataLength - d->m_bytesRemainingToWrite),
d->m_bytesRemainingToWrite,
&bytesWritten);
d->m_bytesRemainingToWrite -= bytesWritten;
if (!d->m_bytesRemainingToWrite) {
// End the request.
d->m_writing = false;
HttpEndRequest(d->m_secondaryHandle, 0, 0, (DWORD_PTR)d->m_jobId);
free(d->m_formDataString);
d->m_formDataString = 0;
}
return;
}
HINTERNET handle = (method() == "POST") ? d->m_secondaryHandle : d->m_resourceHandle;
BOOL ok = FALSE;
static const int bufferSize = 32768;
char buffer[bufferSize];
INTERNET_BUFFERSA buffers;
buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
buffers.lpvBuffer = buffer;
buffers.dwBufferLength = bufferSize;
bool receivedAnyData = false;
while ((ok = InternetReadFileExA(handle, &buffers, IRF_NO_WAIT, (DWORD_PTR)this)) && buffers.dwBufferLength) {
if (!hasReceivedResponse()) {
setHasReceivedResponse();
ResourceResponse response;
client()->didReceiveResponse(this, response);
}
client()->didReceiveData(this, buffer, buffers.dwBufferLength, 0);
buffers.dwBufferLength = bufferSize;
}
PlatformDataStruct platformData;
platformData.errorString = 0;
platformData.error = 0;
platformData.loaded = ok;
if (!ok) {
int error = GetLastError();
if (error == ERROR_IO_PENDING)
return;
DWORD errorStringChars = 0;
if (!InternetGetLastResponseInfo(&platformData.error, 0, &errorStringChars)) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
platformData.errorString = new TCHAR[errorStringChars];
InternetGetLastResponseInfo(&platformData.error, platformData.errorString, &errorStringChars);
}
}
_RPTF1(_CRT_WARN, "Load error: %i\n", error);
}
if (d->m_secondaryHandle)
InternetCloseHandle(d->m_secondaryHandle);
InternetCloseHandle(d->m_resourceHandle);
client()->didFinishLoading(this);
delete this;
}
static void __stdcall transferJobStatusCallback(HINTERNET internetHandle,
DWORD_PTR jobId,
DWORD internetStatus,
LPVOID statusInformation,
DWORD statusInformationLength)
{
#ifdef RESOURCE_LOADER_DEBUG
char buf[64];
_snprintf(buf, sizeof(buf), "status-callback: status=%u, job=%p\n",
internetStatus, jobId);
OutputDebugStringA(buf);
#endif
UINT msg;
LPARAM lParam;
switch (internetStatus) {
case INTERNET_STATUS_HANDLE_CREATED:
// tell the main thread about the newly created handle
msg = handleCreatedMessage;
lParam = (LPARAM) LPINTERNET_ASYNC_RESULT(statusInformation)->dwResult;
break;
case INTERNET_STATUS_REQUEST_COMPLETE:
#ifdef RESOURCE_LOADER_DEBUG
_snprintf(buf, sizeof(buf), "request-complete: result=%p, error=%u\n",
LPINTERNET_ASYNC_RESULT(statusInformation)->dwResult,
LPINTERNET_ASYNC_RESULT(statusInformation)->dwError);
OutputDebugStringA(buf);
#endif
// tell the main thread that the request is done
msg = requestCompleteMessage;
lParam = 0;
break;
case INTERNET_STATUS_REDIRECT:
// tell the main thread to observe this redirect (FIXME: we probably
// need to block the redirect at this point so the application can
// decide whether or not to follow the redirect)
msg = requestRedirectedMessage;
lParam = (LPARAM) new StringImpl((const UChar*) statusInformation,
statusInformationLength);
break;
case INTERNET_STATUS_USER_INPUT_REQUIRED:
// FIXME: prompt the user if necessary
ResumeSuspendedDownload(internetHandle, 0);
case INTERNET_STATUS_STATE_CHANGE:
// may need to call ResumeSuspendedDownload here as well
default:
return;
}
PostMessage(transferJobWindowHandle, msg, (WPARAM) jobId, lParam);
}
bool ResourceHandle::start(Frame* frame)
{
ref();
if (url().isLocalFile()) {
String path = url().path();
// windows does not enjoy a leading slash on paths
if (path[0] == '/')
path = path.substring(1);
// FIXME: This is wrong. Need to use wide version of this call.
d->m_fileHandle = CreateFileA(path.utf8().data(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// FIXME: perhaps this error should be reported asynchronously for
// consistency.
if (d->m_fileHandle == INVALID_HANDLE_VALUE) {
delete this;
return false;
}
d->m_fileLoadTimer.startOneShot(0.0);
return true;
} else {
static HINTERNET internetHandle = 0;
if (!internetHandle) {
String userAgentStr = frame->loader()->userAgent() + String("", 1);
LPCWSTR userAgent = reinterpret_cast<const WCHAR*>(userAgentStr.characters());
// leak the Internet for now
internetHandle = InternetOpen(userAgent, INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, INTERNET_FLAG_ASYNC);
}
if (!internetHandle) {
delete this;
return false;
}
static INTERNET_STATUS_CALLBACK callbackHandle =
InternetSetStatusCallback(internetHandle, transferJobStatusCallback);
initializeOffScreenResourceHandleWindow();
d->m_jobId = addToOutstandingJobs(this);
DWORD flags =
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS |
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP;
// For form posting, we can't use InternetOpenURL. We have to use
// InternetConnect followed by HttpSendRequest.
HINTERNET urlHandle;
String referrer = frame->loader()->referrer();
if (method() == "POST") {
d->m_postReferrer = referrer;
String host = url().host();
urlHandle = InternetConnectA(internetHandle, host.latin1().data(),
url().port(),
NULL, // no username
NULL, // no password
INTERNET_SERVICE_HTTP,
flags, (DWORD_PTR)d->m_jobId);
} else {
String urlStr = url().string();
int fragmentIndex = urlStr.find('#');
if (fragmentIndex != -1)
urlStr = urlStr.left(fragmentIndex);
String headers;
if (!referrer.isEmpty())
headers += String("Referer: ") + referrer + "\r\n";
urlHandle = InternetOpenUrlA(internetHandle, urlStr.latin1().data(),
headers.latin1().data(), headers.length(),
flags, (DWORD_PTR)d->m_jobId);
}
if (urlHandle == INVALID_HANDLE_VALUE) {
delete this;
return false;
}
d->m_threadId = GetCurrentThreadId();
return true;
}
}
void ResourceHandle::fileLoadTimer(Timer<ResourceHandle>* timer)
{
ResourceResponse response;
client()->didReceiveResponse(this, response);
bool result = false;
DWORD bytesRead = 0;
do {
const int bufferSize = 8192;
char buffer[bufferSize];
result = ReadFile(d->m_fileHandle, &buffer, bufferSize, &bytesRead, NULL);
if (result && bytesRead)
client()->didReceiveData(this, buffer, bytesRead, 0);
// Check for end of file.
} while (result && bytesRead);
// FIXME: handle errors better
CloseHandle(d->m_fileHandle);
d->m_fileHandle = INVALID_HANDLE_VALUE;
client()->didFinishLoading(this);
}
void ResourceHandle::cancel()
{
if (d->m_resourceHandle)
InternetCloseHandle(d->m_resourceHandle);
else
d->m_fileLoadTimer.stop();
client()->didFinishLoading(this);
if (!d->m_resourceHandle)
// Async load canceled before we have a handle -- mark ourselves as in error, to be deleted later.
// FIXME: need real cancel error
client()->didFail(this, ResourceError());
}
void ResourceHandle::setHasReceivedResponse(bool b)
{
d->m_hasReceivedResponse = b;
}
bool ResourceHandle::hasReceivedResponse() const
{
return d->m_hasReceivedResponse;
}
} // namespace WebCore