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