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