// Copyright (c) 2011 The Chromium Embedded Framework Authors.
// Portions 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.
//
// This file contains an implementation of the ResourceLoaderBridge class.
// The class is implemented using net::URLRequest, meaning it is a "simple"
// version that directly issues requests. The more complicated one used in the
// browser uses IPC.
//
// Because net::URLRequest only provides an asynchronous resource loading API,
// this file makes use of net::URLRequest from a background IO thread.  Requests
// for cookies and synchronously loaded resources result in the main thread of
// the application blocking until the IO thread completes the operation.  (See
// GetCookies and SyncLoad)
//
// Main thread                          IO thread
// -----------                          ---------
// ResourceLoaderBridge <---o---------> RequestProxy (normal case)
//                           \            -> net::URLRequest
//                            o-------> SyncRequestProxy (synchronous case)
//                                        -> net::URLRequest
//
// NOTE: The implementation in this file may be used to have WebKit fetch
// resources in-process.  For example, it is handy for building a single-
// process WebKit embedding (e.g., test_shell) that can use net::URLRequest to
// perform URL loads.  See renderer/resource_dispatcher.h for details on an
// alternate implementation that defers fetching to another process.

#include "libcef/browser_resource_loader_bridge.h"
#include "libcef/browser_appcache_system.h"
#include "libcef/browser_request_context.h"
#include "libcef/browser_socket_stream_bridge.h"
#include "libcef/browser_webkit_glue.h"
#include "libcef/browser_impl.h"
#include "libcef/cef_context.h"
#include "libcef/cef_process.h"
#include "libcef/cef_process_io_thread.h"
#include "libcef/external_protocol_handler.h"
#include "libcef/request_impl.h"
#include "libcef/response_impl.h"
#include "libcef/http_header_utils.h"

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/time.h"
#include "base/timer.h"
#include "base/threading/thread.h"
#include "base/utf_string_conversions.h"
#include "net/base/auth.h"
#include "net/base/file_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/static_cookie_policy.h"
#include "net/base/upload_data.h"
#include "net/http/http_cache.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job_manager.h"
#include "net/url_request/url_request_redirect_job.h"
#include "webkit/appcache/appcache_interfaces.h"
#include "webkit/blob/blob_storage_controller.h"
#include "webkit/blob/shareable_file_reference.h"
#include "webkit/fileapi/file_system_context.h"
#include "webkit/fileapi/file_system_dir_url_request_job.h"
#include "webkit/fileapi/file_system_url_request_job.h"
#include "webkit/glue/resource_loader_bridge.h"
#include "webkit/glue/resource_request_body.h"
#include "webkit/glue/webkit_glue.h"

#if defined(OS_MACOSX) || defined(OS_WIN)
#include "crypto/nss_util.h"
#endif

using net::HttpResponseHeaders;
using net::StaticCookiePolicy;
using net::URLRequestStatus;
using webkit_blob::ShareableFileReference;
using webkit_glue::ResourceLoaderBridge;
using webkit_glue::ResourceRequestBody;
using webkit_glue::ResourceResponseInfo;


namespace {

static const char kCefUserData[] = "cef_userdata";

struct RequestParams {
  std::string method;
  GURL url;
  GURL first_party_for_cookies;
  GURL referrer;
  WebKit::WebReferrerPolicy referrer_policy;
  std::string headers;
  int load_flags;
  ResourceType::Type request_type;
  int appcache_host_id;
  bool download_to_file;
  scoped_refptr<ResourceRequestBody> request_body;
  net::RequestPriority priority;
};

// The interval for calls to RequestProxy::MaybeUpdateUploadProgress
static const int kUpdateUploadProgressIntervalMsec = 100;

class ExtraRequestInfo : public net::URLRequest::Data {
 public:
  ExtraRequestInfo(CefBrowserImpl* browser, ResourceType::Type resource_type)
    : browser_(browser),
      resource_type_(resource_type),
      allow_download_(resource_type == ResourceType::MAIN_FRAME ||
                      resource_type == ResourceType::SUB_FRAME) {
  }

  // The browser pointer is guaranteed to be valid for the lifespan of the
  // request. The pointer will be NULL in cases where the request was
  // initiated via the CefWebURLRequest API instead of by a browser window.
  CefBrowserImpl* browser() const { return browser_; }

  // Identifies the type of resource, such as subframe, media, etc.
  ResourceType::Type resource_type() const { return resource_type_; }
  bool allow_download() const { return allow_download_; }

 private:
  CefBrowserImpl* browser_;
  ResourceType::Type resource_type_;
  bool allow_download_;
};

// Used to intercept redirect requests.
class RequestInterceptor : public net::URLRequest::Interceptor {
 public:
  RequestInterceptor() {
    REQUIRE_IOT();
    net::URLRequestJobManager::GetInstance()->RegisterRequestInterceptor(this);
  }
  ~RequestInterceptor() {
    REQUIRE_IOT();
    net::URLRequestJobManager::GetInstance()->
        UnregisterRequestInterceptor(this);
  }

  virtual net::URLRequestJob* MaybeIntercept(
      net::URLRequest* request,
      net::NetworkDelegate* network_delegate)
      OVERRIDE {
    return NULL;
  }

  virtual net::URLRequestJob* MaybeInterceptRedirect(
      net::URLRequest* request,
      net::NetworkDelegate* network_delegate,
      const GURL& location) OVERRIDE {
    REQUIRE_IOT();

    ExtraRequestInfo* info =
        static_cast<ExtraRequestInfo*>(request->GetUserData(kCefUserData));
    if (!info)
      return NULL;

    CefRefPtr<CefBrowser> browser = info->browser();
    if (!browser.get())
      return NULL;
    CefRefPtr<CefClient> client = browser->GetClient();
    CefRefPtr<CefRequestHandler> handler;
    if (client.get())
      handler = client->GetRequestHandler();
    if (!handler.get())
      return NULL;

    CefString newUrlStr = location.spec();
    handler->OnResourceRedirect(browser, request->url().spec(), newUrlStr);
    if (newUrlStr != location.spec()) {
      GURL new_url = GURL(std::string(newUrlStr));
      if (!new_url.is_empty() && new_url.is_valid()) {
        return new net::URLRequestRedirectJob(
            request,
            network_delegate,
            new_url,
            net::URLRequestRedirectJob::REDIRECT_302_FOUND);
      }
    }

    return NULL;
  }

  DISALLOW_COPY_AND_ASSIGN(RequestInterceptor);
};

// The RequestProxy does most of its work on the IO thread.  The Start and
// Cancel methods are proxied over to the IO thread, where an net::URLRequest
// object is instantiated.
class RequestProxy : public net::URLRequest::Delegate,
                     public base::RefCountedThreadSafe<RequestProxy> {
 public:
  // Takes ownership of the params.
  explicit RequestProxy(CefRefPtr<CefBrowserImpl> browser)
    : download_to_file_(false),
      buf_(new net::IOBuffer(kDataSize)),
      browser_(browser),
      owner_loop_(NULL),
      peer_(NULL),
      last_upload_position_(0),
      defers_loading_(false),
      defers_loading_want_read_(false) {
  }

  void DropPeer() {
    DCHECK(MessageLoop::current() == owner_loop_);
    peer_ = NULL;
  }

  void Start(ResourceLoaderBridge::Peer* peer, RequestParams* params) {
    peer_ = peer;
    owner_loop_ = MessageLoop::current();

    InitializeParams(params);

    // proxy over to the io thread
    CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
        &RequestProxy::AsyncStart, this, params));
  }

  void Cancel() {
    DCHECK(MessageLoop::current() == owner_loop_);

    if (download_handler_.get()) {
      // WebKit will try to cancel the download but we won't allow it.
      return;
    }

    // proxy over to the io thread
    CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
        &RequestProxy::AsyncCancel, this));
  }

  void SetDefersLoading(bool defer) {
    DCHECK(MessageLoop::current() == owner_loop_);

    CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
        &RequestProxy::AsyncSetDefersLoading, this, defer));
  }

 protected:
  friend class base::RefCountedThreadSafe<RequestProxy>;

  virtual ~RequestProxy() {
    // If we have a request, then we'd better be on the io thread!
    DCHECK(!request_.get() || CefThread::CurrentlyOn(CefThread::IO));
  }

  virtual void InitializeParams(RequestParams* params) {
    params->priority = net::MEDIUM;
  }

  // --------------------------------------------------------------------------
  // The following methods are called on the owner's thread in response to
  // various net::URLRequest callbacks.  The event hooks, defined below, trigger
  // these methods asynchronously.

  void NotifyReceivedRedirect(const GURL& new_url,
                              const ResourceResponseInfo& info) {
    DCHECK(MessageLoop::current() == owner_loop_);

    bool has_new_first_party_for_cookies = false;
    GURL new_first_party_for_cookies;
    if (peer_ && peer_->OnReceivedRedirect(new_url, info,
                                           &has_new_first_party_for_cookies,
                                           &new_first_party_for_cookies)) {
      CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
          &RequestProxy::AsyncFollowDeferredRedirect, this,
          has_new_first_party_for_cookies, new_first_party_for_cookies));
    } else {
      Cancel();
    }
  }

  void NotifyReceivedResponse(const ResourceResponseInfo& info,
                              const GURL& url, bool allow_download) {
    DCHECK(MessageLoop::current() == owner_loop_);

    if (browser_.get() && info.headers.get()) {
      CefRefPtr<CefClient> client = browser_->GetClient();
      CefRefPtr<CefRequestHandler> handler;
      if (client.get())
        handler = client->GetRequestHandler();

      if (handler.get()) {
        CefRefPtr<CefResponse> response = new CefResponseImpl();
        // Transfer response headers
        if (info.headers) {
          CefResponse::HeaderMap headerMap;
          void* header_index = NULL;
          std::string name, value;
          while (info.headers->EnumerateHeaderLines(&header_index, &name,
                                                    &value)) {
            if (!name.empty() && !value.empty())
              headerMap.insert(std::make_pair(name, value));
          }
          response->SetHeaderMap(headerMap);
          response->SetStatusText(info.headers->GetStatusText());
          response->SetStatus(info.headers->response_code());
        }
        response->SetMimeType(info.mime_type);
        handler->OnResourceResponse(browser_.get(), url.spec(), response,
            content_filter_);

        std::string content_disposition;
        info.headers->GetNormalizedHeader("Content-Disposition",
            &content_disposition);

        if (allow_download &&
            webkit_glue::ShouldDownload(content_disposition, info.mime_type)) {
          string16 filename = net::GetSuggestedFilename(url,
              content_disposition, info.charset, "", info.mime_type,
              "download");
          CefRefPtr<CefDownloadHandler> dl_handler;
          if (handler->GetDownloadHandler(browser_.get(), info.mime_type,
                                          filename, info.content_length,
                                          dl_handler)) {
            download_handler_ = dl_handler;
          }
        }
      }
    }

    if (peer_)
      peer_->OnReceivedResponse(info);
  }

  void NotifyReceivedData(int bytes_read) {
    DCHECK(MessageLoop::current() == owner_loop_);

    if (!peer_)
      return;

    // Make a local copy of buf_, since AsyncReadData reuses it.
    scoped_array<char> buf_copy(new char[bytes_read]);
    memcpy(buf_copy.get(), buf_->data(), bytes_read);

    // Continue reading more data into buf_
    // Note: Doing this before notifying our peer ensures our load events get
    // dispatched in a manner consistent with DumpRenderTree (and also avoids a
    // race condition).  If the order of the next 2 functions were reversed, the
    // peer could generate new requests in response to the received data, which
    // when run on the io thread, could race against this function in doing
    // another InvokeLater.  See bug 769249.
    CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
        &RequestProxy::AsyncReadData, this));

    CefRefPtr<CefStreamReader> resourceStream;

    if (content_filter_.get())
      content_filter_->ProcessData(buf_copy.get(), bytes_read, resourceStream);

    if (resourceStream.get()) {
      // The filter made some changes to the data in the buffer.
      resourceStream->Seek(0, SEEK_END);
      bytes_read = resourceStream->Tell();
      resourceStream->Seek(0, SEEK_SET);

      buf_copy.reset(new char[bytes_read]);
      resourceStream->Read(buf_copy.get(), 1, bytes_read);
    }

    if (download_handler_.get() &&
        !download_handler_->ReceivedData(buf_copy.get(), bytes_read)) {
      // Cancel loading by proxying over to the io thread.
      CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
          &RequestProxy::AsyncCancel, this));
    }

    peer_->OnReceivedData(buf_copy.get(), bytes_read, -1);
  }

  void NotifyDownloadedData(int bytes_read) {
    DCHECK(MessageLoop::current() == owner_loop_);

    if (!peer_)
      return;

    // Continue reading more data, see the comment in NotifyReceivedData.
    CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
        &RequestProxy::AsyncReadData, this));

    peer_->OnDownloadedData(bytes_read);
  }

  void NotifyCompletedRequest(int error_code,
                              const std::string& security_info,
                              const base::TimeTicks& complete_time) {
    DCHECK(MessageLoop::current() == owner_loop_);

    // Drain the content filter of all remaining data
    if (content_filter_.get()) {
      CefRefPtr<CefStreamReader> remainder;
      content_filter_->Drain(remainder);

      if (remainder.get()) {
        remainder->Seek(0, SEEK_END);
        int size = static_cast<int>(remainder->Tell());
        if (size > 0) {
          remainder->Seek(0, SEEK_SET);
          scoped_array<char> buf(new char[size]);
          remainder->Read(buf.get(), 1, size);

          if (download_handler_.get() &&
              !download_handler_->ReceivedData(buf.get(), size)) {
            // Cancel loading by proxying over to the io thread.
            CefThread::PostTask(CefThread::IO, FROM_HERE, base::Bind(
                &RequestProxy::AsyncCancel, this));
          }

          if (peer_)
            peer_->OnReceivedData(buf.get(), size, -1);
        }
      }
      content_filter_ = NULL;
    }

    if (download_handler_.get()) {
      download_handler_->Complete();
      download_handler_ = NULL;
    }

    if (peer_) {
      peer_->OnCompletedRequest(error_code, false, security_info, complete_time);
      DropPeer();  // ensure no further notifications
    }
  }

  void NotifyUploadProgress(uint64 position, uint64 size) {
    DCHECK(MessageLoop::current() == owner_loop_);

    if (peer_)
      peer_->OnUploadProgress(position, size);
  }

  // --------------------------------------------------------------------------
  // The following methods are called on the io thread.  They correspond to
  // actions performed on the owner's thread.

  void AsyncStart(RequestParams* params) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    bool handled = false;

    scoped_ptr<net::UploadDataStream> upload_data_stream;
    if (params->request_body) {
      upload_data_stream.reset(
          params->request_body->ResolveElementsAndCreateUploadDataStream(
              _Context->request_context()->blob_storage_controller(),
              _Context->file_system()->file_system_context(),
              base::MessageLoopProxy::current()));
    }

    if (browser_.get()) {
      CefRefPtr<CefClient> client = browser_->GetClient();
      CefRefPtr<CefRequestHandler> handler;
      if (client.get())
        handler = client->GetRequestHandler();

      if (handler.get()) {
        // Build the request object for passing to the handler
        CefRefPtr<CefRequest> request(new CefRequestImpl());
        CefRequestImpl* requestimpl =
            static_cast<CefRequestImpl*>(request.get());

        std::string originalUrl(params->url.spec());
        requestimpl->SetURL(originalUrl);
        requestimpl->SetMethod(params->method);

        // Transfer request headers
        CefRequest::HeaderMap headerMap;
        HttpHeaderUtils::ParseHeaders(params->headers, headerMap);
        headerMap.insert(std::make_pair("Referer", params->referrer.spec()));
        requestimpl->SetHeaderMap(headerMap);

        // Transfer post data, if any
        if (upload_data_stream) {
          CefRefPtr<CefPostData> postdata(new CefPostDataImpl());
          static_cast<CefPostDataImpl*>(postdata.get())->Set(
              *upload_data_stream.get());
          requestimpl->SetPostData(postdata);
        }

        int loadFlags = params->load_flags;

        // Handler output will be returned in these variables
        CefString redirectUrl;
        CefRefPtr<CefStreamReader> resourceStream;
        CefRefPtr<CefResponse> response(new CefResponseImpl());

        handled = handler->OnBeforeResourceLoad(browser_.get(), request,
            redirectUrl, resourceStream, response, loadFlags);
        if (!handled) {
          // Observe URL from request.
          const std::string requestUrl(request->GetURL());
          if (requestUrl != originalUrl)
            params->url = GURL(requestUrl);
          else if (!redirectUrl.empty())
            params->url = GURL(std::string(redirectUrl));

          // Observe method from request.
          params->method = request->GetMethod();

          // Observe headers from request.
          request->GetHeaderMap(headerMap);
          CefString referrerStr;
          referrerStr.FromASCII("Referer");
          CefRequest::HeaderMap::iterator referrer =
              headerMap.find(referrerStr);
          if (referrer == headerMap.end()) {
            params->referrer = GURL();
          } else {
            params->referrer = GURL(std::string(referrer->second));
            headerMap.erase(referrer);
          }
          params->headers = HttpHeaderUtils::GenerateHeaders(headerMap);

          // Observe post data from request.
          CefRefPtr<CefPostData> postData = request->GetPostData();
          if (postData.get()) {
            upload_data_stream.reset(
                static_cast<CefPostDataImpl*>(postData.get())->Get());
          } else {
            upload_data_stream.reset(NULL);
          }
        }

        if (handled) {
          // cancel the resource load
          OnCompletedRequest(net::ERR_ABORTED, std::string(),
                             base::TimeTicks());
        } else if (resourceStream.get()) {
          // load from the provided resource stream
          handled = true;

          resourceStream->Seek(0, SEEK_END);
          int64 offset = resourceStream->Tell();
          resourceStream->Seek(0, SEEK_SET);

          resource_stream_ = resourceStream;

          CefResponseImpl* responseImpl =
              static_cast<CefResponseImpl*>(response.get());

          ResourceResponseInfo info;
          info.content_length = offset;
          info.mime_type = response->GetMimeType();
          info.headers = responseImpl->GetResponseHeaders();
          OnReceivedResponse(info, params->url);
          AsyncReadData();
        } else if (response->GetStatus() != 0) {
          // status set, but no resource stream
          handled = true;

          CefResponseImpl* responseImpl =
              static_cast<CefResponseImpl*>(response.get());

          ResourceResponseInfo info;
          info.content_length = 0;
          info.mime_type = response->GetMimeType();
          info.headers = responseImpl->GetResponseHeaders();
          OnReceivedResponse(info, params->url);
          AsyncReadData();
        }

        if (!handled && ResourceType::IsFrame(params->request_type) &&
            !net::URLRequest::IsHandledProtocol(params->url.scheme())) {
          bool allow_os_execution = false;
          handled = handler->OnProtocolExecution(browser_.get(),
              params->url.spec(), allow_os_execution);
          if (!handled && allow_os_execution &&
              ExternalProtocolHandler::HandleExternalProtocol(params->url)) {
            handled = true;
          }

          if (handled)
            OnCompletedRequest(net::OK, std::string(), base::TimeTicks());
        }
      }
    }

    if (!handled) {
      net::URLRequestContext* context = browser_.get() ?
          browser_->request_context_proxy() : _Context->request_context();

      request_.reset(new net::URLRequest(params->url, this, context));
      request_->set_priority(params->priority);
      request_->set_method(params->method);
      request_->set_first_party_for_cookies(params->first_party_for_cookies);
      request_->set_referrer(params->referrer.spec());
      webkit_glue::ConfigureURLRequestForReferrerPolicy(
          request_.get(), params->referrer_policy);
      net::HttpRequestHeaders headers;
      headers.AddHeadersFromString(params->headers);
      request_->SetExtraRequestHeaders(headers);
      request_->set_load_flags(params->load_flags);
      if (upload_data_stream)
        request_->set_upload(upload_data_stream.Pass());
      request_->SetUserData(kCefUserData,
          new ExtraRequestInfo(browser_.get(), params->request_type));
      BrowserAppCacheSystem::SetExtraRequestInfo(
          request_.get(), params->appcache_host_id, params->request_type);

      download_to_file_ = params->download_to_file;
      if (download_to_file_) {
        base::FilePath path;
        if (file_util::CreateTemporaryFile(&path)) {
          downloaded_file_ = ShareableFileReference::GetOrCreate(
              path, ShareableFileReference::DELETE_ON_FINAL_RELEASE,
              base::MessageLoopProxy::current());
          file_stream_.reset(new net::FileStream(NULL));
          file_stream_->OpenSync(
              path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE);
        }
      }

      request_->Start();

      if (request_.get() && request_->has_upload() &&
          params->load_flags & net::LOAD_ENABLE_UPLOAD_PROGRESS) {
        upload_progress_timer_.Start(FROM_HERE,
            base::TimeDelta::FromMilliseconds(
                kUpdateUploadProgressIntervalMsec),
            this, &RequestProxy::MaybeUpdateUploadProgress);
      }
    }

    delete params;
  }

  void AsyncCancel() {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    // This can be null in cases where the request is already done.
    if (!resource_stream_.get() && !request_.get())
      return;

    if (request_.get())
      request_->Cancel();
    Done();
  }

  void AsyncFollowDeferredRedirect(bool has_new_first_party_for_cookies,
                                   const GURL& new_first_party_for_cookies) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    // This can be null in cases where the request is already done.
    if (!request_.get())
      return;

    if (has_new_first_party_for_cookies)
      request_->set_first_party_for_cookies(new_first_party_for_cookies);
    request_->FollowDeferredRedirect();
  }

  void AsyncSetDefersLoading(bool defer) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (defers_loading_ != defer) {
      defers_loading_ = defer;
      if (!defers_loading_ && defers_loading_want_read_) {
        // Perform the pending AsyncReadData now.
        defers_loading_want_read_ = false;
        AsyncReadData();
      }
    }
  }

  void AsyncReadData() {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    // Pause downloading if we're in deferred mode.
    if (defers_loading_) {
      defers_loading_want_read_ = true;
      return;
    }

    if (resource_stream_.get()) {
      // Read from the handler-provided resource stream
      int bytes_read = resource_stream_->Read(buf_->data(), 1, kDataSize);
      if (bytes_read > 0) {
        OnReceivedData(bytes_read);
      } else {
        Done();
      }
      return;
    }

    // This can be null in cases where the request is already done.
    if (!request_.get())
      return;

    if (request_->status().is_success()) {
      int bytes_read;
      if (request_->Read(buf_, kDataSize, &bytes_read) && bytes_read) {
        OnReceivedData(bytes_read);
      } else if (!request_->status().is_io_pending()) {
        Done();
      }  // else wait for OnReadCompleted
    } else {
      Done();
    }
  }

  // --------------------------------------------------------------------------
  // The following methods are event hooks (corresponding to net::URLRequest
  // callbacks) that run on the IO thread.  They are designed to be overridden
  // by the SyncRequestProxy subclass.

  virtual void OnReceivedRedirect(
      const GURL& new_url,
      const ResourceResponseInfo& info,
      bool* defer_redirect) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    *defer_redirect = true;  // See AsyncFollowDeferredRedirect
    owner_loop_->PostTask(FROM_HERE, base::Bind(
        &RequestProxy::NotifyReceivedRedirect, this, new_url, info));
  }

  virtual void OnReceivedResponse(
      const ResourceResponseInfo& info,
      // only used when loading from a resource stream
      const GURL& simulated_url) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    GURL url;
    bool allow_download(false);
    if (request_.get()) {
      url = request_->url();
      ExtraRequestInfo* info =
          static_cast<ExtraRequestInfo*>(request_->GetUserData(kCefUserData));
      if (info)
        allow_download = info->allow_download();
    } else if (!simulated_url.is_empty() && simulated_url.is_valid()) {
      url = simulated_url;
    }

    owner_loop_->PostTask(FROM_HERE, base::Bind(
        &RequestProxy::NotifyReceivedResponse, this, info, url,
        allow_download));
  }

  virtual void OnReceivedData(int bytes_read) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (download_to_file_) {
      file_stream_->WriteSync(buf_->data(), bytes_read);
      owner_loop_->PostTask(FROM_HERE, base::Bind(
          &RequestProxy::NotifyDownloadedData, this, bytes_read));
      return;
    }

    owner_loop_->PostTask(FROM_HERE, base::Bind(
        &RequestProxy::NotifyReceivedData, this, bytes_read));
  }

  virtual void OnCompletedRequest(int error_code,
                                  const std::string& security_info,
                                  const base::TimeTicks& complete_time) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (download_to_file_)
      file_stream_.reset();

    owner_loop_->PostTask(FROM_HERE, base::Bind(
        &RequestProxy::NotifyCompletedRequest, this, error_code, security_info,
        complete_time));
  }

  // --------------------------------------------------------------------------
  // net::URLRequest::Delegate implementation:

  virtual void OnReceivedRedirect(net::URLRequest* request,
                                  const GURL& new_url,
                                  bool* defer_redirect) OVERRIDE {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    DCHECK(request->status().is_success());
    ResourceResponseInfo info;
    PopulateResponseInfo(request, &info);
    OnReceivedRedirect(new_url, info, defer_redirect);
  }

  virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (request->status().is_success()) {
      ResourceResponseInfo info;
      PopulateResponseInfo(request, &info);
      OnReceivedResponse(info, GURL::EmptyGURL());
      AsyncReadData();  // start reading
    } else {
      Done();
    }
  }

  virtual void OnAuthRequired(net::URLRequest* request,
                              net::AuthChallengeInfo* auth_info) OVERRIDE {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (browser_.get()) {
      CefRefPtr<CefClient> client = browser_->GetClient();
      if (client.get()) {
        CefRefPtr<CefRequestHandler> handler = client->GetRequestHandler();
        if (handler.get()) {
          CefString username, password;
          if (handler->GetAuthCredentials(browser_.get(),
                                          auth_info->is_proxy,
                                          auth_info->challenger.host(),
                                          auth_info->challenger.port(),
                                          auth_info->realm,
                                          auth_info->scheme,
                                          username, password)) {
            request->SetAuth(net::AuthCredentials(username, password));
            return;
          }
        }
      }
    }

    request->CancelAuth();
  }

  virtual void OnSSLCertificateError(net::URLRequest* request,
                                     const net::SSLInfo& ssl_info,
                                     bool fatal) OVERRIDE {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    // Allow all certificate errors.
    request->ContinueDespiteLastError();
  }

  virtual void OnReadCompleted(net::URLRequest* request,
                               int bytes_read) OVERRIDE {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (request->status().is_success() && bytes_read > 0) {
      OnReceivedData(bytes_read);
    } else {
      Done();
    }
  }

  // --------------------------------------------------------------------------
  // Helpers and data:

  void Done() {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (resource_stream_.get()) {
      // Resource stream reads always complete successfully
      OnCompletedRequest(0, std::string(), base::TimeTicks());
      resource_stream_ = NULL;
    } else if (request_.get()) {
      if (upload_progress_timer_.IsRunning()) {
        MaybeUpdateUploadProgress();
        upload_progress_timer_.Stop();
      }
      DCHECK(request_.get());
      OnCompletedRequest(request_->status().error(), std::string(),
                         base::TimeTicks());
      request_.reset();  // destroy on the io thread
    }
  }

  void MaybeUpdateUploadProgress() {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    // If a redirect is received upload is cancelled in net::URLRequest, we
    // should try to stop the |upload_progress_timer_| timer and return.
    if (!request_->has_upload()) {
      if (upload_progress_timer_.IsRunning())
        upload_progress_timer_.Stop();
      return;
    }

    net::UploadProgress progress = request_->GetUploadProgress();
    if (progress.position() == last_upload_position_)
      return;  // no progress made since last time

    const uint64 kHalfPercentIncrements = 200;
    const base::TimeDelta kOneSecond = base::TimeDelta::FromMilliseconds(1000);

    uint64 amt_since_last = progress.position() - last_upload_position_;
    base::TimeDelta time_since_last = base::TimeTicks::Now() -
                                      last_upload_ticks_;

    bool is_finished = (progress.size() == progress.position());
    bool enough_new_progress = (amt_since_last > (progress.size() /
                                                  kHalfPercentIncrements));
    bool too_much_time_passed = time_since_last > kOneSecond;

    if (is_finished || enough_new_progress || too_much_time_passed) {
      owner_loop_->PostTask(
          FROM_HERE,
          base::Bind(&RequestProxy::NotifyUploadProgress, this,
                     progress.position(), progress.size()));
      last_upload_ticks_ = base::TimeTicks::Now();
      last_upload_position_ = progress.position();
    }
  }

  void PopulateResponseInfo(net::URLRequest* request,
                            ResourceResponseInfo* info) const {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    info->request_time = request->request_time();
    info->response_time = request->response_time();
    info->headers = request->response_headers();
    request->GetMimeType(&info->mime_type);
    request->GetCharset(&info->charset);
    info->content_length = request->GetExpectedContentSize();
    if (downloaded_file_)
      info->download_file_path = downloaded_file_->path();
    BrowserAppCacheSystem::GetExtraResponseInfo(
        request,
        &info->appcache_id,
        &info->appcache_manifest_url);
  }

  scoped_ptr<net::URLRequest> request_;
  CefRefPtr<CefStreamReader> resource_stream_;

  // Support for request.download_to_file behavior.
  bool download_to_file_;
  scoped_ptr<net::FileStream> file_stream_;
  scoped_refptr<ShareableFileReference> downloaded_file_;

  // Size of our async IO data buffers. Limited by the sanity check in
  // URLRequestJob::Read().
  static const int kDataSize = 1000000-1;

  // read buffer for async IO
  scoped_refptr<net::IOBuffer> buf_;

  CefRefPtr<CefBrowserImpl> browser_;

  MessageLoop* owner_loop_;

  // This is our peer in WebKit (implemented as ResourceHandleInternal). We do
  // not manage its lifetime, and we may only access it from the owner's
  // message loop (owner_loop_).
  ResourceLoaderBridge::Peer* peer_;

  // Timer used to pull upload progress info.
  base::RepeatingTimer<RequestProxy> upload_progress_timer_;

  // Info used to determine whether or not to send an upload progress update.
  uint64 last_upload_position_;
  base::TimeTicks last_upload_ticks_;

  CefRefPtr<CefDownloadHandler> download_handler_;
  CefRefPtr<CefContentFilter> content_filter_;

  // True if loading of data is currently deferred.
  bool defers_loading_;

  // True if an AsyncReadData was scheduled while we were deferred.
  bool defers_loading_want_read_;
};

//-----------------------------------------------------------------------------

class SyncRequestProxy : public RequestProxy {
 public:
  explicit SyncRequestProxy(CefRefPtr<CefBrowserImpl> browser,
                            ResourceLoaderBridge::SyncLoadResponse* result)
      : RequestProxy(browser), result_(result), event_(true, false) {
  }

  void WaitForCompletion() {
    event_.Wait();
  }

  // --------------------------------------------------------------------------
  // Event hooks that run on the IO thread:

  virtual void OnReceivedRedirect(
      const GURL& new_url,
      const ResourceResponseInfo& info,
      bool* defer_redirect) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    // TODO(darin): It would be much better if this could live in WebCore, but
    // doing so requires API changes at all levels.  Similar code exists in
    // WebCore/platform/network/cf/ResourceHandleCFNet.cpp :-(
    if (new_url.GetOrigin() != result_->url.GetOrigin()) {
      DLOG(WARNING) << "Cross origin redirect denied";
      Cancel();
      return;
    }
    result_->url = new_url;
  }

  virtual void OnReceivedResponse(const ResourceResponseInfo& info,
                                  const GURL&) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    *static_cast<ResourceResponseInfo*>(result_) = info;
  }

  virtual void OnReceivedData(int bytes_read) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (download_to_file_)
      file_stream_->WriteSync(buf_->data(), bytes_read);
    else
      result_->data.append(buf_->data(), bytes_read);
    AsyncReadData();  // read more (may recurse)
  }

  virtual void OnCompletedRequest(int error_code,
                                  const std::string& security_info,
                                  const base::TimeTicks& complete_time) {
    DCHECK(CefThread::CurrentlyOn(CefThread::IO));

    if (download_to_file_)
      file_stream_.reset();

    result_->error_code = error_code;
    event_.Signal();
  }

 protected:
  virtual void InitializeParams(RequestParams* params) {
    // For synchronous requests ignore load limits to avoid a deadlock problem
    // in SyncRequestProxy (issue #192).
    params->load_flags |= net::LOAD_IGNORE_LIMITS;
    params->priority = net::HIGHEST;
  }

 private:
  ResourceLoaderBridge::SyncLoadResponse* result_;
  base::WaitableEvent event_;
};

//-----------------------------------------------------------------------------

class ResourceLoaderBridgeImpl : public ResourceLoaderBridge,
                                 public base::NonThreadSafe {
 public:
  ResourceLoaderBridgeImpl(CefRefPtr<CefBrowserImpl> browser,
      const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info)
      : browser_(browser),
        params_(new RequestParams),
        proxy_(NULL) {
    params_->method = request_info.method;
    params_->url = request_info.url;
    params_->first_party_for_cookies = request_info.first_party_for_cookies;
    params_->referrer = request_info.referrer;
    params_->referrer_policy = request_info.referrer_policy;
    params_->headers = request_info.headers;
    params_->load_flags = request_info.load_flags;
    params_->request_type = request_info.request_type;
    params_->appcache_host_id = request_info.appcache_host_id;
    params_->download_to_file = request_info.download_to_file;
  }

  virtual ~ResourceLoaderBridgeImpl() {
    if (proxy_) {
      proxy_->DropPeer();
      // Let the proxy die on the IO thread
      proxy_->AddRef();
      CefThread::ReleaseSoon(CefThread::IO, FROM_HERE, proxy_.get());
    }
  }

  // --------------------------------------------------------------------------
  // ResourceLoaderBridge implementation:

  virtual void SetRequestBody(ResourceRequestBody* request_body) OVERRIDE {
    DCHECK(CalledOnValidThread());
    DCHECK(params_.get());
    DCHECK(!params_->request_body);
    params_->request_body = request_body;
  }

  virtual bool Start(Peer* peer) OVERRIDE {
    DCHECK(CalledOnValidThread());
    DCHECK(!proxy_);

    proxy_ = new RequestProxy(browser_);
    proxy_->Start(peer, params_.release());

    return true;  // Any errors will be reported asynchronously.
  }

  virtual void Cancel() OVERRIDE {
    DCHECK(CalledOnValidThread());
    DCHECK(proxy_);
    proxy_->Cancel();
  }

  virtual void SetDefersLoading(bool value) OVERRIDE {
    DCHECK(CalledOnValidThread());
    DCHECK(proxy_);
    proxy_->SetDefersLoading(value);
  }

  virtual void DidChangePriority(net::RequestPriority new_priority) OVERRIDE {
  }

  virtual void SyncLoad(SyncLoadResponse* response) OVERRIDE {
    DCHECK(CalledOnValidThread());
    DCHECK(!proxy_);

    // this may change as the result of a redirect
    response->url = params_->url;

    proxy_ = new SyncRequestProxy(browser_, response);
    proxy_->Start(NULL, params_.release());

    static_cast<SyncRequestProxy*>(proxy_.get())->WaitForCompletion();
  }

 private:
  CefRefPtr<CefBrowserImpl> browser_;

  // Ownership of params_ is transfered to the proxy when the proxy is created.
  scoped_ptr<RequestParams> params_;

  // The request proxy is allocated when we start the request, and then it
  // sticks around until this ResourceLoaderBridge is destroyed.
  scoped_refptr<RequestProxy> proxy_;
};

}  // anonymous namespace

//-----------------------------------------------------------------------------

// static
webkit_glue::ResourceLoaderBridge* BrowserResourceLoaderBridge::Create(
    const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info) {
  DCHECK(CefThread::CurrentlyOn(CefThread::UI));

  CefRefPtr<CefBrowserImpl> browser =
      _Context->GetBrowserByID(request_info.routing_id);
  return new ResourceLoaderBridgeImpl(browser.get(), request_info);
}

//-----------------------------------------------------------------------------

// static
CefRefPtr<CefBrowserImpl> BrowserResourceLoaderBridge::GetBrowserForRequest(
    net::URLRequest* request) {
  REQUIRE_IOT();
  ExtraRequestInfo* extra_info =
      static_cast<ExtraRequestInfo*>(request->GetUserData(kCefUserData));
  if (extra_info)
    return extra_info->browser();
  return NULL;
}

// static
scoped_refptr<base::MessageLoopProxy>
BrowserResourceLoaderBridge::GetCacheThread() {
  return CefThread::GetMessageLoopProxyForThread(CefThread::FILE);
}

// static
net::URLRequest::Interceptor*
BrowserResourceLoaderBridge::CreateRequestInterceptor() {
  return new RequestInterceptor();
}