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