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