Commit cbc61810 authored by Michael Tang's avatar Michael Tang Committed by Commit Bot

Implement tag parsing and extra/app arguments

- Ported Omaha 3's ExtraArgsParser into chrome/updater.
- (At Sorin's request) Renamed ExtraArgs to something else (currently TagArgs, but I'm open to renaming it or any other item)
- Ported the Omaha 3 ExtraArgsParser tests

Change-Id: Ief76020716ae35fff26bacd19357847651acad5a
Bug: 1050738
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2081332
Commit-Queue: Michael Tang <tangm@microsoft.com>
Reviewed-by: default avatarSorin Jianu <sorin@chromium.org>
Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#756449}
parent e6e70d3e
...@@ -81,6 +81,9 @@ if (is_win || is_mac) { ...@@ -81,6 +81,9 @@ if (is_win || is_mac) {
"configurator.h", "configurator.h",
"installer.cc", "installer.cc",
"installer.h", "installer.h",
"lib_util.h",
"tag.cc",
"tag.h",
"update_apps.h", "update_apps.h",
"update_service_in_process.cc", "update_service_in_process.cc",
"update_service_in_process.h", "update_service_in_process.h",
...@@ -91,6 +94,7 @@ if (is_win || is_mac) { ...@@ -91,6 +94,7 @@ if (is_win || is_mac) {
if (is_mac) { if (is_mac) {
sources += [ sources += [
"installer_mac.cc", "installer_mac.cc",
"lib_util_mac.mm",
"mac/update_service_out_of_process.h", "mac/update_service_out_of_process.h",
"mac/update_service_out_of_process.mm", "mac/update_service_out_of_process.mm",
"server/mac/server.h", "server/mac/server.h",
...@@ -107,6 +111,7 @@ if (is_win || is_mac) { ...@@ -107,6 +111,7 @@ if (is_win || is_mac) {
if (is_win) { if (is_win) {
sources += [ sources += [
"installer_win.cc", "installer_win.cc",
"lib_util_win.cc",
"server/win/server.cc", "server/win/server.cc",
"server/win/server.h", "server/win/server.h",
"server/win/service_main.cc", "server/win/service_main.cc",
...@@ -179,16 +184,21 @@ if (is_win || is_mac) { ...@@ -179,16 +184,21 @@ if (is_win || is_mac) {
testonly = true testonly = true
sources = [ sources = [
"lib_util_unittest.cc",
"persisted_data_unittest.cc", "persisted_data_unittest.cc",
"prefs_unittest.cc", "prefs_unittest.cc",
"run_all_unittests.cc", "run_all_unittests.cc",
"tag_unittest.cc",
"test/integration_tests.cc", "test/integration_tests.cc",
"test/integration_tests.h", "test/integration_tests.h",
"unittest_util.cc",
"unittest_util.h",
"updater_unittest.cc", "updater_unittest.cc",
] ]
deps = [ deps = [
":base", ":base",
":lib",
":updater", ":updater",
":version_header", ":version_header",
"//base", "//base",
......
// Copyright 2020 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.
#ifndef CHROME_UPDATER_LIB_UTIL_H_
#define CHROME_UPDATER_LIB_UTIL_H_
#include <string>
#include "base/strings/string_piece.h"
namespace updater {
// This is a lightweight implementation of net::UnescapeURLComponent to avoid a
// dependency on //net using the following rules: NORMAL | SPACES |
// PATH_SEPARATORS | URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS.
//
// |escaped_text| should only contain ASCII text.
//
// Returns back |escaped_text| if unescaping failed.
std::string UnescapeURLComponent(base::StringPiece escaped_text);
} // namespace updater
#endif // CHROME_UPDATER_LIB_UTIL_H_
// Copyright 2020 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/updater/lib_util.h"
#import <Cocoa/Cocoa.h>
#include "base/strings/sys_string_conversions.h"
namespace updater {
std::string UnescapeURLComponent(base::StringPiece escaped_text_piece) {
@autoreleasepool {
NSString* escaped_text = base::SysUTF8ToNSString(escaped_text_piece);
// Escape stray percent signs not followed by a hex byte to match the //net
// and Windows' ::UnescapeUrlA behavior of ignoring invalid percent codes.
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:@"%(?![a-f0-9]{2})"
options:NSRegularExpressionCaseInsensitive
error:nil];
escaped_text = [regex
stringByReplacingMatchesInString:escaped_text
options:0
range:NSMakeRange(0, [escaped_text length])
withTemplate:@"%25"];
NSString* unescaped_text = [escaped_text stringByRemovingPercentEncoding];
return base::SysNSStringToUTF8(unescaped_text);
}
}
} // namespace updater
// Copyright 2020 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/updater/lib_util.h"
#include "base/strings/string_piece.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace updater {
// Characters that must be percent-encoded.
TEST(UnescapeUrlComponentTest, ReservedCharacters) {
std::map<base::StringPiece, base::StringPiece> reserved_characters = {
{"!", "%21"}, {"#", "%23"}, {"$", "%24"}, {"%", "%25"}, {"&", "%26"},
{"'", "%27"}, {"(", "%28"}, {")", "%29"}, {"*", "%2A"}, {"+", "%2B"},
{",", "%2C"}, {"/", "%2F"}, {":", "%3A"}, {";", "%3B"}, {"=", "%3D"},
{"?", "%3F"}, {"@", "%40"}, {"[", "%5B"}, {"]", "%5D"}};
for (auto pair : reserved_characters) {
EXPECT_EQ(pair.first, UnescapeURLComponent(pair.second));
}
for (auto pair : reserved_characters) {
// If input contains a reserved character, just ignore it.
std::string escaped_includes_reserved = pair.first.as_string() + "%20";
std::string unescaped = pair.first.as_string() + " ";
EXPECT_EQ(unescaped, UnescapeURLComponent(escaped_includes_reserved));
}
}
TEST(UnescapeUrlComponentTest, CommonCharacters) {
std::map<base::StringPiece, base::StringPiece> common_characters = {
{"\n", "%0A"}, {"\r", "%0D"}, {" ", "%20"}, {"\"", "%22"},
{"%", "%25"}, {"-", "%2D"}, {".", "%2E"}, {"<", "%3C"},
{">", "%3E"}, {"\\", "%5C"}, {"^", "%5E"}, {"_", "%5F"},
{"`", "%60"}, {"{", "%7B"}, {"|", "%7C"}, {"}", "%7D"},
{"~", "%7E"}, {"£", "%C2%A3"}, {"円", "%E5%86%86"}};
for (auto pair : common_characters) {
EXPECT_EQ(pair.first, UnescapeURLComponent(pair.second));
}
}
TEST(UnescapeUrlComponentTest, MixedEscapedAndUnicode) {
EXPECT_EQ("££", UnescapeURLComponent("£%C2%A3"));
}
} // namespace updater
// Copyright 2020 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/updater/lib_util.h"
#include "base/strings/string_util.h"
#include "base/win/shlwapi.h"
#include "base/win/windows_full.h"
#include <wininet.h> // For INTERNET_MAX_URL_LENGTH.
namespace updater {
std::string UnescapeURLComponent(base::StringPiece escaped_text_piece) {
if (escaped_text_piece.empty())
return {};
std::string escaped_text = escaped_text_piece.as_string();
// UrlUnescapeA doesn't modify the buffer unless passed URL_UNESCAPE_INPLACE.
char* escaped_text_ptr = const_cast<char*>(escaped_text.data());
DWORD buf_len = INTERNET_MAX_URL_LENGTH;
std::vector<char> unescaped_text_buf(buf_len);
HRESULT res =
::UrlUnescapeA(escaped_text_ptr, unescaped_text_buf.data(), &buf_len, 0);
CHECK(res != E_POINTER);
CHECK_LE(buf_len, INTERNET_MAX_URL_LENGTH);
if (FAILED(res))
return escaped_text;
return {unescaped_text_buf.data(), buf_len};
}
} // namespace updater
This diff is collapsed.
// Copyright 2020 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.
#ifndef CHROME_UPDATER_TAG_H_
#define CHROME_UPDATER_TAG_H_
#include <string>
#include <vector>
#include "base/optional.h"
#include "base/strings/string_piece.h"
namespace updater {
namespace tagging {
// This struct contains the attributes for a given app parsed from a part of the
// metainstaller tag. It contains minimal policy and is intended to be a
// near-direct mapping from tag to memory. See TagArgs, which stores a list of
// these.
//
// An empty string in std::string members indicates that the given attribute did
// not appear in the tag for this app.
struct AppArgs {
// Represents application requirements for admin.
enum class NeedsAdmin {
// The application will install per user.
kNo = 0,
// The application will install machine-wide.
kYes,
// The application will install machine-wide if permissions allow, else will
// install per-user.
kPrefers,
};
// |app_id| must not be empty and will be made lowercase.
explicit AppArgs(base::StringPiece app_id);
~AppArgs();
AppArgs(AppArgs& other);
AppArgs& operator=(AppArgs& other);
AppArgs(AppArgs&& other);
AppArgs& operator=(AppArgs&& other);
// An ASCII-encoded lowercase string. Must not be empty.
std::string app_id;
std::string app_name;
std::string ap;
std::string encoded_installer_data;
std::string install_data_index;
std::string experiment_labels;
std::string untrusted_data;
base::Optional<NeedsAdmin> needs_admin;
};
// This struct contains the attributes parsed from a metainstaller tag. An empty
// string in std::string members indicates that the given attribute did not
// appear in the tag.
struct TagArgs {
// Must be kept in sync with the enum in
// `google_update\google_update_idl.idl`. Do not include `BrowserType::kMax`
// in the IDL file. Do not move or remove existing elements.
enum class BrowserType {
kUnknown = 0,
kDefault = 1,
kInternetExplorer = 2,
kFirefox = 3,
kChrome = 4,
// Add new browsers above this.
kMax
};
TagArgs();
~TagArgs();
TagArgs(TagArgs& other);
TagArgs& operator=(TagArgs& other);
TagArgs(TagArgs&& other);
TagArgs& operator=(TagArgs&& other);
std::string bundle_name;
std::string installation_id;
std::string brand_code;
std::string client_id;
std::string experiment_labels;
std::string referral_id;
std::string language;
base::Optional<BrowserType> browser_type;
base::Optional<bool> flighting = false;
base::Optional<bool> usage_stats_enable;
// List of apps to install.
std::vector<AppArgs> apps;
};
// List of possible error states that the parser can encounter.
enum class ErrorCode {
// Parsing succeeded.
kSuccess,
// The attribute's name is unrecognized as an arg parameter.
kUnrecognizedName,
// The tag contains disallowed characters.
// See |kDisallowedCharsInExtraArgs|.
kTagIsInvalid,
// All attributes require a value.
kAttributeMustHaveValue,
// An app attribute was specified before an app id was specified.
kApp_AppIdNotSpecified,
// The app's experiment label cannot be whitespace.
kApp_ExperimentLabelsCannotBeWhitespace,
// The specified app id is not a valid.
// It must be ASCII-encoded and 512 bytes or less.
kApp_AppIdIsNotValid,
// The app name cannot be whitespace.
kApp_AppNameCannotBeWhitespace,
// The needsadmin value must be "yes", "no", or "prefers".
kApp_NeedsAdminValueIsInvalid,
// No app matches provided app id. Installer data can only be added to an app
// that has been previously specified in the TagArgs.
kAppInstallerData_AppIdNotFound,
// Cannot specify installer data before specifying at least one app id.
kAppInstallerData_InstallerDataCannotBeSpecifiedBeforeAppId,
// The bundle name cannot be whitespace.
kGlobal_BundleNameCannotBeWhitespace,
// The updater experiment label cannot be whitespace.
kGlobal_ExperimentLabelsCannotBeWhitespace,
// The browser type specified is invalid. It must be an integer matching the
// TagArgs::BrowserType enum.
kGlobal_BrowserTypeIsInvalid,
// The flighting value could not be parsed as a boolean.
kGlobal_FlightingValueIsNotABoolean,
// The usage stats value must be 0 or 1.
// Note: A value of 2 is considered the same as not specifying the usage
// stats.
kGlobal_UsageStatsValueIsInvalid,
};
// The metainstaller tag contains the metadata used to configure the updater as
// a metainstaller. This usually comes from a 3rd party source, either as
// command-line arguments or embedded in the metainstaller image after it is
// signed.
//
// The metainstaller is generic. It gets bound to a specific application by
// using this configuration.
//
// This function parses |tag| and |app_installer_data_args| into |args|.
//
// |tag| is a querystring-encoded ordered list of key-value pairs. All values
// are unescaped from url-encoding. The following keys are valid and affect the
// global parameters and have the following constraints on the value:
// - bundlename Must not contain only whitespace.
// - iid Can be any string.
// - brand Can be any string.
// - client Can be any string.
// - omahaexperiments Must not contain only whitespace.
// - referral Can be any string.
// - browser Must be a positive integer greater than 0 and less than
// TagArgs::BrowserType::kMax.
// - lang Can be any string.
// - flighting Must be "true" or "false".
// - usagestats Must be "0", "1", or "2".
//
// The following keys specify app-specific attributes. "appid" must be specified
// before any other app attribute to specify the "current" app. Other app
// attributes will then affect the parameters of the most recently specified app
// ID. For example, if the tag is
// "appid=App1&brand=BrandForApp1&appid=App2&ap=ApForApp2&iid=GlobalInstallId",
// the resulting tag will look like:
// TagArgs {
// iid = GlobalInstallId
// apps = [
// AppArgs {
// appid = App1
// brand = BrandForApp1
// }
// AppArgs {
// appid = App2
// ap = ApForApp2
// }
// ]
// }
// These attributes has the following constraints on the value:
// - appid Can be any ASCII string. Case-insensitive.
// - ap Can be any string.
// - experiments Must not contain only whitespace.
// - appname Must not contain only whitespace.
// - needsadmin Must be "yes", "no", or "prefers".
// - installdataindex Can by any string.
// - untrusteddata Can be any string.
//
// |app_installer_data_args| is also a querystring-encoded ordered list of
// key-value pairs. Unlike in the |tag|, the values are no unescaped. The
// following keys are valid and affect the app installer data parameters and
// have the following constraints on the value:
// - appid Must be a valid app id specified in |tag|.
// - installerdata Can be any string. Must be specified after appid.
//
// Note: This method assumes all attribute names are ASCII.
ErrorCode Parse(base::StringPiece tag,
base::Optional<base::StringPiece> app_installer_data_args,
TagArgs* args);
} // namespace tagging
} // namespace updater
#endif // CHROME_UPDATER_TAG_H_
This diff is collapsed.
// Copyright 2020 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/updater/unittest_util.h"
#include "chrome/updater/tag.h"
namespace updater {
namespace tagging {
std::ostream& operator<<(std::ostream& os, const ErrorCode& error_code) {
switch (error_code) {
case ErrorCode::kSuccess:
return os << "ErrorCode::kSuccess";
case ErrorCode::kUnrecognizedName:
return os << "ErrorCode::kUnrecognizedName";
case ErrorCode::kTagIsInvalid:
return os << "ErrorCode::kTagIsInvalid";
case ErrorCode::kAttributeMustHaveValue:
return os << "ErrorCode::kAttributeMustHaveValue";
case ErrorCode::kApp_AppIdNotSpecified:
return os << "ErrorCode::kApp_AppIdNotSpecified";
case ErrorCode::kApp_ExperimentLabelsCannotBeWhitespace:
return os << "ErrorCode::kApp_ExperimentLabelsCannotBeWhitespace";
case ErrorCode::kApp_AppIdIsNotValid:
return os << "ErrorCode::kApp_AppIdIsNotValid";
case ErrorCode::kApp_AppNameCannotBeWhitespace:
return os << "ErrorCode::kApp_AppNameCannotBeWhitespace";
case ErrorCode::kApp_NeedsAdminValueIsInvalid:
return os << "ErrorCode::kApp_NeedsAdminValueIsInvalid";
case ErrorCode::kAppInstallerData_AppIdNotFound:
return os << "ErrorCode::kAppInstallerData_AppIdNotFound";
case ErrorCode::kAppInstallerData_InstallerDataCannotBeSpecifiedBeforeAppId:
return os << "ErrorCode::kAppInstallerData_"
"InstallerDataCannotBeSpecifiedBeforeAppId";
case ErrorCode::kGlobal_BundleNameCannotBeWhitespace:
return os << "ErrorCode::kGlobal_BundleNameCannotBeWhitespace";
case ErrorCode::kGlobal_ExperimentLabelsCannotBeWhitespace:
return os << "ErrorCode::kGlobal_ExperimentLabelsCannotBeWhitespace";
case ErrorCode::kGlobal_BrowserTypeIsInvalid:
return os << "ErrorCode::kGlobal_BrowserTypeIsInvalid";
case ErrorCode::kGlobal_FlightingValueIsNotABoolean:
return os << "ErrorCode::kGlobal_FlightingValueIsNotABoolean";
case ErrorCode::kGlobal_UsageStatsValueIsInvalid:
return os << "ErrorCode::kGlobal_UsageStatsValueIsInvalid";
default:
return os << "ErrorCode(" << static_cast<int>(error_code) << ")";
}
}
std::ostream& operator<<(std::ostream& os,
const AppArgs::NeedsAdmin& needs_admin) {
switch (needs_admin) {
case AppArgs::NeedsAdmin::kNo:
return os << "AppArgs::NeedsAdmin::kNo";
case AppArgs::NeedsAdmin::kYes:
return os << "AppArgs::NeedsAdmin::kYes";
case AppArgs::NeedsAdmin::kPrefers:
return os << "AppArgs::NeedsAdmin::kPrefers";
default:
return os << "AppArgs::NeedsAdmin(" << static_cast<int>(needs_admin)
<< ")";
}
}
std::ostream& operator<<(std::ostream& os,
const TagArgs::BrowserType& browser_type) {
switch (browser_type) {
case TagArgs::BrowserType::kUnknown:
return os << "TagArgs::BrowserType::kUnknown";
case TagArgs::BrowserType::kDefault:
return os << "TagArgs::BrowserType::kDefault";
case TagArgs::BrowserType::kInternetExplorer:
return os << "TagArgs::BrowserType::kInternetExplorer";
case TagArgs::BrowserType::kFirefox:
return os << "TagArgs::BrowserType::kFirefox";
case TagArgs::BrowserType::kChrome:
return os << "TagArgs::BrowserType::kChrome";
default:
return os << "TagArgs::BrowserType(" << static_cast<int>(browser_type)
<< ")";
}
}
} // namespace tagging
} // namespace updater
// Copyright 2020 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.
#ifndef CHROME_UPDATER_UNITTEST_UTIL_H_
#define CHROME_UPDATER_UNITTEST_UTIL_H_
#include <ostream>
#include <string>
#include "base/optional.h"
#include "chrome/updater/tag.h"
// Externally-defined printers for base types.
namespace base {
template <class T>
std::ostream& operator<<(std::ostream& os, const base::Optional<T>& opt) {
if (opt.has_value()) {
return os << opt.value();
} else {
return os << "base::nullopt";
}
}
} // namespace base
// Externally-defined printers for chrome/updater-related types.
namespace updater {
namespace tagging {
std::ostream& operator<<(std::ostream&, const ErrorCode&);
std::ostream& operator<<(std::ostream&, const AppArgs::NeedsAdmin&);
std::ostream& operator<<(std::ostream&, const TagArgs::BrowserType&);
} // namespace tagging
} // namespace updater
#endif // CHROME_UPDATER_UNITTEST_UTIL_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment