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