Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / appcache / view_appcache_internals_job.cc
blob84912b733d4d1d72a4d0e7d30bffa3e285921ebd
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"
7 #include <algorithm>
8 #include <string>
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 "content/browser/appcache/appcache.h"
21 #include "content/browser/appcache/appcache_group.h"
22 #include "content/browser/appcache/appcache_policy.h"
23 #include "content/browser/appcache/appcache_response.h"
24 #include "content/browser/appcache/appcache_service_impl.h"
25 #include "content/browser/appcache/appcache_storage.h"
26 #include "net/base/escape.h"
27 #include "net/base/io_buffer.h"
28 #include "net/base/net_errors.h"
29 #include "net/http/http_response_headers.h"
30 #include "net/url_request/url_request.h"
31 #include "net/url_request/url_request_simple_job.h"
32 #include "net/url_request/view_cache_helper.h"
34 namespace content {
35 namespace {
37 const char kErrorMessage[] = "Error in retrieving Application Caches.";
38 const char kEmptyAppCachesMessage[] = "No available Application Caches.";
39 const char kManifestNotFoundMessage[] = "Manifest not found.";
40 const char kManifest[] = "Manifest: ";
41 const char kSize[] = "Size: ";
42 const char kCreationTime[] = "Creation Time: ";
43 const char kLastAccessTime[] = "Last Access Time: ";
44 const char kLastUpdateTime[] = "Last Update Time: ";
45 const char kFormattedDisabledAppCacheMsg[] =
46 "<b><i><font color=\"FF0000\">"
47 "This Application Cache is disabled by policy.</font></i></b><br/>";
48 const char kRemoveCacheLabel[] = "Remove";
49 const char kViewCacheLabel[] = "View Entries";
50 const char kRemoveCacheCommand[] = "remove-cache";
51 const char kViewCacheCommand[] = "view-cache";
52 const char kViewEntryCommand[] = "view-entry";
54 void EmitPageStart(std::string* out) {
55 out->append(
56 "<!DOCTYPE HTML>\n"
57 "<html><title>AppCache Internals</title>\n"
58 "<meta http-equiv=\"Content-Security-Policy\""
59 " content=\"object-src 'none'; script-src 'none'\">\n"
60 "<style>\n"
61 "body { font-family: sans-serif; font-size: 0.8em; }\n"
62 "tt, code, pre { font-family: WebKitHack, monospace; }\n"
63 "form { display: inline; }\n"
64 ".subsection_body { margin: 10px 0 10px 2em; }\n"
65 ".subsection_title { font-weight: bold; }\n"
66 "</style>\n"
67 "</head><body>\n");
70 void EmitPageEnd(std::string* out) {
71 out->append("</body></html>\n");
74 void EmitListItem(const std::string& label,
75 const std::string& data,
76 std::string* out) {
77 out->append("<li>");
78 out->append(net::EscapeForHTML(label));
79 out->append(net::EscapeForHTML(data));
80 out->append("</li>\n");
83 void EmitAnchor(const std::string& url, const std::string& text,
84 std::string* out) {
85 out->append("<a href=\"");
86 out->append(net::EscapeForHTML(url));
87 out->append("\">");
88 out->append(net::EscapeForHTML(text));
89 out->append("</a>");
92 void EmitCommandAnchor(const char* label,
93 const GURL& base_url,
94 const char* command,
95 const char* param,
96 std::string* out) {
97 std::string query(command);
98 query.push_back('=');
99 query.append(param);
100 GURL::Replacements replacements;
101 replacements.SetQuery(query.data(), url::Component(0, query.length()));
102 GURL command_url = base_url.ReplaceComponents(replacements);
103 EmitAnchor(command_url.spec(), label, out);
106 void EmitAppCacheInfo(const GURL& base_url,
107 AppCacheServiceImpl* service,
108 const AppCacheInfo* info,
109 std::string* out) {
110 std::string manifest_url_base64;
111 base::Base64Encode(info->manifest_url.spec(), &manifest_url_base64);
113 out->append("\n<p>");
114 out->append(kManifest);
115 EmitAnchor(info->manifest_url.spec(), info->manifest_url.spec(), out);
116 out->append("<br/>\n");
117 if (!service->appcache_policy()->CanLoadAppCache(
118 info->manifest_url, info->manifest_url)) {
119 out->append(kFormattedDisabledAppCacheMsg);
121 out->append("\n<br/>\n");
122 EmitCommandAnchor(kRemoveCacheLabel, base_url,
123 kRemoveCacheCommand, manifest_url_base64.c_str(), out);
124 out->append("&nbsp;&nbsp;");
125 EmitCommandAnchor(kViewCacheLabel, base_url,
126 kViewCacheCommand, manifest_url_base64.c_str(), out);
127 out->append("\n<br/>\n");
128 out->append("<ul>");
129 EmitListItem(
130 kSize,
131 base::UTF16ToUTF8(FormatBytesUnlocalized(info->size)),
132 out);
133 EmitListItem(
134 kCreationTime,
135 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->creation_time)),
136 out);
137 EmitListItem(
138 kLastUpdateTime,
139 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_update_time)),
140 out);
141 EmitListItem(
142 kLastAccessTime,
143 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_access_time)),
144 out);
145 out->append("</ul></p></br>\n");
148 void EmitAppCacheInfoVector(
149 const GURL& base_url,
150 AppCacheServiceImpl* service,
151 const AppCacheInfoVector& appcaches,
152 std::string* out) {
153 for (std::vector<AppCacheInfo>::const_iterator info =
154 appcaches.begin();
155 info != appcaches.end(); ++info) {
156 EmitAppCacheInfo(base_url, service, &(*info), out);
160 void EmitTableData(const std::string& data, bool align_right, bool bold,
161 std::string* out) {
162 if (align_right)
163 out->append("<td align='right'>");
164 else
165 out->append("<td>");
166 if (bold)
167 out->append("<b>");
168 out->append(data);
169 if (bold)
170 out->append("</b>");
171 out->append("</td>");
174 std::string FormFlagsString(const AppCacheResourceInfo& info) {
175 std::string str;
176 if (info.is_manifest)
177 str.append("Manifest, ");
178 if (info.is_master)
179 str.append("Master, ");
180 if (info.is_intercept)
181 str.append("Intercept, ");
182 if (info.is_fallback)
183 str.append("Fallback, ");
184 if (info.is_explicit)
185 str.append("Explicit, ");
186 if (info.is_foreign)
187 str.append("Foreign, ");
188 return str;
191 std::string FormViewEntryAnchor(const GURL& base_url,
192 const GURL& manifest_url, const GURL& entry_url,
193 int64 response_id,
194 int64 group_id) {
195 std::string manifest_url_base64;
196 std::string entry_url_base64;
197 std::string response_id_string;
198 std::string group_id_string;
199 base::Base64Encode(manifest_url.spec(), &manifest_url_base64);
200 base::Base64Encode(entry_url.spec(), &entry_url_base64);
201 response_id_string = base::Int64ToString(response_id);
202 group_id_string = base::Int64ToString(group_id);
204 std::string query(kViewEntryCommand);
205 query.push_back('=');
206 query.append(manifest_url_base64);
207 query.push_back('|');
208 query.append(entry_url_base64);
209 query.push_back('|');
210 query.append(response_id_string);
211 query.push_back('|');
212 query.append(group_id_string);
214 GURL::Replacements replacements;
215 replacements.SetQuery(query.data(), url::Component(0, query.length()));
216 GURL view_entry_url = base_url.ReplaceComponents(replacements);
218 std::string anchor;
219 EmitAnchor(view_entry_url.spec(), entry_url.spec(), &anchor);
220 return anchor;
223 void EmitAppCacheResourceInfoVector(
224 const GURL& base_url,
225 const GURL& manifest_url,
226 const AppCacheResourceInfoVector& resource_infos,
227 int64 group_id,
228 std::string* out) {
229 out->append("<table border='0'>\n");
230 out->append("<tr>");
231 EmitTableData("Flags", false, true, out);
232 EmitTableData("URL", false, true, out);
233 EmitTableData("Size (headers and data)", true, true, out);
234 out->append("</tr>\n");
235 for (AppCacheResourceInfoVector::const_iterator
236 iter = resource_infos.begin();
237 iter != resource_infos.end(); ++iter) {
238 out->append("<tr>");
239 EmitTableData(FormFlagsString(*iter), false, false, out);
240 EmitTableData(FormViewEntryAnchor(base_url, manifest_url,
241 iter->url, iter->response_id,
242 group_id),
243 false, false, out);
244 EmitTableData(base::UTF16ToUTF8(FormatBytesUnlocalized(iter->size)),
245 true, false, out);
246 out->append("</tr>\n");
248 out->append("</table>\n");
251 void EmitResponseHeaders(net::HttpResponseHeaders* headers, std::string* out) {
252 out->append("<hr><pre>");
253 out->append(net::EscapeForHTML(headers->GetStatusLine()));
254 out->push_back('\n');
256 void* iter = NULL;
257 std::string name, value;
258 while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
259 out->append(net::EscapeForHTML(name));
260 out->append(": ");
261 out->append(net::EscapeForHTML(value));
262 out->push_back('\n');
264 out->append("</pre>");
267 void EmitHexDump(const char *buf, size_t buf_len, size_t total_len,
268 std::string* out) {
269 out->append("<hr><pre>");
270 base::StringAppendF(out, "Showing %d of %d bytes\n\n",
271 static_cast<int>(buf_len), static_cast<int>(total_len));
272 net::ViewCacheHelper::HexDump(buf, buf_len, out);
273 if (buf_len < total_len)
274 out->append("\nNote: data is truncated...");
275 out->append("</pre>");
278 GURL DecodeBase64URL(const std::string& base64) {
279 std::string url;
280 base::Base64Decode(base64, &url);
281 return GURL(url);
284 bool ParseQuery(const std::string& query,
285 std::string* command, std::string* value) {
286 size_t position = query.find("=");
287 if (position == std::string::npos)
288 return false;
289 *command = query.substr(0, position);
290 *value = query.substr(position + 1);
291 return !command->empty() && !value->empty();
294 bool SortByManifestUrl(const AppCacheInfo& lhs,
295 const AppCacheInfo& rhs) {
296 return lhs.manifest_url.spec() < rhs.manifest_url.spec();
299 bool SortByResourceUrl(const AppCacheResourceInfo& lhs,
300 const AppCacheResourceInfo& rhs) {
301 return lhs.url.spec() < rhs.url.spec();
304 GURL ClearQuery(const GURL& url) {
305 GURL::Replacements replacements;
306 replacements.ClearQuery();
307 return url.ReplaceComponents(replacements);
310 // Simple base class for the job subclasses defined here.
311 class BaseInternalsJob : public net::URLRequestSimpleJob,
312 public AppCacheServiceImpl::Observer {
313 protected:
314 BaseInternalsJob(net::URLRequest* request,
315 net::NetworkDelegate* network_delegate,
316 AppCacheServiceImpl* service)
317 : URLRequestSimpleJob(request, network_delegate),
318 appcache_service_(service),
319 appcache_storage_(service->storage()) {
320 appcache_service_->AddObserver(this);
323 ~BaseInternalsJob() override { appcache_service_->RemoveObserver(this); }
325 void OnServiceReinitialized(
326 AppCacheStorageReference* old_storage_ref) override {
327 if (old_storage_ref->storage() == appcache_storage_)
328 disabled_storage_reference_ = old_storage_ref;
331 AppCacheServiceImpl* appcache_service_;
332 AppCacheStorage* appcache_storage_;
333 scoped_refptr<AppCacheStorageReference> disabled_storage_reference_;
336 // Job that lists all appcaches in the system.
337 class MainPageJob : public BaseInternalsJob {
338 public:
339 MainPageJob(net::URLRequest* request,
340 net::NetworkDelegate* network_delegate,
341 AppCacheServiceImpl* service)
342 : BaseInternalsJob(request, network_delegate, service),
343 weak_factory_(this) {
346 void Start() override {
347 DCHECK(request_);
348 info_collection_ = new AppCacheInfoCollection;
349 appcache_service_->GetAllAppCacheInfo(
350 info_collection_.get(),
351 base::Bind(&MainPageJob::OnGotInfoComplete,
352 weak_factory_.GetWeakPtr()));
355 // Produces a page containing the listing
356 int GetData(std::string* mime_type,
357 std::string* charset,
358 std::string* out,
359 const net::CompletionCallback& callback) const override {
360 mime_type->assign("text/html");
361 charset->assign("UTF-8");
363 out->clear();
364 EmitPageStart(out);
365 if (!info_collection_.get()) {
366 out->append(kErrorMessage);
367 } else if (info_collection_->infos_by_origin.empty()) {
368 out->append(kEmptyAppCachesMessage);
369 } else {
370 typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin;
371 AppCacheInfoVector appcaches;
372 for (InfoByOrigin::const_iterator origin =
373 info_collection_->infos_by_origin.begin();
374 origin != info_collection_->infos_by_origin.end(); ++origin) {
375 appcaches.insert(appcaches.end(),
376 origin->second.begin(), origin->second.end());
378 std::sort(appcaches.begin(), appcaches.end(), SortByManifestUrl);
380 GURL base_url = ClearQuery(request_->url());
381 EmitAppCacheInfoVector(base_url, appcache_service_, appcaches, out);
383 EmitPageEnd(out);
384 return net::OK;
387 private:
388 ~MainPageJob() override {}
390 void OnGotInfoComplete(int rv) {
391 if (rv != net::OK)
392 info_collection_ = NULL;
393 StartAsync();
396 scoped_refptr<AppCacheInfoCollection> info_collection_;
397 base::WeakPtrFactory<MainPageJob> weak_factory_;
398 DISALLOW_COPY_AND_ASSIGN(MainPageJob);
401 // Job that redirects back to the main appcache internals page.
402 class RedirectToMainPageJob : public BaseInternalsJob {
403 public:
404 RedirectToMainPageJob(net::URLRequest* request,
405 net::NetworkDelegate* network_delegate,
406 AppCacheServiceImpl* service)
407 : BaseInternalsJob(request, network_delegate, service) {}
409 int GetData(std::string* mime_type,
410 std::string* charset,
411 std::string* data,
412 const net::CompletionCallback& callback) const override {
413 return net::OK; // IsRedirectResponse induces a redirect.
416 bool IsRedirectResponse(GURL* location, int* http_status_code) override {
417 *location = ClearQuery(request_->url());
418 *http_status_code = 307;
419 return true;
422 protected:
423 ~RedirectToMainPageJob() override {}
426 // Job that removes an appcache and then redirects back to the main page.
427 class RemoveAppCacheJob : public RedirectToMainPageJob {
428 public:
429 RemoveAppCacheJob(
430 net::URLRequest* request,
431 net::NetworkDelegate* network_delegate,
432 AppCacheServiceImpl* service,
433 const GURL& manifest_url)
434 : RedirectToMainPageJob(request, network_delegate, service),
435 manifest_url_(manifest_url),
436 weak_factory_(this) {
439 void Start() override {
440 DCHECK(request_);
442 appcache_service_->DeleteAppCacheGroup(
443 manifest_url_,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete,
444 weak_factory_.GetWeakPtr()));
447 private:
448 ~RemoveAppCacheJob() override {}
450 void OnDeleteAppCacheComplete(int rv) {
451 StartAsync(); // Causes the base class to redirect.
454 GURL manifest_url_;
455 base::WeakPtrFactory<RemoveAppCacheJob> weak_factory_;
459 // Job shows the details of a particular manifest url.
460 class ViewAppCacheJob : public BaseInternalsJob,
461 public AppCacheStorage::Delegate {
462 public:
463 ViewAppCacheJob(
464 net::URLRequest* request,
465 net::NetworkDelegate* network_delegate,
466 AppCacheServiceImpl* service,
467 const GURL& manifest_url)
468 : BaseInternalsJob(request, network_delegate, service),
469 manifest_url_(manifest_url) {}
471 void Start() override {
472 DCHECK(request_);
473 appcache_storage_->LoadOrCreateGroup(manifest_url_, this);
476 // Produces a page containing the entries listing.
477 int GetData(std::string* mime_type,
478 std::string* charset,
479 std::string* out,
480 const net::CompletionCallback& callback) const override {
481 mime_type->assign("text/html");
482 charset->assign("UTF-8");
483 out->clear();
484 EmitPageStart(out);
485 if (appcache_info_.manifest_url.is_empty()) {
486 out->append(kManifestNotFoundMessage);
487 } else {
488 GURL base_url = ClearQuery(request_->url());
489 EmitAppCacheInfo(base_url, appcache_service_, &appcache_info_, out);
490 EmitAppCacheResourceInfoVector(base_url,
491 manifest_url_,
492 resource_infos_,
493 appcache_info_.group_id,
494 out);
496 EmitPageEnd(out);
497 return net::OK;
500 private:
501 ~ViewAppCacheJob() override {
502 appcache_storage_->CancelDelegateCallbacks(this);
505 // AppCacheStorage::Delegate override
506 void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) override {
507 DCHECK_EQ(manifest_url_, manifest_url);
508 if (group && group->newest_complete_cache()) {
509 appcache_info_.manifest_url = manifest_url;
510 appcache_info_.group_id = group->group_id();
511 appcache_info_.size = group->newest_complete_cache()->cache_size();
512 appcache_info_.creation_time = group->creation_time();
513 appcache_info_.last_update_time =
514 group->newest_complete_cache()->update_time();
515 appcache_info_.last_access_time = base::Time::Now();
516 group->newest_complete_cache()->ToResourceInfoVector(&resource_infos_);
517 std::sort(resource_infos_.begin(), resource_infos_.end(),
518 SortByResourceUrl);
520 StartAsync();
523 GURL manifest_url_;
524 AppCacheInfo appcache_info_;
525 AppCacheResourceInfoVector resource_infos_;
526 DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob);
529 // Job that shows the details of a particular cached resource.
530 class ViewEntryJob : public BaseInternalsJob,
531 public AppCacheStorage::Delegate {
532 public:
533 ViewEntryJob(
534 net::URLRequest* request,
535 net::NetworkDelegate* network_delegate,
536 AppCacheServiceImpl* service,
537 const GURL& manifest_url,
538 const GURL& entry_url,
539 int64 response_id, int64 group_id)
540 : BaseInternalsJob(request, network_delegate, service),
541 manifest_url_(manifest_url), entry_url_(entry_url),
542 response_id_(response_id), group_id_(group_id), amount_read_(0) {
545 void Start() override {
546 DCHECK(request_);
547 appcache_storage_->LoadResponseInfo(
548 manifest_url_, group_id_, response_id_, this);
551 // Produces a page containing the response headers and data.
552 int GetData(std::string* mime_type,
553 std::string* charset,
554 std::string* out,
555 const net::CompletionCallback& callback) const override {
556 mime_type->assign("text/html");
557 charset->assign("UTF-8");
558 out->clear();
559 EmitPageStart(out);
560 EmitAnchor(entry_url_.spec(), entry_url_.spec(), out);
561 out->append("<br/>\n");
562 if (response_info_.get()) {
563 if (response_info_->http_response_info())
564 EmitResponseHeaders(response_info_->http_response_info()->headers.get(),
565 out);
566 else
567 out->append("Failed to read response headers.<br>");
569 if (response_data_.get()) {
570 EmitHexDump(response_data_->data(),
571 amount_read_,
572 response_info_->response_data_size(),
573 out);
574 } else {
575 out->append("Failed to read response data.<br>");
577 } else {
578 out->append("Failed to read response headers and data.<br>");
580 EmitPageEnd(out);
581 return net::OK;
584 private:
585 ~ViewEntryJob() override { appcache_storage_->CancelDelegateCallbacks(this); }
587 void OnResponseInfoLoaded(AppCacheResponseInfo* response_info,
588 int64 response_id) override {
589 if (!response_info) {
590 StartAsync();
591 return;
593 response_info_ = response_info;
595 // Read the response data, truncating if its too large.
596 const int64 kLimit = 100 * 1000;
597 int64 amount_to_read =
598 std::min(kLimit, response_info->response_data_size());
599 response_data_ = new net::IOBuffer(amount_to_read);
601 reader_.reset(appcache_storage_->CreateResponseReader(
602 manifest_url_, group_id_, response_id_));
603 reader_->ReadData(
604 response_data_.get(),
605 amount_to_read,
606 base::Bind(&ViewEntryJob::OnReadComplete, base::Unretained(this)));
609 void OnReadComplete(int result) {
610 reader_.reset();
611 amount_read_ = result;
612 if (result < 0)
613 response_data_ = NULL;
614 StartAsync();
617 GURL manifest_url_;
618 GURL entry_url_;
619 int64 response_id_;
620 int64 group_id_;
621 scoped_refptr<AppCacheResponseInfo> response_info_;
622 scoped_refptr<net::IOBuffer> response_data_;
623 int amount_read_;
624 scoped_ptr<AppCacheResponseReader> reader_;
627 } // namespace
629 net::URLRequestJob* ViewAppCacheInternalsJobFactory::CreateJobForRequest(
630 net::URLRequest* request,
631 net::NetworkDelegate* network_delegate,
632 AppCacheServiceImpl* service) {
633 if (!request->url().has_query())
634 return new MainPageJob(request, network_delegate, service);
636 std::string command;
637 std::string param;
638 ParseQuery(request->url().query(), &command, &param);
640 if (command == kRemoveCacheCommand)
641 return new RemoveAppCacheJob(request, network_delegate, service,
642 DecodeBase64URL(param));
644 if (command == kViewCacheCommand)
645 return new ViewAppCacheJob(request, network_delegate, service,
646 DecodeBase64URL(param));
648 std::vector<std::string> tokens;
649 int64 response_id = 0;
650 int64 group_id = 0;
651 if (command == kViewEntryCommand && Tokenize(param, "|", &tokens) == 4u &&
652 base::StringToInt64(tokens[2], &response_id) &&
653 base::StringToInt64(tokens[3], &group_id)) {
654 return new ViewEntryJob(request, network_delegate, service,
655 DecodeBase64URL(tokens[0]), // manifest url
656 DecodeBase64URL(tokens[1]), // entry url
657 response_id, group_id);
660 return new RedirectToMainPageJob(request, network_delegate, service);
663 } // namespace content