Coverage Report

Created: 2018-09-25 14:53

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