Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "nsIndexedToHTML.h"
7 :
8 : #include "DateTimeFormat.h"
9 : #include "mozilla/Encoding.h"
10 : #include "mozilla/intl/LocaleService.h"
11 : #include "nsNetUtil.h"
12 : #include "netCore.h"
13 : #include "nsStringStream.h"
14 : #include "nsIFile.h"
15 : #include "nsIFileURL.h"
16 : #include "nsEscape.h"
17 : #include "nsIDirIndex.h"
18 : #include "nsURLHelper.h"
19 : #include "nsIPlatformCharset.h"
20 : #include "nsIPrefService.h"
21 : #include "nsIPrefBranch.h"
22 : #include "nsIPrefLocalizedString.h"
23 : #include "nsIStringBundle.h"
24 : #include "nsITextToSubURI.h"
25 : #include "nsXPIDLString.h"
26 : #include <algorithm>
27 : #include "nsIChannel.h"
28 :
29 : using mozilla::intl::LocaleService;
30 :
31 0 : NS_IMPL_ISUPPORTS(nsIndexedToHTML,
32 : nsIDirIndexListener,
33 : nsIStreamConverter,
34 : nsIRequestObserver,
35 : nsIStreamListener)
36 :
37 0 : static void AppendNonAsciiToNCR(const nsAString& in, nsCString& out)
38 : {
39 0 : nsAString::const_iterator start, end;
40 :
41 0 : in.BeginReading(start);
42 0 : in.EndReading(end);
43 :
44 0 : while (start != end) {
45 0 : if (*start < 128) {
46 0 : out.Append(*start++);
47 : } else {
48 0 : out.AppendLiteral("&#x");
49 0 : out.AppendInt(*start++, 16);
50 0 : out.Append(';');
51 : }
52 : }
53 0 : }
54 :
55 : nsresult
56 0 : nsIndexedToHTML::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) {
57 : nsresult rv;
58 0 : if (aOuter)
59 0 : return NS_ERROR_NO_AGGREGATION;
60 :
61 0 : nsIndexedToHTML* _s = new nsIndexedToHTML();
62 0 : if (_s == nullptr)
63 0 : return NS_ERROR_OUT_OF_MEMORY;
64 :
65 0 : rv = _s->QueryInterface(aIID, aResult);
66 0 : return rv;
67 : }
68 :
69 : nsresult
70 0 : nsIndexedToHTML::Init(nsIStreamListener* aListener) {
71 0 : nsresult rv = NS_OK;
72 :
73 0 : mListener = aListener;
74 :
75 : nsCOMPtr<nsIStringBundleService> sbs =
76 0 : do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
77 0 : if (NS_FAILED(rv)) return rv;
78 0 : rv = sbs->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(mBundle));
79 :
80 0 : mExpectAbsLoc = false;
81 :
82 0 : return rv;
83 : }
84 :
85 : NS_IMETHODIMP
86 0 : nsIndexedToHTML::Convert(nsIInputStream* aFromStream,
87 : const char* aFromType,
88 : const char* aToType,
89 : nsISupports* aCtxt,
90 : nsIInputStream** res) {
91 0 : return NS_ERROR_NOT_IMPLEMENTED;
92 : }
93 :
94 : NS_IMETHODIMP
95 0 : nsIndexedToHTML::AsyncConvertData(const char *aFromType,
96 : const char *aToType,
97 : nsIStreamListener *aListener,
98 : nsISupports *aCtxt) {
99 0 : return Init(aListener);
100 : }
101 :
102 : NS_IMETHODIMP
103 0 : nsIndexedToHTML::OnStartRequest(nsIRequest* request, nsISupports *aContext) {
104 0 : nsCString buffer;
105 0 : nsresult rv = DoOnStartRequest(request, aContext, buffer);
106 0 : if (NS_FAILED(rv)) {
107 0 : request->Cancel(rv);
108 : }
109 :
110 0 : rv = mListener->OnStartRequest(request, aContext);
111 0 : if (NS_FAILED(rv)) return rv;
112 :
113 : // The request may have been canceled, and if that happens, we want to
114 : // suppress calls to OnDataAvailable.
115 0 : request->GetStatus(&rv);
116 0 : if (NS_FAILED(rv)) return rv;
117 :
118 : // Push our buffer to the listener.
119 :
120 0 : rv = SendToListener(request, aContext, buffer);
121 0 : return rv;
122 : }
123 :
124 : nsresult
125 0 : nsIndexedToHTML::DoOnStartRequest(nsIRequest* request, nsISupports *aContext,
126 : nsCString& aBuffer) {
127 : nsresult rv;
128 :
129 0 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
130 0 : nsCOMPtr<nsIURI> uri;
131 0 : rv = channel->GetOriginalURI(getter_AddRefs(uri));
132 0 : if (NS_FAILED(rv)) return rv;
133 :
134 0 : bool isResource = false;
135 0 : rv = uri->SchemeIs("resource", &isResource);
136 0 : if (NS_FAILED(rv)) return rv;
137 :
138 : // We use the original URI for the title and parent link when it's a
139 : // resource:// url, instead of the jar:file:// url it resolves to.
140 0 : if (!isResource) {
141 0 : rv = channel->GetURI(getter_AddRefs(uri));
142 0 : if (NS_FAILED(rv)) return rv;
143 : }
144 :
145 0 : channel->SetContentType(NS_LITERAL_CSTRING("text/html"));
146 :
147 0 : mParser = do_CreateInstance("@mozilla.org/dirIndexParser;1",&rv);
148 0 : if (NS_FAILED(rv)) return rv;
149 :
150 0 : rv = mParser->SetListener(this);
151 0 : if (NS_FAILED(rv)) return rv;
152 :
153 0 : rv = mParser->OnStartRequest(request, aContext);
154 0 : if (NS_FAILED(rv)) return rv;
155 :
156 0 : nsAutoCString baseUri, titleUri;
157 0 : rv = uri->GetAsciiSpec(baseUri);
158 0 : if (NS_FAILED(rv)) return rv;
159 0 : titleUri = baseUri;
160 :
161 0 : nsCString parentStr;
162 :
163 0 : nsCString buffer;
164 0 : buffer.AppendLiteral("<!DOCTYPE html>\n<html>\n<head>\n");
165 :
166 : // XXX - should be using the 300: line from the parser.
167 : // We can't guarantee that that comes before any entry, so we'd have to
168 : // buffer, and do other painful stuff.
169 : // I'll deal with this when I make the changes to handle welcome messages
170 : // The .. stuff should also come from the lower level protocols, but that
171 : // would muck up the XUL display
172 : // - bbaetz
173 :
174 0 : bool isScheme = false;
175 0 : bool isSchemeFile = false;
176 0 : if (NS_SUCCEEDED(uri->SchemeIs("ftp", &isScheme)) && isScheme) {
177 :
178 : // strip out the password here, so it doesn't show in the page title
179 : // This is done by the 300: line generation in ftp, but we don't use
180 : // that - see above
181 :
182 0 : nsAutoCString pw;
183 0 : rv = uri->GetPassword(pw);
184 0 : if (NS_FAILED(rv)) return rv;
185 0 : if (!pw.IsEmpty()) {
186 0 : nsCOMPtr<nsIURI> newUri;
187 0 : rv = uri->Clone(getter_AddRefs(newUri));
188 0 : if (NS_FAILED(rv)) return rv;
189 0 : rv = newUri->SetPassword(EmptyCString());
190 0 : if (NS_FAILED(rv)) return rv;
191 0 : rv = newUri->GetAsciiSpec(titleUri);
192 0 : if (NS_FAILED(rv)) return rv;
193 : }
194 :
195 0 : nsAutoCString path;
196 0 : rv = uri->GetPath(path);
197 0 : if (NS_FAILED(rv)) return rv;
198 :
199 0 : if (!path.EqualsLiteral("//") && !path.LowerCaseEqualsLiteral("/%2f")) {
200 0 : rv = uri->Resolve(NS_LITERAL_CSTRING(".."),parentStr);
201 0 : if (NS_FAILED(rv)) return rv;
202 : }
203 0 : } else if (NS_SUCCEEDED(uri->SchemeIs("file", &isSchemeFile)) && isSchemeFile) {
204 0 : nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
205 0 : nsCOMPtr<nsIFile> file;
206 0 : rv = fileUrl->GetFile(getter_AddRefs(file));
207 0 : if (NS_FAILED(rv)) return rv;
208 0 : file->SetFollowLinks(true);
209 :
210 0 : nsAutoCString url;
211 0 : rv = net_GetURLSpecFromFile(file, url);
212 0 : if (NS_FAILED(rv)) return rv;
213 0 : baseUri.Assign(url);
214 :
215 0 : nsCOMPtr<nsIFile> parent;
216 0 : rv = file->GetParent(getter_AddRefs(parent));
217 :
218 0 : if (parent && NS_SUCCEEDED(rv)) {
219 0 : net_GetURLSpecFromDir(parent, url);
220 0 : if (NS_FAILED(rv)) return rv;
221 0 : parentStr.Assign(url);
222 : }
223 :
224 : // Directory index will be always encoded in UTF-8 if this is file url
225 0 : buffer.AppendLiteral("<meta charset=\"UTF-8\">\n");
226 :
227 0 : } else if (NS_SUCCEEDED(uri->SchemeIs("jar", &isScheme)) && isScheme) {
228 0 : nsAutoCString path;
229 0 : rv = uri->GetPath(path);
230 0 : if (NS_FAILED(rv)) return rv;
231 :
232 : // a top-level jar directory URL is of the form jar:foo.zip!/
233 : // path will be of the form foo.zip!/, and its last two characters
234 : // will be "!/"
235 : //XXX this won't work correctly when the name of the directory being
236 : //XXX displayed ends with "!", but then again, jar: URIs don't deal
237 : //XXX particularly well with such directories anyway
238 0 : if (!StringEndsWith(path, NS_LITERAL_CSTRING("!/"))) {
239 0 : rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
240 0 : if (NS_FAILED(rv)) return rv;
241 : }
242 : }
243 : else {
244 : // default behavior for other protocols is to assume the channel's
245 : // URL references a directory ending in '/' -- fixup if necessary.
246 0 : nsAutoCString path;
247 0 : rv = uri->GetPath(path);
248 0 : if (NS_FAILED(rv)) return rv;
249 0 : if (baseUri.Last() != '/') {
250 0 : baseUri.Append('/');
251 0 : path.Append('/');
252 0 : uri->SetPath(path);
253 : }
254 0 : if (!path.EqualsLiteral("/")) {
255 0 : rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
256 0 : if (NS_FAILED(rv)) return rv;
257 : }
258 : }
259 :
260 : buffer.AppendLiteral("<style type=\"text/css\">\n"
261 : ":root {\n"
262 : " font-family: sans-serif;\n"
263 : "}\n"
264 : "img {\n"
265 : " border: 0;\n"
266 : "}\n"
267 : "th {\n"
268 : " text-align: start;\n"
269 : " white-space: nowrap;\n"
270 : "}\n"
271 : "th > a {\n"
272 : " color: inherit;\n"
273 : "}\n"
274 : "table[order] > thead > tr > th {\n"
275 : " cursor: pointer;\n"
276 : "}\n"
277 : "table[order] > thead > tr > th::after {\n"
278 : " display: none;\n"
279 : " width: .8em;\n"
280 : " margin-inline-end: -.8em;\n"
281 : " text-align: end;\n"
282 : "}\n"
283 : "table[order=\"asc\"] > thead > tr > th::after {\n"
284 : " content: \"\\2193\"; /* DOWNWARDS ARROW (U+2193) */\n"
285 : "}\n"
286 : "table[order=\"desc\"] > thead > tr > th::after {\n"
287 : " content: \"\\2191\"; /* UPWARDS ARROW (U+2191) */\n"
288 : "}\n"
289 : "table[order][order-by=\"0\"] > thead > tr > th:first-child > a ,\n"
290 : "table[order][order-by=\"1\"] > thead > tr > th:first-child + th > a ,\n"
291 : "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th > a {\n"
292 : " text-decoration: underline;\n"
293 : "}\n"
294 : "table[order][order-by=\"0\"] > thead > tr > th:first-child::after ,\n"
295 : "table[order][order-by=\"1\"] > thead > tr > th:first-child + th::after ,\n"
296 : "table[order][order-by=\"2\"] > thead > tr > th:first-child + th + th::after {\n"
297 : " display: inline-block;\n"
298 : "}\n"
299 : "table.remove-hidden > tbody > tr.hidden-object {\n"
300 : " display: none;\n"
301 : "}\n"
302 : "td {\n"
303 : " white-space: nowrap;\n"
304 : "}\n"
305 : "table.ellipsis {\n"
306 : " width: 100%;\n"
307 : " table-layout: fixed;\n"
308 : " border-spacing: 0;\n"
309 : "}\n"
310 : "table.ellipsis > tbody > tr > td {\n"
311 : " padding: 0;\n"
312 : " overflow: hidden;\n"
313 : " text-overflow: ellipsis;\n"
314 : "}\n"
315 : "/* name */\n"
316 : "/* name */\n"
317 : "th:first-child {\n"
318 : " padding-inline-end: 2em;\n"
319 : "}\n"
320 : "/* size */\n"
321 : "th:first-child + th {\n"
322 : " padding-inline-end: 1em;\n"
323 : "}\n"
324 : "td:first-child + td {\n"
325 : " text-align: end;\n"
326 : " padding-inline-end: 1em;\n"
327 : "}\n"
328 : "/* date */\n"
329 : "td:first-child + td + td {\n"
330 : " padding-inline-start: 1em;\n"
331 : " padding-inline-end: .5em;\n"
332 : "}\n"
333 : "/* time */\n"
334 : "td:first-child + td + td + td {\n"
335 : " padding-inline-start: .5em;\n"
336 : "}\n"
337 : ".symlink {\n"
338 : " font-style: italic;\n"
339 : "}\n"
340 : ".dir ,\n"
341 : ".symlink ,\n"
342 : ".file {\n"
343 : " margin-inline-start: 20px;\n"
344 : "}\n"
345 : ".dir::before ,\n"
346 : ".file > img {\n"
347 : " margin-inline-end: 4px;\n"
348 : " margin-inline-start: -20px;\n"
349 : " max-width: 16px;\n"
350 : " max-height: 16px;\n"
351 : " vertical-align: middle;\n"
352 : "}\n"
353 : ".dir::before {\n"
354 : " content: url(resource://gre/res/html/folder.png);\n"
355 : "}\n"
356 : "</style>\n"
357 : "<link rel=\"stylesheet\" media=\"screen, projection\" type=\"text/css\""
358 : " href=\"chrome://global/skin/dirListing/dirListing.css\">\n"
359 : "<script type=\"application/javascript\">\n"
360 : "'use strict';\n"
361 : "var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;\n"
362 : "document.addEventListener(\"DOMContentLoaded\", function() {\n"
363 : " gTable = document.getElementsByTagName(\"table\")[0];\n"
364 : " gTBody = gTable.tBodies[0];\n"
365 : " if (gTBody.rows.length < 2)\n"
366 : " return;\n"
367 : " gUI_showHidden = document.getElementById(\"UI_showHidden\");\n"
368 : " var headCells = gTable.tHead.rows[0].cells,\n"
369 : " hiddenObjects = false;\n"
370 : " function rowAction(i) {\n"
371 : " return function(event) {\n"
372 : " event.preventDefault();\n"
373 : " orderBy(i);\n"
374 : " }\n"
375 : " }\n"
376 : " for (var i = headCells.length - 1; i >= 0; i--) {\n"
377 : " var anchor = document.createElement(\"a\");\n"
378 : " anchor.href = \"\";\n"
379 : " anchor.appendChild(headCells[i].firstChild);\n"
380 : " headCells[i].appendChild(anchor);\n"
381 : " headCells[i].addEventListener(\"click\", rowAction(i), true);\n"
382 : " }\n"
383 : " if (gUI_showHidden) {\n"
384 : " gRows = Array.slice(gTBody.rows);\n"
385 : " hiddenObjects = gRows.some(row => row.className == \"hidden-object\");\n"
386 : " }\n"
387 : " gTable.setAttribute(\"order\", \"\");\n"
388 : " if (hiddenObjects) {\n"
389 : " gUI_showHidden.style.display = \"block\";\n"
390 : " updateHidden();\n"
391 : " }\n"
392 : "}, \"false\");\n"
393 : "function compareRows(rowA, rowB) {\n"
394 : " var a = rowA.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n"
395 : " var b = rowB.cells[gOrderBy].getAttribute(\"sortable-data\") || \"\";\n"
396 : " var intA = +a;\n"
397 : " var intB = +b;\n"
398 : " if (a == intA && b == intB) {\n"
399 : " a = intA;\n"
400 : " b = intB;\n"
401 : " } else {\n"
402 : " a = a.toLowerCase();\n"
403 : " b = b.toLowerCase();\n"
404 : " }\n"
405 : " if (a < b)\n"
406 : " return -1;\n"
407 : " if (a > b)\n"
408 : " return 1;\n"
409 : " return 0;\n"
410 : "}\n"
411 : "function orderBy(column) {\n"
412 : " if (!gRows)\n"
413 : " gRows = Array.slice(gTBody.rows);\n"
414 : " var order;\n"
415 : " if (gOrderBy == column) {\n"
416 : " order = gTable.getAttribute(\"order\") == \"asc\" ? \"desc\" : \"asc\";\n"
417 : " } else {\n"
418 : " order = \"asc\";\n"
419 : " gOrderBy = column;\n"
420 : " gTable.setAttribute(\"order-by\", column);\n"
421 : " gRows.sort(compareRows);\n"
422 : " }\n"
423 : " gTable.removeChild(gTBody);\n"
424 : " gTable.setAttribute(\"order\", order);\n"
425 : " if (order == \"asc\")\n"
426 : " for (var i = 0; i < gRows.length; i++)\n"
427 : " gTBody.appendChild(gRows[i]);\n"
428 : " else\n"
429 : " for (var i = gRows.length - 1; i >= 0; i--)\n"
430 : " gTBody.appendChild(gRows[i]);\n"
431 : " gTable.appendChild(gTBody);\n"
432 : "}\n"
433 : "function updateHidden() {\n"
434 : " gTable.className = gUI_showHidden.getElementsByTagName(\"input\")[0].checked ?\n"
435 : " \"\" :\n"
436 : " \"remove-hidden\";\n"
437 : "}\n"
438 0 : "</script>\n");
439 :
440 0 : buffer.AppendLiteral("<link rel=\"icon\" type=\"image/png\" href=\"");
441 0 : nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
442 0 : if (!innerUri)
443 0 : return NS_ERROR_UNEXPECTED;
444 0 : nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(innerUri));
445 : //XXX bug 388553: can't use skinnable icons here due to security restrictions
446 0 : if (fileURL) {
447 : buffer.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
448 : "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
449 : "ZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR"
450 : "3XQ3ONASdBJCSBxHos5%2B3Bg3rvkCv8PElS78gPkO%2FATj"
451 : "QoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12O"
452 : "YIDhcPiu2Wx%2B%2FHF5CW1Z6Jyegt%2FTNEWSJIjjGFEUIQ"
453 : "xDrFYrWFSzXC4%2FdLvd95pRKpXKy%2BpRFZ7nwaWo1%2BsG"
454 : "nQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5"
455 : "obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM%2FS"
456 : "KK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9c"
457 : "lkYoDo3SZJzzSdp0VSKYmfV1co%2Bz580kw5KDIM8RbRfEnU"
458 : "f1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd%2BzGQQB5llunF"
459 : "%2FM52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxi"
460 : "xfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18l"
461 : "yWh5FFbrOSxbl3V4G%2BVB7T4ajYYxTyuLtO%2BCvWGgJE1M"
462 : "c7JNsJEhvgw%2FQV4fo%2F24nbEsX2u1d5sVyn8sJO0ZAQiI"
463 : "YnFh%2BxrfLz%2Fj29cBS%2FO14zg3i8XigW3ZkErDtmKoeM"
464 : "%2BAJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ%2B"
465 : "4pQ1%2FlPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj%2F8m%2B8"
466 0 : "y%2FkrwABHbz2H9V68DQAAAABJRU5ErkJggg%3D%3D");
467 : } else {
468 : buffer.AppendLiteral("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB"
469 : "AAAAAQCAYAAAAf8%2F9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
470 : "ZSBJbWFnZVJlYWR5ccllPAAAAeBJREFUeNqcU81O20AQ%2Ft"
471 : "Z2AgQSYQRqL1UPVG2hAUQkxLEStz4DrXpLpD5Drz31Cajax%"
472 : "2Bghhx6qHIJURBTxIwQRwopCBbZjHMcOTrzermPipsSt1Iw0"
473 : "3p3ZmW%2B%2B2R0TxhgOD34wjCHZlQ0iDYz9yvEfhxMTCYhE"
474 : "QDIZhkxKd2sqzX2TOD2vBQCQhpPefng1ZP2dVPlLLdpL8SEM"
475 : "cxng%2Fbs0RIHhtgs4twxOh%2BHjZxvzDx%2F3GQQiDFISiR"
476 : "BLFMPKTRMollzcWECrDVhtxtdRVsL9youPxGj%2FbdfFlUZh"
477 : "tDyYbYqWRUdai1oQRZ5oHeHl2gNM%2B01Uqio8RlH%2Bnsaz"
478 : "JzNwXcq1B%2BiXPHprlEEymeBfXs1w8XxxihfyuXqoHqpoGj"
479 : "ZM04bddgG%2F9%2B8WGj87qDdsrK9m%2BoA%2BpbhQTDh2l1"
480 : "%2Bi2weNbSHMZyjvNXmVbqh9Fj5Oz27uEoP%2BSTxANruJs9"
481 : "L%2FT6P0ewqPx5nmiAG5f6AoCtN1PbJzuRyJAyDBzzSQYvEr"
482 : "f06yYxhGXlEa8H2KVGoasjwLx3Ewk858opQWXm%2B%2Fib9E"
483 : "QrBzclLLLy89xYvlpchvtixcX6uo1y%2FzsiwHrkIsgKbp%2"
484 : "BYWFOWicuqppoNTnStHzPFCPQhBEBOyGAX4JMADFetubi4BS"
485 0 : "YAAAAABJRU5ErkJggg%3D%3D");
486 : }
487 0 : buffer.AppendLiteral("\">\n<title>");
488 :
489 : // Everything needs to end in a /,
490 : // otherwise we end up linking to file:///foo/dirfile
491 :
492 0 : if (!mTextToSubURI) {
493 0 : mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
494 0 : if (NS_FAILED(rv)) return rv;
495 : }
496 :
497 0 : nsXPIDLCString encoding;
498 0 : rv = uri->GetOriginCharset(encoding);
499 0 : if (NS_FAILED(rv)) return rv;
500 0 : if (encoding.IsEmpty()) {
501 0 : encoding.AssignLiteral("UTF-8");
502 : }
503 :
504 0 : nsAutoString unEscapeSpec;
505 0 : rv = mTextToSubURI->UnEscapeAndConvert(encoding, titleUri, unEscapeSpec);
506 : // unescape may fail because
507 : // 1. file URL may be encoded in platform charset for backward compatibility
508 : // 2. query part may not be encoded in UTF-8 (see bug 261929)
509 : // so try the platform's default if this is file url
510 0 : if (NS_FAILED(rv) && isSchemeFile) {
511 0 : nsCOMPtr<nsIPlatformCharset> platformCharset(do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv));
512 0 : NS_ENSURE_SUCCESS(rv, rv);
513 0 : nsAutoCString charset;
514 0 : rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName, charset);
515 0 : NS_ENSURE_SUCCESS(rv, rv);
516 :
517 0 : rv = mTextToSubURI->UnEscapeAndConvert(charset, titleUri, unEscapeSpec);
518 : }
519 0 : if (NS_FAILED(rv)) return rv;
520 :
521 0 : nsXPIDLString htmlEscSpec;
522 0 : htmlEscSpec.Adopt(nsEscapeHTML2(unEscapeSpec.get(),
523 0 : unEscapeSpec.Length()));
524 :
525 0 : nsXPIDLString title;
526 : const char16_t* formatTitle[] = {
527 0 : htmlEscSpec.get()
528 0 : };
529 :
530 0 : rv = mBundle->FormatStringFromName(u"DirTitle",
531 : formatTitle,
532 : sizeof(formatTitle)/sizeof(char16_t*),
533 0 : getter_Copies(title));
534 0 : if (NS_FAILED(rv)) return rv;
535 :
536 : // we want to convert string bundle to NCR
537 : // to ensure they're shown in any charsets
538 0 : AppendNonAsciiToNCR(title, buffer);
539 :
540 0 : buffer.AppendLiteral("</title>\n");
541 :
542 : // If there is a quote character in the baseUri, then
543 : // lets not add a base URL. The reason for this is that
544 : // if we stick baseUri containing a quote into a quoted
545 : // string, the quote character will prematurely close
546 : // the base href string. This is a fall-back check;
547 : // that's why it is OK to not use a base rather than
548 : // trying to play nice and escaping the quotes. See bug
549 : // 358128.
550 :
551 0 : if (!baseUri.Contains('"'))
552 : {
553 : // Great, the baseUri does not contain a char that
554 : // will prematurely close the string. Go ahead an
555 : // add a base href, but only do so if we're not
556 : // dealing with a resource URI.
557 0 : if (!isResource) {
558 0 : buffer.AppendLiteral("<base href=\"");
559 0 : nsAdoptingCString htmlEscapedUri(nsEscapeHTML(baseUri.get()));
560 0 : buffer.Append(htmlEscapedUri);
561 0 : buffer.AppendLiteral("\" />\n");
562 : }
563 : }
564 : else
565 : {
566 0 : NS_ERROR("broken protocol handler didn't escape double-quote.");
567 : }
568 :
569 0 : nsCString direction(NS_LITERAL_CSTRING("ltr"));
570 0 : if (LocaleService::GetInstance()->IsAppLocaleRTL()) {
571 0 : direction.AssignLiteral("rtl");
572 : }
573 :
574 0 : buffer.AppendLiteral("</head>\n<body dir=\"");
575 0 : buffer.Append(direction);
576 0 : buffer.AppendLiteral("\">\n<h1>");
577 :
578 : const char16_t* formatHeading[] = {
579 0 : htmlEscSpec.get()
580 0 : };
581 :
582 0 : rv = mBundle->FormatStringFromName(u"DirTitle",
583 : formatHeading,
584 : sizeof(formatHeading)/sizeof(char16_t*),
585 0 : getter_Copies(title));
586 0 : if (NS_FAILED(rv)) return rv;
587 :
588 0 : AppendNonAsciiToNCR(title, buffer);
589 0 : buffer.AppendLiteral("</h1>\n");
590 :
591 0 : if (!parentStr.IsEmpty()) {
592 0 : nsXPIDLString parentText;
593 0 : rv = mBundle->GetStringFromName(u"DirGoUp",
594 0 : getter_Copies(parentText));
595 0 : if (NS_FAILED(rv)) return rv;
596 :
597 0 : buffer.AppendLiteral("<p id=\"UI_goUp\"><a class=\"up\" href=\"");
598 :
599 0 : nsAdoptingCString htmlParentStr(nsEscapeHTML(parentStr.get()));
600 0 : buffer.Append(htmlParentStr);
601 0 : buffer.AppendLiteral("\">");
602 0 : AppendNonAsciiToNCR(parentText, buffer);
603 0 : buffer.AppendLiteral("</a></p>\n");
604 : }
605 :
606 0 : if (isSchemeFile) {
607 0 : nsXPIDLString showHiddenText;
608 0 : rv = mBundle->GetStringFromName(u"ShowHidden",
609 0 : getter_Copies(showHiddenText));
610 0 : if (NS_FAILED(rv)) return rv;
611 :
612 0 : buffer.AppendLiteral("<p id=\"UI_showHidden\" style=\"display:none\"><label><input type=\"checkbox\" checked onchange=\"updateHidden()\">");
613 0 : AppendNonAsciiToNCR(showHiddenText, buffer);
614 0 : buffer.AppendLiteral("</label></p>\n");
615 : }
616 :
617 0 : buffer.AppendLiteral("<table>\n");
618 :
619 0 : nsXPIDLString columnText;
620 :
621 : buffer.AppendLiteral(" <thead>\n"
622 : " <tr>\n"
623 0 : " <th>");
624 :
625 0 : rv = mBundle->GetStringFromName(u"DirColName",
626 0 : getter_Copies(columnText));
627 0 : if (NS_FAILED(rv)) return rv;
628 0 : AppendNonAsciiToNCR(columnText, buffer);
629 : buffer.AppendLiteral("</th>\n"
630 0 : " <th>");
631 :
632 0 : rv = mBundle->GetStringFromName(u"DirColSize",
633 0 : getter_Copies(columnText));
634 0 : if (NS_FAILED(rv)) return rv;
635 0 : AppendNonAsciiToNCR(columnText, buffer);
636 : buffer.AppendLiteral("</th>\n"
637 0 : " <th colspan=\"2\">");
638 :
639 0 : rv = mBundle->GetStringFromName(u"DirColMTime",
640 0 : getter_Copies(columnText));
641 0 : if (NS_FAILED(rv)) return rv;
642 0 : AppendNonAsciiToNCR(columnText, buffer);
643 : buffer.AppendLiteral("</th>\n"
644 : " </tr>\n"
645 0 : " </thead>\n");
646 0 : buffer.AppendLiteral(" <tbody>\n");
647 :
648 0 : aBuffer = buffer;
649 0 : return rv;
650 : }
651 :
652 : NS_IMETHODIMP
653 0 : nsIndexedToHTML::OnStopRequest(nsIRequest* request, nsISupports *aContext,
654 : nsresult aStatus) {
655 0 : if (NS_SUCCEEDED(aStatus)) {
656 0 : nsCString buffer;
657 0 : buffer.AssignLiteral("</tbody></table></body></html>\n");
658 :
659 0 : aStatus = SendToListener(request, aContext, buffer);
660 : }
661 :
662 0 : mParser->OnStopRequest(request, aContext, aStatus);
663 0 : mParser = nullptr;
664 :
665 0 : return mListener->OnStopRequest(request, aContext, aStatus);
666 : }
667 :
668 : nsresult
669 0 : nsIndexedToHTML::SendToListener(nsIRequest* aRequest, nsISupports *aContext, const nsACString &aBuffer)
670 : {
671 0 : nsCOMPtr<nsIInputStream> inputData;
672 0 : nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputData), aBuffer);
673 0 : NS_ENSURE_SUCCESS(rv, rv);
674 0 : return mListener->OnDataAvailable(aRequest, aContext,
675 0 : inputData, 0, aBuffer.Length());
676 : }
677 :
678 : NS_IMETHODIMP
679 0 : nsIndexedToHTML::OnDataAvailable(nsIRequest *aRequest,
680 : nsISupports *aCtxt,
681 : nsIInputStream* aInput,
682 : uint64_t aOffset,
683 : uint32_t aCount) {
684 0 : return mParser->OnDataAvailable(aRequest, aCtxt, aInput, aOffset, aCount);
685 : }
686 :
687 : NS_IMETHODIMP
688 0 : nsIndexedToHTML::OnIndexAvailable(nsIRequest *aRequest,
689 : nsISupports *aCtxt,
690 : nsIDirIndex *aIndex) {
691 : nsresult rv;
692 0 : if (!aIndex)
693 0 : return NS_ERROR_NULL_POINTER;
694 :
695 0 : nsCString pushBuffer;
696 0 : pushBuffer.AppendLiteral("<tr");
697 :
698 : // We don't know the file's character set yet, so retrieve the raw bytes
699 : // which will be decoded by the HTML parser.
700 0 : nsXPIDLCString loc;
701 0 : aIndex->GetLocation(getter_Copies(loc));
702 :
703 : // Adjust the length in case unescaping shortened the string.
704 0 : loc.Truncate(nsUnescapeCount(loc.BeginWriting()));
705 :
706 0 : if (loc.IsEmpty()) {
707 0 : return NS_ERROR_ILLEGAL_VALUE;
708 : }
709 0 : if (loc.First() == char16_t('.'))
710 0 : pushBuffer.AppendLiteral(" class=\"hidden-object\"");
711 :
712 0 : pushBuffer.AppendLiteral(">\n <td sortable-data=\"");
713 :
714 : // The sort key is the name of the item, prepended by either 0, 1 or 2
715 : // in order to group items.
716 : uint32_t type;
717 0 : aIndex->GetType(&type);
718 0 : switch (type) {
719 : case nsIDirIndex::TYPE_SYMLINK:
720 0 : pushBuffer.Append('0');
721 0 : break;
722 : case nsIDirIndex::TYPE_DIRECTORY:
723 0 : pushBuffer.Append('1');
724 0 : break;
725 : default:
726 0 : pushBuffer.Append('2');
727 0 : break;
728 : }
729 0 : nsAdoptingCString escaped(nsEscapeHTML(loc));
730 0 : pushBuffer.Append(escaped);
731 :
732 0 : pushBuffer.AppendLiteral("\"><table class=\"ellipsis\"><tbody><tr><td><a class=\"");
733 0 : switch (type) {
734 : case nsIDirIndex::TYPE_DIRECTORY:
735 0 : pushBuffer.AppendLiteral("dir");
736 0 : break;
737 : case nsIDirIndex::TYPE_SYMLINK:
738 0 : pushBuffer.AppendLiteral("symlink");
739 0 : break;
740 : default:
741 0 : pushBuffer.AppendLiteral("file");
742 0 : break;
743 : }
744 :
745 0 : pushBuffer.AppendLiteral("\" href=\"");
746 :
747 : // need to escape links
748 0 : nsAutoCString locEscaped;
749 :
750 : // Adding trailing slash helps to recognize whether the URL points to a file
751 : // or a directory (bug #214405).
752 0 : if ((type == nsIDirIndex::TYPE_DIRECTORY) && (loc.Last() != '/')) {
753 0 : loc.Append('/');
754 : }
755 :
756 : // now minimally re-escape the location...
757 : uint32_t escFlags;
758 : // for some protocols, we expect the location to be absolute.
759 : // if so, and if the location indeed appears to be a valid URI, then go
760 : // ahead and treat it like one.
761 :
762 0 : nsAutoCString scheme;
763 0 : if (mExpectAbsLoc &&
764 0 : NS_SUCCEEDED(net_ExtractURLScheme(loc, scheme))) {
765 : // escape as absolute
766 0 : escFlags = esc_Forced | esc_AlwaysCopy | esc_Minimal;
767 : }
768 : else {
769 : // escape as relative
770 : // esc_Directory is needed because directories have a trailing slash.
771 : // Without it, the trailing '/' will be escaped, and links from within
772 : // that directory will be incorrect
773 0 : escFlags = esc_Forced | esc_AlwaysCopy | esc_FileBaseName | esc_Colon | esc_Directory;
774 : }
775 0 : NS_EscapeURL(loc.get(), loc.Length(), escFlags, locEscaped);
776 : // esc_Directory does not escape the semicolons, so if a filename
777 : // contains semicolons we need to manually escape them.
778 : // This replacement should be removed in bug #473280
779 0 : locEscaped.ReplaceSubstring(";", "%3b");
780 0 : nsAdoptingCString htmlEscapedURL(nsEscapeHTML(locEscaped.get()));
781 0 : pushBuffer.Append(htmlEscapedURL);
782 :
783 0 : pushBuffer.AppendLiteral("\">");
784 :
785 0 : if (type == nsIDirIndex::TYPE_FILE || type == nsIDirIndex::TYPE_UNKNOWN) {
786 0 : pushBuffer.AppendLiteral("<img src=\"moz-icon://");
787 0 : int32_t lastDot = locEscaped.RFindChar('.');
788 0 : if (lastDot != kNotFound) {
789 0 : locEscaped.Cut(0, lastDot);
790 0 : nsAdoptingCString htmlFileExt(nsEscapeHTML(locEscaped.get()));
791 0 : pushBuffer.Append(htmlFileExt);
792 : } else {
793 0 : pushBuffer.AppendLiteral("unknown");
794 : }
795 0 : pushBuffer.AppendLiteral("?size=16\" alt=\"");
796 :
797 0 : nsXPIDLString altText;
798 0 : rv = mBundle->GetStringFromName(u"DirFileLabel",
799 0 : getter_Copies(altText));
800 0 : if (NS_FAILED(rv)) return rv;
801 0 : AppendNonAsciiToNCR(altText, pushBuffer);
802 0 : pushBuffer.AppendLiteral("\">");
803 : }
804 :
805 0 : pushBuffer.Append(escaped);
806 0 : pushBuffer.AppendLiteral("</a></td></tr></tbody></table></td>\n <td");
807 :
808 0 : if (type == nsIDirIndex::TYPE_DIRECTORY || type == nsIDirIndex::TYPE_SYMLINK) {
809 0 : pushBuffer.Append('>');
810 : } else {
811 : int64_t size;
812 0 : aIndex->GetSize(&size);
813 :
814 0 : if (uint64_t(size) != UINT64_MAX) {
815 0 : pushBuffer.AppendLiteral(" sortable-data=\"");
816 0 : pushBuffer.AppendInt(size);
817 0 : pushBuffer.AppendLiteral("\">");
818 0 : nsAutoCString sizeString;
819 0 : FormatSizeString(size, sizeString);
820 0 : pushBuffer.Append(sizeString);
821 : } else {
822 0 : pushBuffer.Append('>');
823 : }
824 : }
825 0 : pushBuffer.AppendLiteral("</td>\n <td");
826 :
827 : PRTime t;
828 0 : aIndex->GetLastModified(&t);
829 :
830 0 : if (t == -1LL) {
831 0 : pushBuffer.AppendLiteral("></td>\n <td>");
832 : } else {
833 0 : pushBuffer.AppendLiteral(" sortable-data=\"");
834 0 : pushBuffer.AppendInt(static_cast<int64_t>(t));
835 0 : pushBuffer.AppendLiteral("\">");
836 0 : nsAutoString formatted;
837 : mozilla::DateTimeFormat::FormatPRTime(kDateFormatShort,
838 : kTimeFormatNone,
839 : t,
840 0 : formatted);
841 0 : AppendNonAsciiToNCR(formatted, pushBuffer);
842 0 : pushBuffer.AppendLiteral("</td>\n <td>");
843 : mozilla::DateTimeFormat::FormatPRTime(kDateFormatNone,
844 : kTimeFormatSeconds,
845 : t,
846 0 : formatted);
847 : // use NCR to show date in any doc charset
848 0 : AppendNonAsciiToNCR(formatted, pushBuffer);
849 : }
850 :
851 0 : pushBuffer.AppendLiteral("</td>\n</tr>");
852 :
853 0 : return SendToListener(aRequest, aCtxt, pushBuffer);
854 : }
855 :
856 : NS_IMETHODIMP
857 0 : nsIndexedToHTML::OnInformationAvailable(nsIRequest *aRequest,
858 : nsISupports *aCtxt,
859 : const nsAString& aInfo) {
860 0 : nsAutoCString pushBuffer;
861 0 : nsAdoptingString escaped(nsEscapeHTML2(PromiseFlatString(aInfo).get()));
862 0 : if (!escaped)
863 0 : return NS_ERROR_OUT_OF_MEMORY;
864 0 : pushBuffer.AppendLiteral("<tr>\n <td>");
865 : // escaped is provided in Unicode, so write hex NCRs as necessary
866 : // to prevent the HTML parser from applying a character set.
867 0 : AppendNonAsciiToNCR(escaped, pushBuffer);
868 0 : pushBuffer.AppendLiteral("</td>\n <td></td>\n <td></td>\n <td></td>\n</tr>\n");
869 :
870 0 : return SendToListener(aRequest, aCtxt, pushBuffer);
871 : }
872 :
873 0 : void nsIndexedToHTML::FormatSizeString(int64_t inSize, nsCString& outSizeString)
874 : {
875 0 : outSizeString.Truncate();
876 0 : if (inSize > int64_t(0)) {
877 : // round up to the nearest Kilobyte
878 0 : int64_t upperSize = (inSize + int64_t(1023)) / int64_t(1024);
879 0 : outSizeString.AppendInt(upperSize);
880 0 : outSizeString.AppendLiteral(" KB");
881 : }
882 0 : }
883 :
884 0 : nsIndexedToHTML::nsIndexedToHTML() {
885 0 : }
886 :
887 0 : nsIndexedToHTML::~nsIndexedToHTML() {
888 0 : }
|