1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/url_request/url_request_ftp_job.h" 6 7#include "base/compiler_specific.h" 8#include "base/message_loop.h" 9#include "base/utf_string_conversions.h" 10#include "net/base/auth.h" 11#include "net/base/host_port_pair.h" 12#include "net/base/net_errors.h" 13#include "net/base/net_util.h" 14#include "net/ftp/ftp_response_info.h" 15#include "net/ftp/ftp_transaction_factory.h" 16#include "net/url_request/url_request.h" 17#include "net/url_request/url_request_context.h" 18#include "net/url_request/url_request_error_job.h" 19 20namespace net { 21 22URLRequestFtpJob::URLRequestFtpJob(URLRequest* request) 23 : URLRequestJob(request), 24 ALLOW_THIS_IN_INITIALIZER_LIST( 25 start_callback_(this, &URLRequestFtpJob::OnStartCompleted)), 26 ALLOW_THIS_IN_INITIALIZER_LIST( 27 read_callback_(this, &URLRequestFtpJob::OnReadCompleted)), 28 read_in_progress_(false), 29 context_(request->context()), 30 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { 31} 32 33// static 34URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request, 35 const std::string& scheme) { 36 DCHECK_EQ(scheme, "ftp"); 37 38 int port = request->url().IntPort(); 39 if (request->url().has_port() && 40 !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port)) 41 return new URLRequestErrorJob(request, ERR_UNSAFE_PORT); 42 43 DCHECK(request->context()); 44 DCHECK(request->context()->ftp_transaction_factory()); 45 return new URLRequestFtpJob(request); 46} 47 48bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { 49 if (transaction_->GetResponseInfo()->is_directory_listing) { 50 *mime_type = "text/vnd.chromium.ftp-dir"; 51 return true; 52 } 53 return false; 54} 55 56HostPortPair URLRequestFtpJob::GetSocketAddress() const { 57 if (!transaction_.get()) { 58 return HostPortPair(); 59 } 60 return transaction_->GetResponseInfo()->socket_address; 61} 62 63URLRequestFtpJob::~URLRequestFtpJob() { 64} 65 66void URLRequestFtpJob::StartTransaction() { 67 // Create a transaction. 68 DCHECK(!transaction_.get()); 69 DCHECK(request_->context()); 70 DCHECK(request_->context()->ftp_transaction_factory()); 71 72 transaction_.reset( 73 request_->context()->ftp_transaction_factory()->CreateTransaction()); 74 75 // No matter what, we want to report our status as IO pending since we will 76 // be notifying our consumer asynchronously via OnStartCompleted. 77 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 78 int rv; 79 if (transaction_.get()) { 80 rv = transaction_->Start( 81 &request_info_, &start_callback_, request_->net_log()); 82 if (rv == ERR_IO_PENDING) 83 return; 84 } else { 85 rv = ERR_FAILED; 86 } 87 // The transaction started synchronously, but we need to notify the 88 // URLRequest delegate via the message loop. 89 MessageLoop::current()->PostTask( 90 FROM_HERE, 91 method_factory_.NewRunnableMethod( 92 &URLRequestFtpJob::OnStartCompleted, rv)); 93} 94 95void URLRequestFtpJob::OnStartCompleted(int result) { 96 // Clear the IO_PENDING status 97 SetStatus(URLRequestStatus()); 98 99 // FTP obviously doesn't have HTTP Content-Length header. We have to pass 100 // the content size information manually. 101 set_expected_content_size( 102 transaction_->GetResponseInfo()->expected_content_size); 103 104 if (result == OK) { 105 NotifyHeadersComplete(); 106 } else if (transaction_->GetResponseInfo()->needs_auth) { 107 GURL origin = request_->url().GetOrigin(); 108 if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) { 109 request_->context()->ftp_auth_cache()->Remove(origin, 110 server_auth_->username, 111 server_auth_->password); 112 } else if (!server_auth_) { 113 server_auth_ = new AuthData(); 114 } 115 server_auth_->state = AUTH_STATE_NEED_AUTH; 116 117 FtpAuthCache::Entry* cached_auth = 118 request_->context()->ftp_auth_cache()->Lookup(origin); 119 120 if (cached_auth) { 121 // Retry using cached auth data. 122 SetAuth(cached_auth->username, cached_auth->password); 123 } else { 124 // Prompt for a username/password. 125 NotifyHeadersComplete(); 126 } 127 } else { 128 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 129 } 130} 131 132void URLRequestFtpJob::OnReadCompleted(int result) { 133 read_in_progress_ = false; 134 if (result == 0) { 135 NotifyDone(URLRequestStatus()); 136 } else if (result < 0) { 137 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 138 } else { 139 // Clear the IO_PENDING status 140 SetStatus(URLRequestStatus()); 141 } 142 NotifyReadComplete(result); 143} 144 145void URLRequestFtpJob::RestartTransactionWithAuth() { 146 DCHECK(server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH); 147 148 // No matter what, we want to report our status as IO pending since we will 149 // be notifying our consumer asynchronously via OnStartCompleted. 150 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 151 152 int rv = transaction_->RestartWithAuth(server_auth_->username, 153 server_auth_->password, 154 &start_callback_); 155 if (rv == ERR_IO_PENDING) 156 return; 157 158 MessageLoop::current()->PostTask( 159 FROM_HERE, 160 method_factory_.NewRunnableMethod( 161 &URLRequestFtpJob::OnStartCompleted, rv)); 162} 163 164void URLRequestFtpJob::Start() { 165 DCHECK(!transaction_.get()); 166 request_info_.url = request_->url(); 167 StartTransaction(); 168} 169 170void URLRequestFtpJob::Kill() { 171 if (!transaction_.get()) 172 return; 173 transaction_.reset(); 174 URLRequestJob::Kill(); 175 method_factory_.RevokeAll(); 176} 177 178LoadState URLRequestFtpJob::GetLoadState() const { 179 return transaction_.get() ? 180 transaction_->GetLoadState() : LOAD_STATE_IDLE; 181} 182 183bool URLRequestFtpJob::NeedsAuth() { 184 // Note that we only have to worry about cases where an actual FTP server 185 // requires auth (and not a proxy), because connecting to FTP via proxy 186 // effectively means the browser communicates via HTTP, and uses HTTP's 187 // Proxy-Authenticate protocol when proxy servers require auth. 188 return server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH; 189} 190 191void URLRequestFtpJob::GetAuthChallengeInfo( 192 scoped_refptr<AuthChallengeInfo>* result) { 193 DCHECK((server_auth_ != NULL) && 194 (server_auth_->state == AUTH_STATE_NEED_AUTH)); 195 scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo); 196 auth_info->is_proxy = false; 197 auth_info->host_and_port = ASCIIToWide( 198 GetHostAndPort(request_->url())); 199 auth_info->scheme = L""; 200 auth_info->realm = L""; 201 result->swap(auth_info); 202} 203 204void URLRequestFtpJob::SetAuth(const string16& username, 205 const string16& password) { 206 DCHECK(NeedsAuth()); 207 server_auth_->state = AUTH_STATE_HAVE_AUTH; 208 server_auth_->username = username; 209 server_auth_->password = password; 210 211 request_->context()->ftp_auth_cache()->Add(request_->url().GetOrigin(), 212 username, password); 213 214 RestartTransactionWithAuth(); 215} 216 217void URLRequestFtpJob::CancelAuth() { 218 DCHECK(NeedsAuth()); 219 server_auth_->state = AUTH_STATE_CANCELED; 220 221 // Once the auth is cancelled, we proceed with the request as though 222 // there were no auth. Schedule this for later so that we don't cause 223 // any recursing into the caller as a result of this call. 224 MessageLoop::current()->PostTask( 225 FROM_HERE, 226 method_factory_.NewRunnableMethod( 227 &URLRequestFtpJob::OnStartCompleted, OK)); 228} 229 230uint64 URLRequestFtpJob::GetUploadProgress() const { 231 return 0; 232} 233 234bool URLRequestFtpJob::ReadRawData(IOBuffer* buf, 235 int buf_size, 236 int *bytes_read) { 237 DCHECK_NE(buf_size, 0); 238 DCHECK(bytes_read); 239 DCHECK(!read_in_progress_); 240 241 int rv = transaction_->Read(buf, buf_size, &read_callback_); 242 if (rv >= 0) { 243 *bytes_read = rv; 244 return true; 245 } 246 247 if (rv == ERR_IO_PENDING) { 248 read_in_progress_ = true; 249 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 250 } else { 251 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); 252 } 253 return false; 254} 255 256} // namespace net 257