Apply _RELATIVE relocations ahead of others.
[chromium-blink-merge.git] / content / browser / appcache / view_appcache_internals_job.cc
blob5e793bbb7603fde08a5ed8dea208066477660efa
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/profiler/scoped_tracker.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"
35 namespace content {
36 namespace {
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) {
56 out->append(
57 "<!DOCTYPE HTML>\n"
58 "<html><title>AppCache Internals</title>\n"
59 "<meta http-equiv=\"Content-Security-Policy\""
60 " content=\"object-src 'none'; script-src 'none'\">\n"
61 "<style>\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"
67 "</style>\n"
68 "</head><body>\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,
77 std::string* out) {
78 out->append("<li>");
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,
85 std::string* out) {
86 out->append("<a href=\"");
87 out->append(net::EscapeForHTML(url));
88 out->append("\">");
89 out->append(net::EscapeForHTML(text));
90 out->append("</a>");
93 void EmitCommandAnchor(const char* label,
94 const GURL& base_url,
95 const char* command,
96 const char* param,
97 std::string* out) {
98 std::string query(command);
99 query.push_back('=');
100 query.append(param);
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,
110 std::string* out) {
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("&nbsp;&nbsp;");
126 EmitCommandAnchor(kViewCacheLabel, base_url,
127 kViewCacheCommand, manifest_url_base64.c_str(), out);
128 out->append("\n<br/>\n");
129 out->append("<ul>");
130 EmitListItem(
131 kSize,
132 base::UTF16ToUTF8(FormatBytesUnlocalized(info->size)),
133 out);
134 EmitListItem(
135 kCreationTime,
136 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->creation_time)),
137 out);
138 EmitListItem(
139 kLastUpdateTime,
140 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_update_time)),
141 out);
142 EmitListItem(
143 kLastAccessTime,
144 base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_access_time)),
145 out);
146 out->append("</ul></p></br>\n");
149 void EmitAppCacheInfoVector(
150 const GURL& base_url,
151 AppCacheServiceImpl* service,
152 const AppCacheInfoVector& appcaches,
153 std::string* out) {
154 for (std::vector<AppCacheInfo>::const_iterator info =
155 appcaches.begin();
156 info != appcaches.end(); ++info) {
157 EmitAppCacheInfo(base_url, service, &(*info), out);
161 void EmitTableData(const std::string& data, bool align_right, bool bold,
162 std::string* out) {
163 if (align_right)
164 out->append("<td align='right'>");
165 else
166 out->append("<td>");
167 if (bold)
168 out->append("<b>");
169 out->append(data);
170 if (bold)
171 out->append("</b>");
172 out->append("</td>");
175 std::string FormFlagsString(const AppCacheResourceInfo& info) {
176 std::string str;
177 if (info.is_manifest)
178 str.append("Manifest, ");
179 if (info.is_master)
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, ");
187 if (info.is_foreign)
188 str.append("Foreign, ");
189 return str;
192 std::string FormViewEntryAnchor(const GURL& base_url,
193 const GURL& manifest_url, const GURL& entry_url,
194 int64 response_id,
195 int64 group_id) {
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);
219 std::string anchor;
220 EmitAnchor(view_entry_url.spec(), entry_url.spec(), &anchor);
221 return anchor;
224 void EmitAppCacheResourceInfoVector(
225 const GURL& base_url,
226 const GURL& manifest_url,
227 const AppCacheResourceInfoVector& resource_infos,
228 int64 group_id,
229 std::string* out) {
230 out->append("<table border='0'>\n");
231 out->append("<tr>");
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) {
239 out->append("<tr>");
240 EmitTableData(FormFlagsString(*iter), false, false, out);
241 EmitTableData(FormViewEntryAnchor(base_url, manifest_url,
242 iter->url, iter->response_id,
243 group_id),
244 false, false, out);
245 EmitTableData(base::UTF16ToUTF8(FormatBytesUnlocalized(iter->size)),
246 true, false, out);
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');
257 void* iter = NULL;
258 std::string name, value;
259 while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
260 out->append(net::EscapeForHTML(name));
261 out->append(": ");
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,
269 std::string* out) {
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(const std::string& base64) {
280 std::string url;
281 base::Base64Decode(base64, &url);
282 return GURL(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)
289 return false;
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 {
314 protected:
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 {
339 public:
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 {
348 DCHECK(request_);
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,
359 std::string* out,
360 const net::CompletionCallback& callback) const override {
361 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422489 is fixed.
362 tracked_objects::ScopedTracker tracking_profile(
363 FROM_HERE_WITH_EXPLICIT_FUNCTION("422489 MainPageJob::GetData"));
365 mime_type->assign("text/html");
366 charset->assign("UTF-8");
368 out->clear();
369 EmitPageStart(out);
370 if (!info_collection_.get()) {
371 out->append(kErrorMessage);
372 } else if (info_collection_->infos_by_origin.empty()) {
373 out->append(kEmptyAppCachesMessage);
374 } else {
375 typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin;
376 AppCacheInfoVector appcaches;
377 for (InfoByOrigin::const_iterator origin =
378 info_collection_->infos_by_origin.begin();
379 origin != info_collection_->infos_by_origin.end(); ++origin) {
380 appcaches.insert(appcaches.end(),
381 origin->second.begin(), origin->second.end());
383 std::sort(appcaches.begin(), appcaches.end(), SortByManifestUrl);
385 GURL base_url = ClearQuery(request_->url());
386 EmitAppCacheInfoVector(base_url, appcache_service_, appcaches, out);
388 EmitPageEnd(out);
389 return net::OK;
392 private:
393 ~MainPageJob() override {}
395 void OnGotInfoComplete(int rv) {
396 if (rv != net::OK)
397 info_collection_ = NULL;
398 StartAsync();
401 scoped_refptr<AppCacheInfoCollection> info_collection_;
402 base::WeakPtrFactory<MainPageJob> weak_factory_;
403 DISALLOW_COPY_AND_ASSIGN(MainPageJob);
406 // Job that redirects back to the main appcache internals page.
407 class RedirectToMainPageJob : public BaseInternalsJob {
408 public:
409 RedirectToMainPageJob(net::URLRequest* request,
410 net::NetworkDelegate* network_delegate,
411 AppCacheServiceImpl* service)
412 : BaseInternalsJob(request, network_delegate, service) {}
414 int GetData(std::string* mime_type,
415 std::string* charset,
416 std::string* data,
417 const net::CompletionCallback& callback) const override {
418 return net::OK; // IsRedirectResponse induces a redirect.
421 bool IsRedirectResponse(GURL* location, int* http_status_code) override {
422 *location = ClearQuery(request_->url());
423 *http_status_code = 307;
424 return true;
427 protected:
428 ~RedirectToMainPageJob() override {}
431 // Job that removes an appcache and then redirects back to the main page.
432 class RemoveAppCacheJob : public RedirectToMainPageJob {
433 public:
434 RemoveAppCacheJob(
435 net::URLRequest* request,
436 net::NetworkDelegate* network_delegate,
437 AppCacheServiceImpl* service,
438 const GURL& manifest_url)
439 : RedirectToMainPageJob(request, network_delegate, service),
440 manifest_url_(manifest_url),
441 weak_factory_(this) {
444 void Start() override {
445 DCHECK(request_);
447 appcache_service_->DeleteAppCacheGroup(
448 manifest_url_,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete,
449 weak_factory_.GetWeakPtr()));
452 private:
453 ~RemoveAppCacheJob() override {}
455 void OnDeleteAppCacheComplete(int rv) {
456 StartAsync(); // Causes the base class to redirect.
459 GURL manifest_url_;
460 base::WeakPtrFactory<RemoveAppCacheJob> weak_factory_;
464 // Job shows the details of a particular manifest url.
465 class ViewAppCacheJob : public BaseInternalsJob,
466 public AppCacheStorage::Delegate {
467 public:
468 ViewAppCacheJob(
469 net::URLRequest* request,
470 net::NetworkDelegate* network_delegate,
471 AppCacheServiceImpl* service,
472 const GURL& manifest_url)
473 : BaseInternalsJob(request, network_delegate, service),
474 manifest_url_(manifest_url) {}
476 void Start() override {
477 DCHECK(request_);
478 appcache_storage_->LoadOrCreateGroup(manifest_url_, this);
481 // Produces a page containing the entries listing.
482 int GetData(std::string* mime_type,
483 std::string* charset,
484 std::string* out,
485 const net::CompletionCallback& callback) const override {
486 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422489 is fixed.
487 tracked_objects::ScopedTracker tracking_profile(
488 FROM_HERE_WITH_EXPLICIT_FUNCTION("422489 ViewAppCacheJob::GetData"));
490 mime_type->assign("text/html");
491 charset->assign("UTF-8");
492 out->clear();
493 EmitPageStart(out);
494 if (appcache_info_.manifest_url.is_empty()) {
495 out->append(kManifestNotFoundMessage);
496 } else {
497 GURL base_url = ClearQuery(request_->url());
498 EmitAppCacheInfo(base_url, appcache_service_, &appcache_info_, out);
499 EmitAppCacheResourceInfoVector(base_url,
500 manifest_url_,
501 resource_infos_,
502 appcache_info_.group_id,
503 out);
505 EmitPageEnd(out);
506 return net::OK;
509 private:
510 ~ViewAppCacheJob() override {
511 appcache_storage_->CancelDelegateCallbacks(this);
514 // AppCacheStorage::Delegate override
515 void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) override {
516 DCHECK_EQ(manifest_url_, manifest_url);
517 if (group && group->newest_complete_cache()) {
518 appcache_info_.manifest_url = manifest_url;
519 appcache_info_.group_id = group->group_id();
520 appcache_info_.size = group->newest_complete_cache()->cache_size();
521 appcache_info_.creation_time = group->creation_time();
522 appcache_info_.last_update_time =
523 group->newest_complete_cache()->update_time();
524 appcache_info_.last_access_time = base::Time::Now();
525 group->newest_complete_cache()->ToResourceInfoVector(&resource_infos_);
526 std::sort(resource_infos_.begin(), resource_infos_.end(),
527 SortByResourceUrl);
529 StartAsync();
532 GURL manifest_url_;
533 AppCacheInfo appcache_info_;
534 AppCacheResourceInfoVector resource_infos_;
535 DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob);
538 // Job that shows the details of a particular cached resource.
539 class ViewEntryJob : public BaseInternalsJob,
540 public AppCacheStorage::Delegate {
541 public:
542 ViewEntryJob(
543 net::URLRequest* request,
544 net::NetworkDelegate* network_delegate,
545 AppCacheServiceImpl* service,
546 const GURL& manifest_url,
547 const GURL& entry_url,
548 int64 response_id, int64 group_id)
549 : BaseInternalsJob(request, network_delegate, service),
550 manifest_url_(manifest_url), entry_url_(entry_url),
551 response_id_(response_id), group_id_(group_id), amount_read_(0) {
554 void Start() override {
555 DCHECK(request_);
556 appcache_storage_->LoadResponseInfo(
557 manifest_url_, group_id_, response_id_, this);
560 // Produces a page containing the response headers and data.
561 int GetData(std::string* mime_type,
562 std::string* charset,
563 std::string* out,
564 const net::CompletionCallback& callback) const override {
565 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422489 is fixed.
566 tracked_objects::ScopedTracker tracking_profile(
567 FROM_HERE_WITH_EXPLICIT_FUNCTION("422489 ViewEntryJob::GetData"));
569 mime_type->assign("text/html");
570 charset->assign("UTF-8");
571 out->clear();
572 EmitPageStart(out);
573 EmitAnchor(entry_url_.spec(), entry_url_.spec(), out);
574 out->append("<br/>\n");
575 if (response_info_.get()) {
576 if (response_info_->http_response_info())
577 EmitResponseHeaders(response_info_->http_response_info()->headers.get(),
578 out);
579 else
580 out->append("Failed to read response headers.<br>");
582 if (response_data_.get()) {
583 EmitHexDump(response_data_->data(),
584 amount_read_,
585 response_info_->response_data_size(),
586 out);
587 } else {
588 out->append("Failed to read response data.<br>");
590 } else {
591 out->append("Failed to read response headers and data.<br>");
593 EmitPageEnd(out);
594 return net::OK;
597 private:
598 ~ViewEntryJob() override { appcache_storage_->CancelDelegateCallbacks(this); }
600 void OnResponseInfoLoaded(AppCacheResponseInfo* response_info,
601 int64 response_id) override {
602 if (!response_info) {
603 StartAsync();
604 return;
606 response_info_ = response_info;
608 // Read the response data, truncating if its too large.
609 const int64 kLimit = 100 * 1000;
610 int64 amount_to_read =
611 std::min(kLimit, response_info->response_data_size());
612 response_data_ = new net::IOBuffer(amount_to_read);
614 reader_.reset(appcache_storage_->CreateResponseReader(
615 manifest_url_, group_id_, response_id_));
616 reader_->ReadData(
617 response_data_.get(),
618 amount_to_read,
619 base::Bind(&ViewEntryJob::OnReadComplete, base::Unretained(this)));
622 void OnReadComplete(int result) {
623 reader_.reset();
624 amount_read_ = result;
625 if (result < 0)
626 response_data_ = NULL;
627 StartAsync();
630 GURL manifest_url_;
631 GURL entry_url_;
632 int64 response_id_;
633 int64 group_id_;
634 scoped_refptr<AppCacheResponseInfo> response_info_;
635 scoped_refptr<net::IOBuffer> response_data_;
636 int amount_read_;
637 scoped_ptr<AppCacheResponseReader> reader_;
640 } // namespace
642 net::URLRequestJob* ViewAppCacheInternalsJobFactory::CreateJobForRequest(
643 net::URLRequest* request,
644 net::NetworkDelegate* network_delegate,
645 AppCacheServiceImpl* service) {
646 if (!request->url().has_query())
647 return new MainPageJob(request, network_delegate, service);
649 std::string command;
650 std::string param;
651 ParseQuery(request->url().query(), &command, &param);
653 if (command == kRemoveCacheCommand)
654 return new RemoveAppCacheJob(request, network_delegate, service,
655 DecodeBase64URL(param));
657 if (command == kViewCacheCommand)
658 return new ViewAppCacheJob(request, network_delegate, service,
659 DecodeBase64URL(param));
661 std::vector<std::string> tokens;
662 int64 response_id = 0;
663 int64 group_id = 0;
664 if (command == kViewEntryCommand && Tokenize(param, "|", &tokens) == 4u &&
665 base::StringToInt64(tokens[2], &response_id) &&
666 base::StringToInt64(tokens[3], &group_id)) {
667 return new ViewEntryJob(request, network_delegate, service,
668 DecodeBase64URL(tokens[0]), // manifest url
669 DecodeBase64URL(tokens[1]), // entry url
670 response_id, group_id);
673 return new RedirectToMainPageJob(request, network_delegate, service);
676 } // namespace content