| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/url_request/url_request_new_ftp_job.h" | 5 #include "net/url_request/url_request_new_ftp_job.h" |
| 6 | 6 |
| 7 #include "base/compiler_specific.h" | 7 #include "base/compiler_specific.h" |
| 8 #include "base/file_version_info.h" | |
| 9 #include "base/message_loop.h" | 8 #include "base/message_loop.h" |
| 10 #include "base/sys_string_conversions.h" | |
| 11 #include "net/base/auth.h" | 9 #include "net/base/auth.h" |
| 12 #include "net/base/escape.h" | |
| 13 #include "net/base/net_errors.h" | 10 #include "net/base/net_errors.h" |
| 14 #include "net/base/net_util.h" | 11 #include "net/base/net_util.h" |
| 15 #include "net/ftp/ftp_response_info.h" | 12 #include "net/ftp/ftp_response_info.h" |
| 16 #include "net/ftp/ftp_server_type_histograms.h" | |
| 17 #include "net/ftp/ftp_transaction_factory.h" | 13 #include "net/ftp/ftp_transaction_factory.h" |
| 18 #include "net/third_party/parseftp/ParseFTPList.h" | |
| 19 #include "net/url_request/url_request.h" | 14 #include "net/url_request/url_request.h" |
| 20 #include "net/url_request/url_request_context.h" | 15 #include "net/url_request/url_request_context.h" |
| 21 #include "net/url_request/url_request_error_job.h" | 16 #include "net/url_request/url_request_error_job.h" |
| 22 #include "unicode/ucsdet.h" | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 // A very simple-minded character encoding detection. | |
| 27 // TODO(jungshik): We can apply more heuristics here (e.g. using various hints | |
| 28 // like TLD, the UI language/default encoding of a client, etc). In that case, | |
| 29 // this should be pulled out of here and moved somewhere in base because there | |
| 30 // can be other use cases. | |
| 31 std::string DetectEncoding(const char* input, size_t len) { | |
| 32 if (IsStringASCII(std::string(input, len))) | |
| 33 return std::string(); | |
| 34 UErrorCode status = U_ZERO_ERROR; | |
| 35 UCharsetDetector* detector = ucsdet_open(&status); | |
| 36 ucsdet_setText(detector, input, static_cast<int32_t>(len), &status); | |
| 37 const UCharsetMatch* match = ucsdet_detect(detector, &status); | |
| 38 const char* encoding = ucsdet_getName(match, &status); | |
| 39 // Should we check the quality of the match? A rather arbitrary number is | |
| 40 // assigned by ICU and it's hard to come up with a lower limit. | |
| 41 if (U_FAILURE(status)) | |
| 42 return std::string(); | |
| 43 return encoding; | |
| 44 } | |
| 45 | |
| 46 string16 RawByteSequenceToFilename(const char* raw_filename, | |
| 47 const std::string& encoding) { | |
| 48 if (encoding.empty()) | |
| 49 return ASCIIToUTF16(raw_filename); | |
| 50 | |
| 51 // Try the detected encoding before falling back to the native codepage. | |
| 52 // Using the native codepage does not make much sense, but we don't have | |
| 53 // much else to resort to. | |
| 54 string16 filename; | |
| 55 if (!CodepageToUTF16(raw_filename, encoding.c_str(), | |
| 56 OnStringUtilConversionError::SUBSTITUTE, &filename)) | |
| 57 filename = WideToUTF16Hack(base::SysNativeMBToWide(raw_filename)); | |
| 58 return filename; | |
| 59 } | |
| 60 | |
| 61 } // namespace | |
| 62 | 17 |
| 63 URLRequestNewFtpJob::URLRequestNewFtpJob(URLRequest* request) | 18 URLRequestNewFtpJob::URLRequestNewFtpJob(URLRequest* request) |
| 64 : URLRequestJob(request), | 19 : URLRequestJob(request), |
| 65 response_info_(NULL), | |
| 66 dir_listing_buf_size_(0), | |
| 67 ALLOW_THIS_IN_INITIALIZER_LIST( | 20 ALLOW_THIS_IN_INITIALIZER_LIST( |
| 68 start_callback_(this, &URLRequestNewFtpJob::OnStartCompleted)), | 21 start_callback_(this, &URLRequestNewFtpJob::OnStartCompleted)), |
| 69 ALLOW_THIS_IN_INITIALIZER_LIST( | 22 ALLOW_THIS_IN_INITIALIZER_LIST( |
| 70 read_callback_(this, &URLRequestNewFtpJob::OnReadCompleted)), | 23 read_callback_(this, &URLRequestNewFtpJob::OnReadCompleted)), |
| 71 read_in_progress_(false), | 24 read_in_progress_(false), |
| 72 context_(request->context()) { | 25 context_(request->context()) { |
| 73 } | 26 } |
| 74 | 27 |
| 75 URLRequestNewFtpJob::~URLRequestNewFtpJob() { | 28 URLRequestNewFtpJob::~URLRequestNewFtpJob() { |
| 76 } | 29 } |
| 77 | 30 |
| 78 // static | 31 // static |
| 79 URLRequestJob* URLRequestNewFtpJob::Factory(URLRequest* request, | 32 URLRequestJob* URLRequestNewFtpJob::Factory(URLRequest* request, |
| 80 const std::string& scheme) { | 33 const std::string& scheme) { |
| 81 DCHECK_EQ(scheme, "ftp"); | 34 DCHECK_EQ(scheme, "ftp"); |
| 82 | 35 |
| 83 int port = request->url().IntPort(); | 36 int port = request->url().IntPort(); |
| 84 if (request->url().has_port() && | 37 if (request->url().has_port() && |
| 85 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port)) | 38 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port)) |
| 86 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT); | 39 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT); |
| 87 | 40 |
| 88 DCHECK(request->context()); | 41 DCHECK(request->context()); |
| 89 DCHECK(request->context()->ftp_transaction_factory()); | 42 DCHECK(request->context()->ftp_transaction_factory()); |
| 90 return new URLRequestNewFtpJob(request); | 43 return new URLRequestNewFtpJob(request); |
| 91 } | 44 } |
| 92 | 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 |
| 93 void URLRequestNewFtpJob::Start() { | 54 void URLRequestNewFtpJob::Start() { |
| 94 DCHECK(!transaction_.get()); | 55 DCHECK(!transaction_.get()); |
| 95 request_info_.url = request_->url(); | 56 request_info_.url = request_->url(); |
| 96 StartTransaction(); | 57 StartTransaction(); |
| 97 } | 58 } |
| 98 | 59 |
| 99 void URLRequestNewFtpJob::Kill() { | 60 void URLRequestNewFtpJob::Kill() { |
| 100 if (!transaction_.get()) | 61 if (!transaction_.get()) |
| 101 return; | 62 return; |
| 102 DestroyTransaction(); | 63 DestroyTransaction(); |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 152 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( | 113 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( |
| 153 this, &URLRequestNewFtpJob::OnStartCompleted, net::OK)); | 114 this, &URLRequestNewFtpJob::OnStartCompleted, net::OK)); |
| 154 } | 115 } |
| 155 | 116 |
| 156 bool URLRequestNewFtpJob::ReadRawData(net::IOBuffer* buf, | 117 bool URLRequestNewFtpJob::ReadRawData(net::IOBuffer* buf, |
| 157 int buf_size, | 118 int buf_size, |
| 158 int *bytes_read) { | 119 int *bytes_read) { |
| 159 DCHECK_NE(buf_size, 0); | 120 DCHECK_NE(buf_size, 0); |
| 160 DCHECK(bytes_read); | 121 DCHECK(bytes_read); |
| 161 DCHECK(!read_in_progress_); | 122 DCHECK(!read_in_progress_); |
| 162 if (response_info_ == NULL) { | |
| 163 response_info_ = transaction_->GetResponseInfo(); | |
| 164 if (response_info_->is_directory_listing) { | |
| 165 std::string escaped_path = | |
| 166 UnescapeURLComponent(request_->url().path(), | |
| 167 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); | |
| 168 string16 path_utf16; | |
| 169 // Per RFC 2640, FTP servers should use UTF-8 or its proper subset ASCII, | |
| 170 // but many old FTP servers use legacy encodings. Try UTF-8 first and | |
| 171 // detect the encoding. | |
| 172 if (IsStringUTF8(escaped_path)) { | |
| 173 path_utf16 = UTF8ToUTF16(escaped_path); | |
| 174 } else { | |
| 175 std::string encoding = DetectEncoding(escaped_path.c_str(), | |
| 176 escaped_path.size()); | |
| 177 // Try the detected encoding. If it fails, resort to the | |
| 178 // OS native encoding. | |
| 179 if (encoding.empty() || | |
| 180 !CodepageToUTF16(escaped_path, encoding.c_str(), | |
| 181 OnStringUtilConversionError::SUBSTITUTE, | |
| 182 &path_utf16)) | |
| 183 path_utf16 = WideToUTF16Hack(base::SysNativeMBToWide(escaped_path)); | |
| 184 } | |
| 185 | |
| 186 directory_html_ = net::GetDirectoryListingHeader(path_utf16); | |
| 187 // If this isn't top level directory (i.e. the path isn't "/",) | |
| 188 // add a link to the parent directory. | |
| 189 if (request_->url().path().length() > 1) | |
| 190 directory_html_.append( | |
| 191 net::GetDirectoryListingEntry(ASCIIToUTF16(".."), | |
| 192 std::string(), | |
| 193 false, 0, | |
| 194 base::Time())); | |
| 195 } | |
| 196 } | |
| 197 if (!directory_html_.empty()) { | |
| 198 size_t bytes_to_copy = std::min(static_cast<size_t>(buf_size), | |
| 199 directory_html_.size()); | |
| 200 memcpy(buf->data(), directory_html_.c_str(), bytes_to_copy); | |
| 201 *bytes_read = bytes_to_copy; | |
| 202 directory_html_.erase(0, bytes_to_copy); | |
| 203 return true; | |
| 204 } | |
| 205 | 123 |
| 206 int rv = transaction_->Read(buf, buf_size, &read_callback_); | 124 int rv = transaction_->Read(buf, buf_size, &read_callback_); |
| 207 if (rv >= 0) { | 125 if (rv >= 0) { |
| 208 if (response_info_->is_directory_listing) { | 126 *bytes_read = rv; |
| 209 *bytes_read = ProcessFtpDir(buf, buf_size, rv); | |
| 210 } else { | |
| 211 *bytes_read = rv; | |
| 212 } | |
| 213 return true; | 127 return true; |
| 214 } | 128 } |
| 215 | 129 |
| 216 if (response_info_->is_directory_listing) { | |
| 217 dir_listing_buf_ = buf; | |
| 218 dir_listing_buf_size_ = buf_size; | |
| 219 } | |
| 220 | |
| 221 if (rv == net::ERR_IO_PENDING) { | 130 if (rv == net::ERR_IO_PENDING) { |
| 222 read_in_progress_ = true; | 131 read_in_progress_ = true; |
| 223 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | 132 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); |
| 224 } else { | 133 } else { |
| 225 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); | 134 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); |
| 226 } | 135 } |
| 227 return false; | 136 return false; |
| 228 } | 137 } |
| 229 | 138 |
| 230 int URLRequestNewFtpJob::ProcessFtpDir(net::IOBuffer *buf, | |
| 231 int buf_size, | |
| 232 int bytes_read) { | |
| 233 std::string file_entry; | |
| 234 std::string line; | |
| 235 | |
| 236 // If all we've seen so far is ASCII, encoding_ is empty. Try to detect the | |
| 237 // encoding. We don't do the separate UTF-8 check here because the encoding | |
| 238 // detection with a longer chunk (as opposed to the relatively short path | |
| 239 // component of the url) is unlikely to mistake UTF-8 for a legacy encoding. | |
| 240 // If it turns out to be wrong, a separate UTF-8 check has to be added. | |
| 241 // | |
| 242 // TODO(jungshik): UTF-8 has to be 'enforced' without any heuristics when | |
| 243 // we're talking to an FTP server compliant to RFC 2640 (that is, its response | |
| 244 // to FEAT command includes 'UTF8'). | |
| 245 // See http://wiki.filezilla-project.org/Character_Set | |
| 246 if (encoding_.empty()) | |
| 247 encoding_ = DetectEncoding(buf->data(), bytes_read); | |
| 248 | |
| 249 int64 file_size; | |
| 250 std::istringstream iss(std::string(buf->data(), bytes_read)); | |
| 251 struct net::list_state state; | |
| 252 memset(&state, 0, sizeof(state)); | |
| 253 while (getline(iss, line)) { | |
| 254 struct net::list_result result; | |
| 255 std::replace(line.begin(), line.end(), '\r', '\0'); | |
| 256 int line_type = net::ParseFTPList(line.c_str(), &state, &result); | |
| 257 | |
| 258 // The original code assumed months are in range 0-11 (PRExplodedTime), | |
| 259 // but our Time class expects a 1-12 range. Adjust it here, because | |
| 260 // the third-party parsing code uses bit-shifting on the month, | |
| 261 // and it'd be too easy to break that logic. | |
| 262 result.fe_time.month++; | |
| 263 DCHECK_LE(1, result.fe_time.month); | |
| 264 DCHECK_GE(12, result.fe_time.month); | |
| 265 | |
| 266 switch (line_type) { | |
| 267 case 'd': // Directory entry. | |
| 268 file_entry.append(net::GetDirectoryListingEntry( | |
| 269 RawByteSequenceToFilename(result.fe_fname, encoding_), | |
| 270 result.fe_fname, true, 0, | |
| 271 base::Time::FromLocalExploded(result.fe_time))); | |
| 272 break; | |
| 273 case 'f': // File entry. | |
| 274 if (StringToInt64(result.fe_size, &file_size)) | |
| 275 file_entry.append(net::GetDirectoryListingEntry( | |
| 276 RawByteSequenceToFilename(result.fe_fname, encoding_), | |
| 277 result.fe_fname, false, file_size, | |
| 278 base::Time::FromLocalExploded(result.fe_time))); | |
| 279 break; | |
| 280 case 'l': { // Symlink entry. | |
| 281 std::string filename(result.fe_fname, result.fe_fnlen); | |
| 282 | |
| 283 // Parsers for styles 'U' and 'W' handle " -> " themselves. | |
| 284 if (state.lstyle != 'U' && state.lstyle != 'W') { | |
| 285 std::string::size_type offset = filename.find(" -> "); | |
| 286 if (offset != std::string::npos) | |
| 287 filename = filename.substr(0, offset); | |
| 288 } | |
| 289 | |
| 290 if (StringToInt64(result.fe_size, &file_size)) { | |
| 291 file_entry.append(net::GetDirectoryListingEntry( | |
| 292 RawByteSequenceToFilename(filename.c_str(), encoding_), | |
| 293 filename, false, file_size, | |
| 294 base::Time::FromLocalExploded(result.fe_time))); | |
| 295 } | |
| 296 } | |
| 297 break; | |
| 298 case '?': // Junk entry. | |
| 299 case '"': // Comment entry. | |
| 300 break; | |
| 301 default: | |
| 302 NOTREACHED(); | |
| 303 break; | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 // We can't recognize server type based on empty directory listings. Only log | |
| 308 // server type when we have enough data to recognize one. | |
| 309 if (state.parsed_one) | |
| 310 LogFtpServerType(state.lstyle); | |
| 311 | |
| 312 directory_html_.append(file_entry); | |
| 313 size_t bytes_to_copy = std::min(static_cast<size_t>(buf_size), | |
| 314 directory_html_.length()); | |
| 315 if (bytes_to_copy) { | |
| 316 memcpy(buf->data(), directory_html_.c_str(), bytes_to_copy); | |
| 317 directory_html_.erase(0, bytes_to_copy); | |
| 318 } | |
| 319 return bytes_to_copy; | |
| 320 } | |
| 321 | |
| 322 void URLRequestNewFtpJob::LogFtpServerType(char server_type) { | |
| 323 switch (server_type) { | |
| 324 case 'E': | |
| 325 net::UpdateFtpServerTypeHistograms(net::SERVER_EPLF); | |
| 326 break; | |
| 327 case 'V': | |
| 328 net::UpdateFtpServerTypeHistograms(net::SERVER_VMS); | |
| 329 break; | |
| 330 case 'C': | |
| 331 net::UpdateFtpServerTypeHistograms(net::SERVER_CMS); | |
| 332 break; | |
| 333 case 'W': | |
| 334 net::UpdateFtpServerTypeHistograms(net::SERVER_DOS); | |
| 335 break; | |
| 336 case 'O': | |
| 337 net::UpdateFtpServerTypeHistograms(net::SERVER_OS2); | |
| 338 break; | |
| 339 case 'U': | |
| 340 net::UpdateFtpServerTypeHistograms(net::SERVER_LSL); | |
| 341 break; | |
| 342 case 'w': | |
| 343 net::UpdateFtpServerTypeHistograms(net::SERVER_W16); | |
| 344 break; | |
| 345 case 'D': | |
| 346 net::UpdateFtpServerTypeHistograms(net::SERVER_DLS); | |
| 347 break; | |
| 348 default: | |
| 349 net::UpdateFtpServerTypeHistograms(net::SERVER_UNKNOWN); | |
| 350 break; | |
| 351 } | |
| 352 } | |
| 353 | |
| 354 void URLRequestNewFtpJob::OnStartCompleted(int result) { | 139 void URLRequestNewFtpJob::OnStartCompleted(int result) { |
| 355 // If the request was destroyed, then there is no more work to do. | 140 // If the request was destroyed, then there is no more work to do. |
| 356 if (!request_ || !request_->delegate()) | 141 if (!request_ || !request_->delegate()) |
| 357 return; | 142 return; |
| 358 // If the transaction was destroyed, then the job was cancelled, and | 143 // If the transaction was destroyed, then the job was cancelled, and |
| 359 // we can just ignore this notification. | 144 // we can just ignore this notification. |
| 360 if (!transaction_.get()) | 145 if (!transaction_.get()) |
| 361 return; | 146 return; |
| 362 // Clear the IO_PENDING status | 147 // Clear the IO_PENDING status |
| 363 SetStatus(URLRequestStatus()); | 148 SetStatus(URLRequestStatus()); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 389 } | 174 } |
| 390 } | 175 } |
| 391 | 176 |
| 392 void URLRequestNewFtpJob::OnReadCompleted(int result) { | 177 void URLRequestNewFtpJob::OnReadCompleted(int result) { |
| 393 read_in_progress_ = false; | 178 read_in_progress_ = false; |
| 394 if (result == 0) { | 179 if (result == 0) { |
| 395 NotifyDone(URLRequestStatus()); | 180 NotifyDone(URLRequestStatus()); |
| 396 } else if (result < 0) { | 181 } else if (result < 0) { |
| 397 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); | 182 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); |
| 398 } else { | 183 } else { |
| 399 // TODO(ibrar): find the best place to delete dir_listing_buf_ | |
| 400 // Filter for Directory listing. | |
| 401 if (response_info_->is_directory_listing) | |
| 402 result = ProcessFtpDir(dir_listing_buf_, dir_listing_buf_size_, result); | |
| 403 | |
| 404 // Clear the IO_PENDING status | 184 // Clear the IO_PENDING status |
| 405 SetStatus(URLRequestStatus()); | 185 SetStatus(URLRequestStatus()); |
| 406 } | 186 } |
| 407 NotifyReadComplete(result); | 187 NotifyReadComplete(result); |
| 408 } | 188 } |
| 409 | 189 |
| 410 void URLRequestNewFtpJob::RestartTransactionWithAuth() { | 190 void URLRequestNewFtpJob::RestartTransactionWithAuth() { |
| 411 DCHECK(server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH); | 191 DCHECK(server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH); |
| 412 | 192 |
| 413 response_info_ = NULL; | |
| 414 | |
| 415 // No matter what, we want to report our status as IO pending since we will | 193 // No matter what, we want to report our status as IO pending since we will |
| 416 // be notifying our consumer asynchronously via OnStartCompleted. | 194 // be notifying our consumer asynchronously via OnStartCompleted. |
| 417 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | 195 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); |
| 418 | 196 |
| 419 int rv = transaction_->RestartWithAuth(server_auth_->username, | 197 int rv = transaction_->RestartWithAuth(server_auth_->username, |
| 420 server_auth_->password, | 198 server_auth_->password, |
| 421 &start_callback_); | 199 &start_callback_); |
| 422 if (rv == net::ERR_IO_PENDING) | 200 if (rv == net::ERR_IO_PENDING) |
| 423 return; | 201 return; |
| 424 | 202 |
| (...skipping 25 matching lines...) Expand all Loading... |
| 450 // The transaction started synchronously, but we need to notify the | 228 // The transaction started synchronously, but we need to notify the |
| 451 // URLRequest delegate via the message loop. | 229 // URLRequest delegate via the message loop. |
| 452 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( | 230 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( |
| 453 this, &URLRequestNewFtpJob::OnStartCompleted, rv)); | 231 this, &URLRequestNewFtpJob::OnStartCompleted, rv)); |
| 454 } | 232 } |
| 455 | 233 |
| 456 void URLRequestNewFtpJob::DestroyTransaction() { | 234 void URLRequestNewFtpJob::DestroyTransaction() { |
| 457 DCHECK(transaction_.get()); | 235 DCHECK(transaction_.get()); |
| 458 | 236 |
| 459 transaction_.reset(); | 237 transaction_.reset(); |
| 460 response_info_ = NULL; | |
| 461 } | 238 } |
| OLD | NEW |