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