OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |