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