| Index: net/url_request/url_request_inet_job.cc
|
| ===================================================================
|
| --- net/url_request/url_request_inet_job.cc (revision 35423)
|
| +++ net/url_request/url_request_inet_job.cc (working copy)
|
| @@ -1,371 +0,0 @@
|
| -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include "net/url_request/url_request_inet_job.h"
|
| -
|
| -#include <algorithm>
|
| -
|
| -#include "base/message_loop.h"
|
| -#include "base/string_util.h"
|
| -#include "googleurl/src/gurl.h"
|
| -#include "net/base/auth.h"
|
| -#include "net/base/io_buffer.h"
|
| -#include "net/base/net_errors.h"
|
| -#include "net/base/net_util.h"
|
| -#include "net/base/wininet_util.h"
|
| -#include "net/url_request/url_request_context.h"
|
| -#include "net/url_request/url_request_error_job.h"
|
| -#include "net/url_request/url_request_ftp_job.h"
|
| -#include "net/url_request/url_request_job_metrics.h"
|
| -#include "net/url_request/url_request_job_tracker.h"
|
| -
|
| -using net::WinInetUtil;
|
| -
|
| -//
|
| -// HOW ASYNC IO WORKS
|
| -//
|
| -// The URLRequestInet* classes are now fully asynchronous. This means that
|
| -// all IO operations pass buffers into WinInet, and as WinInet completes those
|
| -// IO requests, it will fill the buffer, and then callback to the client.
|
| -// Asynchronous IO Operations include:
|
| -// HttpSendRequestEx
|
| -// InternetWriteFile
|
| -// HttpEndRequest
|
| -// InternetOpenUrl
|
| -// InternetReadFile (for FTP)
|
| -// InternetReadFileEx (for HTTP)
|
| -// InternetCloseHandle
|
| -//
|
| -// To understand how this works, you need to understand the basic class
|
| -// hierarchy for the URLRequestJob classes:
|
| -//
|
| -// URLRequestJob
|
| -// |
|
| -// +--------------+-------------------+
|
| -// | |
|
| -// (Other Job Types) URLRequestInetJob
|
| -// e.g. | |
|
| -// URLRequestFileJob URLRequestFtpJob URLRequestHttpJob
|
| -// |
|
| -// URLRequestHttpUploadJob
|
| -//
|
| -//
|
| -// To make this work, each URLRequestInetJob has a virtual method called
|
| -// OnIOComplete(). If a derived URLRequestInetJob class issues
|
| -// an asynchronous IO, it must override the OnIOComplete method
|
| -// to handle the IO completion. Once it has overridden this method,
|
| -// *all* asynchronous IO completions will come to this method, even
|
| -// those asynchronous IOs which may have been issued by a base class.
|
| -// For example, URLRequestInetJob has methods which Read from the
|
| -// connection asynchronously. Once URLRequestHttpJob overrides
|
| -// OnIOComplete (so that it can receive its own async IO callbacks)
|
| -// it will also receive the URLRequestInetJob async IO callbacks. To
|
| -// make this work, the derived class must track its own state, and call
|
| -// the base class' version of OnIOComplete if appropriate.
|
| -//
|
| -
|
| -COMPILE_ASSERT(
|
| - sizeof(URLRequestInetJob::AsyncResult) == sizeof(INTERNET_ASYNC_RESULT),
|
| - async_result_inconsistent_size);
|
| -
|
| -HINTERNET URLRequestInetJob::the_internet_ = NULL;
|
| -#ifndef NDEBUG
|
| -MessageLoop* URLRequestInetJob::my_message_loop_ = NULL;
|
| -#endif
|
| -
|
| -URLRequestInetJob::URLRequestInetJob(URLRequest* request)
|
| - : URLRequestJob(request),
|
| - connection_handle_(NULL),
|
| - request_handle_(NULL),
|
| - last_error_(ERROR_SUCCESS),
|
| - is_waiting_(false),
|
| - read_in_progress_(false),
|
| - loop_(MessageLoop::current()) {
|
| - // TODO(darin): we should re-create the internet if the UA string changes,
|
| - // but we have to be careful about existing users of this internet.
|
| - if (!the_internet_) {
|
| - InitializeTheInternet(request->context() ?
|
| - request->context()->GetUserAgent(GURL()) : std::string());
|
| - }
|
| -#ifndef NDEBUG
|
| - DCHECK(MessageLoop::current() == my_message_loop_) <<
|
| - "All URLRequests should happen on the same thread";
|
| -#endif
|
| -}
|
| -
|
| -URLRequestInetJob::~URLRequestInetJob() {
|
| - DCHECK(!request_) << "request should be detached at this point";
|
| -
|
| - // The connections may have already been cleaned up. It is ok to call
|
| - // CleanupConnection again to make sure the resource is properly released.
|
| - // See bug 684997.
|
| - CleanupConnection();
|
| -}
|
| -
|
| -void URLRequestInetJob::Kill() {
|
| - CleanupConnection();
|
| -
|
| - {
|
| - AutoLock locked(loop_lock_);
|
| - loop_ = NULL;
|
| - }
|
| -
|
| - // Dispatch the NotifyDone message to the URLRequest
|
| - URLRequestJob::Kill();
|
| -}
|
| -
|
| -void URLRequestInetJob::SetAuth(const std::wstring& username,
|
| - const std::wstring& password) {
|
| - DCHECK((proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH) ||
|
| - (server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH));
|
| -
|
| - // Proxy gets set first, then WWW.
|
| - net::AuthData* auth =
|
| - (proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH ?
|
| - proxy_auth_.get() : server_auth_.get());
|
| -
|
| - if (auth) {
|
| - auth->state = net::AUTH_STATE_HAVE_AUTH;
|
| - auth->username = username;
|
| - auth->password = password;
|
| - }
|
| -
|
| - // Resend the request with the new username and password.
|
| - // Do this asynchronously in case we were called from within a
|
| - // NotifyDataAvailable callback.
|
| - // TODO(mpcomplete): hmm... is it possible 'this' gets deleted before the task
|
| - // is run?
|
| - OnSetAuth();
|
| -}
|
| -
|
| -void URLRequestInetJob::CancelAuth() {
|
| - DCHECK((proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH) ||
|
| - (server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH));
|
| -
|
| - // Proxy gets set first, then WWW.
|
| - net::AuthData* auth =
|
| - (proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH ?
|
| - proxy_auth_.get() : server_auth_.get());
|
| -
|
| - if (auth) {
|
| - auth->state = net::AUTH_STATE_CANCELED;
|
| - }
|
| -
|
| - // Once the auth is cancelled, we proceed with the request as though
|
| - // there were no auth. So, send the OnResponseStarted. Schedule this
|
| - // for later so that we don't cause any recursing into the caller
|
| - // as a result of this call.
|
| - OnCancelAuth();
|
| -}
|
| -
|
| -void URLRequestInetJob::OnIOComplete(const AsyncResult& result) {
|
| - URLRequestStatus status;
|
| -
|
| - if (read_in_progress_) {
|
| - read_in_progress_ = false;
|
| - int bytes_read = 0;
|
| - if (GetReadBytes(result, &bytes_read)) {
|
| - SetStatus(status);
|
| - if (bytes_read == 0) {
|
| - NotifyDone(status);
|
| - CleanupConnection();
|
| - }
|
| - } else {
|
| - bytes_read = -1;
|
| - URLRequestStatus status;
|
| - status.set_status(URLRequestStatus::FAILED);
|
| - status.set_os_error(WinInetUtil::OSErrorToNetError(result.dwError));
|
| - NotifyDone(status);
|
| - CleanupConnection();
|
| - }
|
| - NotifyReadComplete(bytes_read);
|
| - } else {
|
| - // If we get here, an IO is completing which we didn't
|
| - // start or we lost track of our state.
|
| - NOTREACHED();
|
| - }
|
| -}
|
| -
|
| -bool URLRequestInetJob::ReadRawData(net::IOBuffer* dest, int dest_size,
|
| - int *bytes_read) {
|
| - if (is_done())
|
| - return 0;
|
| -
|
| - DCHECK_NE(dest_size, 0);
|
| - DCHECK_NE(bytes_read, (int*)NULL);
|
| - DCHECK(!read_in_progress_);
|
| -
|
| - *bytes_read = 0;
|
| -
|
| - int result = CallInternetRead(dest->data(), dest_size, bytes_read);
|
| - if (result == ERROR_SUCCESS) {
|
| - DLOG(INFO) << "read " << *bytes_read << " bytes";
|
| - if (*bytes_read == 0)
|
| - CleanupConnection(); // finished reading all the data
|
| - return true;
|
| - }
|
| -
|
| - if (ProcessRequestError(result))
|
| - read_in_progress_ = true;
|
| -
|
| - // Whether we had an error or the request is pending.
|
| - // Both of these cases return false.
|
| - return false;
|
| -}
|
| -
|
| -void URLRequestInetJob::CallOnIOComplete(const AsyncResult& result) {
|
| - // It's important to clear this flag before calling OnIOComplete
|
| - is_waiting_ = false;
|
| -
|
| - // the job could have completed with an error while the message was pending
|
| - if (!is_done()) {
|
| - // Verify that our status is currently set to IO_PENDING and
|
| - // reset it on success.
|
| - DCHECK(GetStatus().is_io_pending());
|
| - if (result.dwResult && result.dwError == 0)
|
| - SetStatus(URLRequestStatus());
|
| -
|
| - OnIOComplete(result);
|
| - }
|
| -
|
| - Release(); // may destroy self if last reference
|
| -}
|
| -
|
| -bool URLRequestInetJob::ProcessRequestError(int error) {
|
| - if (error == ERROR_IO_PENDING) {
|
| - DLOG(INFO) << "waiting for WinInet call to complete";
|
| - AddRef(); // balanced in CallOnIOComplete
|
| - is_waiting_ = true;
|
| - SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| - return true;
|
| - }
|
| - DLOG(ERROR) << "WinInet call failed: " << error;
|
| - CleanupConnection();
|
| - NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
|
| - WinInetUtil::OSErrorToNetError(error)));
|
| - return false;
|
| -}
|
| -
|
| -void URLRequestInetJob::CleanupConnection() {
|
| - if (!request_handle_ && !connection_handle_)
|
| - return; // nothing to clean up
|
| -
|
| - if (request_handle_) {
|
| - CleanupHandle(request_handle_);
|
| - request_handle_ = NULL;
|
| - }
|
| - if (connection_handle_) {
|
| - CleanupHandle(connection_handle_);
|
| - connection_handle_ = NULL;
|
| - }
|
| -}
|
| -
|
| -void URLRequestInetJob::CleanupHandle(HINTERNET handle) {
|
| - // We no longer need notifications from this connection.
|
| - InternetSetStatusCallback(handle, NULL);
|
| -
|
| - if (!InternetCloseHandle(handle)) {
|
| - // InternetCloseHandle is evil. The documentation specifies that it
|
| - // either succeeds immediately or returns ERROR_IO_PENDING if there is
|
| - // something outstanding, in which case the close will happen automagically
|
| - // later. In either of these cases, it will call us back with
|
| - // INTERNET_STATUS_HANDLE_CLOSING (because we set up the async callbacks)
|
| - // and we simply do nothing for the message.
|
| - //
|
| - // However, sometimes it also seems to fail with ERROR_INVALID_HANDLE.
|
| - // This seems to happen when we cancel before it has called us back with
|
| - // data. For example, if we cancel during DNS resolution or while waiting
|
| - // for a slow server.
|
| - //
|
| - // Our speculation is that in these cases WinInet creates a handle for
|
| - // us with an internal structure, but that the driver has not yet called
|
| - // it back with a "real" handle (the driver level is probably what
|
| - // generates IO_PENDING). The driver has not yet specified a handle, which
|
| - // causes WinInet to barf.
|
| - //
|
| - // However, in this case, the cancel seems to work. The TCP connection is
|
| - // closed and we still get a callback that the handle is being closed. Yay.
|
| - //
|
| - // We assert that the error is either of these two because we aren't sure
|
| - // if any other error values could also indicate this bogus condition, and
|
| - // we want to notice if we do something wrong that causes a real error.
|
| - DWORD last_error = GetLastError();
|
| - DCHECK(last_error == ERROR_INVALID_HANDLE) <<
|
| - "Unknown error when closing handle, possibly leaking job";
|
| - if (ERROR_IO_PENDING == last_error) {
|
| - SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| -
|
| - AsyncResult result;
|
| - result.dwError = ERROR_INTERNET_CONNECTION_ABORTED;
|
| - result.dwResult = reinterpret_cast<DWORD_PTR>(handle);
|
| - MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
|
| - this, &URLRequestInetJob::CallOnIOComplete, result));
|
| - }
|
| - }
|
| -}
|
| -
|
| -// static
|
| -HINTERNET URLRequestInetJob::GetTheInternet() {
|
| - return the_internet_;
|
| -}
|
| -
|
| -// static
|
| -void URLRequestInetJob::InitializeTheInternet(const std::string& user_agent) {
|
| - // Hack attack. We are hitting a deadlock in wininet deinitialization.
|
| - // What is happening is that when we deinitialize, FreeLibrary will be
|
| - // called on wininet. The loader lock is held, and wininet!DllMain is
|
| - // called. The problem is that wininet tries to do a bunch of cleanup
|
| - // in their DllMain, including calling ICAsyncThread::~ICASyncThread.
|
| - // This tries to shutdown the "select thread", and then does a
|
| - // WaitForSingleObject on the thread with a 5 sec timeout. However the
|
| - // thread they are waiting for cannot exit because the thread shutdown
|
| - // routine (LdrShutdownThread) is trying to acquire the loader lock.
|
| - // This causes chrome.exe to hang for 5 seconds on shutdown before the
|
| - // process will exit. Making sure we close our wininet handles did not help.
|
| - //
|
| - // Since DLLs are reference counted, we inflate the reference count on
|
| - // wininet so that it will never be deinitialized :)
|
| - LoadLibraryA("wininet");
|
| -
|
| - the_internet_ = InternetOpenA(user_agent.c_str(),
|
| - INTERNET_OPEN_TYPE_PRECONFIG,
|
| - NULL, // no proxy override
|
| - NULL, // no proxy bypass list
|
| - INTERNET_FLAG_ASYNC);
|
| - InternetSetStatusCallback(the_internet_, URLRequestStatusCallback);
|
| -
|
| - // Keep track of this message loop so we can catch callers who don't make
|
| - // requests on the same thread. Only do this in debug mode; in release mode
|
| - // my_message_loop_ doesn't exist.
|
| -#ifndef NDEBUG
|
| - DCHECK(!my_message_loop_) << "InitializeTheInternet() called twice";
|
| - DCHECK(my_message_loop_ = MessageLoop::current());
|
| -#endif
|
| -}
|
| -
|
| -// static
|
| -void CALLBACK URLRequestInetJob::URLRequestStatusCallback(
|
| - HINTERNET handle, DWORD_PTR job_id, DWORD status, LPVOID status_info,
|
| - DWORD status_info_len) {
|
| - switch (status) {
|
| - case INTERNET_STATUS_REQUEST_COMPLETE: {
|
| - URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(job_id);
|
| -
|
| - DCHECK(status_info_len == sizeof(AsyncResult));
|
| - AsyncResult* result = static_cast<AsyncResult*>(status_info);
|
| -
|
| - AutoLock locked(job->loop_lock_);
|
| - if (job->loop_) {
|
| - job->loop_->PostTask(FROM_HERE, NewRunnableMethod(
|
| - job, &URLRequestInetJob::CallOnIOComplete, *result));
|
| - }
|
| - break;
|
| - }
|
| - case INTERNET_STATUS_USER_INPUT_REQUIRED:
|
| - case INTERNET_STATUS_STATE_CHANGE:
|
| - // TODO(darin): This is probably a security problem. Do something better.
|
| - ResumeSuspendedDownload(handle, 0);
|
| - break;
|
| - }
|
| -}
|
|
|