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/. */
9 The converts a filesystem directory into an "HTTP index" stream per
10 Lou Montulli's original spec:
12 http://www.mozilla.org/projects/netlib/dirindexformat.html
17 #include "nsDirectoryIndexStream.h"
18 #include "mozilla/Logging.h"
21 #include "nsNativeCharsetUtils.h"
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
27 // So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
28 // behaviour, though. See bug 99382.
30 using namespace mozilla
;
31 static LazyLogModule
gLog("nsDirectoryIndexStream");
33 nsDirectoryIndexStream::nsDirectoryIndexStream() {
34 MOZ_LOG(gLog
, LogLevel::Debug
, ("nsDirectoryIndexStream[%p]: created", this));
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
);
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
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
);
59 nsAutoCString name1
, name2
;
60 aElement1
->GetNativeLeafName(name1
);
61 aElement2
->GetNativeLeafName(name2
);
63 return Compare(name1
, name2
);
66 nsresult
nsDirectoryIndexStream::Init(nsIFile
* aDir
) {
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
;
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()));
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
;
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?
90 nsCOMPtr
<nsIFile
> file
;
91 while (NS_SUCCEEDED(iter
->GetNextFile(getter_AddRefs(file
))) && file
) {
92 mArray
.AppendObject(file
); // addrefs
97 mBuf
.AppendLiteral("200: filename content-length last-modified file-type\n");
102 nsDirectoryIndexStream::~nsDirectoryIndexStream() {
103 MOZ_LOG(gLog
, LogLevel::Debug
,
104 ("nsDirectoryIndexStream[%p]: destroyed", this));
107 nsresult
nsDirectoryIndexStream::Create(nsIFile
* aDir
,
108 nsIInputStream
** aResult
) {
109 RefPtr
<nsDirectoryIndexStream
> result
= new nsDirectoryIndexStream();
110 if (!result
) return NS_ERROR_OUT_OF_MEMORY
;
112 nsresult rv
= result
->Init(aDir
);
117 result
.forget(aResult
);
121 NS_IMPL_ISUPPORTS(nsDirectoryIndexStream
, nsIInputStream
)
123 // The below routines are proxied to the UI thread!
125 nsDirectoryIndexStream::Close() {
126 mStatus
= NS_BASE_STREAM_CLOSED
;
131 nsDirectoryIndexStream::Available(uint64_t* aLength
) {
132 if (NS_FAILED(mStatus
)) return mStatus
;
134 // If there's data in our buffer, use that
135 if (mOffset
< (int32_t)mBuf
.Length()) {
136 *aLength
= mBuf
.Length() - mOffset
;
140 // Returning one byte is not ideal, but good enough
141 *aLength
= (mPos
< mArray
.Count()) ? 1 : 0;
146 nsDirectoryIndexStream::StreamStatus() { return mStatus
; }
149 nsDirectoryIndexStream::Read(char* aBuf
, uint32_t aCount
,
150 uint32_t* aReadCount
) {
151 if (mStatus
== NS_BASE_STREAM_CLOSED
) {
155 if (NS_FAILED(mStatus
)) return mStatus
;
159 // If anything is enqueued (or left-over) in mBuf, then feed it to
161 while (mOffset
< (int32_t)mBuf
.Length() && aCount
!= 0) {
162 *(aBuf
++) = char(mBuf
.CharAt(mOffset
++));
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();
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
);
182 if (MOZ_LOG_TEST(gLog
, LogLevel::Debug
)) {
183 MOZ_LOG(gLog
, LogLevel::Debug
,
184 ("nsDirectoryIndexStream[%p]: iterated %s", this,
185 current
->HumanReadablePath().get()));
188 // rjc: don't return hidden files/directories!
193 current
->IsHidden(&hidden
);
195 MOZ_LOG(gLog
, LogLevel::Debug
,
196 ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
202 int64_t fileSize
= 0;
203 current
->GetFileSize(&fileSize
);
205 PRTime fileInfoModifyTime
= 0;
206 current
->GetLastModifiedTime(&fileInfoModifyTime
);
207 fileInfoModifyTime
*= PR_USEC_PER_MSEC
;
209 mBuf
.AppendLiteral("201: ");
211 // The "filename" field
212 if (!NS_IsNativeUTF8()) {
213 nsAutoString leafname
;
214 rv
= current
->GetLeafName(leafname
);
215 if (NS_FAILED(rv
)) return rv
;
217 nsAutoCString escaped
;
218 if (!leafname
.IsEmpty() &&
219 NS_Escape(NS_ConvertUTF16toUTF8(leafname
), escaped
, url_Path
)) {
220 mBuf
.Append(escaped
);
224 nsAutoCString leafname
;
225 rv
= current
->GetNativeLeafName(leafname
);
226 if (NS_FAILED(rv
)) return rv
;
228 nsAutoCString escaped
;
229 if (!leafname
.IsEmpty() && NS_Escape(leafname
, escaped
, url_Path
)) {
230 mBuf
.Append(escaped
);
235 // The "content-length" field
236 mBuf
.AppendInt(fileSize
, 10);
239 // The "last-modified" field
241 PR_ExplodeTime(fileInfoModifyTime
, PR_GMTParameters
, &tm
);
244 PR_FormatTimeUSEnglish(
245 buf
, sizeof(buf
), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm
);
249 // The "file-type" field
251 current
->IsFile(&isFile
);
253 mBuf
.AppendLiteral("FILE ");
256 rv
= current
->IsDirectory(&isDir
);
257 if (NS_FAILED(rv
)) return rv
;
259 mBuf
.AppendLiteral("DIRECTORY ");
262 rv
= current
->IsSymlink(&isLink
);
263 if (NS_FAILED(rv
)) return rv
;
265 mBuf
.AppendLiteral("SYMBOLIC-LINK ");
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
++));
287 nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer
, void* closure
,
288 uint32_t count
, uint32_t* _retval
) {
289 return NS_ERROR_NOT_IMPLEMENTED
;
293 nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking
) {
294 *aNonBlocking
= false;