tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }