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