| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/url_request/url_request_ftp_job.h" |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "net/base/auth.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/ftp/ftp_auth_cache.h" |
| #include "net/ftp/ftp_network_transaction.h" |
| #include "net/ftp/ftp_response_info.h" |
| #include "net/ftp/ftp_transaction_factory.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_transaction_factory.h" |
| #include "net/proxy_resolution/proxy_resolution_request.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_error_job.h" |
| |
| namespace net { |
| |
| class URLRequestFtpJob::AuthData { |
| public: |
| AuthState state; // Whether we need, have, or gave up on authentication. |
| AuthCredentials credentials; // The credentials to use for auth. |
| |
| AuthData(); |
| ~AuthData(); |
| }; |
| |
| URLRequestFtpJob::AuthData::AuthData() : state(AUTH_STATE_NEED_AUTH) {} |
| |
| URLRequestFtpJob::AuthData::~AuthData() = default; |
| |
| URLRequestFtpJob::URLRequestFtpJob( |
| URLRequest* request, |
| NetworkDelegate* network_delegate, |
| FtpTransactionFactory* ftp_transaction_factory, |
| FtpAuthCache* ftp_auth_cache) |
| : URLRequestJob(request, network_delegate), |
| proxy_resolution_service_( |
| request_->context()->proxy_resolution_service()), |
| read_in_progress_(false), |
| ftp_transaction_factory_(ftp_transaction_factory), |
| ftp_auth_cache_(ftp_auth_cache) { |
| DCHECK(proxy_resolution_service_); |
| DCHECK(ftp_transaction_factory); |
| DCHECK(ftp_auth_cache); |
| } |
| |
| URLRequestFtpJob::~URLRequestFtpJob() { |
| Kill(); |
| } |
| |
| bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) { |
| // Disallow all redirects. |
| return false; |
| } |
| |
| bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { |
| // When auth has been cancelled, return a blank text/plain page instead of |
| // triggering a download. |
| if (auth_data_ && auth_data_->state == AUTH_STATE_CANCELED) { |
| *mime_type = "text/plain"; |
| return true; |
| } |
| |
| if (ftp_transaction_->GetResponseInfo()->is_directory_listing) { |
| *mime_type = "text/vnd.chromium.ftp-dir"; |
| return true; |
| } |
| |
| // FTP resources other than directory listings ought to be handled as raw |
| // binary data, not sniffed into HTML or etc. |
| *mime_type = "application/octet-stream"; |
| return true; |
| } |
| |
| IPEndPoint URLRequestFtpJob::GetResponseRemoteEndpoint() const { |
| if (!ftp_transaction_) |
| return IPEndPoint(); |
| return ftp_transaction_->GetResponseInfo()->remote_endpoint; |
| } |
| |
| void URLRequestFtpJob::Start() { |
| DCHECK(!proxy_resolve_request_); |
| DCHECK(!ftp_transaction_); |
| |
| int rv = OK; |
| if (request_->load_flags() & LOAD_BYPASS_PROXY) { |
| proxy_info_.UseDirect(); |
| } else { |
| DCHECK_EQ(request_->context()->proxy_resolution_service(), |
| proxy_resolution_service_); |
| // "Fine" to use an empty NetworkIsolationKey() because FTP is slated for |
| // removal. |
| rv = proxy_resolution_service_->ResolveProxy( |
| request_->url(), "GET", NetworkIsolationKey(), &proxy_info_, |
| base::BindOnce(&URLRequestFtpJob::OnResolveProxyComplete, |
| base::Unretained(this)), |
| &proxy_resolve_request_, request_->net_log()); |
| |
| if (rv == ERR_IO_PENDING) |
| return; |
| } |
| OnResolveProxyComplete(rv); |
| } |
| |
| void URLRequestFtpJob::Kill() { |
| if (proxy_resolve_request_) { |
| proxy_resolve_request_.reset(); |
| } |
| if (ftp_transaction_) |
| ftp_transaction_.reset(); |
| URLRequestJob::Kill(); |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) { |
| // Don't expose the challenge if it has already been successfully |
| // authenticated. |
| if (!auth_data_ || auth_data_->state == AUTH_STATE_HAVE_AUTH) |
| return; |
| |
| std::unique_ptr<AuthChallengeInfo> challenge = GetAuthChallengeInfo(); |
| if (challenge) |
| info->auth_challenge = *challenge; |
| } |
| |
| void URLRequestFtpJob::OnResolveProxyComplete(int result) { |
| proxy_resolve_request_ = nullptr; |
| |
| if (result != OK) { |
| OnStartCompletedAsync(result); |
| return; |
| } |
| |
| // Remove unsupported proxies from the list. |
| proxy_info_.RemoveProxiesWithoutScheme(ProxyServer::SCHEME_DIRECT); |
| |
| if (proxy_info_.is_direct()) { |
| StartFtpTransaction(); |
| } else { |
| OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES); |
| } |
| } |
| |
| void URLRequestFtpJob::StartFtpTransaction() { |
| // Create a transaction. |
| DCHECK(!ftp_transaction_); |
| |
| ftp_request_info_.url = request_->url(); |
| ftp_transaction_ = ftp_transaction_factory_->CreateTransaction(); |
| |
| int rv; |
| if (ftp_transaction_) { |
| rv = ftp_transaction_->Start( |
| &ftp_request_info_, |
| base::BindOnce(&URLRequestFtpJob::OnStartCompleted, |
| base::Unretained(this)), |
| request_->net_log(), request_->traffic_annotation()); |
| if (rv == ERR_IO_PENDING) |
| return; |
| } else { |
| rv = ERR_FAILED; |
| } |
| // The transaction started synchronously, but we need to notify the |
| // URLRequest delegate via the message loop. |
| OnStartCompletedAsync(rv); |
| } |
| |
| void URLRequestFtpJob::OnStartCompleted(int result) { |
| if (result == OK) { |
| DCHECK(ftp_transaction_); |
| |
| // FTP obviously doesn't have HTTP Content-Length header. We have to pass |
| // the content size information manually. |
| set_expected_content_size( |
| ftp_transaction_->GetResponseInfo()->expected_content_size); |
| |
| if (auth_data_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) { |
| LogFtpStartResult(FTPStartResult::kSuccessAuth); |
| } else { |
| LogFtpStartResult(FTPStartResult::kSuccessNoAuth); |
| } |
| |
| NotifyHeadersComplete(); |
| } else if (ftp_transaction_ /* May be null if creation fails. */ && |
| ftp_transaction_->GetResponseInfo()->needs_auth) { |
| HandleAuthNeededResponse(); |
| } else { |
| LogFtpStartResult(FTPStartResult::kFailed); |
| NotifyStartError(result); |
| } |
| } |
| |
| void URLRequestFtpJob::OnStartCompletedAsync(int result) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&URLRequestFtpJob::OnStartCompleted, |
| weak_factory_.GetWeakPtr(), result)); |
| } |
| |
| void URLRequestFtpJob::OnReadCompleted(int result) { |
| read_in_progress_ = false; |
| ReadRawDataComplete(result); |
| } |
| |
| void URLRequestFtpJob::RestartTransactionWithAuth() { |
| DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH); |
| |
| int rv = ftp_transaction_->RestartWithAuth( |
| auth_data_->credentials, |
| base::BindOnce(&URLRequestFtpJob::OnStartCompleted, |
| base::Unretained(this))); |
| if (rv == ERR_IO_PENDING) |
| return; |
| |
| OnStartCompletedAsync(rv); |
| } |
| |
| LoadState URLRequestFtpJob::GetLoadState() const { |
| if (proxy_resolve_request_) |
| return proxy_resolve_request_->GetLoadState(); |
| return ftp_transaction_ ? ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE; |
| } |
| |
| bool URLRequestFtpJob::NeedsAuth() { |
| return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH; |
| } |
| |
| std::unique_ptr<AuthChallengeInfo> URLRequestFtpJob::GetAuthChallengeInfo() { |
| std::unique_ptr<AuthChallengeInfo> result = |
| std::make_unique<AuthChallengeInfo>(); |
| result->is_proxy = false; |
| result->challenger = url::Origin::Create(request_->url()); |
| // scheme, realm, path, and challenge are kept empty. |
| DCHECK(result->scheme.empty()); |
| DCHECK(result->realm.empty()); |
| DCHECK(result->challenge.empty()); |
| DCHECK(result->path.empty()); |
| return result; |
| } |
| |
| void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) { |
| DCHECK(ftp_transaction_); |
| DCHECK(NeedsAuth()); |
| |
| auth_data_->state = AUTH_STATE_HAVE_AUTH; |
| auth_data_->credentials = credentials; |
| |
| ftp_auth_cache_->Add(request_->url().GetOrigin(), auth_data_->credentials); |
| |
| RestartTransactionWithAuth(); |
| } |
| |
| void URLRequestFtpJob::CancelAuth() { |
| DCHECK(ftp_transaction_); |
| DCHECK(NeedsAuth()); |
| |
| auth_data_->state = AUTH_STATE_CANCELED; |
| |
| ftp_transaction_.reset(); |
| NotifyHeadersComplete(); |
| } |
| |
| int URLRequestFtpJob::ReadRawData(IOBuffer* buf, int buf_size) { |
| DCHECK_NE(buf_size, 0); |
| DCHECK(!read_in_progress_); |
| |
| if (!ftp_transaction_) |
| return 0; |
| |
| int rv = |
| ftp_transaction_->Read(buf, buf_size, |
| base::BindOnce(&URLRequestFtpJob::OnReadCompleted, |
| base::Unretained(this))); |
| |
| if (rv == ERR_IO_PENDING) |
| read_in_progress_ = true; |
| return rv; |
| } |
| |
| void URLRequestFtpJob::HandleAuthNeededResponse() { |
| GURL origin = request_->url().GetOrigin(); |
| |
| if (auth_data_.get()) { |
| if (auth_data_->state == AUTH_STATE_CANCELED) { |
| NotifyHeadersComplete(); |
| return; |
| } |
| |
| if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) { |
| ftp_auth_cache_->Remove(origin, auth_data_->credentials); |
| |
| // The user entered invalid auth |
| LogFtpStartResult(FTPStartResult::kFailed); |
| } |
| } else { |
| auth_data_ = std::make_unique<AuthData>(); |
| } |
| auth_data_->state = AUTH_STATE_NEED_AUTH; |
| |
| FtpAuthCache::Entry* cached_auth = nullptr; |
| if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth) |
| cached_auth = ftp_auth_cache_->Lookup(origin); |
| if (cached_auth) { |
| // Retry using cached auth data. |
| SetAuth(cached_auth->credentials); |
| } else { |
| // Prompt for a username/password. |
| NotifyHeadersComplete(); |
| } |
| } |
| |
| void URLRequestFtpJob::LogFtpStartResult(FTPStartResult result) { |
| UMA_HISTOGRAM_ENUMERATION("Net.FTP.StartResult", result); |
| } |
| |
| } // namespace net |