net: Provide better HTTP User-Agent strings

Previously, macOS used “User-Agent: crashpad_util_test (unknown version)
CFNetwork/807.2.14 Darwin/16.4.0 (x86_64)” and Windows gave results like
“User-Agent: Crashpad/0.8.0”.

Now, macOS uses “User-Agent: Crashpad/0.8.0 CFNetwork/807.2.14
Darwin/16.4.0 (x86_64)” and Windows uses “User-Agent: Crashpad/0.8.0
WinHTTP/10.0.14393.351 Windows_NT/10.0.14393.0 (x64)”

Change-Id: I578b44734cf59d79e3d9b6136b4b92f05acefe71
Reviewed-on: https://chromium-review.googlesource.com/447796
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Reviewed-by: Scott Graham <scottmg@chromium.org>
This commit is contained in:
Mark Mentovai 2017-02-28 22:02:34 -05:00
parent bf2c5155d2
commit 60be5a66a0
3 changed files with 139 additions and 12 deletions

View File

@ -106,17 +106,17 @@ void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) {
os_version_major_ = ffi.dwFileVersionMS >> 16;
os_version_minor_ = ffi.dwFileVersionMS & 0xffff;
os_version_bugfix_ = ffi.dwFileVersionLS >> 16;
os_version_build_ =
base::StringPrintf("%d", ffi.dwFileVersionLS & 0xffff);
os_version_build_ = base::StringPrintf("%u", ffi.dwFileVersionLS & 0xffff);
os_version_full_ = base::StringPrintf(
"%s %d.%d.%d.%s%s",
"%s %u.%u.%u.%s%s",
os_name.c_str(),
os_version_major_,
os_version_minor_,
os_version_bugfix_,
os_version_build_.c_str(),
flags_string.empty() ? "" : (std::string(" (") + flags_string + ")")
.c_str());
flags_string.empty()
? ""
: (std::string(" (") + flags_string + ")").c_str());
}
INITIALIZATION_STATE_SET_VALID(initialized_);

View File

@ -16,11 +16,14 @@
#include <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include <sys/utsname.h>
#include "base/mac/foundation_util.h"
#import "base/mac/scoped_nsobject.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "build/build_config.h"
#include "package.h"
#include "third_party/apple_cf/CFStreamAbstract.h"
#include "util/file/file_io.h"
#include "util/misc/implicit_cast.h"
@ -30,6 +33,80 @@ namespace crashpad {
namespace {
NSString* AppendEscapedFormat(NSString* base,
NSString* format,
NSString* data) {
return [base stringByAppendingFormat:
format,
[data stringByAddingPercentEncodingWithAllowedCharacters:
[[NSCharacterSet
characterSetWithCharactersInString:
@"()<>@,;:\\\"/[]?={} \t"] invertedSet]]];
}
// This builds the same User-Agent string that CFNetwork would build internally,
// but it uses PACKAGE_NAME and PACKAGE_VERSION in place of values obtained from
// the main bundles Info.plist.
NSString* UserAgentString() {
NSString* user_agent = [NSString string];
// CFNetwork would use the main bundles CFBundleName, or the main
// executables filename if none.
user_agent = AppendEscapedFormat(
user_agent, @"%@", [NSString stringWithUTF8String:PACKAGE_NAME]);
// CFNetwork would use the main bundles CFBundleVersion, or the string
// “(unknown version)” if none.
user_agent = AppendEscapedFormat(
user_agent, @"/%@", [NSString stringWithUTF8String:PACKAGE_VERSION]);
// Expected to be CFNetwork.
NSBundle* nsurl_bundle = [NSBundle bundleForClass:[NSURLRequest class]];
NSString* bundle_name = base::mac::ObjCCast<NSString>([nsurl_bundle
objectForInfoDictionaryKey:base::mac::CFToNSCast(kCFBundleNameKey)]);
if (bundle_name) {
user_agent = AppendEscapedFormat(user_agent, @" %@", bundle_name);
NSString* bundle_version = base::mac::ObjCCast<NSString>([nsurl_bundle
objectForInfoDictionaryKey:base::mac::CFToNSCast(kCFBundleVersionKey)]);
if (bundle_version) {
user_agent = AppendEscapedFormat(user_agent, @"/%@", bundle_version);
}
}
utsname os;
if (uname(&os) != 0) {
PLOG(WARNING) << "uname";
} else {
user_agent = AppendEscapedFormat(
user_agent, @" %@", [NSString stringWithUTF8String:os.sysname]);
user_agent = AppendEscapedFormat(
user_agent, @"/%@", [NSString stringWithUTF8String:os.release]);
// CFNetwork just uses the equivalent of os.machine to obtain the native
// (kernel) architecture. Here, give the process architecture as well as
// the native architecture. Use the same strings that the kernel would, so
// that they can be de-duplicated.
#if defined(ARCH_CPU_X86)
NSString* arch = @"i386";
#elif defined(ARCH_CPU_X86_64)
NSString* arch = @"x86_64";
#else
#error Port
#endif
user_agent = AppendEscapedFormat(user_agent, @" (%@", arch);
NSString* machine = [NSString stringWithUTF8String:os.machine];
if (![machine isEqualToString:arch]) {
user_agent = AppendEscapedFormat(user_agent, @"; %@", machine);
}
user_agent = [user_agent stringByAppendingString:@")"];
}
return user_agent;
}
// An implementation of CFReadStream. This implements the V0 callback
// scheme.
class HTTPBodyStreamCFReadStream {
@ -171,6 +248,13 @@ bool HTTPTransportMac::ExecuteSynchronously(std::string* response_body) {
timeoutInterval:timeout()];
[request setHTTPMethod:base::SysUTF8ToNSString(method())];
// If left to its own devices, CFNetwork would build a user-agent string
// based on keys in the main bundles Info.plist, giving ugly results if
// there is no Info.plist. Provide a User-Agent string similar to the one
// that CFNetwork would use, but with appropriate values in place of the
// Info.plist-derived strings.
[request setValue:UserAgentString() forHTTPHeaderField:@"User-Agent"];
for (const auto& pair : headers()) {
[request setValue:base::SysUTF8ToNSString(pair.second)
forHTTPHeaderField:base::SysUTF8ToNSString(pair.first)];

View File

@ -28,15 +28,59 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "package.h"
#include "util/file/file_io.h"
#include "util/numeric/safe_assignment.h"
#include "util/net/http_body.h"
#include "util/win/module_version.h"
namespace crashpad {
namespace {
const wchar_t kWinHttpDll[] = L"winhttp.dll";
std::string UserAgent() {
std::string user_agent =
base::StringPrintf("%s/%s WinHTTP", PACKAGE_NAME, PACKAGE_VERSION);
VS_FIXEDFILEINFO version;
if (GetModuleVersionAndType(base::FilePath(kWinHttpDll), &version)) {
user_agent.append(base::StringPrintf("/%u.%u.%u.%u",
version.dwFileVersionMS >> 16,
version.dwFileVersionMS & 0xffff,
version.dwFileVersionLS >> 16,
version.dwFileVersionLS & 0xffff));
}
if (GetModuleVersionAndType(base::FilePath(L"kernel32.dll"), &version) &&
(version.dwFileOS & VOS_NT_WINDOWS32) == VOS_NT_WINDOWS32) {
user_agent.append(base::StringPrintf(" Windows_NT/%u.%u.%u.%u (",
version.dwFileVersionMS >> 16,
version.dwFileVersionMS & 0xffff,
version.dwFileVersionLS >> 16,
version.dwFileVersionLS & 0xffff));
#if defined(ARCH_CPU_X86)
user_agent.append("x86");
#elif defined(ARCH_CPU_X86_64)
user_agent.append("x64");
#else
#error Port
#endif
BOOL is_wow64;
if (!IsWow64Process(GetCurrentProcess(), &is_wow64)) {
PLOG(WARNING) << "IsWow64Process";
} else if (is_wow64) {
user_agent.append("; WoW64");
}
user_agent.append(1, ')');
}
return user_agent;
}
// PLOG doesn't work for messages from WinHTTP, so we need to use
// FORMAT_MESSAGE_FROM_HMODULE + the dll name manually here.
std::string WinHttpMessage(const char* extra) {
@ -45,7 +89,7 @@ std::string WinHttpMessage(const char* extra) {
DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_HMODULE;
DWORD len = FormatMessageA(flags,
GetModuleHandle(L"winhttp.dll"),
GetModuleHandle(kWinHttpDll),
error_code,
0,
msgbuf,
@ -93,12 +137,11 @@ HTTPTransportWin::~HTTPTransportWin() {
}
bool HTTPTransportWin::ExecuteSynchronously(std::string* response_body) {
ScopedHINTERNET session(
WinHttpOpen(base::UTF8ToUTF16(PACKAGE_NAME "/" PACKAGE_VERSION).c_str(),
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0));
ScopedHINTERNET session(WinHttpOpen(base::UTF8ToUTF16(UserAgent()).c_str(),
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0));
if (!session.get()) {
LOG(ERROR) << WinHttpMessage("WinHttpOpen");
return false;