netwerk/streamconv/converters/nsIndexedToHTML.cpp

branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
equal deleted inserted replaced
-1:000000000000 0:b5c990fbc79c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 #include "nsIndexedToHTML.h"
7 #include "mozilla/dom/EncodingUtils.h"
8 #include "nsNetUtil.h"
9 #include "netCore.h"
10 #include "nsStringStream.h"
11 #include "nsIFileURL.h"
12 #include "nsEscape.h"
13 #include "nsIDirIndex.h"
14 #include "nsDateTimeFormatCID.h"
15 #include "nsURLHelper.h"
16 #include "nsIPlatformCharset.h"
17 #include "nsIPrefService.h"
18 #include "nsIPrefBranch.h"
19 #include "nsIPrefLocalizedString.h"
20 #include "nsIChromeRegistry.h"
21 #include "nsICharsetConverterManager.h"
22 #include "nsIDateTimeFormat.h"
23 #include "nsIStringBundle.h"
24 #include "nsITextToSubURI.h"
25 #include "nsXPIDLString.h"
26 #include <algorithm>
27
28 NS_IMPL_ISUPPORTS(nsIndexedToHTML,
29 nsIDirIndexListener,
30 nsIStreamConverter,
31 nsIRequestObserver,
32 nsIStreamListener)
33
34 static void AppendNonAsciiToNCR(const nsAString& in, nsCString& out)
35 {
36 nsAString::const_iterator start, end;
37
38 in.BeginReading(start);
39 in.EndReading(end);
40
41 while (start != end) {
42 if (*start < 128) {
43 out.Append(*start++);
44 } else {
45 out.AppendLiteral("&#x");
46 out.AppendInt(*start++, 16);
47 out.Append(';');
48 }
49 }
50 }
51
52 nsresult
53 nsIndexedToHTML::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) {
54 nsresult rv;
55 if (aOuter)
56 return NS_ERROR_NO_AGGREGATION;
57
58 nsIndexedToHTML* _s = new nsIndexedToHTML();
59 if (_s == nullptr)
60 return NS_ERROR_OUT_OF_MEMORY;
61
62 rv = _s->QueryInterface(aIID, aResult);
63 return rv;
64 }
65
66 nsresult
67 nsIndexedToHTML::Init(nsIStreamListener* aListener) {
68 nsresult rv = NS_OK;
69
70 mListener = aListener;
71
72 mDateTime = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
73 if (NS_FAILED(rv))
74 return rv;
75
76 nsCOMPtr<nsIStringBundleService> sbs =
77 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
78 if (NS_FAILED(rv)) return rv;
79 rv = sbs->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(mBundle));
80
81 mExpectAbsLoc = false;
82
83 return rv;
84 }
85
86 NS_IMETHODIMP
87 nsIndexedToHTML::Convert(nsIInputStream* aFromStream,
88 const char* aFromType,
89 const char* aToType,
90 nsISupports* aCtxt,
91 nsIInputStream** res) {
92 return NS_ERROR_NOT_IMPLEMENTED;
93 }
94
95 NS_IMETHODIMP
96 nsIndexedToHTML::AsyncConvertData(const char *aFromType,
97 const char *aToType,
98 nsIStreamListener *aListener,
99 nsISupports *aCtxt) {
100 return Init(aListener);
101 }
102
103 NS_IMETHODIMP
104 nsIndexedToHTML::OnStartRequest(nsIRequest* request, nsISupports *aContext) {
105 nsCString buffer;
106 nsresult rv = DoOnStartRequest(request, aContext, buffer);
107 if (NS_FAILED(rv)) {
108 request->Cancel(rv);
109 }
110
111 rv = mListener->OnStartRequest(request, aContext);
112 if (NS_FAILED(rv)) return rv;
113
114 // The request may have been canceled, and if that happens, we want to
115 // suppress calls to OnDataAvailable.
116 request->GetStatus(&rv);
117 if (NS_FAILED(rv)) return rv;
118
119 // Push our buffer to the listener.
120
121 rv = SendToListener(request, aContext, buffer);
122 return rv;
123 }
124
125 nsresult
126 nsIndexedToHTML::DoOnStartRequest(nsIRequest* request, nsISupports *aContext,
127 nsCString& aBuffer) {
128 nsresult rv;
129
130 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
131 nsCOMPtr<nsIURI> uri;
132 rv = channel->GetURI(getter_AddRefs(uri));
133 if (NS_FAILED(rv)) return rv;
134
135 channel->SetContentType(NS_LITERAL_CSTRING("text/html"));
136
137 mParser = do_CreateInstance("@mozilla.org/dirIndexParser;1",&rv);
138 if (NS_FAILED(rv)) return rv;
139
140 rv = mParser->SetListener(this);
141 if (NS_FAILED(rv)) return rv;
142
143 rv = mParser->OnStartRequest(request, aContext);
144 if (NS_FAILED(rv)) return rv;
145
146 nsAutoCString baseUri, titleUri;
147 rv = uri->GetAsciiSpec(baseUri);
148 if (NS_FAILED(rv)) return rv;
149 titleUri = baseUri;
150
151 nsCString parentStr;
152
153 nsCString buffer;
154 buffer.AppendLiteral("<!DOCTYPE html>\n<html>\n<head>\n");
155
156 // XXX - should be using the 300: line from the parser.
157 // We can't guarantee that that comes before any entry, so we'd have to
158 // buffer, and do other painful stuff.
159 // I'll deal with this when I make the changes to handle welcome messages
160 // The .. stuff should also come from the lower level protocols, but that
161 // would muck up the XUL display
162 // - bbaetz
163
164 bool isScheme = false;
165 bool isSchemeFile = false;
166 if (NS_SUCCEEDED(uri->SchemeIs("ftp", &isScheme)) && isScheme) {
167
168 // strip out the password here, so it doesn't show in the page title
169 // This is done by the 300: line generation in ftp, but we don't use
170 // that - see above
171
172 nsAutoCString pw;
173 rv = uri->GetPassword(pw);
174 if (NS_FAILED(rv)) return rv;
175 if (!pw.IsEmpty()) {
176 nsCOMPtr<nsIURI> newUri;
177 rv = uri->Clone(getter_AddRefs(newUri));
178 if (NS_FAILED(rv)) return rv;
179 rv = newUri->SetPassword(EmptyCString());
180 if (NS_FAILED(rv)) return rv;
181 rv = newUri->GetAsciiSpec(titleUri);
182 if (NS_FAILED(rv)) return rv;
183 }
184
185 nsAutoCString path;
186 rv = uri->GetPath(path);
187 if (NS_FAILED(rv)) return rv;
188
189 if (!path.EqualsLiteral("//") && !path.LowerCaseEqualsLiteral("/%2f")) {
190 rv = uri->Resolve(NS_LITERAL_CSTRING(".."),parentStr);
191 if (NS_FAILED(rv)) return rv;
192 }
193 } else if (NS_SUCCEEDED(uri->SchemeIs("file", &isSchemeFile)) && isSchemeFile) {
194 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
195 nsCOMPtr<nsIFile> file;
196 rv = fileUrl->GetFile(getter_AddRefs(file));
197 if (NS_FAILED(rv)) return rv;
198 file->SetFollowLinks(true);
199
200 nsAutoCString url;
201 rv = net_GetURLSpecFromFile(file, url);
202 if (NS_FAILED(rv)) return rv;
203 baseUri.Assign(url);
204
205 nsCOMPtr<nsIFile> parent;
206 rv = file->GetParent(getter_AddRefs(parent));
207
208 if (parent && NS_SUCCEEDED(rv)) {
209 net_GetURLSpecFromDir(parent, url);
210 if (NS_FAILED(rv)) return rv;
211 parentStr.Assign(url);
212 }
213
214 // Directory index will be always encoded in UTF-8 if this is file url
215 buffer.AppendLiteral("<meta charset=\"UTF-8\">\n");
216
217 } else if (NS_SUCCEEDED(uri->SchemeIs("jar", &isScheme)) && isScheme) {
218 nsAutoCString path;
219 rv = uri->GetPath(path);
220 if (NS_FAILED(rv)) return rv;
221
222 // a top-level jar directory URL is of the form jar:foo.zip!/
223 // path will be of the form foo.zip!/, and its last two characters
224 // will be "!/"
225 //XXX this won't work correctly when the name of the directory being
226 //XXX displayed ends with "!", but then again, jar: URIs don't deal
227 //XXX particularly well with such directories anyway
228 if (!StringEndsWith(path, NS_LITERAL_CSTRING("!/"))) {
229 rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
230 if (NS_FAILED(rv)) return rv;
231 }
232 }
233 else {
234 // default behavior for other protocols is to assume the channel's
235 // URL references a directory ending in '/' -- fixup if necessary.
236 nsAutoCString path;
237 rv = uri->GetPath(path);
238 if (NS_FAILED(rv)) return rv;
239 if (baseUri.Last() != '/') {
240 baseUri.Append('/');
241 path.Append('/');
242 uri->SetPath(path);
243 }
244 if (!path.EqualsLiteral("/")) {
245 rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
246 if (NS_FAILED(rv)) return rv;
247 }
248 }
249
250 buffer.AppendLiteral("<style type=\"text/css\">\n"
251 ":root {\n"
252 " font-family: sans-serif;\n"
253 "}\n"
254 "img {\n"
255 " border: 0;\n"
256 "}\n"
257 "th {\n"
258 " text-align: start;\n"
259 " white-space: nowrap;\n"
260 "}\n"
261 "th > a {\n"
262 " color: inherit;\n"
263 "}\n"
264 "table[order] > thead > tr > th {\n"
265 " cursor: pointer;\n"
266 "}\n"
267 "table[order] > thead > tr > th::after {\n"
268 " display: none;\n"
269 " width: .8em;\n"
270 " -moz-margin-end: -.8em;\n"
271 " text-align: end;\n"
272 "}\n"
273 "table[order=\"asc\"] > thead > tr > th::after {\n"
274 " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n"
275 "}\n"
276 "table[order=\"desc\"] > thead > tr > th::after {\n"
277 " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n"
278 "}\n"
279 "table[order][order-by=\"0\"] > thead > tr > th:first-child > a ,\n"
280 "table[order][order-by=\"1\"] > thead > tr > th:first-child + th > a ,\n"
281 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th > a {\n"
282 " text-decoration: underline;\n"
283 "}\n"
284 "table[order][order-by=\"0\"] > thead > tr > th:first-child::after ,\n"
285 "table[order][order-by=\"1\"] > thead > tr > th:first-child + th::after ,\n"
286 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th::after {\n"
287 " display: inline-block;\n"
288 "}\n"
289 "table.remove-hidden > tbody > tr.hidden-object {\n"
290 " display: none;\n"
291 "}\n"
292 "td {\n"
293 " white-space: nowrap;\n"
294 "}\n"
295 "table.ellipsis {\n"
296 " width: 100%;\n"
297 " table-layout: fixed;\n"
298 " border-spacing: 0;\n"
299 "}\n"
300 "table.ellipsis > tbody > tr > td {\n"
301 " padding: 0;\n"
302 " overflow: hidden;\n"
303 " text-overflow: ellipsis;\n"
304 "}\n"
305 "/* name */\n"
306 "/* name */\n"
307 "th:first-child {\n"
308 " -moz-padding-end: 2em;\n"
309 "}\n"
310 "/* size */\n"
311 "th:first-child + th {\n"
312 " -moz-padding-end: 1em;\n"
313 "}\n"
314 "td:first-child + td {\n"
315 " text-align: end;\n"
316 " -moz-padding-end: 1em;\n"
317 "}\n"
318 "/* date */\n"
319 "td:first-child + td + td {\n"
320 " -moz-padding-start: 1em;\n"
321 " -moz-padding-end: .5em;\n"
322 "}\n"
323 "/* time */\n"
324 "td:first-child + td + td + td {\n"
325 " -moz-padding-start: .5em;\n"
326 "}\n"
327 ".symlink {\n"
328 " font-style: italic;\n"
329 "}\n"
330 ".dir ,\n"
331 ".symlink ,\n"
332 ".file {\n"
333 " -moz-margin-start: 20px;\n"
334 "}\n"
335 ".dir::before ,\n"
336 ".file > img {\n"
337 " -moz-margin-end: 4px;\n"
338 " -moz-margin-start: -20px;\n"
339 " max-width: 16px;\n"
340 " max-height: 16px;\n"
341 " vertical-align: middle;\n"
342 "}\n"
343 ".dir::before {\n"
344 " content: url(resource://gre/res/html/folder.png);\n"
345 "}\n"
346 "</style>\n"
347 "<link rel=\"stylesheet\" media=\"screen, projection\" type=\"text/css\""
348 " href=\"chrome://global/skin/dirListing/dirListing.css\">\n"
349 "<script type=\"application/javascript\">\n"
350 "var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;\n"
351 "document.addEventListener(\"DOMContentLoaded\", function() {\n"
352 " gTable = document.getElementsByTagName(\"table\")[0];\n"
353 " gTBody = gTable.tBodies[0];\n"
354 " if (gTBody.rows.length < 2)\n"
355 " return;\n"
356 " gUI_showHidden = document.getElementById(\"UI_showHidden\");\n"
357 " var headCells = gTable.tHead.rows[0].cells,\n"
358 " hiddenObjects = false;\n"
359 " function rowAction(i) {\n"
360 " return function(event) {\n"
361 " event.preventDefault();\n"
362 " orderBy(i);\n"
363 " }\n"
364 " }\n"
365 " for (var i = headCells.length - 1; i >= 0; i--) {\n"
366 " var anchor = document.createElement(\"a\");\n"
367 " anchor.href = \"\";\n"
368 " anchor.appendChild(headCells[i].firstChild);\n"
369 " headCells[i].appendChild(anchor);\n"
370 " headCells[i].addEventListener(\"click\", rowAction(i), true);\n"
371 " }\n"
372 " if (gUI_showHidden) {\n"
373 " gRows = Array.slice(gTBody.rows);\n"
374 " hiddenObjects = gRows.some(function (row) row.className == \"hidden-object\");\n"
375 " }\n"
376 " gTable.setAttribute(\"order\", \"\");\n"
377 " if (hiddenObjects) {\n"
378 " gUI_showHidden.style.display = \"block\";\n"
379 " updateHidden();\n"
380 " }\n"
381 "}, \"false\");\n"
382 "function compareRows(rowA, rowB) {\n"
383 " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n"
384 " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n"
385 " var intA = +a;\n"
386 " var intB = +b;\n"
387 " if (a == intA && b == intB) {\n"
388 " a = intA;\n"
389 " b = intB;\n"
390 " } else {\n"
391 " a = a.toLowerCase();\n"
392 " b = b.toLowerCase();\n"
393 " }\n"
394 " if (a < b)\n"
395 " return -1;\n"
396 " if (a > b)\n"
397 " return 1;\n"
398 " return 0;\n"
399 "}\n"
400 "function orderBy(column) {\n"
401 " if (!gRows)\n"
402 " gRows = Array.slice(gTBody.rows);\n"
403 " var order;\n"
404 " if (gOrderBy == column) {\n"
405 " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : \"asc\";\n"
406 " } else {\n"
407 " order = \"asc\";\n"
408 " gOrderBy = column;\n"
409 " gTable.setAttribute(\"order-by\", column);\n"
410 " gRows.sort(compareRows);\n"
411 " }\n"
412 " gTable.removeChild(gTBody);\n"
413 " gTable.setAttribute(\"order\", order);\n"
414 " if (order == \"asc\")\n"
415 " for (var i = 0; i < gRows.length; i++)\n"
416 " gTBody.appendChild(gRows[i]);\n"
417 " else\n"
418 " for (var i = gRows.length - 1; i >= 0; i--)\n"
419 " gTBody.appendChild(gRows[i]);\n"
420 " gTable.appendChild(gTBody);\n"
421 "}\n"
422 "function updateHidden() {\n"
423 " gTable.className = gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n"
424 " \"\" :\n"
425 " \"remove-hidden\";\n"
426 "}\n"
427 "</script>\n");
428
429 buffer.AppendLiteral("<link rel=\"icon\" type=\"image/png\" href=\"");
430 nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
431 if (!innerUri)
432 return NS_ERROR_UNEXPECTED;
433 nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(innerUri));
434 //XXX bug 388553: can't use skinnable icons here due to security restrictions
435 if (fileURL) {
436 //buffer.AppendLiteral("chrome://global/skin/dirListing/local.png");
437 buffer.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
438 "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
439 "ZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR"
440 "3XQ3ONASdBJCSBxHos5%2B3Bg3rvkCv8PElS78gPkO%2FATj"
441 "QoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12O"
442 "YIDhcPiu2Wx%2B%2FHF5CW1Z6Jyegt%2FTNEWSJIjjGFEUIQ"
443 "xDrFYrWFSzXC4%2FdLvd95pRKpXKy%2BpRFZ7nwaWo1%2BsG"
444 "nQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5"
445 "obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM%2FS"
446 "KK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9c"
447 "lkYoDo3SZJzzSdp0VSKYmfV1co%2Bz580kw5KDIM8RbRfEnU"
448 "f1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd%2BzGQQB5llunF"
449 "%2FM52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxi"
450 "xfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18l"
451 "yWh5FFbrOSxbl3V4G%2BVB7T4ajYYxTyuLtO%2BCvWGgJE1M"
452 "c7JNsJEhvgw%2FQV4fo%2F24nbEsX2u1d5sVyn8sJO0ZAQiI"
453 "YnFh%2BxrfLz%2Fj29cBS%2FO14zg3i8XigW3ZkErDtmKoeM"
454 "%2BAJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ%2B"
455 "4pQ1%2FlPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj%2F8m%2B8"
456 "y%2FkrwABHbz2H9V68DQAAAABJRU5ErkJggg%3D%3D");
457 } else {
458 //buffer.AppendLiteral("chrome://global/skin/dirListing/remote.png");
459 buffer.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
460 "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
461 "ZSBJbWFnZVJlYWR5ccllPAAAAeBJREFUeNqcU81O20AQ%2Ft"
462 "Z2AgQSYQRqL1UPVG2hAUQkxLEStz4DrXpLpD5Drz31Cajax%"
463 "2Bghhx6qHIJURBTxIwQRwopCBbZjHMcOTrzermPipsSt1Iw0"
464 "3p3ZmW%2B%2B2R0TxhgOD34wjCHZlQ0iDYz9yvEfhxMTCYhE"
465 "QDIZhkxKd2sqzX2TOD2vBQCQhpPefng1ZP2dVPlLLdpL8SEM"
466 "cxng%2Fbs0RIHhtgs4twxOh%2BHjZxvzDx%2F3GQQiDFISiR"
467 "BLFMPKTRMollzcWECrDVhtxtdRVsL9youPxGj%2FbdfFlUZh"
468 "tDyYbYqWRUdai1oQRZ5oHeHl2gNM%2B01Uqio8RlH%2Bnsaz"
469 "JzNwXcq1B%2BiXPHprlEEymeBfXs1w8XxxihfyuXqoHqpoGj"
470 "ZM04bddgG%2F9%2B8WGj87qDdsrK9m%2BoA%2BpbhQTDh2l1"
471 "%2Bi2weNbSHMZyjvNXmVbqh9Fj5Oz27uEoP%2BSTxANruJs9"
472 "L%2FT6P0ewqPx5nmiAG5f6AoCtN1PbJzuRyJAyDBzzSQYvEr"
473 "f06yYxhGXlEa8H2KVGoasjwLx3Ewk858opQWXm%2B%2Fib9E"
474 "QrBzclLLLy89xYvlpchvtixcX6uo1y%2FzsiwHrkIsgKbp%2"
475 "BYWFOWicuqppoNTnStHzPFCPQhBEBOyGAX4JMADFetubi4BS"
476 "YAAAAABJRU5ErkJggg%3D%3D");
477 }
478 buffer.AppendLiteral("\">\n<title>");
479
480 // Everything needs to end in a /,
481 // otherwise we end up linking to file:///foo/dirfile
482
483 if (!mTextToSubURI) {
484 mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
485 if (NS_FAILED(rv)) return rv;
486 }
487
488 nsXPIDLCString encoding;
489 rv = uri->GetOriginCharset(encoding);
490 if (NS_FAILED(rv)) return rv;
491 if (encoding.IsEmpty()) {
492 encoding.AssignLiteral("UTF-8");
493 }
494
495 nsXPIDLString unEscapeSpec;
496 rv = mTextToSubURI->UnEscapeAndConvert(encoding, titleUri.get(),
497 getter_Copies(unEscapeSpec));
498 // unescape may fail because
499 // 1. file URL may be encoded in platform charset for backward compatibility
500 // 2. query part may not be encoded in UTF-8 (see bug 261929)
501 // so try the platform's default if this is file url
502 if (NS_FAILED(rv) && isSchemeFile) {
503 nsCOMPtr<nsIPlatformCharset> platformCharset(do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv));
504 NS_ENSURE_SUCCESS(rv, rv);
505 nsAutoCString charset;
506 rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName, charset);
507 NS_ENSURE_SUCCESS(rv, rv);
508
509 rv = mTextToSubURI->UnEscapeAndConvert(charset.get(), titleUri.get(),
510 getter_Copies(unEscapeSpec));
511 }
512 if (NS_FAILED(rv)) return rv;
513
514 nsXPIDLString htmlEscSpec;
515 htmlEscSpec.Adopt(nsEscapeHTML2(unEscapeSpec.get(),
516 unEscapeSpec.Length()));
517
518 nsXPIDLString title;
519 const char16_t* formatTitle[] = {
520 htmlEscSpec.get()
521 };
522
523 rv = mBundle->FormatStringFromName(MOZ_UTF16("DirTitle"),
524 formatTitle,
525 sizeof(formatTitle)/sizeof(char16_t*),
526 getter_Copies(title));
527 if (NS_FAILED(rv)) return rv;
528
529 // we want to convert string bundle to NCR
530 // to ensure they're shown in any charsets
531 AppendNonAsciiToNCR(title, buffer);
532
533 buffer.AppendLiteral("</title>\n");
534
535 // If there is a quote character in the baseUri, then
536 // lets not add a base URL. The reason for this is that
537 // if we stick baseUri containing a quote into a quoted
538 // string, the quote character will prematurely close
539 // the base href string. This is a fall-back check;
540 // that's why it is OK to not use a base rather than
541 // trying to play nice and escaping the quotes. See bug
542 // 358128.
543
544 if (baseUri.FindChar('"') == kNotFound)
545 {
546 // Great, the baseUri does not contain a char that
547 // will prematurely close the string. Go ahead an
548 // add a base href.
549 buffer.AppendLiteral("<base href=\"");
550 nsAdoptingCString htmlEscapedUri(nsEscapeHTML(baseUri.get()));
551 buffer.Append(htmlEscapedUri);
552 buffer.AppendLiteral("\" />\n");
553 }
554 else
555 {
556 NS_ERROR("broken protocol handler didn't escape double-quote.");
557 }
558
559 nsCString direction(NS_LITERAL_CSTRING("ltr"));
560 nsCOMPtr<nsIXULChromeRegistry> reg =
561 mozilla::services::GetXULChromeRegistryService();
562 if (reg) {
563 bool isRTL = false;
564 reg->IsLocaleRTL(NS_LITERAL_CSTRING("global"), &isRTL);
565 if (isRTL) {
566 direction.AssignLiteral("rtl");
567 }
568 }
569
570 buffer.AppendLiteral("</head>\n<body dir=\"");
571 buffer.Append(direction);
572 buffer.AppendLiteral("\">\n<h1>");
573
574 const char16_t* formatHeading[] = {
575 htmlEscSpec.get()
576 };
577
578 rv = mBundle->FormatStringFromName(MOZ_UTF16("DirTitle"),
579 formatHeading,
580 sizeof(formatHeading)/sizeof(char16_t*),
581 getter_Copies(title));
582 if (NS_FAILED(rv)) return rv;
583
584 AppendNonAsciiToNCR(title, buffer);
585 buffer.AppendLiteral("</h1>\n");
586
587 if (!parentStr.IsEmpty()) {
588 nsXPIDLString parentText;
589 rv = mBundle->GetStringFromName(MOZ_UTF16("DirGoUp"),
590 getter_Copies(parentText));
591 if (NS_FAILED(rv)) return rv;
592
593 buffer.AppendLiteral("<p id=\"UI_goUp\"><a class=\"up\" href=\"");
594
595 nsAdoptingCString htmlParentStr(nsEscapeHTML(parentStr.get()));
596 buffer.Append(htmlParentStr);
597 buffer.AppendLiteral("\">");
598 AppendNonAsciiToNCR(parentText, buffer);
599 buffer.AppendLiteral("</a></p>\n");
600 }
601
602 if (isSchemeFile) {
603 nsXPIDLString showHiddenText;
604 rv = mBundle->GetStringFromName(MOZ_UTF16("ShowHidden"),
605 getter_Copies(showHiddenText));
606 if (NS_FAILED(rv)) return rv;
607
608 buffer.AppendLiteral("<p id=\"UI_showHidden\" style=\"display:none\"><label><input type=\"checkbox\" checked onchange=\"updateHidden()\">");
609 AppendNonAsciiToNCR(showHiddenText, buffer);
610 buffer.AppendLiteral("</label></p>\n");
611 }
612
613 buffer.AppendLiteral("<table>\n");
614
615 nsXPIDLString columnText;
616
617 buffer.AppendLiteral(" <thead>\n"
618 " <tr>\n"
619 " <th>");
620
621 rv = mBundle->GetStringFromName(MOZ_UTF16("DirColName"),
622 getter_Copies(columnText));
623 if (NS_FAILED(rv)) return rv;
624 AppendNonAsciiToNCR(columnText, buffer);
625 buffer.AppendLiteral("</th>\n"
626 " <th>");
627
628 rv = mBundle->GetStringFromName(MOZ_UTF16("DirColSize"),
629 getter_Copies(columnText));
630 if (NS_FAILED(rv)) return rv;
631 AppendNonAsciiToNCR(columnText, buffer);
632 buffer.AppendLiteral("</th>\n"
633 " <th colspan=\"2\">");
634
635 rv = mBundle->GetStringFromName(MOZ_UTF16("DirColMTime"),
636 getter_Copies(columnText));
637 if (NS_FAILED(rv)) return rv;
638 AppendNonAsciiToNCR(columnText, buffer);
639 buffer.AppendLiteral("</th>\n"
640 " </tr>\n"
641 " </thead>\n");
642 buffer.AppendLiteral(" <tbody>\n");
643
644 aBuffer = buffer;
645 return rv;
646 }
647
648 NS_IMETHODIMP
649 nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsISupports *aContext,
650 nsresult aStatus) {
651 if (NS_SUCCEEDED(aStatus)) {
652 nsCString buffer;
653 buffer.AssignLiteral("</tbody></table></body></html>\n");
654
655 aStatus = SendToListener(request, aContext, buffer);
656 }
657
658 mParser->OnStopRequest(request, aContext, aStatus);
659 mParser = 0;
660
661 return mListener->OnStopRequest(request, aContext, aStatus);
662 }
663
664 nsresult
665 nsIndexedToHTML::SendToListener(nsIRequest* aRequest, nsISupports *aContext, const nsACString &aBuffer)
666 {
667 nsCOMPtr<nsIInputStream> inputData;
668 nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputData), aBuffer);
669 NS_ENSURE_SUCCESS(rv, rv);
670 return mListener->OnDataAvailable(aRequest, aContext,
671 inputData, 0, aBuffer.Length());
672 }
673
674 NS_IMETHODIMP
675 nsIndexedToHTML::OnDataAvailable(nsIRequest *aRequest,
676 nsISupports *aCtxt,
677 nsIInputStream* aInput,
678 uint64_t aOffset,
679 uint32_t aCount) {
680 return mParser->OnDataAvailable(aRequest, aCtxt, aInput, aOffset, aCount);
681 }
682
683 NS_IMETHODIMP
684 nsIndexedToHTML::OnIndexAvailable(nsIRequest *aRequest,
685 nsISupports *aCtxt,
686 nsIDirIndex *aIndex) {
687 nsresult rv;
688 if (!aIndex)
689 return NS_ERROR_NULL_POINTER;
690
691 nsCString pushBuffer;
692 pushBuffer.AppendLiteral("<tr");
693
694 // We don't know the file's character set yet, so retrieve the raw bytes
695 // which will be decoded by the HTML parser.
696 nsXPIDLCString loc;
697 aIndex->GetLocation(getter_Copies(loc));
698
699 // Adjust the length in case unescaping shortened the string.
700 loc.Truncate(nsUnescapeCount(loc.BeginWriting()));
701 if (loc.First() == PRUnichar('.'))
702 pushBuffer.AppendLiteral(" class=\"hidden-object\"");
703
704 pushBuffer.AppendLiteral(">\n <td sortable-data=\"");
705
706 // The sort key is the name of the item, prepended by either 0, 1 or 2
707 // in order to group items.
708 uint32_t type;
709 aIndex->GetType(&type);
710 switch (type) {
711 case nsIDirIndex::TYPE_SYMLINK:
712 pushBuffer.Append('0');
713 break;
714 case nsIDirIndex::TYPE_DIRECTORY:
715 pushBuffer.Append('1');
716 break;
717 default:
718 pushBuffer.Append('2');
719 break;
720 }
721 nsAdoptingCString escaped(nsEscapeHTML(loc));
722 pushBuffer.Append(escaped);
723
724 pushBuffer.AppendLiteral("\"><table class=\"ellipsis\"><tbody><tr><td><a class=\"");
725 switch (type) {
726 case nsIDirIndex::TYPE_DIRECTORY:
727 pushBuffer.AppendLiteral("dir");
728 break;
729 case nsIDirIndex::TYPE_SYMLINK:
730 pushBuffer.AppendLiteral("symlink");
731 break;
732 default:
733 pushBuffer.AppendLiteral("file");
734 break;
735 }
736
737 pushBuffer.AppendLiteral("\" href=\"");
738
739 // need to escape links
740 nsAutoCString locEscaped;
741
742 // Adding trailing slash helps to recognize whether the URL points to a file
743 // or a directory (bug #214405).
744 if ((type == nsIDirIndex::TYPE_DIRECTORY) && (loc.Last() != '/')) {
745 loc.Append('/');
746 }
747
748 // now minimally re-escape the location...
749 uint32_t escFlags;
750 // for some protocols, we expect the location to be absolute.
751 // if so, and if the location indeed appears to be a valid URI, then go
752 // ahead and treat it like one.
753 if (mExpectAbsLoc &&
754 NS_SUCCEEDED(net_ExtractURLScheme(loc, nullptr, nullptr, nullptr))) {
755 // escape as absolute
756 escFlags = esc_Forced | esc_AlwaysCopy | esc_Minimal;
757 }
758 else {
759 // escape as relative
760 // esc_Directory is needed because directories have a trailing slash.
761 // Without it, the trailing '/' will be escaped, and links from within
762 // that directory will be incorrect
763 escFlags = esc_Forced | esc_AlwaysCopy | esc_FileBaseName | esc_Colon | esc_Directory;
764 }
765 NS_EscapeURL(loc.get(), loc.Length(), escFlags, locEscaped);
766 // esc_Directory does not escape the semicolons, so if a filename
767 // contains semicolons we need to manually escape them.
768 // This replacement should be removed in bug #473280
769 locEscaped.ReplaceSubstring(";", "%3b");
770 nsAdoptingCString htmlEscapedURL(nsEscapeHTML(locEscaped.get()));
771 pushBuffer.Append(htmlEscapedURL);
772
773 pushBuffer.AppendLiteral("\">");
774
775 if (type == nsIDirIndex::TYPE_FILE || type == nsIDirIndex::TYPE_UNKNOWN) {
776 pushBuffer.AppendLiteral("<img src=\"moz-icon://");
777 int32_t lastDot = locEscaped.RFindChar('.');
778 if (lastDot != kNotFound) {
779 locEscaped.Cut(0, lastDot);
780 nsAdoptingCString htmlFileExt(nsEscapeHTML(locEscaped.get()));
781 pushBuffer.Append(htmlFileExt);
782 } else {
783 pushBuffer.AppendLiteral("unknown");
784 }
785 pushBuffer.AppendLiteral("?size=16\" alt=\"");
786
787 nsXPIDLString altText;
788 rv = mBundle->GetStringFromName(MOZ_UTF16("DirFileLabel"),
789 getter_Copies(altText));
790 if (NS_FAILED(rv)) return rv;
791 AppendNonAsciiToNCR(altText, pushBuffer);
792 pushBuffer.AppendLiteral("\">");
793 }
794
795 pushBuffer.Append(escaped);
796 pushBuffer.AppendLiteral("</a></td></tr></tbody></table></td>\n <td");
797
798 if (type == nsIDirIndex::TYPE_DIRECTORY || type == nsIDirIndex::TYPE_SYMLINK) {
799 pushBuffer.AppendLiteral(">");
800 } else {
801 int64_t size;
802 aIndex->GetSize(&size);
803
804 if (uint64_t(size) != UINT64_MAX) {
805 pushBuffer.AppendLiteral(" sortable-data=\"");
806 pushBuffer.AppendInt(size);
807 pushBuffer.AppendLiteral("\">");
808 nsAutoCString sizeString;
809 FormatSizeString(size, sizeString);
810 pushBuffer.Append(sizeString);
811 } else {
812 pushBuffer.AppendLiteral(">");
813 }
814 }
815 pushBuffer.AppendLiteral("</td>\n <td");
816
817 PRTime t;
818 aIndex->GetLastModified(&t);
819
820 if (t == -1) {
821 pushBuffer.AppendLiteral("></td>\n <td>");
822 } else {
823 pushBuffer.AppendLiteral(" sortable-data=\"");
824 pushBuffer.AppendInt(static_cast<int64_t>(t));
825 pushBuffer.AppendLiteral("\">");
826 nsAutoString formatted;
827 mDateTime->FormatPRTime(nullptr,
828 kDateFormatShort,
829 kTimeFormatNone,
830 t,
831 formatted);
832 AppendNonAsciiToNCR(formatted, pushBuffer);
833 pushBuffer.AppendLiteral("</td>\n <td>");
834 mDateTime->FormatPRTime(nullptr,
835 kDateFormatNone,
836 kTimeFormatSeconds,
837 t,
838 formatted);
839 // use NCR to show date in any doc charset
840 AppendNonAsciiToNCR(formatted, pushBuffer);
841 }
842
843 pushBuffer.AppendLiteral("</td>\n</tr>");
844
845 return SendToListener(aRequest, aCtxt, pushBuffer);
846 }
847
848 NS_IMETHODIMP
849 nsIndexedToHTML::OnInformationAvailable(nsIRequest *aRequest,
850 nsISupports *aCtxt,
851 const nsAString& aInfo) {
852 nsAutoCString pushBuffer;
853 nsAdoptingString escaped(nsEscapeHTML2(PromiseFlatString(aInfo).get()));
854 if (!escaped)
855 return NS_ERROR_OUT_OF_MEMORY;
856 pushBuffer.AppendLiteral("<tr>\n <td>");
857 // escaped is provided in Unicode, so write hex NCRs as necessary
858 // to prevent the HTML parser from applying a character set.
859 AppendNonAsciiToNCR(escaped, pushBuffer);
860 pushBuffer.AppendLiteral("</td>\n <td></td>\n <td></td>\n <td></td>\n</tr>\n");
861
862 return SendToListener(aRequest, aCtxt, pushBuffer);
863 }
864
865 void nsIndexedToHTML::FormatSizeString(int64_t inSize, nsCString& outSizeString)
866 {
867 outSizeString.Truncate();
868 if (inSize > int64_t(0)) {
869 // round up to the nearest Kilobyte
870 int64_t upperSize = (inSize + int64_t(1023)) / int64_t(1024);
871 outSizeString.AppendInt(upperSize);
872 outSizeString.AppendLiteral(" KB");
873 }
874 }
875
876 nsIndexedToHTML::nsIndexedToHTML() {
877 }
878
879 nsIndexedToHTML::~nsIndexedToHTML() {
880 }

mercurial