diff options
Diffstat (limited to 'webkit')
-rw-r--r-- | webkit/glue/DEPS | 3 | ||||
-rw-r--r-- | webkit/glue/ftp_directory_listing_response_delegate.cc | 253 | ||||
-rw-r--r-- | webkit/glue/ftp_directory_listing_response_delegate.h | 62 | ||||
-rw-r--r-- | webkit/glue/weburlloader_impl.cc | 24 | ||||
-rw-r--r-- | webkit/webkit.gyp | 2 |
5 files changed, 339 insertions, 5 deletions
diff --git a/webkit/glue/DEPS b/webkit/glue/DEPS index 1129c20..89a1131 100644 --- a/webkit/glue/DEPS +++ b/webkit/glue/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+media", + "+net/third_party/parseftp", "+skia/ext", "+skia/include", "+webkit/tools/test_shell", # Needed for test shell tests. @@ -7,7 +8,7 @@ include_rules = [ # This is not actually a directory, but npruntime_util.cc includes a file # from WebKit starting with this path in JSCore mode. "+bindings/c", - + # FIXME - refactor code and remove these dependencies "+chrome/browser", "+chrome/common", diff --git a/webkit/glue/ftp_directory_listing_response_delegate.cc b/webkit/glue/ftp_directory_listing_response_delegate.cc new file mode 100644 index 0000000..a0ee89d --- /dev/null +++ b/webkit/glue/ftp_directory_listing_response_delegate.cc @@ -0,0 +1,253 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/ftp_directory_listing_response_delegate.h" + +#include <vector> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/time.h" +#include "net/base/escape.h" +#include "net/base/net_util.h" +#include "net/ftp/ftp_server_type_histograms.h" +#include "unicode/ucsdet.h" +#include "webkit/api/public/WebURL.h" +#include "webkit/api/public/WebURLLoaderClient.h" + +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLResponse; + +namespace { + +// A very simple-minded character encoding detection. +// TODO(jungshik): We can apply more heuristics here (e.g. using various hints +// like TLD, the UI language/default encoding of a client, etc). In that case, +// this should be pulled out of here and moved somewhere in base because there +// can be other use cases. +std::string DetectEncoding(const std::string& text) { + if (IsStringASCII(text)) + return std::string(); + UErrorCode status = U_ZERO_ERROR; + UCharsetDetector* detector = ucsdet_open(&status); + ucsdet_setText(detector, text.data(), static_cast<int32_t>(text.length()), + &status); + const UCharsetMatch* match = ucsdet_detect(detector, &status); + const char* encoding = ucsdet_getName(match, &status); + // Should we check the quality of the match? A rather arbitrary number is + // assigned by ICU and it's hard to come up with a lower limit. + if (U_FAILURE(status)) + return std::string(); + return encoding; +} + +string16 RawByteSequenceToFilename(const char* raw_filename, + const std::string& encoding) { + if (encoding.empty()) + return ASCIIToUTF16(raw_filename); + + // Try the detected encoding before falling back to the native codepage. + // Using the native codepage does not make much sense, but we don't have + // much else to resort to. + string16 filename; + if (!CodepageToUTF16(raw_filename, encoding.c_str(), + OnStringUtilConversionError::SUBSTITUTE, &filename)) + filename = WideToUTF16Hack(base::SysNativeMBToWide(raw_filename)); + return filename; +} + +void ExtractFullLinesFromBuffer(std::string* buffer, + std::vector<std::string>* lines) { + int cut_pos = 0; + for (size_t i = 0; i < buffer->length(); i++) { + if (i >= 1 && (*buffer)[i - 1] == '\r' && (*buffer)[i] == '\n') { + lines->push_back(buffer->substr(cut_pos, i - cut_pos - 1)); + cut_pos = i + 1; + } + } + buffer->erase(0, cut_pos); +} + +void LogFtpServerType(char server_type) { + switch (server_type) { + case 'E': + net::UpdateFtpServerTypeHistograms(net::SERVER_EPLF); + break; + case 'V': + net::UpdateFtpServerTypeHistograms(net::SERVER_VMS); + break; + case 'C': + net::UpdateFtpServerTypeHistograms(net::SERVER_CMS); + break; + case 'W': + net::UpdateFtpServerTypeHistograms(net::SERVER_DOS); + break; + case 'O': + net::UpdateFtpServerTypeHistograms(net::SERVER_OS2); + break; + case 'U': + net::UpdateFtpServerTypeHistograms(net::SERVER_LSL); + break; + case 'w': + net::UpdateFtpServerTypeHistograms(net::SERVER_W16); + break; + case 'D': + net::UpdateFtpServerTypeHistograms(net::SERVER_DLS); + break; + default: + net::UpdateFtpServerTypeHistograms(net::SERVER_UNKNOWN); + break; + } +} + +} // namespace + +namespace webkit_glue { + +FtpDirectoryListingResponseDelegate::FtpDirectoryListingResponseDelegate( + WebURLLoaderClient* client, + WebURLLoader* loader, + const WebURLResponse& response) + : client_(client), + loader_(loader), + original_response_(response) { + Init(); +} + +void FtpDirectoryListingResponseDelegate::OnReceivedData(const char* data, + int data_len) { + input_buffer_.append(data, data_len); + + // If all we've seen so far is ASCII, encoding_ is empty. Try to detect the + // encoding. We don't do the separate UTF-8 check here because the encoding + // detection with a longer chunk (as opposed to the relatively short path + // component of the url) is unlikely to mistake UTF-8 for a legacy encoding. + // If it turns out to be wrong, a separate UTF-8 check has to be added. + // + // TODO(jungshik): UTF-8 has to be 'enforced' without any heuristics when + // we're talking to an FTP server compliant to RFC 2640 (that is, its response + // to FEAT command includes 'UTF8'). + // See http://wiki.filezilla-project.org/Character_Set + if (encoding_.empty()) + encoding_ = DetectEncoding(input_buffer_); + + std::vector<std::string> lines; + ExtractFullLinesFromBuffer(&input_buffer_, &lines); + + for (std::vector<std::string>::const_iterator line = lines.begin(); + line != lines.end(); ++line) { + struct net::list_result result; + int line_type = net::ParseFTPList(line->c_str(), &parse_state_, &result); + + // The original code assumed months are in range 0-11 (PRExplodedTime), + // but our Time class expects a 1-12 range. Adjust it here, because + // the third-party parsing code uses bit-shifting on the month, + // and it'd be too easy to break that logic. + result.fe_time.month++; + DCHECK_LE(1, result.fe_time.month); + DCHECK_GE(12, result.fe_time.month); + + int64 file_size; + switch (line_type) { + case 'd': // Directory entry. + response_buffer_.append(net::GetDirectoryListingEntry( + RawByteSequenceToFilename(result.fe_fname, encoding_), + result.fe_fname, true, 0, + base::Time::FromLocalExploded(result.fe_time))); + break; + case 'f': // File entry. + if (StringToInt64(result.fe_size, &file_size)) { + response_buffer_.append(net::GetDirectoryListingEntry( + RawByteSequenceToFilename(result.fe_fname, encoding_), + result.fe_fname, false, file_size, + base::Time::FromLocalExploded(result.fe_time))); + } + break; + case 'l': { // Symlink entry. + std::string filename(result.fe_fname, result.fe_fnlen); + + // Parsers for styles 'U' and 'W' handle " -> " themselves. + if (parse_state_.lstyle != 'U' && parse_state_.lstyle != 'W') { + std::string::size_type offset = filename.find(" -> "); + if (offset != std::string::npos) + filename = filename.substr(0, offset); + } + + if (StringToInt64(result.fe_size, &file_size)) { + response_buffer_.append(net::GetDirectoryListingEntry( + RawByteSequenceToFilename(filename.c_str(), encoding_), + filename, false, file_size, + base::Time::FromLocalExploded(result.fe_time))); + } + } + break; + case '?': // Junk entry. + case '"': // Comment entry. + break; + default: + NOTREACHED(); + break; + } + } + + SendResponseBufferToClient(); +} + +void FtpDirectoryListingResponseDelegate::OnCompletedRequest() { + SendResponseBufferToClient(); + + // Only log the server type if we got enough data to reliably detect it. + if (parse_state_.parsed_one) + LogFtpServerType(parse_state_.lstyle); +} + +void FtpDirectoryListingResponseDelegate::Init() { + memset(&parse_state_, 0, sizeof(parse_state_)); + + GURL response_url(original_response_.url()); + UnescapeRule::Type unescape_rules = UnescapeRule::SPACES | + UnescapeRule::URL_SPECIAL_CHARS; + std::string unescaped_path = UnescapeURLComponent(response_url.path(), + unescape_rules); + string16 path_utf16; + // Per RFC 2640, FTP servers should use UTF-8 or its proper subset ASCII, + // but many old FTP servers use legacy encodings. Try UTF-8 first and + // detect the encoding. + if (IsStringUTF8(unescaped_path)) { + path_utf16 = UTF8ToUTF16(unescaped_path); + } else { + std::string encoding = DetectEncoding(unescaped_path); + // Try the detected encoding. If it fails, resort to the + // OS native encoding. + if (encoding.empty() || + !CodepageToUTF16(unescaped_path, encoding.c_str(), + OnStringUtilConversionError::SUBSTITUTE, + &path_utf16)) + path_utf16 = WideToUTF16Hack(base::SysNativeMBToWide(unescaped_path)); + } + + response_buffer_ = net::GetDirectoryListingHeader(path_utf16); + + // If this isn't top level directory (i.e. the path isn't "/",) + // add a link to the parent directory. + if (response_url.path().length() > 1) { + response_buffer_.append( + net::GetDirectoryListingEntry(ASCIIToUTF16(".."), + std::string(), + false, 0, + base::Time())); + } +} + +void FtpDirectoryListingResponseDelegate::SendResponseBufferToClient() { + if (!response_buffer_.empty()) { + client_->didReceiveData(loader_, response_buffer_.data(), + response_buffer_.length(), -1); + response_buffer_.clear(); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/ftp_directory_listing_response_delegate.h b/webkit/glue/ftp_directory_listing_response_delegate.h new file mode 100644 index 0000000..1ca42d7 --- /dev/null +++ b/webkit/glue/ftp_directory_listing_response_delegate.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A delegate class of WebURLLoaderImpl that handles text/vnd.chromium.ftp-dir +// data. + +#ifndef WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ +#define WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ + +#include <string> + +#include "net/third_party/parseftp/ParseFTPList.h" +#include "webkit/api/public/WebURLResponse.h" + +namespace WebKit { +class WebURLLoader; +class WebURLLoaderClient; +} + +namespace webkit_glue { + +class FtpDirectoryListingResponseDelegate { + public: + FtpDirectoryListingResponseDelegate(WebKit::WebURLLoaderClient* client, + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + + // Passed through from ResourceHandleInternal + void OnReceivedData(const char* data, int data_len); + void OnCompletedRequest(); + + private: + void Init(); + + void SendResponseBufferToClient(); + + // Pointers to the client and associated loader so we can make callbacks as + // we parse pieces of data. + WebKit::WebURLLoaderClient* client_; + WebKit::WebURLLoader* loader_; + + // The original resource response for this request. We use this as a + // starting point for each parts response. + WebKit::WebURLResponse original_response_; + + // State kept between parsing each line of the response. + struct net::list_state parse_state_; + + // Detected encoding of the response. + std::string encoding_; + + // Buffer to hold not-yet-parsed input. + std::string input_buffer_; + + // Buffer to hold response not-yet-sent to the caller. + std::string response_buffer_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ diff --git a/webkit/glue/weburlloader_impl.cc b/webkit/glue/weburlloader_impl.cc index b6d82c9..8884bdc 100644 --- a/webkit/glue/weburlloader_impl.cc +++ b/webkit/glue/weburlloader_impl.cc @@ -21,6 +21,7 @@ #include "webkit/api/public/WebURLLoaderClient.h" #include "webkit/api/public/WebURLRequest.h" #include "webkit/api/public/WebURLResponse.h" +#include "webkit/glue/ftp_directory_listing_response_delegate.h" #include "webkit/glue/glue_util.h" #include "webkit/glue/multipart_response_delegate.h" #include "webkit/glue/resource_loader_bridge.h" @@ -225,6 +226,7 @@ class WebURLLoaderImpl::Context : public base::RefCounted<Context>, WebURLRequest request_; WebURLLoaderClient* client_; scoped_ptr<ResourceLoaderBridge> bridge_; + scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_; scoped_ptr<MultipartResponseDelegate> multipart_delegate_; int64 expected_content_length_; }; @@ -427,13 +429,17 @@ void WebURLLoaderImpl::Context::OnReceivedResponse( expected_content_length_ = response.expectedContentLength(); + if (info.mime_type == "text/vnd.chromium.ftp-dir") + response.setMIMEType(WebString::fromUTF8("text/html")); + client_->didReceiveResponse(loader_, response); - // we may have been cancelled after didReceiveResponse, which would leave us - // without a client and therefore without much need to do multipart handling. + // We may have been cancelled after didReceiveResponse, which would leave us + // without a client and therefore without much need to do further handling. if (!client_) return; + DCHECK(!ftp_listing_delegate_.get()); DCHECK(!multipart_delegate_.get()); if (info.headers && info.mime_type == "multipart/x-mixed-replace") { std::string content_type; @@ -448,6 +454,9 @@ void WebURLLoaderImpl::Context::OnReceivedResponse( multipart_delegate_.reset( new MultipartResponseDelegate(client_, loader_, response, boundary)); } + } else if (info.mime_type == "text/vnd.chromium.ftp-dir") { + ftp_listing_delegate_.reset( + new FtpDirectoryListingResponseDelegate(client_, loader_, response)); } } @@ -455,7 +464,11 @@ void WebURLLoaderImpl::Context::OnReceivedData(const char* data, int len) { if (!client_) return; - if (multipart_delegate_.get()) { + if (ftp_listing_delegate_.get()) { + // The FTP listing delegate will make the appropriate calls to + // client_->didReceiveData and client_->didReceiveResponse. + ftp_listing_delegate_->OnReceivedData(data, len); + } else if (multipart_delegate_.get()) { // The multipart delegate will make the appropriate calls to // client_->didReceiveData and client_->didReceiveResponse. multipart_delegate_->OnReceivedData(data, len); @@ -467,7 +480,10 @@ void WebURLLoaderImpl::Context::OnReceivedData(const char* data, int len) { void WebURLLoaderImpl::Context::OnCompletedRequest( const URLRequestStatus& status, const std::string& security_info) { - if (multipart_delegate_.get()) { + if (ftp_listing_delegate_.get()) { + ftp_listing_delegate_->OnCompletedRequest(); + ftp_listing_delegate_.reset(NULL); + } else if (multipart_delegate_.get()) { multipart_delegate_->OnCompletedRequest(); multipart_delegate_.reset(NULL); } diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp index a0d73fe..8122d20 100644 --- a/webkit/webkit.gyp +++ b/webkit/webkit.gyp @@ -482,6 +482,8 @@ 'glue/feed_preview.cc', 'glue/feed_preview.h', 'glue/form_data.h', + 'glue/ftp_directory_listing_response_delegate.cc', + 'glue/ftp_directory_listing_response_delegate.h', 'glue/glue_accessibility_object.cc', 'glue/glue_accessibility_object.h', 'glue/glue_serialize.cc', |