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