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 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 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 request_->load_flags(), 106 &proxy_info_, 107 base::Bind(&URLRequestFtpJob::OnResolveProxyComplete, 108 base::Unretained(this)), 109 &pac_request_, 110 NULL, 111 request_->net_log()); 112 113 if (rv == ERR_IO_PENDING) 114 return; 115 } 116 OnResolveProxyComplete(rv); 117 } 118 119 void URLRequestFtpJob::Kill() { 120 if (ftp_transaction_) 121 ftp_transaction_.reset(); 122 if (http_transaction_) 123 http_transaction_.reset(); 124 URLRequestJob::Kill(); 125 weak_factory_.InvalidateWeakPtrs(); 126 } 127 128 void URLRequestFtpJob::OnResolveProxyComplete(int result) { 129 pac_request_ = NULL; 130 131 if (result != OK) { 132 OnStartCompletedAsync(result); 133 return; 134 } 135 136 // Remove unsupported proxies from the list. 137 proxy_info_.RemoveProxiesWithoutScheme( 138 ProxyServer::SCHEME_DIRECT | 139 ProxyServer::SCHEME_HTTP | 140 ProxyServer::SCHEME_HTTPS); 141 142 // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 . 143 if (proxy_info_.is_direct()) 144 StartFtpTransaction(); 145 else if (proxy_info_.is_http() || proxy_info_.is_https()) 146 StartHttpTransaction(); 147 else 148 OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES); 149 } 150 151 void URLRequestFtpJob::StartFtpTransaction() { 152 // Create a transaction. 153 DCHECK(!ftp_transaction_); 154 155 ftp_request_info_.url = request_->url(); 156 ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction()); 157 158 // No matter what, we want to report our status as IO pending since we will 159 // be notifying our consumer asynchronously via OnStartCompleted. 160 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 161 int rv; 162 if (ftp_transaction_) { 163 rv = ftp_transaction_->Start( 164 &ftp_request_info_, 165 base::Bind(&URLRequestFtpJob::OnStartCompleted, 166 base::Unretained(this)), 167 request_->net_log()); 168 if (rv == ERR_IO_PENDING) 169 return; 170 } else { 171 rv = ERR_FAILED; 172 } 173 // The transaction started synchronously, but we need to notify the 174 // URLRequest delegate via the message loop. 175 OnStartCompletedAsync(rv); 176 } 177 178 void URLRequestFtpJob::StartHttpTransaction() { 179 // Create a transaction. 180 DCHECK(!http_transaction_); 181 182 // Do not cache FTP responses sent through HTTP proxy. 183 request_->SetLoadFlags(request_->load_flags() | 184 LOAD_DISABLE_CACHE | 185 LOAD_DO_NOT_SAVE_COOKIES | 186 LOAD_DO_NOT_SEND_COOKIES); 187 188 http_request_info_.url = request_->url(); 189 http_request_info_.method = request_->method(); 190 http_request_info_.load_flags = request_->load_flags(); 191 192 int rv = request_->context()->http_transaction_factory()->CreateTransaction( 193 priority_, &http_transaction_); 194 if (rv == OK) { 195 rv = http_transaction_->Start( 196 &http_request_info_, 197 base::Bind(&URLRequestFtpJob::OnStartCompleted, 198 base::Unretained(this)), 199 request_->net_log()); 200 if (rv == ERR_IO_PENDING) 201 return; 202 } 203 // The transaction started synchronously, but we need to notify the 204 // URLRequest delegate via the message loop. 205 OnStartCompletedAsync(rv); 206 } 207 208 void URLRequestFtpJob::OnStartCompleted(int result) { 209 // Clear the IO_PENDING status 210 SetStatus(URLRequestStatus()); 211 212 // Note that ftp_transaction_ may be NULL due to a creation failure. 213 if (ftp_transaction_) { 214 // FTP obviously doesn't have HTTP Content-Length header. We have to pass 215 // the content size information manually. 216 set_expected_content_size( 217 ftp_transaction_->GetResponseInfo()->expected_content_size); 218 } 219 220 if (result == OK) { 221 if (http_transaction_) { 222 http_response_info_ = http_transaction_->GetResponseInfo(); 223 SetProxyServer(http_response_info_->proxy_server); 224 225 if (http_response_info_->headers->response_code() == 401 || 226 http_response_info_->headers->response_code() == 407) { 227 HandleAuthNeededResponse(); 228 return; 229 } 230 } 231 NotifyHeadersComplete(); 232 } else if (ftp_transaction_ && 233 ftp_transaction_->GetResponseInfo()->needs_auth) { 234 HandleAuthNeededResponse(); 235 return; 236 } else { 237 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 238 } 239 } 240 241 void URLRequestFtpJob::OnStartCompletedAsync(int result) { 242 base::MessageLoop::current()->PostTask( 243 FROM_HERE, 244 base::Bind(&URLRequestFtpJob::OnStartCompleted, 245 weak_factory_.GetWeakPtr(), result)); 246 } 247 248 void URLRequestFtpJob::OnReadCompleted(int result) { 249 read_in_progress_ = false; 250 if (result == 0) { 251 NotifyDone(URLRequestStatus()); 252 } else if (result < 0) { 253 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 254 } else { 255 // Clear the IO_PENDING status 256 SetStatus(URLRequestStatus()); 257 } 258 NotifyReadComplete(result); 259 } 260 261 void URLRequestFtpJob::RestartTransactionWithAuth() { 262 DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH); 263 264 // No matter what, we want to report our status as IO pending since we will 265 // be notifying our consumer asynchronously via OnStartCompleted. 266 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 267 268 int rv; 269 if (proxy_info_.is_direct()) { 270 rv = ftp_transaction_->RestartWithAuth( 271 auth_data_->credentials, 272 base::Bind(&URLRequestFtpJob::OnStartCompleted, 273 base::Unretained(this))); 274 } else { 275 rv = http_transaction_->RestartWithAuth( 276 auth_data_->credentials, 277 base::Bind(&URLRequestFtpJob::OnStartCompleted, 278 base::Unretained(this))); 279 } 280 if (rv == ERR_IO_PENDING) 281 return; 282 283 OnStartCompletedAsync(rv); 284 } 285 286 LoadState URLRequestFtpJob::GetLoadState() const { 287 if (proxy_info_.is_direct()) { 288 return ftp_transaction_ ? 289 ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE; 290 } else { 291 return http_transaction_ ? 292 http_transaction_->GetLoadState() : LOAD_STATE_IDLE; 293 } 294 } 295 296 bool URLRequestFtpJob::NeedsAuth() { 297 return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH; 298 } 299 300 void URLRequestFtpJob::GetAuthChallengeInfo( 301 scoped_refptr<AuthChallengeInfo>* result) { 302 DCHECK(NeedsAuth()); 303 304 if (http_response_info_) { 305 *result = http_response_info_->auth_challenge; 306 return; 307 } 308 309 scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo); 310 auth_info->is_proxy = false; 311 auth_info->challenger = HostPortPair::FromURL(request_->url()); 312 // scheme and realm are kept empty. 313 DCHECK(auth_info->scheme.empty()); 314 DCHECK(auth_info->realm.empty()); 315 result->swap(auth_info); 316 } 317 318 void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) { 319 DCHECK(ftp_transaction_ || http_transaction_); 320 DCHECK(NeedsAuth()); 321 322 auth_data_->state = AUTH_STATE_HAVE_AUTH; 323 auth_data_->credentials = credentials; 324 325 if (ftp_transaction_) { 326 ftp_auth_cache_->Add(request_->url().GetOrigin(), 327 auth_data_->credentials); 328 } 329 330 RestartTransactionWithAuth(); 331 } 332 333 void URLRequestFtpJob::CancelAuth() { 334 DCHECK(ftp_transaction_ || http_transaction_); 335 DCHECK(NeedsAuth()); 336 337 auth_data_->state = AUTH_STATE_CANCELED; 338 339 // Once the auth is cancelled, we proceed with the request as though 340 // there were no auth. Schedule this for later so that we don't cause 341 // any recursing into the caller as a result of this call. 342 OnStartCompletedAsync(OK); 343 } 344 345 UploadProgress URLRequestFtpJob::GetUploadProgress() const { 346 return UploadProgress(); 347 } 348 349 bool URLRequestFtpJob::ReadRawData(IOBuffer* buf, 350 int buf_size, 351 int *bytes_read) { 352 DCHECK_NE(buf_size, 0); 353 DCHECK(bytes_read); 354 DCHECK(!read_in_progress_); 355 356 int rv; 357 if (proxy_info_.is_direct()) { 358 rv = ftp_transaction_->Read(buf, buf_size, 359 base::Bind(&URLRequestFtpJob::OnReadCompleted, 360 base::Unretained(this))); 361 } else { 362 rv = http_transaction_->Read(buf, buf_size, 363 base::Bind(&URLRequestFtpJob::OnReadCompleted, 364 base::Unretained(this))); 365 } 366 367 if (rv >= 0) { 368 *bytes_read = rv; 369 return true; 370 } 371 372 if (rv == ERR_IO_PENDING) { 373 read_in_progress_ = true; 374 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 375 } else { 376 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); 377 } 378 return false; 379 } 380 381 void URLRequestFtpJob::HandleAuthNeededResponse() { 382 GURL origin = request_->url().GetOrigin(); 383 384 if (auth_data_.get()) { 385 if (auth_data_->state == AUTH_STATE_CANCELED) { 386 NotifyHeadersComplete(); 387 return; 388 } 389 390 if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) 391 ftp_auth_cache_->Remove(origin, auth_data_->credentials); 392 } else { 393 auth_data_ = new AuthData; 394 } 395 auth_data_->state = AUTH_STATE_NEED_AUTH; 396 397 FtpAuthCache::Entry* cached_auth = NULL; 398 if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth) 399 cached_auth = ftp_auth_cache_->Lookup(origin); 400 if (cached_auth) { 401 // Retry using cached auth data. 402 SetAuth(cached_auth->credentials); 403 } else { 404 // Prompt for a username/password. 405 NotifyHeadersComplete(); 406 } 407 } 408 409 } // namespace net 410