nsDirectoryIndexStream.cpp (8843B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set sw=2 sts=2 et cin: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 9 The converts a filesystem directory into an "HTTP index" stream per 10 Lou Montulli's original spec: 11 12 http://www.mozilla.org/projects/netlib/dirindexformat.html 13 14 */ 15 16 #include "nsEscape.h" 17 #include "nsDirectoryIndexStream.h" 18 #include "mozilla/Logging.h" 19 #include "prtime.h" 20 #include "nsIFile.h" 21 #include "nsNativeCharsetUtils.h" 22 23 // NOTE: This runs on the _file transport_ thread. 24 // The problem is that now that we're actually doing something with the data, 25 // we want to do stuff like i18n sorting. However, none of the collation stuff 26 // is threadsafe. 27 // So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current 28 // behaviour, though. See bug 99382. 29 30 using namespace mozilla; 31 static LazyLogModule gLog("nsDirectoryIndexStream"); 32 33 nsDirectoryIndexStream::nsDirectoryIndexStream() { 34 MOZ_LOG(gLog, LogLevel::Debug, ("nsDirectoryIndexStream[%p]: created", this)); 35 } 36 37 static int compare(nsIFile* aElement1, nsIFile* aElement2) { 38 if (!NS_IsNativeUTF8()) { 39 // don't check for errors, because we can't report them anyway 40 nsAutoString name1, name2; 41 aElement1->GetLeafName(name1); 42 aElement2->GetLeafName(name2); 43 44 // Note - we should do the collation to do sorting. Why don't we? 45 // Because that is _slow_. Using TestProtocols to list file:///dev/ 46 // goes from 3 seconds to 22. (This may be why nsXULSortService is 47 // so slow as well). 48 // Does this have bad effects? Probably, but since nsXULTree appears 49 // to use the raw RDF literal value as the sort key (which ammounts to an 50 // strcmp), it won't be any worse, I think. 51 // This could be made faster, by creating the keys once, 52 // but CompareString could still be smarter - see bug 99383 - bbaetz 53 // NB - 99393 has been WONTFIXed. So if the I18N code is ever made 54 // threadsafe so that this matters, we'd have to pass through a 55 // struct { nsIFile*, uint8_t* } with the pre-calculated key. 56 return Compare(name1, name2); 57 } 58 59 nsAutoCString name1, name2; 60 aElement1->GetNativeLeafName(name1); 61 aElement2->GetNativeLeafName(name2); 62 63 return Compare(name1, name2); 64 } 65 66 nsresult nsDirectoryIndexStream::Init(nsIFile* aDir) { 67 nsresult rv; 68 bool isDir; 69 rv = aDir->IsDirectory(&isDir); 70 if (NS_FAILED(rv)) return rv; 71 MOZ_ASSERT(isDir, "not a directory"); 72 if (!isDir) return NS_ERROR_ILLEGAL_VALUE; 73 74 if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { 75 MOZ_LOG(gLog, LogLevel::Debug, 76 ("nsDirectoryIndexStream[%p]: initialized on %s", this, 77 aDir->HumanReadablePath().get())); 78 } 79 80 // Sigh. We have to allocate on the heap because there are no 81 // assignment operators defined. 82 nsCOMPtr<nsIDirectoryEnumerator> iter; 83 rv = aDir->GetDirectoryEntries(getter_AddRefs(iter)); 84 if (NS_FAILED(rv)) return rv; 85 86 // Now lets sort, because clients expect it that way 87 // XXX - should we do so here, or when the first item is requested? 88 // XXX - use insertion sort instead? 89 90 nsCOMPtr<nsIFile> file; 91 while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) { 92 mArray.AppendObject(file); // addrefs 93 } 94 95 mArray.Sort(compare); 96 97 mBuf.AppendLiteral("200: filename content-length last-modified file-type\n"); 98 99 return NS_OK; 100 } 101 102 nsDirectoryIndexStream::~nsDirectoryIndexStream() { 103 MOZ_LOG(gLog, LogLevel::Debug, 104 ("nsDirectoryIndexStream[%p]: destroyed", this)); 105 } 106 107 nsresult nsDirectoryIndexStream::Create(nsIFile* aDir, 108 nsIInputStream** aResult) { 109 RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream(); 110 if (!result) return NS_ERROR_OUT_OF_MEMORY; 111 112 nsresult rv = result->Init(aDir); 113 if (NS_FAILED(rv)) { 114 return rv; 115 } 116 117 result.forget(aResult); 118 return NS_OK; 119 } 120 121 NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream) 122 123 // The below routines are proxied to the UI thread! 124 NS_IMETHODIMP 125 nsDirectoryIndexStream::Close() { 126 mStatus = NS_BASE_STREAM_CLOSED; 127 return NS_OK; 128 } 129 130 NS_IMETHODIMP 131 nsDirectoryIndexStream::Available(uint64_t* aLength) { 132 if (NS_FAILED(mStatus)) return mStatus; 133 134 // If there's data in our buffer, use that 135 if (mOffset < (int32_t)mBuf.Length()) { 136 *aLength = mBuf.Length() - mOffset; 137 return NS_OK; 138 } 139 140 // Returning one byte is not ideal, but good enough 141 *aLength = (mPos < mArray.Count()) ? 1 : 0; 142 return NS_OK; 143 } 144 145 NS_IMETHODIMP 146 nsDirectoryIndexStream::StreamStatus() { return mStatus; } 147 148 NS_IMETHODIMP 149 nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, 150 uint32_t* aReadCount) { 151 if (mStatus == NS_BASE_STREAM_CLOSED) { 152 *aReadCount = 0; 153 return NS_OK; 154 } 155 if (NS_FAILED(mStatus)) return mStatus; 156 157 uint32_t nread = 0; 158 159 // If anything is enqueued (or left-over) in mBuf, then feed it to 160 // the reader first. 161 while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { 162 *(aBuf++) = char(mBuf.CharAt(mOffset++)); 163 --aCount; 164 ++nread; 165 } 166 167 // Room left? 168 if (aCount > 0) { 169 mOffset = 0; 170 mBuf.Truncate(); 171 172 // Okay, now we'll suck stuff off of our iterator into the mBuf... 173 while (uint32_t(mBuf.Length()) < aCount) { 174 bool more = mPos < mArray.Count(); 175 if (!more) break; 176 177 // don't addref, for speed - an addref happened when it 178 // was placed in the array, so it's not going to go stale 179 nsIFile* current = mArray.ObjectAt(mPos); 180 ++mPos; 181 182 if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { 183 MOZ_LOG(gLog, LogLevel::Debug, 184 ("nsDirectoryIndexStream[%p]: iterated %s", this, 185 current->HumanReadablePath().get())); 186 } 187 188 // rjc: don't return hidden files/directories! 189 // bbaetz: why not? 190 nsresult rv; 191 #ifndef XP_UNIX 192 bool hidden = false; 193 current->IsHidden(&hidden); 194 if (hidden) { 195 MOZ_LOG(gLog, LogLevel::Debug, 196 ("nsDirectoryIndexStream[%p]: skipping hidden file/directory", 197 this)); 198 continue; 199 } 200 #endif 201 202 int64_t fileSize = 0; 203 current->GetFileSize(&fileSize); 204 205 PRTime fileInfoModifyTime = 0; 206 current->GetLastModifiedTime(&fileInfoModifyTime); 207 fileInfoModifyTime *= PR_USEC_PER_MSEC; 208 209 mBuf.AppendLiteral("201: "); 210 211 // The "filename" field 212 if (!NS_IsNativeUTF8()) { 213 nsAutoString leafname; 214 rv = current->GetLeafName(leafname); 215 if (NS_FAILED(rv)) return rv; 216 217 nsAutoCString escaped; 218 if (!leafname.IsEmpty() && 219 NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) { 220 mBuf.Append(escaped); 221 mBuf.Append(' '); 222 } 223 } else { 224 nsAutoCString leafname; 225 rv = current->GetNativeLeafName(leafname); 226 if (NS_FAILED(rv)) return rv; 227 228 nsAutoCString escaped; 229 if (!leafname.IsEmpty() && NS_Escape(leafname, escaped, url_Path)) { 230 mBuf.Append(escaped); 231 mBuf.Append(' '); 232 } 233 } 234 235 // The "content-length" field 236 mBuf.AppendInt(fileSize, 10); 237 mBuf.Append(' '); 238 239 // The "last-modified" field 240 PRExplodedTime tm; 241 PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm); 242 { 243 char buf[64]; 244 PR_FormatTimeUSEnglish( 245 buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm); 246 mBuf.Append(buf); 247 } 248 249 // The "file-type" field 250 bool isFile = true; 251 current->IsFile(&isFile); 252 if (isFile) { 253 mBuf.AppendLiteral("FILE "); 254 } else { 255 bool isDir; 256 rv = current->IsDirectory(&isDir); 257 if (NS_FAILED(rv)) return rv; 258 if (isDir) { 259 mBuf.AppendLiteral("DIRECTORY "); 260 } else { 261 bool isLink; 262 rv = current->IsSymlink(&isLink); 263 if (NS_FAILED(rv)) return rv; 264 if (isLink) { 265 mBuf.AppendLiteral("SYMBOLIC-LINK "); 266 } 267 } 268 } 269 270 mBuf.Append('\n'); 271 } 272 273 // ...and once we've either run out of directory entries, or 274 // filled up the buffer, then we'll push it to the reader. 275 while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { 276 *(aBuf++) = char(mBuf.CharAt(mOffset++)); 277 --aCount; 278 ++nread; 279 } 280 } 281 282 *aReadCount = nread; 283 return NS_OK; 284 } 285 286 NS_IMETHODIMP 287 nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void* closure, 288 uint32_t count, uint32_t* _retval) { 289 return NS_ERROR_NOT_IMPLEMENTED; 290 } 291 292 NS_IMETHODIMP 293 nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking) { 294 *aNonBlocking = false; 295 return NS_OK; 296 }