blob: df24eab5eb50ad6ff0dd5225ca139c3038e1be8a [file] [log] [blame]
// Copyright (c) 2006-2008 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/url_request/url_request_file_dir_job.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"
#include "net/url_request/url_request.h"
#if defined(OS_POSIX)
#include <sys/stat.h>
#endif
using std::string;
URLRequestFileDirJob::URLRequestFileDirJob(URLRequest* request,
const FilePath& dir_path)
: URLRequestJob(request),
dir_path_(dir_path),
canceled_(false),
list_complete_(false),
wrote_header_(false),
read_pending_(false),
read_buffer_(NULL),
read_buffer_length_(0) {
}
URLRequestFileDirJob::~URLRequestFileDirJob() {
DCHECK(read_pending_ == false);
DCHECK(lister_ == NULL);
}
void URLRequestFileDirJob::Start() {
// Start reading asynchronously so that all error reporting and data
// callbacks happen as they would for network requests.
MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
this, &URLRequestFileDirJob::StartAsync));
}
void URLRequestFileDirJob::StartAsync() {
DCHECK(!lister_);
// AddRef so that *this* cannot be destroyed while the lister_
// is trying to feed us data.
AddRef();
lister_ = new net::DirectoryLister(dir_path_, this);
lister_->Start();
NotifyHeadersComplete();
}
void URLRequestFileDirJob::Kill() {
if (canceled_)
return;
canceled_ = true;
// Don't call CloseLister or dispatch an error to the URLRequest because we
// want OnListDone to be called to also write the error to the output stream.
// OnListDone will notify the URLRequest at this time.
if (lister_)
lister_->Cancel();
}
bool URLRequestFileDirJob::ReadRawData(char* buf, int buf_size,
int *bytes_read) {
DCHECK(bytes_read);
*bytes_read = 0;
if (is_done())
return true;
if (FillReadBuffer(buf, buf_size, bytes_read))
return true;
// We are waiting for more data
read_pending_ = true;
read_buffer_ = buf;
read_buffer_length_ = buf_size;
SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
return false;
}
bool URLRequestFileDirJob::GetMimeType(string* mime_type) {
*mime_type = "text/html";
return true;
}
bool URLRequestFileDirJob::GetCharset(string* charset) {
// All the filenames are converted to UTF-8 before being added.
*charset = "utf-8";
return true;
}
void URLRequestFileDirJob::OnListFile(
const file_util::FileEnumerator::FindInfo& data) {
// We wait to write out the header until we get the first file, so that we
// can catch errors from DirectoryLister and show an error page.
if (!wrote_header_) {
#if defined(OS_WIN)
const std::string& title = WideToUTF8(dir_path_.value());
#elif defined(OS_POSIX)
const std::string& title = dir_path_.value();
#endif
data_.append(net::GetDirectoryListingHeader(title));
wrote_header_ = true;
}
#if defined(OS_WIN)
FILETIME local_time;
::FileTimeToLocalFileTime(&data.ftLastWriteTime, &local_time);
int64 size = (static_cast<unsigned __int64>(data.nFileSizeHigh) << 32) |
data.nFileSizeLow;
data_.append(net::GetDirectoryListingEntry(
WideToUTF8(data.cFileName),
(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false,
size,
base::Time::FromFileTime(local_time)));
#elif defined(OS_POSIX)
data_.append(net::GetDirectoryListingEntry(
data.filename.c_str(),
S_ISDIR(data.stat.st_mode),
data.stat.st_size,
base::Time::FromTimeT(data.stat.st_mtime)));
#endif
// TODO(darin): coalesce more?
CompleteRead();
}
void URLRequestFileDirJob::OnListDone(int error) {
CloseLister();
if (error) {
read_pending_ = false;
NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, error));
} else if (canceled_) {
read_pending_ = false;
NotifyCanceled();
} else {
list_complete_ = true;
CompleteRead();
}
Release(); // The Lister is finished; may delete *this*
}
void URLRequestFileDirJob::CloseLister() {
if (lister_) {
lister_->Cancel();
lister_->set_delegate(NULL);
lister_ = NULL;
}
}
bool URLRequestFileDirJob::FillReadBuffer(char *buf, int buf_size,
int *bytes_read) {
DCHECK(bytes_read);
*bytes_read = 0;
int count = std::min(buf_size, static_cast<int>(data_.size()));
if (count) {
memcpy(buf, &data_[0], count);
data_.erase(0, count);
*bytes_read = count;
return true;
} else if (list_complete_) {
// EOF
return true;
}
return false;
}
void URLRequestFileDirJob::CompleteRead() {
if (read_pending_) {
int bytes_read;
if (FillReadBuffer(read_buffer_, read_buffer_length_, &bytes_read)) {
// We completed the read, so reset the read buffer.
read_pending_ = false;
read_buffer_ = NULL;
read_buffer_length_ = 0;
SetStatus(URLRequestStatus());
NotifyReadComplete(bytes_read);
} else {
NOTREACHED();
// TODO: Better error code.
NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 0));
}
}
}
bool URLRequestFileDirJob::IsRedirectResponse(
GURL* location, int* http_status_code) {
// If the URL did not have a trailing slash, treat the response as a redirect
// to the URL with a trailing slash appended.
std::string path = request_->url().path();
if (path.empty() || (path[path.size() - 1] != '/')) {
// This happens when we discovered the file is a directory, so needs a
// slash at the end of the path.
std::string new_path = path;
new_path.push_back('/');
GURL::Replacements replacements;
replacements.SetPathStr(new_path);
*location = request_->url().ReplaceComponents(replacements);
*http_status_code = 301; // simulate a permanent redirect
return true;
}
return false;
}