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 "nsURLHelper.h"
22 #include "nsNativeCharsetUtils.h"
24 // NOTE: This runs on the _file transport_ thread.
25 // The problem is that now that we're actually doing something with the data,
26 // we want to do stuff like i18n sorting. However, none of the collation stuff
28 // So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
29 // behaviour, though. See bug 99382.
31 using namespace mozilla
;
32 static LazyLogModule
gLog("nsDirectoryIndexStream");
34 nsDirectoryIndexStream::nsDirectoryIndexStream() {
35 MOZ_LOG(gLog
, LogLevel::Debug
, ("nsDirectoryIndexStream[%p]: created", this));
38 static int compare(nsIFile
* aElement1
, nsIFile
* aElement2
, void* aData
) {
39 if (!NS_IsNativeUTF8()) {
40 // don't check for errors, because we can't report them anyway
41 nsAutoString name1
, name2
;
42 aElement1
->GetLeafName(name1
);
43 aElement2
->GetLeafName(name2
);
45 // Note - we should do the collation to do sorting. Why don't we?
46 // Because that is _slow_. Using TestProtocols to list file:///dev/
47 // goes from 3 seconds to 22. (This may be why nsXULSortService is
49 // Does this have bad effects? Probably, but since nsXULTree appears
50 // to use the raw RDF literal value as the sort key (which ammounts to an
51 // strcmp), it won't be any worse, I think.
52 // This could be made faster, by creating the keys once,
53 // but CompareString could still be smarter - see bug 99383 - bbaetz
54 // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
55 // threadsafe so that this matters, we'd have to pass through a
56 // struct { nsIFile*, uint8_t* } with the pre-calculated key.
57 return Compare(name1
, name2
);
60 nsAutoCString name1
, name2
;
61 aElement1
->GetNativeLeafName(name1
);
62 aElement2
->GetNativeLeafName(name2
);
64 return Compare(name1
, name2
);
67 nsresult
nsDirectoryIndexStream::Init(nsIFile
* aDir
) {
70 rv
= aDir
->IsDirectory(&isDir
);
71 if (NS_FAILED(rv
)) return rv
;
72 MOZ_ASSERT(isDir
, "not a directory");
73 if (!isDir
) return NS_ERROR_ILLEGAL_VALUE
;
75 if (MOZ_LOG_TEST(gLog
, LogLevel::Debug
)) {
76 MOZ_LOG(gLog
, LogLevel::Debug
,
77 ("nsDirectoryIndexStream[%p]: initialized on %s", this,
78 aDir
->HumanReadablePath().get()));
81 // Sigh. We have to allocate on the heap because there are no
82 // assignment operators defined.
83 nsCOMPtr
<nsIDirectoryEnumerator
> iter
;
84 rv
= aDir
->GetDirectoryEntries(getter_AddRefs(iter
));
85 if (NS_FAILED(rv
)) return rv
;
87 // Now lets sort, because clients expect it that way
88 // XXX - should we do so here, or when the first item is requested?
89 // XXX - use insertion sort instead?
91 nsCOMPtr
<nsIFile
> file
;
92 while (NS_SUCCEEDED(iter
->GetNextFile(getter_AddRefs(file
))) && file
) {
93 mArray
.AppendObject(file
); // addrefs
96 mArray
.Sort(compare
, nullptr);
98 mBuf
.AppendLiteral("300: ");
100 rv
= net_GetURLSpecFromFile(aDir
, url
);
101 if (NS_FAILED(rv
)) return rv
;
105 mBuf
.AppendLiteral("200: filename content-length last-modified file-type\n");
110 nsDirectoryIndexStream::~nsDirectoryIndexStream() {
111 MOZ_LOG(gLog
, LogLevel::Debug
,
112 ("nsDirectoryIndexStream[%p]: destroyed", this));
115 nsresult
nsDirectoryIndexStream::Create(nsIFile
* aDir
,
116 nsIInputStream
** aResult
) {
117 RefPtr
<nsDirectoryIndexStream
> result
= new nsDirectoryIndexStream();
118 if (!result
) return NS_ERROR_OUT_OF_MEMORY
;
120 nsresult rv
= result
->Init(aDir
);
125 result
.forget(aResult
);
129 NS_IMPL_ISUPPORTS(nsDirectoryIndexStream
, nsIInputStream
)
131 // The below routines are proxied to the UI thread!
133 nsDirectoryIndexStream::Close() {
134 mStatus
= NS_BASE_STREAM_CLOSED
;
139 nsDirectoryIndexStream::Available(uint64_t* aLength
) {
140 if (NS_FAILED(mStatus
)) return mStatus
;
142 // If there's data in our buffer, use that
143 if (mOffset
< (int32_t)mBuf
.Length()) {
144 *aLength
= mBuf
.Length() - mOffset
;
148 // Returning one byte is not ideal, but good enough
149 *aLength
= (mPos
< mArray
.Count()) ? 1 : 0;
154 nsDirectoryIndexStream::Read(char* aBuf
, uint32_t aCount
,
155 uint32_t* aReadCount
) {
156 if (mStatus
== NS_BASE_STREAM_CLOSED
) {
160 if (NS_FAILED(mStatus
)) return mStatus
;
164 // If anything is enqueued (or left-over) in mBuf, then feed it to
166 while (mOffset
< (int32_t)mBuf
.Length() && aCount
!= 0) {
167 *(aBuf
++) = char(mBuf
.CharAt(mOffset
++));
177 // Okay, now we'll suck stuff off of our iterator into the mBuf...
178 while (uint32_t(mBuf
.Length()) < aCount
) {
179 bool more
= mPos
< mArray
.Count();
182 // don't addref, for speed - an addref happened when it
183 // was placed in the array, so it's not going to go stale
184 nsIFile
* current
= mArray
.ObjectAt(mPos
);
187 if (MOZ_LOG_TEST(gLog
, LogLevel::Debug
)) {
188 MOZ_LOG(gLog
, LogLevel::Debug
,
189 ("nsDirectoryIndexStream[%p]: iterated %s", this,
190 current
->HumanReadablePath().get()));
193 // rjc: don't return hidden files/directories!
198 current
->IsHidden(&hidden
);
200 MOZ_LOG(gLog
, LogLevel::Debug
,
201 ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
207 int64_t fileSize
= 0;
208 current
->GetFileSize(&fileSize
);
210 PRTime fileInfoModifyTime
= 0;
211 current
->GetLastModifiedTime(&fileInfoModifyTime
);
212 fileInfoModifyTime
*= PR_USEC_PER_MSEC
;
214 mBuf
.AppendLiteral("201: ");
216 // The "filename" field
217 if (!NS_IsNativeUTF8()) {
218 nsAutoString leafname
;
219 rv
= current
->GetLeafName(leafname
);
220 if (NS_FAILED(rv
)) return rv
;
222 nsAutoCString escaped
;
223 if (!leafname
.IsEmpty() &&
224 NS_Escape(NS_ConvertUTF16toUTF8(leafname
), escaped
, url_Path
)) {
225 mBuf
.Append(escaped
);
229 nsAutoCString leafname
;
230 rv
= current
->GetNativeLeafName(leafname
);
231 if (NS_FAILED(rv
)) return rv
;
233 nsAutoCString escaped
;
234 if (!leafname
.IsEmpty() && NS_Escape(leafname
, escaped
, url_Path
)) {
235 mBuf
.Append(escaped
);
240 // The "content-length" field
241 mBuf
.AppendInt(fileSize
, 10);
244 // The "last-modified" field
246 PR_ExplodeTime(fileInfoModifyTime
, PR_GMTParameters
, &tm
);
249 PR_FormatTimeUSEnglish(
250 buf
, sizeof(buf
), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm
);
254 // The "file-type" field
256 current
->IsFile(&isFile
);
258 mBuf
.AppendLiteral("FILE ");
261 rv
= current
->IsDirectory(&isDir
);
262 if (NS_FAILED(rv
)) return rv
;
264 mBuf
.AppendLiteral("DIRECTORY ");
267 rv
= current
->IsSymlink(&isLink
);
268 if (NS_FAILED(rv
)) return rv
;
270 mBuf
.AppendLiteral("SYMBOLIC-LINK ");
278 // ...and once we've either run out of directory entries, or
279 // filled up the buffer, then we'll push it to the reader.
280 while (mOffset
< (int32_t)mBuf
.Length() && aCount
!= 0) {
281 *(aBuf
++) = char(mBuf
.CharAt(mOffset
++));
292 nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer
, void* closure
,
293 uint32_t count
, uint32_t* _retval
) {
294 return NS_ERROR_NOT_IMPLEMENTED
;
298 nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking
) {
299 *aNonBlocking
= false;