WebCore:
[WebKit-https.git] / WebCore / platform / network / win / ResourceHandleWin.cpp
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             DeprecatedString 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(), 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         DeprecatedString path = url().path();
332         // windows does not enjoy a leading slash on paths
333         if (path[0] == '/')
334             path = path.mid(1);
335         d->m_fileHandle = CreateFileA(path.ascii(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
336
337         // FIXME: perhaps this error should be reported asynchronously for
338         // consistency.
339         if (d->m_fileHandle == INVALID_HANDLE_VALUE) {
340             delete this;
341             return false;
342         }
343
344         d->m_fileLoadTimer.startOneShot(0.0);
345         return true;
346     } else {
347         static HINTERNET internetHandle = 0;
348         if (!internetHandle) {
349             String userAgentStr = frame->loader()->userAgent() + String("", 1);
350             LPCWSTR userAgent = reinterpret_cast<const WCHAR*>(userAgentStr.characters());
351             // leak the Internet for now
352             internetHandle = InternetOpen(userAgent, INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, INTERNET_FLAG_ASYNC);
353         }
354         if (!internetHandle) {
355             delete this;
356             return false;
357         }
358         static INTERNET_STATUS_CALLBACK callbackHandle = 
359             InternetSetStatusCallback(internetHandle, transferJobStatusCallback);
360
361         initializeOffScreenResourceHandleWindow();
362         d->m_jobId = addToOutstandingJobs(this);
363
364         DWORD flags =
365             INTERNET_FLAG_KEEP_CONNECTION |
366             INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS |
367             INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP;
368
369         // For form posting, we can't use InternetOpenURL.  We have to use
370         // InternetConnect followed by HttpSendRequest.
371         HINTERNET urlHandle;
372         String referrer = frame->loader()->referrer();
373         if (method() == "POST") {
374             d->m_postReferrer = referrer;
375             DeprecatedString host = url().host();
376             host += "\0";
377             urlHandle = InternetConnectA(internetHandle, host.ascii(),
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             DeprecatedString urlStr = url().url();
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.ascii(),
393                                          headers.latin1(), 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