Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(612)

Side by Side Diff: net/ftp/ftp_directory_listing_parser_ls.cc

Issue 6670085: FTP: Detect the character encoding only after the entire listing is received. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: update histograms for SERVER_UNKNOWN Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/ftp/ftp_directory_listing_parser_ls.h" 5 #include "net/ftp/ftp_directory_listing_parser_ls.h"
6 6
7 #include <vector> 7 #include <vector>
8 8
9 #include "base/string_number_conversions.h" 9 #include "base/string_number_conversions.h"
10 #include "base/string_split.h" 10 #include "base/string_split.h"
11 #include "base/string_util.h" 11 #include "base/string_util.h"
12 #include "base/time.h"
12 #include "base/utf_string_conversions.h" 13 #include "base/utf_string_conversions.h"
14 #include "net/ftp/ftp_directory_listing_parser.h"
13 #include "net/ftp/ftp_util.h" 15 #include "net/ftp/ftp_util.h"
14 16
15 namespace { 17 namespace {
16 18
17 bool LooksLikeUnixPermission(const string16& text) { 19 bool LooksLikeUnixPermission(const string16& text) {
18 if (text.length() != 3) 20 if (text.length() != 3)
19 return false; 21 return false;
20 22
21 // Meaning of the flags: 23 // Meaning of the flags:
22 // r - file is readable 24 // r - file is readable
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
93 } 95 }
94 96
95 // Unrecognized listing style. 97 // Unrecognized listing style.
96 return false; 98 return false;
97 } 99 }
98 100
99 } // namespace 101 } // namespace
100 102
101 namespace net { 103 namespace net {
102 104
103 FtpDirectoryListingParserLs::FtpDirectoryListingParserLs( 105 bool ParseFtpDirectoryListingLs(
104 const base::Time& current_time) 106 const std::vector<string16>& lines,
105 : received_total_line_(false), 107 const base::Time& current_time,
106 current_time_(current_time) { 108 std::vector<FtpDirectoryListingEntry>* entries) {
107 } 109 // True after we have received a "total n" listing header, where n is an
110 // integer. Only one such header is allowed per listing.
111 bool received_total_line = false;
108 112
109 FtpDirectoryListingParserLs::~FtpDirectoryListingParserLs() {} 113 for (size_t i = 0; i < lines.size(); i++) {
114 if (lines[i].empty())
115 continue;
110 116
111 FtpServerType FtpDirectoryListingParserLs::GetServerType() const { 117 std::vector<string16> columns;
112 return SERVER_LS; 118 base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
113 }
114 119
115 bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) { 120 // Some FTP servers put a "total n" line at the beginning of the listing
116 if (line.empty()) 121 // (n is an integer). Allow such a line, but only once, and only if it's
117 return true; 122 // the first non-empty line. Do not match the word exactly, because it may
123 // be in different languages (at least English and German have been seen
124 // in the field).
125 if (columns.size() == 2 && !received_total_line) {
126 received_total_line = true;
118 127
119 std::vector<string16> columns; 128 int total_number;
120 base::SplitString(CollapseWhitespace(line, false), ' ', &columns); 129 if (!base::StringToInt(columns[1], &total_number))
130 return false;
131 if (total_number < 0)
132 return false;
121 133
122 // Some FTP servers put a "total n" line at the beginning of the listing 134 continue;
123 // (n is an integer). Allow such a line, but only once, and only if it's 135 }
124 // the first non-empty line. Do not match the word exactly, because it may be
125 // in different languages (at least English and German have been seen in the
126 // field).
127 if (columns.size() == 2 && !received_total_line_) {
128 received_total_line_ = true;
129 136
130 int total_number; 137 int column_offset;
131 if (!base::StringToInt(columns[1], &total_number)) 138 if (!DetectColumnOffset(columns, current_time, &column_offset)) {
139 // If we can't recognize a normal listing line, maybe it's an error?
140 // In that case, just ignore the error, but still recognize the data
141 // as valid listing.
142 if (LooksLikePermissionDeniedError(lines[i]))
143 continue;
144
132 return false; 145 return false;
133 if (total_number < 0) 146 }
147
148 // We may receive file names containing spaces, which can make the number of
149 // columns arbitrarily large. We will handle that later. For now just make
150 // sure we have all the columns that should normally be there:
151 //
152 // 1. permission listing
153 // 2. number of links (optional)
154 // 3. owner name
155 // 4. group name (optional)
156 // 5. size in bytes
157 // 6. month
158 // 7. day of month
159 // 8. year or time
160 //
161 // The number of optional columns is stored in |column_offset|
162 // and is between 0 and 2 (inclusive).
163 if (columns.size() < 6U + column_offset)
134 return false; 164 return false;
135 165
136 return true; 166 if (!LooksLikeUnixPermissionsListing(columns[0]))
167 return false;
168
169 FtpDirectoryListingEntry entry;
170 if (columns[0][0] == 'l') {
171 entry.type = FtpDirectoryListingEntry::SYMLINK;
172 } else if (columns[0][0] == 'd') {
173 entry.type = FtpDirectoryListingEntry::DIRECTORY;
174 } else {
175 entry.type = FtpDirectoryListingEntry::FILE;
176 }
177
178 if (!base::StringToInt64(columns[2 + column_offset], &entry.size)) {
eroman 2011/03/24 23:09:35 I only glossed over this section quickly. I assume
Paweł Hajdan Jr. 2011/03/26 09:47:50 Yes, "ls -l" is just a move. VMS had some non-triv
179 // Some FTP servers do not separate owning group name from file size,
180 // like "group1234". We still want to display the file name for that
181 // entry, but can't really get the size (What if the group is named
182 // "group1", and the size is in fact 234? We can't distinguish between
183 // that and "group" with size 1234). Use a dummy value for the size.
184 // TODO(phajdan.jr): Use a value that means "unknown" instead of 0 bytes.
185 entry.size = 0;
186 }
187 if (entry.size < 0)
188 return false;
189 if (entry.type != FtpDirectoryListingEntry::FILE)
190 entry.size = -1;
191
192 if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset],
193 columns[4 + column_offset],
194 columns[5 + column_offset],
195 current_time,
196 &entry.last_modified)) {
197 return false;
198 }
199
200 entry.name = FtpUtil::GetStringPartAfterColumns(lines[i],
201 6 + column_offset);
202
203 if (entry.name.empty()) {
204 // Some FTP servers send listing entries with empty names.
205 // It's not obvious how to display such an entry, so ignore them.
206 // We don't want to make the parsing fail at this point though.
207 // Other entries can still be useful.
208 continue;
209 }
210
211 if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
212 string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
213
214 // We don't require the " -> " to be present. Some FTP servers don't send
215 // the symlink target, possibly for security reasons.
216 if (pos != string16::npos)
217 entry.name = entry.name.substr(0, pos);
218 }
219
220 entries->push_back(entry);
137 } 221 }
138 222
139 int column_offset;
140 if (!DetectColumnOffset(columns, current_time_, &column_offset)) {
141 // If we can't recognize a normal listing line, maybe it's an error?
142 // In that case, just ignore the error, but still recognize the data
143 // as valid listing.
144 return LooksLikePermissionDeniedError(line);
145 }
146
147 // We may receive file names containing spaces, which can make the number of
148 // columns arbitrarily large. We will handle that later. For now just make
149 // sure we have all the columns that should normally be there:
150 //
151 // 1. permission listing
152 // 2. number of links (optional)
153 // 3. owner name
154 // 4. group name (optional)
155 // 5. size in bytes
156 // 6. month
157 // 7. day of month
158 // 8. year or time
159 //
160 // The number of optional columns is stored in |column_offset|
161 // and is between 0 and 2 (inclusive).
162 if (columns.size() < 6U + column_offset)
163 return false;
164
165 if (!LooksLikeUnixPermissionsListing(columns[0]))
166 return false;
167
168 FtpDirectoryListingEntry entry;
169 if (columns[0][0] == 'l') {
170 entry.type = FtpDirectoryListingEntry::SYMLINK;
171 } else if (columns[0][0] == 'd') {
172 entry.type = FtpDirectoryListingEntry::DIRECTORY;
173 } else {
174 entry.type = FtpDirectoryListingEntry::FILE;
175 }
176
177 if (!base::StringToInt64(columns[2 + column_offset], &entry.size)) {
178 // Some FTP servers do not separate owning group name from file size,
179 // like "group1234". We still want to display the file name for that entry,
180 // but can't really get the size (What if the group is named "group1",
181 // and the size is in fact 234? We can't distinguish between that
182 // and "group" with size 1234). Use a dummy value for the size.
183 // TODO(phajdan.jr): Use a value that means "unknown" instead of 0 bytes.
184 entry.size = 0;
185 }
186 if (entry.size < 0)
187 return false;
188 if (entry.type != FtpDirectoryListingEntry::FILE)
189 entry.size = -1;
190
191 if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset],
192 columns[4 + column_offset],
193 columns[5 + column_offset],
194 current_time_,
195 &entry.last_modified)) {
196 return false;
197 }
198
199 entry.name = FtpUtil::GetStringPartAfterColumns(line, 6 + column_offset);
200
201 if (entry.name.empty()) {
202 // Some FTP servers send listing entries with empty names. It's not obvious
203 // how to display such an entry, so we ignore them. We don't want to make
204 // the parsing fail at this point though. Other entries can still be useful.
205 return true;
206 }
207
208 if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
209 string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
210
211 // We don't require the " -> " to be present. Some FTP servers don't send
212 // the symlink target, possibly for security reasons.
213 if (pos != string16::npos)
214 entry.name = entry.name.substr(0, pos);
215 }
216
217 entries_.push(entry);
218 return true; 223 return true;
219 } 224 }
220 225
221 bool FtpDirectoryListingParserLs::OnEndOfInput() {
222 return true;
223 }
224
225 bool FtpDirectoryListingParserLs::EntryAvailable() const {
226 return !entries_.empty();
227 }
228
229 FtpDirectoryListingEntry FtpDirectoryListingParserLs::PopEntry() {
230 FtpDirectoryListingEntry entry = entries_.front();
231 entries_.pop();
232 return entry;
233 }
234
235 } // namespace net 226 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698