Home | History | Annotate | Download | only in win
      1 /*
      2  * Copyright (C) 2004, 2006 Apple Computer, 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  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "ResourceHandle.h"
     28 #include "ResourceHandleClient.h"
     29 #include "ResourceHandleInternal.h"
     30 #include "ResourceHandleWin.h"
     31 
     32 #include "CString.h"
     33 #include "DocLoader.h"
     34 #include "Document.h"
     35 #include "Frame.h"
     36 #include "FrameLoader.h"
     37 #include "Page.h"
     38 #include "ResourceError.h"
     39 #include "Timer.h"
     40 #include <windows.h>
     41 #include <wininet.h>
     42 
     43 namespace WebCore {
     44 
     45 static unsigned transferJobId = 0;
     46 static HashMap<int, ResourceHandle*>* jobIdMap = 0;
     47 
     48 static HWND transferJobWindowHandle = 0;
     49 const LPCWSTR kResourceHandleWindowClassName = L"ResourceHandleWindowClass";
     50 
     51 // Message types for internal use (keep in sync with kMessageHandlers)
     52 enum {
     53   handleCreatedMessage = WM_USER,
     54   requestRedirectedMessage,
     55   requestCompleteMessage
     56 };
     57 
     58 typedef void (ResourceHandle:: *ResourceHandleEventHandler)(LPARAM);
     59 static const ResourceHandleEventHandler messageHandlers[] = {
     60     &ResourceHandle::onHandleCreated,
     61     &ResourceHandle::onRequestRedirected,
     62     &ResourceHandle::onRequestComplete
     63 };
     64 
     65 static int addToOutstandingJobs(ResourceHandle* job)
     66 {
     67     if (!jobIdMap)
     68         jobIdMap = new HashMap<int, ResourceHandle*>;
     69     transferJobId++;
     70     jobIdMap->set(transferJobId, job);
     71     return transferJobId;
     72 }
     73 
     74 static void removeFromOutstandingJobs(int jobId)
     75 {
     76     if (!jobIdMap)
     77         return;
     78     jobIdMap->remove(jobId);
     79 }
     80 
     81 static ResourceHandle* lookupResourceHandle(int jobId)
     82 {
     83     if (!jobIdMap)
     84         return 0;
     85     return jobIdMap->get(jobId);
     86 }
     87 
     88 static LRESULT CALLBACK ResourceHandleWndProc(HWND hWnd, UINT message,
     89                                               WPARAM wParam, LPARAM lParam)
     90 {
     91     if (message >= handleCreatedMessage) {
     92         UINT index = message - handleCreatedMessage;
     93         if (index < _countof(messageHandlers)) {
     94             unsigned jobId = (unsigned) wParam;
     95             ResourceHandle* job = lookupResourceHandle(jobId);
     96             if (job) {
     97                 ASSERT(job->d->m_jobId == jobId);
     98                 ASSERT(job->d->m_threadId == GetCurrentThreadId());
     99                 (job->*(messageHandlers[index]))(lParam);
    100             }
    101             return 0;
    102         }
    103     }
    104     return DefWindowProc(hWnd, message, wParam, lParam);
    105 }
    106 
    107 static void initializeOffScreenResourceHandleWindow()
    108 {
    109     if (transferJobWindowHandle)
    110         return;
    111 
    112     WNDCLASSEX wcex;
    113     memset(&wcex, 0, sizeof(WNDCLASSEX));
    114     wcex.cbSize = sizeof(WNDCLASSEX);
    115     wcex.lpfnWndProc    = ResourceHandleWndProc;
    116     wcex.hInstance      = Page::instanceHandle();
    117     wcex.lpszClassName  = kResourceHandleWindowClassName;
    118     RegisterClassEx(&wcex);
    119 
    120     transferJobWindowHandle = CreateWindow(kResourceHandleWindowClassName, 0, 0, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
    121         HWND_MESSAGE, 0, Page::instanceHandle(), 0);
    122 }
    123 
    124 ResourceHandleInternal::~ResourceHandleInternal()
    125 {
    126     if (m_fileHandle != INVALID_HANDLE_VALUE)
    127         CloseHandle(m_fileHandle);
    128 }
    129 
    130 ResourceHandle::~ResourceHandle()
    131 {
    132     if (d->m_jobId)
    133         removeFromOutstandingJobs(d->m_jobId);
    134 }
    135 
    136 void ResourceHandle::onHandleCreated(LPARAM lParam)
    137 {
    138     if (!d->m_resourceHandle) {
    139         d->m_resourceHandle = HINTERNET(lParam);
    140         if (d->status != 0) {
    141             // We were canceled before Windows actually created a handle for us, close and delete now.
    142             InternetCloseHandle(d->m_resourceHandle);
    143             delete this;
    144             return;
    145         }
    146 
    147         if (method() == "POST") {
    148             // FIXME: Too late to set referrer properly.
    149             String urlStr = url().path();
    150             int fragmentIndex = urlStr.find('#');
    151             if (fragmentIndex != -1)
    152                 urlStr = urlStr.left(fragmentIndex);
    153             static LPCSTR accept[2]={"*/*", NULL};
    154             HINTERNET urlHandle = HttpOpenRequestA(d->m_resourceHandle,
    155                                                    "POST", urlStr.latin1().data(), 0, 0, accept,
    156                                                    INTERNET_FLAG_KEEP_CONNECTION |
    157                                                    INTERNET_FLAG_FORMS_SUBMIT |
    158                                                    INTERNET_FLAG_RELOAD |
    159                                                    INTERNET_FLAG_NO_CACHE_WRITE |
    160                                                    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS |
    161                                                    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP,
    162                                                    (DWORD_PTR)d->m_jobId);
    163             if (urlHandle == INVALID_HANDLE_VALUE) {
    164                 InternetCloseHandle(d->m_resourceHandle);
    165                 delete this;
    166             }
    167         }
    168     } else if (!d->m_secondaryHandle) {
    169         assert(method() == "POST");
    170         d->m_secondaryHandle = HINTERNET(lParam);
    171 
    172         // Need to actually send the request now.
    173         String headers = "Content-Type: application/x-www-form-urlencoded\n";
    174         headers += "Referer: ";
    175         headers += d->m_postReferrer;
    176         headers += "\n";
    177         const CString& headersLatin1 = headers.latin1();
    178         String formData = postData()->flattenToString();
    179         INTERNET_BUFFERSA buffers;
    180         memset(&buffers, 0, sizeof(buffers));
    181         buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
    182         buffers.lpcszHeader = headersLatin1;
    183         buffers.dwHeadersLength = headers.length();
    184         buffers.dwBufferTotal = formData.length();
    185 
    186         d->m_bytesRemainingToWrite = formData.length();
    187         d->m_formDataString = (char*)malloc(formData.length());
    188         d->m_formDataLength = formData.length();
    189         strncpy(d->m_formDataString, formData.latin1(), formData.length());
    190         d->m_writing = true;
    191         HttpSendRequestExA(d->m_secondaryHandle, &buffers, 0, 0, (DWORD_PTR)d->m_jobId);
    192         // FIXME: add proper error handling
    193     }
    194 }
    195 
    196 void ResourceHandle::onRequestRedirected(LPARAM lParam)
    197 {
    198     // If already canceled, then ignore this event.
    199     if (d->status != 0)
    200         return;
    201 
    202     ResourceRequest request((StringImpl*) lParam);
    203     ResourceResponse redirectResponse;
    204     client()->willSendRequest(this, request, redirectResponse);
    205 }
    206 
    207 void ResourceHandle::onRequestComplete(LPARAM lParam)
    208 {
    209     if (d->m_writing) {
    210         DWORD bytesWritten;
    211         InternetWriteFile(d->m_secondaryHandle,
    212                           d->m_formDataString + (d->m_formDataLength - d->m_bytesRemainingToWrite),
    213                           d->m_bytesRemainingToWrite,
    214                           &bytesWritten);
    215         d->m_bytesRemainingToWrite -= bytesWritten;
    216         if (!d->m_bytesRemainingToWrite) {
    217             // End the request.
    218             d->m_writing = false;
    219             HttpEndRequest(d->m_secondaryHandle, 0, 0, (DWORD_PTR)d->m_jobId);
    220             free(d->m_formDataString);
    221             d->m_formDataString = 0;
    222         }
    223         return;
    224     }
    225 
    226     HINTERNET handle = (method() == "POST") ? d->m_secondaryHandle : d->m_resourceHandle;
    227     BOOL ok = FALSE;
    228 
    229     static const int bufferSize = 32768;
    230     char buffer[bufferSize];
    231     INTERNET_BUFFERSA buffers;
    232     buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
    233     buffers.lpvBuffer = buffer;
    234     buffers.dwBufferLength = bufferSize;
    235 
    236     bool receivedAnyData = false;
    237     while ((ok = InternetReadFileExA(handle, &buffers, IRF_NO_WAIT, (DWORD_PTR)this)) && buffers.dwBufferLength) {
    238         if (!hasReceivedResponse()) {
    239             setHasReceivedResponse();
    240             ResourceResponse response;
    241             client()->didReceiveResponse(this, response);
    242         }
    243         client()->didReceiveData(this, buffer, buffers.dwBufferLength, 0);
    244         buffers.dwBufferLength = bufferSize;
    245     }
    246 
    247     PlatformDataStruct platformData;
    248     platformData.errorString = 0;
    249     platformData.error = 0;
    250     platformData.loaded = ok;
    251 
    252     if (!ok) {
    253         int error = GetLastError();
    254         if (error == ERROR_IO_PENDING)
    255             return;
    256         DWORD errorStringChars = 0;
    257         if (!InternetGetLastResponseInfo(&platformData.error, 0, &errorStringChars)) {
    258             if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
    259                 platformData.errorString = new TCHAR[errorStringChars];
    260                 InternetGetLastResponseInfo(&platformData.error, platformData.errorString, &errorStringChars);
    261             }
    262         }
    263         _RPTF1(_CRT_WARN, "Load error: %i\n", error);
    264     }
    265 
    266     if (d->m_secondaryHandle)
    267         InternetCloseHandle(d->m_secondaryHandle);
    268     InternetCloseHandle(d->m_resourceHandle);
    269 
    270     client()->didFinishLoading(this);
    271     delete this;
    272 }
    273 
    274 static void __stdcall transferJobStatusCallback(HINTERNET internetHandle,
    275                                                 DWORD_PTR jobId,
    276                                                 DWORD internetStatus,
    277                                                 LPVOID statusInformation,
    278                                                 DWORD statusInformationLength)
    279 {
    280 #ifdef RESOURCE_LOADER_DEBUG
    281     char buf[64];
    282     _snprintf(buf, sizeof(buf), "status-callback: status=%u, job=%p\n",
    283               internetStatus, jobId);
    284     OutputDebugStringA(buf);
    285 #endif
    286 
    287     UINT msg;
    288     LPARAM lParam;
    289 
    290     switch (internetStatus) {
    291     case INTERNET_STATUS_HANDLE_CREATED:
    292         // tell the main thread about the newly created handle
    293         msg = handleCreatedMessage;
    294         lParam = (LPARAM) LPINTERNET_ASYNC_RESULT(statusInformation)->dwResult;
    295         break;
    296     case INTERNET_STATUS_REQUEST_COMPLETE:
    297 #ifdef RESOURCE_LOADER_DEBUG
    298         _snprintf(buf, sizeof(buf), "request-complete: result=%p, error=%u\n",
    299             LPINTERNET_ASYNC_RESULT(statusInformation)->dwResult,
    300             LPINTERNET_ASYNC_RESULT(statusInformation)->dwError);
    301         OutputDebugStringA(buf);
    302 #endif
    303         // tell the main thread that the request is done
    304         msg = requestCompleteMessage;
    305         lParam = 0;
    306         break;
    307     case INTERNET_STATUS_REDIRECT:
    308         // tell the main thread to observe this redirect (FIXME: we probably
    309         // need to block the redirect at this point so the application can
    310         // decide whether or not to follow the redirect)
    311         msg = requestRedirectedMessage;
    312         lParam = (LPARAM) new StringImpl((const UChar*) statusInformation,
    313                                          statusInformationLength);
    314         break;
    315     case INTERNET_STATUS_USER_INPUT_REQUIRED:
    316         // FIXME: prompt the user if necessary
    317         ResumeSuspendedDownload(internetHandle, 0);
    318     case INTERNET_STATUS_STATE_CHANGE:
    319         // may need to call ResumeSuspendedDownload here as well
    320     default:
    321         return;
    322     }
    323 
    324     PostMessage(transferJobWindowHandle, msg, (WPARAM) jobId, lParam);
    325 }
    326 
    327 bool ResourceHandle::start(Frame* frame)
    328 {
    329     ref();
    330     if (url().isLocalFile()) {
    331         String path = url().path();
    332         // windows does not enjoy a leading slash on paths
    333         if (path[0] == '/')
    334             path = path.substring(1);
    335         // FIXME: This is wrong. Need to use wide version of this call.
    336         d->m_fileHandle = CreateFileA(path.utf8().data(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    337 
    338         // FIXME: perhaps this error should be reported asynchronously for
    339         // consistency.
    340         if (d->m_fileHandle == INVALID_HANDLE_VALUE) {
    341             delete this;
    342             return false;
    343         }
    344 
    345         d->m_fileLoadTimer.startOneShot(0.0);
    346         return true;
    347     } else {
    348         static HINTERNET internetHandle = 0;
    349         if (!internetHandle) {
    350             String userAgentStr = frame->loader()->userAgent() + String("", 1);
    351             LPCWSTR userAgent = reinterpret_cast<const WCHAR*>(userAgentStr.characters());
    352             // leak the Internet for now
    353             internetHandle = InternetOpen(userAgent, INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, INTERNET_FLAG_ASYNC);
    354         }
    355         if (!internetHandle) {
    356             delete this;
    357             return false;
    358         }
    359         static INTERNET_STATUS_CALLBACK callbackHandle =
    360             InternetSetStatusCallback(internetHandle, transferJobStatusCallback);
    361 
    362         initializeOffScreenResourceHandleWindow();
    363         d->m_jobId = addToOutstandingJobs(this);
    364 
    365         DWORD flags =
    366             INTERNET_FLAG_KEEP_CONNECTION |
    367             INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS |
    368             INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP;
    369 
    370         // For form posting, we can't use InternetOpenURL.  We have to use
    371         // InternetConnect followed by HttpSendRequest.
    372         HINTERNET urlHandle;
    373         String referrer = frame->loader()->referrer();
    374         if (method() == "POST") {
    375             d->m_postReferrer = referrer;
    376             String host = url().host();
    377             urlHandle = InternetConnectA(internetHandle, host.latin1().data(),
    378                                          url().port(),
    379                                          NULL, // no username
    380                                          NULL, // no password
    381                                          INTERNET_SERVICE_HTTP,
    382                                          flags, (DWORD_PTR)d->m_jobId);
    383         } else {
    384             String urlStr = url().string();
    385             int fragmentIndex = urlStr.find('#');
    386             if (fragmentIndex != -1)
    387                 urlStr = urlStr.left(fragmentIndex);
    388             String headers;
    389             if (!referrer.isEmpty())
    390                 headers += String("Referer: ") + referrer + "\r\n";
    391 
    392             urlHandle = InternetOpenUrlA(internetHandle, urlStr.latin1().data(),
    393                                          headers.latin1().data(), headers.length(),
    394                                          flags, (DWORD_PTR)d->m_jobId);
    395         }
    396 
    397         if (urlHandle == INVALID_HANDLE_VALUE) {
    398             delete this;
    399             return false;
    400         }
    401         d->m_threadId = GetCurrentThreadId();
    402 
    403         return true;
    404     }
    405 }
    406 
    407 void ResourceHandle::fileLoadTimer(Timer<ResourceHandle>* timer)
    408 {
    409     ResourceResponse response;
    410     client()->didReceiveResponse(this, response);
    411 
    412     bool result = false;
    413     DWORD bytesRead = 0;
    414 
    415     do {
    416         const int bufferSize = 8192;
    417         char buffer[bufferSize];
    418         result = ReadFile(d->m_fileHandle, &buffer, bufferSize, &bytesRead, NULL);
    419         if (result && bytesRead)
    420             client()->didReceiveData(this, buffer, bytesRead, 0);
    421         // Check for end of file.
    422     } while (result && bytesRead);
    423 
    424     // FIXME: handle errors better
    425 
    426     CloseHandle(d->m_fileHandle);
    427     d->m_fileHandle = INVALID_HANDLE_VALUE;
    428 
    429     client()->didFinishLoading(this);
    430 }
    431 
    432 void ResourceHandle::cancel()
    433 {
    434     if (d->m_resourceHandle)
    435         InternetCloseHandle(d->m_resourceHandle);
    436     else
    437         d->m_fileLoadTimer.stop();
    438 
    439     client()->didFinishLoading(this);
    440 
    441     if (!d->m_resourceHandle)
    442         // Async load canceled before we have a handle -- mark ourselves as in error, to be deleted later.
    443         // FIXME: need real cancel error
    444         client()->didFail(this, ResourceError());
    445 }
    446 
    447 void ResourceHandle::setHasReceivedResponse(bool b)
    448 {
    449     d->m_hasReceivedResponse = b;
    450 }
    451 
    452 bool ResourceHandle::hasReceivedResponse() const
    453 {
    454     return d->m_hasReceivedResponse;
    455 }
    456 
    457 } // namespace WebCore
    458