1 // Copyright (c) 2012 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 "webkit/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 "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 33 namespace appcache { 34 namespace { 35 36 const char kErrorMessage[] = "Error in retrieving Application Caches."; 37 const char kEmptyAppCachesMessage[] = "No available Application Caches."; 38 const char kManifestNotFoundMessage[] = "Manifest not found."; 39 const char kManifest[] = "Manifest: "; 40 const char kSize[] = "Size: "; 41 const char kCreationTime[] = "Creation Time: "; 42 const char kLastAccessTime[] = "Last Access Time: "; 43 const char kLastUpdateTime[] = "Last Update Time: "; 44 const char kFormattedDisabledAppCacheMsg[] = 45 "<b><i><font color=\"FF0000\">" 46 "This Application Cache is disabled by policy.</font></i></b><br/>"; 47 const char kRemoveCacheLabel[] = "Remove"; 48 const char kViewCacheLabel[] = "View Entries"; 49 const char kRemoveCacheCommand[] = "remove-cache"; 50 const char kViewCacheCommand[] = "view-cache"; 51 const char kViewEntryCommand[] = "view-entry"; 52 53 void EmitPageStart(std::string* out) { 54 out->append( 55 "<!DOCTYPE HTML>\n" 56 "<html><title>AppCache Internals</title>\n" 57 "<meta http-equiv=\"Content-Security-Policy\"" 58 " content=\"object-src 'none'; script-src 'none'\">\n" 59 "<style>\n" 60 "body { font-family: sans-serif; font-size: 0.8em; }\n" 61 "tt, code, pre { font-family: WebKitHack, monospace; }\n" 62 "form { display: inline; }\n" 63 ".subsection_body { margin: 10px 0 10px 2em; }\n" 64 ".subsection_title { font-weight: bold; }\n" 65 "</style>\n" 66 "</head><body>\n"); 67 } 68 69 void EmitPageEnd(std::string* out) { 70 out->append("</body></html>\n"); 71 } 72 73 void EmitListItem(const std::string& label, 74 const std::string& data, 75 std::string* out) { 76 out->append("<li>"); 77 out->append(net::EscapeForHTML(label)); 78 out->append(net::EscapeForHTML(data)); 79 out->append("</li>\n"); 80 } 81 82 void EmitAnchor(const std::string& url, const std::string& text, 83 std::string* out) { 84 out->append("<a href=\""); 85 out->append(net::EscapeForHTML(url)); 86 out->append("\">"); 87 out->append(net::EscapeForHTML(text)); 88 out->append("</a>"); 89 } 90 91 void EmitCommandAnchor(const char* label, 92 const GURL& base_url, 93 const char* command, 94 const char* param, 95 std::string* out) { 96 std::string query(command); 97 query.push_back('='); 98 query.append(param); 99 GURL::Replacements replacements; 100 replacements.SetQuery(query.data(), 101 url_parse::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 AppCacheService* 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 UTF16ToUTF8(FormatBytesUnlocalized(info->size)), 132 out); 133 EmitListItem( 134 kCreationTime, 135 UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->creation_time)), 136 out); 137 EmitListItem( 138 kLastUpdateTime, 139 UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_update_time)), 140 out); 141 EmitListItem( 142 kLastAccessTime, 143 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 AppCacheService* 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(), 216 url_parse::Component(0, query.length())); 217 GURL view_entry_url = base_url.ReplaceComponents(replacements); 218 219 std::string anchor; 220 EmitAnchor(view_entry_url.spec(), entry_url.spec(), &anchor); 221 return anchor; 222 } 223 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(UTF16ToUTF8(FormatBytesUnlocalized(iter->size)), 246 true, false, out); 247 out->append("</tr>\n"); 248 } 249 out->append("</table>\n"); 250 } 251 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'); 256 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'); 264 } 265 out->append("</pre>"); 266 } 267 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>"); 277 } 278 279 GURL DecodeBase64URL(const std::string& base64) { 280 std::string url; 281 base::Base64Decode(base64, &url); 282 return GURL(url); 283 } 284 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(); 293 } 294 295 bool SortByManifestUrl(const AppCacheInfo& lhs, 296 const AppCacheInfo& rhs) { 297 return lhs.manifest_url.spec() < rhs.manifest_url.spec(); 298 } 299 300 bool SortByResourceUrl(const AppCacheResourceInfo& lhs, 301 const AppCacheResourceInfo& rhs) { 302 return lhs.url.spec() < rhs.url.spec(); 303 } 304 305 GURL ClearQuery(const GURL& url) { 306 GURL::Replacements replacements; 307 replacements.ClearQuery(); 308 return url.ReplaceComponents(replacements); 309 } 310 311 // Simple base class for the job subclasses defined here. 312 class BaseInternalsJob : public net::URLRequestSimpleJob { 313 protected: 314 BaseInternalsJob(net::URLRequest* request, 315 net::NetworkDelegate* network_delegate, 316 AppCacheService* service) 317 : URLRequestSimpleJob(request, network_delegate), 318 appcache_service_(service) {} 319 virtual ~BaseInternalsJob() {} 320 321 AppCacheService* appcache_service_; 322 }; 323 324 // Job that lists all appcaches in the system. 325 class MainPageJob : public BaseInternalsJob { 326 public: 327 MainPageJob(net::URLRequest* request, 328 net::NetworkDelegate* network_delegate, 329 AppCacheService* service) 330 : BaseInternalsJob(request, network_delegate, service), 331 weak_factory_(this) { 332 } 333 334 virtual void Start() OVERRIDE { 335 DCHECK(request_); 336 info_collection_ = new AppCacheInfoCollection; 337 appcache_service_->GetAllAppCacheInfo( 338 info_collection_.get(), 339 base::Bind(&MainPageJob::OnGotInfoComplete, 340 weak_factory_.GetWeakPtr())); 341 } 342 343 // Produces a page containing the listing 344 virtual int GetData(std::string* mime_type, 345 std::string* charset, 346 std::string* out, 347 const net::CompletionCallback& callback) const OVERRIDE { 348 mime_type->assign("text/html"); 349 charset->assign("UTF-8"); 350 351 out->clear(); 352 EmitPageStart(out); 353 if (!info_collection_.get()) { 354 out->append(kErrorMessage); 355 } else if (info_collection_->infos_by_origin.empty()) { 356 out->append(kEmptyAppCachesMessage); 357 } else { 358 typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin; 359 AppCacheInfoVector appcaches; 360 for (InfoByOrigin::const_iterator origin = 361 info_collection_->infos_by_origin.begin(); 362 origin != info_collection_->infos_by_origin.end(); ++origin) { 363 appcaches.insert(appcaches.end(), 364 origin->second.begin(), origin->second.end()); 365 } 366 std::sort(appcaches.begin(), appcaches.end(), SortByManifestUrl); 367 368 GURL base_url = ClearQuery(request_->url()); 369 EmitAppCacheInfoVector(base_url, appcache_service_, appcaches, out); 370 } 371 EmitPageEnd(out); 372 return net::OK; 373 } 374 375 private: 376 virtual ~MainPageJob() {} 377 378 void OnGotInfoComplete(int rv) { 379 if (rv != net::OK) 380 info_collection_ = NULL; 381 StartAsync(); 382 } 383 384 base::WeakPtrFactory<MainPageJob> weak_factory_; 385 scoped_refptr<AppCacheInfoCollection> info_collection_; 386 DISALLOW_COPY_AND_ASSIGN(MainPageJob); 387 }; 388 389 // Job that redirects back to the main appcache internals page. 390 class RedirectToMainPageJob : public BaseInternalsJob { 391 public: 392 RedirectToMainPageJob(net::URLRequest* request, 393 net::NetworkDelegate* network_delegate, 394 AppCacheService* service) 395 : BaseInternalsJob(request, network_delegate, service) {} 396 397 virtual int GetData(std::string* mime_type, 398 std::string* charset, 399 std::string* data, 400 const net::CompletionCallback& callback) const OVERRIDE { 401 return net::OK; // IsRedirectResponse induces a redirect. 402 } 403 404 virtual bool IsRedirectResponse(GURL* location, 405 int* http_status_code) OVERRIDE { 406 *location = ClearQuery(request_->url()); 407 *http_status_code = 307; 408 return true; 409 } 410 411 protected: 412 virtual ~RedirectToMainPageJob() {} 413 }; 414 415 // Job that removes an appcache and then redirects back to the main page. 416 class RemoveAppCacheJob : public RedirectToMainPageJob { 417 public: 418 RemoveAppCacheJob( 419 net::URLRequest* request, 420 net::NetworkDelegate* network_delegate, 421 AppCacheService* service, 422 const GURL& manifest_url) 423 : RedirectToMainPageJob(request, network_delegate, service), 424 manifest_url_(manifest_url), 425 weak_factory_(this) { 426 } 427 428 virtual void Start() OVERRIDE { 429 DCHECK(request_); 430 431 appcache_service_->DeleteAppCacheGroup( 432 manifest_url_,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete, 433 weak_factory_.GetWeakPtr())); 434 } 435 436 private: 437 virtual ~RemoveAppCacheJob() {} 438 439 void OnDeleteAppCacheComplete(int rv) { 440 StartAsync(); // Causes the base class to redirect. 441 } 442 443 GURL manifest_url_; 444 base::WeakPtrFactory<RemoveAppCacheJob> weak_factory_; 445 }; 446 447 448 // Job shows the details of a particular manifest url. 449 class ViewAppCacheJob : public BaseInternalsJob, 450 public AppCacheStorage::Delegate { 451 public: 452 ViewAppCacheJob( 453 net::URLRequest* request, 454 net::NetworkDelegate* network_delegate, 455 AppCacheService* service, 456 const GURL& manifest_url) 457 : BaseInternalsJob(request, network_delegate, service), 458 manifest_url_(manifest_url) {} 459 460 virtual void Start() OVERRIDE { 461 DCHECK(request_); 462 appcache_service_->storage()->LoadOrCreateGroup(manifest_url_, this); 463 } 464 465 // Produces a page containing the entries listing. 466 virtual int GetData(std::string* mime_type, 467 std::string* charset, 468 std::string* out, 469 const net::CompletionCallback& callback) const OVERRIDE { 470 mime_type->assign("text/html"); 471 charset->assign("UTF-8"); 472 out->clear(); 473 EmitPageStart(out); 474 if (appcache_info_.manifest_url.is_empty()) { 475 out->append(kManifestNotFoundMessage); 476 } else { 477 GURL base_url = ClearQuery(request_->url()); 478 EmitAppCacheInfo(base_url, appcache_service_, &appcache_info_, out); 479 EmitAppCacheResourceInfoVector(base_url, 480 manifest_url_, 481 resource_infos_, 482 appcache_info_.group_id, 483 out); 484 } 485 EmitPageEnd(out); 486 return net::OK; 487 } 488 489 private: 490 virtual ~ViewAppCacheJob() { 491 appcache_service_->storage()->CancelDelegateCallbacks(this); 492 } 493 494 // AppCacheStorage::Delegate override 495 virtual void OnGroupLoaded( 496 AppCacheGroup* group, const GURL& manifest_url) OVERRIDE { 497 DCHECK_EQ(manifest_url_, manifest_url); 498 if (group && group->newest_complete_cache()) { 499 appcache_info_.manifest_url = manifest_url; 500 appcache_info_.group_id = group->group_id(); 501 appcache_info_.size = group->newest_complete_cache()->cache_size(); 502 appcache_info_.creation_time = group->creation_time(); 503 appcache_info_.last_update_time = 504 group->newest_complete_cache()->update_time(); 505 appcache_info_.last_access_time = base::Time::Now(); 506 group->newest_complete_cache()->ToResourceInfoVector(&resource_infos_); 507 std::sort(resource_infos_.begin(), resource_infos_.end(), 508 SortByResourceUrl); 509 } 510 StartAsync(); 511 } 512 513 GURL manifest_url_; 514 AppCacheInfo appcache_info_; 515 AppCacheResourceInfoVector resource_infos_; 516 DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob); 517 }; 518 519 // Job that shows the details of a particular cached resource. 520 class ViewEntryJob : public BaseInternalsJob, 521 public AppCacheStorage::Delegate { 522 public: 523 ViewEntryJob( 524 net::URLRequest* request, 525 net::NetworkDelegate* network_delegate, 526 AppCacheService* service, 527 const GURL& manifest_url, 528 const GURL& entry_url, 529 int64 response_id, int64 group_id) 530 : BaseInternalsJob(request, network_delegate, service), 531 manifest_url_(manifest_url), entry_url_(entry_url), 532 response_id_(response_id), group_id_(group_id), amount_read_(0) { 533 } 534 535 virtual void Start() OVERRIDE { 536 DCHECK(request_); 537 appcache_service_->storage()->LoadResponseInfo( 538 manifest_url_, group_id_, response_id_, this); 539 } 540 541 // Produces a page containing the response headers and data. 542 virtual int GetData(std::string* mime_type, 543 std::string* charset, 544 std::string* out, 545 const net::CompletionCallback& callback) const OVERRIDE { 546 mime_type->assign("text/html"); 547 charset->assign("UTF-8"); 548 out->clear(); 549 EmitPageStart(out); 550 EmitAnchor(entry_url_.spec(), entry_url_.spec(), out); 551 out->append("<br/>\n"); 552 if (response_info_.get()) { 553 if (response_info_->http_response_info()) 554 EmitResponseHeaders(response_info_->http_response_info()->headers.get(), 555 out); 556 else 557 out->append("Failed to read response headers.<br>"); 558 559 if (response_data_.get()) { 560 EmitHexDump(response_data_->data(), 561 amount_read_, 562 response_info_->response_data_size(), 563 out); 564 } else { 565 out->append("Failed to read response data.<br>"); 566 } 567 } else { 568 out->append("Failed to read response headers and data.<br>"); 569 } 570 EmitPageEnd(out); 571 return net::OK; 572 } 573 574 private: 575 virtual ~ViewEntryJob() { 576 appcache_service_->storage()->CancelDelegateCallbacks(this); 577 } 578 579 virtual void OnResponseInfoLoaded( 580 AppCacheResponseInfo* response_info, int64 response_id) OVERRIDE { 581 if (!response_info) { 582 StartAsync(); 583 return; 584 } 585 response_info_ = response_info; 586 587 // Read the response data, truncating if its too large. 588 const int64 kLimit = 100 * 1000; 589 int64 amount_to_read = 590 std::min(kLimit, response_info->response_data_size()); 591 response_data_ = new net::IOBuffer(amount_to_read); 592 593 reader_.reset(appcache_service_->storage()->CreateResponseReader( 594 manifest_url_, group_id_, response_id_)); 595 reader_->ReadData( 596 response_data_.get(), 597 amount_to_read, 598 base::Bind(&ViewEntryJob::OnReadComplete, base::Unretained(this))); 599 } 600 601 void OnReadComplete(int result) { 602 reader_.reset(); 603 amount_read_ = result; 604 if (result < 0) 605 response_data_ = NULL; 606 StartAsync(); 607 } 608 609 GURL manifest_url_; 610 GURL entry_url_; 611 int64 response_id_; 612 int64 group_id_; 613 scoped_refptr<AppCacheResponseInfo> response_info_; 614 scoped_refptr<net::IOBuffer> response_data_; 615 int amount_read_; 616 scoped_ptr<AppCacheResponseReader> reader_; 617 }; 618 619 } // namespace 620 621 net::URLRequestJob* ViewAppCacheInternalsJobFactory::CreateJobForRequest( 622 net::URLRequest* request, 623 net::NetworkDelegate* network_delegate, 624 AppCacheService* service) { 625 if (!request->url().has_query()) 626 return new MainPageJob(request, network_delegate, service); 627 628 std::string command; 629 std::string param; 630 ParseQuery(request->url().query(), &command, ¶m); 631 632 if (command == kRemoveCacheCommand) 633 return new RemoveAppCacheJob(request, network_delegate, service, 634 DecodeBase64URL(param)); 635 636 if (command == kViewCacheCommand) 637 return new ViewAppCacheJob(request, network_delegate, service, 638 DecodeBase64URL(param)); 639 640 std::vector<std::string> tokens; 641 int64 response_id; 642 int64 group_id; 643 if (command == kViewEntryCommand && Tokenize(param, "|", &tokens) == 4u && 644 base::StringToInt64(tokens[2], &response_id) && 645 base::StringToInt64(tokens[3], &group_id)) { 646 return new ViewEntryJob(request, network_delegate, service, 647 DecodeBase64URL(tokens[0]), // manifest url 648 DecodeBase64URL(tokens[1]), // entry url 649 response_id, group_id); 650 } 651 652 return new RedirectToMainPageJob(request, network_delegate, service); 653 } 654 655 } // namespace appcache 656