Bug 460926 A11y hierachy is broken on Ubuntu 8.10 (GNOME 2.24), r=Evan.Yan sr=roc
[wine-gecko.git] / netwerk / streamconv / converters / nsIndexedToHTML.cpp
blobb4e9b82b9066928da6cde0b2277eaa9bc7f2eff5
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
13 * License.
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.
22 * Contributor(s):
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"
43 #include "netCore.h"
44 #include "nsStringStream.h"
45 #include "nsIFileURL.h"
46 #include "nsEscape.h"
47 #include "nsIDirIndex.h"
48 #include "prtime.h"
49 #include "nsDateTimeFormatCID.h"
50 #include "nsURLHelper.h"
51 #include "nsCRT.h"
52 #include "nsIPlatformCharset.h"
53 #include "nsIPrefService.h"
54 #include "nsIPrefBranch.h"
55 #include "nsIPrefLocalizedString.h"
57 NS_IMPL_ISUPPORTS4(nsIndexedToHTML,
58 nsIDirIndexListener,
59 nsIStreamConverter,
60 nsIRequestObserver,
61 nsIStreamListener)
63 static void AppendNonAsciiToNCR(const nsAString& in, nsAFlatString& out)
65 nsAString::const_iterator start, end;
67 in.BeginReading(start);
68 in.EndReading(end);
70 while (start != end) {
71 if (*start < 128) {
72 out.Append(*start++);
73 } else {
74 out.AppendLiteral("&#x");
75 nsAutoString hex;
76 hex.AppendInt(*start++, 16);
77 out.Append(hex);
78 out.Append((PRUnichar)';');
83 NS_METHOD
84 nsIndexedToHTML::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) {
85 nsresult rv;
86 if (aOuter)
87 return NS_ERROR_NO_AGGREGATION;
89 nsIndexedToHTML* _s = new nsIndexedToHTML();
90 if (_s == nsnull)
91 return NS_ERROR_OUT_OF_MEMORY;
93 rv = _s->QueryInterface(aIID, aResult);
94 return rv;
97 nsresult
98 nsIndexedToHTML::Init(nsIStreamListener* aListener) {
100 nsXPIDLString ellipsis;
101 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
102 if (prefs) {
103 nsCOMPtr<nsIPrefLocalizedString> prefVal;
104 prefs->GetComplexValue("intl.ellipsis",
105 NS_GET_IID(nsIPrefLocalizedString),
106 getter_AddRefs(prefVal));
107 if (prefVal)
108 prefVal->ToString(getter_Copies(ellipsis));
110 if (ellipsis.IsEmpty())
111 mEscapedEllipsis.AppendLiteral("&#8230;");
112 else
113 mEscapedEllipsis.Adopt(nsEscapeHTML2(ellipsis.get(), ellipsis.Length()));
115 nsresult rv = NS_OK;
117 mListener = aListener;
119 mDateTime = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
120 if (NS_FAILED(rv))
121 return 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;
130 return rv;
133 NS_IMETHODIMP
134 nsIndexedToHTML::Convert(nsIInputStream* aFromStream,
135 const char* aFromType,
136 const char* aToType,
137 nsISupports* aCtxt,
138 nsIInputStream** res) {
139 return NS_ERROR_NOT_IMPLEMENTED;
142 NS_IMETHODIMP
143 nsIndexedToHTML::AsyncConvertData(const char *aFromType,
144 const char *aToType,
145 nsIStreamListener *aListener,
146 nsISupports *aCtxt) {
147 return Init(aListener);
150 NS_IMETHODIMP
151 nsIndexedToHTML::OnStartRequest(nsIRequest* request, nsISupports *aContext) {
152 nsresult rv;
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;
173 titleUri = baseUri;
175 nsCString parentStr;
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
183 // - bbaetz
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
192 // that - see above
194 nsCAutoString pw;
195 rv = uri->GetPassword(pw);
196 if (NS_FAILED(rv)) return rv;
197 if (!pw.IsEmpty()) {
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;
207 nsCAutoString path;
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);
224 nsCAutoString url;
225 rv = net_GetURLSpecFromFile(file, url);
226 if (NS_FAILED(rv)) return rv;
227 baseUri.Assign(url);
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) {
245 nsCAutoString path;
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
251 // will be "!/"
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;
260 else {
261 // default behavior for other protocols is to assume the channel's
262 // URL references a directory ending in '/' -- fixup if necessary.
263 nsCAutoString path;
264 rv = uri->GetPath(path);
265 if (NS_FAILED(rv)) return rv;
266 if (baseUri.Last() != '/') {
267 baseUri.Append('/');
268 path.Append('/');
269 uri->SetPath(path);
271 if (!path.EqualsLiteral("/")) {
272 rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
273 if (NS_FAILED(rv)) return rv;
277 nsString buffer;
278 buffer.AppendLiteral("<!DOCTYPE html>\n"
279 "<html>\n<head>\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
285 // for that.
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"
294 ":root {\n"
295 " font-family: sans-serif;\n"
296 "}\n"
297 "img {\n"
298 " border: 0;\n"
299 "}\n"
300 "th {\n"
301 " text-align: left;\n"
302 " white-space: nowrap;\n"
303 "}\n"
304 "th > a {\n"
305 " color: inherit;\n"
306 "}\n"
307 "table[order] > thead > tr > th {\n"
308 " cursor: pointer;\n"
309 "}\n"
310 "table[order] > thead > tr > th::after {\n"
311 " display: none;\n"
312 " width: .8em;\n"
313 " -moz-margin-end: -.8em;\n"
314 " text-align: right;\n"
315 "}\n"
316 "table[order=\"asc\"] > thead > tr > th::after {\n"
317 " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n"
318 "}\n"
319 "table[order=\"desc\"] > thead > tr > th::after {\n"
320 " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n"
321 "}\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"
326 "}\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"
331 "}\n"
332 "table.remove-hidden > tbody > tr.hidden-object {\n"
333 " display: none;\n"
334 "}\n"
335 "td > a {\n"
336 " display: inline-block;\n"
337 "}\n"
338 "/* name */\n"
339 "th:first-child {\n"
340 " -moz-padding-end: 2em;\n"
341 "}\n"
342 "/* size */\n"
343 "th:first-child + th {\n"
344 " -moz-padding-end: 1em;\n"
345 "}\n"
346 "td:first-child + td {\n"
347 " text-align: right;\n"
348 " -moz-padding-end: 1em;\n"
349 " white-space: nowrap;\n"
350 "}\n"
351 "/* date */\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"
356 "}\n"
357 "/* time */\n"
358 "td:last-child {\n"
359 " -moz-padding-start: .5em;\n"
360 " white-space: nowrap;\n"
361 "}\n"
362 "@-moz-document url-prefix(gopher://) {\n"
363 " td {\n"
364 " white-space: pre !important;\n"
365 " font-family: monospace;\n"
366 " }\n"
367 "}\n"
368 ".symlink {\n"
369 " font-style: italic;\n"
370 "}\n"
371 ".dir ,\n"
372 ".symlink ,\n"
373 ".file {\n"
374 " -moz-margin-start: 20px;\n"
375 "}\n"
376 ".dir::before ,\n"
377 ".file > img {\n"
378 " -moz-margin-end: 4px;\n"
379 " -moz-margin-start: -20px;\n"
380 " vertical-align: middle;\n"
381 "}\n"
382 ".dir::before {\n"
383 " content: url(resource://gre/res/html/folder.png);\n"
384 "}\n"
385 "</style>\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"
396 " return;\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"
403 " orderBy(i);\n"
404 " }\n"
405 " }\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"
412 " }\n"
413 " if (gUI_showHidden) {\n"
414 " gRows = Array.slice(gTBody.rows);\n"
415 " hiddenObjects = gRows.some(function (row) row.className == \"hidden-object\");\n"
416 " }\n"
417 " gTable.setAttribute(\"order\", \"\");\n"
418 " if (hiddenObjects) {\n"
419 " gUI_showHidden.style.display = \"block\";\n"
420 " updateHidden();\n"
421 " }\n"
422 "}, \"false\");\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"
426 " var intA = +a;\n"
427 " var intB = +b;\n"
428 " if (a == intA && b == intB) {\n"
429 " a = intA;\n"
430 " b = intB;\n"
431 " } else {\n"
432 " a = a.toLowerCase();\n"
433 " b = b.toLowerCase();\n"
434 " }\n"
435 " if (a < b)\n"
436 " return -1;\n"
437 " if (a > b)\n"
438 " return 1;\n"
439 " return 0;\n"
440 "}\n"
441 "function orderBy(column) {\n"
442 " if (!gRows)\n"
443 " gRows = Array.slice(gTBody.rows);\n"
444 " var order;\n"
445 " if (gOrderBy == column) {\n"
446 " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : \"asc\";\n"
447 " } else {\n"
448 " order = \"asc\";\n"
449 " gOrderBy = column;\n"
450 " gTable.setAttribute(\"order-by\", column);\n"
451 " gRows.sort(compareRows);\n"
452 " }\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"
458 " else\n"
459 " for (var i = gRows.length - 1; i >= 0; i--)\n"
460 " gTBody.appendChild(gRows[i]);\n"
461 " gTable.appendChild(gTBody);\n"
462 "}\n"
463 "function updateHidden() {\n"
464 " gTable.className = gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n"
465 " \"\" :\n"
466 " \"remove-hidden\";\n"
467 "}\n"
468 "</script>\n");
470 buffer.AppendLiteral("<link rel=\"icon\" type=\"image/png\" href=\"");
471 nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
472 if (!innerUri)
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
476 if (fileURL) {
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");
498 } else {
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()));
552 nsXPIDLString title;
553 const PRUnichar* formatTitle[] = {
554 htmlEscSpec.get()
557 rv = mBundle->FormatStringFromName(NS_LITERAL_STRING("DirTitle").get(),
558 formatTitle,
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
576 // 358128.
578 if (baseUri.FindChar('"') == kNotFound)
580 // Great, the baseUri does not contain a char that
581 // will prematurely close the string. Go ahead an
582 // add a base href.
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");
590 else
592 NS_ERROR("broken protocol handler didn't escape double-quote.");
595 buffer.AppendLiteral("</head>\n<body>\n<h1>");
597 const PRUnichar* formatHeading[] = {
598 htmlEscSpec.get()
601 rv = mBundle->FormatStringFromName(NS_LITERAL_STRING("DirTitle").get(),
602 formatHeading,
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");
627 if (isSchemeFile) {
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"
644 " <tr>\n"
645 " <th>");
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"
652 " <th>");
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"
666 " </tr>\n"
667 " </thead>\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);
683 return rv;
686 NS_IMETHODIMP
687 nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsISupports *aContext,
688 nsresult aStatus) {
689 if (NS_SUCCEEDED(aStatus)) {
690 nsString buffer;
691 buffer.AssignLiteral("</tbody></table></body></html>\n");
693 aStatus = FormatInputStream(request, aContext, buffer);
696 mParser->OnStopRequest(request, aContext, aStatus);
697 mParser = 0;
699 return mListener->OnStopRequest(request, aContext, aStatus);
702 nsresult
703 nsIndexedToHTML::FormatInputStream(nsIRequest* aRequest, nsISupports *aContext, const nsAString &aBuffer)
705 nsresult rv = NS_OK;
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;
724 PRInt32 dstLength;
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,
734 buffer, &dstLength);
735 if (NS_SUCCEEDED(rv)) {
736 PRInt32 finLen = 0;
737 rv = mUnicodeEncoder->Finish(buffer + dstLength, &finLen);
738 if (NS_SUCCEEDED(rv))
739 dstLength += finLen;
744 // if conversion error then fallback to UTF-8
745 if (NS_FAILED(rv)) {
746 rv = NS_OK;
747 if (buffer) {
748 nsMemory::Free(buffer);
749 buffer = nsnull;
753 nsCOMPtr<nsIInputStream> inputData;
754 if (buffer) {
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);
761 else {
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());
768 return (rv);
771 NS_IMETHODIMP
772 nsIndexedToHTML::OnDataAvailable(nsIRequest *aRequest,
773 nsISupports *aCtxt,
774 nsIInputStream* aInput,
775 PRUint32 aOffset,
776 PRUint32 aCount) {
777 return mParser->OnDataAvailable(aRequest, aCtxt, aInput, aOffset, aCount);
780 NS_IMETHODIMP
781 nsIndexedToHTML::OnIndexAvailable(nsIRequest *aRequest,
782 nsISupports *aCtxt,
783 nsIDirIndex *aIndex) {
784 nsresult rv;
785 if (!aIndex)
786 return NS_ERROR_NULL_POINTER;
788 nsString pushBuffer;
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.
800 PRUint32 type;
801 aIndex->GetType(&type);
802 switch (type) {
803 case nsIDirIndex::TYPE_SYMLINK:
804 pushBuffer.AppendInt(0);
805 break;
806 case nsIDirIndex::TYPE_DIRECTORY:
807 pushBuffer.AppendInt(1);
808 break;
809 case nsIDirIndex::TYPE_FILE:
810 case nsIDirIndex::TYPE_UNKNOWN:
811 pushBuffer.AppendInt(2);
812 break;
814 PRUnichar* escaped = nsEscapeHTML2(description.get(), description.Length());
815 pushBuffer.Append(escaped);
817 pushBuffer.AppendLiteral("\"><a class=\"");
818 switch (type) {
819 case nsIDirIndex::TYPE_DIRECTORY:
820 pushBuffer.AppendLiteral("dir");
821 break;
822 case nsIDirIndex::TYPE_SYMLINK:
823 pushBuffer.AppendLiteral("symlink");
824 break;
825 case nsIDirIndex::TYPE_FILE:
826 case nsIDirIndex::TYPE_UNKNOWN:
827 pushBuffer.AppendLiteral("file");
828 break;
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("&#8203;");
859 nsString tmp;
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=\"");
873 nsXPIDLCString loc;
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...
896 PRUint32 escFlags;
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.
900 if (mExpectAbsLoc &&
901 NS_SUCCEEDED(net_ExtractURLScheme(utf8UnEscapeSpec, nsnull, nsnull, nsnull))) {
902 // escape as absolute
903 escFlags = esc_Forced | esc_OnlyASCII | esc_AlwaysCopy | esc_Minimal;
905 else {
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);
927 } else {
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(">");
945 } else {
946 PRInt64 size;
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);
956 } else {
957 pushBuffer.AppendLiteral(">");
960 pushBuffer.AppendLiteral("</td>\n <td");
962 PRTime t;
963 aIndex->GetLastModified(&t);
965 if (t == -1) {
966 pushBuffer.AppendLiteral("></td>\n <td>");
967 } else {
968 pushBuffer.AppendLiteral(" sortable-data=\"");
969 pushBuffer.AppendInt(t);
970 pushBuffer.AppendLiteral("\">");
971 nsAutoString formatted;
972 mDateTime->FormatPRTime(nsnull,
973 kDateFormatShort,
974 kTimeFormatNone,
976 formatted);
977 AppendNonAsciiToNCR(formatted, pushBuffer);
978 pushBuffer.AppendLiteral("</td>\n <td>");
979 mDateTime->FormatPRTime(nsnull,
980 kDateFormatNone,
981 kTimeFormatSeconds,
983 formatted);
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);
993 NS_IMETHODIMP
994 nsIndexedToHTML::OnInformationAvailable(nsIRequest *aRequest,
995 nsISupports *aCtxt,
996 const nsAString& aInfo) {
997 nsAutoString pushBuffer;
998 PRUnichar* escaped = nsEscapeHTML2(PromiseFlatString(aInfo).get());
999 if (!escaped)
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() {