1// Copyright (c) 2010 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/ftp/ftp_network_transaction.h" 6 7#include "base/compiler_specific.h" 8#include "base/metrics/histogram.h" 9#include "base/string_number_conversions.h" 10#include "base/string_util.h" 11#include "base/utf_string_conversions.h" 12#include "net/base/address_list.h" 13#include "net/base/connection_type_histograms.h" 14#include "net/base/escape.h" 15#include "net/base/net_errors.h" 16#include "net/base/net_log.h" 17#include "net/base/net_util.h" 18#include "net/ftp/ftp_network_session.h" 19#include "net/ftp/ftp_request_info.h" 20#include "net/ftp/ftp_util.h" 21#include "net/socket/client_socket.h" 22#include "net/socket/client_socket_factory.h" 23 24// TODO(ibrar): Try to avoid sscanf. 25#if !defined(COMPILER_MSVC) 26#define sscanf_s sscanf 27#endif 28 29const char kCRLF[] = "\r\n"; 30 31const int kCtrlBufLen = 1024; 32 33namespace { 34 35// Returns true if |input| can be safely used as a part of FTP command. 36bool IsValidFTPCommandString(const std::string& input) { 37 // RFC 959 only allows ASCII strings, but at least Firefox can send non-ASCII 38 // characters in the command if the request path contains them. To be 39 // compatible, we do the same and allow non-ASCII characters in a command. 40 41 // Protect agains newline injection attack. 42 if (input.find_first_of("\r\n") != std::string::npos) 43 return false; 44 45 return true; 46} 47 48enum ErrorClass { 49 // The requested action was initiated. The client should expect another 50 // reply before issuing the next command. 51 ERROR_CLASS_INITIATED, 52 53 // The requested action has been successfully completed. 54 ERROR_CLASS_OK, 55 56 // The command has been accepted, but to complete the operation, more 57 // information must be sent by the client. 58 ERROR_CLASS_INFO_NEEDED, 59 60 // The command was not accepted and the requested action did not take place. 61 // This condition is temporary, and the client is encouraged to restart the 62 // command sequence. 63 ERROR_CLASS_TRANSIENT_ERROR, 64 65 // The command was not accepted and the requested action did not take place. 66 // This condition is rather permanent, and the client is discouraged from 67 // repeating the exact request. 68 ERROR_CLASS_PERMANENT_ERROR, 69}; 70 71// Returns the error class for given response code. Caller should ensure 72// that |response_code| is in range 100-599. 73ErrorClass GetErrorClass(int response_code) { 74 if (response_code >= 100 && response_code <= 199) 75 return ERROR_CLASS_INITIATED; 76 77 if (response_code >= 200 && response_code <= 299) 78 return ERROR_CLASS_OK; 79 80 if (response_code >= 300 && response_code <= 399) 81 return ERROR_CLASS_INFO_NEEDED; 82 83 if (response_code >= 400 && response_code <= 499) 84 return ERROR_CLASS_TRANSIENT_ERROR; 85 86 if (response_code >= 500 && response_code <= 599) 87 return ERROR_CLASS_PERMANENT_ERROR; 88 89 // We should not be called on invalid error codes. 90 NOTREACHED() << response_code; 91 return ERROR_CLASS_PERMANENT_ERROR; 92} 93 94// Returns network error code for received FTP |response_code|. 95int GetNetErrorCodeForFtpResponseCode(int response_code) { 96 switch (response_code) { 97 case 421: 98 return net::ERR_FTP_SERVICE_UNAVAILABLE; 99 case 426: 100 return net::ERR_FTP_TRANSFER_ABORTED; 101 case 450: 102 return net::ERR_FTP_FILE_BUSY; 103 case 500: 104 case 501: 105 return net::ERR_FTP_SYNTAX_ERROR; 106 case 502: 107 case 504: 108 return net::ERR_FTP_COMMAND_NOT_SUPPORTED; 109 case 503: 110 return net::ERR_FTP_BAD_COMMAND_SEQUENCE; 111 default: 112 return net::ERR_FTP_FAILED; 113 } 114} 115 116// From RFC 2428 Section 3: 117// The text returned in response to the EPSV command MUST be: 118// <some text> (<d><d><d><tcp-port><d>) 119// <d> is a delimiter character, ideally to be | 120bool ExtractPortFromEPSVResponse(const net::FtpCtrlResponse& response, 121 int* port) { 122 if (response.lines.size() != 1) 123 return false; 124 const char* ptr = response.lines[0].c_str(); 125 while (*ptr && *ptr != '(') 126 ++ptr; 127 if (!*ptr) 128 return false; 129 char sep = *(++ptr); 130 if (!sep || isdigit(sep) || *(++ptr) != sep || *(++ptr) != sep) 131 return false; 132 if (!isdigit(*(++ptr))) 133 return false; 134 *port = *ptr - '0'; 135 while (isdigit(*(++ptr))) { 136 *port *= 10; 137 *port += *ptr - '0'; 138 } 139 if (*ptr != sep) 140 return false; 141 142 return true; 143} 144 145// There are two way we can receive IP address and port. 146// (127,0,0,1,23,21) IP address and port encapsulated in (). 147// 127,0,0,1,23,21 IP address and port without (). 148// 149// See RFC 959, Section 4.1.2 150bool ExtractPortFromPASVResponse(const net::FtpCtrlResponse& response, 151 int* port) { 152 if (response.lines.size() != 1) 153 return false; 154 const char* ptr = response.lines[0].c_str(); 155 while (*ptr && *ptr != '(') // Try with bracket. 156 ++ptr; 157 if (*ptr) { 158 ++ptr; 159 } else { 160 ptr = response.lines[0].c_str(); // Try without bracket. 161 while (*ptr && *ptr != ',') 162 ++ptr; 163 while (*ptr && *ptr != ' ') 164 --ptr; 165 } 166 int i0, i1, i2, i3, p0, p1; 167 if (sscanf_s(ptr, "%d,%d,%d,%d,%d,%d", &i0, &i1, &i2, &i3, &p0, &p1) != 6) 168 return false; 169 170 // Ignore the IP address supplied in the response. We are always going 171 // to connect back to the same server to prevent FTP PASV port scanning. 172 *port = (p0 << 8) + p1; 173 return true; 174} 175 176} // namespace 177 178namespace net { 179 180FtpNetworkTransaction::FtpNetworkTransaction( 181 FtpNetworkSession* session, 182 ClientSocketFactory* socket_factory) 183 : command_sent_(COMMAND_NONE), 184 ALLOW_THIS_IN_INITIALIZER_LIST( 185 io_callback_(this, &FtpNetworkTransaction::OnIOComplete)), 186 user_callback_(NULL), 187 session_(session), 188 request_(NULL), 189 resolver_(session->host_resolver()), 190 read_ctrl_buf_(new IOBuffer(kCtrlBufLen)), 191 ctrl_response_buffer_(new FtpCtrlResponseBuffer()), 192 read_data_buf_len_(0), 193 last_error_(OK), 194 system_type_(SYSTEM_TYPE_UNKNOWN), 195 // Use image (binary) transfer by default. It should always work, 196 // whereas the ascii transfer may damage binary data. 197 data_type_(DATA_TYPE_IMAGE), 198 resource_type_(RESOURCE_TYPE_UNKNOWN), 199 use_epsv_(true), 200 data_connection_port_(0), 201 socket_factory_(socket_factory), 202 next_state_(STATE_NONE) { 203} 204 205FtpNetworkTransaction::~FtpNetworkTransaction() { 206} 207 208int FtpNetworkTransaction::Stop(int error) { 209 if (command_sent_ == COMMAND_QUIT) 210 return error; 211 212 next_state_ = STATE_CTRL_WRITE_QUIT; 213 last_error_ = error; 214 return OK; 215} 216 217int FtpNetworkTransaction::RestartIgnoringLastError( 218 CompletionCallback* callback) { 219 return ERR_NOT_IMPLEMENTED; 220} 221 222int FtpNetworkTransaction::Start(const FtpRequestInfo* request_info, 223 CompletionCallback* callback, 224 const BoundNetLog& net_log) { 225 net_log_ = net_log; 226 request_ = request_info; 227 228 if (request_->url.has_username()) { 229 GetIdentityFromURL(request_->url, &username_, &password_); 230 } else { 231 username_ = ASCIIToUTF16("anonymous"); 232 password_ = ASCIIToUTF16("chrome@example.com"); 233 } 234 235 DetectTypecode(); 236 237 next_state_ = STATE_CTRL_RESOLVE_HOST; 238 int rv = DoLoop(OK); 239 if (rv == ERR_IO_PENDING) 240 user_callback_ = callback; 241 return rv; 242} 243 244int FtpNetworkTransaction::RestartWithAuth(const string16& username, 245 const string16& password, 246 CompletionCallback* callback) { 247 ResetStateForRestart(); 248 249 username_ = username; 250 password_ = password; 251 252 next_state_ = STATE_CTRL_RESOLVE_HOST; 253 int rv = DoLoop(OK); 254 if (rv == ERR_IO_PENDING) 255 user_callback_ = callback; 256 return rv; 257} 258 259int FtpNetworkTransaction::Read(IOBuffer* buf, 260 int buf_len, 261 CompletionCallback* callback) { 262 DCHECK(buf); 263 DCHECK_GT(buf_len, 0); 264 265 read_data_buf_ = buf; 266 read_data_buf_len_ = buf_len; 267 268 next_state_ = STATE_DATA_READ; 269 int rv = DoLoop(OK); 270 if (rv == ERR_IO_PENDING) 271 user_callback_ = callback; 272 return rv; 273} 274 275const FtpResponseInfo* FtpNetworkTransaction::GetResponseInfo() const { 276 return &response_; 277} 278 279LoadState FtpNetworkTransaction::GetLoadState() const { 280 if (next_state_ == STATE_CTRL_RESOLVE_HOST_COMPLETE) 281 return LOAD_STATE_RESOLVING_HOST; 282 283 if (next_state_ == STATE_CTRL_CONNECT_COMPLETE || 284 next_state_ == STATE_DATA_CONNECT_COMPLETE) 285 return LOAD_STATE_CONNECTING; 286 287 if (next_state_ == STATE_DATA_READ_COMPLETE) 288 return LOAD_STATE_READING_RESPONSE; 289 290 if (command_sent_ == COMMAND_RETR && read_data_buf_.get()) 291 return LOAD_STATE_READING_RESPONSE; 292 293 if (command_sent_ == COMMAND_QUIT) 294 return LOAD_STATE_IDLE; 295 296 if (command_sent_ != COMMAND_NONE) 297 return LOAD_STATE_SENDING_REQUEST; 298 299 return LOAD_STATE_IDLE; 300} 301 302uint64 FtpNetworkTransaction::GetUploadProgress() const { 303 return 0; 304} 305 306void FtpNetworkTransaction::ResetStateForRestart() { 307 command_sent_ = COMMAND_NONE; 308 user_callback_ = NULL; 309 response_ = FtpResponseInfo(); 310 read_ctrl_buf_ = new IOBuffer(kCtrlBufLen); 311 ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer()); 312 read_data_buf_ = NULL; 313 read_data_buf_len_ = 0; 314 if (write_buf_) 315 write_buf_->SetOffset(0); 316 last_error_ = OK; 317 data_connection_port_ = 0; 318 ctrl_socket_.reset(); 319 data_socket_.reset(); 320 next_state_ = STATE_NONE; 321} 322 323void FtpNetworkTransaction::DoCallback(int rv) { 324 DCHECK(rv != ERR_IO_PENDING); 325 DCHECK(user_callback_); 326 327 // Since Run may result in Read being called, clear callback_ up front. 328 CompletionCallback* c = user_callback_; 329 user_callback_ = NULL; 330 c->Run(rv); 331} 332 333void FtpNetworkTransaction::OnIOComplete(int result) { 334 int rv = DoLoop(result); 335 if (rv != ERR_IO_PENDING) 336 DoCallback(rv); 337} 338 339int FtpNetworkTransaction::ProcessCtrlResponse() { 340 FtpCtrlResponse response = ctrl_response_buffer_->PopResponse(); 341 342 int rv = OK; 343 switch (command_sent_) { 344 case COMMAND_NONE: 345 // TODO(phajdan.jr): Check for errors in the welcome message. 346 next_state_ = STATE_CTRL_WRITE_USER; 347 break; 348 case COMMAND_USER: 349 rv = ProcessResponseUSER(response); 350 break; 351 case COMMAND_PASS: 352 rv = ProcessResponsePASS(response); 353 break; 354 case COMMAND_SYST: 355 rv = ProcessResponseSYST(response); 356 break; 357 case COMMAND_PWD: 358 rv = ProcessResponsePWD(response); 359 break; 360 case COMMAND_TYPE: 361 rv = ProcessResponseTYPE(response); 362 break; 363 case COMMAND_EPSV: 364 rv = ProcessResponseEPSV(response); 365 break; 366 case COMMAND_PASV: 367 rv = ProcessResponsePASV(response); 368 break; 369 case COMMAND_SIZE: 370 rv = ProcessResponseSIZE(response); 371 break; 372 case COMMAND_RETR: 373 rv = ProcessResponseRETR(response); 374 break; 375 case COMMAND_CWD: 376 rv = ProcessResponseCWD(response); 377 break; 378 case COMMAND_LIST: 379 rv = ProcessResponseLIST(response); 380 break; 381 case COMMAND_QUIT: 382 rv = ProcessResponseQUIT(response); 383 break; 384 default: 385 LOG(DFATAL) << "Unexpected value of command_sent_: " << command_sent_; 386 return ERR_UNEXPECTED; 387 } 388 389 // We may get multiple responses for some commands, 390 // see http://crbug.com/18036. 391 while (ctrl_response_buffer_->ResponseAvailable() && rv == OK) { 392 response = ctrl_response_buffer_->PopResponse(); 393 394 switch (command_sent_) { 395 case COMMAND_RETR: 396 rv = ProcessResponseRETR(response); 397 break; 398 case COMMAND_LIST: 399 rv = ProcessResponseLIST(response); 400 break; 401 default: 402 // Multiple responses for other commands are invalid. 403 return Stop(ERR_INVALID_RESPONSE); 404 } 405 } 406 407 return rv; 408} 409 410// Used to prepare and send FTP command. 411int FtpNetworkTransaction::SendFtpCommand(const std::string& command, 412 Command cmd) { 413 // If we send a new command when we still have unprocessed responses 414 // for previous commands, the response receiving code will have no way to know 415 // which responses are for which command. 416 DCHECK(!ctrl_response_buffer_->ResponseAvailable()); 417 418 DCHECK(!write_command_buf_); 419 DCHECK(!write_buf_); 420 421 if (!IsValidFTPCommandString(command)) { 422 // Callers should validate the command themselves and return a more specific 423 // error code. 424 NOTREACHED(); 425 return Stop(ERR_UNEXPECTED); 426 } 427 428 command_sent_ = cmd; 429 430 write_command_buf_ = new IOBufferWithSize(command.length() + 2); 431 write_buf_ = new DrainableIOBuffer(write_command_buf_, 432 write_command_buf_->size()); 433 memcpy(write_command_buf_->data(), command.data(), command.length()); 434 memcpy(write_command_buf_->data() + command.length(), kCRLF, 2); 435 436 next_state_ = STATE_CTRL_WRITE; 437 return OK; 438} 439 440std::string FtpNetworkTransaction::GetRequestPathForFtpCommand( 441 bool is_directory) const { 442 std::string path(current_remote_directory_); 443 if (request_->url.has_path()) { 444 std::string gurl_path(request_->url.path()); 445 446 // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path. 447 std::string::size_type pos = gurl_path.rfind(';'); 448 if (pos != std::string::npos) 449 gurl_path.resize(pos); 450 451 path.append(gurl_path); 452 } 453 // Make sure that if the path is expected to be a file, it won't end 454 // with a trailing slash. 455 if (!is_directory && path.length() > 1 && path[path.length() - 1] == '/') 456 path.erase(path.length() - 1); 457 UnescapeRule::Type unescape_rules = UnescapeRule::SPACES | 458 UnescapeRule::URL_SPECIAL_CHARS; 459 // This may unescape to non-ASCII characters, but we allow that. See the 460 // comment for IsValidFTPCommandString. 461 path = UnescapeURLComponent(path, unescape_rules); 462 463 if (system_type_ == SYSTEM_TYPE_VMS) { 464 if (is_directory) 465 path = FtpUtil::UnixDirectoryPathToVMS(path); 466 else 467 path = FtpUtil::UnixFilePathToVMS(path); 468 } 469 470 DCHECK(IsValidFTPCommandString(path)); 471 return path; 472} 473 474void FtpNetworkTransaction::DetectTypecode() { 475 if (!request_->url.has_path()) 476 return; 477 std::string gurl_path(request_->url.path()); 478 479 // Extract the typecode, see RFC 1738 section 3.2.2. FTP url-path. 480 std::string::size_type pos = gurl_path.rfind(';'); 481 if (pos == std::string::npos) 482 return; 483 std::string typecode_string(gurl_path.substr(pos)); 484 if (typecode_string == ";type=a") { 485 data_type_ = DATA_TYPE_ASCII; 486 resource_type_ = RESOURCE_TYPE_FILE; 487 } else if (typecode_string == ";type=i") { 488 data_type_ = DATA_TYPE_IMAGE; 489 resource_type_ = RESOURCE_TYPE_FILE; 490 } else if (typecode_string == ";type=d") { 491 resource_type_ = RESOURCE_TYPE_DIRECTORY; 492 } 493} 494 495int FtpNetworkTransaction::DoLoop(int result) { 496 DCHECK(next_state_ != STATE_NONE); 497 498 int rv = result; 499 do { 500 State state = next_state_; 501 next_state_ = STATE_NONE; 502 switch (state) { 503 case STATE_CTRL_RESOLVE_HOST: 504 DCHECK(rv == OK); 505 rv = DoCtrlResolveHost(); 506 break; 507 case STATE_CTRL_RESOLVE_HOST_COMPLETE: 508 rv = DoCtrlResolveHostComplete(rv); 509 break; 510 case STATE_CTRL_CONNECT: 511 DCHECK(rv == OK); 512 rv = DoCtrlConnect(); 513 break; 514 case STATE_CTRL_CONNECT_COMPLETE: 515 rv = DoCtrlConnectComplete(rv); 516 break; 517 case STATE_CTRL_READ: 518 DCHECK(rv == OK); 519 rv = DoCtrlRead(); 520 break; 521 case STATE_CTRL_READ_COMPLETE: 522 rv = DoCtrlReadComplete(rv); 523 break; 524 case STATE_CTRL_WRITE: 525 DCHECK(rv == OK); 526 rv = DoCtrlWrite(); 527 break; 528 case STATE_CTRL_WRITE_COMPLETE: 529 rv = DoCtrlWriteComplete(rv); 530 break; 531 case STATE_CTRL_WRITE_USER: 532 DCHECK(rv == OK); 533 rv = DoCtrlWriteUSER(); 534 break; 535 case STATE_CTRL_WRITE_PASS: 536 DCHECK(rv == OK); 537 rv = DoCtrlWritePASS(); 538 break; 539 case STATE_CTRL_WRITE_SYST: 540 DCHECK(rv == OK); 541 rv = DoCtrlWriteSYST(); 542 break; 543 case STATE_CTRL_WRITE_PWD: 544 DCHECK(rv == OK); 545 rv = DoCtrlWritePWD(); 546 break; 547 case STATE_CTRL_WRITE_TYPE: 548 DCHECK(rv == OK); 549 rv = DoCtrlWriteTYPE(); 550 break; 551 case STATE_CTRL_WRITE_EPSV: 552 DCHECK(rv == OK); 553 rv = DoCtrlWriteEPSV(); 554 break; 555 case STATE_CTRL_WRITE_PASV: 556 DCHECK(rv == OK); 557 rv = DoCtrlWritePASV(); 558 break; 559 case STATE_CTRL_WRITE_RETR: 560 DCHECK(rv == OK); 561 rv = DoCtrlWriteRETR(); 562 break; 563 case STATE_CTRL_WRITE_SIZE: 564 DCHECK(rv == OK); 565 rv = DoCtrlWriteSIZE(); 566 break; 567 case STATE_CTRL_WRITE_CWD: 568 DCHECK(rv == OK); 569 rv = DoCtrlWriteCWD(); 570 break; 571 case STATE_CTRL_WRITE_LIST: 572 DCHECK(rv == OK); 573 rv = DoCtrlWriteLIST(); 574 break; 575 case STATE_CTRL_WRITE_QUIT: 576 DCHECK(rv == OK); 577 rv = DoCtrlWriteQUIT(); 578 break; 579 case STATE_DATA_CONNECT: 580 DCHECK(rv == OK); 581 rv = DoDataConnect(); 582 break; 583 case STATE_DATA_CONNECT_COMPLETE: 584 rv = DoDataConnectComplete(rv); 585 break; 586 case STATE_DATA_READ: 587 DCHECK(rv == OK); 588 rv = DoDataRead(); 589 break; 590 case STATE_DATA_READ_COMPLETE: 591 rv = DoDataReadComplete(rv); 592 break; 593 default: 594 NOTREACHED() << "bad state"; 595 rv = ERR_UNEXPECTED; 596 break; 597 } 598 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); 599 return rv; 600} 601 602int FtpNetworkTransaction::DoCtrlResolveHost() { 603 next_state_ = STATE_CTRL_RESOLVE_HOST_COMPLETE; 604 605 HostResolver::RequestInfo info(HostPortPair::FromURL(request_->url)); 606 // No known referrer. 607 return resolver_.Resolve(info, &addresses_, &io_callback_, net_log_); 608} 609 610int FtpNetworkTransaction::DoCtrlResolveHostComplete(int result) { 611 if (result == OK) 612 next_state_ = STATE_CTRL_CONNECT; 613 return result; 614} 615 616int FtpNetworkTransaction::DoCtrlConnect() { 617 next_state_ = STATE_CTRL_CONNECT_COMPLETE; 618 ctrl_socket_.reset(socket_factory_->CreateTransportClientSocket( 619 addresses_, net_log_.net_log(), net_log_.source())); 620 return ctrl_socket_->Connect(&io_callback_); 621} 622 623int FtpNetworkTransaction::DoCtrlConnectComplete(int result) { 624 if (result == OK) { 625 // Put the peer's IP address and port into the response. 626 AddressList address; 627 result = ctrl_socket_->GetPeerAddress(&address); 628 if (result == OK) { 629 response_.socket_address = HostPortPair::FromAddrInfo(address.head()); 630 next_state_ = STATE_CTRL_READ; 631 } 632 } 633 return result; 634} 635 636int FtpNetworkTransaction::DoCtrlRead() { 637 next_state_ = STATE_CTRL_READ_COMPLETE; 638 return ctrl_socket_->Read( 639 read_ctrl_buf_, 640 kCtrlBufLen, 641 &io_callback_); 642} 643 644int FtpNetworkTransaction::DoCtrlReadComplete(int result) { 645 if (result == 0) { 646 // Some servers (for example Pure-FTPd) apparently close the control 647 // connection when anonymous login is not permitted. For more details 648 // see http://crbug.com/25023. 649 if (command_sent_ == COMMAND_USER && username_ == ASCIIToUTF16("anonymous")) 650 response_.needs_auth = true; 651 return Stop(ERR_EMPTY_RESPONSE); 652 } 653 if (result < 0) 654 return Stop(result); 655 656 ctrl_response_buffer_->ConsumeData(read_ctrl_buf_->data(), result); 657 658 if (!ctrl_response_buffer_->ResponseAvailable()) { 659 // Read more data from the control socket. 660 next_state_ = STATE_CTRL_READ; 661 return OK; 662 } 663 664 return ProcessCtrlResponse(); 665} 666 667int FtpNetworkTransaction::DoCtrlWrite() { 668 next_state_ = STATE_CTRL_WRITE_COMPLETE; 669 670 return ctrl_socket_->Write(write_buf_, 671 write_buf_->BytesRemaining(), 672 &io_callback_); 673} 674 675int FtpNetworkTransaction::DoCtrlWriteComplete(int result) { 676 if (result < 0) 677 return result; 678 679 write_buf_->DidConsume(result); 680 if (write_buf_->BytesRemaining() == 0) { 681 // Clear the write buffer. 682 write_buf_ = NULL; 683 write_command_buf_ = NULL; 684 685 next_state_ = STATE_CTRL_READ; 686 } else { 687 next_state_ = STATE_CTRL_WRITE; 688 } 689 return OK; 690} 691 692// FTP Commands and responses 693 694// USER Command. 695int FtpNetworkTransaction::DoCtrlWriteUSER() { 696 std::string command = "USER " + UTF16ToUTF8(username_); 697 698 if (!IsValidFTPCommandString(command)) 699 return Stop(ERR_MALFORMED_IDENTITY); 700 701 next_state_ = STATE_CTRL_READ; 702 return SendFtpCommand(command, COMMAND_USER); 703} 704 705int FtpNetworkTransaction::ProcessResponseUSER( 706 const FtpCtrlResponse& response) { 707 switch (GetErrorClass(response.status_code)) { 708 case ERROR_CLASS_OK: 709 next_state_ = STATE_CTRL_WRITE_SYST; 710 break; 711 case ERROR_CLASS_INFO_NEEDED: 712 next_state_ = STATE_CTRL_WRITE_PASS; 713 break; 714 case ERROR_CLASS_TRANSIENT_ERROR: 715 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 716 case ERROR_CLASS_PERMANENT_ERROR: 717 response_.needs_auth = true; 718 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 719 default: 720 NOTREACHED(); 721 return Stop(ERR_UNEXPECTED); 722 } 723 return OK; 724} 725 726// PASS command. 727int FtpNetworkTransaction::DoCtrlWritePASS() { 728 std::string command = "PASS " + UTF16ToUTF8(password_); 729 730 if (!IsValidFTPCommandString(command)) 731 return Stop(ERR_MALFORMED_IDENTITY); 732 733 next_state_ = STATE_CTRL_READ; 734 return SendFtpCommand(command, COMMAND_PASS); 735} 736 737int FtpNetworkTransaction::ProcessResponsePASS( 738 const FtpCtrlResponse& response) { 739 switch (GetErrorClass(response.status_code)) { 740 case ERROR_CLASS_OK: 741 next_state_ = STATE_CTRL_WRITE_SYST; 742 break; 743 case ERROR_CLASS_INFO_NEEDED: 744 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 745 case ERROR_CLASS_TRANSIENT_ERROR: 746 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 747 case ERROR_CLASS_PERMANENT_ERROR: 748 response_.needs_auth = true; 749 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 750 default: 751 NOTREACHED(); 752 return Stop(ERR_UNEXPECTED); 753 } 754 return OK; 755} 756 757// SYST command. 758int FtpNetworkTransaction::DoCtrlWriteSYST() { 759 std::string command = "SYST"; 760 next_state_ = STATE_CTRL_READ; 761 return SendFtpCommand(command, COMMAND_SYST); 762} 763 764int FtpNetworkTransaction::ProcessResponseSYST( 765 const FtpCtrlResponse& response) { 766 switch (GetErrorClass(response.status_code)) { 767 case ERROR_CLASS_INITIATED: 768 return Stop(ERR_INVALID_RESPONSE); 769 case ERROR_CLASS_OK: { 770 // All important info should be on the first line. 771 std::string line = response.lines[0]; 772 // The response should be ASCII, which allows us to do case-insensitive 773 // comparisons easily. If it is not ASCII, we leave the system type 774 // as unknown. 775 if (IsStringASCII(line)) { 776 line = StringToLowerASCII(line); 777 // The "magic" strings we test for below have been gathered by an 778 // empirical study. 779 if (line.find("l8") != std::string::npos || 780 line.find("unix") != std::string::npos || 781 line.find("bsd") != std::string::npos) { 782 system_type_ = SYSTEM_TYPE_UNIX; 783 } else if (line.find("win32") != std::string::npos || 784 line.find("windows") != std::string::npos) { 785 system_type_ = SYSTEM_TYPE_WINDOWS; 786 } else if (line.find("os/2") != std::string::npos) { 787 system_type_ = SYSTEM_TYPE_OS2; 788 } else if (line.find("vms") != std::string::npos) { 789 system_type_ = SYSTEM_TYPE_VMS; 790 } 791 } 792 next_state_ = STATE_CTRL_WRITE_PWD; 793 break; 794 } 795 case ERROR_CLASS_INFO_NEEDED: 796 return Stop(ERR_INVALID_RESPONSE); 797 case ERROR_CLASS_TRANSIENT_ERROR: 798 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 799 case ERROR_CLASS_PERMANENT_ERROR: 800 // Server does not recognize the SYST command so proceed. 801 next_state_ = STATE_CTRL_WRITE_PWD; 802 break; 803 default: 804 NOTREACHED(); 805 return Stop(ERR_UNEXPECTED); 806 } 807 return OK; 808} 809 810// PWD command. 811int FtpNetworkTransaction::DoCtrlWritePWD() { 812 std::string command = "PWD"; 813 next_state_ = STATE_CTRL_READ; 814 return SendFtpCommand(command, COMMAND_PWD); 815} 816 817int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) { 818 switch (GetErrorClass(response.status_code)) { 819 case ERROR_CLASS_INITIATED: 820 return Stop(ERR_INVALID_RESPONSE); 821 case ERROR_CLASS_OK: { 822 // The info we look for should be on the first line. 823 std::string line = response.lines[0]; 824 if (line.empty()) 825 return Stop(ERR_INVALID_RESPONSE); 826 std::string::size_type quote_pos = line.find('"'); 827 if (quote_pos != std::string::npos) { 828 line = line.substr(quote_pos + 1); 829 quote_pos = line.find('"'); 830 if (quote_pos == std::string::npos) 831 return Stop(ERR_INVALID_RESPONSE); 832 line = line.substr(0, quote_pos); 833 } 834 if (system_type_ == SYSTEM_TYPE_VMS) 835 line = FtpUtil::VMSPathToUnix(line); 836 if (line.length() && line[line.length() - 1] == '/') 837 line.erase(line.length() - 1); 838 current_remote_directory_ = line; 839 next_state_ = STATE_CTRL_WRITE_TYPE; 840 break; 841 } 842 case ERROR_CLASS_INFO_NEEDED: 843 return Stop(ERR_INVALID_RESPONSE); 844 case ERROR_CLASS_TRANSIENT_ERROR: 845 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 846 case ERROR_CLASS_PERMANENT_ERROR: 847 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 848 default: 849 NOTREACHED(); 850 return Stop(ERR_UNEXPECTED); 851 } 852 return OK; 853} 854 855// TYPE command. 856int FtpNetworkTransaction::DoCtrlWriteTYPE() { 857 std::string command = "TYPE "; 858 if (data_type_ == DATA_TYPE_ASCII) { 859 command += "A"; 860 } else if (data_type_ == DATA_TYPE_IMAGE) { 861 command += "I"; 862 } else { 863 NOTREACHED(); 864 return Stop(ERR_UNEXPECTED); 865 } 866 next_state_ = STATE_CTRL_READ; 867 return SendFtpCommand(command, COMMAND_TYPE); 868} 869 870int FtpNetworkTransaction::ProcessResponseTYPE( 871 const FtpCtrlResponse& response) { 872 switch (GetErrorClass(response.status_code)) { 873 case ERROR_CLASS_INITIATED: 874 return Stop(ERR_INVALID_RESPONSE); 875 case ERROR_CLASS_OK: 876 next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV; 877 break; 878 case ERROR_CLASS_INFO_NEEDED: 879 return Stop(ERR_INVALID_RESPONSE); 880 case ERROR_CLASS_TRANSIENT_ERROR: 881 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 882 case ERROR_CLASS_PERMANENT_ERROR: 883 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 884 default: 885 NOTREACHED(); 886 return Stop(ERR_UNEXPECTED); 887 } 888 return OK; 889} 890 891// EPSV command 892int FtpNetworkTransaction::DoCtrlWriteEPSV() { 893 const std::string command = "EPSV"; 894 next_state_ = STATE_CTRL_READ; 895 return SendFtpCommand(command, COMMAND_EPSV); 896} 897 898int FtpNetworkTransaction::ProcessResponseEPSV( 899 const FtpCtrlResponse& response) { 900 switch (GetErrorClass(response.status_code)) { 901 case ERROR_CLASS_INITIATED: 902 return Stop(ERR_INVALID_RESPONSE); 903 case ERROR_CLASS_OK: 904 if (!ExtractPortFromEPSVResponse( response, &data_connection_port_)) 905 return Stop(ERR_INVALID_RESPONSE); 906 if (data_connection_port_ < 1024 || 907 !IsPortAllowedByFtp(data_connection_port_)) 908 return Stop(ERR_UNSAFE_PORT); 909 next_state_ = STATE_DATA_CONNECT; 910 break; 911 case ERROR_CLASS_INFO_NEEDED: 912 return Stop(ERR_INVALID_RESPONSE); 913 case ERROR_CLASS_TRANSIENT_ERROR: 914 case ERROR_CLASS_PERMANENT_ERROR: 915 use_epsv_ = false; 916 next_state_ = STATE_CTRL_WRITE_PASV; 917 return OK; 918 default: 919 NOTREACHED(); 920 return Stop(ERR_UNEXPECTED); 921 } 922 return OK; 923} 924 925// PASV command 926int FtpNetworkTransaction::DoCtrlWritePASV() { 927 std::string command = "PASV"; 928 next_state_ = STATE_CTRL_READ; 929 return SendFtpCommand(command, COMMAND_PASV); 930} 931 932int FtpNetworkTransaction::ProcessResponsePASV( 933 const FtpCtrlResponse& response) { 934 switch (GetErrorClass(response.status_code)) { 935 case ERROR_CLASS_INITIATED: 936 return Stop(ERR_INVALID_RESPONSE); 937 case ERROR_CLASS_OK: 938 if (!ExtractPortFromPASVResponse(response, &data_connection_port_)) 939 return Stop(ERR_INVALID_RESPONSE); 940 if (data_connection_port_ < 1024 || 941 !IsPortAllowedByFtp(data_connection_port_)) 942 return Stop(ERR_UNSAFE_PORT); 943 next_state_ = STATE_DATA_CONNECT; 944 break; 945 case ERROR_CLASS_INFO_NEEDED: 946 return Stop(ERR_INVALID_RESPONSE); 947 case ERROR_CLASS_TRANSIENT_ERROR: 948 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 949 case ERROR_CLASS_PERMANENT_ERROR: 950 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 951 default: 952 NOTREACHED(); 953 return Stop(ERR_UNEXPECTED); 954 } 955 return OK; 956} 957 958// RETR command 959int FtpNetworkTransaction::DoCtrlWriteRETR() { 960 std::string command = "RETR " + GetRequestPathForFtpCommand(false); 961 next_state_ = STATE_CTRL_READ; 962 return SendFtpCommand(command, COMMAND_RETR); 963} 964 965int FtpNetworkTransaction::ProcessResponseRETR( 966 const FtpCtrlResponse& response) { 967 switch (GetErrorClass(response.status_code)) { 968 case ERROR_CLASS_INITIATED: 969 // We want the client to start reading the response at this point. 970 // It got here either through Start or RestartWithAuth. We want that 971 // method to complete. Not setting next state here will make DoLoop exit 972 // and in turn make Start/RestartWithAuth complete. 973 resource_type_ = RESOURCE_TYPE_FILE; 974 break; 975 case ERROR_CLASS_OK: 976 resource_type_ = RESOURCE_TYPE_FILE; 977 next_state_ = STATE_CTRL_WRITE_QUIT; 978 break; 979 case ERROR_CLASS_INFO_NEEDED: 980 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 981 case ERROR_CLASS_TRANSIENT_ERROR: 982 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 983 case ERROR_CLASS_PERMANENT_ERROR: 984 // Code 550 means "Failed to open file". Other codes are unrelated, 985 // like "Not logged in" etc. 986 if (response.status_code != 550 || resource_type_ == RESOURCE_TYPE_FILE) 987 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 988 989 // It's possible that RETR failed because the path is a directory. 990 resource_type_ = RESOURCE_TYPE_DIRECTORY; 991 992 // We're going to try CWD next, but first send a PASV one more time, 993 // because some FTP servers, including FileZilla, require that. 994 // See http://crbug.com/25316. 995 next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV; 996 break; 997 default: 998 NOTREACHED(); 999 return Stop(ERR_UNEXPECTED); 1000 } 1001 1002 // We should be sure about our resource type now. Otherwise we risk 1003 // an infinite loop (RETR can later send CWD, and CWD can later send RETR). 1004 DCHECK_NE(RESOURCE_TYPE_UNKNOWN, resource_type_); 1005 1006 return OK; 1007} 1008 1009// SIZE command 1010int FtpNetworkTransaction::DoCtrlWriteSIZE() { 1011 std::string command = "SIZE " + GetRequestPathForFtpCommand(false); 1012 next_state_ = STATE_CTRL_READ; 1013 return SendFtpCommand(command, COMMAND_SIZE); 1014} 1015 1016int FtpNetworkTransaction::ProcessResponseSIZE( 1017 const FtpCtrlResponse& response) { 1018 switch (GetErrorClass(response.status_code)) { 1019 case ERROR_CLASS_INITIATED: 1020 break; 1021 case ERROR_CLASS_OK: 1022 if (response.lines.size() != 1) 1023 return Stop(ERR_INVALID_RESPONSE); 1024 int64 size; 1025 if (!base::StringToInt64(response.lines[0], &size)) 1026 return Stop(ERR_INVALID_RESPONSE); 1027 if (size < 0) 1028 return Stop(ERR_INVALID_RESPONSE); 1029 1030 // A successful response to SIZE does not mean the resource is a file. 1031 // Some FTP servers (for example, the qnx one) send a SIZE even for 1032 // directories. 1033 response_.expected_content_size = size; 1034 break; 1035 case ERROR_CLASS_INFO_NEEDED: 1036 break; 1037 case ERROR_CLASS_TRANSIENT_ERROR: 1038 break; 1039 case ERROR_CLASS_PERMANENT_ERROR: 1040 // It's possible that SIZE failed because the path is a directory. 1041 if (resource_type_ == RESOURCE_TYPE_UNKNOWN && 1042 response.status_code != 550) { 1043 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 1044 } 1045 break; 1046 default: 1047 NOTREACHED(); 1048 return Stop(ERR_UNEXPECTED); 1049 } 1050 1051 if (resource_type_ == RESOURCE_TYPE_FILE) 1052 next_state_ = STATE_CTRL_WRITE_RETR; 1053 else 1054 next_state_ = STATE_CTRL_WRITE_CWD; 1055 1056 return OK; 1057} 1058 1059// CWD command 1060int FtpNetworkTransaction::DoCtrlWriteCWD() { 1061 std::string command = "CWD " + GetRequestPathForFtpCommand(true); 1062 next_state_ = STATE_CTRL_READ; 1063 return SendFtpCommand(command, COMMAND_CWD); 1064} 1065 1066int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) { 1067 // We should never issue CWD if we know the target resource is a file. 1068 DCHECK_NE(RESOURCE_TYPE_FILE, resource_type_); 1069 1070 switch (GetErrorClass(response.status_code)) { 1071 case ERROR_CLASS_INITIATED: 1072 return Stop(ERR_INVALID_RESPONSE); 1073 case ERROR_CLASS_OK: 1074 next_state_ = STATE_CTRL_WRITE_LIST; 1075 break; 1076 case ERROR_CLASS_INFO_NEEDED: 1077 return Stop(ERR_INVALID_RESPONSE); 1078 case ERROR_CLASS_TRANSIENT_ERROR: 1079 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 1080 case ERROR_CLASS_PERMANENT_ERROR: 1081 if (response.status_code == 550) { 1082 if (resource_type_ == RESOURCE_TYPE_DIRECTORY) { 1083 // We're assuming that the resource is a directory, but the server 1084 // says it's not true. The most probable interpretation is that it 1085 // doesn't exist (with FTP we can't be sure). 1086 return Stop(ERR_FILE_NOT_FOUND); 1087 } 1088 1089 // We are here because SIZE failed and we are not sure what the resource 1090 // type is. It could still be file, and SIZE could fail because of 1091 // an access error (http://crbug.com/56734). Try RETR just to be sure. 1092 resource_type_ = RESOURCE_TYPE_FILE; 1093 next_state_ = STATE_CTRL_WRITE_RETR; 1094 return OK; 1095 } 1096 1097 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 1098 default: 1099 NOTREACHED(); 1100 return Stop(ERR_UNEXPECTED); 1101 } 1102 return OK; 1103} 1104 1105// LIST command 1106int FtpNetworkTransaction::DoCtrlWriteLIST() { 1107 std::string command(system_type_ == SYSTEM_TYPE_VMS ? "LIST *.*;0" : "LIST"); 1108 next_state_ = STATE_CTRL_READ; 1109 return SendFtpCommand(command, COMMAND_LIST); 1110} 1111 1112int FtpNetworkTransaction::ProcessResponseLIST( 1113 const FtpCtrlResponse& response) { 1114 switch (GetErrorClass(response.status_code)) { 1115 case ERROR_CLASS_INITIATED: 1116 // We want the client to start reading the response at this point. 1117 // It got here either through Start or RestartWithAuth. We want that 1118 // method to complete. Not setting next state here will make DoLoop exit 1119 // and in turn make Start/RestartWithAuth complete. 1120 response_.is_directory_listing = true; 1121 break; 1122 case ERROR_CLASS_OK: 1123 response_.is_directory_listing = true; 1124 next_state_ = STATE_CTRL_WRITE_QUIT; 1125 break; 1126 case ERROR_CLASS_INFO_NEEDED: 1127 return Stop(ERR_INVALID_RESPONSE); 1128 case ERROR_CLASS_TRANSIENT_ERROR: 1129 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 1130 case ERROR_CLASS_PERMANENT_ERROR: 1131 return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); 1132 default: 1133 NOTREACHED(); 1134 return Stop(ERR_UNEXPECTED); 1135 } 1136 return OK; 1137} 1138 1139// QUIT command 1140int FtpNetworkTransaction::DoCtrlWriteQUIT() { 1141 std::string command = "QUIT"; 1142 next_state_ = STATE_CTRL_READ; 1143 return SendFtpCommand(command, COMMAND_QUIT); 1144} 1145 1146int FtpNetworkTransaction::ProcessResponseQUIT( 1147 const FtpCtrlResponse& response) { 1148 ctrl_socket_->Disconnect(); 1149 return last_error_; 1150} 1151 1152// Data Connection 1153 1154int FtpNetworkTransaction::DoDataConnect() { 1155 next_state_ = STATE_DATA_CONNECT_COMPLETE; 1156 AddressList data_address; 1157 // Connect to the same host as the control socket to prevent PASV port 1158 // scanning attacks. 1159 int rv = ctrl_socket_->GetPeerAddress(&data_address); 1160 if (rv != OK) 1161 return Stop(rv); 1162 data_address.SetPort(data_connection_port_); 1163 data_socket_.reset(socket_factory_->CreateTransportClientSocket( 1164 data_address, net_log_.net_log(), net_log_.source())); 1165 return data_socket_->Connect(&io_callback_); 1166} 1167 1168int FtpNetworkTransaction::DoDataConnectComplete(int result) { 1169 if (result != OK && use_epsv_) { 1170 // It's possible we hit a broken server, sadly. They can break in different 1171 // ways. Some time out, some reset a connection. Fall back to PASV. 1172 // TODO(phajdan.jr): remember it for future transactions with this server. 1173 // TODO(phajdan.jr): write a test for this code path. 1174 use_epsv_ = false; 1175 next_state_ = STATE_CTRL_WRITE_PASV; 1176 return OK; 1177 } 1178 1179 // Only record the connection error after we've applied all our fallbacks. 1180 // We want to capture the final error, one we're not going to recover from. 1181 RecordDataConnectionError(result); 1182 1183 if (result != OK) 1184 return Stop(result); 1185 1186 next_state_ = STATE_CTRL_WRITE_SIZE; 1187 return OK; 1188} 1189 1190int FtpNetworkTransaction::DoDataRead() { 1191 DCHECK(read_data_buf_); 1192 DCHECK_GT(read_data_buf_len_, 0); 1193 1194 if (data_socket_ == NULL || !data_socket_->IsConnected()) { 1195 // If we don't destroy the data socket completely, some servers will wait 1196 // for us (http://crbug.com/21127). The half-closed TCP connection needs 1197 // to be closed on our side too. 1198 data_socket_.reset(); 1199 1200 if (ctrl_socket_->IsConnected()) { 1201 // Wait for the server's response, we should get it before sending QUIT. 1202 next_state_ = STATE_CTRL_READ; 1203 return OK; 1204 } 1205 1206 // We are no longer connected to the server, so just finish the transaction. 1207 return Stop(OK); 1208 } 1209 1210 next_state_ = STATE_DATA_READ_COMPLETE; 1211 read_data_buf_->data()[0] = 0; 1212 return data_socket_->Read(read_data_buf_, read_data_buf_len_, 1213 &io_callback_); 1214} 1215 1216int FtpNetworkTransaction::DoDataReadComplete(int result) { 1217 return result; 1218} 1219 1220// We're using a histogram as a group of counters, with one bucket for each 1221// enumeration value. We're only interested in the values of the counters. 1222// Ignore the shape, average, and standard deviation of the histograms because 1223// they are meaningless. 1224// 1225// We use two histograms. In the first histogram we tally whether the user has 1226// seen an error of that type during the session. In the second histogram we 1227// tally the total number of times the users sees each errer. 1228void FtpNetworkTransaction::RecordDataConnectionError(int result) { 1229 // Gather data for http://crbug.com/3073. See how many users have trouble 1230 // establishing FTP data connection in passive FTP mode. 1231 enum { 1232 // Data connection successful. 1233 NET_ERROR_OK = 0, 1234 1235 // Local firewall blocked the connection. 1236 NET_ERROR_ACCESS_DENIED = 1, 1237 1238 // Connection timed out. 1239 NET_ERROR_TIMED_OUT = 2, 1240 1241 // Connection has been estabilished, but then got broken (either reset 1242 // or aborted). 1243 NET_ERROR_CONNECTION_BROKEN = 3, 1244 1245 // Connection has been refused. 1246 NET_ERROR_CONNECTION_REFUSED = 4, 1247 1248 // No connection to the internet. 1249 NET_ERROR_INTERNET_DISCONNECTED = 5, 1250 1251 // Could not reach the destination address. 1252 NET_ERROR_ADDRESS_UNREACHABLE = 6, 1253 1254 // A programming error in our network stack. 1255 NET_ERROR_UNEXPECTED = 7, 1256 1257 // Other kind of error. 1258 NET_ERROR_OTHER = 20, 1259 1260 NUM_OF_NET_ERROR_TYPES 1261 } type; 1262 switch (result) { 1263 case OK: 1264 type = NET_ERROR_OK; 1265 break; 1266 case ERR_ACCESS_DENIED: 1267 case ERR_NETWORK_ACCESS_DENIED: 1268 type = NET_ERROR_ACCESS_DENIED; 1269 break; 1270 case ERR_TIMED_OUT: 1271 type = NET_ERROR_TIMED_OUT; 1272 break; 1273 case ERR_CONNECTION_ABORTED: 1274 case ERR_CONNECTION_RESET: 1275 case ERR_CONNECTION_CLOSED: 1276 type = NET_ERROR_CONNECTION_BROKEN; 1277 break; 1278 case ERR_CONNECTION_FAILED: 1279 case ERR_CONNECTION_REFUSED: 1280 type = NET_ERROR_CONNECTION_REFUSED; 1281 break; 1282 case ERR_INTERNET_DISCONNECTED: 1283 type = NET_ERROR_INTERNET_DISCONNECTED; 1284 break; 1285 case ERR_ADDRESS_INVALID: 1286 case ERR_ADDRESS_UNREACHABLE: 1287 type = NET_ERROR_ADDRESS_UNREACHABLE; 1288 break; 1289 case ERR_UNEXPECTED: 1290 type = NET_ERROR_UNEXPECTED; 1291 break; 1292 default: 1293 type = NET_ERROR_OTHER; 1294 break; 1295 }; 1296 static bool had_error_type[NUM_OF_NET_ERROR_TYPES]; 1297 1298 DCHECK(type >= 0 && type < NUM_OF_NET_ERROR_TYPES); 1299 if (!had_error_type[type]) { 1300 had_error_type[type] = true; 1301 UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorHappened", 1302 type, NUM_OF_NET_ERROR_TYPES); 1303 } 1304 UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorCount", 1305 type, NUM_OF_NET_ERROR_TYPES); 1306} 1307 1308} // namespace net 1309