blob: 51eeddb5cf1778aa9360c7b5f850a5355998cc3c [file] [log] [blame]
// Copyright 2014 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.
#import "ios/web/net/request_group_util.h"
#import <Foundation/Foundation.h>
#include "base/base64.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/sys_string_conversions.h"
#import "net/base/mac/url_conversions.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
namespace {
// Minimum length for a request group ID. A shorter string is considered as an
// invalid ID.
const int kMinimumIDLength = 5;
namespace web {
// Generates a request-group ID used to correlate web requests with the embedder
// that triggered it. It is important that the return value should not be unique
// for different users. See crbug/355613 for context.
NSString* GenerateNewRequestGroupID() {
const unsigned int kGroupSize = 1000;
static unsigned long count = 0;
static unsigned int offset = base::RandInt(0, kGroupSize - 1);
unsigned long current = count++;
if (current < kGroupSize)
current = (current + offset) % kGroupSize;
// The returned string must have a minimum of kMinimumIDLength characters, and
// no spaces.
// TODO(blundell): Develop a long-term solution to this problem.
return [NSString stringWithFormat:@"%06lu", current];
NSString* ExtractRequestGroupIDFromUserAgent(NSString* user_agent) {
if (![user_agent length])
return nil;
// The request_group_id is wrapped by parenthesis in the last space-delimited
// fragment.
NSString* fragment =
[[user_agent componentsSeparatedByString:@" "] lastObject];
NSString* request_group_id =
[fragment hasPrefix:@"("] && [fragment hasSuffix:@")"]
? [fragment substringWithRange:NSMakeRange(1, [fragment length] - 2)]
: nil;
// GTLService constructs user agents that end with "(gzip)". To avoid these
// getting treated as having the request_group_id "gzip", short-circuit out if
// the request_group_id is not long enough to be a valid request_group_id (all
// valid request_group_id are at least kMinimumIDLength characters long).
// TODO(blundell): Develop a long-term solution to this problem.
if ([request_group_id length] < kMinimumIDLength)
return nil;
return request_group_id;
NSString* AddRequestGroupIDToUserAgent(NSString* base_user_agent,
NSString* request_group_id) {
if (!request_group_id)
return base_user_agent;
// TODO(blundell): Develop a long-term solution to this problem.
DCHECK([request_group_id length] >= kMinimumIDLength);
[NSString stringWithFormat:@"%@ (%@)", base_user_agent, request_group_id];
NSString* ExtractRequestGroupIDFromURL(NSURL* url) {
GURL gurl = net::GURLWithNSURL(url);
if (!gurl.has_username())
return nil;
std::string request_group_id_as_string;
if (base::Base64Decode(gurl.username(), &request_group_id_as_string))
return base::SysUTF8ToNSString(request_group_id_as_string);
return nil;
NSURL* AddRequestGroupIDToURL(NSURL* base_url, NSString* request_group_id) {
GURL url = net::GURLWithNSURL(base_url);
std::string base64RequestGroupID;
GURL::Replacements replacements;
url = url.ReplaceComponents(replacements);
return net::NSURLWithGURL(url);
NSString* ExtractRequestGroupIDFromRequest(NSURLRequest* request,
NSString* application_scheme) {
NSString* user_agent =
[[request allHTTPHeaderFields] objectForKey:@"User-Agent"];
NSString* request_group_id = ExtractRequestGroupIDFromUserAgent(user_agent);
if (request_group_id)
return request_group_id;
if (application_scheme &&
[[request.mainDocumentURL scheme]
caseInsensitiveCompare:application_scheme] == NSOrderedSame) {
return ExtractRequestGroupIDFromURL(request.mainDocumentURL);
return nil;
} // namespace web