diff options
Diffstat (limited to 'netwerk/streamconv/converters/nsIndexedToHTML.cpp')
-rw-r--r-- | netwerk/streamconv/converters/nsIndexedToHTML.cpp | 895 |
1 files changed, 895 insertions, 0 deletions
diff --git a/netwerk/streamconv/converters/nsIndexedToHTML.cpp b/netwerk/streamconv/converters/nsIndexedToHTML.cpp new file mode 100644 index 000000000..0414c4841 --- /dev/null +++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp @@ -0,0 +1,895 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIndexedToHTML.h" +#include "mozilla/dom/EncodingUtils.h" +#include "nsNetUtil.h" +#include "netCore.h" +#include "nsStringStream.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsEscape.h" +#include "nsIDirIndex.h" +#include "nsURLHelper.h" +#include "nsIPlatformCharset.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsIChromeRegistry.h" +#include "nsIDateTimeFormat.h" +#include "nsIStringBundle.h" +#include "nsITextToSubURI.h" +#include "nsXPIDLString.h" +#include <algorithm> +#include "nsIChannel.h" + +NS_IMPL_ISUPPORTS(nsIndexedToHTML, + nsIDirIndexListener, + nsIStreamConverter, + nsIRequestObserver, + nsIStreamListener) + +static void AppendNonAsciiToNCR(const nsAString& in, nsCString& out) +{ + nsAString::const_iterator start, end; + + in.BeginReading(start); + in.EndReading(end); + + while (start != end) { + if (*start < 128) { + out.Append(*start++); + } else { + out.AppendLiteral("&#x"); + out.AppendInt(*start++, 16); + out.Append(';'); + } + } +} + +nsresult +nsIndexedToHTML::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) { + nsresult rv; + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsIndexedToHTML* _s = new nsIndexedToHTML(); + if (_s == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + rv = _s->QueryInterface(aIID, aResult); + return rv; +} + +nsresult +nsIndexedToHTML::Init(nsIStreamListener* aListener) { + nsresult rv = NS_OK; + + mListener = aListener; + + mDateTime = nsIDateTimeFormat::Create(); + if (!mDateTime) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStringBundleService> sbs = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + rv = sbs->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(mBundle)); + + mExpectAbsLoc = false; + + return rv; +} + +NS_IMETHODIMP +nsIndexedToHTML::Convert(nsIInputStream* aFromStream, + const char* aFromType, + const char* aToType, + nsISupports* aCtxt, + nsIInputStream** res) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIndexedToHTML::AsyncConvertData(const char *aFromType, + const char *aToType, + nsIStreamListener *aListener, + nsISupports *aCtxt) { + return Init(aListener); +} + +NS_IMETHODIMP +nsIndexedToHTML::OnStartRequest(nsIRequest* request, nsISupports *aContext) { + nsCString buffer; + nsresult rv = DoOnStartRequest(request, aContext, buffer); + if (NS_FAILED(rv)) { + request->Cancel(rv); + } + + rv = mListener->OnStartRequest(request, aContext); + if (NS_FAILED(rv)) return rv; + + // The request may have been canceled, and if that happens, we want to + // suppress calls to OnDataAvailable. + request->GetStatus(&rv); + if (NS_FAILED(rv)) return rv; + + // Push our buffer to the listener. + + rv = SendToListener(request, aContext, buffer); + return rv; +} + +nsresult +nsIndexedToHTML::DoOnStartRequest(nsIRequest* request, nsISupports *aContext, + nsCString& aBuffer) { + nsresult rv; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + channel->SetContentType(NS_LITERAL_CSTRING("text/html")); + + mParser = do_CreateInstance("@mozilla.org/dirIndexParser;1",&rv); + if (NS_FAILED(rv)) return rv; + + rv = mParser->SetListener(this); + if (NS_FAILED(rv)) return rv; + + rv = mParser->OnStartRequest(request, aContext); + if (NS_FAILED(rv)) return rv; + + nsAutoCString baseUri, titleUri; + rv = uri->GetAsciiSpec(baseUri); + if (NS_FAILED(rv)) return rv; + titleUri = baseUri; + + nsCString parentStr; + + nsCString buffer; + buffer.AppendLiteral("<!DOCTYPE html>\n<html>\n<head>\n"); + + // XXX - should be using the 300: line from the parser. + // We can't guarantee that that comes before any entry, so we'd have to + // buffer, and do other painful stuff. + // I'll deal with this when I make the changes to handle welcome messages + // The .. stuff should also come from the lower level protocols, but that + // would muck up the XUL display + // - bbaetz + + bool isScheme = false; + bool isSchemeFile = false; + if (NS_SUCCEEDED(uri->SchemeIs("ftp", &isScheme)) && isScheme) { + + // strip out the password here, so it doesn't show in the page title + // This is done by the 300: line generation in ftp, but we don't use + // that - see above + + nsAutoCString pw; + rv = uri->GetPassword(pw); + if (NS_FAILED(rv)) return rv; + if (!pw.IsEmpty()) { + nsCOMPtr<nsIURI> newUri; + rv = uri->Clone(getter_AddRefs(newUri)); + if (NS_FAILED(rv)) return rv; + rv = newUri->SetPassword(EmptyCString()); + if (NS_FAILED(rv)) return rv; + rv = newUri->GetAsciiSpec(titleUri); + if (NS_FAILED(rv)) return rv; + } + + nsAutoCString path; + rv = uri->GetPath(path); + if (NS_FAILED(rv)) return rv; + + if (!path.EqualsLiteral("//") && !path.LowerCaseEqualsLiteral("/%2f")) { + rv = uri->Resolve(NS_LITERAL_CSTRING(".."),parentStr); + if (NS_FAILED(rv)) return rv; + } + } else if (NS_SUCCEEDED(uri->SchemeIs("file", &isSchemeFile)) && isSchemeFile) { + nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri); + nsCOMPtr<nsIFile> file; + rv = fileUrl->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + file->SetFollowLinks(true); + + nsAutoCString url; + rv = net_GetURLSpecFromFile(file, url); + if (NS_FAILED(rv)) return rv; + baseUri.Assign(url); + + nsCOMPtr<nsIFile> parent; + rv = file->GetParent(getter_AddRefs(parent)); + + if (parent && NS_SUCCEEDED(rv)) { + net_GetURLSpecFromDir(parent, url); + if (NS_FAILED(rv)) return rv; + parentStr.Assign(url); + } + + // Directory index will be always encoded in UTF-8 if this is file url + buffer.AppendLiteral("<meta charset=\"UTF-8\">\n"); + + } else if (NS_SUCCEEDED(uri->SchemeIs("jar", &isScheme)) && isScheme) { + nsAutoCString path; + rv = uri->GetPath(path); + if (NS_FAILED(rv)) return rv; + + // a top-level jar directory URL is of the form jar:foo.zip!/ + // path will be of the form foo.zip!/, and its last two characters + // will be "!/" + //XXX this won't work correctly when the name of the directory being + //XXX displayed ends with "!", but then again, jar: URIs don't deal + //XXX particularly well with such directories anyway + if (!StringEndsWith(path, NS_LITERAL_CSTRING("!/"))) { + rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr); + if (NS_FAILED(rv)) return rv; + } + } + else { + // default behavior for other protocols is to assume the channel's + // URL references a directory ending in '/' -- fixup if necessary. + nsAutoCString path; + rv = uri->GetPath(path); + if (NS_FAILED(rv)) return rv; + if (baseUri.Last() != '/') { + baseUri.Append('/'); + path.Append('/'); + uri->SetPath(path); + } + if (!path.EqualsLiteral("/")) { + rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr); + if (NS_FAILED(rv)) return rv; + } + } + + buffer.AppendLiteral("<style type=\"text/css\">\n" + ":root {\n" + " font-family: sans-serif;\n" + "}\n" + "img {\n" + " border: 0;\n" + "}\n" + "th {\n" + " text-align: start;\n" + " white-space: nowrap;\n" + "}\n" + "th > a {\n" + " color: inherit;\n" + "}\n" + "table[order] > thead > tr > th {\n" + " cursor: pointer;\n" + "}\n" + "table[order] > thead > tr > th::after {\n" + " display: none;\n" + " width: .8em;\n" + " margin-inline-end: -.8em;\n" + " text-align: end;\n" + "}\n" + "table[order=\"asc\"] > thead > tr > th::after {\n" + " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n" + "}\n" + "table[order=\"desc\"] > thead > tr > th::after {\n" + " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n" + "}\n" + "table[order][order-by=\"0\"] > thead > tr > th:first-child > a ,\n" + "table[order][order-by=\"1\"] > thead > tr > th:first-child + th > a ,\n" + "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th > a {\n" + " text-decoration: underline;\n" + "}\n" + "table[order][order-by=\"0\"] > thead > tr > th:first-child::after ,\n" + "table[order][order-by=\"1\"] > thead > tr > th:first-child + th::after ,\n" + "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th::after {\n" + " display: inline-block;\n" + "}\n" + "table.remove-hidden > tbody > tr.hidden-object {\n" + " display: none;\n" + "}\n" + "td {\n" + " white-space: nowrap;\n" + "}\n" + "table.ellipsis {\n" + " width: 100%;\n" + " table-layout: fixed;\n" + " border-spacing: 0;\n" + "}\n" + "table.ellipsis > tbody > tr > td {\n" + " padding: 0;\n" + " overflow: hidden;\n" + " text-overflow: ellipsis;\n" + "}\n" + "/* name */\n" + "/* name */\n" + "th:first-child {\n" + " padding-inline-end: 2em;\n" + "}\n" + "/* size */\n" + "th:first-child + th {\n" + " padding-inline-end: 1em;\n" + "}\n" + "td:first-child + td {\n" + " text-align: end;\n" + " padding-inline-end: 1em;\n" + "}\n" + "/* date */\n" + "td:first-child + td + td {\n" + " padding-inline-start: 1em;\n" + " padding-inline-end: .5em;\n" + "}\n" + "/* time */\n" + "td:first-child + td + td + td {\n" + " padding-inline-start: .5em;\n" + "}\n" + ".symlink {\n" + " font-style: italic;\n" + "}\n" + ".dir ,\n" + ".symlink ,\n" + ".file {\n" + " margin-inline-start: 20px;\n" + "}\n" + ".dir::before ,\n" + ".file > img {\n" + " margin-inline-end: 4px;\n" + " margin-inline-start: -20px;\n" + " max-width: 16px;\n" + " max-height: 16px;\n" + " vertical-align: middle;\n" + "}\n" + ".dir::before {\n" + " content: url(resource://gre/res/html/folder.png);\n" + "}\n" + "</style>\n" + "<link rel=\"stylesheet\" media=\"screen, projection\" type=\"text/css\"" + " href=\"chrome://global/skin/dirListing/dirListing.css\">\n" + "<script type=\"application/javascript\">\n" + "'use strict';\n" + "var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;\n" + "document.addEventListener(\"DOMContentLoaded\", function() {\n" + " gTable = document.getElementsByTagName(\"table\")[0];\n" + " gTBody = gTable.tBodies[0];\n" + " if (gTBody.rows.length < 2)\n" + " return;\n" + " gUI_showHidden = document.getElementById(\"UI_showHidden\");\n" + " var headCells = gTable.tHead.rows[0].cells,\n" + " hiddenObjects = false;\n" + " function rowAction(i) {\n" + " return function(event) {\n" + " event.preventDefault();\n" + " orderBy(i);\n" + " }\n" + " }\n" + " for (var i = headCells.length - 1; i >= 0; i--) {\n" + " var anchor = document.createElement(\"a\");\n" + " anchor.href = \"\";\n" + " anchor.appendChild(headCells[i].firstChild);\n" + " headCells[i].appendChild(anchor);\n" + " headCells[i].addEventListener(\"click\", rowAction(i), true);\n" + " }\n" + " if (gUI_showHidden) {\n" + " gRows = Array.slice(gTBody.rows);\n" + " hiddenObjects = gRows.some(row => row.className == \"hidden-object\");\n" + " }\n" + " gTable.setAttribute(\"order\", \"\");\n" + " if (hiddenObjects) {\n" + " gUI_showHidden.style.display = \"block\";\n" + " updateHidden();\n" + " }\n" + "}, \"false\");\n" + "function compareRows(rowA, rowB) {\n" + " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n" + " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n" + " var intA = +a;\n" + " var intB = +b;\n" + " if (a == intA && b == intB) {\n" + " a = intA;\n" + " b = intB;\n" + " } else {\n" + " a = a.toLowerCase();\n" + " b = b.toLowerCase();\n" + " }\n" + " if (a < b)\n" + " return -1;\n" + " if (a > b)\n" + " return 1;\n" + " return 0;\n" + "}\n" + "function orderBy(column) {\n" + " if (!gRows)\n" + " gRows = Array.slice(gTBody.rows);\n" + " var order;\n" + " if (gOrderBy == column) {\n" + " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : \"asc\";\n" + " } else {\n" + " order = \"asc\";\n" + " gOrderBy = column;\n" + " gTable.setAttribute(\"order-by\", column);\n" + " gRows.sort(compareRows);\n" + " }\n" + " gTable.removeChild(gTBody);\n" + " gTable.setAttribute(\"order\", order);\n" + " if (order == \"asc\")\n" + " for (var i = 0; i < gRows.length; i++)\n" + " gTBody.appendChild(gRows[i]);\n" + " else\n" + " for (var i = gRows.length - 1; i >= 0; i--)\n" + " gTBody.appendChild(gRows[i]);\n" + " gTable.appendChild(gTBody);\n" + "}\n" + "function updateHidden() {\n" + " gTable.className = gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n" + " \"\" :\n" + " \"remove-hidden\";\n" + "}\n" + "</script>\n"); + + buffer.AppendLiteral("<link rel=\"icon\" type=\"image/png\" href=\""); + nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri); + if (!innerUri) + return NS_ERROR_UNEXPECTED; + nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(innerUri)); + //XXX bug 388553: can't use skinnable icons here due to security restrictions + if (fileURL) { + //buffer.AppendLiteral("chrome://global/skin/dirListing/local.png"); + buffer.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB" + "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i" + "ZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR" + "3XQ3ONASdBJCSBxHos5%2B3Bg3rvkCv8PElS78gPkO%2FATj" + "QoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12O" + "YIDhcPiu2Wx%2B%2FHF5CW1Z6Jyegt%2FTNEWSJIjjGFEUIQ" + "xDrFYrWFSzXC4%2FdLvd95pRKpXKy%2BpRFZ7nwaWo1%2BsG" + "nQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5" + "obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM%2FS" + "KK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9c" + "lkYoDo3SZJzzSdp0VSKYmfV1co%2Bz580kw5KDIM8RbRfEnU" + "f1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd%2BzGQQB5llunF" + "%2FM52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxi" + "xfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18l" + "yWh5FFbrOSxbl3V4G%2BVB7T4ajYYxTyuLtO%2BCvWGgJE1M" + "c7JNsJEhvgw%2FQV4fo%2F24nbEsX2u1d5sVyn8sJO0ZAQiI" + "YnFh%2BxrfLz%2Fj29cBS%2FO14zg3i8XigW3ZkErDtmKoeM" + "%2BAJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ%2B" + "4pQ1%2FlPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj%2F8m%2B8" + "y%2FkrwABHbz2H9V68DQAAAABJRU5ErkJggg%3D%3D"); + } else { + //buffer.AppendLiteral("chrome://global/skin/dirListing/remote.png"); + buffer.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB" + "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i" + "ZSBJbWFnZVJlYWR5ccllPAAAAeBJREFUeNqcU81O20AQ%2Ft" + "Z2AgQSYQRqL1UPVG2hAUQkxLEStz4DrXpLpD5Drz31Cajax%" + "2Bghhx6qHIJURBTxIwQRwopCBbZjHMcOTrzermPipsSt1Iw0" + "3p3ZmW%2B%2B2R0TxhgOD34wjCHZlQ0iDYz9yvEfhxMTCYhE" + "QDIZhkxKd2sqzX2TOD2vBQCQhpPefng1ZP2dVPlLLdpL8SEM" + "cxng%2Fbs0RIHhtgs4twxOh%2BHjZxvzDx%2F3GQQiDFISiR" + "BLFMPKTRMollzcWECrDVhtxtdRVsL9youPxGj%2FbdfFlUZh" + "tDyYbYqWRUdai1oQRZ5oHeHl2gNM%2B01Uqio8RlH%2Bnsaz" + "JzNwXcq1B%2BiXPHprlEEymeBfXs1w8XxxihfyuXqoHqpoGj" + "ZM04bddgG%2F9%2B8WGj87qDdsrK9m%2BoA%2BpbhQTDh2l1" + "%2Bi2weNbSHMZyjvNXmVbqh9Fj5Oz27uEoP%2BSTxANruJs9" + "L%2FT6P0ewqPx5nmiAG5f6AoCtN1PbJzuRyJAyDBzzSQYvEr" + "f06yYxhGXlEa8H2KVGoasjwLx3Ewk858opQWXm%2B%2Fib9E" + "QrBzclLLLy89xYvlpchvtixcX6uo1y%2FzsiwHrkIsgKbp%2" + "BYWFOWicuqppoNTnStHzPFCPQhBEBOyGAX4JMADFetubi4BS" + "YAAAAABJRU5ErkJggg%3D%3D"); + } + buffer.AppendLiteral("\">\n<title>"); + + // Everything needs to end in a /, + // otherwise we end up linking to file:///foo/dirfile + + if (!mTextToSubURI) { + mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + } + + nsXPIDLCString encoding; + rv = uri->GetOriginCharset(encoding); + if (NS_FAILED(rv)) return rv; + if (encoding.IsEmpty()) { + encoding.AssignLiteral("UTF-8"); + } + + nsXPIDLString unEscapeSpec; + rv = mTextToSubURI->UnEscapeAndConvert(encoding, titleUri.get(), + getter_Copies(unEscapeSpec)); + // unescape may fail because + // 1. file URL may be encoded in platform charset for backward compatibility + // 2. query part may not be encoded in UTF-8 (see bug 261929) + // so try the platform's default if this is file url + if (NS_FAILED(rv) && isSchemeFile) { + nsCOMPtr<nsIPlatformCharset> platformCharset(do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString charset; + rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName, charset); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mTextToSubURI->UnEscapeAndConvert(charset.get(), titleUri.get(), + getter_Copies(unEscapeSpec)); + } + if (NS_FAILED(rv)) return rv; + + nsXPIDLString htmlEscSpec; + htmlEscSpec.Adopt(nsEscapeHTML2(unEscapeSpec.get(), + unEscapeSpec.Length())); + + nsXPIDLString title; + const char16_t* formatTitle[] = { + htmlEscSpec.get() + }; + + rv = mBundle->FormatStringFromName(u"DirTitle", + formatTitle, + sizeof(formatTitle)/sizeof(char16_t*), + getter_Copies(title)); + if (NS_FAILED(rv)) return rv; + + // we want to convert string bundle to NCR + // to ensure they're shown in any charsets + AppendNonAsciiToNCR(title, buffer); + + buffer.AppendLiteral("</title>\n"); + + // If there is a quote character in the baseUri, then + // lets not add a base URL. The reason for this is that + // if we stick baseUri containing a quote into a quoted + // string, the quote character will prematurely close + // the base href string. This is a fall-back check; + // that's why it is OK to not use a base rather than + // trying to play nice and escaping the quotes. See bug + // 358128. + + if (!baseUri.Contains('"')) + { + // Great, the baseUri does not contain a char that + // will prematurely close the string. Go ahead an + // add a base href, but only do so if we're not + // dealing with a resource URI. + nsCOMPtr<nsIURI> originalUri; + rv = channel->GetOriginalURI(getter_AddRefs(originalUri)); + bool wasResource = false; + if (NS_FAILED(rv) || + NS_FAILED(originalUri->SchemeIs("resource", &wasResource)) || + !wasResource) { + buffer.AppendLiteral("<base href=\""); + nsAdoptingCString htmlEscapedUri(nsEscapeHTML(baseUri.get())); + buffer.Append(htmlEscapedUri); + buffer.AppendLiteral("\" />\n"); + } + } + else + { + NS_ERROR("broken protocol handler didn't escape double-quote."); + } + + nsCString direction(NS_LITERAL_CSTRING("ltr")); + nsCOMPtr<nsIXULChromeRegistry> reg = + mozilla::services::GetXULChromeRegistryService(); + if (reg) { + bool isRTL = false; + reg->IsLocaleRTL(NS_LITERAL_CSTRING("global"), &isRTL); + if (isRTL) { + direction.AssignLiteral("rtl"); + } + } + + buffer.AppendLiteral("</head>\n<body dir=\""); + buffer.Append(direction); + buffer.AppendLiteral("\">\n<h1>"); + + const char16_t* formatHeading[] = { + htmlEscSpec.get() + }; + + rv = mBundle->FormatStringFromName(u"DirTitle", + formatHeading, + sizeof(formatHeading)/sizeof(char16_t*), + getter_Copies(title)); + if (NS_FAILED(rv)) return rv; + + AppendNonAsciiToNCR(title, buffer); + buffer.AppendLiteral("</h1>\n"); + + if (!parentStr.IsEmpty()) { + nsXPIDLString parentText; + rv = mBundle->GetStringFromName(u"DirGoUp", + getter_Copies(parentText)); + if (NS_FAILED(rv)) return rv; + + buffer.AppendLiteral("<p id=\"UI_goUp\"><a class=\"up\" href=\""); + + nsAdoptingCString htmlParentStr(nsEscapeHTML(parentStr.get())); + buffer.Append(htmlParentStr); + buffer.AppendLiteral("\">"); + AppendNonAsciiToNCR(parentText, buffer); + buffer.AppendLiteral("</a></p>\n"); + } + + if (isSchemeFile) { + nsXPIDLString showHiddenText; + rv = mBundle->GetStringFromName(u"ShowHidden", + getter_Copies(showHiddenText)); + if (NS_FAILED(rv)) return rv; + + buffer.AppendLiteral("<p id=\"UI_showHidden\" style=\"display:none\"><label><input type=\"checkbox\" checked onchange=\"updateHidden()\">"); + AppendNonAsciiToNCR(showHiddenText, buffer); + buffer.AppendLiteral("</label></p>\n"); + } + + buffer.AppendLiteral("<table>\n"); + + nsXPIDLString columnText; + + buffer.AppendLiteral(" <thead>\n" + " <tr>\n" + " <th>"); + + rv = mBundle->GetStringFromName(u"DirColName", + getter_Copies(columnText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(columnText, buffer); + buffer.AppendLiteral("</th>\n" + " <th>"); + + rv = mBundle->GetStringFromName(u"DirColSize", + getter_Copies(columnText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(columnText, buffer); + buffer.AppendLiteral("</th>\n" + " <th colspan=\"2\">"); + + rv = mBundle->GetStringFromName(u"DirColMTime", + getter_Copies(columnText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(columnText, buffer); + buffer.AppendLiteral("</th>\n" + " </tr>\n" + " </thead>\n"); + buffer.AppendLiteral(" <tbody>\n"); + + aBuffer = buffer; + return rv; +} + +NS_IMETHODIMP +nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsISupports *aContext, + nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { + nsCString buffer; + buffer.AssignLiteral("</tbody></table></body></html>\n"); + + aStatus = SendToListener(request, aContext, buffer); + } + + mParser->OnStopRequest(request, aContext, aStatus); + mParser = nullptr; + + return mListener->OnStopRequest(request, aContext, aStatus); +} + +nsresult +nsIndexedToHTML::SendToListener(nsIRequest* aRequest, nsISupports *aContext, const nsACString &aBuffer) +{ + nsCOMPtr<nsIInputStream> inputData; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputData), aBuffer); + NS_ENSURE_SUCCESS(rv, rv); + return mListener->OnDataAvailable(aRequest, aContext, + inputData, 0, aBuffer.Length()); +} + +NS_IMETHODIMP +nsIndexedToHTML::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aCtxt, + nsIInputStream* aInput, + uint64_t aOffset, + uint32_t aCount) { + return mParser->OnDataAvailable(aRequest, aCtxt, aInput, aOffset, aCount); +} + +NS_IMETHODIMP +nsIndexedToHTML::OnIndexAvailable(nsIRequest *aRequest, + nsISupports *aCtxt, + nsIDirIndex *aIndex) { + nsresult rv; + if (!aIndex) + return NS_ERROR_NULL_POINTER; + + nsCString pushBuffer; + pushBuffer.AppendLiteral("<tr"); + + // We don't know the file's character set yet, so retrieve the raw bytes + // which will be decoded by the HTML parser. + nsXPIDLCString loc; + aIndex->GetLocation(getter_Copies(loc)); + + // Adjust the length in case unescaping shortened the string. + loc.Truncate(nsUnescapeCount(loc.BeginWriting())); + + if (loc.IsEmpty()) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (loc.First() == char16_t('.')) + pushBuffer.AppendLiteral(" class=\"hidden-object\""); + + pushBuffer.AppendLiteral(">\n <td sortable-data=\""); + + // The sort key is the name of the item, prepended by either 0, 1 or 2 + // in order to group items. + uint32_t type; + aIndex->GetType(&type); + switch (type) { + case nsIDirIndex::TYPE_SYMLINK: + pushBuffer.Append('0'); + break; + case nsIDirIndex::TYPE_DIRECTORY: + pushBuffer.Append('1'); + break; + default: + pushBuffer.Append('2'); + break; + } + nsAdoptingCString escaped(nsEscapeHTML(loc)); + pushBuffer.Append(escaped); + + pushBuffer.AppendLiteral("\"><table class=\"ellipsis\"><tbody><tr><td><a class=\""); + switch (type) { + case nsIDirIndex::TYPE_DIRECTORY: + pushBuffer.AppendLiteral("dir"); + break; + case nsIDirIndex::TYPE_SYMLINK: + pushBuffer.AppendLiteral("symlink"); + break; + default: + pushBuffer.AppendLiteral("file"); + break; + } + + pushBuffer.AppendLiteral("\" href=\""); + + // need to escape links + nsAutoCString locEscaped; + + // Adding trailing slash helps to recognize whether the URL points to a file + // or a directory (bug #214405). + if ((type == nsIDirIndex::TYPE_DIRECTORY) && (loc.Last() != '/')) { + loc.Append('/'); + } + + // now minimally re-escape the location... + uint32_t escFlags; + // for some protocols, we expect the location to be absolute. + // if so, and if the location indeed appears to be a valid URI, then go + // ahead and treat it like one. + + nsAutoCString scheme; + if (mExpectAbsLoc && + NS_SUCCEEDED(net_ExtractURLScheme(loc, scheme))) { + // escape as absolute + escFlags = esc_Forced | esc_AlwaysCopy | esc_Minimal; + } + else { + // escape as relative + // esc_Directory is needed because directories have a trailing slash. + // Without it, the trailing '/' will be escaped, and links from within + // that directory will be incorrect + escFlags = esc_Forced | esc_AlwaysCopy | esc_FileBaseName | esc_Colon | esc_Directory; + } + NS_EscapeURL(loc.get(), loc.Length(), escFlags, locEscaped); + // esc_Directory does not escape the semicolons, so if a filename + // contains semicolons we need to manually escape them. + // This replacement should be removed in bug #473280 + locEscaped.ReplaceSubstring(";", "%3b"); + nsAdoptingCString htmlEscapedURL(nsEscapeHTML(locEscaped.get())); + pushBuffer.Append(htmlEscapedURL); + + pushBuffer.AppendLiteral("\">"); + + if (type == nsIDirIndex::TYPE_FILE || type == nsIDirIndex::TYPE_UNKNOWN) { + pushBuffer.AppendLiteral("<img src=\"moz-icon://"); + int32_t lastDot = locEscaped.RFindChar('.'); + if (lastDot != kNotFound) { + locEscaped.Cut(0, lastDot); + nsAdoptingCString htmlFileExt(nsEscapeHTML(locEscaped.get())); + pushBuffer.Append(htmlFileExt); + } else { + pushBuffer.AppendLiteral("unknown"); + } + pushBuffer.AppendLiteral("?size=16\" alt=\""); + + nsXPIDLString altText; + rv = mBundle->GetStringFromName(u"DirFileLabel", + getter_Copies(altText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(altText, pushBuffer); + pushBuffer.AppendLiteral("\">"); + } + + pushBuffer.Append(escaped); + pushBuffer.AppendLiteral("</a></td></tr></tbody></table></td>\n <td"); + + if (type == nsIDirIndex::TYPE_DIRECTORY || type == nsIDirIndex::TYPE_SYMLINK) { + pushBuffer.Append('>'); + } else { + int64_t size; + aIndex->GetSize(&size); + + if (uint64_t(size) != UINT64_MAX) { + pushBuffer.AppendLiteral(" sortable-data=\""); + pushBuffer.AppendInt(size); + pushBuffer.AppendLiteral("\">"); + nsAutoCString sizeString; + FormatSizeString(size, sizeString); + pushBuffer.Append(sizeString); + } else { + pushBuffer.Append('>'); + } + } + pushBuffer.AppendLiteral("</td>\n <td"); + + PRTime t; + aIndex->GetLastModified(&t); + + if (t == -1LL) { + pushBuffer.AppendLiteral("></td>\n <td>"); + } else { + pushBuffer.AppendLiteral(" sortable-data=\""); + pushBuffer.AppendInt(static_cast<int64_t>(t)); + pushBuffer.AppendLiteral("\">"); + nsAutoString formatted; + mDateTime->FormatPRTime(nullptr, + kDateFormatShort, + kTimeFormatNone, + t, + formatted); + AppendNonAsciiToNCR(formatted, pushBuffer); + pushBuffer.AppendLiteral("</td>\n <td>"); + mDateTime->FormatPRTime(nullptr, + kDateFormatNone, + kTimeFormatSeconds, + t, + formatted); + // use NCR to show date in any doc charset + AppendNonAsciiToNCR(formatted, pushBuffer); + } + + pushBuffer.AppendLiteral("</td>\n</tr>"); + + return SendToListener(aRequest, aCtxt, pushBuffer); +} + +NS_IMETHODIMP +nsIndexedToHTML::OnInformationAvailable(nsIRequest *aRequest, + nsISupports *aCtxt, + const nsAString& aInfo) { + nsAutoCString pushBuffer; + nsAdoptingString escaped(nsEscapeHTML2(PromiseFlatString(aInfo).get())); + if (!escaped) + return NS_ERROR_OUT_OF_MEMORY; + pushBuffer.AppendLiteral("<tr>\n <td>"); + // escaped is provided in Unicode, so write hex NCRs as necessary + // to prevent the HTML parser from applying a character set. + AppendNonAsciiToNCR(escaped, pushBuffer); + pushBuffer.AppendLiteral("</td>\n <td></td>\n <td></td>\n <td></td>\n</tr>\n"); + + return SendToListener(aRequest, aCtxt, pushBuffer); +} + +void nsIndexedToHTML::FormatSizeString(int64_t inSize, nsCString& outSizeString) +{ + outSizeString.Truncate(); + if (inSize > int64_t(0)) { + // round up to the nearest Kilobyte + int64_t upperSize = (inSize + int64_t(1023)) / int64_t(1024); + outSizeString.AppendInt(upperSize); + outSizeString.AppendLiteral(" KB"); + } +} + +nsIndexedToHTML::nsIndexedToHTML() { +} + +nsIndexedToHTML::~nsIndexedToHTML() { +} |