1 // Copyright (c) 2012 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/message_loop.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "net/base/auth.h" 11 #include "net/base/host_port_pair.h" 12 #include "net/base/load_flags.h" 13 #include "net/base/net_errors.h" 14 #include "net/base/net_util.h" 15 #include "net/ftp/ftp_auth_cache.h" 16 #include "net/ftp/ftp_response_info.h" 17 #include "net/ftp/ftp_transaction_factory.h" 18 #include "net/http/http_response_headers.h" 19 #include "net/http/http_transaction_factory.h" 20 #include "net/url_request/url_request.h" 21 #include "net/url_request/url_request_context.h" 22 #include "net/url_request/url_request_error_job.h" 23 24 namespace net { 25 26 URLRequestFtpJob::URLRequestFtpJob( 27 URLRequest* request, 28 NetworkDelegate* network_delegate, 29 FtpTransactionFactory* ftp_transaction_factory, 30 FtpAuthCache* ftp_auth_cache) 31 : URLRequestJob(request, network_delegate), 32 priority_(DEFAULT_PRIORITY), 33 proxy_service_(request_->context()->proxy_service()), 34 pac_request_(NULL), 35 http_response_info_(NULL), 36 read_in_progress_(false), 37 weak_factory_(this), 38 ftp_transaction_factory_(ftp_transaction_factory), 39 ftp_auth_cache_(ftp_auth_cache) { 40 DCHECK(proxy_service_); 41 DCHECK(ftp_transaction_factory); 42 DCHECK(ftp_auth_cache); 43 } 44 45 URLRequestFtpJob::~URLRequestFtpJob() { 46 if (pac_request_) 47 proxy_service_->CancelPacRequest(pac_request_); 48 } 49 50 bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) { 51 // Disallow all redirects. 52 return false; 53 } 54 55 bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { 56 if (proxy_info_.is_direct()) { 57 if (ftp_transaction_->GetResponseInfo()->is_directory_listing) { 58 *mime_type = "text/vnd.chromium.ftp-dir"; 59 return true; 60 } 61 } else { 62 // No special handling of MIME type is needed. As opposed to direct FTP 63 // transaction, we do not get a raw directory listing to parse. 64 return http_transaction_->GetResponseInfo()-> 65 headers->GetMimeType(mime_type); 66 } 67 return false; 68 } 69 70 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) { 71 if (http_response_info_) 72 *info = *http_response_info_; 73 } 74 75 HostPortPair URLRequestFtpJob::GetSocketAddress() const { 76 if (proxy_info_.is_direct()) { 77 if (!ftp_transaction_) 78 return HostPortPair(); 79 return ftp_transaction_->GetResponseInfo()->socket_address; 80 } else { 81 if (!http_transaction_) 82 return HostPortPair(); 83 return http_transaction_->GetResponseInfo()->socket_address; 84 } 85 } 86 87 void URLRequestFtpJob::SetPriority(RequestPriority priority) { 88 priority_ = priority; 89 if (http_transaction_) 90 http_transaction_->SetPriority(priority); 91 } 92 93 void URLRequestFtpJob::Start() { 94 DCHECK(!pac_request_); 95 DCHECK(!ftp_transaction_); 96 DCHECK(!http_transaction_); 97 98 int rv = OK; 99 if (request_->load_flags() & LOAD_BYPASS_PROXY) { 100 proxy_info_.UseDirect(); 101 } else { 102 DCHECK_EQ(request_->context()->proxy_service(), proxy_service_); 103 rv = proxy_service_->ResolveProxy( 104 request_->url(), 105 &proxy_info_, 106 base::Bind(&URLRequestFtpJob::OnResolveProxyComplete, 107 base::Unretained(this)), 108 &pac_request_, 109 request_->net_log()); 110 111 if (rv == ERR_IO_PENDING) 112 return; 113 } 114 OnResolveProxyComplete(rv); 115 } 116 117 void URLRequestFtpJob::Kill() { 118 if (ftp_transaction_) 119 ftp_transaction_.reset(); 120 if (http_transaction_) 121 http_transaction_.reset(); 122 URLRequestJob::Kill(); 123 weak_factory_.InvalidateWeakPtrs(); 124 } 125 126 void URLRequestFtpJob::OnResolveProxyComplete(int result) { 127 pac_request_ = NULL; 128 129 if (result != OK) { 130 OnStartCompletedAsync(result); 131 return; 132 } 133 134 // Remove unsupported proxies from the list. 135 proxy_info_.RemoveProxiesWithoutScheme( 136 ProxyServer::SCHEME_DIRECT | 137 ProxyServer::SCHEME_HTTP | 138 ProxyServer::SCHEME_HTTPS); 139 140 // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 . 141 if (proxy_info_.is_direct()) 142 StartFtpTransaction(); 143 else if (proxy_info_.is_http() || proxy_info_.is_https()) 144 StartHttpTransaction(); 145 else 146 OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES); 147 } 148 149 void URLRequestFtpJob::StartFtpTransaction() { 150 // Create a transaction. 151 DCHECK(!ftp_transaction_); 152 153 ftp_request_info_.url = request_->url(); 154 ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction()); 155 156 // No matter what, we want to report our status as IO pending since we will 157 // be notifying our consumer asynchronously via OnStartCompleted. 158 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 159 int rv; 160 if (ftp_transaction_) { 161 rv = ftp_transaction_->Start( 162 &ftp_request_info_, 163 base::Bind(&URLRequestFtpJob::OnStartCompleted, 164 base::Unretained(this)), 165 request_->net_log()); 166 if (rv == ERR_IO_PENDING) 167 return; 168 } else { 169 rv = ERR_FAILED; 170 } 171 // The transaction started synchronously, but we need to notify the 172 // URLRequest delegate via the message loop. 173 OnStartCompletedAsync(rv); 174 } 175 176 void URLRequestFtpJob::StartHttpTransaction() { 177 // Create a transaction. 178 DCHECK(!http_transaction_); 179 180 // Do not cache FTP responses sent through HTTP proxy. 181 request_->SetLoadFlags(request_->load_flags() | 182 LOAD_DISABLE_CACHE | 183 LOAD_DO_NOT_SAVE_COOKIES | 184 LOAD_DO_NOT_SEND_COOKIES); 185 186 http_request_info_.url = request_->url(); 187 http_request_info_.method = request_->method(); 188 http_request_info_.load_flags = request_->load_flags(); 189 190 int rv = request_->context()->http_transaction_factory()->CreateTransaction( 191 priority_, &http_transaction_, NULL); 192 if (rv == OK) { 193 rv = http_transaction_->Start( 194 &http_request_info_, 195 base::Bind(&URLRequestFtpJob::OnStartCompleted, 196 base::Unretained(this)), 197 request_->net_log()); 198 if (rv == ERR_IO_PENDING) 199 return; 200 } 201 // The transaction started synchronously, but we need to notify the 202 // URLRequest delegate via the message loop. 203 OnStartCompletedAsync(rv); 204 } 205 206 void URLRequestFtpJob::OnStartCompleted(int result) { 207 // Clear the IO_PENDING status 208 SetStatus(URLRequestStatus()); 209 210 // Note that ftp_transaction_ may be NULL due to a creation failure. 211 if (ftp_transaction_) { 212 // FTP obviously doesn't have HTTP Content-Length header. We have to pass 213 // the content size information manually. 214 set_expected_content_size( 215 ftp_transaction_->GetResponseInfo()->expected_content_size); 216 } 217 218 if (result == OK) { 219 if (http_transaction_) { 220 http_response_info_ = http_transaction_->GetResponseInfo(); 221 222 if (http_response_info_->headers->response_code() == 401 || 223 http_response_info_->headers->response_code() == 407) { 224 HandleAuthNeededResponse(); 225 return; 226 } 227 } 228 NotifyHeadersComplete(); 229 } else if (ftp_transaction_ && 230 ftp_transaction_->GetResponseInfo()->needs_auth) { 231 HandleAuthNeededResponse(); 232 return; 233 } else { 234 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 235 } 236 } 237 238 void URLRequestFtpJob::OnStartCompletedAsync(int result) { 239 base::MessageLoop::current()->PostTask( 240 FROM_HERE, 241 base::Bind(&URLRequestFtpJob::OnStartCompleted, 242 weak_factory_.GetWeakPtr(), result)); 243 } 244 245 void URLRequestFtpJob::OnReadCompleted(int result) { 246 read_in_progress_ = false; 247 if (result == 0) { 248 NotifyDone(URLRequestStatus()); 249 } else if (result < 0) { 250 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 251 } else { 252 // Clear the IO_PENDING status 253 SetStatus(URLRequestStatus()); 254 } 255 NotifyReadComplete(result); 256 } 257 258 void URLRequestFtpJob::RestartTransactionWithAuth() { 259 DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH); 260 261 // No matter what, we want to report our status as IO pending since we will 262 // be notifying our consumer asynchronously via OnStartCompleted. 263 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 264 265 int rv; 266 if (proxy_info_.is_direct()) { 267 rv = ftp_transaction_->RestartWithAuth( 268 auth_data_->credentials, 269 base::Bind(&URLRequestFtpJob::OnStartCompleted, 270 base::Unretained(this))); 271 } else { 272 rv = http_transaction_->RestartWithAuth( 273 auth_data_->credentials, 274 base::Bind(&URLRequestFtpJob::OnStartCompleted, 275 base::Unretained(this))); 276 } 277 if (rv == ERR_IO_PENDING) 278 return; 279 280 OnStartCompletedAsync(rv); 281 } 282 283 LoadState URLRequestFtpJob::GetLoadState() const { 284 if (proxy_info_.is_direct()) { 285 return ftp_transaction_ ? 286 ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE; 287 } else { 288 return http_transaction_ ? 289 http_transaction_->GetLoadState() : LOAD_STATE_IDLE; 290 } 291 } 292 293 bool URLRequestFtpJob::NeedsAuth() { 294 return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH; 295 } 296 297 void URLRequestFtpJob::GetAuthChallengeInfo( 298 scoped_refptr<AuthChallengeInfo>* result) { 299 DCHECK(NeedsAuth()); 300 301 if (http_response_info_) { 302 *result = http_response_info_->auth_challenge; 303 return; 304 } 305 306 scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo); 307 auth_info->is_proxy = false; 308 auth_info->challenger = HostPortPair::FromURL(request_->url()); 309 // scheme and realm are kept empty. 310 DCHECK(auth_info->scheme.empty()); 311 DCHECK(auth_info->realm.empty()); 312 result->swap(auth_info); 313 } 314 315 void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) { 316 DCHECK(ftp_transaction_ || http_transaction_); 317 DCHECK(NeedsAuth()); 318 319 auth_data_->state = AUTH_STATE_HAVE_AUTH; 320 auth_data_->credentials = credentials; 321 322 if (ftp_transaction_) { 323 ftp_auth_cache_->Add(request_->url().GetOrigin(), 324 auth_data_->credentials); 325 } 326 327 RestartTransactionWithAuth(); 328 } 329 330 void URLRequestFtpJob::CancelAuth() { 331 DCHECK(ftp_transaction_ || http_transaction_); 332 DCHECK(NeedsAuth()); 333 334 auth_data_->state = AUTH_STATE_CANCELED; 335 336 // Once the auth is cancelled, we proceed with the request as though 337 // there were no auth. Schedule this for later so that we don't cause 338 // any recursing into the caller as a result of this call. 339 OnStartCompletedAsync(OK); 340 } 341 342 UploadProgress URLRequestFtpJob::GetUploadProgress() const { 343 return UploadProgress(); 344 } 345 346 bool URLRequestFtpJob::ReadRawData(IOBuffer* buf, 347 int buf_size, 348 int *bytes_read) { 349 DCHECK_NE(buf_size, 0); 350 DCHECK(bytes_read); 351 DCHECK(!read_in_progress_); 352 353 int rv; 354 if (proxy_info_.is_direct()) { 355 rv = ftp_transaction_->Read(buf, buf_size, 356 base::Bind(&URLRequestFtpJob::OnReadCompleted, 357 base::Unretained(this))); 358 } else { 359 rv = http_transaction_->Read(buf, buf_size, 360 base::Bind(&URLRequestFtpJob::OnReadCompleted, 361 base::Unretained(this))); 362 } 363 364 if (rv >= 0) { 365 *bytes_read = rv; 366 return true; 367 } 368 369 if (rv == ERR_IO_PENDING) { 370 read_in_progress_ = true; 371 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 372 } else { 373 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); 374 } 375 return false; 376 } 377 378 void URLRequestFtpJob::HandleAuthNeededResponse() { 379 GURL origin = request_->url().GetOrigin(); 380 381 if (auth_data_.get()) { 382 if (auth_data_->state == AUTH_STATE_CANCELED) { 383 NotifyHeadersComplete(); 384 return; 385 } 386 387 if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) 388 ftp_auth_cache_->Remove(origin, auth_data_->credentials); 389 } else { 390 auth_data_ = new AuthData; 391 } 392 auth_data_->state = AUTH_STATE_NEED_AUTH; 393 394 FtpAuthCache::Entry* cached_auth = NULL; 395 if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth) 396 cached_auth = ftp_auth_cache_->Lookup(origin); 397 if (cached_auth) { 398 // Retry using cached auth data. 399 SetAuth(cached_auth->credentials); 400 } else { 401 // Prompt for a username/password. 402 NotifyHeadersComplete(); 403 } 404 } 405 406 } // namespace net 407