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