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