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.
4
5#include "net/ftp/ftp_directory_listing_parser.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/i18n/icu_encoding_detection.h"
10#include "base/i18n/icu_string_conversions.h"
11#include "base/stl_util.h"
12#include "base/strings/string_util.h"
13#include "base/strings/string_split.h"
14#include "base/strings/utf_string_conversions.h"
15#include "net/base/net_errors.h"
16#include "net/ftp/ftp_directory_listing_parser_ls.h"
17#include "net/ftp/ftp_directory_listing_parser_netware.h"
18#include "net/ftp/ftp_directory_listing_parser_os2.h"
19#include "net/ftp/ftp_directory_listing_parser_vms.h"
20#include "net/ftp/ftp_directory_listing_parser_windows.h"
21#include "net/ftp/ftp_server_type_histograms.h"
22
23namespace net {
24
25namespace {
26
27// Fills in |raw_name| for all |entries| using |encoding|. Returns network
28// error code.
29int FillInRawName(const std::string& encoding,
30                  std::vector<FtpDirectoryListingEntry>* entries) {
31  for (size_t i = 0; i < entries->size(); i++) {
32    if (!base::UTF16ToCodepage(entries->at(i).name, encoding.c_str(),
33                               base::OnStringConversionError::FAIL,
34                               &entries->at(i).raw_name)) {
35      return ERR_ENCODING_CONVERSION_FAILED;
36    }
37  }
38
39  return OK;
40}
41
42// Parses |text| as an FTP directory listing. Fills in |entries|
43// and |server_type| and returns network error code.
44int ParseListing(const base::string16& text,
45                 const base::string16& newline_separator,
46                 const std::string& encoding,
47                 const base::Time& current_time,
48                 std::vector<FtpDirectoryListingEntry>* entries,
49                 FtpServerType* server_type) {
50  std::vector<base::string16> lines;
51  base::SplitStringUsingSubstr(text, newline_separator, &lines);
52
53  struct {
54    base::Callback<bool(void)> callback;
55    FtpServerType server_type;
56  } parsers[] = {
57    {
58      base::Bind(&ParseFtpDirectoryListingLs, lines, current_time, entries),
59      SERVER_LS
60    },
61    {
62      base::Bind(&ParseFtpDirectoryListingWindows, lines, entries),
63      SERVER_WINDOWS
64    },
65    {
66      base::Bind(&ParseFtpDirectoryListingVms, lines, entries),
67      SERVER_VMS
68    },
69    {
70      base::Bind(&ParseFtpDirectoryListingNetware,
71                 lines, current_time, entries),
72      SERVER_NETWARE
73    },
74    {
75      base::Bind(&ParseFtpDirectoryListingOS2, lines, entries),
76      SERVER_OS2
77    }
78  };
79
80  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(parsers); i++) {
81    entries->clear();
82    if (parsers[i].callback.Run()) {
83      *server_type = parsers[i].server_type;
84      return FillInRawName(encoding, entries);
85    }
86  }
87
88  entries->clear();
89  return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
90}
91
92// Detects encoding of |text| and parses it as an FTP directory listing.
93// Fills in |entries| and |server_type| and returns network error code.
94int DecodeAndParse(const std::string& text,
95                   const base::Time& current_time,
96                   std::vector<FtpDirectoryListingEntry>* entries,
97                   FtpServerType* server_type) {
98  const char* kNewlineSeparators[] = { "\n", "\r\n" };
99
100  std::vector<std::string> encodings;
101  if (!base::DetectAllEncodings(text, &encodings))
102    return ERR_ENCODING_DETECTION_FAILED;
103
104  // Use first encoding that can be used to decode the text.
105  for (size_t i = 0; i < encodings.size(); i++) {
106    base::string16 converted_text;
107    if (base::CodepageToUTF16(text,
108                              encodings[i].c_str(),
109                              base::OnStringConversionError::FAIL,
110                              &converted_text)) {
111      for (size_t j = 0; j < arraysize(kNewlineSeparators); j++) {
112        int rv = ParseListing(converted_text,
113                              base::ASCIIToUTF16(kNewlineSeparators[j]),
114                              encodings[i],
115                              current_time,
116                              entries,
117                              server_type);
118        if (rv == OK)
119          return rv;
120      }
121    }
122  }
123
124  entries->clear();
125  *server_type = SERVER_UNKNOWN;
126  return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
127}
128
129}  // namespace
130
131FtpDirectoryListingEntry::FtpDirectoryListingEntry()
132    : type(UNKNOWN),
133      size(-1) {
134}
135
136int ParseFtpDirectoryListing(const std::string& text,
137                             const base::Time& current_time,
138                             std::vector<FtpDirectoryListingEntry>* entries) {
139  FtpServerType server_type = SERVER_UNKNOWN;
140  int rv = DecodeAndParse(text, current_time, entries, &server_type);
141  UpdateFtpServerTypeHistograms(server_type);
142  return rv;
143}
144
145}  // namespace net
146