1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Bradley Baetz <bbaetz@cs.mcgill.ca>
24 * Christopher A. Aillon <christopher@aillon.com>
25 * Dão Gottwald <dao@design-noir.de>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #include "nsIndexedToHTML.h"
42 #include "nsNetUtil.h"
44 #include "nsStringStream.h"
45 #include "nsIFileURL.h"
47 #include "nsIDirIndex.h"
49 #include "nsDateTimeFormatCID.h"
50 #include "nsURLHelper.h"
52 #include "nsIPlatformCharset.h"
53 #include "nsIPrefService.h"
54 #include "nsIPrefBranch.h"
55 #include "nsIPrefLocalizedString.h"
57 NS_IMPL_ISUPPORTS4(nsIndexedToHTML
,
63 static void AppendNonAsciiToNCR(const nsAString
& in
, nsAFlatString
& out
)
65 nsAString::const_iterator start
, end
;
67 in
.BeginReading(start
);
70 while (start
!= end
) {
74 out
.AppendLiteral("&#x");
76 hex
.AppendInt(*start
++, 16);
78 out
.Append((PRUnichar
)';');
84 nsIndexedToHTML::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
) {
87 return NS_ERROR_NO_AGGREGATION
;
89 nsIndexedToHTML
* _s
= new nsIndexedToHTML();
91 return NS_ERROR_OUT_OF_MEMORY
;
93 rv
= _s
->QueryInterface(aIID
, aResult
);
98 nsIndexedToHTML::Init(nsIStreamListener
* aListener
) {
100 nsXPIDLString ellipsis
;
101 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID
));
103 nsCOMPtr
<nsIPrefLocalizedString
> prefVal
;
104 prefs
->GetComplexValue("intl.ellipsis",
105 NS_GET_IID(nsIPrefLocalizedString
),
106 getter_AddRefs(prefVal
));
108 prefVal
->ToString(getter_Copies(ellipsis
));
110 if (ellipsis
.IsEmpty())
111 mEscapedEllipsis
.AppendLiteral("…");
113 mEscapedEllipsis
.Adopt(nsEscapeHTML2(ellipsis
.get(), ellipsis
.Length()));
117 mListener
= aListener
;
119 mDateTime
= do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID
, &rv
);
123 nsCOMPtr
<nsIStringBundleService
> sbs
=
124 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
125 if (NS_FAILED(rv
)) return rv
;
126 rv
= sbs
->CreateBundle(NECKO_MSGS_URL
, getter_AddRefs(mBundle
));
128 mExpectAbsLoc
= PR_FALSE
;
134 nsIndexedToHTML::Convert(nsIInputStream
* aFromStream
,
135 const char* aFromType
,
138 nsIInputStream
** res
) {
139 return NS_ERROR_NOT_IMPLEMENTED
;
143 nsIndexedToHTML::AsyncConvertData(const char *aFromType
,
145 nsIStreamListener
*aListener
,
146 nsISupports
*aCtxt
) {
147 return Init(aListener
);
151 nsIndexedToHTML::OnStartRequest(nsIRequest
* request
, nsISupports
*aContext
) {
154 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
155 nsCOMPtr
<nsIURI
> uri
;
156 rv
= channel
->GetURI(getter_AddRefs(uri
));
157 if (NS_FAILED(rv
)) return rv
;
159 channel
->SetContentType(NS_LITERAL_CSTRING("text/html"));
161 mParser
= do_CreateInstance("@mozilla.org/dirIndexParser;1",&rv
);
162 if (NS_FAILED(rv
)) return rv
;
164 rv
= mParser
->SetListener(this);
165 if (NS_FAILED(rv
)) return rv
;
167 rv
= mParser
->OnStartRequest(request
, aContext
);
168 if (NS_FAILED(rv
)) return rv
;
170 nsCAutoString baseUri
, titleUri
;
171 rv
= uri
->GetAsciiSpec(baseUri
);
172 if (NS_FAILED(rv
)) return rv
;
177 // XXX - should be using the 300: line from the parser.
178 // We can't guarantee that that comes before any entry, so we'd have to
179 // buffer, and do other painful stuff.
180 // I'll deal with this when I make the changes to handle welcome messages
181 // The .. stuff should also come from the lower level protocols, but that
182 // would muck up the XUL display
185 PRBool isScheme
= PR_FALSE
;
186 PRBool isSchemeFile
= PR_FALSE
;
187 PRBool isSchemeGopher
= PR_FALSE
;
188 if (NS_SUCCEEDED(uri
->SchemeIs("ftp", &isScheme
)) && isScheme
) {
190 // strip out the password here, so it doesn't show in the page title
191 // This is done by the 300: line generation in ftp, but we don't use
195 rv
= uri
->GetPassword(pw
);
196 if (NS_FAILED(rv
)) return rv
;
198 nsCOMPtr
<nsIURI
> newUri
;
199 rv
= uri
->Clone(getter_AddRefs(newUri
));
200 if (NS_FAILED(rv
)) return rv
;
201 rv
= newUri
->SetPassword(EmptyCString());
202 if (NS_FAILED(rv
)) return rv
;
203 rv
= newUri
->GetAsciiSpec(titleUri
);
204 if (NS_FAILED(rv
)) return rv
;
208 rv
= uri
->GetPath(path
);
209 if (NS_FAILED(rv
)) return rv
;
211 if (!path
.EqualsLiteral("//") && !path
.LowerCaseEqualsLiteral("/%2f")) {
212 rv
= uri
->Resolve(NS_LITERAL_CSTRING(".."),parentStr
);
213 if (NS_FAILED(rv
)) return rv
;
215 } else if (NS_SUCCEEDED(uri
->SchemeIs("file", &isSchemeFile
)) && isSchemeFile
) {
216 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(uri
);
217 nsCOMPtr
<nsIFile
> file
;
218 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
219 if (NS_FAILED(rv
)) return rv
;
220 nsCOMPtr
<nsILocalFile
> lfile
= do_QueryInterface(file
, &rv
);
221 if (NS_FAILED(rv
)) return rv
;
222 lfile
->SetFollowLinks(PR_TRUE
);
225 rv
= net_GetURLSpecFromFile(file
, url
);
226 if (NS_FAILED(rv
)) return rv
;
229 nsCOMPtr
<nsIFile
> parent
;
230 rv
= file
->GetParent(getter_AddRefs(parent
));
232 if (parent
&& NS_SUCCEEDED(rv
)) {
233 net_GetURLSpecFromFile(parent
, url
);
234 if (NS_FAILED(rv
)) return rv
;
235 parentStr
.Assign(url
);
238 // Directory index will be always encoded in UTF-8 if this is file url
239 rv
= mParser
->SetEncoding("UTF-8");
240 NS_ENSURE_SUCCESS(rv
, rv
);
242 } else if (NS_SUCCEEDED(uri
->SchemeIs("gopher", &isSchemeGopher
)) && isSchemeGopher
) {
243 mExpectAbsLoc
= PR_TRUE
;
244 } else if (NS_SUCCEEDED(uri
->SchemeIs("jar", &isScheme
)) && isScheme
) {
246 rv
= uri
->GetPath(path
);
247 if (NS_FAILED(rv
)) return rv
;
249 // a top-level jar directory URL is of the form jar:foo.zip!/
250 // path will be of the form foo.zip!/, and its last two characters
252 //XXX this won't work correctly when the name of the directory being
253 //XXX displayed ends with "!", but then again, jar: URIs don't deal
254 //XXX particularly well with such directories anyway
255 if (!StringEndsWith(path
, NS_LITERAL_CSTRING("!/"))) {
256 rv
= uri
->Resolve(NS_LITERAL_CSTRING(".."), parentStr
);
257 if (NS_FAILED(rv
)) return rv
;
261 // default behavior for other protocols is to assume the channel's
262 // URL references a directory ending in '/' -- fixup if necessary.
264 rv
= uri
->GetPath(path
);
265 if (NS_FAILED(rv
)) return rv
;
266 if (baseUri
.Last() != '/') {
271 if (!path
.EqualsLiteral("/")) {
272 rv
= uri
->Resolve(NS_LITERAL_CSTRING(".."), parentStr
);
273 if (NS_FAILED(rv
)) return rv
;
278 buffer
.AppendLiteral("<!DOCTYPE html>\n"
280 "<meta http-equiv=\"content-type\" content=\"text/html; charset=");
282 // Get the encoding from the parser
283 // XXX - this won't work for any encoding set via a 301: line in the
284 // format - this output stuff would need to move to OnDataAvailable
287 nsXPIDLCString encoding
;
288 rv
= mParser
->GetEncoding(getter_Copies(encoding
));
289 if (NS_FAILED(rv
)) return rv
;
291 AppendASCIItoUTF16(encoding
, buffer
);
292 buffer
.AppendLiteral("\">\n"
293 "<style type=\"text/css\">\n"
295 " font-family: sans-serif;\n"
301 " text-align: left;\n"
302 " white-space: nowrap;\n"
307 "table[order] > thead > tr > th {\n"
308 " cursor: pointer;\n"
310 "table[order] > thead > tr > th::after {\n"
313 " -moz-margin-end: -.8em;\n"
314 " text-align: right;\n"
316 "table[order=\"asc\"] > thead > tr > th::after {\n"
317 " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n"
319 "table[order=\"desc\"] > thead > tr > th::after {\n"
320 " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n"
322 "table[order][order-by=\"0\"] > thead > tr > th:first-child > a ,\n"
323 "table[order][order-by=\"1\"] > thead > tr > th:first-child + th > a ,\n"
324 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th > a {\n"
325 " text-decoration: underline;\n"
327 "table[order][order-by=\"0\"] > thead > tr > th:first-child::after ,\n"
328 "table[order][order-by=\"1\"] > thead > tr > th:first-child + th::after ,\n"
329 "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th::after {\n"
330 " display: inline-block;\n"
332 "table.remove-hidden > tbody > tr.hidden-object {\n"
336 " display: inline-block;\n"
340 " -moz-padding-end: 2em;\n"
343 "th:first-child + th {\n"
344 " -moz-padding-end: 1em;\n"
346 "td:first-child + td {\n"
347 " text-align: right;\n"
348 " -moz-padding-end: 1em;\n"
349 " white-space: nowrap;\n"
352 "td:first-child + td + td {\n"
353 " -moz-padding-start: 1em;\n"
354 " -moz-padding-end: .5em;\n"
355 " white-space: nowrap;\n"
359 " -moz-padding-start: .5em;\n"
360 " white-space: nowrap;\n"
362 "@-moz-document url-prefix(gopher://) {\n"
364 " white-space: pre !important;\n"
365 " font-family: monospace;\n"
369 " font-style: italic;\n"
374 " -moz-margin-start: 20px;\n"
378 " -moz-margin-end: 4px;\n"
379 " -moz-margin-start: -20px;\n"
380 " vertical-align: middle;\n"
383 " content: url(resource://gre/res/html/folder.png);\n"
386 "<link rel=\"stylesheet\" media=\"screen, projection\" type=\"text/css\""
387 " href=\"chrome://global/skin/dirListing/dirListing.css\">\n");
389 if (!isSchemeGopher
) {
390 buffer
.AppendLiteral("<script type=\"application/javascript\">\n"
391 "var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;\n"
392 "document.addEventListener(\"DOMContentLoaded\", function() {\n"
393 " gTable = document.getElementsByTagName(\"table\")[0];\n"
394 " gTBody = gTable.tBodies[0];\n"
395 " if (gTBody.rows.length < 2)\n"
397 " gUI_showHidden = document.getElementById(\"UI_showHidden\");\n"
398 " var headCells = gTable.tHead.rows[0].cells,\n"
399 " hiddenObjects = false;\n"
400 " function rowAction(i) {\n"
401 " return function(event) {\n"
402 " event.preventDefault();\n"
406 " for (var i = headCells.length - 1; i >= 0; i--) {\n"
407 " var anchor = document.createElement(\"a\");\n"
408 " anchor.href = \"\";\n"
409 " anchor.appendChild(headCells[i].firstChild);\n"
410 " headCells[i].appendChild(anchor);\n"
411 " headCells[i].addEventListener(\"click\", rowAction(i), true);\n"
413 " if (gUI_showHidden) {\n"
414 " gRows = Array.slice(gTBody.rows);\n"
415 " hiddenObjects = gRows.some(function (row) row.className == \"hidden-object\");\n"
417 " gTable.setAttribute(\"order\", \"\");\n"
418 " if (hiddenObjects) {\n"
419 " gUI_showHidden.style.display = \"block\";\n"
423 "function compareRows(rowA, rowB) {\n"
424 " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n"
425 " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n"
428 " if (a == intA && b == intB) {\n"
432 " a = a.toLowerCase();\n"
433 " b = b.toLowerCase();\n"
441 "function orderBy(column) {\n"
443 " gRows = Array.slice(gTBody.rows);\n"
445 " if (gOrderBy == column) {\n"
446 " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : \"asc\";\n"
448 " order = \"asc\";\n"
449 " gOrderBy = column;\n"
450 " gTable.setAttribute(\"order-by\", column);\n"
451 " gRows.sort(compareRows);\n"
453 " gTable.removeChild(gTBody);\n"
454 " gTable.setAttribute(\"order\", order);\n"
455 " if (order == \"asc\")\n"
456 " for (var i = 0; i < gRows.length; i++)\n"
457 " gTBody.appendChild(gRows[i]);\n"
459 " for (var i = gRows.length - 1; i >= 0; i--)\n"
460 " gTBody.appendChild(gRows[i]);\n"
461 " gTable.appendChild(gTBody);\n"
463 "function updateHidden() {\n"
464 " gTable.className = gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n"
466 " \"remove-hidden\";\n"
470 buffer
.AppendLiteral("<link rel=\"icon\" type=\"image/png\" href=\"");
471 nsCOMPtr
<nsIURI
> innerUri
= NS_GetInnermostURI(uri
);
473 return NS_ERROR_UNEXPECTED
;
474 nsCOMPtr
<nsIFileURL
> fileURL(do_QueryInterface(innerUri
));
475 //XXX bug 388553: can't use skinnable icons here due to security restrictions
477 //buffer.AppendLiteral("chrome://global/skin/dirListing/local.png");
478 buffer
.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
479 "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
480 "ZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR"
481 "3XQ3ONASdBJCSBxHos5%2B3Bg3rvkCv8PElS78gPkO%2FATj"
482 "QoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12O"
483 "YIDhcPiu2Wx%2B%2FHF5CW1Z6Jyegt%2FTNEWSJIjjGFEUIQ"
484 "xDrFYrWFSzXC4%2FdLvd95pRKpXKy%2BpRFZ7nwaWo1%2BsG"
485 "nQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5"
486 "obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM%2FS"
487 "KK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9c"
488 "lkYoDo3SZJzzSdp0VSKYmfV1co%2Bz580kw5KDIM8RbRfEnU"
489 "f1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd%2BzGQQB5llunF"
490 "%2FM52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxi"
491 "xfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18l"
492 "yWh5FFbrOSxbl3V4G%2BVB7T4ajYYxTyuLtO%2BCvWGgJE1M"
493 "c7JNsJEhvgw%2FQV4fo%2F24nbEsX2u1d5sVyn8sJO0ZAQiI"
494 "YnFh%2BxrfLz%2Fj29cBS%2FO14zg3i8XigW3ZkErDtmKoeM"
495 "%2BAJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ%2B"
496 "4pQ1%2FlPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj%2F8m%2B8"
497 "y%2FkrwABHbz2H9V68DQAAAABJRU5ErkJggg%3D%3D");
499 //buffer.AppendLiteral("chrome://global/skin/dirListing/remote.png");
500 buffer
.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
501 "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
502 "ZSBJbWFnZVJlYWR5ccllPAAAAeBJREFUeNqcU81O20AQ%2Ft"
503 "Z2AgQSYQRqL1UPVG2hAUQkxLEStz4DrXpLpD5Drz31Cajax%"
504 "2Bghhx6qHIJURBTxIwQRwopCBbZjHMcOTrzermPipsSt1Iw0"
505 "3p3ZmW%2B%2B2R0TxhgOD34wjCHZlQ0iDYz9yvEfhxMTCYhE"
506 "QDIZhkxKd2sqzX2TOD2vBQCQhpPefng1ZP2dVPlLLdpL8SEM"
507 "cxng%2Fbs0RIHhtgs4twxOh%2BHjZxvzDx%2F3GQQiDFISiR"
508 "BLFMPKTRMollzcWECrDVhtxtdRVsL9youPxGj%2FbdfFlUZh"
509 "tDyYbYqWRUdai1oQRZ5oHeHl2gNM%2B01Uqio8RlH%2Bnsaz"
510 "JzNwXcq1B%2BiXPHprlEEymeBfXs1w8XxxihfyuXqoHqpoGj"
511 "ZM04bddgG%2F9%2B8WGj87qDdsrK9m%2BoA%2BpbhQTDh2l1"
512 "%2Bi2weNbSHMZyjvNXmVbqh9Fj5Oz27uEoP%2BSTxANruJs9"
513 "L%2FT6P0ewqPx5nmiAG5f6AoCtN1PbJzuRyJAyDBzzSQYvEr"
514 "f06yYxhGXlEa8H2KVGoasjwLx3Ewk858opQWXm%2B%2Fib9E"
515 "QrBzclLLLy89xYvlpchvtixcX6uo1y%2FzsiwHrkIsgKbp%2"
516 "BYWFOWicuqppoNTnStHzPFCPQhBEBOyGAX4JMADFetubi4BS"
517 "YAAAAABJRU5ErkJggg%3D%3D");
519 buffer
.AppendLiteral("\">\n<title>");
521 // Anything but a gopher url needs to end in a /,
522 // otherwise we end up linking to file:///foo/dirfile
524 if (!mTextToSubURI
) {
525 mTextToSubURI
= do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
526 if (NS_FAILED(rv
)) return rv
;
529 nsXPIDLString unEscapeSpec
;
530 rv
= mTextToSubURI
->UnEscapeAndConvert(encoding
, titleUri
.get(),
531 getter_Copies(unEscapeSpec
));
532 // unescape may fail because
533 // 1. file URL may be encoded in platform charset for backward compatibility
534 // 2. query part may not be encoded in UTF-8 (see bug 261929)
535 // so try the platform's default if this is file url
536 if (NS_FAILED(rv
) && isSchemeFile
) {
537 nsCOMPtr
<nsIPlatformCharset
> platformCharset(do_GetService(NS_PLATFORMCHARSET_CONTRACTID
, &rv
));
538 NS_ENSURE_SUCCESS(rv
, rv
);
539 nsCAutoString charset
;
540 rv
= platformCharset
->GetCharset(kPlatformCharsetSel_FileName
, charset
);
541 NS_ENSURE_SUCCESS(rv
, rv
);
543 rv
= mTextToSubURI
->UnEscapeAndConvert(charset
.get(), titleUri
.get(),
544 getter_Copies(unEscapeSpec
));
546 if (NS_FAILED(rv
)) return rv
;
548 nsXPIDLString htmlEscSpec
;
549 htmlEscSpec
.Adopt(nsEscapeHTML2(unEscapeSpec
.get(),
550 unEscapeSpec
.Length()));
553 const PRUnichar
* formatTitle
[] = {
557 rv
= mBundle
->FormatStringFromName(NS_LITERAL_STRING("DirTitle").get(),
559 sizeof(formatTitle
)/sizeof(PRUnichar
*),
560 getter_Copies(title
));
561 if (NS_FAILED(rv
)) return rv
;
563 // we want to convert string bundle to NCR
564 // to ensure they're shown in any charsets
565 AppendNonAsciiToNCR(title
, buffer
);
567 buffer
.AppendLiteral("</title>\n");
569 // If there is a quote character in the baseUri, then
570 // lets not add a base URL. The reason for this is that
571 // if we stick baseUri containing a quote into a quoted
572 // string, the quote character will prematurely close
573 // the base href string. This is a fall-back check;
574 // that's why it is OK to not use a base rather than
575 // trying to play nice and escaping the quotes. See bug
578 if (baseUri
.FindChar('"') == kNotFound
)
580 // Great, the baseUri does not contain a char that
581 // will prematurely close the string. Go ahead an
583 buffer
.AppendLiteral("<base href=\"");
584 NS_ConvertUTF8toUTF16
utf16BaseURI(baseUri
);
585 nsString htmlEscapedUri
;
586 htmlEscapedUri
.Adopt(nsEscapeHTML2(utf16BaseURI
.get(), utf16BaseURI
.Length()));
587 buffer
.Append(htmlEscapedUri
);
588 buffer
.AppendLiteral("\">\n");
592 NS_ERROR("broken protocol handler didn't escape double-quote.");
595 buffer
.AppendLiteral("</head>\n<body>\n<h1>");
597 const PRUnichar
* formatHeading
[] = {
601 rv
= mBundle
->FormatStringFromName(NS_LITERAL_STRING("DirTitle").get(),
603 sizeof(formatHeading
)/sizeof(PRUnichar
*),
604 getter_Copies(title
));
605 if (NS_FAILED(rv
)) return rv
;
607 AppendNonAsciiToNCR(title
, buffer
);
608 buffer
.AppendLiteral("</h1>\n");
610 if (!parentStr
.IsEmpty()) {
611 nsXPIDLString parentText
;
612 rv
= mBundle
->GetStringFromName(NS_LITERAL_STRING("DirGoUp").get(),
613 getter_Copies(parentText
));
614 if (NS_FAILED(rv
)) return rv
;
616 buffer
.AppendLiteral("<p id=\"UI_goUp\"><a class=\"up\" href=\"");
618 NS_ConvertUTF8toUTF16
utf16ParentStr(parentStr
);
619 nsString htmlParentStr
;
620 htmlParentStr
.Adopt(nsEscapeHTML2(utf16ParentStr
.get(), utf16ParentStr
.Length()));
621 buffer
.Append(htmlParentStr
);
622 buffer
.AppendLiteral("\">");
623 AppendNonAsciiToNCR(parentText
, buffer
);
624 buffer
.AppendLiteral("</a></p>\n");
628 nsXPIDLString showHiddenText
;
629 rv
= mBundle
->GetStringFromName(NS_LITERAL_STRING("ShowHidden").get(),
630 getter_Copies(showHiddenText
));
631 if (NS_FAILED(rv
)) return rv
;
633 buffer
.AppendLiteral("<p id=\"UI_showHidden\" style=\"display:none\"><label><input type=\"checkbox\" checked onchange=\"updateHidden()\">");
634 AppendNonAsciiToNCR(showHiddenText
, buffer
);
635 buffer
.AppendLiteral("</label></p>\n");
638 buffer
.AppendLiteral("<table>\n");
640 if (!isSchemeGopher
) {
641 nsXPIDLString columnText
;
643 buffer
.AppendLiteral(" <thead>\n"
647 rv
= mBundle
->GetStringFromName(NS_LITERAL_STRING("DirColName").get(),
648 getter_Copies(columnText
));
649 if (NS_FAILED(rv
)) return rv
;
650 AppendNonAsciiToNCR(columnText
, buffer
);
651 buffer
.AppendLiteral("</th>\n"
654 rv
= mBundle
->GetStringFromName(NS_LITERAL_STRING("DirColSize").get(),
655 getter_Copies(columnText
));
656 if (NS_FAILED(rv
)) return rv
;
657 AppendNonAsciiToNCR(columnText
, buffer
);
658 buffer
.AppendLiteral("</th>\n"
659 " <th colspan=\"2\">");
661 rv
= mBundle
->GetStringFromName(NS_LITERAL_STRING("DirColMTime").get(),
662 getter_Copies(columnText
));
663 if (NS_FAILED(rv
)) return rv
;
664 AppendNonAsciiToNCR(columnText
, buffer
);
665 buffer
.AppendLiteral("</th>\n"
669 buffer
.AppendLiteral(" <tbody>\n");
671 // Push buffer to the listener now, so the initial HTML will not
672 // be parsed in OnDataAvailable().
674 rv
= mListener
->OnStartRequest(request
, aContext
);
675 if (NS_FAILED(rv
)) return rv
;
677 // The request may have been canceled, and if that happens, we want to
678 // suppress calls to OnDataAvailable.
679 request
->GetStatus(&rv
);
680 if (NS_FAILED(rv
)) return rv
;
682 rv
= FormatInputStream(request
, aContext
, buffer
);
687 nsIndexedToHTML::OnStopRequest(nsIRequest
* request
, nsISupports
*aContext
,
689 if (NS_SUCCEEDED(aStatus
)) {
691 buffer
.AssignLiteral("</tbody></table></body></html>\n");
693 aStatus
= FormatInputStream(request
, aContext
, buffer
);
696 mParser
->OnStopRequest(request
, aContext
, aStatus
);
699 return mListener
->OnStopRequest(request
, aContext
, aStatus
);
703 nsIndexedToHTML::FormatInputStream(nsIRequest
* aRequest
, nsISupports
*aContext
, const nsAString
&aBuffer
)
707 // set up unicode encoder
708 if (!mUnicodeEncoder
) {
709 nsXPIDLCString encoding
;
710 rv
= mParser
->GetEncoding(getter_Copies(encoding
));
711 if (NS_SUCCEEDED(rv
)) {
712 nsCOMPtr
<nsICharsetConverterManager
> charsetConverterManager
;
713 charsetConverterManager
= do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID
, &rv
);
714 rv
= charsetConverterManager
->GetUnicodeEncoder(encoding
.get(),
715 getter_AddRefs(mUnicodeEncoder
));
716 if (NS_SUCCEEDED(rv
))
717 rv
= mUnicodeEncoder
->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace
,
718 nsnull
, (PRUnichar
)'?');
722 // convert the data with unicode encoder
723 char *buffer
= nsnull
;
725 if (NS_SUCCEEDED(rv
)) {
726 PRInt32 unicharLength
= aBuffer
.Length();
727 rv
= mUnicodeEncoder
->GetMaxLength(PromiseFlatString(aBuffer
).get(),
728 unicharLength
, &dstLength
);
729 if (NS_SUCCEEDED(rv
)) {
730 buffer
= (char *) nsMemory::Alloc(dstLength
);
731 NS_ENSURE_TRUE(buffer
, NS_ERROR_OUT_OF_MEMORY
);
733 rv
= mUnicodeEncoder
->Convert(PromiseFlatString(aBuffer
).get(), &unicharLength
,
735 if (NS_SUCCEEDED(rv
)) {
737 rv
= mUnicodeEncoder
->Finish(buffer
+ dstLength
, &finLen
);
738 if (NS_SUCCEEDED(rv
))
744 // if conversion error then fallback to UTF-8
748 nsMemory::Free(buffer
);
753 nsCOMPtr
<nsIInputStream
> inputData
;
755 rv
= NS_NewCStringInputStream(getter_AddRefs(inputData
), Substring(buffer
, buffer
+ dstLength
));
756 nsMemory::Free(buffer
);
757 NS_ENSURE_SUCCESS(rv
, rv
);
758 rv
= mListener
->OnDataAvailable(aRequest
, aContext
,
759 inputData
, 0, dstLength
);
762 NS_ConvertUTF16toUTF8
utf8Buffer(aBuffer
);
763 rv
= NS_NewCStringInputStream(getter_AddRefs(inputData
), utf8Buffer
);
764 NS_ENSURE_SUCCESS(rv
, rv
);
765 rv
= mListener
->OnDataAvailable(aRequest
, aContext
,
766 inputData
, 0, utf8Buffer
.Length());
772 nsIndexedToHTML::OnDataAvailable(nsIRequest
*aRequest
,
774 nsIInputStream
* aInput
,
777 return mParser
->OnDataAvailable(aRequest
, aCtxt
, aInput
, aOffset
, aCount
);
781 nsIndexedToHTML::OnIndexAvailable(nsIRequest
*aRequest
,
783 nsIDirIndex
*aIndex
) {
786 return NS_ERROR_NULL_POINTER
;
789 pushBuffer
.AppendLiteral("<tr");
791 nsXPIDLString description
;
792 aIndex
->GetDescription(getter_Copies(description
));
793 if (description
.First() == PRUnichar('.'))
794 pushBuffer
.AppendLiteral(" class=\"hidden-object\"");
796 pushBuffer
.AppendLiteral(">\n <td sortable-data=\"");
798 // The sort key is the name of the item, prepended by either 0, 1 or 2
799 // in order to group items.
801 aIndex
->GetType(&type
);
803 case nsIDirIndex::TYPE_SYMLINK
:
804 pushBuffer
.AppendInt(0);
806 case nsIDirIndex::TYPE_DIRECTORY
:
807 pushBuffer
.AppendInt(1);
809 case nsIDirIndex::TYPE_FILE
:
810 case nsIDirIndex::TYPE_UNKNOWN
:
811 pushBuffer
.AppendInt(2);
814 PRUnichar
* escaped
= nsEscapeHTML2(description
.get(), description
.Length());
815 pushBuffer
.Append(escaped
);
817 pushBuffer
.AppendLiteral("\"><a class=\"");
819 case nsIDirIndex::TYPE_DIRECTORY
:
820 pushBuffer
.AppendLiteral("dir");
822 case nsIDirIndex::TYPE_SYMLINK
:
823 pushBuffer
.AppendLiteral("symlink");
825 case nsIDirIndex::TYPE_FILE
:
826 case nsIDirIndex::TYPE_UNKNOWN
:
827 pushBuffer
.AppendLiteral("file");
830 pushBuffer
.AppendLiteral("\"");
832 // Truncate long names to not stretch the table
833 //XXX this should be left to the stylesheet (bug 391471)
834 nsString escapedShort
;
835 if (description
.Length() > 71) {
836 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
837 nsCOMPtr
<nsIURI
> uri
;
838 rv
= channel
->GetURI(getter_AddRefs(uri
));
839 if (NS_FAILED(rv
)) return rv
;
841 // No need to do this for Gopher, as the table has only one column in that case
842 PRBool isSchemeGopher
= PR_FALSE
;
843 if (!(NS_SUCCEEDED(uri
->SchemeIs("gopher", &isSchemeGopher
)) && isSchemeGopher
)) {
844 //XXX this potentially truncates after a combining char (bug 391472)
845 nsXPIDLString descriptionAffix
;
846 descriptionAffix
.Assign(description
);
847 descriptionAffix
.Cut(0, descriptionAffix
.Length() - 25);
848 if (NS_IS_LOW_SURROGATE(descriptionAffix
.First()))
849 descriptionAffix
.Cut(0, 1);
850 description
.Truncate(PR_MIN(71, description
.Length() - 28));
851 if (NS_IS_HIGH_SURROGATE(description
.Last()))
852 description
.Truncate(description
.Length() - 1);
854 escapedShort
.Adopt(nsEscapeHTML2(description
.get(), description
.Length()));
856 escapedShort
.Append(mEscapedEllipsis
);
857 // add ZERO WIDTH SPACE (U+200B) for wrapping
858 escapedShort
.AppendLiteral("​");
860 tmp
.Adopt(nsEscapeHTML2(descriptionAffix
.get(), descriptionAffix
.Length()));
861 escapedShort
.Append(tmp
);
863 pushBuffer
.AppendLiteral(" title=\"");
864 pushBuffer
.Append(escaped
);
865 pushBuffer
.AppendLiteral("\"");
868 if (escapedShort
.IsEmpty())
869 escapedShort
.Assign(escaped
);
870 nsMemory::Free(escaped
);
872 pushBuffer
.AppendLiteral(" href=\"");
874 aIndex
->GetLocation(getter_Copies(loc
));
876 if (!mTextToSubURI
) {
877 mTextToSubURI
= do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
878 if (NS_FAILED(rv
)) return rv
;
881 nsXPIDLCString encoding
;
882 rv
= mParser
->GetEncoding(getter_Copies(encoding
));
883 if (NS_FAILED(rv
)) return rv
;
885 nsXPIDLString unEscapeSpec
;
886 rv
= mTextToSubURI
->UnEscapeAndConvert(encoding
, loc
,
887 getter_Copies(unEscapeSpec
));
888 if (NS_FAILED(rv
)) return rv
;
890 // need to escape links
891 nsCAutoString escapeBuf
;
893 NS_ConvertUTF16toUTF8
utf8UnEscapeSpec(unEscapeSpec
);
895 // now minimally re-escape the location...
897 // for some protocols, like gopher, we expect the location to be absolute.
898 // if so, and if the location indeed appears to be a valid URI, then go
899 // ahead and treat it like one.
901 NS_SUCCEEDED(net_ExtractURLScheme(utf8UnEscapeSpec
, nsnull
, nsnull
, nsnull
))) {
902 // escape as absolute
903 escFlags
= esc_Forced
| esc_OnlyASCII
| esc_AlwaysCopy
| esc_Minimal
;
906 // escape as relative
907 // esc_Directory is needed for protocols which allow the same name for
908 // both a directory and a file and distinguish between the two by a
909 // trailing '/' -- without it, the trailing '/' will be escaped, and
910 // links from within that directory will be incorrect
911 escFlags
= esc_Forced
| esc_OnlyASCII
| esc_AlwaysCopy
| esc_FileBaseName
| esc_Colon
| esc_Directory
;
913 NS_EscapeURL(utf8UnEscapeSpec
.get(), utf8UnEscapeSpec
.Length(), escFlags
, escapeBuf
);
914 NS_ConvertUTF8toUTF16
utf16URI(escapeBuf
);
915 nsString htmlEscapedURL
;
916 htmlEscapedURL
.Adopt(nsEscapeHTML2(utf16URI
.get(), utf16URI
.Length()));
917 pushBuffer
.Append(htmlEscapedURL
);
919 pushBuffer
.AppendLiteral("\">");
921 if (type
== nsIDirIndex::TYPE_FILE
|| type
== nsIDirIndex::TYPE_UNKNOWN
) {
922 pushBuffer
.AppendLiteral("<img src=\"moz-icon://");
923 PRInt32 lastDot
= escapeBuf
.RFindChar('.');
924 if (lastDot
!= kNotFound
) {
925 escapeBuf
.Cut(0, lastDot
);
926 AppendUTF8toUTF16(escapeBuf
, pushBuffer
);
928 pushBuffer
.AppendLiteral("unknown");
930 pushBuffer
.AppendLiteral("?size=16\" alt=\"");
932 nsXPIDLString altText
;
933 rv
= mBundle
->GetStringFromName(NS_LITERAL_STRING("DirFileLabel").get(),
934 getter_Copies(altText
));
935 if (NS_FAILED(rv
)) return rv
;
936 AppendNonAsciiToNCR(altText
, pushBuffer
);
937 pushBuffer
.AppendLiteral("\">");
940 pushBuffer
.Append(escapedShort
);
941 pushBuffer
.AppendLiteral("</a></td>\n <td");
943 if (type
== nsIDirIndex::TYPE_DIRECTORY
|| type
== nsIDirIndex::TYPE_SYMLINK
) {
944 pushBuffer
.AppendLiteral(">");
947 aIndex
->GetSize(&size
);
949 if (PRUint64(size
) != LL_MAXUINT
) {
950 pushBuffer
.AppendLiteral(" sortable-data=\"");
951 pushBuffer
.AppendInt(size
);
952 pushBuffer
.AppendLiteral("\">");
953 nsAutoString sizeString
;
954 FormatSizeString(size
, sizeString
);
955 pushBuffer
.Append(sizeString
);
957 pushBuffer
.AppendLiteral(">");
960 pushBuffer
.AppendLiteral("</td>\n <td");
963 aIndex
->GetLastModified(&t
);
966 pushBuffer
.AppendLiteral("></td>\n <td>");
968 pushBuffer
.AppendLiteral(" sortable-data=\"");
969 pushBuffer
.AppendInt(t
);
970 pushBuffer
.AppendLiteral("\">");
971 nsAutoString formatted
;
972 mDateTime
->FormatPRTime(nsnull
,
977 AppendNonAsciiToNCR(formatted
, pushBuffer
);
978 pushBuffer
.AppendLiteral("</td>\n <td>");
979 mDateTime
->FormatPRTime(nsnull
,
984 // use NCR to show date in any doc charset
985 AppendNonAsciiToNCR(formatted
, pushBuffer
);
988 pushBuffer
.AppendLiteral("</td>\n</tr>");
990 return FormatInputStream(aRequest
, aCtxt
, pushBuffer
);
994 nsIndexedToHTML::OnInformationAvailable(nsIRequest
*aRequest
,
996 const nsAString
& aInfo
) {
997 nsAutoString pushBuffer
;
998 PRUnichar
* escaped
= nsEscapeHTML2(PromiseFlatString(aInfo
).get());
1000 return NS_ERROR_OUT_OF_MEMORY
;
1001 pushBuffer
.AppendLiteral("<tr>\n <td>");
1002 pushBuffer
.Append(escaped
);
1003 nsMemory::Free(escaped
);
1004 pushBuffer
.AppendLiteral("</td>\n <td></td>\n <td></td>\n <td></td>\n</tr>\n");
1006 return FormatInputStream(aRequest
, aCtxt
, pushBuffer
);
1009 void nsIndexedToHTML::FormatSizeString(PRInt64 inSize
, nsString
& outSizeString
)
1011 outSizeString
.Truncate();
1012 if (inSize
> PRInt64(0)) {
1013 // round up to the nearest Kilobyte
1014 PRInt64 upperSize
= (inSize
+ PRInt64(1023)) / PRInt64(1024);
1015 outSizeString
.AppendInt(upperSize
);
1016 outSizeString
.AppendLiteral(" KB");
1020 nsIndexedToHTML::nsIndexedToHTML() {
1023 nsIndexedToHTML::~nsIndexedToHTML() {