blob: e005884f563c3471f065e5b6d6f79ff14654aa4a [file]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process/environment_internal.h"
#include <stddef.h>
#include <algorithm>
#include <vector>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <string.h>
#if BUILDFLAG(IS_APPLE)
#include <crt_externs.h>
#else
extern char** environ;
#endif
#endif
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include "base/check_op.h"
#endif
namespace base::internal {
namespace {
// Parses a null-terminated input string of an environment block. The key is
// placed into the given string, and the total length of the line, including
// the terminating null, is returned.
size_t ParseEnvLine(NativeEnvironmentCStringView input,
NativeEnvironmentStringView* key) {
// Skip to the equals or end of the string, this is the key.
size_t loc = input.find('=');
*key = input.substr(0, loc);
// +1 to account for the null terminator.
return input.size() + 1;
}
} // namespace
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
// Returns a span over the current process' environment. Each string in the
// span is null-terminated.
base::span<const char* const> GetEnvironment() {
#if BUILDFLAG(IS_APPLE)
char** environ = *_NSGetEnviron();
#endif
size_t count = 0;
if (environ) {
// SAFETY: environ is a null-terminated array of pointers. See
// https://man7.org/linux/man-pages/man7/environ.7.html for details.
for (char** env = environ; *env; UNSAFE_BUFFERS(++env)) {
count++;
}
}
// SAFETY: We have just calculated the size of the environ array.
return UNSAFE_BUFFERS(base::span<const char* const>(environ, count));
}
void SetEnvironment(base::span<char*> env) {
CHECK(env.empty() || env.back() == nullptr);
#if BUILDFLAG(IS_APPLE)
*_NSGetEnviron() = env.empty() ? nullptr : env.data();
#else
environ = env.empty() ? nullptr : env.data();
#endif
}
// PRECONDITIONS: Each string in 'env' must be null-terminated. The caller is
// responsible for ensuring this.
UNSAFE_BUFFER_USAGE base::HeapArray<char*> AlterEnvironment(
const base::span<const char* const> env,
const EnvironmentMap& changes) {
std::string value_storage; // Holds concatenated null-terminated strings.
std::vector<size_t> result_indices; // Line indices into value_storage.
// First build up all of the unchanged environment strings. These are
// null-terminated of the form "key=value".
NativeEnvironmentStringView key;
for (const char* const env_var : env) {
// SAFETY: Each string in `env` is guaranteed to be null-terminated by the
// precondition of AlterEnvironment().
size_t line_length = ParseEnvLine(
UNSAFE_BUFFERS(NativeEnvironmentCStringView(env_var)), &key);
// Keep only values not specified in the change vector.
auto found_change = changes.find(key);
if (found_change == changes.end()) {
result_indices.push_back(value_storage.size());
value_storage.append(env_var, line_length);
}
}
// Now append all modified and new values.
for (const auto& i : changes) {
if (!i.second.empty()) {
result_indices.push_back(value_storage.size());
value_storage.append(i.first);
value_storage.push_back('=');
value_storage.append(i.second);
value_storage.push_back(0);
}
}
size_t pointer_count_required =
result_indices.size() + 1 + // Null-terminated array of pointers.
(value_storage.size() + sizeof(char*) - 1) / sizeof(char*); // Buffer.
auto result = base::HeapArray<char*>::WithSize(pointer_count_required);
if (!value_storage.empty()) {
// The string storage goes after the array of pointers.
// Here `as_writable_chars` converts char* found in the HeapArray to char
// so they can copy the value_storage in
base::span<char> storage_data =
base::as_writable_chars(result.subspan(result_indices.size() + 1));
storage_data.copy_prefix_from(value_storage);
// Fill array of pointers at the beginning of the result.
for (size_t i = 0; i < result_indices.size(); i++) {
result[i] = &storage_data[result_indices[i]];
}
}
result[result_indices.size()] = 0; // Null terminator.
return result;
}
#elif BUILDFLAG(IS_WIN)
// static
base::HeapArray<wchar_t> GetEnvironment() {
wchar_t* env_ptr = GetEnvironmentStrings();
if (!env_ptr) {
return {};
}
const wchar_t* p = env_ptr;
while (*p) {
// SAFETY: env_ptr points to a memory block provided by the OS. The loop
// follows the Win32 API contract where strings are NUL-terminated and the
// entire block ends with an extra NUL.
const size_t length = UNSAFE_BUFFERS(wcslen(p));
p = UNSAFE_BUFFERS(p + length + 1);
}
// Include the final terminating NUL.
const size_t total_len = static_cast<size_t>(p - env_ptr) + 1;
auto result = base::HeapArray<wchar_t>::WithSize(total_len);
// SAFETY: env_ptr points to a block of memory provided by the OS, and
// total_len has been calculated by scanning that same block until the
// double-NUL terminator.
base::span<const wchar_t> env_span =
UNSAFE_BUFFERS(base::span(env_ptr, total_len));
result.copy_from(env_span);
FreeEnvironmentStrings(env_ptr);
return result;
}
NativeEnvironmentString AlterEnvironment(base::span<const wchar_t> env,
const EnvironmentMap& changes) {
NativeEnvironmentString result;
// First build up all of the unchanged environment strings.
base::span<const wchar_t> remaining = env;
while (!remaining.empty() && remaining[0] != L'\0') {
// Find the next NUL terminator.
auto it = std::ranges::find(remaining, L'\0');
CHECK(it != remaining.end()); // Malformed block.
// SAFETY: We verified `remaining` contains L'\0' at `it`.
const wchar_t* env_var_ptr = remaining.data();
NativeEnvironmentCStringView env_var =
UNSAFE_BUFFERS(NativeEnvironmentCStringView(env_var_ptr));
NativeEnvironmentStringView key;
size_t line_length = ParseEnvLine(env_var, &key);
// Keep only values not specified in the change vector.
if (changes.find(key) == changes.end()) {
result.append(remaining.data(), line_length);
}
remaining = remaining.subspan(line_length);
}
// Now append all modified and new values.
for (const auto& i : changes) {
// Windows environment blocks cannot handle keys or values with NULs.
CHECK_EQ(std::wstring::npos, i.first.find(L'\0'));
CHECK_EQ(std::wstring::npos, i.second.find(L'\0'));
if (!i.second.empty()) {
result += i.first;
result.push_back(L'=');
result += i.second;
result.push_back(L'\0');
}
}
// Add the terminating NUL.
result.push_back(L'\0');
return result;
}
#endif // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
} // namespace base::internal