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