| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/url_request/url_request_inet_job.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/message_loop.h" | |
| 10 #include "base/string_util.h" | |
| 11 #include "googleurl/src/gurl.h" | |
| 12 #include "net/base/auth.h" | |
| 13 #include "net/base/io_buffer.h" | |
| 14 #include "net/base/net_errors.h" | |
| 15 #include "net/base/net_util.h" | |
| 16 #include "net/base/wininet_util.h" | |
| 17 #include "net/url_request/url_request_context.h" | |
| 18 #include "net/url_request/url_request_error_job.h" | |
| 19 #include "net/url_request/url_request_ftp_job.h" | |
| 20 #include "net/url_request/url_request_job_metrics.h" | |
| 21 #include "net/url_request/url_request_job_tracker.h" | |
| 22 | |
| 23 using net::WinInetUtil; | |
| 24 | |
| 25 // | |
| 26 // HOW ASYNC IO WORKS | |
| 27 // | |
| 28 // The URLRequestInet* classes are now fully asynchronous. This means that | |
| 29 // all IO operations pass buffers into WinInet, and as WinInet completes those | |
| 30 // IO requests, it will fill the buffer, and then callback to the client. | |
| 31 // Asynchronous IO Operations include: | |
| 32 // HttpSendRequestEx | |
| 33 // InternetWriteFile | |
| 34 // HttpEndRequest | |
| 35 // InternetOpenUrl | |
| 36 // InternetReadFile (for FTP) | |
| 37 // InternetReadFileEx (for HTTP) | |
| 38 // InternetCloseHandle | |
| 39 // | |
| 40 // To understand how this works, you need to understand the basic class | |
| 41 // hierarchy for the URLRequestJob classes: | |
| 42 // | |
| 43 // URLRequestJob | |
| 44 // | | |
| 45 // +--------------+-------------------+ | |
| 46 // | | | |
| 47 // (Other Job Types) URLRequestInetJob | |
| 48 // e.g. | | | |
| 49 // URLRequestFileJob URLRequestFtpJob URLRequestHttpJob | |
| 50 // | | |
| 51 // URLRequestHttpUploadJob | |
| 52 // | |
| 53 // | |
| 54 // To make this work, each URLRequestInetJob has a virtual method called | |
| 55 // OnIOComplete(). If a derived URLRequestInetJob class issues | |
| 56 // an asynchronous IO, it must override the OnIOComplete method | |
| 57 // to handle the IO completion. Once it has overridden this method, | |
| 58 // *all* asynchronous IO completions will come to this method, even | |
| 59 // those asynchronous IOs which may have been issued by a base class. | |
| 60 // For example, URLRequestInetJob has methods which Read from the | |
| 61 // connection asynchronously. Once URLRequestHttpJob overrides | |
| 62 // OnIOComplete (so that it can receive its own async IO callbacks) | |
| 63 // it will also receive the URLRequestInetJob async IO callbacks. To | |
| 64 // make this work, the derived class must track its own state, and call | |
| 65 // the base class' version of OnIOComplete if appropriate. | |
| 66 // | |
| 67 | |
| 68 COMPILE_ASSERT( | |
| 69 sizeof(URLRequestInetJob::AsyncResult) == sizeof(INTERNET_ASYNC_RESULT), | |
| 70 async_result_inconsistent_size); | |
| 71 | |
| 72 HINTERNET URLRequestInetJob::the_internet_ = NULL; | |
| 73 #ifndef NDEBUG | |
| 74 MessageLoop* URLRequestInetJob::my_message_loop_ = NULL; | |
| 75 #endif | |
| 76 | |
| 77 URLRequestInetJob::URLRequestInetJob(URLRequest* request) | |
| 78 : URLRequestJob(request), | |
| 79 connection_handle_(NULL), | |
| 80 request_handle_(NULL), | |
| 81 last_error_(ERROR_SUCCESS), | |
| 82 is_waiting_(false), | |
| 83 read_in_progress_(false), | |
| 84 loop_(MessageLoop::current()) { | |
| 85 // TODO(darin): we should re-create the internet if the UA string changes, | |
| 86 // but we have to be careful about existing users of this internet. | |
| 87 if (!the_internet_) { | |
| 88 InitializeTheInternet(request->context() ? | |
| 89 request->context()->GetUserAgent(GURL()) : std::string()); | |
| 90 } | |
| 91 #ifndef NDEBUG | |
| 92 DCHECK(MessageLoop::current() == my_message_loop_) << | |
| 93 "All URLRequests should happen on the same thread"; | |
| 94 #endif | |
| 95 } | |
| 96 | |
| 97 URLRequestInetJob::~URLRequestInetJob() { | |
| 98 DCHECK(!request_) << "request should be detached at this point"; | |
| 99 | |
| 100 // The connections may have already been cleaned up. It is ok to call | |
| 101 // CleanupConnection again to make sure the resource is properly released. | |
| 102 // See bug 684997. | |
| 103 CleanupConnection(); | |
| 104 } | |
| 105 | |
| 106 void URLRequestInetJob::Kill() { | |
| 107 CleanupConnection(); | |
| 108 | |
| 109 { | |
| 110 AutoLock locked(loop_lock_); | |
| 111 loop_ = NULL; | |
| 112 } | |
| 113 | |
| 114 // Dispatch the NotifyDone message to the URLRequest | |
| 115 URLRequestJob::Kill(); | |
| 116 } | |
| 117 | |
| 118 void URLRequestInetJob::SetAuth(const std::wstring& username, | |
| 119 const std::wstring& password) { | |
| 120 DCHECK((proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH) || | |
| 121 (server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH)); | |
| 122 | |
| 123 // Proxy gets set first, then WWW. | |
| 124 net::AuthData* auth = | |
| 125 (proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH ? | |
| 126 proxy_auth_.get() : server_auth_.get()); | |
| 127 | |
| 128 if (auth) { | |
| 129 auth->state = net::AUTH_STATE_HAVE_AUTH; | |
| 130 auth->username = username; | |
| 131 auth->password = password; | |
| 132 } | |
| 133 | |
| 134 // Resend the request with the new username and password. | |
| 135 // Do this asynchronously in case we were called from within a | |
| 136 // NotifyDataAvailable callback. | |
| 137 // TODO(mpcomplete): hmm... is it possible 'this' gets deleted before the task | |
| 138 // is run? | |
| 139 OnSetAuth(); | |
| 140 } | |
| 141 | |
| 142 void URLRequestInetJob::CancelAuth() { | |
| 143 DCHECK((proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH) || | |
| 144 (server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH)); | |
| 145 | |
| 146 // Proxy gets set first, then WWW. | |
| 147 net::AuthData* auth = | |
| 148 (proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH ? | |
| 149 proxy_auth_.get() : server_auth_.get()); | |
| 150 | |
| 151 if (auth) { | |
| 152 auth->state = net::AUTH_STATE_CANCELED; | |
| 153 } | |
| 154 | |
| 155 // Once the auth is cancelled, we proceed with the request as though | |
| 156 // there were no auth. So, send the OnResponseStarted. Schedule this | |
| 157 // for later so that we don't cause any recursing into the caller | |
| 158 // as a result of this call. | |
| 159 OnCancelAuth(); | |
| 160 } | |
| 161 | |
| 162 void URLRequestInetJob::OnIOComplete(const AsyncResult& result) { | |
| 163 URLRequestStatus status; | |
| 164 | |
| 165 if (read_in_progress_) { | |
| 166 read_in_progress_ = false; | |
| 167 int bytes_read = 0; | |
| 168 if (GetReadBytes(result, &bytes_read)) { | |
| 169 SetStatus(status); | |
| 170 if (bytes_read == 0) { | |
| 171 NotifyDone(status); | |
| 172 CleanupConnection(); | |
| 173 } | |
| 174 } else { | |
| 175 bytes_read = -1; | |
| 176 URLRequestStatus status; | |
| 177 status.set_status(URLRequestStatus::FAILED); | |
| 178 status.set_os_error(WinInetUtil::OSErrorToNetError(result.dwError)); | |
| 179 NotifyDone(status); | |
| 180 CleanupConnection(); | |
| 181 } | |
| 182 NotifyReadComplete(bytes_read); | |
| 183 } else { | |
| 184 // If we get here, an IO is completing which we didn't | |
| 185 // start or we lost track of our state. | |
| 186 NOTREACHED(); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 bool URLRequestInetJob::ReadRawData(net::IOBuffer* dest, int dest_size, | |
| 191 int *bytes_read) { | |
| 192 if (is_done()) | |
| 193 return 0; | |
| 194 | |
| 195 DCHECK_NE(dest_size, 0); | |
| 196 DCHECK_NE(bytes_read, (int*)NULL); | |
| 197 DCHECK(!read_in_progress_); | |
| 198 | |
| 199 *bytes_read = 0; | |
| 200 | |
| 201 int result = CallInternetRead(dest->data(), dest_size, bytes_read); | |
| 202 if (result == ERROR_SUCCESS) { | |
| 203 DLOG(INFO) << "read " << *bytes_read << " bytes"; | |
| 204 if (*bytes_read == 0) | |
| 205 CleanupConnection(); // finished reading all the data | |
| 206 return true; | |
| 207 } | |
| 208 | |
| 209 if (ProcessRequestError(result)) | |
| 210 read_in_progress_ = true; | |
| 211 | |
| 212 // Whether we had an error or the request is pending. | |
| 213 // Both of these cases return false. | |
| 214 return false; | |
| 215 } | |
| 216 | |
| 217 void URLRequestInetJob::CallOnIOComplete(const AsyncResult& result) { | |
| 218 // It's important to clear this flag before calling OnIOComplete | |
| 219 is_waiting_ = false; | |
| 220 | |
| 221 // the job could have completed with an error while the message was pending | |
| 222 if (!is_done()) { | |
| 223 // Verify that our status is currently set to IO_PENDING and | |
| 224 // reset it on success. | |
| 225 DCHECK(GetStatus().is_io_pending()); | |
| 226 if (result.dwResult && result.dwError == 0) | |
| 227 SetStatus(URLRequestStatus()); | |
| 228 | |
| 229 OnIOComplete(result); | |
| 230 } | |
| 231 | |
| 232 Release(); // may destroy self if last reference | |
| 233 } | |
| 234 | |
| 235 bool URLRequestInetJob::ProcessRequestError(int error) { | |
| 236 if (error == ERROR_IO_PENDING) { | |
| 237 DLOG(INFO) << "waiting for WinInet call to complete"; | |
| 238 AddRef(); // balanced in CallOnIOComplete | |
| 239 is_waiting_ = true; | |
| 240 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | |
| 241 return true; | |
| 242 } | |
| 243 DLOG(ERROR) << "WinInet call failed: " << error; | |
| 244 CleanupConnection(); | |
| 245 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, | |
| 246 WinInetUtil::OSErrorToNetError(error))); | |
| 247 return false; | |
| 248 } | |
| 249 | |
| 250 void URLRequestInetJob::CleanupConnection() { | |
| 251 if (!request_handle_ && !connection_handle_) | |
| 252 return; // nothing to clean up | |
| 253 | |
| 254 if (request_handle_) { | |
| 255 CleanupHandle(request_handle_); | |
| 256 request_handle_ = NULL; | |
| 257 } | |
| 258 if (connection_handle_) { | |
| 259 CleanupHandle(connection_handle_); | |
| 260 connection_handle_ = NULL; | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 void URLRequestInetJob::CleanupHandle(HINTERNET handle) { | |
| 265 // We no longer need notifications from this connection. | |
| 266 InternetSetStatusCallback(handle, NULL); | |
| 267 | |
| 268 if (!InternetCloseHandle(handle)) { | |
| 269 // InternetCloseHandle is evil. The documentation specifies that it | |
| 270 // either succeeds immediately or returns ERROR_IO_PENDING if there is | |
| 271 // something outstanding, in which case the close will happen automagically | |
| 272 // later. In either of these cases, it will call us back with | |
| 273 // INTERNET_STATUS_HANDLE_CLOSING (because we set up the async callbacks) | |
| 274 // and we simply do nothing for the message. | |
| 275 // | |
| 276 // However, sometimes it also seems to fail with ERROR_INVALID_HANDLE. | |
| 277 // This seems to happen when we cancel before it has called us back with | |
| 278 // data. For example, if we cancel during DNS resolution or while waiting | |
| 279 // for a slow server. | |
| 280 // | |
| 281 // Our speculation is that in these cases WinInet creates a handle for | |
| 282 // us with an internal structure, but that the driver has not yet called | |
| 283 // it back with a "real" handle (the driver level is probably what | |
| 284 // generates IO_PENDING). The driver has not yet specified a handle, which | |
| 285 // causes WinInet to barf. | |
| 286 // | |
| 287 // However, in this case, the cancel seems to work. The TCP connection is | |
| 288 // closed and we still get a callback that the handle is being closed. Yay. | |
| 289 // | |
| 290 // We assert that the error is either of these two because we aren't sure | |
| 291 // if any other error values could also indicate this bogus condition, and | |
| 292 // we want to notice if we do something wrong that causes a real error. | |
| 293 DWORD last_error = GetLastError(); | |
| 294 DCHECK(last_error == ERROR_INVALID_HANDLE) << | |
| 295 "Unknown error when closing handle, possibly leaking job"; | |
| 296 if (ERROR_IO_PENDING == last_error) { | |
| 297 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | |
| 298 | |
| 299 AsyncResult result; | |
| 300 result.dwError = ERROR_INTERNET_CONNECTION_ABORTED; | |
| 301 result.dwResult = reinterpret_cast<DWORD_PTR>(handle); | |
| 302 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( | |
| 303 this, &URLRequestInetJob::CallOnIOComplete, result)); | |
| 304 } | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 // static | |
| 309 HINTERNET URLRequestInetJob::GetTheInternet() { | |
| 310 return the_internet_; | |
| 311 } | |
| 312 | |
| 313 // static | |
| 314 void URLRequestInetJob::InitializeTheInternet(const std::string& user_agent) { | |
| 315 // Hack attack. We are hitting a deadlock in wininet deinitialization. | |
| 316 // What is happening is that when we deinitialize, FreeLibrary will be | |
| 317 // called on wininet. The loader lock is held, and wininet!DllMain is | |
| 318 // called. The problem is that wininet tries to do a bunch of cleanup | |
| 319 // in their DllMain, including calling ICAsyncThread::~ICASyncThread. | |
| 320 // This tries to shutdown the "select thread", and then does a | |
| 321 // WaitForSingleObject on the thread with a 5 sec timeout. However the | |
| 322 // thread they are waiting for cannot exit because the thread shutdown | |
| 323 // routine (LdrShutdownThread) is trying to acquire the loader lock. | |
| 324 // This causes chrome.exe to hang for 5 seconds on shutdown before the | |
| 325 // process will exit. Making sure we close our wininet handles did not help. | |
| 326 // | |
| 327 // Since DLLs are reference counted, we inflate the reference count on | |
| 328 // wininet so that it will never be deinitialized :) | |
| 329 LoadLibraryA("wininet"); | |
| 330 | |
| 331 the_internet_ = InternetOpenA(user_agent.c_str(), | |
| 332 INTERNET_OPEN_TYPE_PRECONFIG, | |
| 333 NULL, // no proxy override | |
| 334 NULL, // no proxy bypass list | |
| 335 INTERNET_FLAG_ASYNC); | |
| 336 InternetSetStatusCallback(the_internet_, URLRequestStatusCallback); | |
| 337 | |
| 338 // Keep track of this message loop so we can catch callers who don't make | |
| 339 // requests on the same thread. Only do this in debug mode; in release mode | |
| 340 // my_message_loop_ doesn't exist. | |
| 341 #ifndef NDEBUG | |
| 342 DCHECK(!my_message_loop_) << "InitializeTheInternet() called twice"; | |
| 343 DCHECK(my_message_loop_ = MessageLoop::current()); | |
| 344 #endif | |
| 345 } | |
| 346 | |
| 347 // static | |
| 348 void CALLBACK URLRequestInetJob::URLRequestStatusCallback( | |
| 349 HINTERNET handle, DWORD_PTR job_id, DWORD status, LPVOID status_info, | |
| 350 DWORD status_info_len) { | |
| 351 switch (status) { | |
| 352 case INTERNET_STATUS_REQUEST_COMPLETE: { | |
| 353 URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(job_id); | |
| 354 | |
| 355 DCHECK(status_info_len == sizeof(AsyncResult)); | |
| 356 AsyncResult* result = static_cast<AsyncResult*>(status_info); | |
| 357 | |
| 358 AutoLock locked(job->loop_lock_); | |
| 359 if (job->loop_) { | |
| 360 job->loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
| 361 job, &URLRequestInetJob::CallOnIOComplete, *result)); | |
| 362 } | |
| 363 break; | |
| 364 } | |
| 365 case INTERNET_STATUS_USER_INPUT_REQUIRED: | |
| 366 case INTERNET_STATUS_STATE_CHANGE: | |
| 367 // TODO(darin): This is probably a security problem. Do something better. | |
| 368 ResumeSuspendedDownload(handle, 0); | |
| 369 break; | |
| 370 } | |
| 371 } | |
| OLD | NEW |