1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/appcache/view_appcache_internals_job.h"
10 #include "base/base64.h"
11 #include "base/bind.h"
12 #include "base/format_macros.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/numerics/safe_math.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "content/browser/appcache/appcache.h"
22 #include "content/browser/appcache/appcache_group.h"
23 #include "content/browser/appcache/appcache_policy.h"
24 #include "content/browser/appcache/appcache_response.h"
25 #include "content/browser/appcache/appcache_service_impl.h"
26 #include "content/browser/appcache/appcache_storage.h"
27 #include "net/base/escape.h"
28 #include "net/base/io_buffer.h"
29 #include "net/base/net_errors.h"
30 #include "net/http/http_response_headers.h"
31 #include "net/url_request/url_request.h"
32 #include "net/url_request/url_request_simple_job.h"
33 #include "net/url_request/view_cache_helper.h"
38 const char kErrorMessage
[] = "Error in retrieving Application Caches.";
39 const char kEmptyAppCachesMessage
[] = "No available Application Caches.";
40 const char kManifestNotFoundMessage
[] = "Manifest not found.";
41 const char kManifest
[] = "Manifest: ";
42 const char kSize
[] = "Size: ";
43 const char kCreationTime
[] = "Creation Time: ";
44 const char kLastAccessTime
[] = "Last Access Time: ";
45 const char kLastUpdateTime
[] = "Last Update Time: ";
46 const char kFormattedDisabledAppCacheMsg
[] =
47 "<b><i><font color=\"FF0000\">"
48 "This Application Cache is disabled by policy.</font></i></b><br/>";
49 const char kRemoveCacheLabel
[] = "Remove";
50 const char kViewCacheLabel
[] = "View Entries";
51 const char kRemoveCacheCommand
[] = "remove-cache";
52 const char kViewCacheCommand
[] = "view-cache";
53 const char kViewEntryCommand
[] = "view-entry";
55 void EmitPageStart(std::string
* out
) {
58 "<html><title>AppCache Internals</title>\n"
59 "<meta http-equiv=\"Content-Security-Policy\""
60 " content=\"object-src 'none'; script-src 'none'\">\n"
62 "body { font-family: sans-serif; font-size: 0.8em; }\n"
63 "tt, code, pre { font-family: WebKitHack, monospace; }\n"
64 "form { display: inline; }\n"
65 ".subsection_body { margin: 10px 0 10px 2em; }\n"
66 ".subsection_title { font-weight: bold; }\n"
71 void EmitPageEnd(std::string
* out
) {
72 out
->append("</body></html>\n");
75 void EmitListItem(const std::string
& label
,
76 const std::string
& data
,
79 out
->append(net::EscapeForHTML(label
));
80 out
->append(net::EscapeForHTML(data
));
81 out
->append("</li>\n");
84 void EmitAnchor(const std::string
& url
, const std::string
& text
,
86 out
->append("<a href=\"");
87 out
->append(net::EscapeForHTML(url
));
89 out
->append(net::EscapeForHTML(text
));
93 void EmitCommandAnchor(const char* label
,
98 std::string
query(command
);
101 GURL::Replacements replacements
;
102 replacements
.SetQuery(query
.data(), url::Component(0, query
.length()));
103 GURL command_url
= base_url
.ReplaceComponents(replacements
);
104 EmitAnchor(command_url
.spec(), label
, out
);
107 void EmitAppCacheInfo(const GURL
& base_url
,
108 AppCacheServiceImpl
* service
,
109 const AppCacheInfo
* info
,
111 std::string manifest_url_base64
;
112 base::Base64Encode(info
->manifest_url
.spec(), &manifest_url_base64
);
114 out
->append("\n<p>");
115 out
->append(kManifest
);
116 EmitAnchor(info
->manifest_url
.spec(), info
->manifest_url
.spec(), out
);
117 out
->append("<br/>\n");
118 if (!service
->appcache_policy()->CanLoadAppCache(
119 info
->manifest_url
, info
->manifest_url
)) {
120 out
->append(kFormattedDisabledAppCacheMsg
);
122 out
->append("\n<br/>\n");
123 EmitCommandAnchor(kRemoveCacheLabel
, base_url
,
124 kRemoveCacheCommand
, manifest_url_base64
.c_str(), out
);
125 out
->append(" ");
126 EmitCommandAnchor(kViewCacheLabel
, base_url
,
127 kViewCacheCommand
, manifest_url_base64
.c_str(), out
);
128 out
->append("\n<br/>\n");
132 base::UTF16ToUTF8(base::FormatBytesUnlocalized(info
->size
)),
136 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info
->creation_time
)),
140 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info
->last_update_time
)),
144 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info
->last_access_time
)),
146 out
->append("</ul></p></br>\n");
149 void EmitAppCacheInfoVector(
150 const GURL
& base_url
,
151 AppCacheServiceImpl
* service
,
152 const AppCacheInfoVector
& appcaches
,
154 for (std::vector
<AppCacheInfo
>::const_iterator info
=
156 info
!= appcaches
.end(); ++info
) {
157 EmitAppCacheInfo(base_url
, service
, &(*info
), out
);
161 void EmitTableData(const std::string
& data
, bool align_right
, bool bold
,
164 out
->append("<td align='right'>");
172 out
->append("</td>");
175 std::string
FormFlagsString(const AppCacheResourceInfo
& info
) {
177 if (info
.is_manifest
)
178 str
.append("Manifest, ");
180 str
.append("Master, ");
181 if (info
.is_intercept
)
182 str
.append("Intercept, ");
183 if (info
.is_fallback
)
184 str
.append("Fallback, ");
185 if (info
.is_explicit
)
186 str
.append("Explicit, ");
188 str
.append("Foreign, ");
192 std::string
FormViewEntryAnchor(const GURL
& base_url
,
193 const GURL
& manifest_url
, const GURL
& entry_url
,
196 std::string manifest_url_base64
;
197 std::string entry_url_base64
;
198 std::string response_id_string
;
199 std::string group_id_string
;
200 base::Base64Encode(manifest_url
.spec(), &manifest_url_base64
);
201 base::Base64Encode(entry_url
.spec(), &entry_url_base64
);
202 response_id_string
= base::Int64ToString(response_id
);
203 group_id_string
= base::Int64ToString(group_id
);
205 std::string
query(kViewEntryCommand
);
206 query
.push_back('=');
207 query
.append(manifest_url_base64
);
208 query
.push_back('|');
209 query
.append(entry_url_base64
);
210 query
.push_back('|');
211 query
.append(response_id_string
);
212 query
.push_back('|');
213 query
.append(group_id_string
);
215 GURL::Replacements replacements
;
216 replacements
.SetQuery(query
.data(), url::Component(0, query
.length()));
217 GURL view_entry_url
= base_url
.ReplaceComponents(replacements
);
220 EmitAnchor(view_entry_url
.spec(), entry_url
.spec(), &anchor
);
224 void EmitAppCacheResourceInfoVector(
225 const GURL
& base_url
,
226 const GURL
& manifest_url
,
227 const AppCacheResourceInfoVector
& resource_infos
,
230 out
->append("<table border='0'>\n");
232 EmitTableData("Flags", false, true, out
);
233 EmitTableData("URL", false, true, out
);
234 EmitTableData("Size (headers and data)", true, true, out
);
235 out
->append("</tr>\n");
236 for (AppCacheResourceInfoVector::const_iterator
237 iter
= resource_infos
.begin();
238 iter
!= resource_infos
.end(); ++iter
) {
240 EmitTableData(FormFlagsString(*iter
), false, false, out
);
241 EmitTableData(FormViewEntryAnchor(base_url
, manifest_url
,
242 iter
->url
, iter
->response_id
,
245 EmitTableData(base::UTF16ToUTF8(base::FormatBytesUnlocalized(iter
->size
)),
247 out
->append("</tr>\n");
249 out
->append("</table>\n");
252 void EmitResponseHeaders(net::HttpResponseHeaders
* headers
, std::string
* out
) {
253 out
->append("<hr><pre>");
254 out
->append(net::EscapeForHTML(headers
->GetStatusLine()));
255 out
->push_back('\n');
258 std::string name
, value
;
259 while (headers
->EnumerateHeaderLines(&iter
, &name
, &value
)) {
260 out
->append(net::EscapeForHTML(name
));
262 out
->append(net::EscapeForHTML(value
));
263 out
->push_back('\n');
265 out
->append("</pre>");
268 void EmitHexDump(const char *buf
, size_t buf_len
, size_t total_len
,
270 out
->append("<hr><pre>");
271 base::StringAppendF(out
, "Showing %d of %d bytes\n\n",
272 static_cast<int>(buf_len
), static_cast<int>(total_len
));
273 net::ViewCacheHelper::HexDump(buf
, buf_len
, out
);
274 if (buf_len
< total_len
)
275 out
->append("\nNote: data is truncated...");
276 out
->append("</pre>");
279 GURL
DecodeBase64URL(base::StringPiece base64
) {
281 base::Base64Decode(base64
, &url
);
285 bool ParseQuery(const std::string
& query
,
286 std::string
* command
, std::string
* value
) {
287 size_t position
= query
.find("=");
288 if (position
== std::string::npos
)
290 *command
= query
.substr(0, position
);
291 *value
= query
.substr(position
+ 1);
292 return !command
->empty() && !value
->empty();
295 bool SortByManifestUrl(const AppCacheInfo
& lhs
,
296 const AppCacheInfo
& rhs
) {
297 return lhs
.manifest_url
.spec() < rhs
.manifest_url
.spec();
300 bool SortByResourceUrl(const AppCacheResourceInfo
& lhs
,
301 const AppCacheResourceInfo
& rhs
) {
302 return lhs
.url
.spec() < rhs
.url
.spec();
305 GURL
ClearQuery(const GURL
& url
) {
306 GURL::Replacements replacements
;
307 replacements
.ClearQuery();
308 return url
.ReplaceComponents(replacements
);
311 // Simple base class for the job subclasses defined here.
312 class BaseInternalsJob
: public net::URLRequestSimpleJob
,
313 public AppCacheServiceImpl::Observer
{
315 BaseInternalsJob(net::URLRequest
* request
,
316 net::NetworkDelegate
* network_delegate
,
317 AppCacheServiceImpl
* service
)
318 : URLRequestSimpleJob(request
, network_delegate
),
319 appcache_service_(service
),
320 appcache_storage_(service
->storage()) {
321 appcache_service_
->AddObserver(this);
324 ~BaseInternalsJob() override
{ appcache_service_
->RemoveObserver(this); }
326 void OnServiceReinitialized(
327 AppCacheStorageReference
* old_storage_ref
) override
{
328 if (old_storage_ref
->storage() == appcache_storage_
)
329 disabled_storage_reference_
= old_storage_ref
;
332 AppCacheServiceImpl
* appcache_service_
;
333 AppCacheStorage
* appcache_storage_
;
334 scoped_refptr
<AppCacheStorageReference
> disabled_storage_reference_
;
337 // Job that lists all appcaches in the system.
338 class MainPageJob
: public BaseInternalsJob
{
340 MainPageJob(net::URLRequest
* request
,
341 net::NetworkDelegate
* network_delegate
,
342 AppCacheServiceImpl
* service
)
343 : BaseInternalsJob(request
, network_delegate
, service
),
344 weak_factory_(this) {
347 void Start() override
{
349 info_collection_
= new AppCacheInfoCollection
;
350 appcache_service_
->GetAllAppCacheInfo(
351 info_collection_
.get(),
352 base::Bind(&MainPageJob::OnGotInfoComplete
,
353 weak_factory_
.GetWeakPtr()));
356 // Produces a page containing the listing
357 int GetData(std::string
* mime_type
,
358 std::string
* charset
,
360 const net::CompletionCallback
& callback
) const override
{
361 mime_type
->assign("text/html");
362 charset
->assign("UTF-8");
366 if (!info_collection_
.get()) {
367 out
->append(kErrorMessage
);
368 } else if (info_collection_
->infos_by_origin
.empty()) {
369 out
->append(kEmptyAppCachesMessage
);
371 typedef std::map
<GURL
, AppCacheInfoVector
> InfoByOrigin
;
372 AppCacheInfoVector appcaches
;
373 for (InfoByOrigin::const_iterator origin
=
374 info_collection_
->infos_by_origin
.begin();
375 origin
!= info_collection_
->infos_by_origin
.end(); ++origin
) {
376 appcaches
.insert(appcaches
.end(),
377 origin
->second
.begin(), origin
->second
.end());
379 std::sort(appcaches
.begin(), appcaches
.end(), SortByManifestUrl
);
381 GURL base_url
= ClearQuery(request_
->url());
382 EmitAppCacheInfoVector(base_url
, appcache_service_
, appcaches
, out
);
389 ~MainPageJob() override
{}
391 void OnGotInfoComplete(int rv
) {
393 info_collection_
= NULL
;
397 scoped_refptr
<AppCacheInfoCollection
> info_collection_
;
398 base::WeakPtrFactory
<MainPageJob
> weak_factory_
;
399 DISALLOW_COPY_AND_ASSIGN(MainPageJob
);
402 // Job that redirects back to the main appcache internals page.
403 class RedirectToMainPageJob
: public BaseInternalsJob
{
405 RedirectToMainPageJob(net::URLRequest
* request
,
406 net::NetworkDelegate
* network_delegate
,
407 AppCacheServiceImpl
* service
)
408 : BaseInternalsJob(request
, network_delegate
, service
) {}
410 int GetData(std::string
* mime_type
,
411 std::string
* charset
,
413 const net::CompletionCallback
& callback
) const override
{
414 return net::OK
; // IsRedirectResponse induces a redirect.
417 bool IsRedirectResponse(GURL
* location
, int* http_status_code
) override
{
418 *location
= ClearQuery(request_
->url());
419 *http_status_code
= 307;
424 ~RedirectToMainPageJob() override
{}
427 // Job that removes an appcache and then redirects back to the main page.
428 class RemoveAppCacheJob
: public RedirectToMainPageJob
{
431 net::URLRequest
* request
,
432 net::NetworkDelegate
* network_delegate
,
433 AppCacheServiceImpl
* service
,
434 const GURL
& manifest_url
)
435 : RedirectToMainPageJob(request
, network_delegate
, service
),
436 manifest_url_(manifest_url
),
437 weak_factory_(this) {
440 void Start() override
{
443 appcache_service_
->DeleteAppCacheGroup(
444 manifest_url_
,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete
,
445 weak_factory_
.GetWeakPtr()));
449 ~RemoveAppCacheJob() override
{}
451 void OnDeleteAppCacheComplete(int rv
) {
452 StartAsync(); // Causes the base class to redirect.
456 base::WeakPtrFactory
<RemoveAppCacheJob
> weak_factory_
;
460 // Job shows the details of a particular manifest url.
461 class ViewAppCacheJob
: public BaseInternalsJob
,
462 public AppCacheStorage::Delegate
{
465 net::URLRequest
* request
,
466 net::NetworkDelegate
* network_delegate
,
467 AppCacheServiceImpl
* service
,
468 const GURL
& manifest_url
)
469 : BaseInternalsJob(request
, network_delegate
, service
),
470 manifest_url_(manifest_url
) {}
472 void Start() override
{
474 appcache_storage_
->LoadOrCreateGroup(manifest_url_
, this);
477 // Produces a page containing the entries listing.
478 int GetData(std::string
* mime_type
,
479 std::string
* charset
,
481 const net::CompletionCallback
& callback
) const override
{
482 mime_type
->assign("text/html");
483 charset
->assign("UTF-8");
486 if (appcache_info_
.manifest_url
.is_empty()) {
487 out
->append(kManifestNotFoundMessage
);
489 GURL base_url
= ClearQuery(request_
->url());
490 EmitAppCacheInfo(base_url
, appcache_service_
, &appcache_info_
, out
);
491 EmitAppCacheResourceInfoVector(base_url
,
494 appcache_info_
.group_id
,
502 ~ViewAppCacheJob() override
{
503 appcache_storage_
->CancelDelegateCallbacks(this);
506 // AppCacheStorage::Delegate override
507 void OnGroupLoaded(AppCacheGroup
* group
, const GURL
& manifest_url
) override
{
508 DCHECK_EQ(manifest_url_
, manifest_url
);
509 if (group
&& group
->newest_complete_cache()) {
510 appcache_info_
.manifest_url
= manifest_url
;
511 appcache_info_
.group_id
= group
->group_id();
512 appcache_info_
.size
= group
->newest_complete_cache()->cache_size();
513 appcache_info_
.creation_time
= group
->creation_time();
514 appcache_info_
.last_update_time
=
515 group
->newest_complete_cache()->update_time();
516 appcache_info_
.last_access_time
= base::Time::Now();
517 group
->newest_complete_cache()->ToResourceInfoVector(&resource_infos_
);
518 std::sort(resource_infos_
.begin(), resource_infos_
.end(),
525 AppCacheInfo appcache_info_
;
526 AppCacheResourceInfoVector resource_infos_
;
527 DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob
);
530 // Job that shows the details of a particular cached resource.
531 class ViewEntryJob
: public BaseInternalsJob
,
532 public AppCacheStorage::Delegate
{
535 net::URLRequest
* request
,
536 net::NetworkDelegate
* network_delegate
,
537 AppCacheServiceImpl
* service
,
538 const GURL
& manifest_url
,
539 const GURL
& entry_url
,
540 int64 response_id
, int64 group_id
)
541 : BaseInternalsJob(request
, network_delegate
, service
),
542 manifest_url_(manifest_url
), entry_url_(entry_url
),
543 response_id_(response_id
), group_id_(group_id
), amount_read_(0) {
546 void Start() override
{
548 appcache_storage_
->LoadResponseInfo(
549 manifest_url_
, group_id_
, response_id_
, this);
552 // Produces a page containing the response headers and data.
553 int GetData(std::string
* mime_type
,
554 std::string
* charset
,
556 const net::CompletionCallback
& callback
) const override
{
557 mime_type
->assign("text/html");
558 charset
->assign("UTF-8");
561 EmitAnchor(entry_url_
.spec(), entry_url_
.spec(), out
);
562 out
->append("<br/>\n");
563 if (response_info_
.get()) {
564 if (response_info_
->http_response_info())
565 EmitResponseHeaders(response_info_
->http_response_info()->headers
.get(),
568 out
->append("Failed to read response headers.<br>");
570 if (response_data_
.get()) {
571 EmitHexDump(response_data_
->data(),
573 response_info_
->response_data_size(),
576 out
->append("Failed to read response data.<br>");
579 out
->append("Failed to read response headers and data.<br>");
586 ~ViewEntryJob() override
{ appcache_storage_
->CancelDelegateCallbacks(this); }
588 void OnResponseInfoLoaded(AppCacheResponseInfo
* response_info
,
589 int64 response_id
) override
{
590 if (!response_info
) {
594 response_info_
= response_info
;
596 // Read the response data, truncating if its too large.
597 const int64 kLimit
= 100 * 1000;
598 int64 amount_to_read
=
599 std::min(kLimit
, response_info
->response_data_size());
600 response_data_
= new net::IOBuffer(
601 base::CheckedNumeric
<size_t>(amount_to_read
).ValueOrDie());
603 reader_
.reset(appcache_storage_
->CreateResponseReader(
604 manifest_url_
, group_id_
, response_id_
));
606 response_data_
.get(),
608 base::Bind(&ViewEntryJob::OnReadComplete
, base::Unretained(this)));
611 void OnReadComplete(int result
) {
613 amount_read_
= result
;
615 response_data_
= NULL
;
623 scoped_refptr
<AppCacheResponseInfo
> response_info_
;
624 scoped_refptr
<net::IOBuffer
> response_data_
;
626 scoped_ptr
<AppCacheResponseReader
> reader_
;
631 net::URLRequestJob
* ViewAppCacheInternalsJobFactory::CreateJobForRequest(
632 net::URLRequest
* request
,
633 net::NetworkDelegate
* network_delegate
,
634 AppCacheServiceImpl
* service
) {
635 if (!request
->url().has_query())
636 return new MainPageJob(request
, network_delegate
, service
);
640 ParseQuery(request
->url().query(), &command
, ¶m
);
642 if (command
== kRemoveCacheCommand
)
643 return new RemoveAppCacheJob(request
, network_delegate
, service
,
644 DecodeBase64URL(param
));
646 if (command
== kViewCacheCommand
)
647 return new ViewAppCacheJob(request
, network_delegate
, service
,
648 DecodeBase64URL(param
));
650 if (command
== kViewEntryCommand
) {
651 std::vector
<base::StringPiece
> tokens
= base::SplitStringPiece(
652 param
, "|", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
653 int64 response_id
= 0;
655 if (tokens
.size() == 4u &&
656 base::StringToInt64(tokens
[2], &response_id
) &&
657 base::StringToInt64(tokens
[3], &group_id
)) {
658 return new ViewEntryJob(request
, network_delegate
, service
,
659 DecodeBase64URL(tokens
[0]), // manifest url
660 DecodeBase64URL(tokens
[1]), // entry url
661 response_id
, group_id
);
665 return new RedirectToMainPageJob(request
, network_delegate
, service
);
668 } // namespace content