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 "nsAboutCacheEntry.h"
7 :
8 : #include "mozilla/Sprintf.h"
9 :
10 : #include "nsAboutCache.h"
11 : #include "nsICacheStorage.h"
12 : #include "CacheObserver.h"
13 : #include "nsNetUtil.h"
14 : #include "nsEscape.h"
15 : #include "nsIAsyncInputStream.h"
16 : #include "nsIAsyncOutputStream.h"
17 : #include "nsAboutProtocolUtils.h"
18 : #include "nsContentUtils.h"
19 : #include "nsInputStreamPump.h"
20 : #include "CacheFileUtils.h"
21 : #include <algorithm>
22 : #include "nsIPipe.h"
23 :
24 : using namespace mozilla::net;
25 :
26 : #define HEXDUMP_MAX_ROWS 16
27 :
28 : static void
29 0 : HexDump(uint32_t *state, const char *buf, int32_t n, nsCString &result)
30 : {
31 : char temp[16];
32 :
33 : const unsigned char *p;
34 0 : while (n) {
35 0 : SprintfLiteral(temp, "%08x: ", *state);
36 0 : result.Append(temp);
37 0 : *state += HEXDUMP_MAX_ROWS;
38 :
39 0 : p = (const unsigned char *) buf;
40 :
41 0 : int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n);
42 :
43 : // print hex codes:
44 0 : for (i = 0; i < row_max; ++i) {
45 0 : SprintfLiteral(temp, "%02x ", *p++);
46 0 : result.Append(temp);
47 : }
48 0 : for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) {
49 0 : result.AppendLiteral(" ");
50 : }
51 :
52 : // print ASCII glyphs if possible:
53 0 : p = (const unsigned char *) buf;
54 0 : for (i = 0; i < row_max; ++i, ++p) {
55 0 : switch (*p) {
56 : case '<':
57 0 : result.AppendLiteral("<");
58 0 : break;
59 : case '>':
60 0 : result.AppendLiteral(">");
61 0 : break;
62 : case '&':
63 0 : result.AppendLiteral("&");
64 0 : break;
65 : default:
66 0 : if (*p < 0x7F && *p > 0x1F) {
67 0 : result.Append(*p);
68 : } else {
69 0 : result.Append('.');
70 : }
71 : }
72 : }
73 :
74 0 : result.Append('\n');
75 :
76 0 : buf += row_max;
77 0 : n -= row_max;
78 : }
79 0 : }
80 :
81 : //-----------------------------------------------------------------------------
82 : // nsAboutCacheEntry::nsISupports
83 :
84 0 : NS_IMPL_ISUPPORTS(nsAboutCacheEntry,
85 : nsIAboutModule)
86 0 : NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel,
87 : nsICacheEntryOpenCallback,
88 : nsICacheEntryMetaDataVisitor,
89 : nsIStreamListener,
90 : nsIRequest,
91 : nsIChannel)
92 :
93 : //-----------------------------------------------------------------------------
94 : // nsAboutCacheEntry::nsIAboutModule
95 :
96 : NS_IMETHODIMP
97 0 : nsAboutCacheEntry::NewChannel(nsIURI* uri,
98 : nsILoadInfo* aLoadInfo,
99 : nsIChannel** result)
100 : {
101 0 : NS_ENSURE_ARG_POINTER(uri);
102 : nsresult rv;
103 :
104 0 : RefPtr<Channel> channel = new Channel();
105 0 : rv = channel->Init(uri, aLoadInfo);
106 0 : if (NS_FAILED(rv)) return rv;
107 :
108 0 : channel.forget(result);
109 :
110 0 : return NS_OK;
111 : }
112 :
113 : NS_IMETHODIMP
114 0 : nsAboutCacheEntry::GetURIFlags(nsIURI *aURI, uint32_t *result)
115 : {
116 0 : *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT |
117 : nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
118 0 : return NS_OK;
119 : }
120 :
121 : //-----------------------------------------------------------------------------
122 : // nsAboutCacheEntry::Channel
123 :
124 : nsresult
125 0 : nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo)
126 : {
127 : nsresult rv;
128 :
129 0 : nsCOMPtr<nsIInputStream> stream;
130 0 : rv = GetContentStream(uri, getter_AddRefs(stream));
131 0 : if (NS_FAILED(rv)) return rv;
132 :
133 0 : rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel),
134 : uri,
135 : stream,
136 0 : NS_LITERAL_CSTRING("text/html"),
137 0 : NS_LITERAL_CSTRING("utf-8"),
138 0 : aLoadInfo);
139 0 : if (NS_FAILED(rv)) return rv;
140 :
141 0 : return NS_OK;
142 : }
143 :
144 : nsresult
145 0 : nsAboutCacheEntry::Channel::GetContentStream(nsIURI *uri, nsIInputStream **result)
146 : {
147 : nsresult rv;
148 :
149 : // Init: (block size, maximum length)
150 0 : nsCOMPtr<nsIAsyncInputStream> inputStream;
151 0 : rv = NS_NewPipe2(getter_AddRefs(inputStream),
152 0 : getter_AddRefs(mOutputStream),
153 : true, false,
154 0 : 256, UINT32_MAX);
155 0 : if (NS_FAILED(rv)) return rv;
156 :
157 0 : NS_NAMED_LITERAL_CSTRING(
158 : buffer,
159 : "<!DOCTYPE html>\n"
160 : "<html>\n"
161 : "<head>\n"
162 : " <title>Cache entry information</title>\n"
163 : " <link rel=\"stylesheet\" "
164 : "href=\"chrome://global/skin/about.css\" type=\"text/css\"/>\n"
165 : " <link rel=\"stylesheet\" "
166 : "href=\"chrome://global/skin/aboutCacheEntry.css\" type=\"text/css\"/>\n"
167 : "</head>\n"
168 : "<body>\n"
169 : "<h1>Cache entry information</h1>\n");
170 : uint32_t n;
171 0 : rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n);
172 0 : if (NS_FAILED(rv)) return rv;
173 0 : if (n != buffer.Length()) return NS_ERROR_UNEXPECTED;
174 :
175 0 : rv = OpenCacheEntry(uri);
176 0 : if (NS_FAILED(rv)) return rv;
177 :
178 0 : inputStream.forget(result);
179 0 : return NS_OK;
180 : }
181 :
182 : nsresult
183 0 : nsAboutCacheEntry::Channel::OpenCacheEntry(nsIURI *uri)
184 : {
185 : nsresult rv;
186 :
187 0 : rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo),
188 0 : mEnhanceId, getter_AddRefs(mCacheURI));
189 0 : if (NS_FAILED(rv)) return rv;
190 :
191 0 : if (!CacheObserver::UseNewCache() &&
192 0 : mLoadInfo->IsPrivate() &&
193 0 : mStorageName.EqualsLiteral("disk")) {
194 : // The cache v1 is storing all private entries in the memory-only
195 : // cache, so it would not be found in the v1 disk cache.
196 0 : mStorageName = NS_LITERAL_CSTRING("memory");
197 : }
198 :
199 0 : return OpenCacheEntry();
200 : }
201 :
202 : nsresult
203 0 : nsAboutCacheEntry::Channel::OpenCacheEntry()
204 : {
205 : nsresult rv;
206 :
207 0 : nsCOMPtr<nsICacheStorage> storage;
208 0 : rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo, getter_AddRefs(storage));
209 0 : if (NS_FAILED(rv)) return rv;
210 :
211 : // Invokes OnCacheEntryAvailable()
212 0 : rv = storage->AsyncOpenURI(mCacheURI, mEnhanceId,
213 : nsICacheStorage::OPEN_READONLY |
214 : nsICacheStorage::OPEN_SECRETLY,
215 0 : this);
216 0 : if (NS_FAILED(rv)) return rv;
217 :
218 0 : return NS_OK;
219 : }
220 :
221 : nsresult
222 0 : nsAboutCacheEntry::Channel::ParseURI(nsIURI *uri,
223 : nsACString &storageName,
224 : nsILoadContextInfo **loadInfo,
225 : nsCString &enahnceID,
226 : nsIURI **cacheUri)
227 : {
228 : //
229 : // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string]
230 : //
231 : nsresult rv;
232 :
233 0 : nsAutoCString path;
234 0 : rv = uri->GetPath(path);
235 0 : if (NS_FAILED(rv))
236 0 : return rv;
237 :
238 0 : nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end;
239 0 : path.BeginReading(begin);
240 0 : path.EndReading(end);
241 :
242 0 : keyBegin = begin; keyEnd = end;
243 0 : if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), keyBegin, keyEnd))
244 0 : return NS_ERROR_FAILURE;
245 :
246 0 : valBegin = keyEnd; // the value of the storage key starts after the key
247 :
248 0 : keyBegin = keyEnd; keyEnd = end;
249 0 : if (!FindInReadable(NS_LITERAL_CSTRING("&context="), keyBegin, keyEnd))
250 0 : return NS_ERROR_FAILURE;
251 :
252 0 : storageName.Assign(Substring(valBegin, keyBegin));
253 0 : valBegin = keyEnd; // the value of the context key starts after the key
254 :
255 0 : keyBegin = keyEnd; keyEnd = end;
256 0 : if (!FindInReadable(NS_LITERAL_CSTRING("&eid="), keyBegin, keyEnd))
257 0 : return NS_ERROR_FAILURE;
258 :
259 0 : nsAutoCString contextKey(Substring(valBegin, keyBegin));
260 0 : valBegin = keyEnd; // the value of the eid key starts after the key
261 :
262 0 : keyBegin = keyEnd; keyEnd = end;
263 0 : if (!FindInReadable(NS_LITERAL_CSTRING("&uri="), keyBegin, keyEnd))
264 0 : return NS_ERROR_FAILURE;
265 :
266 0 : enahnceID.Assign(Substring(valBegin, keyBegin));
267 :
268 0 : valBegin = keyEnd; // the value of the uri key starts after the key
269 0 : nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one
270 :
271 : // Uf... parsing done, now get some objects from it...
272 :
273 : nsCOMPtr<nsILoadContextInfo> info =
274 0 : CacheFileUtils::ParseKey(contextKey);
275 0 : if (!info)
276 0 : return NS_ERROR_FAILURE;
277 0 : info.forget(loadInfo);
278 :
279 0 : rv = NS_NewURI(cacheUri, uriSpec);
280 0 : if (NS_FAILED(rv))
281 0 : return rv;
282 :
283 0 : return NS_OK;
284 : }
285 :
286 : //-----------------------------------------------------------------------------
287 : // nsICacheEntryOpenCallback implementation
288 : //-----------------------------------------------------------------------------
289 :
290 : NS_IMETHODIMP
291 0 : nsAboutCacheEntry::Channel::OnCacheEntryCheck(nsICacheEntry *aEntry,
292 : nsIApplicationCache *aApplicationCache,
293 : uint32_t *result)
294 : {
295 0 : *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
296 0 : return NS_OK;
297 : }
298 :
299 : NS_IMETHODIMP
300 0 : nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry *entry,
301 : bool isNew,
302 : nsIApplicationCache *aApplicationCache,
303 : nsresult status)
304 : {
305 : nsresult rv;
306 :
307 0 : mWaitingForData = false;
308 0 : if (entry) {
309 0 : rv = WriteCacheEntryDescription(entry);
310 0 : } else if (!CacheObserver::UseNewCache() &&
311 0 : !mLoadInfo->IsPrivate() &&
312 0 : mStorageName.EqualsLiteral("memory")) {
313 : // If we were not able to find the entry in the memory storage
314 : // try again in the disk storage.
315 : // This is a workaround for cache v1: when an originally disk
316 : // cache entry is recreated as memory-only, it's clientID doesn't
317 : // change and we cannot find it in "HTTP-memory-only" session.
318 : // "Disk" cache storage looks at "HTTP".
319 0 : mStorageName = NS_LITERAL_CSTRING("disk");
320 0 : rv = OpenCacheEntry();
321 0 : if (NS_SUCCEEDED(rv)) {
322 0 : return NS_OK;
323 : }
324 : } else {
325 0 : rv = WriteCacheEntryUnavailable();
326 : }
327 0 : if (NS_FAILED(rv)) return rv;
328 :
329 :
330 0 : if (!mWaitingForData) {
331 : // Data is not expected, close the output of content now.
332 0 : CloseContent();
333 : }
334 :
335 0 : return NS_OK;
336 : }
337 :
338 : //-----------------------------------------------------------------------------
339 : // Print-out helper methods
340 : //-----------------------------------------------------------------------------
341 :
342 : #define APPEND_ROW(label, value) \
343 : PR_BEGIN_MACRO \
344 : buffer.AppendLiteral(" <tr>\n" \
345 : " <th>"); \
346 : buffer.AppendLiteral(label); \
347 : buffer.AppendLiteral(":</th>\n" \
348 : " <td>"); \
349 : buffer.Append(value); \
350 : buffer.AppendLiteral("</td>\n" \
351 : " </tr>\n"); \
352 : PR_END_MACRO
353 :
354 : nsresult
355 0 : nsAboutCacheEntry::Channel::WriteCacheEntryDescription(nsICacheEntry *entry)
356 : {
357 : nsresult rv;
358 0 : nsCString buffer;
359 : uint32_t n;
360 :
361 0 : nsAutoCString str;
362 :
363 0 : rv = entry->GetKey(str);
364 0 : if (NS_FAILED(rv)) return rv;
365 :
366 0 : buffer.SetCapacity(4096);
367 : buffer.AssignLiteral("<table>\n"
368 : " <tr>\n"
369 : " <th>key:</th>\n"
370 0 : " <td id=\"td-key\">");
371 :
372 : // Test if the key is actually a URI
373 0 : nsCOMPtr<nsIURI> uri;
374 0 : bool isJS = false;
375 0 : bool isData = false;
376 :
377 0 : rv = NS_NewURI(getter_AddRefs(uri), str);
378 : // javascript: and data: URLs should not be linkified
379 : // since clicking them can cause scripts to run - bug 162584
380 0 : if (NS_SUCCEEDED(rv)) {
381 0 : uri->SchemeIs("javascript", &isJS);
382 0 : uri->SchemeIs("data", &isData);
383 : }
384 0 : char* escapedStr = nsEscapeHTML(str.get());
385 0 : if (NS_SUCCEEDED(rv) && !(isJS || isData)) {
386 0 : buffer.AppendLiteral("<a href=\"");
387 0 : buffer.Append(escapedStr);
388 0 : buffer.AppendLiteral("\">");
389 0 : buffer.Append(escapedStr);
390 0 : buffer.AppendLiteral("</a>");
391 0 : uri = nullptr;
392 : } else {
393 0 : buffer.Append(escapedStr);
394 : }
395 0 : free(escapedStr);
396 : buffer.AppendLiteral("</td>\n"
397 0 : " </tr>\n");
398 :
399 : // temp vars for reporting
400 : char timeBuf[255];
401 0 : uint32_t u = 0;
402 0 : int32_t i = 0;
403 0 : nsAutoCString s;
404 :
405 : // Fetch Count
406 0 : s.Truncate();
407 0 : entry->GetFetchCount(&i);
408 0 : s.AppendInt(i);
409 0 : APPEND_ROW("fetch count", s);
410 :
411 : // Last Fetched
412 0 : entry->GetLastFetched(&u);
413 0 : if (u) {
414 0 : PrintTimeString(timeBuf, sizeof(timeBuf), u);
415 0 : APPEND_ROW("last fetched", timeBuf);
416 : } else {
417 0 : APPEND_ROW("last fetched", "No last fetch time (bug 1000338)");
418 : }
419 :
420 : // Last Modified
421 0 : entry->GetLastModified(&u);
422 0 : if (u) {
423 0 : PrintTimeString(timeBuf, sizeof(timeBuf), u);
424 0 : APPEND_ROW("last modified", timeBuf);
425 : } else {
426 0 : APPEND_ROW("last modified", "No last modified time (bug 1000338)");
427 : }
428 :
429 : // Expiration Time
430 0 : entry->GetExpirationTime(&u);
431 :
432 : // Bug - 633747.
433 : // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
434 : // So we check if time is 0, then we show a message, "Expired Immediately"
435 0 : if (u == 0) {
436 0 : APPEND_ROW("expires", "Expired Immediately");
437 0 : } else if (u < 0xFFFFFFFF) {
438 0 : PrintTimeString(timeBuf, sizeof(timeBuf), u);
439 0 : APPEND_ROW("expires", timeBuf);
440 : } else {
441 0 : APPEND_ROW("expires", "No expiration time");
442 : }
443 :
444 : // Data Size
445 0 : s.Truncate();
446 : uint32_t dataSize;
447 0 : if (NS_FAILED(entry->GetStorageDataSize(&dataSize)))
448 0 : dataSize = 0;
449 0 : s.AppendInt((int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed.
450 0 : s.AppendLiteral(" B");
451 0 : APPEND_ROW("Data size", s);
452 :
453 : // TODO - mayhemer
454 : // Here used to be a link to the disk file (in the old cache for entries that
455 : // did not fit any of the block files, in the new cache every time).
456 : // I'd rather have a small set of buttons here to action on the entry:
457 : // 1. save the content
458 : // 2. save as a complete HTTP response (response head, headers, content)
459 : // 3. doom the entry
460 : // A new bug(s) should be filed here.
461 :
462 : // Security Info
463 0 : nsCOMPtr<nsISupports> securityInfo;
464 0 : entry->GetSecurityInfo(getter_AddRefs(securityInfo));
465 0 : if (securityInfo) {
466 0 : APPEND_ROW("Security", "This is a secure document.");
467 : } else {
468 0 : APPEND_ROW("Security",
469 : "This document does not have any security info associated with it.");
470 : }
471 :
472 : buffer.AppendLiteral("</table>\n"
473 : "<hr/>\n"
474 0 : "<table>\n");
475 :
476 0 : mBuffer = &buffer; // make it available for OnMetaDataElement().
477 0 : entry->VisitMetaData(this);
478 0 : mBuffer = nullptr;
479 :
480 0 : buffer.AppendLiteral("</table>\n");
481 0 : mOutputStream->Write(buffer.get(), buffer.Length(), &n);
482 0 : buffer.Truncate();
483 :
484 : // Provide a hexdump of the data
485 0 : if (!dataSize) {
486 0 : return NS_OK;
487 : }
488 :
489 0 : nsCOMPtr<nsIInputStream> stream;
490 0 : entry->OpenInputStream(0, getter_AddRefs(stream));
491 0 : if (!stream) {
492 0 : return NS_OK;
493 : }
494 :
495 0 : RefPtr<nsInputStreamPump> pump;
496 0 : rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
497 0 : if (NS_FAILED(rv)) {
498 0 : return NS_OK; // just ignore
499 : }
500 :
501 0 : rv = pump->AsyncRead(this, nullptr);
502 0 : if (NS_FAILED(rv)) {
503 0 : return NS_OK; // just ignore
504 : }
505 :
506 0 : mWaitingForData = true;
507 0 : return NS_OK;
508 : }
509 :
510 : nsresult
511 0 : nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable()
512 : {
513 : uint32_t n;
514 0 : NS_NAMED_LITERAL_CSTRING(buffer,
515 : "The cache entry you selected is not available.");
516 0 : mOutputStream->Write(buffer.get(), buffer.Length(), &n);
517 0 : return NS_OK;
518 : }
519 :
520 : //-----------------------------------------------------------------------------
521 : // nsICacheEntryMetaDataVisitor implementation
522 : //-----------------------------------------------------------------------------
523 :
524 : NS_IMETHODIMP
525 0 : nsAboutCacheEntry::Channel::OnMetaDataElement(char const * key, char const * value)
526 : {
527 0 : mBuffer->AppendLiteral(" <tr>\n"
528 0 : " <th>");
529 0 : mBuffer->Append(key);
530 0 : mBuffer->AppendLiteral(":</th>\n"
531 0 : " <td>");
532 0 : char* escapedValue = nsEscapeHTML(value);
533 0 : mBuffer->Append(escapedValue);
534 0 : free(escapedValue);
535 0 : mBuffer->AppendLiteral("</td>\n"
536 0 : " </tr>\n");
537 :
538 0 : return NS_OK;
539 : }
540 :
541 : //-----------------------------------------------------------------------------
542 : // nsIStreamListener implementation
543 : //-----------------------------------------------------------------------------
544 :
545 : NS_IMETHODIMP
546 0 : nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest *request, nsISupports *ctx)
547 : {
548 0 : mHexDumpState = 0;
549 :
550 0 : NS_NAMED_LITERAL_CSTRING(buffer, "<hr/>\n<pre>");
551 : uint32_t n;
552 0 : return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
553 : }
554 :
555 : NS_IMETHODIMP
556 0 : nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
557 : nsIInputStream *aInputStream,
558 : uint64_t aOffset,
559 : uint32_t aCount)
560 : {
561 : uint32_t n;
562 : return aInputStream->ReadSegments(
563 0 : &nsAboutCacheEntry::Channel::PrintCacheData, this, aCount, &n);
564 : }
565 :
566 : /* static */ nsresult
567 0 : nsAboutCacheEntry::Channel::PrintCacheData(nsIInputStream *aInStream,
568 : void *aClosure,
569 : const char *aFromSegment,
570 : uint32_t aToOffset,
571 : uint32_t aCount,
572 : uint32_t *aWriteCount)
573 : {
574 : nsAboutCacheEntry::Channel *a =
575 0 : static_cast<nsAboutCacheEntry::Channel*>(aClosure);
576 :
577 0 : nsCString buffer;
578 0 : HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer);
579 :
580 : uint32_t n;
581 0 : a->mOutputStream->Write(buffer.get(), buffer.Length(), &n);
582 :
583 0 : *aWriteCount = aCount;
584 :
585 0 : return NS_OK;
586 : }
587 :
588 : NS_IMETHODIMP
589 0 : nsAboutCacheEntry::Channel::OnStopRequest(nsIRequest *request, nsISupports *ctx,
590 : nsresult result)
591 : {
592 0 : NS_NAMED_LITERAL_CSTRING(buffer, "</pre>\n");
593 : uint32_t n;
594 0 : mOutputStream->Write(buffer.get(), buffer.Length(), &n);
595 :
596 0 : CloseContent();
597 :
598 0 : return NS_OK;
599 : }
600 :
601 : void
602 0 : nsAboutCacheEntry::Channel::CloseContent()
603 : {
604 0 : NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
605 : uint32_t n;
606 0 : mOutputStream->Write(buffer.get(), buffer.Length(), &n);
607 :
608 0 : mOutputStream->Close();
609 0 : mOutputStream = nullptr;
610 0 : }
|