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