diff -r 000000000000 -r 6474c204b198 netwerk/streamconv/converters/nsIndexedToHTML.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,880 @@ +/* -*- 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 "nsIFileURL.h" +#include "nsEscape.h" +#include "nsIDirIndex.h" +#include "nsDateTimeFormatCID.h" +#include "nsURLHelper.h" +#include "nsIPlatformCharset.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsIChromeRegistry.h" +#include "nsICharsetConverterManager.h" +#include "nsIDateTimeFormat.h" +#include "nsIStringBundle.h" +#include "nsITextToSubURI.h" +#include "nsXPIDLString.h" +#include + +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 = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr 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 channel = do_QueryInterface(request); + nsCOMPtr 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("\n\n\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 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 fileUrl = do_QueryInterface(uri); + nsCOMPtr 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 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("\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("\n" + "\n" + "\n"); + + buffer.AppendLiteral(" innerUri = NS_GetInnermostURI(uri); + if (!innerUri) + return NS_ERROR_UNEXPECTED; + nsCOMPtr 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"); + + // 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(MOZ_UTF16("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("\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.FindChar('"') == kNotFound) + { + // Great, the baseUri does not contain a char that + // will prematurely close the string. Go ahead an + // add a base href. + buffer.AppendLiteral("\n"); + } + else + { + NS_ERROR("broken protocol handler didn't escape double-quote."); + } + + nsCString direction(NS_LITERAL_CSTRING("ltr")); + nsCOMPtr reg = + mozilla::services::GetXULChromeRegistryService(); + if (reg) { + bool isRTL = false; + reg->IsLocaleRTL(NS_LITERAL_CSTRING("global"), &isRTL); + if (isRTL) { + direction.AssignLiteral("rtl"); + } + } + + buffer.AppendLiteral("\n\n

"); + + const char16_t* formatHeading[] = { + htmlEscSpec.get() + }; + + rv = mBundle->FormatStringFromName(MOZ_UTF16("DirTitle"), + formatHeading, + sizeof(formatHeading)/sizeof(char16_t*), + getter_Copies(title)); + if (NS_FAILED(rv)) return rv; + + AppendNonAsciiToNCR(title, buffer); + buffer.AppendLiteral("

\n"); + + if (!parentStr.IsEmpty()) { + nsXPIDLString parentText; + rv = mBundle->GetStringFromName(MOZ_UTF16("DirGoUp"), + getter_Copies(parentText)); + if (NS_FAILED(rv)) return rv; + + buffer.AppendLiteral("

"); + AppendNonAsciiToNCR(parentText, buffer); + buffer.AppendLiteral("

\n"); + } + + if (isSchemeFile) { + nsXPIDLString showHiddenText; + rv = mBundle->GetStringFromName(MOZ_UTF16("ShowHidden"), + getter_Copies(showHiddenText)); + if (NS_FAILED(rv)) return rv; + + buffer.AppendLiteral("

\n"); + } + + buffer.AppendLiteral("\n"); + + nsXPIDLString columnText; + + buffer.AppendLiteral(" \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); + buffer.AppendLiteral(" \n"); + + aBuffer = buffer; + return rv; +} + +NS_IMETHODIMP +nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsISupports *aContext, + nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { + nsCString buffer; + buffer.AssignLiteral("
"); + + rv = mBundle->GetStringFromName(MOZ_UTF16("DirColName"), + getter_Copies(columnText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(columnText, buffer); + buffer.AppendLiteral(""); + + rv = mBundle->GetStringFromName(MOZ_UTF16("DirColSize"), + getter_Copies(columnText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(columnText, buffer); + buffer.AppendLiteral(""); + + rv = mBundle->GetStringFromName(MOZ_UTF16("DirColMTime"), + getter_Copies(columnText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(columnText, buffer); + buffer.AppendLiteral("
\n"); + + aStatus = SendToListener(request, aContext, buffer); + } + + mParser->OnStopRequest(request, aContext, aStatus); + mParser = 0; + + return mListener->OnStopRequest(request, aContext, aStatus); +} + +nsresult +nsIndexedToHTML::SendToListener(nsIRequest* aRequest, nsISupports *aContext, const nsACString &aBuffer) +{ + nsCOMPtr 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("GetLocation(getter_Copies(loc)); + + // Adjust the length in case unescaping shortened the string. + loc.Truncate(nsUnescapeCount(loc.BeginWriting())); + if (loc.First() == PRUnichar('.')) + pushBuffer.AppendLiteral(" class=\"hidden-object\""); + + pushBuffer.AppendLiteral(">\n 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("\">
"); + + if (type == nsIDirIndex::TYPE_FILE || type == nsIDirIndex::TYPE_UNKNOWN) { + pushBuffer.AppendLiteral("\"");GetStringFromName(MOZ_UTF16("DirFileLabel"), + getter_Copies(altText)); + if (NS_FAILED(rv)) return rv; + AppendNonAsciiToNCR(altText, pushBuffer); + pushBuffer.AppendLiteral("\">"); + } + + pushBuffer.Append(escaped); + pushBuffer.AppendLiteral("
\n "); + } 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.AppendLiteral(">"); + } + } + pushBuffer.AppendLiteral("\n GetLastModified(&t); + + if (t == -1) { + pushBuffer.AppendLiteral(">\n "); + } else { + pushBuffer.AppendLiteral(" sortable-data=\""); + pushBuffer.AppendInt(static_cast(t)); + pushBuffer.AppendLiteral("\">"); + nsAutoString formatted; + mDateTime->FormatPRTime(nullptr, + kDateFormatShort, + kTimeFormatNone, + t, + formatted); + AppendNonAsciiToNCR(formatted, pushBuffer); + pushBuffer.AppendLiteral("\n "); + mDateTime->FormatPRTime(nullptr, + kDateFormatNone, + kTimeFormatSeconds, + t, + formatted); + // use NCR to show date in any doc charset + AppendNonAsciiToNCR(formatted, pushBuffer); + } + + pushBuffer.AppendLiteral("\n"); + + 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("\n "); + // 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("\n \n \n \n\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() { +}