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