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