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/Encoding.h"
9 #include "mozilla/intl/AppDateTimeFormat.h"
10 #include "mozilla/intl/LocaleService.h"
11 #include "nsIThreadRetargetableStreamListener.h"
12 #include "nsNetUtil.h"
14 #include "nsStringStream.h"
16 #include "nsIFileURL.h"
18 #include "nsIDirIndex.h"
19 #include "nsURLHelper.h"
20 #include "nsIStringBundle.h"
21 #include "nsDirIndexParser.h"
22 #include "nsNativeCharsetUtils.h"
24 #include "nsContentUtils.h"
26 #include "nsIChannel.h"
27 #include "mozilla/Unused.h"
28 #include "nsIURIMutator.h"
29 #include "nsITextToSubURI.h"
31 using mozilla::intl::LocaleService
;
32 using namespace mozilla
;
34 NS_IMPL_ISUPPORTS(nsIndexedToHTML
, nsIDirIndexListener
, nsIStreamConverter
,
35 nsIThreadRetargetableStreamListener
, nsIRequestObserver
,
38 static void AppendNonAsciiToNCR(const nsAString
& in
, nsCString
& out
) {
39 nsAString::const_iterator start
, end
;
41 in
.BeginReading(start
);
44 while (start
!= end
) {
48 out
.AppendLiteral("&#x");
49 out
.AppendInt(*start
++, 16);
55 nsresult
nsIndexedToHTML::Create(REFNSIID aIID
, void** aResult
) {
58 nsIndexedToHTML
* _s
= new nsIndexedToHTML();
59 if (_s
== nullptr) return NS_ERROR_OUT_OF_MEMORY
;
61 rv
= _s
->QueryInterface(aIID
, aResult
);
65 nsresult
nsIndexedToHTML::Init(nsIStreamListener
* aListener
) {
68 mListener
= aListener
;
70 nsCOMPtr
<nsIStringBundleService
> sbs
=
71 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
72 if (NS_FAILED(rv
)) return rv
;
73 rv
= sbs
->CreateBundle(NECKO_MSGS_URL
, getter_AddRefs(mBundle
));
75 mExpectAbsLoc
= false;
81 nsIndexedToHTML::Convert(nsIInputStream
* aFromStream
, const char* aFromType
,
82 const char* aToType
, nsISupports
* aCtxt
,
83 nsIInputStream
** res
) {
84 return NS_ERROR_NOT_IMPLEMENTED
;
88 nsIndexedToHTML::AsyncConvertData(const char* aFromType
, const char* aToType
,
89 nsIStreamListener
* aListener
,
91 return Init(aListener
);
95 nsIndexedToHTML::GetConvertedType(const nsACString
& aFromType
,
96 nsIChannel
* aChannel
, nsACString
& aToType
) {
97 return NS_ERROR_NOT_IMPLEMENTED
;
101 nsIndexedToHTML::MaybeRetarget(nsIRequest
* request
) {
102 return NS_ERROR_NOT_IMPLEMENTED
;
106 nsIndexedToHTML::OnStartRequest(nsIRequest
* request
) {
108 nsresult rv
= DoOnStartRequest(request
, buffer
);
113 rv
= mListener
->OnStartRequest(request
);
114 if (NS_FAILED(rv
)) return rv
;
116 // The request may have been canceled, and if that happens, we want to
117 // suppress calls to OnDataAvailable.
118 request
->GetStatus(&rv
);
119 if (NS_FAILED(rv
)) return rv
;
121 // Push our buffer to the listener.
123 rv
= SendToListener(request
, buffer
);
127 nsresult
nsIndexedToHTML::DoOnStartRequest(nsIRequest
* request
,
128 nsCString
& aBuffer
) {
131 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
132 nsCOMPtr
<nsIURI
> uri
;
133 rv
= channel
->GetOriginalURI(getter_AddRefs(uri
));
134 if (NS_FAILED(rv
)) return rv
;
136 // We use the original URI for the title and parent link when it's a
137 // resource:// url, instead of the jar:file:// url it resolves to.
138 if (!uri
->SchemeIs("resource")) {
139 rv
= channel
->GetURI(getter_AddRefs(uri
));
140 if (NS_FAILED(rv
)) return rv
;
143 channel
->SetContentType("text/html"_ns
);
145 mParser
= nsDirIndexParser::CreateInstance();
146 if (!mParser
) return NS_ERROR_FAILURE
;
148 rv
= mParser
->SetListener(this);
149 if (NS_FAILED(rv
)) return rv
;
151 rv
= mParser
->OnStartRequest(request
);
152 if (NS_FAILED(rv
)) return rv
;
154 nsAutoCString baseUri
, titleUri
;
155 rv
= uri
->GetAsciiSpec(baseUri
);
156 if (NS_FAILED(rv
)) return rv
;
158 nsCOMPtr
<nsIURI
> titleURL
;
159 rv
= NS_MutateURI(uri
).SetQuery(""_ns
).SetRef(""_ns
).Finalize(titleURL
);
167 buffer
.AppendLiteral("<!DOCTYPE html>\n<html>\n<head>\n");
169 // XXX - should be using the 300: line from the parser.
170 // We can't guarantee that that comes before any entry, so we'd have to
171 // buffer, and do other painful stuff.
172 // I'll deal with this when I make the changes to handle welcome messages
173 // The .. stuff should also come from the lower level protocols, but that
174 // would muck up the XUL display
177 if (uri
->SchemeIs("file")) {
178 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(uri
);
179 nsCOMPtr
<nsIFile
> file
;
180 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
181 if (NS_FAILED(rv
)) return rv
;
184 rv
= net_GetURLSpecFromFile(file
, url
);
185 if (NS_FAILED(rv
)) return rv
;
188 nsCOMPtr
<nsIFile
> parent
;
189 rv
= file
->GetParent(getter_AddRefs(parent
));
191 if (parent
&& NS_SUCCEEDED(rv
)) {
192 net_GetURLSpecFromDir(parent
, url
);
193 if (NS_FAILED(rv
)) return rv
;
194 parentStr
.Assign(url
);
197 // Directory index will be always encoded in UTF-8 if this is file url
198 buffer
.AppendLiteral("<meta charset=\"UTF-8\">\n");
200 } else if (uri
->SchemeIs("jar")) {
202 rv
= uri
->GetPathQueryRef(path
);
203 if (NS_FAILED(rv
)) return rv
;
205 // a top-level jar directory URL is of the form jar:foo.zip!/
206 // path will be of the form foo.zip!/, and its last two characters
208 // XXX this won't work correctly when the name of the directory being
209 // XXX displayed ends with "!", but then again, jar: URIs don't deal
210 // XXX particularly well with such directories anyway
211 if (!StringEndsWith(path
, "!/"_ns
)) {
212 rv
= uri
->Resolve(".."_ns
, parentStr
);
213 if (NS_FAILED(rv
)) return rv
;
216 // default behavior for other protocols is to assume the channel's
217 // URL references a directory ending in '/' -- fixup if necessary.
219 rv
= uri
->GetPathQueryRef(path
);
220 if (NS_FAILED(rv
)) return rv
;
221 if (baseUri
.Last() != '/') {
224 mozilla::Unused
<< NS_MutateURI(uri
).SetPathQueryRef(path
).Finalize(uri
);
226 if (!path
.EqualsLiteral("/")) {
227 rv
= uri
->Resolve(".."_ns
, parentStr
);
228 if (NS_FAILED(rv
)) return rv
;
232 rv
= titleURL
->GetAsciiSpec(titleUri
);
237 buffer
.AppendLiteral(
238 "<style type=\"text/css\">\n"
240 " font-family: sans-serif;\n"
246 " text-align: start;\n"
247 " white-space: nowrap;\n"
252 "table[order] > thead > tr > th {\n"
253 " cursor: pointer;\n"
255 "table[order] > thead > tr > th::after {\n"
258 " margin-inline-end: -.8em;\n"
259 " text-align: end;\n"
261 "table[order=\"asc\"] > thead > tr > th::after {\n"
262 " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n"
264 "table[order=\"desc\"] > thead > tr > th::after {\n"
265 " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n"
267 "table[order][order-by=\"0\"] > thead > tr > th:first-child > a ,\n"
268 "table[order][order-by=\"1\"] > thead > tr > th:first-child + th > a ,\n"
269 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th > "
271 " text-decoration: underline;\n"
273 "table[order][order-by=\"0\"] > thead > tr > th:first-child::after ,\n"
274 "table[order][order-by=\"1\"] > thead > tr > th:first-child + th::after "
276 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + "
278 " display: inline-block;\n"
280 "table.remove-hidden > tbody > tr.hidden-object {\n"
284 " white-space: nowrap;\n"
288 " table-layout: fixed;\n"
289 " border-spacing: 0;\n"
291 "table.ellipsis > tbody > tr > td {\n"
292 " overflow: hidden;\n"
293 " text-overflow: ellipsis;\n"
298 " padding-inline-end: 2em;\n"
301 "th:first-child + th {\n"
302 " padding-inline-end: 1em;\n"
304 "td:first-child + td {\n"
305 " text-align: end;\n"
306 " padding-inline-end: 1em;\n"
309 "td:first-child + td + td {\n"
310 " padding-inline-start: 1em;\n"
311 " padding-inline-end: .5em;\n"
314 "td:first-child + td + td + td {\n"
315 " padding-inline-start: .5em;\n"
318 " font-style: italic;\n"
323 " margin-inline-start: 20px;\n"
327 " margin-inline-end: 4px;\n"
328 " margin-inline-start: -20px;\n"
329 " max-width: 16px;\n"
330 " max-height: 16px;\n"
331 " vertical-align: middle;\n"
334 " content: url(resource://content-accessible/html/folder.png);\n"
337 "<link rel=\"stylesheet\" media=\"screen, projection\" type=\"text/css\""
338 " href=\"chrome://global/skin/dirListing/dirListing.css\">\n"
339 "<script type=\"application/javascript\">\n"
341 "var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;\n"
342 "document.addEventListener(\"DOMContentLoaded\", function() {\n"
343 " gTable = document.getElementsByTagName(\"table\")[0];\n"
344 " gTBody = gTable.tBodies[0];\n"
345 " if (gTBody.rows.length < 2)\n"
347 " gUI_showHidden = document.getElementById(\"UI_showHidden\");\n"
348 " var headCells = gTable.tHead.rows[0].cells,\n"
349 " hiddenObjects = false;\n"
350 " function rowAction(i) {\n"
351 " return function(event) {\n"
352 " event.preventDefault();\n"
356 " for (var i = headCells.length - 1; i >= 0; i--) {\n"
357 " var anchor = document.createElement(\"a\");\n"
358 " anchor.href = \"\";\n"
359 " anchor.appendChild(headCells[i].firstChild);\n"
360 " headCells[i].appendChild(anchor);\n"
361 " headCells[i].addEventListener(\"click\", rowAction(i), true);\n"
363 " if (gUI_showHidden) {\n"
364 " gRows = Array.from(gTBody.rows);\n"
365 " hiddenObjects = gRows.some(row => row.className == "
366 "\"hidden-object\");\n"
368 " gTable.setAttribute(\"order\", \"\");\n"
369 " if (hiddenObjects) {\n"
370 " gUI_showHidden.style.display = \"block\";\n"
374 "function compareRows(rowA, rowB) {\n"
375 " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || "
377 " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || "
381 " if (a == intA && b == intB) {\n"
385 " a = a.toLowerCase();\n"
386 " b = b.toLowerCase();\n"
394 "function orderBy(column) {\n"
396 " gRows = Array.from(gTBody.rows);\n"
398 " if (gOrderBy == column) {\n"
399 " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : "
402 " order = \"asc\";\n"
403 " gOrderBy = column;\n"
404 " gTable.setAttribute(\"order-by\", column);\n"
405 " gRows.sort(compareRows);\n"
407 " gTable.removeChild(gTBody);\n"
408 " gTable.setAttribute(\"order\", order);\n"
409 " if (order == \"asc\")\n"
410 " for (var i = 0; i < gRows.length; i++)\n"
411 " gTBody.appendChild(gRows[i]);\n"
413 " for (var i = gRows.length - 1; i >= 0; i--)\n"
414 " gTBody.appendChild(gRows[i]);\n"
415 " gTable.appendChild(gTBody);\n"
417 "function updateHidden() {\n"
418 " gTable.className = "
419 "gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n"
421 " \"remove-hidden\";\n"
425 buffer
.AppendLiteral(R
"(<link rel="icon
" type="image
/png
" href=")");
426 nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
427 if (!innerUri) return NS_ERROR_UNEXPECTED;
428 nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(innerUri));
429 // XXX bug 388553: can't use skinnable icons here due to security restrictions
431 buffer.AppendLiteral(
432 "data
:image
/png
;base64
,iVBORw0KGgoAAAANSUhEUgAAAB
"
433 "AAAAAQCAYAAAAf8
%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i
"
434 "ZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR
"
435 "3XQ3ONASdBJCSBxHos5
%2B3Bg3rvkCv8PElS78gPkO
%2FATj
"
436 "QoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12O
"
437 "YIDhcPiu2Wx
%2B
%2FHF5CW1Z6Jyegt
%2FTNEWSJIjjGFEUIQ
"
438 "xDrFYrWFSzXC4
%2FdLvd95pRKpXKy
%2BpRFZ7nwaWo1
%2BsG
"
439 "nQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5
"
440 "obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM
%2FS
"
441 "KK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9c
"
442 "lkYoDo3SZJzzSdp0VSKYmfV1co
%2Bz580kw5KDIM8RbRfEnU
"
443 "f1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd
%2BzGQQB5llunF
"
444 "%2FM52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxi
"
445 "xfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18l
"
446 "yWh5FFbrOSxbl3V4G
%2BVB7T4ajYYxTyuLtO
%2BCvWGgJE1M
"
447 "c7JNsJEhvgw
%2FQV4fo
%2F24nbEsX2u1d5sVyn8sJO0ZAQiI
"
448 "YnFh
%2BxrfLz
%2Fj29cBS
%2FO14zg3i8XigW3ZkErDtmKoeM
"
449 "%2BAJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ
%2B
"
450 "4pQ1
%2FlPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj
%2F8m
%2B8
"
451 "y
%2FkrwABHbz2H9V68DQAAAABJRU5ErkJggg
%3D
%3D
");
453 buffer.AppendLiteral(
454 "data
:image
/png
;base64
,iVBORw0KGgoAAAANSUhEUgAAAB
"
455 "AAAAAQCAYAAAAf8
%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i
"
456 "ZSBJbWFnZVJlYWR5ccllPAAAAeBJREFUeNqcU81O20AQ
%2Ft
"
457 "Z2AgQSYQRqL1UPVG2hAUQkxLEStz4DrXpLpD5Drz31Cajax
%"
458 "2Bghhx6qHIJURBTxIwQRwopCBbZjHMcOTrzermPipsSt1Iw0
"
459 "3p3ZmW
%2B
%2B2R0TxhgOD34wjCHZlQ0iDYz9yvEfhxMTCYhE
"
460 "QDIZhkxKd2sqzX2TOD2vBQCQhpPefng1ZP2dVPlLLdpL8SEM
"
461 "cxng
%2Fbs0RIHhtgs4twxOh
%2BHjZxvzDx
%2F3GQQiDFISiR
"
462 "BLFMPKTRMollzcWECrDVhtxtdRVsL9youPxGj
%2FbdfFlUZh
"
463 "tDyYbYqWRUdai1oQRZ5oHeHl2gNM
%2B01Uqio8RlH
%2Bnsaz
"
464 "JzNwXcq1B
%2BiXPHprlEEymeBfXs1w8XxxihfyuXqoHqpoGj
"
465 "ZM04bddgG
%2F9
%2B8WGj87qDdsrK9m
%2BoA
%2BpbhQTDh2l1
"
466 "%2Bi2weNbSHMZyjvNXmVbqh9Fj5Oz27uEoP
%2BSTxANruJs9
"
467 "L
%2FT6P0ewqPx5nmiAG5f6AoCtN1PbJzuRyJAyDBzzSQYvEr
"
468 "f06yYxhGXlEa8H2KVGoasjwLx3Ewk858opQWXm
%2B
%2Fib9E
"
469 "QrBzclLLLy89xYvlpchvtixcX6uo1y
%2FzsiwHrkIsgKbp
%2"
470 "BYWFOWicuqppoNTnStHzPFCPQhBEBOyGAX4JMADFetubi4BS
"
471 "YAAAAABJRU5ErkJggg
%3D
%3D
");
473 buffer.AppendLiteral("\">\n<title
>");
475 // Everything needs to end in a /,
476 // otherwise we end up linking to file:///foo/dirfile
478 if (!mTextToSubURI) {
479 mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
480 if (NS_FAILED(rv)) return rv;
483 nsAutoString unEscapeSpec;
484 rv = mTextToSubURI->UnEscapeAndConvert("UTF
-8"_ns, titleUri, unEscapeSpec);
489 nsCString htmlEscSpecUtf8;
490 nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(unEscapeSpec), htmlEscSpecUtf8);
491 AutoTArray<nsString, 1> formatTitle;
492 CopyUTF8toUTF16(htmlEscSpecUtf8, *formatTitle.AppendElement());
495 rv = mBundle->FormatStringFromName("DirTitle
", formatTitle, title);
496 if (NS_FAILED(rv)) return rv;
498 // we want to convert string bundle to NCR
499 // to ensure they're shown in any charsets
500 AppendNonAsciiToNCR(title, buffer);
502 buffer.AppendLiteral("</title
>\n");
504 // If there is a quote character in the baseUri, then
505 // lets not add a base URL. The reason for this is that
506 // if we stick baseUri containing a quote into a quoted
507 // string, the quote character will prematurely close
508 // the base href string. This is a fall-back check;
509 // that's why it is OK to not use a base rather than
510 // trying to play nice and escaping the quotes. See bug
513 if (!baseUri.Contains('"')) {
514 // Great, the baseUri does not contain a char that
515 // will prematurely close the string. Go ahead an
516 // add a base href, but only do so if we're
not
517 // dealing with a resource URI.
518 if (!uri
->SchemeIs("resource")) {
519 buffer
.AppendLiteral("<base href=\"");
520 nsAppendEscapedHTML(baseUri
, buffer
);
521 buffer
.AppendLiteral("\" />\n");
524 NS_ERROR("broken protocol handler didn't escape double-quote.");
527 nsCString
direction("ltr"_ns
);
528 if (LocaleService::GetInstance()->IsAppLocaleRTL()) {
529 direction
.AssignLiteral("rtl");
532 buffer
.AppendLiteral("</head>\n<body dir=\"");
533 buffer
.Append(direction
);
534 buffer
.AppendLiteral("\">\n<h1>");
535 AppendNonAsciiToNCR(title
, buffer
);
536 buffer
.AppendLiteral("</h1>\n");
538 if (!parentStr
.IsEmpty()) {
539 nsAutoString parentText
;
540 rv
= mBundle
->GetStringFromName("DirGoUp", parentText
);
541 if (NS_FAILED(rv
)) return rv
;
543 buffer
.AppendLiteral(R
"(<p id="UI_goUp
"><a class="up
" href=")");
544 nsAppendEscapedHTML(parentStr, buffer);
545 buffer.AppendLiteral("\">");
546 AppendNonAsciiToNCR(parentText, buffer);
547 buffer.AppendLiteral("</a
></p
>\n");
550 if (uri->SchemeIs("file
")) {
551 nsAutoString showHiddenText;
552 rv = mBundle->GetStringFromName("ShowHidden
", showHiddenText);
553 if (NS_FAILED(rv)) return rv;
555 buffer.AppendLiteral(
556 "<p id
=\"UI_showHidden
\" style
=\"display
:none
\"><label
><input
"
557 "type
=\"checkbox
\" checked onchange
=\"updateHidden()\">");
558 AppendNonAsciiToNCR(showHiddenText, buffer);
559 buffer.AppendLiteral("</label
></p
>\n");
562 buffer.AppendLiteral(
568 nsAutoString columnText;
569 rv = mBundle->GetStringFromName("DirColName
", columnText);
570 if (NS_FAILED(rv)) return rv;
571 AppendNonAsciiToNCR(columnText, buffer);
572 buffer.AppendLiteral(
576 rv = mBundle->GetStringFromName("DirColSize
", columnText);
577 if (NS_FAILED(rv)) return rv;
578 AppendNonAsciiToNCR(columnText, buffer);
579 buffer.AppendLiteral(
581 " <th colspan
=\"2\">");
583 rv = mBundle->GetStringFromName("DirColMTime
", columnText);
584 if (NS_FAILED(rv)) return rv;
585 AppendNonAsciiToNCR(columnText, buffer);
586 buffer.AppendLiteral(
590 buffer.AppendLiteral(" <tbody
>\n");
597 nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsresult aStatus) {
598 if (NS_SUCCEEDED(aStatus)) {
600 buffer.AssignLiteral("</tbody
></table
></body
></html
>\n");
602 aStatus = SendToListener(request, buffer);
605 mParser->OnStopRequest(request, aStatus);
608 return mListener->OnStopRequest(request, aStatus);
611 nsresult nsIndexedToHTML::SendToListener(nsIRequest* aRequest,
612 const nsACString& aBuffer) {
613 nsCOMPtr<nsIInputStream> inputData;
614 nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputData), aBuffer);
615 NS_ENSURE_SUCCESS(rv, rv);
616 return mListener->OnDataAvailable(aRequest, inputData, 0, aBuffer.Length());
620 nsIndexedToHTML::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInput,
621 uint64_t aOffset, uint32_t aCount) {
622 return mParser->OnDataAvailable(aRequest, aInput, aOffset, aCount);
626 nsIndexedToHTML::OnDataFinished(nsresult aStatus) {
627 return NS_ERROR_NOT_IMPLEMENTED;
631 nsIndexedToHTML::CheckListenerChain() {
632 // nsIndexedToHTML does not support OnDataAvailable to run OMT. This class
633 // should only pass-through OnDataFinished notification.
634 return NS_ERROR_NO_INTERFACE;
637 static nsresult FormatTime(
638 const mozilla::intl::DateTimeFormat::StyleBag& aStyleBag,
639 const PRTime aPrTime, nsAString& aStringOut) {
640 // FormatPRExplodedTime will use GMT based formatted string (e.g. GMT+1)
641 // instead of local time zone name (e.g. CEST).
642 // To avoid this case when ResistFingerprinting is disabled, use
643 // |FormatPRTime| to show exact time zone name.
644 if (!nsContentUtils::ShouldResistFingerprinting(true,
645 RFPTarget::JSDateTimeUTC)) {
646 return mozilla::intl::AppDateTimeFormat::Format(aStyleBag, aPrTime,
650 PRExplodedTime prExplodedTime;
651 PR_ExplodeTime(aPrTime, PR_GMTParameters, &prExplodedTime);
652 return mozilla::intl::AppDateTimeFormat::Format(aStyleBag, &prExplodedTime,
657 nsIndexedToHTML::OnIndexAvailable(nsIRequest* aRequest, nsIDirIndex* aIndex) {
659 if (!aIndex) return NS_ERROR_NULL_POINTER;
661 nsCString pushBuffer;
662 pushBuffer.AppendLiteral("<tr
");
664 // We don't know the file's character set yet, so retrieve the raw bytes
665 // which will be decoded by the HTML parser.
667 aIndex->GetLocation(loc);
669 // Adjust the length in case unescaping shortened the string.
670 loc.Truncate(nsUnescapeCount(loc.BeginWriting()));
673 return NS_ERROR_ILLEGAL_VALUE;
675 if (loc.First() == char16_t('.')) {
676 pushBuffer.AppendLiteral(" class=\"hidden
-object
\"");
679 pushBuffer.AppendLiteral(">\n <td sortable
-data
=\"");
681 // The sort key is the name of the item, prepended by either 0, 1 or 2
682 // in order to group items.
684 aIndex->GetType(&type);
686 case nsIDirIndex::TYPE_SYMLINK:
687 pushBuffer.Append('0');
689 case nsIDirIndex::TYPE_DIRECTORY:
690 pushBuffer.Append('1');
693 pushBuffer.Append('2');
697 nsAppendEscapedHTML(loc, escaped);
698 pushBuffer.Append(escaped);
700 pushBuffer.AppendLiteral(
701 R"("><table class="ellipsis
"><tbody><tr><td><a class=")");
703 case nsIDirIndex::TYPE_DIRECTORY:
704 pushBuffer.AppendLiteral("dir
");
706 case nsIDirIndex::TYPE_SYMLINK:
707 pushBuffer.AppendLiteral("symlink
");
710 pushBuffer.AppendLiteral("file
");
714 pushBuffer.AppendLiteral("\" href
=\"");
716 // need to escape links
717 nsAutoCString locEscaped;
719 // Adding trailing slash helps to recognize whether the URL points to a file
720 // or a directory (bug #214405).
721 if ((type == nsIDirIndex::TYPE_DIRECTORY) && (loc.Last() != '/')) {
725 // now minimally re-escape the location...
727 // for some protocols, we expect the location to be absolute.
728 // if so, and if the location indeed appears to be a valid URI, then go
729 // ahead and treat it like one.
731 nsAutoCString scheme;
732 if (mExpectAbsLoc && NS_SUCCEEDED(net_ExtractURLScheme(loc, scheme))) {
733 // escape as absolute
734 escFlags = esc_Forced | esc_AlwaysCopy | esc_Minimal;
736 // escape as relative
737 // esc_Directory is needed because directories have a trailing slash.
738 // Without it, the trailing '/' will be escaped, and links from within
739 // that directory will be incorrect
740 escFlags = esc_Forced | esc_AlwaysCopy | esc_FileBaseName | esc_Colon |
743 NS_EscapeURL(loc.get(), loc.Length(), escFlags, locEscaped);
744 // esc_Directory does not escape the semicolons, so if a filename
745 // contains semicolons we need to manually escape them.
746 // This replacement should be removed in bug #473280
747 locEscaped.ReplaceSubstring(";", "%3b
");
748 nsAppendEscapedHTML(locEscaped, pushBuffer);
749 pushBuffer.AppendLiteral("\">");
751 if (type == nsIDirIndex::TYPE_FILE || type == nsIDirIndex::TYPE_UNKNOWN) {
752 pushBuffer.AppendLiteral("<img src
=\"moz
-icon
://");
753 int32_t lastDot
= locEscaped
.RFindChar('.');
754 if (lastDot
!= kNotFound
) {
755 locEscaped
.Cut(0, lastDot
);
756 nsAppendEscapedHTML(locEscaped
, pushBuffer
);
758 pushBuffer
.AppendLiteral("unknown");
760 pushBuffer
.AppendLiteral("?size=16\" alt=\"");
762 nsAutoString altText
;
763 rv
= mBundle
->GetStringFromName("DirFileLabel", altText
);
764 if (NS_FAILED(rv
)) return rv
;
765 AppendNonAsciiToNCR(altText
, pushBuffer
);
766 pushBuffer
.AppendLiteral("\">");
769 pushBuffer
.Append(escaped
);
770 pushBuffer
.AppendLiteral("</a></td></tr></tbody></table></td>\n <td");
772 if (type
== nsIDirIndex::TYPE_DIRECTORY
||
773 type
== nsIDirIndex::TYPE_SYMLINK
) {
774 pushBuffer
.Append('>');
777 aIndex
->GetSize(&size
);
779 if (uint64_t(size
) != UINT64_MAX
) {
780 pushBuffer
.AppendLiteral(" sortable-data=\"");
781 pushBuffer
.AppendInt(size
);
782 pushBuffer
.AppendLiteral("\">");
783 nsAutoCString sizeString
;
784 FormatSizeString(size
, sizeString
);
785 pushBuffer
.Append(sizeString
);
787 pushBuffer
.Append('>');
790 pushBuffer
.AppendLiteral("</td>\n <td");
793 aIndex
->GetLastModified(&t
);
796 pushBuffer
.AppendLiteral("></td>\n <td>");
798 pushBuffer
.AppendLiteral(" sortable-data=\"");
799 pushBuffer
.AppendInt(static_cast<int64_t>(t
));
800 pushBuffer
.AppendLiteral("\">");
802 nsAutoString formatted
;
803 mozilla::intl::DateTimeFormat::StyleBag dateBag
;
804 dateBag
.date
= Some(mozilla::intl::DateTimeFormat::Style::Short
);
805 FormatTime(dateBag
, t
, formatted
);
806 AppendNonAsciiToNCR(formatted
, pushBuffer
);
807 pushBuffer
.AppendLiteral("</td>\n <td>");
809 mozilla::intl::DateTimeFormat::StyleBag timeBag
;
810 timeBag
.time
= Some(mozilla::intl::DateTimeFormat::Style::Long
);
811 FormatTime(timeBag
, t
, formatted
);
812 // use NCR to show date in any doc charset
813 AppendNonAsciiToNCR(formatted
, pushBuffer
);
816 pushBuffer
.AppendLiteral("</td>\n</tr>");
818 return SendToListener(aRequest
, pushBuffer
);
821 void nsIndexedToHTML::FormatSizeString(int64_t inSize
,
822 nsCString
& outSizeString
) {
823 outSizeString
.Truncate();
824 if (inSize
> int64_t(0)) {
825 // round up to the nearest Kilobyte
826 int64_t upperSize
= (inSize
+ int64_t(1023)) / int64_t(1024);
827 outSizeString
.AppendInt(upperSize
);
828 outSizeString
.AppendLiteral(" KB");