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. 4 5 #include "content/browser/appcache/view_appcache_internals_job.h" 6 7 #include <algorithm> 8 #include <string> 9 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" 33 34 namespace content { 35 namespace { 36 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"; 53 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"); 68 } 69 70 void EmitPageEnd(std::string* out) { 71 out->append("</body></html>\n"); 72 } 73 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"); 81 } 82 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>"); 90 } 91 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); 104 } 105 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); 112 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); 120 } 121 out->append("\n<br/>\n"); 122 EmitCommandAnchor(kRemoveCacheLabel, base_url, 123 kRemoveCacheCommand, manifest_url_base64.c_str(), out); 124 out->append(" "); 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"); 146 } 147 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); 157 } 158 } 159 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>"); 172 } 173 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; 189 } 190 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); 203 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); 213 214 GURL::Replacements replacements; 215 replacements.SetQuery(query.data(), url::Component(0, query.length())); 216 GURL view_entry_url = base_url.ReplaceComponents(replacements); 217 218 std::string anchor; 219 EmitAnchor(view_entry_url.spec(), entry_url.spec(), &anchor); 220 return anchor; 221 } 222 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"); 247 } 248 out->append("</table>\n"); 249 } 250 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'); 255 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'); 263 } 264 out->append("</pre>"); 265 } 266 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>"); 276 } 277 278 GURL DecodeBase64URL(const std::string& base64) { 279 std::string url; 280 base::Base64Decode(base64, &url); 281 return GURL(url); 282 } 283 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(); 292 } 293 294 bool SortByManifestUrl(const AppCacheInfo& lhs, 295 const AppCacheInfo& rhs) { 296 return lhs.manifest_url.spec() < rhs.manifest_url.spec(); 297 } 298 299 bool SortByResourceUrl(const AppCacheResourceInfo& lhs, 300 const AppCacheResourceInfo& rhs) { 301 return lhs.url.spec() < rhs.url.spec(); 302 } 303 304 GURL ClearQuery(const GURL& url) { 305 GURL::Replacements replacements; 306 replacements.ClearQuery(); 307 return url.ReplaceComponents(replacements); 308 } 309 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); 321 } 322 323 virtual ~BaseInternalsJob() { 324 appcache_service_->RemoveObserver(this); 325 } 326 327 virtual void OnServiceReinitialized( 328 AppCacheStorageReference* old_storage_ref) OVERRIDE { 329 if (old_storage_ref->storage() == appcache_storage_) 330 disabled_storage_reference_ = old_storage_ref; 331 } 332 333 AppCacheServiceImpl* appcache_service_; 334 AppCacheStorage* appcache_storage_; 335 scoped_refptr<AppCacheStorageReference> disabled_storage_reference_; 336 }; 337 338 // Job that lists all appcaches in the system. 339 class MainPageJob : public BaseInternalsJob { 340 public: 341 MainPageJob(net::URLRequest* request, 342 net::NetworkDelegate* network_delegate, 343 AppCacheServiceImpl* service) 344 : BaseInternalsJob(request, network_delegate, service), 345 weak_factory_(this) { 346 } 347 348 virtual void Start() OVERRIDE { 349 DCHECK(request_); 350 info_collection_ = new AppCacheInfoCollection; 351 appcache_service_->GetAllAppCacheInfo( 352 info_collection_.get(), 353 base::Bind(&MainPageJob::OnGotInfoComplete, 354 weak_factory_.GetWeakPtr())); 355 } 356 357 // Produces a page containing the listing 358 virtual int GetData(std::string* mime_type, 359 std::string* charset, 360 std::string* out, 361 const net::CompletionCallback& callback) const OVERRIDE { 362 mime_type->assign("text/html"); 363 charset->assign("UTF-8"); 364 365 out->clear(); 366 EmitPageStart(out); 367 if (!info_collection_.get()) { 368 out->append(kErrorMessage); 369 } else if (info_collection_->infos_by_origin.empty()) { 370 out->append(kEmptyAppCachesMessage); 371 } else { 372 typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin; 373 AppCacheInfoVector appcaches; 374 for (InfoByOrigin::const_iterator origin = 375 info_collection_->infos_by_origin.begin(); 376 origin != info_collection_->infos_by_origin.end(); ++origin) { 377 appcaches.insert(appcaches.end(), 378 origin->second.begin(), origin->second.end()); 379 } 380 std::sort(appcaches.begin(), appcaches.end(), SortByManifestUrl); 381 382 GURL base_url = ClearQuery(request_->url()); 383 EmitAppCacheInfoVector(base_url, appcache_service_, appcaches, out); 384 } 385 EmitPageEnd(out); 386 return net::OK; 387 } 388 389 private: 390 virtual ~MainPageJob() {} 391 392 void OnGotInfoComplete(int rv) { 393 if (rv != net::OK) 394 info_collection_ = NULL; 395 StartAsync(); 396 } 397 398 scoped_refptr<AppCacheInfoCollection> info_collection_; 399 base::WeakPtrFactory<MainPageJob> weak_factory_; 400 DISALLOW_COPY_AND_ASSIGN(MainPageJob); 401 }; 402 403 // Job that redirects back to the main appcache internals page. 404 class RedirectToMainPageJob : public BaseInternalsJob { 405 public: 406 RedirectToMainPageJob(net::URLRequest* request, 407 net::NetworkDelegate* network_delegate, 408 AppCacheServiceImpl* service) 409 : BaseInternalsJob(request, network_delegate, service) {} 410 411 virtual int GetData(std::string* mime_type, 412 std::string* charset, 413 std::string* data, 414 const net::CompletionCallback& callback) const OVERRIDE { 415 return net::OK; // IsRedirectResponse induces a redirect. 416 } 417 418 virtual bool IsRedirectResponse(GURL* location, 419 int* http_status_code) OVERRIDE { 420 *location = ClearQuery(request_->url()); 421 *http_status_code = 307; 422 return true; 423 } 424 425 protected: 426 virtual ~RedirectToMainPageJob() {} 427 }; 428 429 // Job that removes an appcache and then redirects back to the main page. 430 class RemoveAppCacheJob : public RedirectToMainPageJob { 431 public: 432 RemoveAppCacheJob( 433 net::URLRequest* request, 434 net::NetworkDelegate* network_delegate, 435 AppCacheServiceImpl* service, 436 const GURL& manifest_url) 437 : RedirectToMainPageJob(request, network_delegate, service), 438 manifest_url_(manifest_url), 439 weak_factory_(this) { 440 } 441 442 virtual void Start() OVERRIDE { 443 DCHECK(request_); 444 445 appcache_service_->DeleteAppCacheGroup( 446 manifest_url_,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete, 447 weak_factory_.GetWeakPtr())); 448 } 449 450 private: 451 virtual ~RemoveAppCacheJob() {} 452 453 void OnDeleteAppCacheComplete(int rv) { 454 StartAsync(); // Causes the base class to redirect. 455 } 456 457 GURL manifest_url_; 458 base::WeakPtrFactory<RemoveAppCacheJob> weak_factory_; 459 }; 460 461 462 // Job shows the details of a particular manifest url. 463 class ViewAppCacheJob : public BaseInternalsJob, 464 public AppCacheStorage::Delegate { 465 public: 466 ViewAppCacheJob( 467 net::URLRequest* request, 468 net::NetworkDelegate* network_delegate, 469 AppCacheServiceImpl* service, 470 const GURL& manifest_url) 471 : BaseInternalsJob(request, network_delegate, service), 472 manifest_url_(manifest_url) {} 473 474 virtual void Start() OVERRIDE { 475 DCHECK(request_); 476 appcache_storage_->LoadOrCreateGroup(manifest_url_, this); 477 } 478 479 // Produces a page containing the entries listing. 480 virtual int GetData(std::string* mime_type, 481 std::string* charset, 482 std::string* out, 483 const net::CompletionCallback& callback) const OVERRIDE { 484 mime_type->assign("text/html"); 485 charset->assign("UTF-8"); 486 out->clear(); 487 EmitPageStart(out); 488 if (appcache_info_.manifest_url.is_empty()) { 489 out->append(kManifestNotFoundMessage); 490 } else { 491 GURL base_url = ClearQuery(request_->url()); 492 EmitAppCacheInfo(base_url, appcache_service_, &appcache_info_, out); 493 EmitAppCacheResourceInfoVector(base_url, 494 manifest_url_, 495 resource_infos_, 496 appcache_info_.group_id, 497 out); 498 } 499 EmitPageEnd(out); 500 return net::OK; 501 } 502 503 private: 504 virtual ~ViewAppCacheJob() { 505 appcache_storage_->CancelDelegateCallbacks(this); 506 } 507 508 // AppCacheStorage::Delegate override 509 virtual void OnGroupLoaded( 510 AppCacheGroup* group, const GURL& manifest_url) OVERRIDE { 511 DCHECK_EQ(manifest_url_, manifest_url); 512 if (group && group->newest_complete_cache()) { 513 appcache_info_.manifest_url = manifest_url; 514 appcache_info_.group_id = group->group_id(); 515 appcache_info_.size = group->newest_complete_cache()->cache_size(); 516 appcache_info_.creation_time = group->creation_time(); 517 appcache_info_.last_update_time = 518 group->newest_complete_cache()->update_time(); 519 appcache_info_.last_access_time = base::Time::Now(); 520 group->newest_complete_cache()->ToResourceInfoVector(&resource_infos_); 521 std::sort(resource_infos_.begin(), resource_infos_.end(), 522 SortByResourceUrl); 523 } 524 StartAsync(); 525 } 526 527 GURL manifest_url_; 528 AppCacheInfo appcache_info_; 529 AppCacheResourceInfoVector resource_infos_; 530 DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob); 531 }; 532 533 // Job that shows the details of a particular cached resource. 534 class ViewEntryJob : public BaseInternalsJob, 535 public AppCacheStorage::Delegate { 536 public: 537 ViewEntryJob( 538 net::URLRequest* request, 539 net::NetworkDelegate* network_delegate, 540 AppCacheServiceImpl* service, 541 const GURL& manifest_url, 542 const GURL& entry_url, 543 int64 response_id, int64 group_id) 544 : BaseInternalsJob(request, network_delegate, service), 545 manifest_url_(manifest_url), entry_url_(entry_url), 546 response_id_(response_id), group_id_(group_id), amount_read_(0) { 547 } 548 549 virtual void Start() OVERRIDE { 550 DCHECK(request_); 551 appcache_storage_->LoadResponseInfo( 552 manifest_url_, group_id_, response_id_, this); 553 } 554 555 // Produces a page containing the response headers and data. 556 virtual int GetData(std::string* mime_type, 557 std::string* charset, 558 std::string* out, 559 const net::CompletionCallback& callback) const OVERRIDE { 560 mime_type->assign("text/html"); 561 charset->assign("UTF-8"); 562 out->clear(); 563 EmitPageStart(out); 564 EmitAnchor(entry_url_.spec(), entry_url_.spec(), out); 565 out->append("<br/>\n"); 566 if (response_info_.get()) { 567 if (response_info_->http_response_info()) 568 EmitResponseHeaders(response_info_->http_response_info()->headers.get(), 569 out); 570 else 571 out->append("Failed to read response headers.<br>"); 572 573 if (response_data_.get()) { 574 EmitHexDump(response_data_->data(), 575 amount_read_, 576 response_info_->response_data_size(), 577 out); 578 } else { 579 out->append("Failed to read response data.<br>"); 580 } 581 } else { 582 out->append("Failed to read response headers and data.<br>"); 583 } 584 EmitPageEnd(out); 585 return net::OK; 586 } 587 588 private: 589 virtual ~ViewEntryJob() { 590 appcache_storage_->CancelDelegateCallbacks(this); 591 } 592 593 virtual void OnResponseInfoLoaded( 594 AppCacheResponseInfo* response_info, int64 response_id) OVERRIDE { 595 if (!response_info) { 596 StartAsync(); 597 return; 598 } 599 response_info_ = response_info; 600 601 // Read the response data, truncating if its too large. 602 const int64 kLimit = 100 * 1000; 603 int64 amount_to_read = 604 std::min(kLimit, response_info->response_data_size()); 605 response_data_ = new net::IOBuffer(amount_to_read); 606 607 reader_.reset(appcache_storage_->CreateResponseReader( 608 manifest_url_, group_id_, response_id_)); 609 reader_->ReadData( 610 response_data_.get(), 611 amount_to_read, 612 base::Bind(&ViewEntryJob::OnReadComplete, base::Unretained(this))); 613 } 614 615 void OnReadComplete(int result) { 616 reader_.reset(); 617 amount_read_ = result; 618 if (result < 0) 619 response_data_ = NULL; 620 StartAsync(); 621 } 622 623 GURL manifest_url_; 624 GURL entry_url_; 625 int64 response_id_; 626 int64 group_id_; 627 scoped_refptr<AppCacheResponseInfo> response_info_; 628 scoped_refptr<net::IOBuffer> response_data_; 629 int amount_read_; 630 scoped_ptr<AppCacheResponseReader> reader_; 631 }; 632 633 } // namespace 634 635 net::URLRequestJob* ViewAppCacheInternalsJobFactory::CreateJobForRequest( 636 net::URLRequest* request, 637 net::NetworkDelegate* network_delegate, 638 AppCacheServiceImpl* service) { 639 if (!request->url().has_query()) 640 return new MainPageJob(request, network_delegate, service); 641 642 std::string command; 643 std::string param; 644 ParseQuery(request->url().query(), &command, ¶m); 645 646 if (command == kRemoveCacheCommand) 647 return new RemoveAppCacheJob(request, network_delegate, service, 648 DecodeBase64URL(param)); 649 650 if (command == kViewCacheCommand) 651 return new ViewAppCacheJob(request, network_delegate, service, 652 DecodeBase64URL(param)); 653 654 std::vector<std::string> tokens; 655 int64 response_id = 0; 656 int64 group_id = 0; 657 if (command == kViewEntryCommand && Tokenize(param, "|", &tokens) == 4u && 658 base::StringToInt64(tokens[2], &response_id) && 659 base::StringToInt64(tokens[3], &group_id)) { 660 return new ViewEntryJob(request, network_delegate, service, 661 DecodeBase64URL(tokens[0]), // manifest url 662 DecodeBase64URL(tokens[1]), // entry url 663 response_id, group_id); 664 } 665 666 return new RedirectToMainPageJob(request, network_delegate, service); 667 } 668 669 } // namespace content 670