blob: 58533b81112702dad3acb3f1dfe296ccaaabb700 [file] [log] [blame]
// 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 "net/ftp/ftp_directory_listing_buffer.h"
#include "base/i18n/icu_encoding_detection.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
#include "net/ftp/ftp_directory_listing_parser_ls.h"
#include "net/ftp/ftp_directory_listing_parser_netware.h"
#include "net/ftp/ftp_directory_listing_parser_vms.h"
#include "net/ftp/ftp_directory_listing_parser_windows.h"
namespace net {
FtpDirectoryListingBuffer::FtpDirectoryListingBuffer(
const base::Time& current_time)
: current_parser_(NULL) {
parsers_.insert(new FtpDirectoryListingParserLs(current_time));
parsers_.insert(new FtpDirectoryListingParserNetware(current_time));
parsers_.insert(new FtpDirectoryListingParserVms());
parsers_.insert(new FtpDirectoryListingParserWindows());
}
FtpDirectoryListingBuffer::~FtpDirectoryListingBuffer() {
STLDeleteElements(&parsers_);
}
int FtpDirectoryListingBuffer::ConsumeData(const char* data, int data_length) {
buffer_.append(data, data_length);
if (!encoding_.empty() || buffer_.length() > 1024) {
int rv = ConsumeBuffer();
if (rv != OK)
return rv;
}
return ParseLines();
}
int FtpDirectoryListingBuffer::ProcessRemainingData() {
int rv = ConsumeBuffer();
if (rv != OK)
return rv;
DCHECK(buffer_.empty());
if (!converted_buffer_.empty())
return ERR_INVALID_RESPONSE;
rv = ParseLines();
if (rv != OK)
return rv;
rv = OnEndOfInput();
if (rv != OK)
return rv;
return OK;
}
bool FtpDirectoryListingBuffer::EntryAvailable() const {
return (current_parser_ ? current_parser_->EntryAvailable() : false);
}
FtpDirectoryListingEntry FtpDirectoryListingBuffer::PopEntry() {
DCHECK(EntryAvailable());
return current_parser_->PopEntry();
}
FtpServerType FtpDirectoryListingBuffer::GetServerType() const {
return (current_parser_ ? current_parser_->GetServerType() : SERVER_UNKNOWN);
}
int FtpDirectoryListingBuffer::DecodeBufferUsingEncoding(
const std::string& encoding) {
string16 converted;
if (!base::CodepageToUTF16(buffer_,
encoding.c_str(),
base::OnStringConversionError::FAIL,
&converted))
return ERR_ENCODING_CONVERSION_FAILED;
buffer_.clear();
converted_buffer_ += converted;
return OK;
}
int FtpDirectoryListingBuffer::ConvertBufferToUTF16() {
if (encoding_.empty()) {
std::vector<std::string> encodings;
if (!base::DetectAllEncodings(buffer_, &encodings))
return ERR_ENCODING_DETECTION_FAILED;
// Use first encoding that can be used to decode the buffer.
for (size_t i = 0; i < encodings.size(); i++) {
if (DecodeBufferUsingEncoding(encodings[i]) == OK) {
encoding_ = encodings[i];
return OK;
}
}
return ERR_ENCODING_DETECTION_FAILED;
}
return DecodeBufferUsingEncoding(encoding_);
}
void FtpDirectoryListingBuffer::ExtractFullLinesFromBuffer() {
int cut_pos = 0;
// TODO(phajdan.jr): This code accepts all endlines matching \r*\n. Should it
// be more strict, or enforce consistent line endings?
for (size_t i = 0; i < converted_buffer_.length(); ++i) {
if (converted_buffer_[i] != '\n')
continue;
int line_length = i - cut_pos;
if (i >= 1 && converted_buffer_[i - 1] == '\r')
line_length--;
lines_.push_back(converted_buffer_.substr(cut_pos, line_length));
cut_pos = i + 1;
}
converted_buffer_.erase(0, cut_pos);
}
int FtpDirectoryListingBuffer::ConsumeBuffer() {
int rv = ConvertBufferToUTF16();
if (rv != OK)
return rv;
ExtractFullLinesFromBuffer();
return OK;
}
int FtpDirectoryListingBuffer::ParseLines() {
while (!lines_.empty()) {
string16 line = lines_.front();
lines_.pop_front();
if (current_parser_) {
if (!current_parser_->ConsumeLine(line))
return ERR_FAILED;
} else {
ParserSet::iterator i = parsers_.begin();
while (i != parsers_.end()) {
if ((*i)->ConsumeLine(line)) {
i++;
} else {
delete *i;
parsers_.erase(i++);
}
}
if (parsers_.empty())
return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
if (parsers_.size() == 1)
current_parser_ = *parsers_.begin();
}
}
return OK;
}
int FtpDirectoryListingBuffer::OnEndOfInput() {
ParserSet::iterator i = parsers_.begin();
while (i != parsers_.end()) {
if ((*i)->OnEndOfInput()) {
i++;
} else {
delete *i;
parsers_.erase(i++);
}
}
if (parsers_.size() != 1) {
current_parser_ = NULL;
// We may hit an ambiguity in case of listings which have no entries. That's
// fine, as long as all remaining parsers agree that the listing is empty.
bool all_listings_empty = true;
for (ParserSet::iterator i = parsers_.begin(); i != parsers_.end(); ++i) {
if ((*i)->EntryAvailable())
all_listings_empty = false;
}
if (all_listings_empty)
return OK;
return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
}
current_parser_ = *parsers_.begin();
return OK;
}
} // namespace net