Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : /* This parsing code originally lived in xpfe/components/directory/ - bbaetz */
7 :
8 : #include "nsDirIndexParser.h"
9 :
10 : #include "mozilla/ArrayUtils.h"
11 : #include "mozilla/dom/FallbackEncoding.h"
12 : #include "mozilla/Encoding.h"
13 : #include "prprf.h"
14 : #include "nsCRT.h"
15 : #include "nsEscape.h"
16 : #include "nsIDirIndex.h"
17 : #include "nsIInputStream.h"
18 : #include "nsITextToSubURI.h"
19 : #include "nsServiceManagerUtils.h"
20 :
21 : using namespace mozilla;
22 :
23 0 : NS_IMPL_ISUPPORTS(nsDirIndexParser,
24 : nsIRequestObserver,
25 : nsIStreamListener,
26 : nsIDirIndexParser)
27 :
28 0 : nsDirIndexParser::nsDirIndexParser() {
29 0 : }
30 :
31 : nsresult
32 0 : nsDirIndexParser::Init() {
33 0 : mLineStart = 0;
34 0 : mHasDescription = false;
35 0 : mFormat[0] = -1;
36 0 : auto encoding = mozilla::dom::FallbackEncoding::FromLocale();
37 0 : encoding->Name(mEncoding);
38 :
39 : nsresult rv;
40 : // XXX not threadsafe
41 0 : if (gRefCntParser++ == 0)
42 0 : rv = CallGetService(NS_ITEXTTOSUBURI_CONTRACTID, &gTextToSubURI);
43 : else
44 0 : rv = NS_OK;
45 :
46 0 : return rv;
47 : }
48 :
49 0 : nsDirIndexParser::~nsDirIndexParser() {
50 : // XXX not threadsafe
51 0 : if (--gRefCntParser == 0) {
52 0 : NS_IF_RELEASE(gTextToSubURI);
53 : }
54 0 : }
55 :
56 : NS_IMETHODIMP
57 0 : nsDirIndexParser::SetListener(nsIDirIndexListener* aListener) {
58 0 : mListener = aListener;
59 0 : return NS_OK;
60 : }
61 :
62 : NS_IMETHODIMP
63 0 : nsDirIndexParser::GetListener(nsIDirIndexListener** aListener) {
64 0 : NS_IF_ADDREF(*aListener = mListener.get());
65 0 : return NS_OK;
66 : }
67 :
68 : NS_IMETHODIMP
69 0 : nsDirIndexParser::GetComment(char** aComment) {
70 0 : *aComment = ToNewCString(mComment);
71 :
72 0 : if (!*aComment)
73 0 : return NS_ERROR_OUT_OF_MEMORY;
74 :
75 0 : return NS_OK;
76 : }
77 :
78 : NS_IMETHODIMP
79 0 : nsDirIndexParser::SetEncoding(const char* aEncoding) {
80 0 : mEncoding.Assign(aEncoding);
81 0 : return NS_OK;
82 : }
83 :
84 : NS_IMETHODIMP
85 0 : nsDirIndexParser::GetEncoding(char** aEncoding) {
86 0 : *aEncoding = ToNewCString(mEncoding);
87 :
88 0 : if (!*aEncoding)
89 0 : return NS_ERROR_OUT_OF_MEMORY;
90 :
91 0 : return NS_OK;
92 : }
93 :
94 : NS_IMETHODIMP
95 0 : nsDirIndexParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) {
96 0 : return NS_OK;
97 : }
98 :
99 : NS_IMETHODIMP
100 0 : nsDirIndexParser::OnStopRequest(nsIRequest *aRequest, nsISupports *aCtxt,
101 : nsresult aStatusCode) {
102 : // Finish up
103 0 : if (mBuf.Length() > (uint32_t) mLineStart) {
104 0 : ProcessData(aRequest, aCtxt);
105 : }
106 :
107 0 : return NS_OK;
108 : }
109 :
110 : nsDirIndexParser::Field
111 : nsDirIndexParser::gFieldTable[] = {
112 : { "Filename", FIELD_FILENAME },
113 : { "Description", FIELD_DESCRIPTION },
114 : { "Content-Length", FIELD_CONTENTLENGTH },
115 : { "Last-Modified", FIELD_LASTMODIFIED },
116 : { "Content-Type", FIELD_CONTENTTYPE },
117 : { "File-Type", FIELD_FILETYPE },
118 : { nullptr, FIELD_UNKNOWN }
119 : };
120 :
121 : nsrefcnt nsDirIndexParser::gRefCntParser = 0;
122 : nsITextToSubURI *nsDirIndexParser::gTextToSubURI;
123 :
124 : nsresult
125 0 : nsDirIndexParser::ParseFormat(const char* aFormatStr)
126 : {
127 : // Parse a "200" format line, and remember the fields and their
128 : // ordering in mFormat. Multiple 200 lines stomp on each other.
129 0 : unsigned int formatNum = 0;
130 0 : mFormat[0] = -1;
131 :
132 0 : do {
133 0 : while (*aFormatStr && nsCRT::IsAsciiSpace(char16_t(*aFormatStr)))
134 0 : ++aFormatStr;
135 :
136 0 : if (! *aFormatStr)
137 0 : break;
138 :
139 0 : nsAutoCString name;
140 0 : int32_t len = 0;
141 0 : while (aFormatStr[len] && !nsCRT::IsAsciiSpace(char16_t(aFormatStr[len])))
142 0 : ++len;
143 0 : name.SetCapacity(len + 1);
144 0 : name.Append(aFormatStr, len);
145 0 : aFormatStr += len;
146 :
147 : // Okay, we're gonna monkey with the nsStr. Bold!
148 0 : name.SetLength(nsUnescapeCount(name.BeginWriting()));
149 :
150 : // All tokens are case-insensitive - http://www.mozilla.org/projects/netlib/dirindexformat.html
151 0 : if (name.LowerCaseEqualsLiteral("description"))
152 0 : mHasDescription = true;
153 :
154 0 : for (Field* i = gFieldTable; i->mName; ++i) {
155 0 : if (name.EqualsIgnoreCase(i->mName)) {
156 0 : mFormat[formatNum] = i->mType;
157 0 : mFormat[++formatNum] = -1;
158 0 : break;
159 : }
160 : }
161 :
162 0 : } while (*aFormatStr && (formatNum < (ArrayLength(mFormat)-1)));
163 :
164 0 : return NS_OK;
165 : }
166 :
167 : nsresult
168 0 : nsDirIndexParser::ParseData(nsIDirIndex *aIdx, char* aDataStr, int32_t aLineLen)
169 : {
170 : // Parse a "201" data line, using the field ordering specified in
171 : // mFormat.
172 :
173 0 : if(mFormat[0] == -1) {
174 : // Ignore if we haven't seen a format yet.
175 0 : return NS_OK;
176 : }
177 :
178 0 : nsresult rv = NS_OK;
179 0 : nsAutoCString filename;
180 0 : int32_t lineLen = aLineLen;
181 :
182 0 : for (int32_t i = 0; mFormat[i] != -1; ++i) {
183 : // If we've exhausted the data before we run out of fields, just bail.
184 0 : if (!*aDataStr || (lineLen < 1)) {
185 0 : return NS_OK;
186 : }
187 :
188 0 : while ((lineLen > 0) && nsCRT::IsAsciiSpace(*aDataStr)) {
189 0 : ++aDataStr;
190 0 : --lineLen;
191 : }
192 :
193 0 : if (lineLen < 1) {
194 : // invalid format, bail
195 0 : return NS_OK;
196 : }
197 :
198 0 : char *value = aDataStr;
199 0 : if (*aDataStr == '"' || *aDataStr == '\'') {
200 : // it's a quoted string. snarf everything up to the next quote character
201 0 : const char quotechar = *(aDataStr++);
202 0 : lineLen--;
203 0 : ++value;
204 0 : while ((lineLen > 0) && *aDataStr != quotechar) {
205 0 : ++aDataStr;
206 0 : --lineLen;
207 : }
208 0 : if (lineLen > 0) {
209 0 : *aDataStr++ = '\0';
210 0 : --lineLen;
211 : }
212 :
213 0 : if (!lineLen) {
214 : // invalid format, bail
215 0 : return NS_OK;
216 0 : }
217 : } else {
218 : // it's unquoted. snarf until we see whitespace.
219 0 : value = aDataStr;
220 0 : while ((lineLen > 0) && (!nsCRT::IsAsciiSpace(*aDataStr))) {
221 0 : ++aDataStr;
222 0 : --lineLen;
223 : }
224 0 : if (lineLen > 0) {
225 0 : *aDataStr++ = '\0';
226 0 : --lineLen;
227 : }
228 : // even if we ran out of line length here, there's still a trailing zero
229 : // byte afterwards
230 : }
231 :
232 0 : fieldType t = fieldType(mFormat[i]);
233 0 : switch (t) {
234 : case FIELD_FILENAME: {
235 : // don't unescape at this point, so that UnEscapeAndConvert() can
236 0 : filename = value;
237 :
238 0 : bool success = false;
239 :
240 0 : nsAutoString entryuri;
241 :
242 0 : if (gTextToSubURI) {
243 0 : nsAutoString result;
244 0 : if (NS_SUCCEEDED(rv = gTextToSubURI->UnEscapeAndConvert(
245 : mEncoding, filename, result))) {
246 0 : if (!result.IsEmpty()) {
247 0 : aIdx->SetLocation(filename.get());
248 0 : if (!mHasDescription)
249 0 : aIdx->SetDescription(result.get());
250 0 : success = true;
251 : }
252 : } else {
253 0 : NS_WARNING("UnEscapeAndConvert error");
254 : }
255 : }
256 :
257 0 : if (!success) {
258 : // if unsuccessfully at charset conversion, then
259 : // just fallback to unescape'ing in-place
260 : // XXX - this shouldn't be using UTF8, should it?
261 : // when can we fail to get the service, anyway? - bbaetz
262 0 : aIdx->SetLocation(filename.get());
263 0 : if (!mHasDescription) {
264 0 : aIdx->SetDescription(NS_ConvertUTF8toUTF16(value).get());
265 : }
266 : }
267 : }
268 0 : break;
269 : case FIELD_DESCRIPTION:
270 0 : nsUnescape(value);
271 0 : aIdx->SetDescription(NS_ConvertUTF8toUTF16(value).get());
272 0 : break;
273 : case FIELD_CONTENTLENGTH:
274 : {
275 : int64_t len;
276 0 : int32_t status = PR_sscanf(value, "%lld", &len);
277 0 : if (status == 1)
278 0 : aIdx->SetSize(len);
279 : else
280 0 : aIdx->SetSize(UINT64_MAX); // UINT64_MAX means unknown
281 : }
282 0 : break;
283 : case FIELD_LASTMODIFIED:
284 : {
285 : PRTime tm;
286 0 : nsUnescape(value);
287 0 : if (PR_ParseTimeString(value, false, &tm) == PR_SUCCESS) {
288 0 : aIdx->SetLastModified(tm);
289 : }
290 : }
291 0 : break;
292 : case FIELD_CONTENTTYPE:
293 0 : aIdx->SetContentType(value);
294 0 : break;
295 : case FIELD_FILETYPE:
296 : // unescape in-place
297 0 : nsUnescape(value);
298 0 : if (!nsCRT::strcasecmp(value, "directory")) {
299 0 : aIdx->SetType(nsIDirIndex::TYPE_DIRECTORY);
300 0 : } else if (!nsCRT::strcasecmp(value, "file")) {
301 0 : aIdx->SetType(nsIDirIndex::TYPE_FILE);
302 0 : } else if (!nsCRT::strcasecmp(value, "symbolic-link")) {
303 0 : aIdx->SetType(nsIDirIndex::TYPE_SYMLINK);
304 : } else {
305 0 : aIdx->SetType(nsIDirIndex::TYPE_UNKNOWN);
306 : }
307 0 : break;
308 : case FIELD_UNKNOWN:
309 : // ignore
310 0 : break;
311 : }
312 : }
313 :
314 0 : return NS_OK;
315 : }
316 :
317 : NS_IMETHODIMP
318 0 : nsDirIndexParser::OnDataAvailable(nsIRequest *aRequest, nsISupports *aCtxt,
319 : nsIInputStream *aStream,
320 : uint64_t aSourceOffset,
321 : uint32_t aCount) {
322 0 : if (aCount < 1)
323 0 : return NS_OK;
324 :
325 0 : int32_t len = mBuf.Length();
326 :
327 : // Ensure that our mBuf has capacity to hold the data we're about to
328 : // read.
329 0 : if (!mBuf.SetLength(len + aCount, fallible))
330 0 : return NS_ERROR_OUT_OF_MEMORY;
331 :
332 : // Now read the data into our buffer.
333 : nsresult rv;
334 : uint32_t count;
335 0 : rv = aStream->Read(mBuf.BeginWriting() + len, aCount, &count);
336 0 : if (NS_FAILED(rv)) return rv;
337 :
338 : // Set the string's length according to the amount of data we've read.
339 : // Note: we know this to work on nsCString. This isn't guaranteed to
340 : // work on other strings.
341 0 : mBuf.SetLength(len + count);
342 :
343 0 : return ProcessData(aRequest, aCtxt);
344 : }
345 :
346 : nsresult
347 0 : nsDirIndexParser::ProcessData(nsIRequest *aRequest, nsISupports *aCtxt) {
348 0 : if (!mListener)
349 0 : return NS_ERROR_FAILURE;
350 :
351 0 : int32_t numItems = 0;
352 :
353 : while(true) {
354 0 : ++numItems;
355 :
356 0 : int32_t eol = mBuf.FindCharInSet("\n\r", mLineStart);
357 0 : if (eol < 0) break;
358 0 : mBuf.SetCharAt(char16_t('\0'), eol);
359 :
360 0 : const char *line = mBuf.get() + mLineStart;
361 :
362 0 : int32_t lineLen = eol - mLineStart;
363 0 : mLineStart = eol + 1;
364 :
365 0 : if (lineLen >= 4) {
366 : nsresult rv;
367 0 : const char *buf = line;
368 :
369 0 : if (buf[0] == '1') {
370 0 : if (buf[1] == '0') {
371 0 : if (buf[2] == '0' && buf[3] == ':') {
372 : // 100. Human-readable comment line. Ignore
373 0 : } else if (buf[2] == '1' && buf[3] == ':') {
374 : // 101. Human-readable information line.
375 0 : mComment.Append(buf + 4);
376 :
377 0 : char *value = ((char *)buf) + 4;
378 0 : nsUnescape(value);
379 0 : mListener->OnInformationAvailable(aRequest, aCtxt, NS_ConvertUTF8toUTF16(value));
380 :
381 0 : } else if (buf[2] == '2' && buf[3] == ':') {
382 : // 102. Human-readable information line, HTML.
383 0 : mComment.Append(buf + 4);
384 : }
385 : }
386 0 : } else if (buf[0] == '2') {
387 0 : if (buf[1] == '0') {
388 0 : if (buf[2] == '0' && buf[3] == ':') {
389 : // 200. Define field names
390 0 : rv = ParseFormat(buf + 4);
391 0 : if (NS_FAILED(rv)) {
392 0 : return rv;
393 : }
394 0 : } else if (buf[2] == '1' && buf[3] == ':') {
395 : // 201. Field data
396 0 : nsCOMPtr<nsIDirIndex> idx = do_CreateInstance("@mozilla.org/dirIndex;1",&rv);
397 0 : if (NS_FAILED(rv))
398 0 : return rv;
399 :
400 0 : rv = ParseData(idx, ((char *)buf) + 4, lineLen - 4);
401 0 : if (NS_FAILED(rv)) {
402 0 : return rv;
403 : }
404 :
405 0 : mListener->OnIndexAvailable(aRequest, aCtxt, idx);
406 : }
407 : }
408 0 : } else if (buf[0] == '3') {
409 0 : if (buf[1] == '0') {
410 0 : if (buf[2] == '0' && buf[3] == ':') {
411 : // 300. Self-referring URL
412 0 : } else if (buf[2] == '1' && buf[3] == ':') {
413 : // 301. OUR EXTENSION - encoding
414 0 : int i = 4;
415 0 : while (buf[i] && nsCRT::IsAsciiSpace(buf[i]))
416 0 : ++i;
417 :
418 0 : if (buf[i])
419 0 : SetEncoding(buf+i);
420 : }
421 : }
422 : }
423 : }
424 0 : }
425 :
426 0 : return NS_OK;
427 : }
|