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 24namespace net { 25 26URLRequestFtpJob::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 ftp_transaction_factory_(ftp_transaction_factory), 38 ftp_auth_cache_(ftp_auth_cache), 39 weak_factory_(this) { 40 DCHECK(proxy_service_); 41 DCHECK(ftp_transaction_factory); 42 DCHECK(ftp_auth_cache); 43} 44 45URLRequestFtpJob::~URLRequestFtpJob() { 46 if (pac_request_) 47 proxy_service_->CancelPacRequest(pac_request_); 48} 49 50bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) { 51 // Disallow all redirects. 52 return false; 53} 54 55bool 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 70void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) { 71 if (http_response_info_) 72 *info = *http_response_info_; 73} 74 75HostPortPair 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 87void URLRequestFtpJob::SetPriority(RequestPriority priority) { 88 priority_ = priority; 89 if (http_transaction_) 90 http_transaction_->SetPriority(priority); 91} 92 93void 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 117void 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 126void 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 149void 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 176void 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_); 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 206void 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 SetProxyServer(http_response_info_->proxy_server); 222 223 if (http_response_info_->headers->response_code() == 401 || 224 http_response_info_->headers->response_code() == 407) { 225 HandleAuthNeededResponse(); 226 return; 227 } 228 } 229 NotifyHeadersComplete(); 230 } else if (ftp_transaction_ && 231 ftp_transaction_->GetResponseInfo()->needs_auth) { 232 HandleAuthNeededResponse(); 233 return; 234 } else { 235 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 236 } 237} 238 239void URLRequestFtpJob::OnStartCompletedAsync(int result) { 240 base::MessageLoop::current()->PostTask( 241 FROM_HERE, 242 base::Bind(&URLRequestFtpJob::OnStartCompleted, 243 weak_factory_.GetWeakPtr(), result)); 244} 245 246void URLRequestFtpJob::OnReadCompleted(int result) { 247 read_in_progress_ = false; 248 if (result == 0) { 249 NotifyDone(URLRequestStatus()); 250 } else if (result < 0) { 251 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 252 } else { 253 // Clear the IO_PENDING status 254 SetStatus(URLRequestStatus()); 255 } 256 NotifyReadComplete(result); 257} 258 259void URLRequestFtpJob::RestartTransactionWithAuth() { 260 DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH); 261 262 // No matter what, we want to report our status as IO pending since we will 263 // be notifying our consumer asynchronously via OnStartCompleted. 264 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 265 266 int rv; 267 if (proxy_info_.is_direct()) { 268 rv = ftp_transaction_->RestartWithAuth( 269 auth_data_->credentials, 270 base::Bind(&URLRequestFtpJob::OnStartCompleted, 271 base::Unretained(this))); 272 } else { 273 rv = http_transaction_->RestartWithAuth( 274 auth_data_->credentials, 275 base::Bind(&URLRequestFtpJob::OnStartCompleted, 276 base::Unretained(this))); 277 } 278 if (rv == ERR_IO_PENDING) 279 return; 280 281 OnStartCompletedAsync(rv); 282} 283 284LoadState URLRequestFtpJob::GetLoadState() const { 285 if (proxy_info_.is_direct()) { 286 return ftp_transaction_ ? 287 ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE; 288 } else { 289 return http_transaction_ ? 290 http_transaction_->GetLoadState() : LOAD_STATE_IDLE; 291 } 292} 293 294bool URLRequestFtpJob::NeedsAuth() { 295 return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH; 296} 297 298void URLRequestFtpJob::GetAuthChallengeInfo( 299 scoped_refptr<AuthChallengeInfo>* result) { 300 DCHECK(NeedsAuth()); 301 302 if (http_response_info_) { 303 *result = http_response_info_->auth_challenge; 304 return; 305 } 306 307 scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo); 308 auth_info->is_proxy = false; 309 auth_info->challenger = HostPortPair::FromURL(request_->url()); 310 // scheme and realm are kept empty. 311 DCHECK(auth_info->scheme.empty()); 312 DCHECK(auth_info->realm.empty()); 313 result->swap(auth_info); 314} 315 316void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) { 317 DCHECK(ftp_transaction_ || http_transaction_); 318 DCHECK(NeedsAuth()); 319 320 auth_data_->state = AUTH_STATE_HAVE_AUTH; 321 auth_data_->credentials = credentials; 322 323 if (ftp_transaction_) { 324 ftp_auth_cache_->Add(request_->url().GetOrigin(), 325 auth_data_->credentials); 326 } 327 328 RestartTransactionWithAuth(); 329} 330 331void URLRequestFtpJob::CancelAuth() { 332 DCHECK(ftp_transaction_ || http_transaction_); 333 DCHECK(NeedsAuth()); 334 335 auth_data_->state = AUTH_STATE_CANCELED; 336 337 // Once the auth is cancelled, we proceed with the request as though 338 // there were no auth. Schedule this for later so that we don't cause 339 // any recursing into the caller as a result of this call. 340 OnStartCompletedAsync(OK); 341} 342 343UploadProgress URLRequestFtpJob::GetUploadProgress() const { 344 return UploadProgress(); 345} 346 347bool URLRequestFtpJob::ReadRawData(IOBuffer* buf, 348 int buf_size, 349 int *bytes_read) { 350 DCHECK_NE(buf_size, 0); 351 DCHECK(bytes_read); 352 DCHECK(!read_in_progress_); 353 354 int rv; 355 if (proxy_info_.is_direct()) { 356 rv = ftp_transaction_->Read(buf, buf_size, 357 base::Bind(&URLRequestFtpJob::OnReadCompleted, 358 base::Unretained(this))); 359 } else { 360 rv = http_transaction_->Read(buf, buf_size, 361 base::Bind(&URLRequestFtpJob::OnReadCompleted, 362 base::Unretained(this))); 363 } 364 365 if (rv >= 0) { 366 *bytes_read = rv; 367 return true; 368 } 369 370 if (rv == ERR_IO_PENDING) { 371 read_in_progress_ = true; 372 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 373 } else { 374 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); 375 } 376 return false; 377} 378 379void URLRequestFtpJob::HandleAuthNeededResponse() { 380 GURL origin = request_->url().GetOrigin(); 381 382 if (auth_data_.get()) { 383 if (auth_data_->state == AUTH_STATE_CANCELED) { 384 NotifyHeadersComplete(); 385 return; 386 } 387 388 if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) 389 ftp_auth_cache_->Remove(origin, auth_data_->credentials); 390 } else { 391 auth_data_ = new AuthData; 392 } 393 auth_data_->state = AUTH_STATE_NEED_AUTH; 394 395 FtpAuthCache::Entry* cached_auth = NULL; 396 if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth) 397 cached_auth = ftp_auth_cache_->Lookup(origin); 398 if (cached_auth) { 399 // Retry using cached auth data. 400 SetAuth(cached_auth->credentials); 401 } else { 402 // Prompt for a username/password. 403 NotifyHeadersComplete(); 404 } 405} 406 407} // namespace net 408