Home | History | Annotate | Download | only in url_request
      1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/url_request/url_request_file_dir_job.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/message_loop.h"
      9 #include "base/string_util.h"
     10 #include "base/sys_string_conversions.h"
     11 #include "base/time.h"
     12 #include "googleurl/src/gurl.h"
     13 #include "net/base/io_buffer.h"
     14 #include "net/base/net_util.h"
     15 #include "net/url_request/url_request.h"
     16 
     17 #if defined(OS_POSIX)
     18 #include <sys/stat.h>
     19 #endif
     20 
     21 using std::string;
     22 
     23 URLRequestFileDirJob::URLRequestFileDirJob(URLRequest* request,
     24                                            const FilePath& dir_path)
     25     : URLRequestJob(request),
     26       dir_path_(dir_path),
     27       canceled_(false),
     28       list_complete_(false),
     29       wrote_header_(false),
     30       read_pending_(false),
     31       read_buffer_length_(0) {
     32 }
     33 
     34 URLRequestFileDirJob::~URLRequestFileDirJob() {
     35   DCHECK(read_pending_ == false);
     36   DCHECK(lister_ == NULL);
     37 }
     38 
     39 void URLRequestFileDirJob::Start() {
     40   // Start reading asynchronously so that all error reporting and data
     41   // callbacks happen as they would for network requests.
     42   MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
     43       this, &URLRequestFileDirJob::StartAsync));
     44 }
     45 
     46 void URLRequestFileDirJob::StartAsync() {
     47   DCHECK(!lister_);
     48 
     49   // AddRef so that *this* cannot be destroyed while the lister_
     50   // is trying to feed us data.
     51 
     52   AddRef();
     53   lister_ = new net::DirectoryLister(dir_path_, this);
     54   lister_->Start();
     55 
     56   NotifyHeadersComplete();
     57 }
     58 
     59 void URLRequestFileDirJob::Kill() {
     60   if (canceled_)
     61     return;
     62 
     63   canceled_ = true;
     64 
     65   // Don't call CloseLister or dispatch an error to the URLRequest because we
     66   // want OnListDone to be called to also write the error to the output stream.
     67   // OnListDone will notify the URLRequest at this time.
     68   if (lister_)
     69     lister_->Cancel();
     70 
     71   URLRequestJob::Kill();
     72 }
     73 
     74 bool URLRequestFileDirJob::ReadRawData(net::IOBuffer* buf, int buf_size,
     75                                        int *bytes_read) {
     76   DCHECK(bytes_read);
     77   *bytes_read = 0;
     78 
     79   if (is_done())
     80     return true;
     81 
     82   if (FillReadBuffer(buf->data(), buf_size, bytes_read))
     83     return true;
     84 
     85   // We are waiting for more data
     86   read_pending_ = true;
     87   read_buffer_ = buf;
     88   read_buffer_length_ = buf_size;
     89   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
     90   return false;
     91 }
     92 
     93 bool URLRequestFileDirJob::GetMimeType(string* mime_type) const {
     94   *mime_type = "text/html";
     95   return true;
     96 }
     97 
     98 bool URLRequestFileDirJob::GetCharset(string* charset) {
     99   // All the filenames are converted to UTF-8 before being added.
    100   *charset = "utf-8";
    101   return true;
    102 }
    103 
    104 void URLRequestFileDirJob::OnListFile(
    105     const file_util::FileEnumerator::FindInfo& data) {
    106   // We wait to write out the header until we get the first file, so that we
    107   // can catch errors from DirectoryLister and show an error page.
    108   if (!wrote_header_) {
    109 #if defined(OS_WIN)
    110     const string16& title = dir_path_.value();
    111 #elif defined(OS_POSIX)
    112     // TODO(jungshik): Add SysNativeMBToUTF16 to sys_string_conversions.
    113     // On Mac, need to add NFKC->NFC conversion either here or in file_path.
    114     // On Linux, the file system encoding is not defined, but we assume that
    115     // SysNativeMBToWide takes care of it at least for now. We can try something
    116     // more sophisticated if necessary later.
    117     const string16& title = WideToUTF16(
    118         base::SysNativeMBToWide(dir_path_.value()));
    119 #endif
    120     data_.append(net::GetDirectoryListingHeader(title));
    121     wrote_header_ = true;
    122   }
    123 
    124 #if defined(OS_WIN)
    125   int64 size = (static_cast<unsigned __int64>(data.nFileSizeHigh) << 32) |
    126       data.nFileSizeLow;
    127 
    128   // Note that we should not convert ftLastWriteTime to the local time because
    129   // ICU's datetime formatting APIs expect time in UTC and take into account
    130   // the timezone before formatting.
    131   data_.append(net::GetDirectoryListingEntry(
    132       data.cFileName, std::string(),
    133       (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false,
    134       size,
    135       base::Time::FromFileTime(data.ftLastWriteTime)));
    136 
    137 #elif defined(OS_POSIX)
    138   // TOOD(jungshik): The same issue as for the directory name.
    139   data_.append(net::GetDirectoryListingEntry(
    140       WideToUTF16(base::SysNativeMBToWide(data.filename)),
    141       data.filename,
    142       S_ISDIR(data.stat.st_mode),
    143       data.stat.st_size,
    144       base::Time::FromTimeT(data.stat.st_mtime)));
    145 #endif
    146 
    147   // TODO(darin): coalesce more?
    148   CompleteRead();
    149 }
    150 
    151 void URLRequestFileDirJob::OnListDone(int error) {
    152   CloseLister();
    153 
    154   if (canceled_) {
    155     read_pending_ = false;
    156     // No need for NotifyCanceled() since canceled_ is set inside Kill().
    157   } else if (error) {
    158     read_pending_ = false;
    159     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, error));
    160   } else {
    161     list_complete_ = true;
    162     CompleteRead();
    163   }
    164 
    165   Release();  // The Lister is finished; may delete *this*
    166 }
    167 
    168 void URLRequestFileDirJob::CloseLister() {
    169   if (lister_) {
    170     lister_->Cancel();
    171     lister_->set_delegate(NULL);
    172     lister_ = NULL;
    173   }
    174 }
    175 
    176 bool URLRequestFileDirJob::FillReadBuffer(char *buf, int buf_size,
    177                                           int *bytes_read) {
    178   DCHECK(bytes_read);
    179 
    180   *bytes_read = 0;
    181 
    182   int count = std::min(buf_size, static_cast<int>(data_.size()));
    183   if (count) {
    184     memcpy(buf, &data_[0], count);
    185     data_.erase(0, count);
    186     *bytes_read = count;
    187     return true;
    188   } else if (list_complete_) {
    189     // EOF
    190     return true;
    191   }
    192   return false;
    193 }
    194 
    195 void URLRequestFileDirJob::CompleteRead() {
    196   if (read_pending_) {
    197     int bytes_read;
    198     if (FillReadBuffer(read_buffer_->data(), read_buffer_length_,
    199                        &bytes_read)) {
    200       // We completed the read, so reset the read buffer.
    201       read_pending_ = false;
    202       read_buffer_ = NULL;
    203       read_buffer_length_ = 0;
    204 
    205       SetStatus(URLRequestStatus());
    206       NotifyReadComplete(bytes_read);
    207     } else {
    208       NOTREACHED();
    209       // TODO: Better error code.
    210       NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 0));
    211     }
    212   }
    213 }
    214 
    215 bool URLRequestFileDirJob::IsRedirectResponse(
    216     GURL* location, int* http_status_code) {
    217   // If the URL did not have a trailing slash, treat the response as a redirect
    218   // to the URL with a trailing slash appended.
    219   std::string path = request_->url().path();
    220   if (path.empty() || (path[path.size() - 1] != '/')) {
    221     // This happens when we discovered the file is a directory, so needs a
    222     // slash at the end of the path.
    223     std::string new_path = path;
    224     new_path.push_back('/');
    225     GURL::Replacements replacements;
    226     replacements.SetPathStr(new_path);
    227 
    228     *location = request_->url().ReplaceComponents(replacements);
    229     *http_status_code = 301;  // simulate a permanent redirect
    230     return true;
    231   }
    232 
    233   return false;
    234 }
    235