summaryrefslogtreecommitdiffstats
path: root/net/url_request/url_request_inet_job.cc
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
commit586acc5fe142f498261f52c66862fa417c3d52d2 (patch)
treec98b3417a883f2477029c8cd5888f4078681e24e /net/url_request/url_request_inet_job.cc
parenta814a8d55429605fe6d7045045cd25b6bf624580 (diff)
downloadchromium_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.cc462
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);
+}