diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 22:42:52 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 22:42:52 +0000 |
commit | 586acc5fe142f498261f52c66862fa417c3d52d2 (patch) | |
tree | c98b3417a883f2477029c8cd5888f4078681e24e /net/url_request/url_request_inet_job.cc | |
parent | a814a8d55429605fe6d7045045cd25b6bf624580 (diff) | |
download | chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.zip chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.gz chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.bz2 |
Add net to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/url_request/url_request_inet_job.cc')
-rw-r--r-- | net/url_request/url_request_inet_job.cc | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/net/url_request/url_request_inet_job.cc b/net/url_request/url_request_inet_job.cc new file mode 100644 index 0000000..ab4f91b --- /dev/null +++ b/net/url_request/url_request_inet_job.cc @@ -0,0 +1,462 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#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/net_errors.h" +#include "net/base/net_util.h" +#include "net/base/wininet_util.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" + +// +// 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. +// + + +using namespace std; + +using net::WinInetUtil; + +static const wchar_t kWndClass[] = L"URLRequestMessageWnd"; + +// Custom message types for use with message_hwnd +enum { + MSG_REQUEST_COMPLETE = WM_USER + 1 +}; + +HINTERNET URLRequestInetJob::the_internet_ = NULL; +HWND URLRequestInetJob::message_hwnd_ = 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) { + // 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()->user_agent() : 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(); + + // Dispatch the NotifyDone message to the URLRequest + URLRequestJob::Kill(); +} + +void URLRequestInetJob::SetAuth(const wstring& username, + const wstring& password) { + DCHECK((proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH) || + (server_auth_ != NULL && + (server_auth_->state == AUTH_STATE_NEED_AUTH))); + + // Proxy gets set first, then WWW. + AuthData* auth = + (proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH ? + proxy_auth_.get() : server_auth_.get()); + + if (auth) { + auth->state = 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_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH) || + (server_auth_ != NULL && + (server_auth_->state == AUTH_STATE_NEED_AUTH))); + + // Proxy gets set first, then WWW. + AuthData* auth = + (proxy_auth_ != NULL && proxy_auth_->state == AUTH_STATE_NEED_AUTH ? + proxy_auth_.get() : server_auth_.get()); + + if (auth) { + auth->state = 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(char* 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, 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()) { + Release(); // may destroy self if last reference + return; + } + + // 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; +} + +bool URLRequestInetJob::GetMoreData() { + if (!is_waiting_ && !is_done()) { + // The connection is still in the middle of transmission. + // Return true so InternetReadFileExA can be called again. + return true; + } else { + 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)); + + async_result_.dwError = ERROR_INTERNET_CONNECTION_ABORTED; + async_result_.dwResult = reinterpret_cast<DWORD_PTR>(handle); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &URLRequestInetJob::CallOnIOComplete, async_result_)); + } + } +} + +// static +HINTERNET URLRequestInetJob::GetTheInternet() { + return the_internet_; +} + +// static +void URLRequestInetJob::InitializeTheInternet(const std::string& user_agent) { + // construct message window for processsing + HINSTANCE hinst = GetModuleHandle(NULL); + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = URLRequestWndProc; + wc.hInstance = hinst; + wc.lpszClassName = kWndClass; + RegisterClassEx(&wc); + + message_hwnd_ = CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, + hinst, 0); + if (!message_hwnd_) { + NOTREACHED() << "error: " << GetLastError(); + return; + } + + // 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 +LRESULT CALLBACK URLRequestInetJob::URLRequestWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(wparam); + HINTERNET handle = reinterpret_cast<HINTERNET>(lparam); + + switch (message) { + case MSG_REQUEST_COMPLETE: { + // The callback will be reset if we have closed the handle and deleted + // the job instance. Call CallOnIOComplete only if the handle still + // has a valid callback. + INTERNET_STATUS_CALLBACK callback = NULL; + DWORD option_buffer_size = sizeof(callback); + if (InternetQueryOption(handle, INTERNET_OPTION_CALLBACK, + &callback, &option_buffer_size) + && (NULL != callback)) { + const AsyncResult& r = job->async_result_; + DLOG(INFO) << "REQUEST_COMPLETE: job=" << job << ", result=" << + (void*) r.dwResult << ", error=" << r.dwError; + job->CallOnIOComplete(r); + } + break; + } + default: + return DefWindowProc(hwnd, message, wparam, lparam); + } + + return 0; +} + +// static +void CALLBACK URLRequestInetJob::URLRequestStatusCallback( + HINTERNET handle, DWORD_PTR job_id, DWORD status, LPVOID status_info, + DWORD status_info_len) { + UINT message = 0; + LPARAM message_param = 0; + switch (status) { + case INTERNET_STATUS_REQUEST_COMPLETE: { + message = MSG_REQUEST_COMPLETE; + DCHECK(status_info_len == sizeof(INTERNET_ASYNC_RESULT)); + LPINTERNET_ASYNC_RESULT r = + static_cast<LPINTERNET_ASYNC_RESULT>(status_info); + URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(job_id); + job->async_result_.dwResult = r->dwResult; + job->async_result_.dwError = r->dwError; + message_param = reinterpret_cast<LPARAM>(handle); + 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; + } + + if (message) + PostMessage(URLRequestInetJob::message_hwnd_, message, + static_cast<WPARAM>(job_id), message_param); +} |