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