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/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "net/base/escape.h"
21 #include "net/base/io_buffer.h"
22 #include "net/base/net_errors.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/url_request/url_request.h"
25 #include "net/url_request/url_request_simple_job.h"
26 #include "net/url_request/view_cache_helper.h"
27 #include "webkit/browser/appcache/appcache.h"
28 #include "webkit/browser/appcache/appcache_group.h"
29 #include "webkit/browser/appcache/appcache_policy.h"
30 #include "webkit/browser/appcache/appcache_response.h"
31 #include "webkit/browser/appcache/appcache_service.h"
32 #include "webkit/browser/appcache/appcache_storage.h"
34 using appcache::AppCacheGroup
;
35 using appcache::AppCacheInfo
;
36 using appcache::AppCacheInfoCollection
;
37 using appcache::AppCacheInfoVector
;
38 using appcache::AppCacheService
;
39 using appcache::AppCacheStorage
;
40 using appcache::AppCacheStorageReference
;
41 using appcache::AppCacheResourceInfo
;
42 using appcache::AppCacheResourceInfoVector
;
43 using appcache::AppCacheResponseInfo
;
44 using appcache::AppCacheResponseReader
;
49 const char kErrorMessage
[] = "Error in retrieving Application Caches.";
50 const char kEmptyAppCachesMessage
[] = "No available Application Caches.";
51 const char kManifestNotFoundMessage
[] = "Manifest not found.";
52 const char kManifest
[] = "Manifest: ";
53 const char kSize
[] = "Size: ";
54 const char kCreationTime
[] = "Creation Time: ";
55 const char kLastAccessTime
[] = "Last Access Time: ";
56 const char kLastUpdateTime
[] = "Last Update Time: ";
57 const char kFormattedDisabledAppCacheMsg
[] =
58 "<b><i><font color=\"FF0000\">"
59 "This Application Cache is disabled by policy.</font></i></b><br/>";
60 const char kRemoveCacheLabel
[] = "Remove";
61 const char kViewCacheLabel
[] = "View Entries";
62 const char kRemoveCacheCommand
[] = "remove-cache";
63 const char kViewCacheCommand
[] = "view-cache";
64 const char kViewEntryCommand
[] = "view-entry";
66 void EmitPageStart(std::string
* out
) {
69 "<html><title>AppCache Internals</title>\n"
70 "<meta http-equiv=\"Content-Security-Policy\""
71 " content=\"object-src 'none'; script-src 'none'\">\n"
73 "body { font-family: sans-serif; font-size: 0.8em; }\n"
74 "tt, code, pre { font-family: WebKitHack, monospace; }\n"
75 "form { display: inline; }\n"
76 ".subsection_body { margin: 10px 0 10px 2em; }\n"
77 ".subsection_title { font-weight: bold; }\n"
82 void EmitPageEnd(std::string
* out
) {
83 out
->append("</body></html>\n");
86 void EmitListItem(const std::string
& label
,
87 const std::string
& data
,
90 out
->append(net::EscapeForHTML(label
));
91 out
->append(net::EscapeForHTML(data
));
92 out
->append("</li>\n");
95 void EmitAnchor(const std::string
& url
, const std::string
& text
,
97 out
->append("<a href=\"");
98 out
->append(net::EscapeForHTML(url
));
100 out
->append(net::EscapeForHTML(text
));
104 void EmitCommandAnchor(const char* label
,
105 const GURL
& base_url
,
109 std::string
query(command
);
110 query
.push_back('=');
112 GURL::Replacements replacements
;
113 replacements
.SetQuery(query
.data(), url::Component(0, query
.length()));
114 GURL command_url
= base_url
.ReplaceComponents(replacements
);
115 EmitAnchor(command_url
.spec(), label
, out
);
118 void EmitAppCacheInfo(const GURL
& base_url
,
119 AppCacheService
* service
,
120 const AppCacheInfo
* info
,
122 std::string manifest_url_base64
;
123 base::Base64Encode(info
->manifest_url
.spec(), &manifest_url_base64
);
125 out
->append("\n<p>");
126 out
->append(kManifest
);
127 EmitAnchor(info
->manifest_url
.spec(), info
->manifest_url
.spec(), out
);
128 out
->append("<br/>\n");
129 if (!service
->appcache_policy()->CanLoadAppCache(
130 info
->manifest_url
, info
->manifest_url
)) {
131 out
->append(kFormattedDisabledAppCacheMsg
);
133 out
->append("\n<br/>\n");
134 EmitCommandAnchor(kRemoveCacheLabel
, base_url
,
135 kRemoveCacheCommand
, manifest_url_base64
.c_str(), out
);
136 out
->append(" ");
137 EmitCommandAnchor(kViewCacheLabel
, base_url
,
138 kViewCacheCommand
, manifest_url_base64
.c_str(), out
);
139 out
->append("\n<br/>\n");
143 base::UTF16ToUTF8(FormatBytesUnlocalized(info
->size
)),
147 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info
->creation_time
)),
151 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info
->last_update_time
)),
155 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info
->last_access_time
)),
157 out
->append("</ul></p></br>\n");
160 void EmitAppCacheInfoVector(
161 const GURL
& base_url
,
162 AppCacheService
* service
,
163 const AppCacheInfoVector
& appcaches
,
165 for (std::vector
<AppCacheInfo
>::const_iterator info
=
167 info
!= appcaches
.end(); ++info
) {
168 EmitAppCacheInfo(base_url
, service
, &(*info
), out
);
172 void EmitTableData(const std::string
& data
, bool align_right
, bool bold
,
175 out
->append("<td align='right'>");
183 out
->append("</td>");
186 std::string
FormFlagsString(const AppCacheResourceInfo
& info
) {
188 if (info
.is_manifest
)
189 str
.append("Manifest, ");
191 str
.append("Master, ");
192 if (info
.is_intercept
)
193 str
.append("Intercept, ");
194 if (info
.is_fallback
)
195 str
.append("Fallback, ");
196 if (info
.is_explicit
)
197 str
.append("Explicit, ");
199 str
.append("Foreign, ");
203 std::string
FormViewEntryAnchor(const GURL
& base_url
,
204 const GURL
& manifest_url
, const GURL
& entry_url
,
207 std::string manifest_url_base64
;
208 std::string entry_url_base64
;
209 std::string response_id_string
;
210 std::string group_id_string
;
211 base::Base64Encode(manifest_url
.spec(), &manifest_url_base64
);
212 base::Base64Encode(entry_url
.spec(), &entry_url_base64
);
213 response_id_string
= base::Int64ToString(response_id
);
214 group_id_string
= base::Int64ToString(group_id
);
216 std::string
query(kViewEntryCommand
);
217 query
.push_back('=');
218 query
.append(manifest_url_base64
);
219 query
.push_back('|');
220 query
.append(entry_url_base64
);
221 query
.push_back('|');
222 query
.append(response_id_string
);
223 query
.push_back('|');
224 query
.append(group_id_string
);
226 GURL::Replacements replacements
;
227 replacements
.SetQuery(query
.data(), url::Component(0, query
.length()));
228 GURL view_entry_url
= base_url
.ReplaceComponents(replacements
);
231 EmitAnchor(view_entry_url
.spec(), entry_url
.spec(), &anchor
);
235 void EmitAppCacheResourceInfoVector(
236 const GURL
& base_url
,
237 const GURL
& manifest_url
,
238 const AppCacheResourceInfoVector
& resource_infos
,
241 out
->append("<table border='0'>\n");
243 EmitTableData("Flags", false, true, out
);
244 EmitTableData("URL", false, true, out
);
245 EmitTableData("Size (headers and data)", true, true, out
);
246 out
->append("</tr>\n");
247 for (AppCacheResourceInfoVector::const_iterator
248 iter
= resource_infos
.begin();
249 iter
!= resource_infos
.end(); ++iter
) {
251 EmitTableData(FormFlagsString(*iter
), false, false, out
);
252 EmitTableData(FormViewEntryAnchor(base_url
, manifest_url
,
253 iter
->url
, iter
->response_id
,
256 EmitTableData(base::UTF16ToUTF8(FormatBytesUnlocalized(iter
->size
)),
258 out
->append("</tr>\n");
260 out
->append("</table>\n");
263 void EmitResponseHeaders(net::HttpResponseHeaders
* headers
, std::string
* out
) {
264 out
->append("<hr><pre>");
265 out
->append(net::EscapeForHTML(headers
->GetStatusLine()));
266 out
->push_back('\n');
269 std::string name
, value
;
270 while (headers
->EnumerateHeaderLines(&iter
, &name
, &value
)) {
271 out
->append(net::EscapeForHTML(name
));
273 out
->append(net::EscapeForHTML(value
));
274 out
->push_back('\n');
276 out
->append("</pre>");
279 void EmitHexDump(const char *buf
, size_t buf_len
, size_t total_len
,
281 out
->append("<hr><pre>");
282 base::StringAppendF(out
, "Showing %d of %d bytes\n\n",
283 static_cast<int>(buf_len
), static_cast<int>(total_len
));
284 net::ViewCacheHelper::HexDump(buf
, buf_len
, out
);
285 if (buf_len
< total_len
)
286 out
->append("\nNote: data is truncated...");
287 out
->append("</pre>");
290 GURL
DecodeBase64URL(const std::string
& base64
) {
292 base::Base64Decode(base64
, &url
);
296 bool ParseQuery(const std::string
& query
,
297 std::string
* command
, std::string
* value
) {
298 size_t position
= query
.find("=");
299 if (position
== std::string::npos
)
301 *command
= query
.substr(0, position
);
302 *value
= query
.substr(position
+ 1);
303 return !command
->empty() && !value
->empty();
306 bool SortByManifestUrl(const AppCacheInfo
& lhs
,
307 const AppCacheInfo
& rhs
) {
308 return lhs
.manifest_url
.spec() < rhs
.manifest_url
.spec();
311 bool SortByResourceUrl(const AppCacheResourceInfo
& lhs
,
312 const AppCacheResourceInfo
& rhs
) {
313 return lhs
.url
.spec() < rhs
.url
.spec();
316 GURL
ClearQuery(const GURL
& url
) {
317 GURL::Replacements replacements
;
318 replacements
.ClearQuery();
319 return url
.ReplaceComponents(replacements
);
322 // Simple base class for the job subclasses defined here.
323 class BaseInternalsJob
: public net::URLRequestSimpleJob
,
324 public AppCacheService::Observer
{
326 BaseInternalsJob(net::URLRequest
* request
,
327 net::NetworkDelegate
* network_delegate
,
328 AppCacheService
* service
)
329 : URLRequestSimpleJob(request
, network_delegate
),
330 appcache_service_(service
),
331 appcache_storage_(service
->storage()) {
332 appcache_service_
->AddObserver(this);
335 virtual ~BaseInternalsJob() {
336 appcache_service_
->RemoveObserver(this);
339 virtual void OnServiceReinitialized(
340 AppCacheStorageReference
* old_storage_ref
) OVERRIDE
{
341 if (old_storage_ref
->storage() == appcache_storage_
)
342 disabled_storage_reference_
= old_storage_ref
;
345 AppCacheService
* appcache_service_
;
346 AppCacheStorage
* appcache_storage_
;
347 scoped_refptr
<AppCacheStorageReference
> disabled_storage_reference_
;
350 // Job that lists all appcaches in the system.
351 class MainPageJob
: public BaseInternalsJob
{
353 MainPageJob(net::URLRequest
* request
,
354 net::NetworkDelegate
* network_delegate
,
355 AppCacheService
* service
)
356 : BaseInternalsJob(request
, network_delegate
, service
),
357 weak_factory_(this) {
360 virtual void Start() OVERRIDE
{
362 info_collection_
= new AppCacheInfoCollection
;
363 appcache_service_
->GetAllAppCacheInfo(
364 info_collection_
.get(),
365 base::Bind(&MainPageJob::OnGotInfoComplete
,
366 weak_factory_
.GetWeakPtr()));
369 // Produces a page containing the listing
370 virtual int GetData(std::string
* mime_type
,
371 std::string
* charset
,
373 const net::CompletionCallback
& callback
) const OVERRIDE
{
374 mime_type
->assign("text/html");
375 charset
->assign("UTF-8");
379 if (!info_collection_
.get()) {
380 out
->append(kErrorMessage
);
381 } else if (info_collection_
->infos_by_origin
.empty()) {
382 out
->append(kEmptyAppCachesMessage
);
384 typedef std::map
<GURL
, AppCacheInfoVector
> InfoByOrigin
;
385 AppCacheInfoVector appcaches
;
386 for (InfoByOrigin::const_iterator origin
=
387 info_collection_
->infos_by_origin
.begin();
388 origin
!= info_collection_
->infos_by_origin
.end(); ++origin
) {
389 appcaches
.insert(appcaches
.end(),
390 origin
->second
.begin(), origin
->second
.end());
392 std::sort(appcaches
.begin(), appcaches
.end(), SortByManifestUrl
);
394 GURL base_url
= ClearQuery(request_
->url());
395 EmitAppCacheInfoVector(base_url
, appcache_service_
, appcaches
, out
);
402 virtual ~MainPageJob() {}
404 void OnGotInfoComplete(int rv
) {
406 info_collection_
= NULL
;
410 scoped_refptr
<AppCacheInfoCollection
> info_collection_
;
411 base::WeakPtrFactory
<MainPageJob
> weak_factory_
;
412 DISALLOW_COPY_AND_ASSIGN(MainPageJob
);
415 // Job that redirects back to the main appcache internals page.
416 class RedirectToMainPageJob
: public BaseInternalsJob
{
418 RedirectToMainPageJob(net::URLRequest
* request
,
419 net::NetworkDelegate
* network_delegate
,
420 AppCacheService
* service
)
421 : BaseInternalsJob(request
, network_delegate
, service
) {}
423 virtual int GetData(std::string
* mime_type
,
424 std::string
* charset
,
426 const net::CompletionCallback
& callback
) const OVERRIDE
{
427 return net::OK
; // IsRedirectResponse induces a redirect.
430 virtual bool IsRedirectResponse(GURL
* location
,
431 int* http_status_code
) OVERRIDE
{
432 *location
= ClearQuery(request_
->url());
433 *http_status_code
= 307;
438 virtual ~RedirectToMainPageJob() {}
441 // Job that removes an appcache and then redirects back to the main page.
442 class RemoveAppCacheJob
: public RedirectToMainPageJob
{
445 net::URLRequest
* request
,
446 net::NetworkDelegate
* network_delegate
,
447 AppCacheService
* service
,
448 const GURL
& manifest_url
)
449 : RedirectToMainPageJob(request
, network_delegate
, service
),
450 manifest_url_(manifest_url
),
451 weak_factory_(this) {
454 virtual void Start() OVERRIDE
{
457 appcache_service_
->DeleteAppCacheGroup(
458 manifest_url_
,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete
,
459 weak_factory_
.GetWeakPtr()));
463 virtual ~RemoveAppCacheJob() {}
465 void OnDeleteAppCacheComplete(int rv
) {
466 StartAsync(); // Causes the base class to redirect.
470 base::WeakPtrFactory
<RemoveAppCacheJob
> weak_factory_
;
474 // Job shows the details of a particular manifest url.
475 class ViewAppCacheJob
: public BaseInternalsJob
,
476 public AppCacheStorage::Delegate
{
479 net::URLRequest
* request
,
480 net::NetworkDelegate
* network_delegate
,
481 AppCacheService
* service
,
482 const GURL
& manifest_url
)
483 : BaseInternalsJob(request
, network_delegate
, service
),
484 manifest_url_(manifest_url
) {}
486 virtual void Start() OVERRIDE
{
488 appcache_storage_
->LoadOrCreateGroup(manifest_url_
, this);
491 // Produces a page containing the entries listing.
492 virtual int GetData(std::string
* mime_type
,
493 std::string
* charset
,
495 const net::CompletionCallback
& callback
) const OVERRIDE
{
496 mime_type
->assign("text/html");
497 charset
->assign("UTF-8");
500 if (appcache_info_
.manifest_url
.is_empty()) {
501 out
->append(kManifestNotFoundMessage
);
503 GURL base_url
= ClearQuery(request_
->url());
504 EmitAppCacheInfo(base_url
, appcache_service_
, &appcache_info_
, out
);
505 EmitAppCacheResourceInfoVector(base_url
,
508 appcache_info_
.group_id
,
516 virtual ~ViewAppCacheJob() {
517 appcache_storage_
->CancelDelegateCallbacks(this);
520 // AppCacheStorage::Delegate override
521 virtual void OnGroupLoaded(
522 AppCacheGroup
* group
, const GURL
& manifest_url
) OVERRIDE
{
523 DCHECK_EQ(manifest_url_
, manifest_url
);
524 if (group
&& group
->newest_complete_cache()) {
525 appcache_info_
.manifest_url
= manifest_url
;
526 appcache_info_
.group_id
= group
->group_id();
527 appcache_info_
.size
= group
->newest_complete_cache()->cache_size();
528 appcache_info_
.creation_time
= group
->creation_time();
529 appcache_info_
.last_update_time
=
530 group
->newest_complete_cache()->update_time();
531 appcache_info_
.last_access_time
= base::Time::Now();
532 group
->newest_complete_cache()->ToResourceInfoVector(&resource_infos_
);
533 std::sort(resource_infos_
.begin(), resource_infos_
.end(),
540 AppCacheInfo appcache_info_
;
541 AppCacheResourceInfoVector resource_infos_
;
542 DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob
);
545 // Job that shows the details of a particular cached resource.
546 class ViewEntryJob
: public BaseInternalsJob
,
547 public AppCacheStorage::Delegate
{
550 net::URLRequest
* request
,
551 net::NetworkDelegate
* network_delegate
,
552 AppCacheService
* service
,
553 const GURL
& manifest_url
,
554 const GURL
& entry_url
,
555 int64 response_id
, int64 group_id
)
556 : BaseInternalsJob(request
, network_delegate
, service
),
557 manifest_url_(manifest_url
), entry_url_(entry_url
),
558 response_id_(response_id
), group_id_(group_id
), amount_read_(0) {
561 virtual void Start() OVERRIDE
{
563 appcache_storage_
->LoadResponseInfo(
564 manifest_url_
, group_id_
, response_id_
, this);
567 // Produces a page containing the response headers and data.
568 virtual int GetData(std::string
* mime_type
,
569 std::string
* charset
,
571 const net::CompletionCallback
& callback
) const OVERRIDE
{
572 mime_type
->assign("text/html");
573 charset
->assign("UTF-8");
576 EmitAnchor(entry_url_
.spec(), entry_url_
.spec(), out
);
577 out
->append("<br/>\n");
578 if (response_info_
.get()) {
579 if (response_info_
->http_response_info())
580 EmitResponseHeaders(response_info_
->http_response_info()->headers
.get(),
583 out
->append("Failed to read response headers.<br>");
585 if (response_data_
.get()) {
586 EmitHexDump(response_data_
->data(),
588 response_info_
->response_data_size(),
591 out
->append("Failed to read response data.<br>");
594 out
->append("Failed to read response headers and data.<br>");
601 virtual ~ViewEntryJob() {
602 appcache_storage_
->CancelDelegateCallbacks(this);
605 virtual void OnResponseInfoLoaded(
606 AppCacheResponseInfo
* response_info
, int64 response_id
) OVERRIDE
{
607 if (!response_info
) {
611 response_info_
= response_info
;
613 // Read the response data, truncating if its too large.
614 const int64 kLimit
= 100 * 1000;
615 int64 amount_to_read
=
616 std::min(kLimit
, response_info
->response_data_size());
617 response_data_
= new net::IOBuffer(amount_to_read
);
619 reader_
.reset(appcache_storage_
->CreateResponseReader(
620 manifest_url_
, group_id_
, response_id_
));
622 response_data_
.get(),
624 base::Bind(&ViewEntryJob::OnReadComplete
, base::Unretained(this)));
627 void OnReadComplete(int result
) {
629 amount_read_
= result
;
631 response_data_
= NULL
;
639 scoped_refptr
<AppCacheResponseInfo
> response_info_
;
640 scoped_refptr
<net::IOBuffer
> response_data_
;
642 scoped_ptr
<AppCacheResponseReader
> reader_
;
647 net::URLRequestJob
* ViewAppCacheInternalsJobFactory::CreateJobForRequest(
648 net::URLRequest
* request
,
649 net::NetworkDelegate
* network_delegate
,
650 AppCacheService
* service
) {
651 if (!request
->url().has_query())
652 return new MainPageJob(request
, network_delegate
, service
);
656 ParseQuery(request
->url().query(), &command
, ¶m
);
658 if (command
== kRemoveCacheCommand
)
659 return new RemoveAppCacheJob(request
, network_delegate
, service
,
660 DecodeBase64URL(param
));
662 if (command
== kViewCacheCommand
)
663 return new ViewAppCacheJob(request
, network_delegate
, service
,
664 DecodeBase64URL(param
));
666 std::vector
<std::string
> tokens
;
669 if (command
== kViewEntryCommand
&& Tokenize(param
, "|", &tokens
) == 4u &&
670 base::StringToInt64(tokens
[2], &response_id
) &&
671 base::StringToInt64(tokens
[3], &group_id
)) {
672 return new ViewEntryJob(request
, network_delegate
, service
,
673 DecodeBase64URL(tokens
[0]), // manifest url
674 DecodeBase64URL(tokens
[1]), // entry url
675 response_id
, group_id
);
678 return new RedirectToMainPageJob(request
, network_delegate
, service
);
681 } // namespace content