nsIndexedToHTML.cpp (27539B)
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 "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 "nsIChannel.h" 27 #include "nsIURIMutator.h" 28 #include "nsITextToSubURI.h" 29 30 using mozilla::intl::LocaleService; 31 using namespace mozilla; 32 33 NS_IMPL_ISUPPORTS(nsIndexedToHTML, nsIDirIndexListener, nsIStreamConverter, 34 nsIThreadRetargetableStreamListener, nsIRequestObserver, 35 nsIStreamListener) 36 37 static void AppendNonAsciiToNCR(const nsAString& in, nsCString& out) { 38 nsAString::const_iterator start, end; 39 40 in.BeginReading(start); 41 in.EndReading(end); 42 43 while (start != end) { 44 if (*start < 128) { 45 out.Append(*start++); 46 } else { 47 out.AppendLiteral("&#x"); 48 out.AppendInt(*start++, 16); 49 out.Append(';'); 50 } 51 } 52 } 53 54 nsresult nsIndexedToHTML::Create(REFNSIID aIID, void** aResult) { 55 nsresult rv; 56 57 nsIndexedToHTML* _s = new nsIndexedToHTML(); 58 if (_s == nullptr) return NS_ERROR_OUT_OF_MEMORY; 59 60 rv = _s->QueryInterface(aIID, aResult); 61 return rv; 62 } 63 64 nsresult nsIndexedToHTML::Init(nsIStreamListener* aListener) { 65 nsresult rv = NS_OK; 66 67 mListener = aListener; 68 69 nsCOMPtr<nsIStringBundleService> sbs; 70 sbs = mozilla::components::StringBundle::Service(&rv); 71 if (NS_FAILED(rv)) return rv; 72 rv = sbs->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(mBundle)); 73 74 mExpectAbsLoc = false; 75 76 return rv; 77 } 78 79 NS_IMETHODIMP 80 nsIndexedToHTML::Convert(nsIInputStream* aFromStream, const char* aFromType, 81 const char* aToType, nsISupports* aCtxt, 82 nsIInputStream** res) { 83 return NS_ERROR_NOT_IMPLEMENTED; 84 } 85 86 NS_IMETHODIMP 87 nsIndexedToHTML::AsyncConvertData(const char* aFromType, const char* aToType, 88 nsIStreamListener* aListener, 89 nsISupports* aCtxt) { 90 return Init(aListener); 91 } 92 93 NS_IMETHODIMP 94 nsIndexedToHTML::GetConvertedType(const nsACString& aFromType, 95 nsIChannel* aChannel, nsACString& aToType) { 96 return NS_ERROR_NOT_IMPLEMENTED; 97 } 98 99 NS_IMETHODIMP 100 nsIndexedToHTML::MaybeRetarget(nsIRequest* request) { 101 return NS_ERROR_NOT_IMPLEMENTED; 102 } 103 104 NS_IMETHODIMP 105 nsIndexedToHTML::OnStartRequest(nsIRequest* request) { 106 nsCString buffer; 107 nsresult rv = DoOnStartRequest(request, buffer); 108 if (NS_FAILED(rv)) { 109 request->Cancel(rv); 110 } 111 112 rv = mListener->OnStartRequest(request); 113 if (NS_FAILED(rv)) return rv; 114 115 // The request may have been canceled, and if that happens, we want to 116 // suppress calls to OnDataAvailable. 117 request->GetStatus(&rv); 118 if (NS_FAILED(rv)) return rv; 119 120 // Push our buffer to the listener. 121 122 rv = SendToListener(request, buffer); 123 return rv; 124 } 125 126 nsresult nsIndexedToHTML::DoOnStartRequest(nsIRequest* request, 127 nsCString& aBuffer) { 128 nsresult rv; 129 130 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); 131 nsCOMPtr<nsIURI> uri; 132 rv = channel->GetOriginalURI(getter_AddRefs(uri)); 133 if (NS_FAILED(rv)) return rv; 134 135 // We use the original URI for the title and parent link when it's a 136 // resource:// or moz-extension:// url, instead of the jar:file:// 137 // url it resolves to. 138 if (!uri->SchemeIs("resource") && !uri->SchemeIs("moz-extension")) { 139 rv = channel->GetURI(getter_AddRefs(uri)); 140 if (NS_FAILED(rv)) return rv; 141 } 142 143 channel->SetContentType("text/html"_ns); 144 145 mParser = nsDirIndexParser::CreateInstance(); 146 if (!mParser) return NS_ERROR_FAILURE; 147 148 rv = mParser->SetListener(this); 149 if (NS_FAILED(rv)) return rv; 150 151 rv = mParser->OnStartRequest(request); 152 if (NS_FAILED(rv)) return rv; 153 154 nsAutoCString baseUri, titleUri; 155 rv = uri->GetAsciiSpec(baseUri); 156 if (NS_FAILED(rv)) return rv; 157 158 nsCOMPtr<nsIURI> titleURL; 159 rv = NS_MutateURI(uri).SetQuery(""_ns).SetRef(""_ns).Finalize(titleURL); 160 if (NS_FAILED(rv)) { 161 titleURL = uri; 162 } 163 164 nsCString parentStr; 165 166 nsCString buffer; 167 buffer.AppendLiteral("<!DOCTYPE html>\n<html>\n<head>\n"); 168 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 175 // - bbaetz 176 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; 182 183 nsAutoCString url; 184 rv = net_GetURLSpecFromFile(file, url); 185 if (NS_FAILED(rv)) return rv; 186 baseUri.Assign(url); 187 188 nsCOMPtr<nsIFile> parent; 189 rv = file->GetParent(getter_AddRefs(parent)); 190 191 if (parent && NS_SUCCEEDED(rv)) { 192 net_GetURLSpecFromDir(parent, url); 193 if (NS_FAILED(rv)) return rv; 194 parentStr.Assign(url); 195 } 196 197 // Directory index will be always encoded in UTF-8 if this is file url 198 buffer.AppendLiteral("<meta charset=\"UTF-8\">\n"); 199 200 } else if (uri->SchemeIs("jar")) { 201 nsAutoCString path; 202 rv = uri->GetPathQueryRef(path); 203 if (NS_FAILED(rv)) return rv; 204 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 207 // will be "!/" 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; 214 } 215 } else { 216 // default behavior for other protocols is to assume the channel's 217 // URL references a directory ending in '/' -- fixup if necessary. 218 nsAutoCString path; 219 rv = uri->GetPathQueryRef(path); 220 if (NS_FAILED(rv)) return rv; 221 if (baseUri.Last() != '/') { 222 baseUri.Append('/'); 223 path.Append('/'); 224 (void)NS_MutateURI(uri).SetPathQueryRef(path).Finalize(uri); 225 } 226 if (!path.EqualsLiteral("/")) { 227 rv = uri->Resolve(".."_ns, parentStr); 228 if (NS_FAILED(rv)) return rv; 229 } 230 } 231 232 rv = titleURL->GetAsciiSpec(titleUri); 233 if (NS_FAILED(rv)) { 234 return rv; 235 } 236 237 buffer.AppendLiteral( 238 "<style type=\"text/css\">\n" 239 ":root {\n" 240 " font-family: sans-serif;\n" 241 "}\n" 242 "img {\n" 243 " border: 0;\n" 244 "}\n" 245 "th {\n" 246 " text-align: start;\n" 247 " white-space: nowrap;\n" 248 "}\n" 249 "th > a {\n" 250 " color: inherit;\n" 251 "}\n" 252 "table[order] > thead > tr > th {\n" 253 " cursor: pointer;\n" 254 "}\n" 255 "table[order] > thead > tr > th::after {\n" 256 " display: none;\n" 257 " width: .8em;\n" 258 " margin-inline-end: -.8em;\n" 259 " text-align: end;\n" 260 "}\n" 261 "table[order=\"asc\"] > thead > tr > th::after {\n" 262 " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n" 263 "}\n" 264 "table[order=\"desc\"] > thead > tr > th::after {\n" 265 " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n" 266 "}\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 > " 270 "a {\n" 271 " text-decoration: underline;\n" 272 "}\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 " 275 ",\n" 276 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + " 277 "th::after {\n" 278 " display: inline-block;\n" 279 "}\n" 280 "table.remove-hidden > tbody > tr.hidden-object {\n" 281 " display: none;\n" 282 "}\n" 283 "td {\n" 284 " white-space: nowrap;\n" 285 "}\n" 286 "table.ellipsis {\n" 287 " width: 100%;\n" 288 " table-layout: fixed;\n" 289 " border-spacing: 0;\n" 290 "}\n" 291 "table.ellipsis > tbody > tr > td {\n" 292 " overflow: hidden;\n" 293 " text-overflow: ellipsis;\n" 294 "}\n" 295 "/* name */\n" 296 "/* name */\n" 297 "th:first-child {\n" 298 " padding-inline-end: 2em;\n" 299 "}\n" 300 "/* size */\n" 301 "th:first-child + th {\n" 302 " padding-inline-end: 1em;\n" 303 "}\n" 304 "td:first-child + td {\n" 305 " text-align: end;\n" 306 " padding-inline-end: 1em;\n" 307 "}\n" 308 "/* date */\n" 309 "td:first-child + td + td {\n" 310 " padding-inline-start: 1em;\n" 311 " padding-inline-end: .5em;\n" 312 "}\n" 313 "/* time */\n" 314 "td:first-child + td + td + td {\n" 315 " padding-inline-start: .5em;\n" 316 "}\n" 317 ".symlink {\n" 318 " font-style: italic;\n" 319 "}\n" 320 ".dir ,\n" 321 ".symlink ,\n" 322 ".file {\n" 323 " margin-inline-start: 20px;\n" 324 "}\n" 325 ".dir::before ,\n" 326 ".file > img {\n" 327 " margin-inline-end: 4px;\n" 328 " margin-inline-start: -20px;\n" 329 " width: 16px;\n" 330 " height: 16px;\n" 331 " vertical-align: middle;\n" 332 "}\n" 333 ".dir::before {\n" 334 " content: url(resource://content-accessible/html/folder.png);\n" 335 "}\n" 336 "</style>\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" 340 "'use strict';\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" 346 " return;\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" 353 " orderBy(i);\n" 354 " }\n" 355 " }\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" 362 " }\n" 363 " if (gUI_showHidden) {\n" 364 " gRows = Array.from(gTBody.rows);\n" 365 " hiddenObjects = gRows.some(row => row.className == " 366 "\"hidden-object\");\n" 367 " }\n" 368 " gTable.setAttribute(\"order\", \"\");\n" 369 " if (hiddenObjects) {\n" 370 " gUI_showHidden.style.display = \"block\";\n" 371 " updateHidden();\n" 372 " }\n" 373 "}, \"false\");\n" 374 "function compareRows(rowA, rowB) {\n" 375 " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || " 376 "\"\";\n" 377 " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || " 378 "\"\";\n" 379 " var intA = +a;\n" 380 " var intB = +b;\n" 381 " if (a == intA && b == intB) {\n" 382 " a = intA;\n" 383 " b = intB;\n" 384 " } else {\n" 385 " a = a.toLowerCase();\n" 386 " b = b.toLowerCase();\n" 387 " }\n" 388 " if (a < b)\n" 389 " return -1;\n" 390 " if (a > b)\n" 391 " return 1;\n" 392 " return 0;\n" 393 "}\n" 394 "function orderBy(column) {\n" 395 " if (!gRows)\n" 396 " gRows = Array.from(gTBody.rows);\n" 397 " var order;\n" 398 " if (gOrderBy == column) {\n" 399 " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : " 400 "\"asc\";\n" 401 " } else {\n" 402 " order = \"asc\";\n" 403 " gOrderBy = column;\n" 404 " gTable.setAttribute(\"order-by\", column);\n" 405 " gRows.sort(compareRows);\n" 406 " }\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" 412 " else\n" 413 " for (var i = gRows.length - 1; i >= 0; i--)\n" 414 " gTBody.appendChild(gRows[i]);\n" 415 " gTable.appendChild(gTBody);\n" 416 "}\n" 417 "function updateHidden() {\n" 418 " gTable.className = " 419 "gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n" 420 " \"\" :\n" 421 " \"remove-hidden\";\n" 422 "}\n" 423 "</script>\n"); 424 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 430 if (fileURL) { 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"); 452 } else { 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"); 472 } 473 buffer.AppendLiteral("\">\n<title>"); 474 475 // Everything needs to end in a /, 476 // otherwise we end up linking to file:///foo/dirfile 477 478 if (!mTextToSubURI) { 479 mTextToSubURI = mozilla::components::TextToSubURI::Service(&rv); 480 if (NS_FAILED(rv)) return rv; 481 } 482 483 nsAutoString unEscapeSpec; 484 rv = mTextToSubURI->UnEscapeAndConvert("UTF-8"_ns, titleUri, unEscapeSpec); 485 if (NS_FAILED(rv)) { 486 return rv; 487 } 488 489 nsCString htmlEscSpecUtf8; 490 nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(unEscapeSpec), htmlEscSpecUtf8); 491 AutoTArray<nsString, 1> formatTitle; 492 CopyUTF8toUTF16(htmlEscSpecUtf8, *formatTitle.AppendElement()); 493 494 nsAutoString title; 495 rv = mBundle->FormatStringFromName("DirTitle", formatTitle, title); 496 if (NS_FAILED(rv)) return rv; 497 498 // we want to convert string bundle to NCR 499 // to ensure they're shown in any charsets 500 AppendNonAsciiToNCR(title, buffer); 501 502 buffer.AppendLiteral("</title>\n"); 503 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 511 // 358128. 512 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"); 522 } 523 } else { 524 NS_ERROR("broken protocol handler didn't escape double-quote."); 525 } 526 527 nsCString direction("ltr"_ns); 528 if (LocaleService::GetInstance()->IsAppLocaleRTL()) { 529 direction.AssignLiteral("rtl"); 530 } 531 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"); 537 538 if (!parentStr.IsEmpty()) { 539 nsAutoString parentText; 540 rv = mBundle->GetStringFromName("DirGoUp", parentText); 541 if (NS_FAILED(rv)) return rv; 542 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"); 548 } 549 550 if (uri->SchemeIs("file")) { 551 nsAutoString showHiddenText; 552 rv = mBundle->GetStringFromName("ShowHidden", showHiddenText); 553 if (NS_FAILED(rv)) return rv; 554 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"); 560 } 561 562 buffer.AppendLiteral( 563 "<table>\n" 564 " <thead>\n" 565 " <tr>\n" 566 " <th>"); 567 568 nsAutoString columnText; 569 rv = mBundle->GetStringFromName("DirColName", columnText); 570 if (NS_FAILED(rv)) return rv; 571 AppendNonAsciiToNCR(columnText, buffer); 572 buffer.AppendLiteral( 573 "</th>\n" 574 " <th>"); 575 576 rv = mBundle->GetStringFromName("DirColSize", columnText); 577 if (NS_FAILED(rv)) return rv; 578 AppendNonAsciiToNCR(columnText, buffer); 579 buffer.AppendLiteral( 580 "</th>\n" 581 " <th colspan=\"2\">"); 582 583 rv = mBundle->GetStringFromName("DirColMTime", columnText); 584 if (NS_FAILED(rv)) return rv; 585 AppendNonAsciiToNCR(columnText, buffer); 586 buffer.AppendLiteral( 587 "</th>\n" 588 " </tr>\n" 589 " </thead>\n"); 590 buffer.AppendLiteral(" <tbody>\n"); 591 592 aBuffer = buffer; 593 return rv; 594 } 595 596 NS_IMETHODIMP 597 nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsresult aStatus) { 598 if (NS_SUCCEEDED(aStatus)) { 599 nsCString buffer; 600 buffer.AssignLiteral("</tbody></table></body></html>\n"); 601 602 aStatus = SendToListener(request, buffer); 603 } 604 605 mParser->OnStopRequest(request, aStatus); 606 mParser = nullptr; 607 608 return mListener->OnStopRequest(request, aStatus); 609 } 610 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()); 617 } 618 619 NS_IMETHODIMP 620 nsIndexedToHTML::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInput, 621 uint64_t aOffset, uint32_t aCount) { 622 return mParser->OnDataAvailable(aRequest, aInput, aOffset, aCount); 623 } 624 625 NS_IMETHODIMP 626 nsIndexedToHTML::OnDataFinished(nsresult aStatus) { 627 return NS_ERROR_NOT_IMPLEMENTED; 628 } 629 630 NS_IMETHODIMP 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; 635 } 636 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, 647 aStringOut); 648 } 649 650 PRExplodedTime prExplodedTime; 651 PR_ExplodeTime(aPrTime, PR_GMTParameters, &prExplodedTime); 652 return mozilla::intl::AppDateTimeFormat::Format(aStyleBag, &prExplodedTime, 653 aStringOut); 654 } 655 656 NS_IMETHODIMP 657 nsIndexedToHTML::OnIndexAvailable(nsIRequest* aRequest, nsIDirIndex* aIndex) { 658 nsresult rv; 659 if (!aIndex) return NS_ERROR_NULL_POINTER; 660 661 nsCString pushBuffer; 662 pushBuffer.AppendLiteral("<tr"); 663 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. 666 nsCString loc; 667 aIndex->GetLocation(loc); 668 669 // Adjust the length in case unescaping shortened the string. 670 loc.Truncate(nsUnescapeCount(loc.BeginWriting())); 671 672 if (loc.IsEmpty()) { 673 return NS_ERROR_ILLEGAL_VALUE; 674 } 675 if (loc.First() == char16_t('.')) { 676 pushBuffer.AppendLiteral(" class=\"hidden-object\""); 677 } 678 679 pushBuffer.AppendLiteral(">\n <td sortable-data=\""); 680 681 // The sort key is the name of the item, prepended by either 0, 1 or 2 682 // in order to group items. 683 uint32_t type; 684 aIndex->GetType(&type); 685 switch (type) { 686 case nsIDirIndex::TYPE_SYMLINK: 687 pushBuffer.Append('0'); 688 break; 689 case nsIDirIndex::TYPE_DIRECTORY: 690 pushBuffer.Append('1'); 691 break; 692 default: 693 pushBuffer.Append('2'); 694 break; 695 } 696 nsCString escaped; 697 nsAppendEscapedHTML(loc, escaped); 698 pushBuffer.Append(escaped); 699 700 pushBuffer.AppendLiteral( 701 R"("><table class="ellipsis"><tbody><tr><td><a class=")"); 702 switch (type) { 703 case nsIDirIndex::TYPE_DIRECTORY: 704 pushBuffer.AppendLiteral("dir"); 705 break; 706 case nsIDirIndex::TYPE_SYMLINK: 707 pushBuffer.AppendLiteral("symlink"); 708 break; 709 default: 710 pushBuffer.AppendLiteral("file"); 711 break; 712 } 713 714 pushBuffer.AppendLiteral("\" href=\""); 715 716 // need to escape links 717 nsAutoCString locEscaped; 718 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() != '/')) { 722 loc.Append('/'); 723 } 724 725 // now minimally re-escape the location... 726 uint32_t escFlags; 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. 730 731 nsAutoCString scheme; 732 if (mExpectAbsLoc && NS_SUCCEEDED(net_ExtractURLScheme(loc, scheme))) { 733 // escape as absolute 734 escFlags = esc_Forced | esc_AlwaysCopy | esc_Minimal; 735 } else { 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 | 741 esc_Directory; 742 } 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("\">"); 750 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); 757 } else { 758 pushBuffer.AppendLiteral("unknown"); 759 } 760 pushBuffer.AppendLiteral("?size=16\" alt=\""); 761 762 nsAutoString altText; 763 rv = mBundle->GetStringFromName("DirFileLabel", altText); 764 if (NS_FAILED(rv)) return rv; 765 AppendNonAsciiToNCR(altText, pushBuffer); 766 pushBuffer.AppendLiteral("\">"); 767 } 768 769 pushBuffer.Append(escaped); 770 pushBuffer.AppendLiteral("</a></td></tr></tbody></table></td>\n <td"); 771 772 if (type == nsIDirIndex::TYPE_DIRECTORY || 773 type == nsIDirIndex::TYPE_SYMLINK) { 774 pushBuffer.Append('>'); 775 } else { 776 int64_t size; 777 aIndex->GetSize(&size); 778 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); 786 } else { 787 pushBuffer.Append('>'); 788 } 789 } 790 pushBuffer.AppendLiteral("</td>\n <td"); 791 792 PRTime t; 793 aIndex->GetLastModified(&t); 794 795 if (t == -1LL) { 796 pushBuffer.AppendLiteral("></td>\n <td>"); 797 } else { 798 pushBuffer.AppendLiteral(" sortable-data=\""); 799 pushBuffer.AppendInt(static_cast<int64_t>(t)); 800 pushBuffer.AppendLiteral("\">"); 801 // Add date string 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>"); 808 // Add time string 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); 814 } 815 816 pushBuffer.AppendLiteral("</td>\n</tr>"); 817 818 return SendToListener(aRequest, pushBuffer); 819 } 820 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"); 829 } 830 }