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

Side by Side Diff: net/url_request/url_request_new_ftp_job.cc

Issue 210027: Move FTP LIST parsing code to the renderer process. (Closed)
Patch Set: fixes Created 11 years, 3 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
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2009 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/url_request/url_request_new_ftp_job.h" 5 #include "net/url_request/url_request_new_ftp_job.h"
6 6
7 #include "base/compiler_specific.h" 7 #include "base/compiler_specific.h"
8 #include "base/file_version_info.h"
9 #include "base/message_loop.h" 8 #include "base/message_loop.h"
10 #include "base/sys_string_conversions.h"
11 #include "net/base/auth.h" 9 #include "net/base/auth.h"
12 #include "net/base/escape.h"
13 #include "net/base/net_errors.h" 10 #include "net/base/net_errors.h"
14 #include "net/base/net_util.h" 11 #include "net/base/net_util.h"
15 #include "net/ftp/ftp_response_info.h" 12 #include "net/ftp/ftp_response_info.h"
16 #include "net/ftp/ftp_server_type_histograms.h"
17 #include "net/ftp/ftp_transaction_factory.h" 13 #include "net/ftp/ftp_transaction_factory.h"
18 #include "net/third_party/parseftp/ParseFTPList.h"
19 #include "net/url_request/url_request.h" 14 #include "net/url_request/url_request.h"
20 #include "net/url_request/url_request_context.h" 15 #include "net/url_request/url_request_context.h"
21 #include "net/url_request/url_request_error_job.h" 16 #include "net/url_request/url_request_error_job.h"
22 #include "unicode/ucsdet.h"
23
24 namespace {
25
26 // A very simple-minded character encoding detection.
27 // TODO(jungshik): We can apply more heuristics here (e.g. using various hints
28 // like TLD, the UI language/default encoding of a client, etc). In that case,
29 // this should be pulled out of here and moved somewhere in base because there
30 // can be other use cases.
31 std::string DetectEncoding(const char* input, size_t len) {
32 if (IsStringASCII(std::string(input, len)))
33 return std::string();
34 UErrorCode status = U_ZERO_ERROR;
35 UCharsetDetector* detector = ucsdet_open(&status);
36 ucsdet_setText(detector, input, static_cast<int32_t>(len), &status);
37 const UCharsetMatch* match = ucsdet_detect(detector, &status);
38 const char* encoding = ucsdet_getName(match, &status);
39 // Should we check the quality of the match? A rather arbitrary number is
40 // assigned by ICU and it's hard to come up with a lower limit.
41 if (U_FAILURE(status))
42 return std::string();
43 return encoding;
44 }
45
46 string16 RawByteSequenceToFilename(const char* raw_filename,
47 const std::string& encoding) {
48 if (encoding.empty())
49 return ASCIIToUTF16(raw_filename);
50
51 // Try the detected encoding before falling back to the native codepage.
52 // Using the native codepage does not make much sense, but we don't have
53 // much else to resort to.
54 string16 filename;
55 if (!CodepageToUTF16(raw_filename, encoding.c_str(),
56 OnStringUtilConversionError::SUBSTITUTE, &filename))
57 filename = WideToUTF16Hack(base::SysNativeMBToWide(raw_filename));
58 return filename;
59 }
60
61 } // namespace
62 17
63 URLRequestNewFtpJob::URLRequestNewFtpJob(URLRequest* request) 18 URLRequestNewFtpJob::URLRequestNewFtpJob(URLRequest* request)
64 : URLRequestJob(request), 19 : URLRequestJob(request),
65 response_info_(NULL),
66 dir_listing_buf_size_(0),
67 ALLOW_THIS_IN_INITIALIZER_LIST( 20 ALLOW_THIS_IN_INITIALIZER_LIST(
68 start_callback_(this, &URLRequestNewFtpJob::OnStartCompleted)), 21 start_callback_(this, &URLRequestNewFtpJob::OnStartCompleted)),
69 ALLOW_THIS_IN_INITIALIZER_LIST( 22 ALLOW_THIS_IN_INITIALIZER_LIST(
70 read_callback_(this, &URLRequestNewFtpJob::OnReadCompleted)), 23 read_callback_(this, &URLRequestNewFtpJob::OnReadCompleted)),
71 read_in_progress_(false), 24 read_in_progress_(false),
72 context_(request->context()) { 25 context_(request->context()) {
73 } 26 }
74 27
75 URLRequestNewFtpJob::~URLRequestNewFtpJob() { 28 URLRequestNewFtpJob::~URLRequestNewFtpJob() {
76 } 29 }
77 30
78 // static 31 // static
79 URLRequestJob* URLRequestNewFtpJob::Factory(URLRequest* request, 32 URLRequestJob* URLRequestNewFtpJob::Factory(URLRequest* request,
80 const std::string& scheme) { 33 const std::string& scheme) {
81 DCHECK_EQ(scheme, "ftp"); 34 DCHECK_EQ(scheme, "ftp");
82 35
83 int port = request->url().IntPort(); 36 int port = request->url().IntPort();
84 if (request->url().has_port() && 37 if (request->url().has_port() &&
85 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port)) 38 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port))
86 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT); 39 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT);
87 40
88 DCHECK(request->context()); 41 DCHECK(request->context());
89 DCHECK(request->context()->ftp_transaction_factory()); 42 DCHECK(request->context()->ftp_transaction_factory());
90 return new URLRequestNewFtpJob(request); 43 return new URLRequestNewFtpJob(request);
91 } 44 }
92 45
46 bool URLRequestNewFtpJob::GetMimeType(std::string* mime_type) const {
47 if (transaction_->GetResponseInfo()->is_directory_listing) {
48 *mime_type = "text/vnd.chromium.ftp-dir";
49 return true;
50 }
51 return false;
52 }
53
93 void URLRequestNewFtpJob::Start() { 54 void URLRequestNewFtpJob::Start() {
94 DCHECK(!transaction_.get()); 55 DCHECK(!transaction_.get());
95 request_info_.url = request_->url(); 56 request_info_.url = request_->url();
96 StartTransaction(); 57 StartTransaction();
97 } 58 }
98 59
99 void URLRequestNewFtpJob::Kill() { 60 void URLRequestNewFtpJob::Kill() {
100 if (!transaction_.get()) 61 if (!transaction_.get())
101 return; 62 return;
102 DestroyTransaction(); 63 DestroyTransaction();
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
152 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 113 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
153 this, &URLRequestNewFtpJob::OnStartCompleted, net::OK)); 114 this, &URLRequestNewFtpJob::OnStartCompleted, net::OK));
154 } 115 }
155 116
156 bool URLRequestNewFtpJob::ReadRawData(net::IOBuffer* buf, 117 bool URLRequestNewFtpJob::ReadRawData(net::IOBuffer* buf,
157 int buf_size, 118 int buf_size,
158 int *bytes_read) { 119 int *bytes_read) {
159 DCHECK_NE(buf_size, 0); 120 DCHECK_NE(buf_size, 0);
160 DCHECK(bytes_read); 121 DCHECK(bytes_read);
161 DCHECK(!read_in_progress_); 122 DCHECK(!read_in_progress_);
162 if (response_info_ == NULL) {
163 response_info_ = transaction_->GetResponseInfo();
164 if (response_info_->is_directory_listing) {
165 std::string escaped_path =
166 UnescapeURLComponent(request_->url().path(),
167 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
168 string16 path_utf16;
169 // Per RFC 2640, FTP servers should use UTF-8 or its proper subset ASCII,
170 // but many old FTP servers use legacy encodings. Try UTF-8 first and
171 // detect the encoding.
172 if (IsStringUTF8(escaped_path)) {
173 path_utf16 = UTF8ToUTF16(escaped_path);
174 } else {
175 std::string encoding = DetectEncoding(escaped_path.c_str(),
176 escaped_path.size());
177 // Try the detected encoding. If it fails, resort to the
178 // OS native encoding.
179 if (encoding.empty() ||
180 !CodepageToUTF16(escaped_path, encoding.c_str(),
181 OnStringUtilConversionError::SUBSTITUTE,
182 &path_utf16))
183 path_utf16 = WideToUTF16Hack(base::SysNativeMBToWide(escaped_path));
184 }
185
186 directory_html_ = net::GetDirectoryListingHeader(path_utf16);
187 // If this isn't top level directory (i.e. the path isn't "/",)
188 // add a link to the parent directory.
189 if (request_->url().path().length() > 1)
190 directory_html_.append(
191 net::GetDirectoryListingEntry(ASCIIToUTF16(".."),
192 std::string(),
193 false, 0,
194 base::Time()));
195 }
196 }
197 if (!directory_html_.empty()) {
198 size_t bytes_to_copy = std::min(static_cast<size_t>(buf_size),
199 directory_html_.size());
200 memcpy(buf->data(), directory_html_.c_str(), bytes_to_copy);
201 *bytes_read = bytes_to_copy;
202 directory_html_.erase(0, bytes_to_copy);
203 return true;
204 }
205 123
206 int rv = transaction_->Read(buf, buf_size, &read_callback_); 124 int rv = transaction_->Read(buf, buf_size, &read_callback_);
207 if (rv >= 0) { 125 if (rv >= 0) {
208 if (response_info_->is_directory_listing) { 126 *bytes_read = rv;
209 *bytes_read = ProcessFtpDir(buf, buf_size, rv);
210 } else {
211 *bytes_read = rv;
212 }
213 return true; 127 return true;
214 } 128 }
215 129
216 if (response_info_->is_directory_listing) {
217 dir_listing_buf_ = buf;
218 dir_listing_buf_size_ = buf_size;
219 }
220
221 if (rv == net::ERR_IO_PENDING) { 130 if (rv == net::ERR_IO_PENDING) {
222 read_in_progress_ = true; 131 read_in_progress_ = true;
223 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 132 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
224 } else { 133 } else {
225 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); 134 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
226 } 135 }
227 return false; 136 return false;
228 } 137 }
229 138
230 int URLRequestNewFtpJob::ProcessFtpDir(net::IOBuffer *buf,
231 int buf_size,
232 int bytes_read) {
233 std::string file_entry;
234 std::string line;
235
236 // If all we've seen so far is ASCII, encoding_ is empty. Try to detect the
237 // encoding. We don't do the separate UTF-8 check here because the encoding
238 // detection with a longer chunk (as opposed to the relatively short path
239 // component of the url) is unlikely to mistake UTF-8 for a legacy encoding.
240 // If it turns out to be wrong, a separate UTF-8 check has to be added.
241 //
242 // TODO(jungshik): UTF-8 has to be 'enforced' without any heuristics when
243 // we're talking to an FTP server compliant to RFC 2640 (that is, its response
244 // to FEAT command includes 'UTF8').
245 // See http://wiki.filezilla-project.org/Character_Set
246 if (encoding_.empty())
247 encoding_ = DetectEncoding(buf->data(), bytes_read);
248
249 int64 file_size;
250 std::istringstream iss(std::string(buf->data(), bytes_read));
251 struct net::list_state state;
252 memset(&state, 0, sizeof(state));
253 while (getline(iss, line)) {
254 struct net::list_result result;
255 std::replace(line.begin(), line.end(), '\r', '\0');
256 int line_type = net::ParseFTPList(line.c_str(), &state, &result);
257
258 // The original code assumed months are in range 0-11 (PRExplodedTime),
259 // but our Time class expects a 1-12 range. Adjust it here, because
260 // the third-party parsing code uses bit-shifting on the month,
261 // and it'd be too easy to break that logic.
262 result.fe_time.month++;
263 DCHECK_LE(1, result.fe_time.month);
264 DCHECK_GE(12, result.fe_time.month);
265
266 switch (line_type) {
267 case 'd': // Directory entry.
268 file_entry.append(net::GetDirectoryListingEntry(
269 RawByteSequenceToFilename(result.fe_fname, encoding_),
270 result.fe_fname, true, 0,
271 base::Time::FromLocalExploded(result.fe_time)));
272 break;
273 case 'f': // File entry.
274 if (StringToInt64(result.fe_size, &file_size))
275 file_entry.append(net::GetDirectoryListingEntry(
276 RawByteSequenceToFilename(result.fe_fname, encoding_),
277 result.fe_fname, false, file_size,
278 base::Time::FromLocalExploded(result.fe_time)));
279 break;
280 case 'l': { // Symlink entry.
281 std::string filename(result.fe_fname, result.fe_fnlen);
282
283 // Parsers for styles 'U' and 'W' handle " -> " themselves.
284 if (state.lstyle != 'U' && state.lstyle != 'W') {
285 std::string::size_type offset = filename.find(" -> ");
286 if (offset != std::string::npos)
287 filename = filename.substr(0, offset);
288 }
289
290 if (StringToInt64(result.fe_size, &file_size)) {
291 file_entry.append(net::GetDirectoryListingEntry(
292 RawByteSequenceToFilename(filename.c_str(), encoding_),
293 filename, false, file_size,
294 base::Time::FromLocalExploded(result.fe_time)));
295 }
296 }
297 break;
298 case '?': // Junk entry.
299 case '"': // Comment entry.
300 break;
301 default:
302 NOTREACHED();
303 break;
304 }
305 }
306
307 // We can't recognize server type based on empty directory listings. Only log
308 // server type when we have enough data to recognize one.
309 if (state.parsed_one)
310 LogFtpServerType(state.lstyle);
311
312 directory_html_.append(file_entry);
313 size_t bytes_to_copy = std::min(static_cast<size_t>(buf_size),
314 directory_html_.length());
315 if (bytes_to_copy) {
316 memcpy(buf->data(), directory_html_.c_str(), bytes_to_copy);
317 directory_html_.erase(0, bytes_to_copy);
318 }
319 return bytes_to_copy;
320 }
321
322 void URLRequestNewFtpJob::LogFtpServerType(char server_type) {
323 switch (server_type) {
324 case 'E':
325 net::UpdateFtpServerTypeHistograms(net::SERVER_EPLF);
326 break;
327 case 'V':
328 net::UpdateFtpServerTypeHistograms(net::SERVER_VMS);
329 break;
330 case 'C':
331 net::UpdateFtpServerTypeHistograms(net::SERVER_CMS);
332 break;
333 case 'W':
334 net::UpdateFtpServerTypeHistograms(net::SERVER_DOS);
335 break;
336 case 'O':
337 net::UpdateFtpServerTypeHistograms(net::SERVER_OS2);
338 break;
339 case 'U':
340 net::UpdateFtpServerTypeHistograms(net::SERVER_LSL);
341 break;
342 case 'w':
343 net::UpdateFtpServerTypeHistograms(net::SERVER_W16);
344 break;
345 case 'D':
346 net::UpdateFtpServerTypeHistograms(net::SERVER_DLS);
347 break;
348 default:
349 net::UpdateFtpServerTypeHistograms(net::SERVER_UNKNOWN);
350 break;
351 }
352 }
353
354 void URLRequestNewFtpJob::OnStartCompleted(int result) { 139 void URLRequestNewFtpJob::OnStartCompleted(int result) {
355 // If the request was destroyed, then there is no more work to do. 140 // If the request was destroyed, then there is no more work to do.
356 if (!request_ || !request_->delegate()) 141 if (!request_ || !request_->delegate())
357 return; 142 return;
358 // If the transaction was destroyed, then the job was cancelled, and 143 // If the transaction was destroyed, then the job was cancelled, and
359 // we can just ignore this notification. 144 // we can just ignore this notification.
360 if (!transaction_.get()) 145 if (!transaction_.get())
361 return; 146 return;
362 // Clear the IO_PENDING status 147 // Clear the IO_PENDING status
363 SetStatus(URLRequestStatus()); 148 SetStatus(URLRequestStatus());
(...skipping 25 matching lines...) Expand all
389 } 174 }
390 } 175 }
391 176
392 void URLRequestNewFtpJob::OnReadCompleted(int result) { 177 void URLRequestNewFtpJob::OnReadCompleted(int result) {
393 read_in_progress_ = false; 178 read_in_progress_ = false;
394 if (result == 0) { 179 if (result == 0) {
395 NotifyDone(URLRequestStatus()); 180 NotifyDone(URLRequestStatus());
396 } else if (result < 0) { 181 } else if (result < 0) {
397 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); 182 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
398 } else { 183 } else {
399 // TODO(ibrar): find the best place to delete dir_listing_buf_
400 // Filter for Directory listing.
401 if (response_info_->is_directory_listing)
402 result = ProcessFtpDir(dir_listing_buf_, dir_listing_buf_size_, result);
403
404 // Clear the IO_PENDING status 184 // Clear the IO_PENDING status
405 SetStatus(URLRequestStatus()); 185 SetStatus(URLRequestStatus());
406 } 186 }
407 NotifyReadComplete(result); 187 NotifyReadComplete(result);
408 } 188 }
409 189
410 void URLRequestNewFtpJob::RestartTransactionWithAuth() { 190 void URLRequestNewFtpJob::RestartTransactionWithAuth() {
411 DCHECK(server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH); 191 DCHECK(server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH);
412 192
413 response_info_ = NULL;
414
415 // No matter what, we want to report our status as IO pending since we will 193 // No matter what, we want to report our status as IO pending since we will
416 // be notifying our consumer asynchronously via OnStartCompleted. 194 // be notifying our consumer asynchronously via OnStartCompleted.
417 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 195 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
418 196
419 int rv = transaction_->RestartWithAuth(server_auth_->username, 197 int rv = transaction_->RestartWithAuth(server_auth_->username,
420 server_auth_->password, 198 server_auth_->password,
421 &start_callback_); 199 &start_callback_);
422 if (rv == net::ERR_IO_PENDING) 200 if (rv == net::ERR_IO_PENDING)
423 return; 201 return;
424 202
(...skipping 25 matching lines...) Expand all
450 // The transaction started synchronously, but we need to notify the 228 // The transaction started synchronously, but we need to notify the
451 // URLRequest delegate via the message loop. 229 // URLRequest delegate via the message loop.
452 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 230 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
453 this, &URLRequestNewFtpJob::OnStartCompleted, rv)); 231 this, &URLRequestNewFtpJob::OnStartCompleted, rv));
454 } 232 }
455 233
456 void URLRequestNewFtpJob::DestroyTransaction() { 234 void URLRequestNewFtpJob::DestroyTransaction() {
457 DCHECK(transaction_.get()); 235 DCHECK(transaction_.get());
458 236
459 transaction_.reset(); 237 transaction_.reset();
460 response_info_ = NULL;
461 } 238 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698