e35ab42a5e1f7c913101e825a3e47051833b1ae9
[WebKit-https.git] / WebCore / platform / win / ResourceLoaderWin.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 "ResourceLoader.h"
28 #include "ResourceLoaderInternal.h"
29 #include "ResourceLoaderWin.h"
30
31 #include "CString.h"
32 #include "DocLoader.h"
33 #include "Frame.h"
34 #include "Document.h"
35 #include <windows.h>
36 #include <wininet.h>
37
38 namespace WebCore {
39
40 static unsigned transferJobId = 0;
41 static HashMap<int, ResourceLoader*>* jobIdMap = 0;
42
43 static HWND transferJobWindowHandle = 0;
44 static UINT loadStatusMessage = 0;
45 const LPCWSTR kResourceLoaderWindowClassName = L"ResourceLoaderWindowClass";
46
47 static int addToOutstandingJobs(ResourceLoader* job)
48 {
49     if (!jobIdMap)
50         jobIdMap = new HashMap<int, ResourceLoader*>;
51     transferJobId++;
52     jobIdMap->set(transferJobId, job);
53     return transferJobId;
54 }
55
56 static void removeFromOutstandingJobs(int jobId)
57 {
58     if (!jobIdMap)
59         return;
60     jobIdMap->remove(jobId);
61 }
62
63 static ResourceLoader* lookupResourceLoader(int jobId)
64 {
65     if (!jobIdMap)
66         return 0;
67     return jobIdMap->get(jobId);
68 }
69
70 struct JobLoadStatus {
71     DWORD internetStatus;
72     DWORD_PTR dwResult;
73 };
74
75 LRESULT CALLBACK ResourceLoaderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
76 {
77     if (message == loadStatusMessage) {
78         JobLoadStatus* jobLoadStatus = (JobLoadStatus*)lParam;
79         DWORD internetStatus = jobLoadStatus->internetStatus;
80         DWORD_PTR dwResult = jobLoadStatus->dwResult;
81         delete jobLoadStatus;
82         jobLoadStatus = 0;
83
84         // If we get a message about a job we no longer know about (already deleted), ignore it.
85         unsigned jobId = (unsigned)wParam;
86         ResourceLoader* job = lookupResourceLoader(jobId);
87         if (!job)
88             return 0;
89
90         ASSERT(job->d->m_jobId == jobId);
91         ASSERT(job->d->m_threadId == GetCurrentThreadId());
92
93         if (internetStatus == INTERNET_STATUS_HANDLE_CREATED) {
94             if (!job->d->m_resourceHandle) {
95                 job->d->m_resourceHandle = HINTERNET(dwResult);
96                 if (job->d->status != 0) {
97                     // We were canceled before Windows actually created a handle for us, close and delete now.
98                     InternetCloseHandle(job->d->m_resourceHandle);
99                     delete job;
100                 }
101
102                 if (job->method() == "POST") {
103                     // FIXME: Too late to set referrer properly.
104                     DeprecatedString urlStr = job->d->URL.path();
105                     int fragmentIndex = urlStr.find('#');
106                     if (fragmentIndex != -1)
107                         urlStr = urlStr.left(fragmentIndex);
108                     static LPCSTR accept[2]={"*/*", NULL};
109                     HINTERNET urlHandle = HttpOpenRequestA(job->d->m_resourceHandle, 
110                                                            "POST", urlStr.latin1(), 0, 0, accept,
111                                                            INTERNET_FLAG_KEEP_CONNECTION | 
112                                                            INTERNET_FLAG_FORMS_SUBMIT |
113                                                            INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE,
114                                                            (DWORD_PTR)job->d->m_jobId);
115                     if (urlHandle == INVALID_HANDLE_VALUE) {
116                         InternetCloseHandle(job->d->m_resourceHandle);
117                         delete job;
118                     }
119                 }
120             } else if (!job->d->m_secondaryHandle) {
121                 assert(job->method() == "POST");
122                 job->d->m_secondaryHandle = HINTERNET(dwResult);
123                 
124                 // Need to actually send the request now.
125                 String headers = "Content-Type: application/x-www-form-urlencoded\n";
126                 headers += "Referer: ";
127                 headers += job->d->m_postReferrer;
128                 headers += "\n";
129                 String formData = job->postData().flattenToString();
130                 INTERNET_BUFFERSA buffers;
131                 memset(&buffers, 0, sizeof(buffers));
132                 buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
133                 buffers.lpcszHeader = headers.latin1();
134                 buffers.dwHeadersLength = headers.length();
135                 buffers.dwBufferTotal = formData.length();
136                 
137                 job->d->m_bytesRemainingToWrite = formData.length();
138                 job->d->m_formDataString = (char*)malloc(formData.length());
139                 job->d->m_formDataLength = formData.length();
140                 strncpy(job->d->m_formDataString, formData.latin1(), formData.length());
141                 job->d->m_writing = true;
142                 HttpSendRequestExA(job->d->m_secondaryHandle, &buffers, 0, 0, (DWORD_PTR)job->d->m_jobId);
143             }
144         } else if (internetStatus == INTERNET_STATUS_REQUEST_COMPLETE) {
145             if (job->d->m_writing) {
146                 DWORD bytesWritten;
147                 InternetWriteFile(job->d->m_secondaryHandle,
148                                   job->d->m_formDataString + (job->d->m_formDataLength - job->d->m_bytesRemainingToWrite),
149                                   job->d->m_bytesRemainingToWrite,
150                                   &bytesWritten);
151                 job->d->m_bytesRemainingToWrite -= bytesWritten;
152                 if (!job->d->m_bytesRemainingToWrite) {
153                     // End the request.
154                     job->d->m_writing = false;
155                     HttpEndRequest(job->d->m_secondaryHandle, 0, 0, (DWORD_PTR)job->d->m_jobId);
156                     free(job->d->m_formDataString);
157                     job->d->m_formDataString = 0;
158                 }
159                 return 0;
160             }
161
162             HINTERNET handle = (job->method() == "POST") ? job->d->m_secondaryHandle : job->d->m_resourceHandle;
163             BOOL ok = FALSE;
164
165             static const int bufferSize = 32768;
166             char buffer[bufferSize];
167             INTERNET_BUFFERSA buffers;
168             buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
169             buffers.lpvBuffer = buffer;
170             buffers.dwBufferLength = bufferSize;
171
172             bool receivedAnyData = false;
173             while ((ok = InternetReadFileExA(handle, &buffers, IRF_NO_WAIT, (DWORD_PTR)job)) && buffers.dwBufferLength) {
174                 if (!receivedAnyData) {
175                     receivedAnyData = true;
176                     job->client()->receivedResponse(job, 0);
177                 }
178                 job->client()->receivedData(job, buffer, buffers.dwBufferLength);
179                 buffers.dwBufferLength = bufferSize;
180             }
181
182             PlatformDataStruct platformData;
183             platformData.errorString = 0;
184             platformData.error = 0;
185             platformData.loaded = ok;
186
187             if (!ok) {
188                 int error = GetLastError();
189                 if (error == ERROR_IO_PENDING)
190                     return 0;
191                 else {
192                     DWORD errorStringChars = 0;
193                     if (!InternetGetLastResponseInfo(&platformData.error, 0, &errorStringChars)) {
194                         if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
195                             platformData.errorString = new TCHAR[errorStringChars];
196                             InternetGetLastResponseInfo(&platformData.error, platformData.errorString, &errorStringChars);
197                         }
198                     }
199                     _RPTF1(_CRT_WARN, "Load error: %i\n", error);
200                 }
201             }
202             
203             if (job->d->m_secondaryHandle)
204                 InternetCloseHandle(job->d->m_secondaryHandle);
205             InternetCloseHandle(job->d->m_resourceHandle);
206             
207             job->client()->receivedAllData(job, &platformData);
208             job->client()->receivedAllData(job);
209             delete job;
210         }
211     } else
212         return DefWindowProc(hWnd, message, wParam, lParam);
213     return 0;
214 }
215
216 static void initializeOffScreenResourceLoaderWindow()
217 {
218     if (transferJobWindowHandle)
219         return;
220
221     WNDCLASSEX wcex;
222     memset(&wcex, 0, sizeof(WNDCLASSEX));
223     wcex.cbSize = sizeof(WNDCLASSEX);
224     wcex.lpfnWndProc    = ResourceLoaderWndProc;
225     wcex.hInstance      = Widget::instanceHandle;
226     wcex.lpszClassName  = kResourceLoaderWindowClassName;
227     RegisterClassEx(&wcex);
228
229     transferJobWindowHandle = CreateWindow(kResourceLoaderWindowClassName, 0, 0, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
230         HWND_MESSAGE, 0, Widget::instanceHandle, 0);
231     loadStatusMessage = RegisterWindowMessage(L"com.apple.WebKit.ResourceLoaderLoadStatus");
232 }
233
234 ResourceLoaderInternal::~ResourceLoaderInternal()
235 {
236     if (m_fileHandle)
237         CloseHandle(m_fileHandle);
238 }
239
240 ResourceLoader::~ResourceLoader()
241 {
242     if (d->m_jobId)
243         removeFromOutstandingJobs(d->m_jobId);
244 }
245
246 static void __stdcall transferJobStatusCallback(HINTERNET internetHandle, DWORD_PTR timerId, DWORD internetStatus, LPVOID statusInformation, DWORD statusInformationLength)
247 {
248     switch (internetStatus) {
249     case INTERNET_STATUS_HANDLE_CREATED:
250     case INTERNET_STATUS_REQUEST_COMPLETE:
251         JobLoadStatus* jobLoadStatus = new JobLoadStatus;
252         jobLoadStatus->internetStatus = internetStatus;
253         jobLoadStatus->dwResult = LPINTERNET_ASYNC_RESULT(statusInformation)->dwResult;
254         PostMessage(transferJobWindowHandle, loadStatusMessage, (WPARAM)timerId, (LPARAM)jobLoadStatus);
255     }
256 }
257
258 bool ResourceLoader::start(DocLoader* docLoader)
259 {
260     if (d->URL.isLocalFile()) {
261         DeprecatedString path = d->URL.path();
262         // windows does not enjoy a leading slash on paths
263         if (path[0] == '/')
264             path = path.mid(1);
265         d->m_fileHandle = CreateFileA(path.ascii(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
266
267         if (d->m_fileHandle == INVALID_HANDLE_VALUE) {
268             delete this;
269             return false;
270         }
271
272         d->m_fileLoadTimer.startOneShot(0.0);
273         return true;
274     } else {
275         static HINTERNET internetHandle = 0;
276         if (!internetHandle) {
277             String userAgentStr = docLoader->frame()->userAgent() + String("", 1);
278             LPCWSTR userAgent = reinterpret_cast<const WCHAR*>(userAgentStr.characters());
279             // leak the Internet for now
280             internetHandle = InternetOpen(userAgent, INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, INTERNET_FLAG_ASYNC);
281         }
282         if (!internetHandle) {
283             delete this;
284             return false;
285         }
286         static INTERNET_STATUS_CALLBACK callbackHandle = InternetSetStatusCallback(internetHandle, transferJobStatusCallback);
287
288         initializeOffScreenResourceLoaderWindow();
289         d->m_jobId = addToOutstandingJobs(this);
290
291         // For form posting, we can't use InternetOpenURL.  We have to use InternetConnect followed by
292         // HttpSendRequest.
293         HINTERNET urlHandle;
294         String referrer = docLoader->frame()->referrer();
295         if (method() == "POST") {
296             d->m_postReferrer = referrer;
297             DeprecatedString host = d->URL.host();
298             host += "\0";
299             urlHandle = InternetConnectA(internetHandle, host.ascii(), d->URL.port(), 0, 0, 
300                                          INTERNET_SERVICE_HTTP, 0, (DWORD_PTR)d->m_jobId);
301         } else {
302             DeprecatedString urlStr = d->URL.url();
303             int fragmentIndex = urlStr.find('#');
304             if (fragmentIndex != -1)
305                 urlStr = urlStr.left(fragmentIndex);
306             String headers;
307             if (!referrer.isEmpty())
308                 headers += String("Referer: ") + referrer + "\r\n";
309
310             urlHandle = InternetOpenUrlA(internetHandle, urlStr.ascii(), headers.latin1(), headers.length(),
311                                          INTERNET_FLAG_KEEP_CONNECTION, (DWORD_PTR)d->m_jobId);
312         }
313
314         if (urlHandle == INVALID_HANDLE_VALUE) {
315             delete this;
316             return false;
317         }
318         d->m_threadId = GetCurrentThreadId();
319
320         return true;
321     }
322 }
323
324 void ResourceLoader::fileLoadTimer(Timer<ResourceLoader>* timer)
325 {
326     bool result = false;
327     DWORD bytesRead = 0;
328
329     do {
330         const int bufferSize = 8192;
331         char buffer[bufferSize];
332         result = ReadFile(d->m_fileHandle, &buffer, bufferSize, &bytesRead, NULL); 
333         d->client->receivedData(this, buffer, bytesRead);
334         // Check for end of file. 
335     } while (result && bytesRead);
336
337     CloseHandle(d->m_fileHandle);
338     d->m_fileHandle = 0;
339
340     PlatformDataStruct platformData;
341     platformData.errorString = 0;
342     platformData.error = 0;
343     platformData.loaded = TRUE;
344
345     d->client->receivedAllData(this, &platformData);
346     d->client->receivedAllData(this);
347 }
348
349 void ResourceLoader::cancel()
350 {
351     if (d->m_resourceHandle)
352         InternetCloseHandle(d->m_resourceHandle);
353     else
354         d->m_fileLoadTimer.stop();
355
356     PlatformDataStruct platformData;
357     platformData.errorString = 0;
358     platformData.error = 0;
359     platformData.loaded = FALSE;
360
361     d->client->receivedAllData(this, &platformData);
362     d->client->receivedAllData(this);
363
364     if (!d->m_resourceHandle)
365         // Async load canceled before we have a handle -- mark ourselves as in error, to be deleted later.
366         setError(1);
367 }
368
369 } // namespace WebCore