blob: a173399575f364583a912a1042c6b98429e84ff2 [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_hprc.h"
#include "net/ftp/ftp_directory_listing_parser_ls.h"
#include "net/ftp/ftp_directory_listing_parser_mlsd.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 FtpDirectoryListingParserHprc(current_time));
parsers_.insert(new FtpDirectoryListingParserLs(current_time));
parsers_.insert(new FtpDirectoryListingParserMlsd());
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 = ExtractFullLinesFromBuffer();
if (rv != OK)
return rv;
}
return ParseLines();
}
int FtpDirectoryListingBuffer::ProcessRemainingData() {
int rv = ExtractFullLinesFromBuffer();
if (rv != OK)
return rv;
if (!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);
}
bool FtpDirectoryListingBuffer::ConvertToDetectedEncoding(
const std::string& from, string16* to) {
std::string encoding(encoding_.empty() ? "ascii" : encoding_);
return base::CodepageToUTF16(from, encoding.c_str(),
base::OnStringConversionError::FAIL, to);
}
int FtpDirectoryListingBuffer::ExtractFullLinesFromBuffer() {
if (encoding_.empty()) {
if (!base::DetectEncoding(buffer_, &encoding_))
return ERR_ENCODING_DETECTION_FAILED;
}
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 < buffer_.length(); ++i) {
if (buffer_[i] != '\n')
continue;
int line_length = i - cut_pos;
if (i >= 1 && buffer_[i - 1] == '\r')
line_length--;
std::string line(buffer_.substr(cut_pos, line_length));
cut_pos = i + 1;
string16 line_converted;
if (!ConvertToDetectedEncoding(line, &line_converted)) {
buffer_.erase(0, cut_pos);
return ERR_ENCODING_CONVERSION_FAILED;
}
lines_.push_back(line_converted);
}
buffer_.erase(0, cut_pos);
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