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