1 // Copyright (c) 2009 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_new_ftp_job.h" 6 7 #include "base/compiler_specific.h" 8 #include "base/message_loop.h" 9 #include "net/base/auth.h" 10 #include "net/base/net_errors.h" 11 #include "net/base/net_util.h" 12 #include "net/ftp/ftp_response_info.h" 13 #include "net/ftp/ftp_transaction_factory.h" 14 #include "net/url_request/url_request.h" 15 #include "net/url_request/url_request_context.h" 16 #include "net/url_request/url_request_error_job.h" 17 18 URLRequestNewFtpJob::URLRequestNewFtpJob(URLRequest* request) 19 : URLRequestJob(request), 20 ALLOW_THIS_IN_INITIALIZER_LIST( 21 start_callback_(this, &URLRequestNewFtpJob::OnStartCompleted)), 22 ALLOW_THIS_IN_INITIALIZER_LIST( 23 read_callback_(this, &URLRequestNewFtpJob::OnReadCompleted)), 24 read_in_progress_(false), 25 context_(request->context()) { 26 } 27 28 URLRequestNewFtpJob::~URLRequestNewFtpJob() { 29 } 30 31 // static 32 URLRequestJob* URLRequestNewFtpJob::Factory(URLRequest* request, 33 const std::string& scheme) { 34 DCHECK_EQ(scheme, "ftp"); 35 36 int port = request->url().IntPort(); 37 if (request->url().has_port() && 38 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port)) 39 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT); 40 41 DCHECK(request->context()); 42 DCHECK(request->context()->ftp_transaction_factory()); 43 return new URLRequestNewFtpJob(request); 44 } 45 46 bool URLRequestNewFtpJob::GetMimeType(std::string* mime_type) const { 47 if (transaction_->GetResponseInfo()->is_directory_listing) { 48 *mime_type = "text/vnd.chromium.ftp-dir"; 49 return true; 50 } 51 return false; 52 } 53 54 void URLRequestNewFtpJob::Start() { 55 DCHECK(!transaction_.get()); 56 request_info_.url = request_->url(); 57 StartTransaction(); 58 } 59 60 void URLRequestNewFtpJob::Kill() { 61 if (!transaction_.get()) 62 return; 63 DestroyTransaction(); 64 URLRequestJob::Kill(); 65 } 66 67 net::LoadState URLRequestNewFtpJob::GetLoadState() const { 68 return transaction_.get() ? 69 transaction_->GetLoadState() : net::LOAD_STATE_IDLE; 70 } 71 72 bool URLRequestNewFtpJob::NeedsAuth() { 73 // Note that we only have to worry about cases where an actual FTP server 74 // requires auth (and not a proxy), because connecting to FTP via proxy 75 // effectively means the browser communicates via HTTP, and uses HTTP's 76 // Proxy-Authenticate protocol when proxy servers require auth. 77 return server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH; 78 } 79 80 void URLRequestNewFtpJob::GetAuthChallengeInfo( 81 scoped_refptr<net::AuthChallengeInfo>* result) { 82 DCHECK((server_auth_ != NULL) && 83 (server_auth_->state == net::AUTH_STATE_NEED_AUTH)); 84 scoped_refptr<net::AuthChallengeInfo> auth_info = new net::AuthChallengeInfo; 85 auth_info->is_proxy = false; 86 auth_info->host_and_port = ASCIIToWide( 87 net::GetHostAndPort(request_->url())); 88 auth_info->scheme = L""; 89 auth_info->realm = L""; 90 result->swap(auth_info); 91 } 92 93 void URLRequestNewFtpJob::SetAuth(const std::wstring& username, 94 const std::wstring& password) { 95 DCHECK(NeedsAuth()); 96 server_auth_->state = net::AUTH_STATE_HAVE_AUTH; 97 server_auth_->username = username; 98 server_auth_->password = password; 99 100 request_->context()->ftp_auth_cache()->Add(request_->url().GetOrigin(), 101 username, password); 102 103 RestartTransactionWithAuth(); 104 } 105 106 void URLRequestNewFtpJob::CancelAuth() { 107 DCHECK(NeedsAuth()); 108 server_auth_->state = net::AUTH_STATE_CANCELED; 109 110 // Once the auth is cancelled, we proceed with the request as though 111 // there were no auth. Schedule this for later so that we don't cause 112 // any recursing into the caller as a result of this call. 113 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 114 this, &URLRequestNewFtpJob::OnStartCompleted, net::OK)); 115 } 116 117 bool URLRequestNewFtpJob::ReadRawData(net::IOBuffer* buf, 118 int buf_size, 119 int *bytes_read) { 120 DCHECK_NE(buf_size, 0); 121 DCHECK(bytes_read); 122 DCHECK(!read_in_progress_); 123 124 int rv = transaction_->Read(buf, buf_size, &read_callback_); 125 if (rv >= 0) { 126 *bytes_read = rv; 127 return true; 128 } 129 130 if (rv == net::ERR_IO_PENDING) { 131 read_in_progress_ = true; 132 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 133 } else { 134 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); 135 } 136 return false; 137 } 138 139 void URLRequestNewFtpJob::OnStartCompleted(int result) { 140 // If the request was destroyed, then there is no more work to do. 141 if (!request_ || !request_->delegate()) 142 return; 143 // If the transaction was destroyed, then the job was cancelled, and 144 // we can just ignore this notification. 145 if (!transaction_.get()) 146 return; 147 // Clear the IO_PENDING status 148 SetStatus(URLRequestStatus()); 149 if (result == net::OK) { 150 NotifyHeadersComplete(); 151 } else if (transaction_->GetResponseInfo()->needs_auth) { 152 GURL origin = request_->url().GetOrigin(); 153 if (server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH) { 154 request_->context()->ftp_auth_cache()->Remove(origin, 155 server_auth_->username, 156 server_auth_->password); 157 } else if (!server_auth_) { 158 server_auth_ = new net::AuthData(); 159 } 160 server_auth_->state = net::AUTH_STATE_NEED_AUTH; 161 162 net::FtpAuthCache::Entry* cached_auth = 163 request_->context()->ftp_auth_cache()->Lookup(origin); 164 165 if (cached_auth) { 166 // Retry using cached auth data. 167 SetAuth(cached_auth->username, cached_auth->password); 168 } else { 169 // Prompt for a username/password. 170 NotifyHeadersComplete(); 171 } 172 } else { 173 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 174 } 175 } 176 177 void URLRequestNewFtpJob::OnReadCompleted(int result) { 178 read_in_progress_ = false; 179 if (result == 0) { 180 NotifyDone(URLRequestStatus()); 181 } else if (result < 0) { 182 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 183 } else { 184 // Clear the IO_PENDING status 185 SetStatus(URLRequestStatus()); 186 } 187 NotifyReadComplete(result); 188 } 189 190 void URLRequestNewFtpJob::RestartTransactionWithAuth() { 191 DCHECK(server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH); 192 193 // No matter what, we want to report our status as IO pending since we will 194 // be notifying our consumer asynchronously via OnStartCompleted. 195 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 196 197 int rv = transaction_->RestartWithAuth(server_auth_->username, 198 server_auth_->password, 199 &start_callback_); 200 if (rv == net::ERR_IO_PENDING) 201 return; 202 203 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 204 this, &URLRequestNewFtpJob::OnStartCompleted, rv)); 205 } 206 207 void URLRequestNewFtpJob::StartTransaction() { 208 // Create a transaction. 209 DCHECK(!transaction_.get()); 210 DCHECK(request_->context()); 211 DCHECK(request_->context()->ftp_transaction_factory()); 212 213 transaction_.reset( 214 request_->context()->ftp_transaction_factory()->CreateTransaction()); 215 216 // No matter what, we want to report our status as IO pending since we will 217 // be notifying our consumer asynchronously via OnStartCompleted. 218 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 219 int rv; 220 if (transaction_.get()) { 221 rv = transaction_->Start( 222 &request_info_, &start_callback_, request_->load_log()); 223 if (rv == net::ERR_IO_PENDING) 224 return; 225 } else { 226 rv = net::ERR_FAILED; 227 } 228 // The transaction started synchronously, but we need to notify the 229 // URLRequest delegate via the message loop. 230 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 231 this, &URLRequestNewFtpJob::OnStartCompleted, rv)); 232 } 233 234 void URLRequestNewFtpJob::DestroyTransaction() { 235 DCHECK(transaction_.get()); 236 237 transaction_.reset(); 238 } 239