• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
URLRequestFtpJob(URLRequest * request,NetworkDelegate * network_delegate,FtpTransactionFactory * ftp_transaction_factory,FtpAuthCache * ftp_auth_cache)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 
~URLRequestFtpJob()45 URLRequestFtpJob::~URLRequestFtpJob() {
46   if (pac_request_)
47     proxy_service_->CancelPacRequest(pac_request_);
48 }
49 
IsSafeRedirect(const GURL & location)50 bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) {
51   // Disallow all redirects.
52   return false;
53 }
54 
GetMimeType(std::string * mime_type) const55 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 
GetResponseInfo(HttpResponseInfo * info)70 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
71   if (http_response_info_)
72     *info = *http_response_info_;
73 }
74 
GetSocketAddress() const75 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 
SetPriority(RequestPriority priority)87 void URLRequestFtpJob::SetPriority(RequestPriority priority) {
88   priority_ = priority;
89   if (http_transaction_)
90     http_transaction_->SetPriority(priority);
91 }
92 
Start()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 
Kill()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 
OnResolveProxyComplete(int result)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 
StartFtpTransaction()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 
StartHttpTransaction()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 
OnStartCompleted(int result)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 
OnStartCompletedAsync(int result)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 
OnReadCompleted(int result)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 
RestartTransactionWithAuth()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 
GetLoadState() const286 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 
NeedsAuth()296 bool URLRequestFtpJob::NeedsAuth() {
297   return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
298 }
299 
GetAuthChallengeInfo(scoped_refptr<AuthChallengeInfo> * result)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 
SetAuth(const AuthCredentials & credentials)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 
CancelAuth()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 
GetUploadProgress() const345 UploadProgress URLRequestFtpJob::GetUploadProgress() const {
346   return UploadProgress();
347 }
348 
ReadRawData(IOBuffer * buf,int buf_size,int * bytes_read)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 
HandleAuthNeededResponse()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