| Index: net/url_request/url_request_http_job.cc
|
| diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
|
| index ec0134a5ac11f58a2425764527dba92e981c6fe0..21b84f23ee84c7fc85f1444dd9dadb31ef28d099 100644
|
| --- a/net/url_request/url_request_http_job.cc
|
| +++ b/net/url_request/url_request_http_job.cc
|
| @@ -117,6 +117,7 @@ URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request,
|
| return new URLRequestHttpJob(request);
|
| }
|
|
|
| +
|
| URLRequestHttpJob::URLRequestHttpJob(URLRequest* request)
|
| : URLRequestJob(request),
|
| response_info_(NULL),
|
| @@ -142,354 +143,350 @@ URLRequestHttpJob::URLRequestHttpJob(URLRequest* request)
|
| ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
|
| }
|
|
|
| -URLRequestHttpJob::~URLRequestHttpJob() {
|
| - DCHECK(!sdch_test_control_ || !sdch_test_activated_);
|
| - if (!IsCachedContent()) {
|
| - if (sdch_test_control_)
|
| - RecordPacketStats(SDCH_EXPERIMENT_HOLDBACK);
|
| - if (sdch_test_activated_)
|
| - RecordPacketStats(SDCH_EXPERIMENT_DECODE);
|
| +void URLRequestHttpJob::NotifyHeadersComplete() {
|
| + DCHECK(!response_info_);
|
| +
|
| + response_info_ = transaction_->GetResponseInfo();
|
| +
|
| + // Save boolean, as we'll need this info at destruction time, and filters may
|
| + // also need this info.
|
| + is_cached_content_ = response_info_->was_cached;
|
| +
|
| + if (!is_cached_content_) {
|
| + URLRequestThrottlerHeaderAdapter response_adapter(
|
| + response_info_->headers);
|
| + throttling_entry_->UpdateWithResponse(&response_adapter);
|
| }
|
| - // Make sure SDCH filters are told to emit histogram data while this class
|
| - // can still service the IsCachedContent() call.
|
| - DestroyFilters();
|
|
|
| - if (sdch_dictionary_url_.is_valid()) {
|
| - // Prior to reaching the destructor, request_ has been set to a NULL
|
| - // pointer, so request_->url() is no longer valid in the destructor, and we
|
| - // use an alternate copy |request_info_.url|.
|
| - SdchManager* manager = SdchManager::Global();
|
| - // To be extra safe, since this is a "different time" from when we decided
|
| - // to get the dictionary, we'll validate that an SdchManager is available.
|
| - // At shutdown time, care is taken to be sure that we don't delete this
|
| - // globally useful instance "too soon," so this check is just defensive
|
| - // coding to assure that IF the system is shutting down, we don't have any
|
| - // problem if the manager was deleted ahead of time.
|
| - if (manager) // Defensive programming.
|
| - manager->FetchDictionary(request_info_.url, sdch_dictionary_url_);
|
| + ProcessStrictTransportSecurityHeader();
|
| +
|
| + if (SdchManager::Global() &&
|
| + SdchManager::Global()->IsInSupportedDomain(request_->url())) {
|
| + static const std::string name = "Get-Dictionary";
|
| + std::string url_text;
|
| + void* iter = NULL;
|
| + // TODO(jar): We need to not fetch dictionaries the first time they are
|
| + // seen, but rather wait until we can justify their usefulness.
|
| + // For now, we will only fetch the first dictionary, which will at least
|
| + // require multiple suggestions before we get additional ones for this site.
|
| + // Eventually we should wait until a dictionary is requested several times
|
| + // before we even download it (so that we don't waste memory or bandwidth).
|
| + if (response_info_->headers->EnumerateHeader(&iter, name, &url_text)) {
|
| + // request_->url() won't be valid in the destructor, so we use an
|
| + // alternate copy.
|
| + DCHECK(request_->url() == request_info_.url);
|
| + // Resolve suggested URL relative to request url.
|
| + sdch_dictionary_url_ = request_info_.url.Resolve(url_text);
|
| + }
|
| }
|
| -}
|
|
|
| -void URLRequestHttpJob::SetUpload(UploadData* upload) {
|
| - DCHECK(!transaction_.get()) << "cannot change once started";
|
| - request_info_.upload_data = upload;
|
| + // The HTTP transaction may be restarted several times for the purposes
|
| + // of sending authorization information. Each time it restarts, we get
|
| + // notified of the headers completion so that we can update the cookie store.
|
| + if (transaction_->IsReadyToRestartForAuth()) {
|
| + DCHECK(!response_info_->auth_challenge.get());
|
| + RestartTransactionWithAuth(string16(), string16());
|
| + return;
|
| + }
|
| +
|
| + URLRequestJob::NotifyHeadersComplete();
|
| }
|
|
|
| -void URLRequestHttpJob::SetExtraRequestHeaders(
|
| - const HttpRequestHeaders& headers) {
|
| - DCHECK(!transaction_.get()) << "cannot change once started";
|
| - request_info_.extra_headers.CopyFrom(headers);
|
| +void URLRequestHttpJob::DestroyTransaction() {
|
| + DCHECK(transaction_.get());
|
| +
|
| + transaction_.reset();
|
| + response_info_ = NULL;
|
| + context_ = NULL;
|
| }
|
|
|
| -void URLRequestHttpJob::Start() {
|
| - DCHECK(!transaction_.get());
|
| +void URLRequestHttpJob::StartTransaction() {
|
| + // NOTE: This method assumes that request_info_ is already setup properly.
|
|
|
| - // Ensure that we do not send username and password fields in the referrer.
|
| - GURL referrer(request_->GetSanitizedReferrer());
|
| + // If we already have a transaction, then we should restart the transaction
|
| + // with auth provided by username_ and password_.
|
|
|
| - request_info_.url = request_->url();
|
| - request_info_.referrer = referrer;
|
| - request_info_.method = request_->method();
|
| - request_info_.load_flags = request_->load_flags();
|
| - request_info_.priority = request_->priority();
|
| + int rv;
|
|
|
| - if (request_->context()) {
|
| - request_info_.extra_headers.SetHeader(
|
| - HttpRequestHeaders::kUserAgent,
|
| - request_->context()->GetUserAgent(request_->url()));
|
| - }
|
| + if (transaction_.get()) {
|
| + rv = transaction_->RestartWithAuth(username_, password_, &start_callback_);
|
| + username_.clear();
|
| + password_.clear();
|
| + } else {
|
| + DCHECK(request_->context());
|
| + DCHECK(request_->context()->http_transaction_factory());
|
|
|
| - AddExtraHeaders();
|
| - AddCookieHeaderAndStart();
|
| -}
|
| + rv = request_->context()->http_transaction_factory()->CreateTransaction(
|
| + &transaction_);
|
| + if (rv == OK) {
|
| + if (!throttling_entry_->IsDuringExponentialBackoff() ||
|
| + !net::URLRequestThrottlerManager::GetInstance()->
|
| + enforce_throttling()) {
|
| + rv = transaction_->Start(
|
| + &request_info_, &start_callback_, request_->net_log());
|
| + } else {
|
| + // Special error code for the exponential back-off module.
|
| + rv = ERR_TEMPORARILY_THROTTLED;
|
| + }
|
| + // Make sure the context is alive for the duration of the
|
| + // transaction.
|
| + context_ = request_->context();
|
| + }
|
| + }
|
|
|
| -void URLRequestHttpJob::Kill() {
|
| - if (!transaction_.get())
|
| + if (rv == ERR_IO_PENDING)
|
| return;
|
|
|
| - DestroyTransaction();
|
| - URLRequestJob::Kill();
|
| -}
|
| -
|
| -LoadState URLRequestHttpJob::GetLoadState() const {
|
| - return transaction_.get() ?
|
| - transaction_->GetLoadState() : LOAD_STATE_IDLE;
|
| + // The transaction started synchronously, but we need to notify the
|
| + // URLRequest delegate via the message loop.
|
| + MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + method_factory_.NewRunnableMethod(
|
| + &URLRequestHttpJob::OnStartCompleted, rv));
|
| }
|
|
|
| -uint64 URLRequestHttpJob::GetUploadProgress() const {
|
| - return transaction_.get() ? transaction_->GetUploadProgress() : 0;
|
| -}
|
| +void URLRequestHttpJob::AddExtraHeaders() {
|
| + // TODO(jar): Consider optimizing away SDCH advertising bytes when the URL is
|
| + // probably an img or such (and SDCH encoding is not likely).
|
| + bool advertise_sdch = SdchManager::Global() &&
|
| + SdchManager::Global()->IsInSupportedDomain(request_->url());
|
| + std::string avail_dictionaries;
|
| + if (advertise_sdch) {
|
| + SdchManager::Global()->GetAvailDictionaryList(request_->url(),
|
| + &avail_dictionaries);
|
|
|
| -bool URLRequestHttpJob::GetMimeType(std::string* mime_type) const {
|
| - DCHECK(transaction_.get());
|
| + // The AllowLatencyExperiment() is only true if we've successfully done a
|
| + // full SDCH compression recently in this browser session for this host.
|
| + // Note that for this path, there might be no applicable dictionaries, and
|
| + // hence we can't participate in the experiment.
|
| + if (!avail_dictionaries.empty() &&
|
| + SdchManager::Global()->AllowLatencyExperiment(request_->url())) {
|
| + // We are participating in the test (or control), and hence we'll
|
| + // eventually record statistics via either SDCH_EXPERIMENT_DECODE or
|
| + // SDCH_EXPERIMENT_HOLDBACK, and we'll need some packet timing data.
|
| + EnablePacketCounting(kSdchPacketHistogramCount);
|
| + if (base::RandDouble() < .01) {
|
| + sdch_test_control_ = true; // 1% probability.
|
| + advertise_sdch = false;
|
| + } else {
|
| + sdch_test_activated_ = true;
|
| + }
|
| + }
|
| + }
|
|
|
| - if (!response_info_)
|
| - return false;
|
| + // Supply Accept-Encoding headers first so that it is more likely that they
|
| + // will be in the first transmitted packet. This can sometimes make it easier
|
| + // to filter and analyze the streams to assure that a proxy has not damaged
|
| + // these headers. Some proxies deliberately corrupt Accept-Encoding headers.
|
| + if (!advertise_sdch) {
|
| + // Tell the server what compression formats we support (other than SDCH).
|
| + request_info_.extra_headers.SetHeader(
|
| + HttpRequestHeaders::kAcceptEncoding, "gzip,deflate");
|
| + } else {
|
| + // Include SDCH in acceptable list.
|
| + request_info_.extra_headers.SetHeader(
|
| + HttpRequestHeaders::kAcceptEncoding, "gzip,deflate,sdch");
|
| + if (!avail_dictionaries.empty()) {
|
| + request_info_.extra_headers.SetHeader(
|
| + kAvailDictionaryHeader,
|
| + avail_dictionaries);
|
| + sdch_dictionary_advertised_ = true;
|
| + // Since we're tagging this transaction as advertising a dictionary, we'll
|
| + // definately employ an SDCH filter (or tentative sdch filter) when we get
|
| + // a response. When done, we'll record histograms via SDCH_DECODE or
|
| + // SDCH_PASSTHROUGH. Hence we need to record packet arrival times.
|
| + EnablePacketCounting(kSdchPacketHistogramCount);
|
| + }
|
| + }
|
|
|
| - return response_info_->headers->GetMimeType(mime_type);
|
| + URLRequestContext* context = request_->context();
|
| + if (context) {
|
| + // Only add default Accept-Language and Accept-Charset if the request
|
| + // didn't have them specified.
|
| + if (!request_info_.extra_headers.HasHeader(
|
| + HttpRequestHeaders::kAcceptLanguage)) {
|
| + request_info_.extra_headers.SetHeader(
|
| + HttpRequestHeaders::kAcceptLanguage,
|
| + context->accept_language());
|
| + }
|
| + if (!request_info_.extra_headers.HasHeader(
|
| + HttpRequestHeaders::kAcceptCharset)) {
|
| + request_info_.extra_headers.SetHeader(
|
| + HttpRequestHeaders::kAcceptCharset,
|
| + context->accept_charset());
|
| + }
|
| + }
|
| }
|
|
|
| -bool URLRequestHttpJob::GetCharset(std::string* charset) {
|
| - DCHECK(transaction_.get());
|
| +void URLRequestHttpJob::AddCookieHeaderAndStart() {
|
| + // No matter what, we want to report our status as IO pending since we will
|
| + // be notifying our consumer asynchronously via OnStartCompleted.
|
| + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
|
|
| - if (!response_info_)
|
| - return false;
|
| + AddRef(); // Balanced in OnCanGetCookiesCompleted
|
|
|
| - return response_info_->headers->GetCharset(charset);
|
| -}
|
| + int policy = OK;
|
|
|
| -void URLRequestHttpJob::GetResponseInfo(HttpResponseInfo* info) {
|
| - DCHECK(request_);
|
| - DCHECK(transaction_.get());
|
| + if (request_info_.load_flags & LOAD_DO_NOT_SEND_COOKIES) {
|
| + policy = ERR_FAILED;
|
| + } else if (request_->context()->cookie_policy()) {
|
| + policy = request_->context()->cookie_policy()->CanGetCookies(
|
| + request_->url(),
|
| + request_->first_party_for_cookies(),
|
| + &can_get_cookies_callback_);
|
| + if (policy == ERR_IO_PENDING)
|
| + return; // Wait for completion callback
|
| + }
|
|
|
| - if (response_info_)
|
| - *info = *response_info_;
|
| + OnCanGetCookiesCompleted(policy);
|
| }
|
|
|
| -bool URLRequestHttpJob::GetResponseCookies(
|
| - std::vector<std::string>* cookies) {
|
| +void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete() {
|
| DCHECK(transaction_.get());
|
|
|
| - if (!response_info_)
|
| - return false;
|
| + const HttpResponseInfo* response_info = transaction_->GetResponseInfo();
|
| + DCHECK(response_info);
|
|
|
| - // TODO(darin): Why are we extracting response cookies again? Perhaps we
|
| - // should just leverage response_cookies_.
|
| -
|
| - cookies->clear();
|
| - FetchResponseCookies(response_info_, cookies);
|
| - return true;
|
| -}
|
| -
|
| -int URLRequestHttpJob::GetResponseCode() const {
|
| - DCHECK(transaction_.get());
|
| + response_cookies_.clear();
|
| + response_cookies_save_index_ = 0;
|
|
|
| - if (!response_info_)
|
| - return -1;
|
| + FetchResponseCookies(response_info, &response_cookies_);
|
|
|
| - return response_info_->headers->response_code();
|
| + // Now, loop over the response cookies, and attempt to persist each.
|
| + SaveNextCookie();
|
| }
|
|
|
| -bool URLRequestHttpJob::GetContentEncodings(
|
| - std::vector<Filter::FilterType>* encoding_types) {
|
| - DCHECK(transaction_.get());
|
| - if (!response_info_)
|
| - return false;
|
| - DCHECK(encoding_types->empty());
|
| -
|
| - std::string encoding_type;
|
| - void* iter = NULL;
|
| - while (response_info_->headers->EnumerateHeader(&iter, "Content-Encoding",
|
| - &encoding_type)) {
|
| - encoding_types->push_back(Filter::ConvertEncodingToType(encoding_type));
|
| +void URLRequestHttpJob::SaveNextCookie() {
|
| + if (response_cookies_save_index_ == response_cookies_.size()) {
|
| + response_cookies_.clear();
|
| + response_cookies_save_index_ = 0;
|
| + SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
|
| + NotifyHeadersComplete();
|
| + return;
|
| }
|
|
|
| - // Even if encoding types are empty, there is a chance that we need to add
|
| - // some decoding, as some proxies strip encoding completely. In such cases,
|
| - // we may need to add (for example) SDCH filtering (when the context suggests
|
| - // it is appropriate).
|
| - Filter::FixupEncodingTypes(*this, encoding_types);
|
| -
|
| - return !encoding_types->empty();
|
| -}
|
| -
|
| -bool URLRequestHttpJob::IsCachedContent() const {
|
| - return is_cached_content_;
|
| -}
|
| -
|
| -bool URLRequestHttpJob::IsSdchResponse() const {
|
| - return sdch_dictionary_advertised_;
|
| -}
|
| -
|
| -bool URLRequestHttpJob::IsSafeRedirect(const GURL& location) {
|
| - // We only allow redirects to certain "safe" protocols. This does not
|
| - // restrict redirects to externally handled protocols. Our consumer would
|
| - // need to take care of those.
|
| + // No matter what, we want to report our status as IO pending since we will
|
| + // be notifying our consumer asynchronously via OnStartCompleted.
|
| + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
|
|
| - if (!URLRequest::IsHandledURL(location))
|
| - return true;
|
| + AddRef(); // Balanced in OnCanSetCookieCompleted
|
|
|
| - static const char* kSafeSchemes[] = {
|
| - "http",
|
| - "https",
|
| - "ftp"
|
| - };
|
| + int policy = OK;
|
|
|
| - for (size_t i = 0; i < arraysize(kSafeSchemes); ++i) {
|
| - if (location.SchemeIs(kSafeSchemes[i]))
|
| - return true;
|
| + if (request_info_.load_flags & LOAD_DO_NOT_SAVE_COOKIES) {
|
| + policy = ERR_FAILED;
|
| + } else if (request_->context()->cookie_policy()) {
|
| + policy = request_->context()->cookie_policy()->CanSetCookie(
|
| + request_->url(),
|
| + request_->first_party_for_cookies(),
|
| + response_cookies_[response_cookies_save_index_],
|
| + &can_set_cookie_callback_);
|
| + if (policy == ERR_IO_PENDING)
|
| + return; // Wait for completion callback
|
| }
|
|
|
| - return false;
|
| + OnCanSetCookieCompleted(policy);
|
| }
|
|
|
| -bool URLRequestHttpJob::NeedsAuth() {
|
| - int code = GetResponseCode();
|
| - if (code == -1)
|
| - return false;
|
| +void URLRequestHttpJob::FetchResponseCookies(
|
| + const HttpResponseInfo* response_info,
|
| + std::vector<std::string>* cookies) {
|
| + std::string name = "Set-Cookie";
|
| + std::string value;
|
|
|
| - // Check if we need either Proxy or WWW Authentication. This could happen
|
| - // because we either provided no auth info, or provided incorrect info.
|
| - switch (code) {
|
| - case 407:
|
| - if (proxy_auth_state_ == AUTH_STATE_CANCELED)
|
| - return false;
|
| - proxy_auth_state_ = AUTH_STATE_NEED_AUTH;
|
| - return true;
|
| - case 401:
|
| - if (server_auth_state_ == AUTH_STATE_CANCELED)
|
| - return false;
|
| - server_auth_state_ = AUTH_STATE_NEED_AUTH;
|
| - return true;
|
| + void* iter = NULL;
|
| + while (response_info->headers->EnumerateHeader(&iter, name, &value)) {
|
| + if (!value.empty())
|
| + cookies->push_back(value);
|
| }
|
| - return false;
|
| }
|
|
|
| -void URLRequestHttpJob::GetAuthChallengeInfo(
|
| - scoped_refptr<AuthChallengeInfo>* result) {
|
| - DCHECK(transaction_.get());
|
| +void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() {
|
| DCHECK(response_info_);
|
|
|
| - // sanity checks:
|
| - DCHECK(proxy_auth_state_ == AUTH_STATE_NEED_AUTH ||
|
| - server_auth_state_ == AUTH_STATE_NEED_AUTH);
|
| - DCHECK(response_info_->headers->response_code() == 401 ||
|
| - response_info_->headers->response_code() == 407);
|
| -
|
| - *result = response_info_->auth_challenge;
|
| -}
|
| -
|
| -void URLRequestHttpJob::SetAuth(const string16& username,
|
| - const string16& password) {
|
| - DCHECK(transaction_.get());
|
| -
|
| - // Proxy gets set first, then WWW.
|
| - if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
|
| - proxy_auth_state_ = AUTH_STATE_HAVE_AUTH;
|
| - } else {
|
| - DCHECK(server_auth_state_ == AUTH_STATE_NEED_AUTH);
|
| - server_auth_state_ = AUTH_STATE_HAVE_AUTH;
|
| - }
|
| + URLRequestContext* ctx = request_->context();
|
| + if (!ctx || !ctx->transport_security_state())
|
| + return;
|
|
|
| - RestartTransactionWithAuth(username, password);
|
| -}
|
| + const bool https = response_info_->ssl_info.is_valid();
|
| + const bool valid_https =
|
| + https && !IsCertStatusError(response_info_->ssl_info.cert_status);
|
|
|
| -void URLRequestHttpJob::RestartTransactionWithAuth(
|
| - const string16& username,
|
| - const string16& password) {
|
| - username_ = username;
|
| - password_ = password;
|
| + std::string name = "Strict-Transport-Security";
|
| + std::string value;
|
|
|
| - // These will be reset in OnStartCompleted.
|
| - response_info_ = NULL;
|
| - response_cookies_.clear();
|
| + int max_age;
|
| + bool include_subdomains;
|
|
|
| - // Update the cookies, since the cookie store may have been updated from the
|
| - // headers in the 401/407. Since cookies were already appended to
|
| - // extra_headers, we need to strip them out before adding them again.
|
| - request_info_.extra_headers.RemoveHeader(
|
| - HttpRequestHeaders::kCookie);
|
| + void* iter = NULL;
|
| + while (response_info_->headers->EnumerateHeader(&iter, name, &value)) {
|
| + const bool ok = TransportSecurityState::ParseHeader(
|
| + value, &max_age, &include_subdomains);
|
| + if (!ok)
|
| + continue;
|
| + // We will only accept strict mode if we saw the header from an HTTPS
|
| + // connection with no certificate problems.
|
| + if (!valid_https)
|
| + continue;
|
| + base::Time current_time(base::Time::Now());
|
| + base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age);
|
|
|
| - AddCookieHeaderAndStart();
|
| -}
|
| + TransportSecurityState::DomainState domain_state;
|
| + domain_state.expiry = current_time + max_age_delta;
|
| + domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT;
|
| + domain_state.include_subdomains = include_subdomains;
|
|
|
| -void URLRequestHttpJob::CancelAuth() {
|
| - // Proxy gets set first, then WWW.
|
| - if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
|
| - proxy_auth_state_ = AUTH_STATE_CANCELED;
|
| - } else {
|
| - DCHECK(server_auth_state_ == AUTH_STATE_NEED_AUTH);
|
| - server_auth_state_ = AUTH_STATE_CANCELED;
|
| + ctx->transport_security_state()->EnableHost(request_info_.url.host(),
|
| + domain_state);
|
| }
|
|
|
| - // These will be reset in OnStartCompleted.
|
| - response_info_ = NULL;
|
| - response_cookies_.clear();
|
| -
|
| - // OK, let the consumer read the error page...
|
| - //
|
| - // Because we set the AUTH_STATE_CANCELED flag, NeedsAuth will return false,
|
| - // which will cause the consumer to receive OnResponseStarted instead of
|
| - // OnAuthRequired.
|
| - //
|
| - // We have to do this via InvokeLater to avoid "recursing" the consumer.
|
| - //
|
| - MessageLoop::current()->PostTask(
|
| - FROM_HERE,
|
| - method_factory_.NewRunnableMethod(
|
| - &URLRequestHttpJob::OnStartCompleted, OK));
|
| -}
|
| + // TODO(agl): change this over when we have fixed things at the server end.
|
| + // The string should be "Opportunistic-Transport-Security";
|
| + name = "X-Bodge-Transport-Security";
|
|
|
| -void URLRequestHttpJob::ContinueWithCertificate(
|
| - X509Certificate* client_cert) {
|
| - DCHECK(transaction_.get());
|
| + while (response_info_->headers->EnumerateHeader(&iter, name, &value)) {
|
| + const bool ok = TransportSecurityState::ParseHeader(
|
| + value, &max_age, &include_subdomains);
|
| + if (!ok)
|
| + continue;
|
| + // If we saw an opportunistic request over HTTPS, then clearly we can make
|
| + // HTTPS connections to the host so we should remember this.
|
| + if (https) {
|
| + base::Time current_time(base::Time::Now());
|
| + base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age);
|
|
|
| - DCHECK(!response_info_) << "should not have a response yet";
|
| + TransportSecurityState::DomainState domain_state;
|
| + domain_state.expiry = current_time + max_age_delta;
|
| + domain_state.mode =
|
| + TransportSecurityState::DomainState::MODE_SPDY_ONLY;
|
| + domain_state.include_subdomains = include_subdomains;
|
|
|
| - // No matter what, we want to report our status as IO pending since we will
|
| - // be notifying our consumer asynchronously via OnStartCompleted.
|
| - SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| + ctx->transport_security_state()->EnableHost(request_info_.url.host(),
|
| + domain_state);
|
| + continue;
|
| + }
|
|
|
| - int rv = transaction_->RestartWithCertificate(client_cert, &start_callback_);
|
| - if (rv == ERR_IO_PENDING)
|
| - return;
|
| + if (!request())
|
| + break;
|
|
|
| - // The transaction started synchronously, but we need to notify the
|
| - // URLRequest delegate via the message loop.
|
| - MessageLoop::current()->PostTask(
|
| - FROM_HERE,
|
| - method_factory_.NewRunnableMethod(
|
| - &URLRequestHttpJob::OnStartCompleted, rv));
|
| -}
|
| + // At this point, we have a request for opportunistic encryption over HTTP.
|
| + // In this case we need to probe to check that we can make HTTPS
|
| + // connections to that host.
|
| + HTTPSProber* const prober = HTTPSProber::GetInstance();
|
| + if (prober->HaveProbed(request_info_.url.host()) ||
|
| + prober->InFlight(request_info_.url.host())) {
|
| + continue;
|
| + }
|
|
|
| -void URLRequestHttpJob::ContinueDespiteLastError() {
|
| - // If the transaction was destroyed, then the job was cancelled.
|
| - if (!transaction_.get())
|
| - return;
|
| -
|
| - DCHECK(!response_info_) << "should not have a response yet";
|
| -
|
| - // No matter what, we want to report our status as IO pending since we will
|
| - // be notifying our consumer asynchronously via OnStartCompleted.
|
| - SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| -
|
| - int rv = transaction_->RestartIgnoringLastError(&start_callback_);
|
| - if (rv == ERR_IO_PENDING)
|
| - return;
|
| -
|
| - // The transaction started synchronously, but we need to notify the
|
| - // URLRequest delegate via the message loop.
|
| - MessageLoop::current()->PostTask(
|
| - FROM_HERE,
|
| - method_factory_.NewRunnableMethod(
|
| - &URLRequestHttpJob::OnStartCompleted, rv));
|
| -}
|
| -
|
| -bool URLRequestHttpJob::ReadRawData(IOBuffer* buf, int buf_size,
|
| - int *bytes_read) {
|
| - DCHECK_NE(buf_size, 0);
|
| - DCHECK(bytes_read);
|
| - DCHECK(!read_in_progress_);
|
| -
|
| - int rv = transaction_->Read(buf, buf_size, &read_callback_);
|
| - if (rv >= 0) {
|
| - *bytes_read = rv;
|
| - return true;
|
| - }
|
| -
|
| - if (rv == ERR_IO_PENDING) {
|
| - read_in_progress_ = true;
|
| - SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| - } else {
|
| - NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
|
| + HTTPSProberDelegateImpl* delegate =
|
| + new HTTPSProberDelegateImpl(request_info_.url.host(), max_age,
|
| + include_subdomains,
|
| + ctx->transport_security_state());
|
| + if (!prober->ProbeHost(request_info_.url.host(), request()->context(),
|
| + delegate)) {
|
| + delete delegate;
|
| + }
|
| }
|
| -
|
| - return false;
|
| -}
|
| -
|
| -void URLRequestHttpJob::StopCaching() {
|
| - if (transaction_.get())
|
| - transaction_->StopCaching();
|
| }
|
|
|
| void URLRequestHttpJob::OnCanGetCookiesCompleted(int policy) {
|
| @@ -620,349 +617,353 @@ bool URLRequestHttpJob::ShouldTreatAsCertificateError(int result) {
|
| TransportSecurityState::DomainState::MODE_OPPORTUNISTIC;
|
| }
|
|
|
| -void URLRequestHttpJob::NotifyHeadersComplete() {
|
| - DCHECK(!response_info_);
|
| +void URLRequestHttpJob::RestartTransactionWithAuth(
|
| + const string16& username,
|
| + const string16& password) {
|
| + username_ = username;
|
| + password_ = password;
|
|
|
| - response_info_ = transaction_->GetResponseInfo();
|
| + // These will be reset in OnStartCompleted.
|
| + response_info_ = NULL;
|
| + response_cookies_.clear();
|
|
|
| - // Save boolean, as we'll need this info at destruction time, and filters may
|
| - // also need this info.
|
| - is_cached_content_ = response_info_->was_cached;
|
| + // Update the cookies, since the cookie store may have been updated from the
|
| + // headers in the 401/407. Since cookies were already appended to
|
| + // extra_headers, we need to strip them out before adding them again.
|
| + request_info_.extra_headers.RemoveHeader(
|
| + HttpRequestHeaders::kCookie);
|
|
|
| - if (!is_cached_content_) {
|
| - URLRequestThrottlerHeaderAdapter response_adapter(
|
| - response_info_->headers);
|
| - throttling_entry_->UpdateWithResponse(&response_adapter);
|
| - }
|
| + AddCookieHeaderAndStart();
|
| +}
|
|
|
| - ProcessStrictTransportSecurityHeader();
|
| +void URLRequestHttpJob::SetUpload(UploadData* upload) {
|
| + DCHECK(!transaction_.get()) << "cannot change once started";
|
| + request_info_.upload_data = upload;
|
| +}
|
|
|
| - if (SdchManager::Global() &&
|
| - SdchManager::Global()->IsInSupportedDomain(request_->url())) {
|
| - static const std::string name = "Get-Dictionary";
|
| - std::string url_text;
|
| - void* iter = NULL;
|
| - // TODO(jar): We need to not fetch dictionaries the first time they are
|
| - // seen, but rather wait until we can justify their usefulness.
|
| - // For now, we will only fetch the first dictionary, which will at least
|
| - // require multiple suggestions before we get additional ones for this site.
|
| - // Eventually we should wait until a dictionary is requested several times
|
| - // before we even download it (so that we don't waste memory or bandwidth).
|
| - if (response_info_->headers->EnumerateHeader(&iter, name, &url_text)) {
|
| - // request_->url() won't be valid in the destructor, so we use an
|
| - // alternate copy.
|
| - DCHECK(request_->url() == request_info_.url);
|
| - // Resolve suggested URL relative to request url.
|
| - sdch_dictionary_url_ = request_info_.url.Resolve(url_text);
|
| - }
|
| +void URLRequestHttpJob::SetExtraRequestHeaders(
|
| + const HttpRequestHeaders& headers) {
|
| + DCHECK(!transaction_.get()) << "cannot change once started";
|
| + request_info_.extra_headers.CopyFrom(headers);
|
| +}
|
| +
|
| +void URLRequestHttpJob::Start() {
|
| + DCHECK(!transaction_.get());
|
| +
|
| + // Ensure that we do not send username and password fields in the referrer.
|
| + GURL referrer(request_->GetSanitizedReferrer());
|
| +
|
| + request_info_.url = request_->url();
|
| + request_info_.referrer = referrer;
|
| + request_info_.method = request_->method();
|
| + request_info_.load_flags = request_->load_flags();
|
| + request_info_.priority = request_->priority();
|
| +
|
| + if (request_->context()) {
|
| + request_info_.extra_headers.SetHeader(
|
| + HttpRequestHeaders::kUserAgent,
|
| + request_->context()->GetUserAgent(request_->url()));
|
| }
|
|
|
| - // The HTTP transaction may be restarted several times for the purposes
|
| - // of sending authorization information. Each time it restarts, we get
|
| - // notified of the headers completion so that we can update the cookie store.
|
| - if (transaction_->IsReadyToRestartForAuth()) {
|
| - DCHECK(!response_info_->auth_challenge.get());
|
| - RestartTransactionWithAuth(string16(), string16());
|
| + AddExtraHeaders();
|
| + AddCookieHeaderAndStart();
|
| +}
|
| +
|
| +void URLRequestHttpJob::Kill() {
|
| + if (!transaction_.get())
|
| return;
|
| - }
|
|
|
| - URLRequestJob::NotifyHeadersComplete();
|
| + DestroyTransaction();
|
| + URLRequestJob::Kill();
|
| }
|
|
|
| -void URLRequestHttpJob::DestroyTransaction() {
|
| +LoadState URLRequestHttpJob::GetLoadState() const {
|
| + return transaction_.get() ?
|
| + transaction_->GetLoadState() : LOAD_STATE_IDLE;
|
| +}
|
| +
|
| +uint64 URLRequestHttpJob::GetUploadProgress() const {
|
| + return transaction_.get() ? transaction_->GetUploadProgress() : 0;
|
| +}
|
| +
|
| +bool URLRequestHttpJob::GetMimeType(std::string* mime_type) const {
|
| DCHECK(transaction_.get());
|
|
|
| - transaction_.reset();
|
| - response_info_ = NULL;
|
| - context_ = NULL;
|
| + if (!response_info_)
|
| + return false;
|
| +
|
| + return response_info_->headers->GetMimeType(mime_type);
|
| }
|
|
|
| -void URLRequestHttpJob::StartTransaction() {
|
| - // NOTE: This method assumes that request_info_ is already setup properly.
|
| +bool URLRequestHttpJob::GetCharset(std::string* charset) {
|
| + DCHECK(transaction_.get());
|
|
|
| - // If we already have a transaction, then we should restart the transaction
|
| - // with auth provided by username_ and password_.
|
| + if (!response_info_)
|
| + return false;
|
|
|
| - int rv;
|
| + return response_info_->headers->GetCharset(charset);
|
| +}
|
|
|
| - if (transaction_.get()) {
|
| - rv = transaction_->RestartWithAuth(username_, password_, &start_callback_);
|
| - username_.clear();
|
| - password_.clear();
|
| - } else {
|
| - DCHECK(request_->context());
|
| - DCHECK(request_->context()->http_transaction_factory());
|
| +void URLRequestHttpJob::GetResponseInfo(HttpResponseInfo* info) {
|
| + DCHECK(request_);
|
| + DCHECK(transaction_.get());
|
|
|
| - rv = request_->context()->http_transaction_factory()->CreateTransaction(
|
| - &transaction_);
|
| - if (rv == OK) {
|
| - if (!throttling_entry_->IsDuringExponentialBackoff() ||
|
| - !net::URLRequestThrottlerManager::GetInstance()->
|
| - enforce_throttling()) {
|
| - rv = transaction_->Start(
|
| - &request_info_, &start_callback_, request_->net_log());
|
| - } else {
|
| - // Special error code for the exponential back-off module.
|
| - rv = ERR_TEMPORARILY_THROTTLED;
|
| - }
|
| - // Make sure the context is alive for the duration of the
|
| - // transaction.
|
| - context_ = request_->context();
|
| - }
|
| - }
|
| + if (response_info_)
|
| + *info = *response_info_;
|
| +}
|
|
|
| - if (rv == ERR_IO_PENDING)
|
| - return;
|
| +bool URLRequestHttpJob::GetResponseCookies(
|
| + std::vector<std::string>* cookies) {
|
| + DCHECK(transaction_.get());
|
|
|
| - // The transaction started synchronously, but we need to notify the
|
| - // URLRequest delegate via the message loop.
|
| - MessageLoop::current()->PostTask(
|
| - FROM_HERE,
|
| - method_factory_.NewRunnableMethod(
|
| - &URLRequestHttpJob::OnStartCompleted, rv));
|
| + if (!response_info_)
|
| + return false;
|
| +
|
| + // TODO(darin): Why are we extracting response cookies again? Perhaps we
|
| + // should just leverage response_cookies_.
|
| +
|
| + cookies->clear();
|
| + FetchResponseCookies(response_info_, cookies);
|
| + return true;
|
| }
|
|
|
| -void URLRequestHttpJob::AddExtraHeaders() {
|
| - // TODO(jar): Consider optimizing away SDCH advertising bytes when the URL is
|
| - // probably an img or such (and SDCH encoding is not likely).
|
| - bool advertise_sdch = SdchManager::Global() &&
|
| - SdchManager::Global()->IsInSupportedDomain(request_->url());
|
| - std::string avail_dictionaries;
|
| - if (advertise_sdch) {
|
| - SdchManager::Global()->GetAvailDictionaryList(request_->url(),
|
| - &avail_dictionaries);
|
| +int URLRequestHttpJob::GetResponseCode() const {
|
| + DCHECK(transaction_.get());
|
|
|
| - // The AllowLatencyExperiment() is only true if we've successfully done a
|
| - // full SDCH compression recently in this browser session for this host.
|
| - // Note that for this path, there might be no applicable dictionaries, and
|
| - // hence we can't participate in the experiment.
|
| - if (!avail_dictionaries.empty() &&
|
| - SdchManager::Global()->AllowLatencyExperiment(request_->url())) {
|
| - // We are participating in the test (or control), and hence we'll
|
| - // eventually record statistics via either SDCH_EXPERIMENT_DECODE or
|
| - // SDCH_EXPERIMENT_HOLDBACK, and we'll need some packet timing data.
|
| - EnablePacketCounting(kSdchPacketHistogramCount);
|
| - if (base::RandDouble() < .01) {
|
| - sdch_test_control_ = true; // 1% probability.
|
| - advertise_sdch = false;
|
| - } else {
|
| - sdch_test_activated_ = true;
|
| - }
|
| - }
|
| + if (!response_info_)
|
| + return -1;
|
| +
|
| + return response_info_->headers->response_code();
|
| +}
|
| +
|
| +bool URLRequestHttpJob::GetContentEncodings(
|
| + std::vector<Filter::FilterType>* encoding_types) {
|
| + DCHECK(transaction_.get());
|
| + if (!response_info_)
|
| + return false;
|
| + DCHECK(encoding_types->empty());
|
| +
|
| + std::string encoding_type;
|
| + void* iter = NULL;
|
| + while (response_info_->headers->EnumerateHeader(&iter, "Content-Encoding",
|
| + &encoding_type)) {
|
| + encoding_types->push_back(Filter::ConvertEncodingToType(encoding_type));
|
| }
|
|
|
| - // Supply Accept-Encoding headers first so that it is more likely that they
|
| - // will be in the first transmitted packet. This can sometimes make it easier
|
| - // to filter and analyze the streams to assure that a proxy has not damaged
|
| - // these headers. Some proxies deliberately corrupt Accept-Encoding headers.
|
| - if (!advertise_sdch) {
|
| - // Tell the server what compression formats we support (other than SDCH).
|
| - request_info_.extra_headers.SetHeader(
|
| - HttpRequestHeaders::kAcceptEncoding, "gzip,deflate");
|
| - } else {
|
| - // Include SDCH in acceptable list.
|
| - request_info_.extra_headers.SetHeader(
|
| - HttpRequestHeaders::kAcceptEncoding, "gzip,deflate,sdch");
|
| - if (!avail_dictionaries.empty()) {
|
| - request_info_.extra_headers.SetHeader(
|
| - kAvailDictionaryHeader,
|
| - avail_dictionaries);
|
| - sdch_dictionary_advertised_ = true;
|
| - // Since we're tagging this transaction as advertising a dictionary, we'll
|
| - // definately employ an SDCH filter (or tentative sdch filter) when we get
|
| - // a response. When done, we'll record histograms via SDCH_DECODE or
|
| - // SDCH_PASSTHROUGH. Hence we need to record packet arrival times.
|
| - EnablePacketCounting(kSdchPacketHistogramCount);
|
| - }
|
| + // Even if encoding types are empty, there is a chance that we need to add
|
| + // some decoding, as some proxies strip encoding completely. In such cases,
|
| + // we may need to add (for example) SDCH filtering (when the context suggests
|
| + // it is appropriate).
|
| + Filter::FixupEncodingTypes(*this, encoding_types);
|
| +
|
| + return !encoding_types->empty();
|
| +}
|
| +
|
| +bool URLRequestHttpJob::IsCachedContent() const {
|
| + return is_cached_content_;
|
| +}
|
| +
|
| +bool URLRequestHttpJob::IsSdchResponse() const {
|
| + return sdch_dictionary_advertised_;
|
| +}
|
| +
|
| +bool URLRequestHttpJob::IsSafeRedirect(const GURL& location) {
|
| + // We only allow redirects to certain "safe" protocols. This does not
|
| + // restrict redirects to externally handled protocols. Our consumer would
|
| + // need to take care of those.
|
| +
|
| + if (!URLRequest::IsHandledURL(location))
|
| + return true;
|
| +
|
| + static const char* kSafeSchemes[] = {
|
| + "http",
|
| + "https",
|
| + "ftp"
|
| + };
|
| +
|
| + for (size_t i = 0; i < arraysize(kSafeSchemes); ++i) {
|
| + if (location.SchemeIs(kSafeSchemes[i]))
|
| + return true;
|
| }
|
|
|
| - URLRequestContext* context = request_->context();
|
| - if (context) {
|
| - // Only add default Accept-Language and Accept-Charset if the request
|
| - // didn't have them specified.
|
| - if (!request_info_.extra_headers.HasHeader(
|
| - HttpRequestHeaders::kAcceptLanguage)) {
|
| - request_info_.extra_headers.SetHeader(
|
| - HttpRequestHeaders::kAcceptLanguage,
|
| - context->accept_language());
|
| - }
|
| - if (!request_info_.extra_headers.HasHeader(
|
| - HttpRequestHeaders::kAcceptCharset)) {
|
| - request_info_.extra_headers.SetHeader(
|
| - HttpRequestHeaders::kAcceptCharset,
|
| - context->accept_charset());
|
| - }
|
| + return false;
|
| +}
|
| +
|
| +bool URLRequestHttpJob::NeedsAuth() {
|
| + int code = GetResponseCode();
|
| + if (code == -1)
|
| + return false;
|
| +
|
| + // Check if we need either Proxy or WWW Authentication. This could happen
|
| + // because we either provided no auth info, or provided incorrect info.
|
| + switch (code) {
|
| + case 407:
|
| + if (proxy_auth_state_ == AUTH_STATE_CANCELED)
|
| + return false;
|
| + proxy_auth_state_ = AUTH_STATE_NEED_AUTH;
|
| + return true;
|
| + case 401:
|
| + if (server_auth_state_ == AUTH_STATE_CANCELED)
|
| + return false;
|
| + server_auth_state_ = AUTH_STATE_NEED_AUTH;
|
| + return true;
|
| }
|
| + return false;
|
| }
|
|
|
| -void URLRequestHttpJob::AddCookieHeaderAndStart() {
|
| - // No matter what, we want to report our status as IO pending since we will
|
| - // be notifying our consumer asynchronously via OnStartCompleted.
|
| - SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| +void URLRequestHttpJob::GetAuthChallengeInfo(
|
| + scoped_refptr<AuthChallengeInfo>* result) {
|
| + DCHECK(transaction_.get());
|
| + DCHECK(response_info_);
|
|
|
| - AddRef(); // Balanced in OnCanGetCookiesCompleted
|
| + // sanity checks:
|
| + DCHECK(proxy_auth_state_ == AUTH_STATE_NEED_AUTH ||
|
| + server_auth_state_ == AUTH_STATE_NEED_AUTH);
|
| + DCHECK(response_info_->headers->response_code() == 401 ||
|
| + response_info_->headers->response_code() == 407);
|
|
|
| - int policy = OK;
|
| + *result = response_info_->auth_challenge;
|
| +}
|
|
|
| - if (request_info_.load_flags & LOAD_DO_NOT_SEND_COOKIES) {
|
| - policy = ERR_FAILED;
|
| - } else if (request_->context()->cookie_policy()) {
|
| - policy = request_->context()->cookie_policy()->CanGetCookies(
|
| - request_->url(),
|
| - request_->first_party_for_cookies(),
|
| - &can_get_cookies_callback_);
|
| - if (policy == ERR_IO_PENDING)
|
| - return; // Wait for completion callback
|
| +void URLRequestHttpJob::SetAuth(const string16& username,
|
| + const string16& password) {
|
| + DCHECK(transaction_.get());
|
| +
|
| + // Proxy gets set first, then WWW.
|
| + if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
|
| + proxy_auth_state_ = AUTH_STATE_HAVE_AUTH;
|
| + } else {
|
| + DCHECK(server_auth_state_ == AUTH_STATE_NEED_AUTH);
|
| + server_auth_state_ = AUTH_STATE_HAVE_AUTH;
|
| }
|
|
|
| - OnCanGetCookiesCompleted(policy);
|
| + RestartTransactionWithAuth(username, password);
|
| }
|
|
|
| -void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete() {
|
| - DCHECK(transaction_.get());
|
| -
|
| - const HttpResponseInfo* response_info = transaction_->GetResponseInfo();
|
| - DCHECK(response_info);
|
| +void URLRequestHttpJob::CancelAuth() {
|
| + // Proxy gets set first, then WWW.
|
| + if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
|
| + proxy_auth_state_ = AUTH_STATE_CANCELED;
|
| + } else {
|
| + DCHECK(server_auth_state_ == AUTH_STATE_NEED_AUTH);
|
| + server_auth_state_ = AUTH_STATE_CANCELED;
|
| + }
|
|
|
| + // These will be reset in OnStartCompleted.
|
| + response_info_ = NULL;
|
| response_cookies_.clear();
|
| - response_cookies_save_index_ = 0;
|
| -
|
| - FetchResponseCookies(response_info, &response_cookies_);
|
|
|
| - // Now, loop over the response cookies, and attempt to persist each.
|
| - SaveNextCookie();
|
| + // OK, let the consumer read the error page...
|
| + //
|
| + // Because we set the AUTH_STATE_CANCELED flag, NeedsAuth will return false,
|
| + // which will cause the consumer to receive OnResponseStarted instead of
|
| + // OnAuthRequired.
|
| + //
|
| + // We have to do this via InvokeLater to avoid "recursing" the consumer.
|
| + //
|
| + MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + method_factory_.NewRunnableMethod(
|
| + &URLRequestHttpJob::OnStartCompleted, OK));
|
| }
|
|
|
| -void URLRequestHttpJob::SaveNextCookie() {
|
| - if (response_cookies_save_index_ == response_cookies_.size()) {
|
| - response_cookies_.clear();
|
| - response_cookies_save_index_ = 0;
|
| - SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
|
| - NotifyHeadersComplete();
|
| - return;
|
| - }
|
| +void URLRequestHttpJob::ContinueWithCertificate(
|
| + X509Certificate* client_cert) {
|
| + DCHECK(transaction_.get());
|
| +
|
| + DCHECK(!response_info_) << "should not have a response yet";
|
|
|
| // No matter what, we want to report our status as IO pending since we will
|
| // be notifying our consumer asynchronously via OnStartCompleted.
|
| SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
|
|
| - AddRef(); // Balanced in OnCanSetCookieCompleted
|
| -
|
| - int policy = OK;
|
| -
|
| - if (request_info_.load_flags & LOAD_DO_NOT_SAVE_COOKIES) {
|
| - policy = ERR_FAILED;
|
| - } else if (request_->context()->cookie_policy()) {
|
| - policy = request_->context()->cookie_policy()->CanSetCookie(
|
| - request_->url(),
|
| - request_->first_party_for_cookies(),
|
| - response_cookies_[response_cookies_save_index_],
|
| - &can_set_cookie_callback_);
|
| - if (policy == ERR_IO_PENDING)
|
| - return; // Wait for completion callback
|
| - }
|
| -
|
| - OnCanSetCookieCompleted(policy);
|
| -}
|
| -
|
| -void URLRequestHttpJob::FetchResponseCookies(
|
| - const HttpResponseInfo* response_info,
|
| - std::vector<std::string>* cookies) {
|
| - std::string name = "Set-Cookie";
|
| - std::string value;
|
| + int rv = transaction_->RestartWithCertificate(client_cert, &start_callback_);
|
| + if (rv == ERR_IO_PENDING)
|
| + return;
|
|
|
| - void* iter = NULL;
|
| - while (response_info->headers->EnumerateHeader(&iter, name, &value)) {
|
| - if (!value.empty())
|
| - cookies->push_back(value);
|
| - }
|
| + // The transaction started synchronously, but we need to notify the
|
| + // URLRequest delegate via the message loop.
|
| + MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + method_factory_.NewRunnableMethod(
|
| + &URLRequestHttpJob::OnStartCompleted, rv));
|
| }
|
|
|
| -void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() {
|
| - DCHECK(response_info_);
|
| -
|
| - URLRequestContext* ctx = request_->context();
|
| - if (!ctx || !ctx->transport_security_state())
|
| +void URLRequestHttpJob::ContinueDespiteLastError() {
|
| + // If the transaction was destroyed, then the job was cancelled.
|
| + if (!transaction_.get())
|
| return;
|
|
|
| - const bool https = response_info_->ssl_info.is_valid();
|
| - const bool valid_https =
|
| - https && !IsCertStatusError(response_info_->ssl_info.cert_status);
|
| + DCHECK(!response_info_) << "should not have a response yet";
|
|
|
| - std::string name = "Strict-Transport-Security";
|
| - std::string value;
|
| + // No matter what, we want to report our status as IO pending since we will
|
| + // be notifying our consumer asynchronously via OnStartCompleted.
|
| + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
|
|
| - int max_age;
|
| - bool include_subdomains;
|
| + int rv = transaction_->RestartIgnoringLastError(&start_callback_);
|
| + if (rv == ERR_IO_PENDING)
|
| + return;
|
|
|
| - void* iter = NULL;
|
| - while (response_info_->headers->EnumerateHeader(&iter, name, &value)) {
|
| - const bool ok = TransportSecurityState::ParseHeader(
|
| - value, &max_age, &include_subdomains);
|
| - if (!ok)
|
| - continue;
|
| - // We will only accept strict mode if we saw the header from an HTTPS
|
| - // connection with no certificate problems.
|
| - if (!valid_https)
|
| - continue;
|
| - base::Time current_time(base::Time::Now());
|
| - base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age);
|
| + // The transaction started synchronously, but we need to notify the
|
| + // URLRequest delegate via the message loop.
|
| + MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + method_factory_.NewRunnableMethod(
|
| + &URLRequestHttpJob::OnStartCompleted, rv));
|
| +}
|
|
|
| - TransportSecurityState::DomainState domain_state;
|
| - domain_state.expiry = current_time + max_age_delta;
|
| - domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT;
|
| - domain_state.include_subdomains = include_subdomains;
|
| +bool URLRequestHttpJob::ReadRawData(IOBuffer* buf, int buf_size,
|
| + int *bytes_read) {
|
| + DCHECK_NE(buf_size, 0);
|
| + DCHECK(bytes_read);
|
| + DCHECK(!read_in_progress_);
|
|
|
| - ctx->transport_security_state()->EnableHost(request_info_.url.host(),
|
| - domain_state);
|
| + int rv = transaction_->Read(buf, buf_size, &read_callback_);
|
| + if (rv >= 0) {
|
| + *bytes_read = rv;
|
| + return true;
|
| }
|
|
|
| - // TODO(agl): change this over when we have fixed things at the server end.
|
| - // The string should be "Opportunistic-Transport-Security";
|
| - name = "X-Bodge-Transport-Security";
|
| -
|
| - while (response_info_->headers->EnumerateHeader(&iter, name, &value)) {
|
| - const bool ok = TransportSecurityState::ParseHeader(
|
| - value, &max_age, &include_subdomains);
|
| - if (!ok)
|
| - continue;
|
| - // If we saw an opportunistic request over HTTPS, then clearly we can make
|
| - // HTTPS connections to the host so we should remember this.
|
| - if (https) {
|
| - base::Time current_time(base::Time::Now());
|
| - base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age);
|
| -
|
| - TransportSecurityState::DomainState domain_state;
|
| - domain_state.expiry = current_time + max_age_delta;
|
| - domain_state.mode =
|
| - TransportSecurityState::DomainState::MODE_SPDY_ONLY;
|
| - domain_state.include_subdomains = include_subdomains;
|
| + if (rv == ERR_IO_PENDING) {
|
| + read_in_progress_ = true;
|
| + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
|
| + } else {
|
| + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
|
| + }
|
|
|
| - ctx->transport_security_state()->EnableHost(request_info_.url.host(),
|
| - domain_state);
|
| - continue;
|
| - }
|
| + return false;
|
| +}
|
|
|
| - if (!request())
|
| - break;
|
| +void URLRequestHttpJob::StopCaching() {
|
| + if (transaction_.get())
|
| + transaction_->StopCaching();
|
| +}
|
|
|
| - // At this point, we have a request for opportunistic encryption over HTTP.
|
| - // In this case we need to probe to check that we can make HTTPS
|
| - // connections to that host.
|
| - HTTPSProber* const prober = HTTPSProber::GetInstance();
|
| - if (prober->HaveProbed(request_info_.url.host()) ||
|
| - prober->InFlight(request_info_.url.host())) {
|
| - continue;
|
| - }
|
| +URLRequestHttpJob::~URLRequestHttpJob() {
|
| + DCHECK(!sdch_test_control_ || !sdch_test_activated_);
|
| + if (!IsCachedContent()) {
|
| + if (sdch_test_control_)
|
| + RecordPacketStats(SDCH_EXPERIMENT_HOLDBACK);
|
| + if (sdch_test_activated_)
|
| + RecordPacketStats(SDCH_EXPERIMENT_DECODE);
|
| + }
|
| + // Make sure SDCH filters are told to emit histogram data while this class
|
| + // can still service the IsCachedContent() call.
|
| + DestroyFilters();
|
|
|
| - HTTPSProberDelegateImpl* delegate =
|
| - new HTTPSProberDelegateImpl(request_info_.url.host(), max_age,
|
| - include_subdomains,
|
| - ctx->transport_security_state());
|
| - if (!prober->ProbeHost(request_info_.url.host(), request()->context(),
|
| - delegate)) {
|
| - delete delegate;
|
| - }
|
| + if (sdch_dictionary_url_.is_valid()) {
|
| + // Prior to reaching the destructor, request_ has been set to a NULL
|
| + // pointer, so request_->url() is no longer valid in the destructor, and we
|
| + // use an alternate copy |request_info_.url|.
|
| + SdchManager* manager = SdchManager::Global();
|
| + // To be extra safe, since this is a "different time" from when we decided
|
| + // to get the dictionary, we'll validate that an SdchManager is available.
|
| + // At shutdown time, care is taken to be sure that we don't delete this
|
| + // globally useful instance "too soon," so this check is just defensive
|
| + // coding to assure that IF the system is shutting down, we don't have any
|
| + // problem if the manager was deleted ahead of time.
|
| + if (manager) // Defensive programming.
|
| + manager->FetchDictionary(request_info_.url, sdch_dictionary_url_);
|
| }
|
| }
|
|
|
|
|