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