root/chrome/browser/prerender/prerender_util.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetXModPagespeedBucketFromVersion
  2. IsPageSpeedServiceVersionNumber
  3. ReportPrerenderSchemeCancelReason
  4. MaybeGetQueryStringBasedAliasURL
  5. GetQueryStringBasedExperiment
  6. IsGoogleDomain
  7. IsGoogleSearchResultURL
  8. IsNoSwapInExperiment
  9. IsControlGroupExperiment
  10. GatherPagespeedData
  11. URLRequestResponseStarted
  12. ReportPrerenderExternalURL
  13. ReportUnsupportedPrerenderScheme

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/prerender/prerender_util.h"

#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/string_util.h"
#include "content/public/browser/resource_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "url/url_canon.h"
#include "url/url_parse.h"
#include "url/url_util.h"
#include "webkit/common/resource_type.h"

namespace prerender {

namespace {

const char kModPagespeedHeader[] = "X-Mod-Pagespeed";
const char kPageSpeedHeader[] = "X-Page-Speed";
const char kPagespeedServerHistogram[] =
    "Prerender.PagespeedHeader.ServerCounts";
const char kPagespeedVersionHistogram[] =
    "Prerender.PagespeedHeader.VersionCounts";

enum PagespeedHeaderServerType {
  PAGESPEED_TOTAL_RESPONSES = 0,
  PAGESPEED_MOD_PAGESPEED_SERVER = 1,
  PAGESPEED_NGX_PAGESPEED_SERVER = 2,
  PAGESPEED_PAGESPEED_SERVICE_SERVER = 3,
  PAGESPEED_UNKNOWN_SERVER = 4,
  PAGESPEED_SERVER_MAXIMUM = 5
};

// Private function to parse the PageSpeed version number and encode it in
// buckets 2 through 99: if it is in the format a.b.c.d-e the bucket will be
// 2 + 2 * (max(c, 10) - 10) + (d > 1 ? 1 : 0); if it is not in this format
// we return zero.
int GetXModPagespeedBucketFromVersion(const std::string& version) {
  int a, b, c, d, e;
  int num_parsed = sscanf(version.c_str(), "%d.%d.%d.%d-%d",
                          &a, &b, &c, &d, &e);
  int output = 0;
  if (num_parsed == 5) {
    output = 2;
    if (c > 10)
      output += 2 * (c - 10);
    if (d > 1)
      output++;
    if (output < 2 || output > 99)
      output = 0;
  }
  return output;
}

// Private function to parse the X-Page-Speed header value and determine
// whether it is in the PageSpeed Service format, namely m_n_dc were m_n is
// a version number and dc is an encoded 2-character value.
bool IsPageSpeedServiceVersionNumber(const std::string& version) {
  int a, b;
  char c, d, e;  // e is to detect EOL as we check that it /isn't/ converted.
  int num_parsed = sscanf(version.c_str(), "%d_%d_%c%c%c", &a, &b, &c, &d, &e);
  return (num_parsed == 4);
}

enum PrerenderSchemeCancelReason {
  PRERENDER_SCHEME_CANCEL_REASON_EXTERNAL_PROTOCOL,
  PRERENDER_SCHEME_CANCEL_REASON_DATA,
  PRERENDER_SCHEME_CANCEL_REASON_BLOB,
  PRERENDER_SCHEME_CANCEL_REASON_FILE,
  PRERENDER_SCHEME_CANCEL_REASON_FILESYSTEM,
  PRERENDER_SCHEME_CANCEL_REASON_WEBSOCKET,
  PRERENDER_SCHEME_CANCEL_REASON_FTP,
  PRERENDER_SCHEME_CANCEL_REASON_CHROME,
  PRERENDER_SCHEME_CANCEL_REASON_CHROME_EXTENSION,
  PRERENDER_SCHEME_CANCEL_REASON_ABOUT,
  PRERENDER_SCHEME_CANCEL_REASON_UNKNOWN,
  PRERENDER_SCHEME_CANCEL_REASON_MAX,
};

void ReportPrerenderSchemeCancelReason(PrerenderSchemeCancelReason reason) {
  UMA_HISTOGRAM_ENUMERATION(
      "Prerender.SchemeCancelReason", reason,
      PRERENDER_SCHEME_CANCEL_REASON_MAX);
}

}  // namespace

const char kChromeNavigateExtraDataKey[] = "chrome_navigate";

bool MaybeGetQueryStringBasedAliasURL(
    const GURL& url, GURL* alias_url) {
  DCHECK(alias_url);
  url_parse::Parsed parsed;
  url_parse::ParseStandardURL(url.spec().c_str(), url.spec().length(),
                              &parsed);
  url_parse::Component query = parsed.query;
  url_parse::Component key, value;
  while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key,
                                         &value)) {
    if (key.len != 3 || strncmp(url.spec().c_str() + key.begin, "url", key.len))
      continue;
    // We found a url= query string component.
    if (value.len < 1)
      continue;
    url_canon::RawCanonOutputW<1024> decoded_url;
    url_util::DecodeURLEscapeSequences(url.spec().c_str() + value.begin,
                                       value.len, &decoded_url);
    GURL new_url(base::string16(decoded_url.data(), decoded_url.length()));
    if (!new_url.is_empty() && new_url.is_valid()) {
      *alias_url = new_url;
      return true;
    }
    return false;
  }
  return false;
}

uint8 GetQueryStringBasedExperiment(const GURL& url) {
  url_parse::Parsed parsed;
  url_parse::ParseStandardURL(url.spec().c_str(), url.spec().length(),
                              &parsed);
  url_parse::Component query = parsed.query;
  url_parse::Component key, value;
  while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key,
                                         &value)) {
    if (key.len != 3 || strncmp(url.spec().c_str() + key.begin, "lpe", key.len))
      continue;

    // We found a lpe= query string component.
    if (value.len != 1)
      continue;
    uint8 exp = *(url.spec().c_str() + value.begin) - '0';
    if (exp < 1 || exp > 9)
      continue;
    return exp;
  }
  return kNoExperiment;
}

bool IsGoogleDomain(const GURL& url) {
  return StartsWithASCII(url.host(), std::string("www.google."), true);
}

bool IsGoogleSearchResultURL(const GURL& url) {
  if (!IsGoogleDomain(url))
    return false;
  return (url.path().empty() ||
          StartsWithASCII(url.path(), std::string("/search"), true) ||
          (url.path() == "/") ||
          StartsWithASCII(url.path(), std::string("/webhp"), true));
}

bool IsNoSwapInExperiment(uint8 experiment_id) {
  // Currently, experiments 5 and 6 fall in this category.
  return experiment_id == 5 || experiment_id == 6;
}

bool IsControlGroupExperiment(uint8 experiment_id) {
  // Currently, experiments 7 and 8 fall in this category.
  return experiment_id == 7 || experiment_id == 8;
}

void GatherPagespeedData(const ResourceType::Type resource_type,
                         const GURL& request_url,
                         const net::HttpResponseHeaders* response_headers) {
  if (resource_type != ResourceType::MAIN_FRAME ||
      !request_url.SchemeIsHTTPOrHTTPS())
    return;

  // bucket 0 counts every response seen.
  UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
                            PAGESPEED_TOTAL_RESPONSES,
                            PAGESPEED_SERVER_MAXIMUM);
  if (!response_headers)
    return;

  void* iter = NULL;
  std::string name;
  std::string value;
  while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
    if (name == kModPagespeedHeader) {
      // Bucket 1 counts occurences of the X-Mod-Pagespeed header.
      UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
                                PAGESPEED_MOD_PAGESPEED_SERVER,
                                PAGESPEED_SERVER_MAXIMUM);
      if (!value.empty()) {
        // If the header value is in the X-Mod-Pagespeed version number format
        // then increment the appropriate bucket, otherwise increment bucket 1,
        // which is the catch-all "unknown version number" bucket.
        int bucket = GetXModPagespeedBucketFromVersion(value);
        if (bucket > 0) {
          UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram, bucket);
        } else {
          UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram, 1);
        }
      }
      break;
    } else if (name == kPageSpeedHeader) {
      // X-Page-Speed header versions are either in the X-Mod-Pagespeed format,
      // indicating an nginx installation, or they're in the PageSpeed Service
      // format, indicating a PSS installation, or in some other format,
      // indicating an unknown installation [possibly IISpeed].
      if (!value.empty()) {
        int bucket = GetXModPagespeedBucketFromVersion(value);
        if (bucket > 0) {
          // Bucket 2 counts occurences of the X-Page-Speed header with a
          // value in the X-Mod-Pagespeed version number format. We also
          // count these responses in the version histogram.
          UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
                                    PAGESPEED_NGX_PAGESPEED_SERVER,
                                    PAGESPEED_SERVER_MAXIMUM);
          UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram, bucket);
        } else if (IsPageSpeedServiceVersionNumber(value)) {
          // Bucket 3 counts occurences of the X-Page-Speed header with a
          // value in the PageSpeed Service version number format.
          UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
                                    PAGESPEED_PAGESPEED_SERVICE_SERVER,
                                    PAGESPEED_SERVER_MAXIMUM);
        } else {
          // Bucket 4 counts occurences of all other values.
          UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
                                    PAGESPEED_UNKNOWN_SERVER,
                                    PAGESPEED_SERVER_MAXIMUM);
        }
      }
      break;
    }
  }
}

void URLRequestResponseStarted(net::URLRequest* request) {
  const content::ResourceRequestInfo* info =
      content::ResourceRequestInfo::ForRequest(request);
  GatherPagespeedData(info->GetResourceType(),
                      request->url(),
                      request->response_headers());
}

void ReportPrerenderExternalURL() {
  ReportPrerenderSchemeCancelReason(
      PRERENDER_SCHEME_CANCEL_REASON_EXTERNAL_PROTOCOL);
}

void ReportUnsupportedPrerenderScheme(const GURL& url) {
  if (url.SchemeIs("data")) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_DATA);
  } else if (url.SchemeIs("blob")) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_BLOB);
  } else if (url.SchemeIsFile()) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_FILE);
  } else if (url.SchemeIsFileSystem()) {
    ReportPrerenderSchemeCancelReason(
        PRERENDER_SCHEME_CANCEL_REASON_FILESYSTEM);
  } else if (url.SchemeIs("ws") || url.SchemeIs("wss")) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_WEBSOCKET);
  } else if (url.SchemeIs("ftp")) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_FTP);
  } else if (url.SchemeIs("chrome")) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_CHROME);
  } else if (url.SchemeIs("chrome-extension")) {
    ReportPrerenderSchemeCancelReason(
        PRERENDER_SCHEME_CANCEL_REASON_CHROME_EXTENSION);
  } else if (url.SchemeIs("about")) {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_ABOUT);
  } else {
    ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_UNKNOWN);
  }
}

}  // namespace prerender

/* [<][>][^][v][top][bottom][index][help] */