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