| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "chrome/installer/util/additional_parameters.h" |
| |
| #include <windows.h> |
| |
| #include <string_view> |
| |
| #include "base/check.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/win/registry.h" |
| #include "build/branding_buildflags.h" |
| #include "build/build_config.h" |
| #include "chrome/install_static/install_details.h" |
| #include "components/version_info/channel.h" |
| |
| namespace installer { |
| |
| namespace { |
| |
| constexpr wchar_t kRegValueAp[] = L"ap"; |
| constexpr std::wstring_view kFullSuffix = L"-full"; |
| constexpr std::wstring_view kExtendedChannel = L"extended"; |
| const wchar_t kTokenSeparator = L'-'; |
| |
| // Returns null if the value was not found or otherwise could not be read. |
| std::optional<std::wstring> ReadAdditionalParameters() { |
| std::optional<std::wstring> result; |
| base::win::RegKey key; |
| |
| if (key.Open(install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE |
| : HKEY_CURRENT_USER, |
| install_static::GetClientStateKeyPath().c_str(), |
| KEY_WOW64_32KEY | KEY_QUERY_VALUE) == ERROR_SUCCESS) { |
| result.emplace(); |
| if (key.ReadValue(kRegValueAp, &result.value()) != ERROR_SUCCESS) |
| result.reset(); |
| } |
| return result; |
| } |
| |
| // Writes `value` to the "ap" value in the registry, or deletes the "ap" value |
| // if `value` is null. Returns false and sets the Windows last-error code on |
| // failure; otherwise, returns true. |
| bool WriteAdditionalParameters(const std::optional<std::wstring>& value) { |
| base::win::RegKey key; |
| LONG result = ERROR_SUCCESS; |
| |
| if (!value) { |
| // Delete the value if it exists. |
| result = key.Open(install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE |
| : HKEY_CURRENT_USER, |
| install_static::GetClientStateKeyPath().c_str(), |
| KEY_WOW64_32KEY | KEY_SET_VALUE); |
| if (result == ERROR_SUCCESS) |
| result = key.DeleteValue(kRegValueAp); |
| // Report success if the value was deleted or if either it or the key didn't |
| // exist to start with. |
| if (result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND || |
| result == ERROR_PATH_NOT_FOUND) { |
| return true; |
| } |
| ::SetLastError(result); |
| return false; |
| } |
| |
| // Write the value to the key. |
| result = key.Create(install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE |
| : HKEY_CURRENT_USER, |
| install_static::GetClientStateKeyPath().c_str(), |
| KEY_WOW64_32KEY | KEY_SET_VALUE); |
| if (result == ERROR_SUCCESS) |
| result = key.WriteValue(kRegValueAp, value->c_str()); |
| if (result == ERROR_SUCCESS) |
| return true; |
| ::SetLastError(result); |
| return false; |
| } |
| |
| bool HasFullSuffix(const std::optional<std::wstring>& value) { |
| return value ? base::EndsWith(*value, kFullSuffix) : false; |
| } |
| |
| // Expands `channel` to include an optional -arch_FOO suffix, returning true |
| // if one is found. Returns false without modifying `channel` if none is found. |
| // `channel` must be a sub-std::string_view of `ap`. |
| bool SwallowArchSufix(std::wstring_view ap, std::wstring_view& channel) { |
| DCHECK_LE(channel.size(), ap.size()); |
| DCHECK_GE(channel.data(), ap.data()); |
| DCHECK_LE(channel.data() + channel.size(), ap.data() + ap.size()); |
| static constexpr std::wstring_view kArchPrefix = L"-arch_"; |
| auto channel_position = channel.empty() ? 0 : channel.data() - ap.data(); |
| auto rest_position = channel_position + channel.size(); |
| if (!base::StartsWith(ap.substr(rest_position), kArchPrefix)) |
| return false; |
| // Retain everything up to the next token separator. |
| channel = |
| ap.substr(channel_position, |
| ap.find(kTokenSeparator, rest_position + kArchPrefix.size())); |
| return true; |
| } |
| |
| struct ChannelParseState { |
| // The canonical name of the parsed channel. |
| std::wstring channel_name; |
| |
| // The range within the value that matched a channel name pattern. |
| std::wstring_view channel_match_range; |
| |
| // The range includes an architecture specification (e.g., "x64-beta"). |
| bool includes_arch; |
| }; |
| |
| // Parses a channel identifier in `value`, returning the canonical name of the |
| // channel, the range within `value` that constitutes the identifier, and a |
| // bool indicating whether or not the identifier also includes an architecture |
| // specification. |
| ChannelParseState MakeChannelParseState( |
| const std::optional<std::wstring>& value) { |
| if (!value) // No value means stable channel. |
| return {std::wstring(), std::wstring_view(), /*includes_arch=*/false}; |
| |
| std::wstring_view ap(value.value()); |
| |
| // Expect that "ap" may contain tokens such as "-statsdef_N" and "-full". |
| // Historically, all such tokens begin with '-'. Rely on that when matching |
| // patterns. |
| |
| // Extended stable with an optional -arch_FOO. |
| if (base::StartsWith(ap, kExtendedChannel)) { |
| std::wstring_view channel_range = ap.substr(0, kExtendedChannel.size()); |
| bool includes_arch = SwallowArchSufix(ap, channel_range); |
| return {std::wstring(kExtendedChannel), channel_range, includes_arch}; |
| } |
| |
| // Beta channel: /^1.1-.*$/ with an optional -arch_FOO. Note that '.' is the |
| // regexp wildcard, not a literal period. |
| if (ap.size() >= 4 && ap[0] == L'1' && ap[2] == L'1' && ap[3] == L'-') { |
| std::wstring_view channel_range = ap.substr(0, ap.find(kTokenSeparator, 4)); |
| bool includes_arch = SwallowArchSufix(ap, channel_range); |
| return {L"beta", channel_range, includes_arch}; |
| } |
| |
| // Dev channel: /^2.0-d.*$/ with an optional -arch_FOO. Note that '.' is the |
| // regexp wildcard, not a literal period. |
| if (ap.size() >= 5 && ap[0] == L'2' && ap[2] == L'0' && ap[3] == L'-' && |
| ap[4] == L'd') { |
| std::wstring_view channel_range = ap.substr(0, ap.find(kTokenSeparator, 5)); |
| bool includes_arch = SwallowArchSufix(ap, channel_range); |
| return {L"dev", channel_range, includes_arch}; |
| } |
| |
| // Older channels. |
| static constexpr struct { |
| std::wstring_view literal; |
| bool is_stable; // if false, the channel name is embedded in `literal`. |
| } kLiteralChannels[] = { |
| {L"x64-stable", true}, {L"x86-stable", true}, {L"arm64-stable", true}, |
| {L"x64-beta", false}, {L"x86-beta", false}, {L"arm64-beta", false}, |
| {L"x64-dev", false}, {L"x86-dev", false}, {L"arm64-dev", false}, |
| }; |
| for (const auto& literal_channel : kLiteralChannels) { |
| auto pos = ap.find(literal_channel.literal); |
| if (pos == std::wstring_view::npos) { |
| continue; |
| } |
| auto range = ap.substr(pos, literal_channel.literal.size()); |
| return {literal_channel.is_stable |
| ? std::wstring() |
| : std::wstring(range.substr(range.find(kTokenSeparator) + 1)), |
| range, /*includes_arch=*/true}; |
| } |
| |
| // Explicit stable with an optional -arch_FOO. |
| static constexpr std::wstring_view kStableChannel = L"stable"; |
| if (base::StartsWith(ap, kStableChannel)) { |
| std::wstring_view channel_range = ap.substr(0, kStableChannel.size()); |
| bool includes_arch = SwallowArchSufix(ap, channel_range); |
| return {std::wstring(), channel_range, includes_arch}; |
| } |
| |
| // Implicit stable (no channel identifier) with a bare -arch_FOO. |
| std::wstring_view channel_range = ap.substr(0, 0); |
| if (SwallowArchSufix(ap, channel_range)) |
| return {std::wstring(), channel_range, /*includes_arch=*/true}; |
| |
| // Stable channel if nothing else. |
| return {std::wstring(), std::wstring_view(), /*includes_arch=*/false}; |
| } |
| |
| // Returns the channel identifier based on `channel` and |
| // `is_extended_stable_channel`. If `include_arch`, a processor architecture |
| // specification is included in the identifier. |
| std::wstring GetChannelIdentifier(version_info::Channel channel, |
| bool is_extended_stable_channel, |
| bool include_arch) { |
| static constexpr std::wstring_view kDevChannel = L"2.0-dev"; |
| static constexpr std::wstring_view kBetaChannel = L"1.1-beta"; |
| #if defined(ARCH_CPU_X86_64) |
| static constexpr std::wstring_view kArchSuffix = L"-arch_x64"; |
| #elif defined(ARCH_CPU_X86) |
| static constexpr std::wstring_view kArchSuffix = L"-arch_x86"; |
| #elif defined(ARCH_CPU_ARM64) |
| static constexpr std::wstring_view kArchSuffix = L"-arch_arm64"; |
| #else |
| #error unsupported processor architecture. |
| #endif |
| |
| switch (channel) { |
| case version_info::Channel::UNKNOWN: |
| case version_info::Channel::CANARY: |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| NOTREACHED(); |
| #else |
| return std::wstring(); |
| #endif |
| |
| case version_info::Channel::DEV: |
| if (!include_arch) |
| return std::wstring(kDevChannel); |
| return base::StrCat({kDevChannel, kArchSuffix}); |
| |
| case version_info::Channel::BETA: |
| if (!include_arch) |
| return std::wstring(kBetaChannel); |
| return base::StrCat({kBetaChannel, kArchSuffix}); |
| |
| case version_info::Channel::STABLE: |
| if (is_extended_stable_channel) { |
| if (!include_arch) |
| return std::wstring(kExtendedChannel); |
| return base::StrCat({kExtendedChannel, kArchSuffix}); |
| } |
| if (!include_arch) |
| return std::wstring(); |
| #if defined(ARCH_CPU_X86_64) |
| // For historical reasons, this is the ordinary value for 64-bit stable. |
| return L"x64-stable"; |
| #elif defined(ARCH_CPU_X86) |
| return L"stable-arch_x86"; |
| #elif defined(ARCH_CPU_ARM64) |
| return L"arm64-stable"; |
| #else |
| #error unsupported processor architecture. |
| #endif |
| } |
| } |
| |
| } // namespace |
| |
| AdditionalParameters::AdditionalParameters() |
| : value_(ReadAdditionalParameters()) {} |
| |
| AdditionalParameters::~AdditionalParameters() = default; |
| |
| const wchar_t* AdditionalParameters::value() const { |
| return value_ ? value_->c_str() : L""; |
| } |
| |
| wchar_t AdditionalParameters::GetStatsDefault() const { |
| if (!value_) |
| return 0; |
| |
| static constexpr std::wstring_view kStatsdef = L"-statsdef_"; |
| std::wstring_view value(*value_); |
| auto pos = value.find(kStatsdef); |
| if (pos == std::wstring_view::npos) { |
| return 0; |
| } |
| pos += kStatsdef.size(); |
| return pos < value.size() ? value[pos] : 0; |
| } |
| |
| bool AdditionalParameters::SetFullSuffix(bool set_full_suffix) { |
| if (HasFullSuffix(value_) == set_full_suffix) |
| return false; // Nothing to do. |
| if (set_full_suffix) { |
| if (!value_) { |
| value_ = std::wstring(kFullSuffix); |
| } else { |
| value_->append(kFullSuffix); |
| } |
| } else { |
| DCHECK(value_); |
| const auto value_size = value_->size(); |
| if (value_size == kFullSuffix.size()) { |
| value_.reset(); |
| } else { |
| value_->resize(value_size - kFullSuffix.size()); |
| } |
| } |
| return true; |
| } |
| |
| std::wstring AdditionalParameters::ParseChannel() { |
| auto parse_state = MakeChannelParseState(value_); |
| return std::move(parse_state.channel_name); |
| } |
| |
| void AdditionalParameters::SetChannel(version_info::Channel channel, |
| bool is_extended_stable_channel) { |
| auto parse_state = MakeChannelParseState(value_); |
| |
| std::wstring new_value = value_ ? *value_ : std::wstring(); |
| |
| // Remove the old channel and optional architecture identifier. |
| if (!parse_state.channel_match_range.empty()) { |
| new_value.erase(parse_state.channel_match_range.data() - value_->data(), |
| parse_state.channel_match_range.size()); |
| } |
| |
| // Insert the new channel and architecture identifier (if needed). |
| value_ = |
| base::StrCat({GetChannelIdentifier(channel, is_extended_stable_channel, |
| parse_state.includes_arch), |
| new_value}); |
| } |
| |
| bool AdditionalParameters::Commit() { |
| return WriteAdditionalParameters(value_); |
| } |
| |
| } // namespace installer |