diff options
author | Anna Henningsen <anna@addaleax.net> | 2019-03-13 12:24:27 +0000 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2019-03-30 22:25:33 +0100 |
commit | f5b5fe3937ec353e43d30ae976725e0773e14c6f (patch) | |
tree | 57e532e5d82dbf3c553c83bfaa41bb50c76c4b71 | |
parent | e4e2b0ce134ce781847272ae0b4bb889e75f223f (diff) | |
download | android-node-v8-f5b5fe3937ec353e43d30ae976725e0773e14c6f.tar.gz android-node-v8-f5b5fe3937ec353e43d30ae976725e0773e14c6f.tar.bz2 android-node-v8-f5b5fe3937ec353e43d30ae976725e0773e14c6f.zip |
src: allow per-Environment set of env vars
Abstract the `process.env` backing mechanism in C++ to allow
different kinds of backing stores for `process.env` for different
Environments.
PR-URL: https://github.com/nodejs/node/pull/26544
Fixes: https://github.com/nodejs/node/issues/24947
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
Reviewed-By: Yongsheng Zhang <zyszys98@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
-rw-r--r-- | src/env-inl.h | 8 | ||||
-rw-r--r-- | src/env.cc | 4 | ||||
-rw-r--r-- | src/env.h | 20 | ||||
-rw-r--r-- | src/node_credentials.cc | 24 | ||||
-rw-r--r-- | src/node_env_var.cc | 202 | ||||
-rw-r--r-- | src/node_internals.h | 2 |
6 files changed, 176 insertions, 84 deletions
diff --git a/src/env-inl.h b/src/env-inl.h index 69c70804c8..3eedbb857b 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -447,6 +447,14 @@ inline uint64_t Environment::timer_base() const { return timer_base_; } +inline std::shared_ptr<KVStore> Environment::envvars() { + return envvars_; +} + +inline void Environment::set_envvars(std::shared_ptr<KVStore> envvars) { + envvars_ = envvars; +} + inline bool Environment::printed_error() const { return printed_error_; } diff --git a/src/env.cc b/src/env.cc index 174cadcc04..ffdb4289bb 100644 --- a/src/env.cc +++ b/src/env.cc @@ -178,6 +178,8 @@ Environment::Environment(IsolateData* isolate_data, set_as_callback_data_template(templ); } + set_envvars(per_process::real_environment); + // We create new copies of the per-Environment option sets, so that it is // easier to modify them after Environment creation. The defaults are // part of the per-Isolate option set, for which in turn the defaults are @@ -221,7 +223,7 @@ Environment::Environment(IsolateData* isolate_data, should_abort_on_uncaught_toggle_[0] = 1; std::string debug_cats; - credentials::SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats); + credentials::SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats, this); set_debug_categories(debug_cats, true); isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback( @@ -540,6 +540,23 @@ class AsyncRequest : public MemoryRetainer { std::atomic_bool stopped_ {true}; }; +class KVStore { + public: + virtual v8::Local<v8::String> Get(v8::Isolate* isolate, + v8::Local<v8::String> key) const = 0; + virtual void Set(v8::Isolate* isolate, + v8::Local<v8::String> key, + v8::Local<v8::String> value) = 0; + virtual int32_t Query(v8::Isolate* isolate, + v8::Local<v8::String> key) const = 0; + virtual void Delete(v8::Isolate* isolate, v8::Local<v8::String> key) = 0; + virtual v8::Local<v8::Array> Enumerate(v8::Isolate* isolate) const = 0; +}; + +namespace per_process { +extern std::shared_ptr<KVStore> real_environment; +} + class AsyncHooks { public: // Reason for both UidFields and Fields are that one is stored as a double* @@ -789,6 +806,8 @@ class Environment { inline ImmediateInfo* immediate_info(); inline TickInfo* tick_info(); inline uint64_t timer_base() const; + inline std::shared_ptr<KVStore> envvars(); + inline void set_envvars(std::shared_ptr<KVStore> envvars); inline IsolateData* isolate_data() const; @@ -1075,6 +1094,7 @@ class Environment { ImmediateInfo immediate_info_; TickInfo tick_info_; const uint64_t timer_base_; + std::shared_ptr<KVStore> envvars_; bool printed_error_ = false; bool emit_env_nonstring_warning_ = true; bool emit_err_name_warning_ = true; diff --git a/src/node_credentials.cc b/src/node_credentials.cc index 8d38c38a0c..23b9ad2893 100644 --- a/src/node_credentials.cc +++ b/src/node_credentials.cc @@ -15,11 +15,14 @@ using v8::Array; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; +using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::MaybeLocal; +using v8::NewStringType; using v8::Object; using v8::String; +using v8::TryCatch; using v8::Uint32; using v8::Value; @@ -30,13 +33,27 @@ bool linux_at_secure = false; namespace credentials { // Look up environment variable unless running as setuid root. -bool SafeGetenv(const char* key, std::string* text) { +bool SafeGetenv(const char* key, std::string* text, Environment* env) { #if !defined(__CloudABI__) && !defined(_WIN32) if (per_process::linux_at_secure || getuid() != geteuid() || getgid() != getegid()) goto fail; #endif + if (env != nullptr) { + HandleScope handle_scope(env->isolate()); + TryCatch ignore_errors(env->isolate()); + MaybeLocal<String> value = env->envvars()->Get( + env->isolate(), + String::NewFromUtf8(env->isolate(), key, NewStringType::kNormal) + .ToLocalChecked()); + if (value.IsEmpty()) goto fail; + String::Utf8Value utf8_value(env->isolate(), value.ToLocalChecked()); + if (*utf8_value == nullptr) goto fail; + *text = std::string(*utf8_value, utf8_value.length()); + return true; + } + { Mutex::ScopedLock lock(per_process::env_var_mutex); if (const char* value = getenv(key)) { @@ -52,10 +69,11 @@ fail: static void SafeGetenv(const FunctionCallbackInfo<Value>& args) { CHECK(args[0]->IsString()); - Isolate* isolate = args.GetIsolate(); + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); Utf8Value strenvtag(isolate, args[0]); std::string text; - if (!SafeGetenv(*strenvtag, &text)) return; + if (!SafeGetenv(*strenvtag, &text, env)) return; Local<Value> result = ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked(); args.GetReturnValue().Set(result); diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 7e4913702b..6bd799e19f 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -27,24 +27,29 @@ using v8::PropertyCallbackInfo; using v8::String; using v8::Value; +class RealEnvStore final : public KVStore { + public: + Local<String> Get(Isolate* isolate, Local<String> key) const override; + void Set(Isolate* isolate, Local<String> key, Local<String> value) override; + int32_t Query(Isolate* isolate, Local<String> key) const override; + void Delete(Isolate* isolate, Local<String> key) override; + Local<Array> Enumerate(Isolate* isolate) const override; +}; + namespace per_process { Mutex env_var_mutex; +std::shared_ptr<KVStore> real_environment = std::make_shared<RealEnvStore>(); } // namespace per_process -static void EnvGetter(Local<Name> property, - const PropertyCallbackInfo<Value>& info) { - Isolate* isolate = info.GetIsolate(); - if (property->IsSymbol()) { - return info.GetReturnValue().SetUndefined(); - } +Local<String> RealEnvStore::Get(Isolate* isolate, + Local<String> property) const { Mutex::ScopedLock lock(per_process::env_var_mutex); #ifdef __POSIX__ node::Utf8Value key(isolate, property); const char* val = getenv(*key); if (val) { - return info.GetReturnValue().Set( - String::NewFromUtf8(isolate, val, NewStringType::kNormal) - .ToLocalChecked()); + return String::NewFromUtf8(isolate, val, NewStringType::kNormal) + .ToLocalChecked(); } #else // _WIN32 node::TwoByteValue key(isolate, property); @@ -62,106 +67,72 @@ static void EnvGetter(Local<Name> property, isolate, two_byte_buffer, NewStringType::kNormal); if (rc.IsEmpty()) { isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); - return; + return Local<String>(); } - return info.GetReturnValue().Set(rc.ToLocalChecked()); + return rc.ToLocalChecked(); } #endif + return Local<String>(); } -static void EnvSetter(Local<Name> property, - Local<Value> value, - const PropertyCallbackInfo<Value>& info) { - Environment* env = Environment::GetCurrent(info); - // calling env->EmitProcessEnvWarning() sets a variable indicating that - // warnings have been emitted. It should be called last after other - // conditions leading to a warning have been met. - if (env->options()->pending_deprecation && !value->IsString() && - !value->IsNumber() && !value->IsBoolean() && - env->EmitProcessEnvWarning()) { - if (ProcessEmitDeprecationWarning( - env, - "Assigning any value other than a string, number, or boolean to a " - "process.env property is deprecated. Please make sure to convert " - "the " - "value to a string before setting process.env with it.", - "DEP0104") - .IsNothing()) - return; - } - +void RealEnvStore::Set(Isolate* isolate, + Local<String> property, + Local<String> value) { Mutex::ScopedLock lock(per_process::env_var_mutex); #ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - node::Utf8Value val(info.GetIsolate(), value); + node::Utf8Value key(isolate, property); + node::Utf8Value val(isolate, value); setenv(*key, *val, 1); #else // _WIN32 - node::TwoByteValue key(info.GetIsolate(), property); - node::TwoByteValue val(info.GetIsolate(), value); + node::TwoByteValue key(isolate, property); + node::TwoByteValue val(isolate, value); WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); // Environment variables that start with '=' are read-only. if (key_ptr[0] != L'=') { SetEnvironmentVariableW(key_ptr, reinterpret_cast<WCHAR*>(*val)); } #endif - // Whether it worked or not, always return value. - info.GetReturnValue().Set(value); } -static void EnvQuery(Local<Name> property, - const PropertyCallbackInfo<Integer>& info) { +int32_t RealEnvStore::Query(Isolate* isolate, Local<String> property) const { Mutex::ScopedLock lock(per_process::env_var_mutex); - int32_t rc = -1; // Not found unless proven otherwise. - if (property->IsString()) { #ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - if (getenv(*key)) rc = 0; + node::Utf8Value key(isolate, property); + if (getenv(*key)) return 0; #else // _WIN32 - node::TwoByteValue key(info.GetIsolate(), property); - WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); - SetLastError(ERROR_SUCCESS); - if (GetEnvironmentVariableW(key_ptr, nullptr, 0) > 0 || - GetLastError() == ERROR_SUCCESS) { - rc = 0; - if (key_ptr[0] == L'=') { - // Environment variables that start with '=' are hidden and read-only. - rc = static_cast<int32_t>(v8::ReadOnly) | + node::TwoByteValue key(isolate, property); + WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); + SetLastError(ERROR_SUCCESS); + if (GetEnvironmentVariableW(key_ptr, nullptr, 0) > 0 || + GetLastError() == ERROR_SUCCESS) { + if (key_ptr[0] == L'=') { + // Environment variables that start with '=' are hidden and read-only. + return static_cast<int32_t>(v8::ReadOnly) | static_cast<int32_t>(v8::DontDelete) | static_cast<int32_t>(v8::DontEnum); - } } -#endif + return 0; } - if (rc != -1) info.GetReturnValue().Set(rc); +#endif + return -1; } -static void EnvDeleter(Local<Name> property, - const PropertyCallbackInfo<Boolean>& info) { +void RealEnvStore::Delete(Isolate* isolate, Local<String> property) { Mutex::ScopedLock lock(per_process::env_var_mutex); - if (property->IsString()) { #ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - unsetenv(*key); + node::Utf8Value key(isolate, property); + unsetenv(*key); #else - node::TwoByteValue key(info.GetIsolate(), property); - WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); - SetEnvironmentVariableW(key_ptr, nullptr); + node::TwoByteValue key(isolate, property); + WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); + SetEnvironmentVariableW(key_ptr, nullptr); #endif - } - - // process.env never has non-configurable properties, so always - // return true like the tc39 delete operator. - info.GetReturnValue().Set(true); } -static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { - Environment* env = Environment::GetCurrent(info); - Isolate* isolate = env->isolate(); - +Local<Array> RealEnvStore::Enumerate(Isolate* isolate) const { Mutex::ScopedLock lock(per_process::env_var_mutex); - Local<Array> envarr; - int env_size = 0; #ifdef __POSIX__ + int env_size = 0; while (environ[env_size]) { env_size++; } @@ -177,7 +148,8 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { #else // _WIN32 std::vector<Local<Value>> env_v; WCHAR* environment = GetEnvironmentStringsW(); - if (environment == nullptr) return; // This should not happen. + if (environment == nullptr) + return Array::New(isolate); // This should not happen. WCHAR* p = environment; while (*p) { WCHAR* s; @@ -198,7 +170,7 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { if (rc.IsEmpty()) { isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); FreeEnvironmentStringsW(environment); - return; + return Local<Array>(); } env_v.push_back(rc.ToLocalChecked()); p = s + wcslen(s) + 1; @@ -206,8 +178,80 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { FreeEnvironmentStringsW(environment); #endif - envarr = Array::New(isolate, env_v.data(), env_v.size()); - info.GetReturnValue().Set(envarr); + return Array::New(isolate, env_v.data(), env_v.size()); +} + +static void EnvGetter(Local<Name> property, + const PropertyCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + if (property->IsSymbol()) { + return info.GetReturnValue().SetUndefined(); + } + CHECK(property->IsString()); + info.GetReturnValue().Set( + env->envvars()->Get(env->isolate(), property.As<String>())); +} + +static void EnvSetter(Local<Name> property, + Local<Value> value, + const PropertyCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + // calling env->EmitProcessEnvWarning() sets a variable indicating that + // warnings have been emitted. It should be called last after other + // conditions leading to a warning have been met. + if (env->options()->pending_deprecation && !value->IsString() && + !value->IsNumber() && !value->IsBoolean() && + env->EmitProcessEnvWarning()) { + if (ProcessEmitDeprecationWarning( + env, + "Assigning any value other than a string, number, or boolean to a " + "process.env property is deprecated. Please make sure to convert " + "the " + "value to a string before setting process.env with it.", + "DEP0104") + .IsNothing()) + return; + } + + Local<String> key; + Local<String> value_string; + if (!property->ToString(env->context()).ToLocal(&key) || + !value->ToString(env->context()).ToLocal(&value_string)) { + return; + } + + env->envvars()->Set(env->isolate(), key, value_string); + + // Whether it worked or not, always return value. + info.GetReturnValue().Set(value); +} + +static void EnvQuery(Local<Name> property, + const PropertyCallbackInfo<Integer>& info) { + Environment* env = Environment::GetCurrent(info); + if (property->IsString()) { + int32_t rc = env->envvars()->Query(env->isolate(), property.As<String>()); + if (rc != -1) info.GetReturnValue().Set(rc); + } +} + +static void EnvDeleter(Local<Name> property, + const PropertyCallbackInfo<Boolean>& info) { + Environment* env = Environment::GetCurrent(info); + if (property->IsString()) { + env->envvars()->Delete(env->isolate(), property.As<String>()); + } + + // process.env never has non-configurable properties, so always + // return true like the tc39 delete operator. + info.GetReturnValue().Set(true); +} + +static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { + Environment* env = Environment::GetCurrent(info); + + info.GetReturnValue().Set( + env->envvars()->Enumerate(env->isolate())); } MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context, diff --git a/src/node_internals.h b/src/node_internals.h index 8c2864aae8..012848491e 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -293,7 +293,7 @@ int ThreadPoolWork::CancelWork() { #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) namespace credentials { -bool SafeGetenv(const char* key, std::string* text); +bool SafeGetenv(const char* key, std::string* text, Environment* env = nullptr); } // namespace credentials void DefineZlibConstants(v8::Local<v8::Object> target); |