1// Copyright (c) 2009 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 "net/url_request/url_request_view_net_internals_job.h" 6 7#include <sstream> 8 9#include "base/format_macros.h" 10#include "base/stl_util-inl.h" 11#include "base/string_util.h" 12#include "net/base/escape.h" 13#include "net/base/host_resolver_impl.h" 14#include "net/base/load_log_util.h" 15#include "net/base/net_errors.h" 16#include "net/base/net_util.h" 17#include "net/base/sys_addrinfo.h" 18#include "net/proxy/proxy_service.h" 19#include "net/socket_stream/socket_stream.h" 20#include "net/url_request/url_request.h" 21#include "net/url_request/url_request_context.h" 22#include "net/url_request/view_cache_helper.h" 23 24namespace { 25 26const char kViewHttpCacheSubPath[] = "view-cache"; 27 28//------------------------------------------------------------------------------ 29// Format helpers. 30//------------------------------------------------------------------------------ 31 32void OutputTextInPre(const std::string& text, std::string* out) { 33 out->append("<pre>"); 34 out->append(EscapeForHTML(text)); 35 out->append("</pre>"); 36} 37 38// Appends an input button to |data| with text |title| that sends the command 39// string |command| back to the browser, and then refreshes the page. 40void DrawCommandButton(const std::string& title, 41 const std::string& command, 42 std::string* data) { 43 StringAppendF(data, "<input type=\"button\" value=\"%s\" " 44 "onclick=\"DoCommand('%s')\" />", 45 title.c_str(), 46 command.c_str()); 47} 48 49//------------------------------------------------------------------------------ 50// URLRequestContext helpers. 51//------------------------------------------------------------------------------ 52 53net::HostResolverImpl* GetHostResolverImpl(URLRequestContext* context) { 54 if (context->host_resolver()->IsHostResolverImpl()) 55 return static_cast<net::HostResolverImpl*> (context->host_resolver()); 56 return NULL; 57} 58 59net::HostCache* GetHostCache(URLRequestContext* context) { 60 if (GetHostResolverImpl(context)) 61 return GetHostResolverImpl(context)->cache(); 62 return NULL; 63} 64 65//------------------------------------------------------------------------------ 66// Subsection definitions. 67//------------------------------------------------------------------------------ 68 69class SubSection { 70 public: 71 // |name| is the URL path component for this subsection. 72 // |title| is the textual description. 73 SubSection(SubSection* parent, const char* name, const char* title) 74 : parent_(parent), 75 name_(name), 76 title_(title) { 77 } 78 79 virtual ~SubSection() { 80 STLDeleteContainerPointers(children_.begin(), children_.end()); 81 } 82 83 // Outputs the subsection's contents to |out|. 84 virtual void OutputBody(URLRequestContext* context, std::string* out) {} 85 86 // Outputs this subsection, and all of its children. 87 void OutputRecursive(URLRequestContext* context, 88 URLRequestViewNetInternalsJob::URLFormat* url_format, 89 std::string* out) { 90 if (!is_root()) { 91 // Canonicalizing the URL escapes characters which cause problems in HTML. 92 std::string section_url = 93 url_format->MakeURL(GetFullyQualifiedName()).spec(); 94 95 // Print the heading. 96 StringAppendF( 97 out, 98 "<div>" 99 "<span class=subsection_title>%s</span> " 100 "<span class=subsection_name>(<a href='%s'>%s</a>)<span>" 101 "</div>", 102 EscapeForHTML(title_).c_str(), 103 section_url.c_str(), 104 EscapeForHTML(section_url).c_str()); 105 106 out->append("<div class=subsection_body>"); 107 } 108 109 OutputBody(context, out); 110 111 for (size_t i = 0; i < children_.size(); ++i) 112 children_[i]->OutputRecursive(context, url_format, out); 113 114 if (!is_root()) 115 out->append("</div>"); 116 } 117 118 // Returns the SubSection contained by |this| with fully qualified name 119 // |dotted_name|, or NULL if none was found. 120 SubSection* FindSubSectionByName(const std::string& dotted_name) { 121 if (dotted_name == "") 122 return this; 123 124 std::string child_name; 125 std::string child_sub_name; 126 127 size_t split_pos = dotted_name.find('.'); 128 if (split_pos == std::string::npos) { 129 child_name = dotted_name; 130 child_sub_name = std::string(); 131 } else { 132 child_name = dotted_name.substr(0, split_pos); 133 child_sub_name = dotted_name.substr(split_pos + 1); 134 } 135 136 for (size_t i = 0; i < children_.size(); ++i) { 137 if (children_[i]->name_ == child_name) 138 return children_[i]->FindSubSectionByName(child_sub_name); 139 } 140 141 return NULL; // Not found. 142 } 143 144 std::string GetFullyQualifiedName() { 145 if (!parent_) 146 return name_; 147 148 std::string parent_name = parent_->GetFullyQualifiedName(); 149 if (parent_name.empty()) 150 return name_; 151 152 return parent_name + "." + name_; 153 } 154 155 bool is_root() const { 156 return parent_ == NULL; 157 } 158 159 protected: 160 typedef std::vector<SubSection*> SubSectionList; 161 162 void AddSubSection(SubSection* subsection) { 163 children_.push_back(subsection); 164 } 165 166 SubSection* parent_; 167 std::string name_; 168 std::string title_; 169 170 SubSectionList children_; 171}; 172 173class ProxyServiceCurrentConfigSubSection : public SubSection { 174 public: 175 explicit ProxyServiceCurrentConfigSubSection(SubSection* parent) 176 : SubSection(parent, "config", "Current configuration") { 177 } 178 179 virtual void OutputBody(URLRequestContext* context, std::string* out) { 180 DrawCommandButton("Force reload", "reload-proxy-config", out); 181 182 net::ProxyService* proxy_service = context->proxy_service(); 183 if (proxy_service->config_has_been_initialized()) { 184 // net::ProxyConfig defines an operator<<. 185 std::ostringstream stream; 186 stream << proxy_service->config(); 187 OutputTextInPre(stream.str(), out); 188 } else { 189 out->append("<i>Not yet initialized</i>"); 190 } 191 } 192}; 193 194class ProxyServiceLastInitLogSubSection : public SubSection { 195 public: 196 explicit ProxyServiceLastInitLogSubSection(SubSection* parent) 197 : SubSection(parent, "init_log", "Last initialized load log") { 198 } 199 200 virtual void OutputBody(URLRequestContext* context, std::string* out) { 201 net::ProxyService* proxy_service = context->proxy_service(); 202 net::LoadLog* log = proxy_service->init_proxy_resolver_log(); 203 if (log) { 204 OutputTextInPre(net::LoadLogUtil::PrettyPrintAsEventTree(log), out); 205 } else { 206 out->append("<i>None.</i>"); 207 } 208 } 209}; 210 211class ProxyServiceBadProxiesSubSection : public SubSection { 212 public: 213 explicit ProxyServiceBadProxiesSubSection(SubSection* parent) 214 : SubSection(parent, "bad_proxies", "Bad Proxies") { 215 } 216 217 virtual void OutputBody(URLRequestContext* context, std::string* out) { 218 net::ProxyService* proxy_service = context->proxy_service(); 219 const net::ProxyRetryInfoMap& bad_proxies_map = 220 proxy_service->proxy_retry_info(); 221 222 DrawCommandButton("Clear", "clear-badproxies", out); 223 224 out->append("<table border=1>"); 225 out->append("<tr><th>Bad proxy server</th>" 226 "<th>Remaining time until retry (ms)</th></tr>"); 227 228 for (net::ProxyRetryInfoMap::const_iterator it = bad_proxies_map.begin(); 229 it != bad_proxies_map.end(); ++it) { 230 const std::string& proxy_uri = it->first; 231 const net::ProxyRetryInfo& retry_info = it->second; 232 233 // Note that ttl_ms may be negative, for the cases where entries have 234 // expired but not been garbage collected yet. 235 int ttl_ms = static_cast<int>( 236 (retry_info.bad_until - base::TimeTicks::Now()).InMilliseconds()); 237 238 // Color expired entries blue. 239 if (ttl_ms > 0) 240 out->append("<tr>"); 241 else 242 out->append("<tr style='color:blue'>"); 243 244 StringAppendF(out, "<td>%s</td><td>%d</td>", 245 EscapeForHTML(proxy_uri).c_str(), 246 ttl_ms); 247 248 out->append("</tr>"); 249 } 250 out->append("</table>"); 251 } 252}; 253 254class ProxyServiceSubSection : public SubSection { 255 public: 256 explicit ProxyServiceSubSection(SubSection* parent) 257 : SubSection(parent, "proxyservice", "ProxyService") { 258 AddSubSection(new ProxyServiceCurrentConfigSubSection(this)); 259 AddSubSection(new ProxyServiceLastInitLogSubSection(this)); 260 AddSubSection(new ProxyServiceBadProxiesSubSection(this)); 261 } 262}; 263 264class HostResolverCacheSubSection : public SubSection { 265 public: 266 explicit HostResolverCacheSubSection(SubSection* parent) 267 : SubSection(parent, "hostcache", "HostCache") { 268 } 269 270 virtual void OutputBody(URLRequestContext* context, std::string* out) { 271 const net::HostCache* host_cache = GetHostCache(context); 272 273 if (!host_cache || host_cache->caching_is_disabled()) { 274 out->append("<i>Caching is disabled.</i>"); 275 return; 276 } 277 278 DrawCommandButton("Clear", "clear-hostcache", out); 279 280 StringAppendF( 281 out, 282 "<ul><li>Size: %" PRIuS "</li>" 283 "<li>Capacity: %" PRIuS "</li>" 284 "<li>Time to live (ms) for success entries: %" PRId64"</li>" 285 "<li>Time to live (ms) for failure entries: %" PRId64"</li></ul>", 286 host_cache->size(), 287 host_cache->max_entries(), 288 host_cache->success_entry_ttl().InMilliseconds(), 289 host_cache->failure_entry_ttl().InMilliseconds()); 290 291 out->append("<table border=1>" 292 "<tr>" 293 "<th>Host</th>" 294 "<th>Address family</th>" 295 "<th>Address list</th>" 296 "<th>Time to live (ms)</th>" 297 "</tr>"); 298 299 for (net::HostCache::EntryMap::const_iterator it = 300 host_cache->entries().begin(); 301 it != host_cache->entries().end(); 302 ++it) { 303 const net::HostCache::Key& key = it->first; 304 const net::HostCache::Entry* entry = it->second.get(); 305 306 std::string address_family_str = 307 AddressFamilyToString(key.address_family); 308 309 // Note that ttl_ms may be negative, for the cases where entries have 310 // expired but not been garbage collected yet. 311 int ttl_ms = static_cast<int>( 312 (entry->expiration - base::TimeTicks::Now()).InMilliseconds()); 313 314 // Color expired entries blue. 315 if (ttl_ms > 0) { 316 out->append("<tr>"); 317 } else { 318 out->append("<tr style='color:blue'>"); 319 } 320 321 // Stringify all of the addresses in the address list, separated 322 // by newlines (br). 323 std::string address_list_html; 324 325 if (entry->error != net::OK) { 326 address_list_html = "<span style='font-weight: bold; color:red'>" + 327 EscapeForHTML(net::ErrorToString(entry->error)) + 328 "</span>"; 329 } else { 330 const struct addrinfo* current_address = entry->addrlist.head(); 331 while (current_address) { 332 if (!address_list_html.empty()) 333 address_list_html += "<br>"; 334 address_list_html += EscapeForHTML( 335 net::NetAddressToString(current_address)); 336 current_address = current_address->ai_next; 337 } 338 } 339 340 StringAppendF(out, 341 "<td>%s</td><td>%s</td><td>%s</td>" 342 "<td>%d</td></tr>", 343 EscapeForHTML(key.hostname).c_str(), 344 EscapeForHTML(address_family_str).c_str(), 345 address_list_html.c_str(), 346 ttl_ms); 347 } 348 349 out->append("</table>"); 350 } 351 352 static std::string AddressFamilyToString(net::AddressFamily address_family) { 353 switch (address_family) { 354 case net::ADDRESS_FAMILY_IPV4: 355 return "IPV4"; 356 case net::ADDRESS_FAMILY_IPV6: 357 return "IPV6"; 358 case net::ADDRESS_FAMILY_UNSPECIFIED: 359 return "UNSPECIFIED"; 360 default: 361 NOTREACHED(); 362 return "???"; 363 } 364 } 365}; 366 367class HostResolverTraceSubSection : public SubSection { 368 public: 369 explicit HostResolverTraceSubSection(SubSection* parent) 370 : SubSection(parent, "trace", "Trace of requests") { 371 } 372 373 virtual void OutputBody(URLRequestContext* context, std::string* out) { 374 net::HostResolverImpl* resolver = GetHostResolverImpl(context); 375 if (!resolver) { 376 out->append("<i>Tracing is not supported by this resolver.</i>"); 377 return; 378 } 379 380 DrawCommandButton("Clear", "clear-hostresolver-trace", out); 381 382 if (resolver->IsRequestsTracingEnabled()) { 383 DrawCommandButton("Disable tracing", "hostresolver-trace-disable", out); 384 } else { 385 DrawCommandButton("Enable tracing", "hostresolver-trace-enable", out); 386 } 387 388 scoped_refptr<net::LoadLog> log = resolver->GetRequestsTrace(); 389 390 if (log) { 391 OutputTextInPre(net::LoadLogUtil::PrettyPrintAsEventTree(log), out); 392 } else { 393 out->append("<p><i>No trace information, must enable tracing.</i></p>"); 394 } 395 } 396}; 397 398class HostResolverSubSection : public SubSection { 399 public: 400 explicit HostResolverSubSection(SubSection* parent) 401 : SubSection(parent, "hostresolver", "HostResolver") { 402 AddSubSection(new HostResolverCacheSubSection(this)); 403 AddSubSection(new HostResolverTraceSubSection(this)); 404 } 405}; 406 407// Helper for the URLRequest "outstanding" and "live" sections. 408void OutputURLAndLoadLog(const GURL& url, 409 const net::LoadLog* log, 410 std::string* out) { 411 out->append("<li>"); 412 out->append("<nobr>"); 413 out->append(EscapeForHTML(url.possibly_invalid_spec())); 414 out->append("</nobr>"); 415 if (log) 416 OutputTextInPre(net::LoadLogUtil::PrettyPrintAsEventTree(log), out); 417 out->append("</li>"); 418} 419 420class URLRequestLiveSubSection : public SubSection { 421 public: 422 explicit URLRequestLiveSubSection(SubSection* parent) 423 : SubSection(parent, "outstanding", "Outstanding requests") { 424 } 425 426 virtual void OutputBody(URLRequestContext* context, std::string* out) { 427 std::vector<URLRequest*> requests = 428 context->url_request_tracker()->GetLiveRequests(); 429 430 out->append("<ol>"); 431 for (size_t i = 0; i < requests.size(); ++i) { 432 // Reverse the list order, so we dispay from most recent to oldest. 433 size_t index = requests.size() - i - 1; 434 OutputURLAndLoadLog(requests[index]->original_url(), 435 requests[index]->load_log(), 436 out); 437 } 438 out->append("</ol>"); 439 } 440}; 441 442class URLRequestRecentSubSection : public SubSection { 443 public: 444 explicit URLRequestRecentSubSection(SubSection* parent) 445 : SubSection(parent, "recent", "Recently completed requests") { 446 } 447 448 virtual void OutputBody(URLRequestContext* context, std::string* out) { 449 RequestTracker<URLRequest>::RecentRequestInfoList recent = 450 context->url_request_tracker()->GetRecentlyDeceased(); 451 452 DrawCommandButton("Clear", "clear-urlrequest-graveyard", out); 453 454 out->append("<ol>"); 455 for (size_t i = 0; i < recent.size(); ++i) { 456 // Reverse the list order, so we dispay from most recent to oldest. 457 size_t index = recent.size() - i - 1; 458 OutputURLAndLoadLog(recent[index].original_url, 459 recent[index].load_log, out); 460 } 461 out->append("</ol>"); 462 } 463}; 464 465class URLRequestSubSection : public SubSection { 466 public: 467 explicit URLRequestSubSection(SubSection* parent) 468 : SubSection(parent, "urlrequest", "URLRequest") { 469 AddSubSection(new URLRequestLiveSubSection(this)); 470 AddSubSection(new URLRequestRecentSubSection(this)); 471 } 472}; 473 474class HttpCacheStatsSubSection : public SubSection { 475 public: 476 explicit HttpCacheStatsSubSection(SubSection* parent) 477 : SubSection(parent, "stats", "Statistics") { 478 } 479 480 virtual void OutputBody(URLRequestContext* context, std::string* out) { 481 ViewCacheHelper::GetStatisticsHTML(context, out); 482 } 483}; 484 485class HttpCacheSection : public SubSection { 486 public: 487 explicit HttpCacheSection(SubSection* parent) 488 : SubSection(parent, "httpcache", "HttpCache") { 489 AddSubSection(new HttpCacheStatsSubSection(this)); 490 } 491 492 virtual void OutputBody(URLRequestContext* context, std::string* out) { 493 // Advertise the view-cache URL (too much data to inline it). 494 out->append("<p><a href='/"); 495 out->append(kViewHttpCacheSubPath); 496 out->append("'>View all cache entries</a></p>"); 497 } 498}; 499 500class SocketStreamLiveSubSection : public SubSection { 501 public: 502 explicit SocketStreamLiveSubSection(SubSection* parent) 503 : SubSection(parent, "live", "Live SocketStreams") { 504 } 505 506 virtual void OutputBody(URLRequestContext* context, std::string* out) { 507 std::vector<net::SocketStream*> sockets = 508 context->socket_stream_tracker()->GetLiveRequests(); 509 510 out->append("<ol>"); 511 for (size_t i = 0; i < sockets.size(); ++i) { 512 // Reverse the list order, so we dispay from most recent to oldest. 513 size_t index = sockets.size() - i - 1; 514 OutputURLAndLoadLog(sockets[index]->url(), 515 sockets[index]->load_log(), 516 out); 517 } 518 out->append("</ol>"); 519 } 520}; 521 522class SocketStreamRecentSubSection : public SubSection { 523 public: 524 explicit SocketStreamRecentSubSection(SubSection* parent) 525 : SubSection(parent, "recent", "Recently completed SocketStreams") { 526 } 527 528 virtual void OutputBody(URLRequestContext* context, std::string* out) { 529 RequestTracker<net::SocketStream>::RecentRequestInfoList recent = 530 context->socket_stream_tracker()->GetRecentlyDeceased(); 531 532 DrawCommandButton("Clear", "clear-socketstream-graveyard", out); 533 534 out->append("<ol>"); 535 for (size_t i = 0; i < recent.size(); ++i) { 536 // Reverse the list order, so we dispay from most recent to oldest. 537 size_t index = recent.size() - i - 1; 538 OutputURLAndLoadLog(recent[index].original_url, 539 recent[index].load_log, out); 540 } 541 out->append("</ol>"); 542 } 543}; 544 545class SocketStreamSubSection : public SubSection { 546 public: 547 explicit SocketStreamSubSection(SubSection* parent) 548 : SubSection(parent, "socketstream", "SocketStream") { 549 AddSubSection(new SocketStreamLiveSubSection(this)); 550 AddSubSection(new SocketStreamRecentSubSection(this)); 551 } 552}; 553 554class AllSubSections : public SubSection { 555 public: 556 AllSubSections() : SubSection(NULL, "", "") { 557 AddSubSection(new ProxyServiceSubSection(this)); 558 AddSubSection(new HostResolverSubSection(this)); 559 AddSubSection(new URLRequestSubSection(this)); 560 AddSubSection(new HttpCacheSection(this)); 561 AddSubSection(new SocketStreamSubSection(this)); 562 } 563}; 564 565bool HandleCommand(const std::string& command, URLRequestContext* context) { 566 if (StartsWithASCII(command, "full-logging-", true)) { 567 bool enable_full_logging = (command == "full-logging-enable"); 568 context->url_request_tracker()->SetUnbounded(enable_full_logging); 569 context->socket_stream_tracker()->SetUnbounded(enable_full_logging); 570 return true; 571 } 572 573 if (StartsWithASCII(command, "hostresolver-trace-", true)) { 574 bool enable_tracing = (command == "hostresolver-trace-enable"); 575 if (GetHostResolverImpl(context)) { 576 GetHostResolverImpl(context)->EnableRequestsTracing(enable_tracing); 577 } 578 } 579 580 if (command == "clear-urlrequest-graveyard") { 581 context->url_request_tracker()->ClearRecentlyDeceased(); 582 return true; 583 } 584 585 if (command == "clear-socketstream-graveyard") { 586 context->socket_stream_tracker()->ClearRecentlyDeceased(); 587 return true; 588 } 589 590 if (command == "clear-hostcache") { 591 net::HostCache* host_cache = GetHostCache(context); 592 if (host_cache) 593 host_cache->clear(); 594 return true; 595 } 596 597 if (command == "clear-badproxies") { 598 context->proxy_service()->ClearBadProxiesCache(); 599 return true; 600 } 601 602 if (command == "clear-hostresolver-trace") { 603 if (GetHostResolverImpl(context)) 604 GetHostResolverImpl(context)->ClearRequestsTrace(); 605 } 606 607 if (command == "reload-proxy-config") { 608 context->proxy_service()->ForceReloadProxyConfig(); 609 return true; 610 } 611 612 return false; 613} 614 615// Process any query strings in the request (for actions like toggling 616// full logging. 617void ProcessQueryStringCommands(URLRequestContext* context, 618 const std::string& query) { 619 if (!StartsWithASCII(query, "commands=", true)) { 620 // Not a recognized format. 621 return; 622 } 623 624 std::string commands_str = query.substr(strlen("commands=")); 625 commands_str = UnescapeURLComponent(commands_str, UnescapeRule::NORMAL); 626 627 // The command list is comma-separated. 628 std::vector<std::string> commands; 629 SplitString(commands_str, ',', &commands); 630 631 for (size_t i = 0; i < commands.size(); ++i) 632 HandleCommand(commands[i], context); 633} 634 635// Appends some HTML controls to |data| that allow the user to enable full 636// logging, and clear some of the already logged data. 637void DrawControlsHeader(URLRequestContext* context, std::string* data) { 638 bool is_full_logging_enabled = 639 context->url_request_tracker()->IsUnbounded() && 640 context->socket_stream_tracker()->IsUnbounded(); 641 642 data->append("<div style='margin-bottom: 10px'>"); 643 644 if (is_full_logging_enabled) { 645 DrawCommandButton("Disable full logging", "full-logging-disable", data); 646 } else { 647 DrawCommandButton("Enable full logging", "full-logging-enable", data); 648 } 649 650 DrawCommandButton("Clear all data", 651 // Send a list of comma separated commands: 652 "clear-badproxies," 653 "clear-hostcache," 654 "clear-urlrequest-graveyard," 655 "clear-socketstream-graveyard," 656 "clear-hostresolver-trace", 657 data); 658 659 data->append("</div>"); 660} 661 662} // namespace 663 664bool URLRequestViewNetInternalsJob::GetData(std::string* mime_type, 665 std::string* charset, 666 std::string* data) const { 667 mime_type->assign("text/html"); 668 charset->assign("UTF-8"); 669 670 URLRequestContext* context = request_->context(); 671 672 data->clear(); 673 674 // Use a different handler for "view-cache/*" subpaths. 675 std::string cache_key; 676 if (GetViewCacheKeyForRequest(&cache_key)) { 677 GURL url = url_format_->MakeURL(kViewHttpCacheSubPath + std::string("/")); 678 ViewCacheHelper::GetEntryInfoHTML(cache_key, context, url.spec(), data); 679 return true; 680 } 681 682 // Handle any query arguments as a command request, then redirect back to 683 // the same URL stripped of query parameters. The redirect happens as part 684 // of IsRedirectResponse(). 685 if (request_->url().has_query()) { 686 ProcessQueryStringCommands(context, request_->url().query()); 687 return true; 688 } 689 690 std::string details = url_format_->GetDetails(request_->url()); 691 692 data->append("<!DOCTYPE HTML>" 693 "<html><head><title>Network internals</title>" 694 "<style>" 695 "body { font-family: sans-serif; font-size: 0.8em; }\n" 696 "tt, code, pre { font-family: WebKitHack, monospace; }\n" 697 ".subsection_body { margin: 10px 0 10px 2em; }\n" 698 ".subsection_title { font-weight: bold; }\n" 699 "</style>" 700 "<script>\n" 701 702 // Unfortunately we can't do XHR from chrome://net-internals 703 // because the chrome:// protocol restricts access. 704 // 705 // So instead, we will send commands by doing a form 706 // submission (which as a side effect will reload the page). 707 "function DoCommand(command) {\n" 708 " document.getElementById('cmd').value = command;\n" 709 " document.getElementById('cmdsender').submit();\n" 710 "}\n" 711 712 "</script>\n" 713 "</head><body>" 714 "<form action='' method=GET id=cmdsender>" 715 "<input type='hidden' id=cmd name='commands'>" 716 "</form>" 717 "<p><a href='http://dev.chromium.org/" 718 "developers/design-documents/view-net-internals'>" 719 "Help: how do I use this?</a></p>"); 720 721 DrawControlsHeader(context, data); 722 723 SubSection* all = Singleton<AllSubSections>::get(); 724 SubSection* section = all; 725 726 // Display only the subsection tree asked for. 727 if (!details.empty()) 728 section = all->FindSubSectionByName(details); 729 730 if (section) { 731 section->OutputRecursive(context, url_format_, data); 732 } else { 733 data->append("<i>Nothing found for \""); 734 data->append(EscapeForHTML(details)); 735 data->append("\"</i>"); 736 } 737 738 data->append("</body></html>"); 739 740 return true; 741} 742 743bool URLRequestViewNetInternalsJob::IsRedirectResponse(GURL* location, 744 int* http_status_code) { 745 if (request_->url().has_query() && !GetViewCacheKeyForRequest(NULL)) { 746 // Strip the query parameters. 747 GURL::Replacements replacements; 748 replacements.ClearQuery(); 749 *location = request_->url().ReplaceComponents(replacements); 750 *http_status_code = 307; 751 return true; 752 } 753 return false; 754} 755 756bool URLRequestViewNetInternalsJob::GetViewCacheKeyForRequest( 757 std::string* key) const { 758 std::string path = url_format_->GetDetails(request_->url()); 759 if (!StartsWithASCII(path, kViewHttpCacheSubPath, true)) 760 return false; 761 762 if (path.size() > strlen(kViewHttpCacheSubPath) && 763 path[strlen(kViewHttpCacheSubPath)] != '/') 764 return false; 765 766 if (key && path.size() > strlen(kViewHttpCacheSubPath) + 1) 767 *key = path.substr(strlen(kViewHttpCacheSubPath) + 1); 768 769 return true; 770} 771