1 #include "debug_utils-inl.h"
2 #include "env-inl.h"
3 #include "node_errors.h"
4 #include "node_process.h"
5
6 namespace node {
7 using v8::Array;
8 using v8::Boolean;
9 using v8::Context;
10 using v8::EscapableHandleScope;
11 using v8::HandleScope;
12 using v8::Integer;
13 using v8::Isolate;
14 using v8::Just;
15 using v8::Local;
16 using v8::Maybe;
17 using v8::MaybeLocal;
18 using v8::Name;
19 using v8::NamedPropertyHandlerConfiguration;
20 using v8::NewStringType;
21 using v8::Nothing;
22 using v8::Object;
23 using v8::ObjectTemplate;
24 using v8::PropertyCallbackInfo;
25 using v8::PropertyHandlerFlags;
26 using v8::String;
27 using v8::Value;
28
29 class RealEnvStore final : public KVStore {
30 public:
31 MaybeLocal<String> Get(Isolate* isolate, Local<String> key) const override;
32 Maybe<std::string> Get(const char* key) const override;
33 void Set(Isolate* isolate, Local<String> key, Local<String> value) override;
34 int32_t Query(Isolate* isolate, Local<String> key) const override;
35 int32_t Query(const char* key) const override;
36 void Delete(Isolate* isolate, Local<String> key) override;
37 Local<Array> Enumerate(Isolate* isolate) const override;
38 };
39
40 class MapKVStore final : public KVStore {
41 public:
42 MaybeLocal<String> Get(Isolate* isolate, Local<String> key) const override;
43 Maybe<std::string> Get(const char* key) const override;
44 void Set(Isolate* isolate, Local<String> key, Local<String> value) override;
45 int32_t Query(Isolate* isolate, Local<String> key) const override;
46 int32_t Query(const char* key) const override;
47 void Delete(Isolate* isolate, Local<String> key) override;
48 Local<Array> Enumerate(Isolate* isolate) const override;
49
50 std::shared_ptr<KVStore> Clone(Isolate* isolate) const override;
51
52 MapKVStore() = default;
MapKVStore(const MapKVStore & other)53 MapKVStore(const MapKVStore& other) : KVStore(), map_(other.map_) {}
54
55 private:
56 mutable Mutex mutex_;
57 std::unordered_map<std::string, std::string> map_;
58 };
59
60 namespace per_process {
61 Mutex env_var_mutex;
62 std::shared_ptr<KVStore> system_environment = std::make_shared<RealEnvStore>();
63 } // namespace per_process
64
Get(const char * key) const65 Maybe<std::string> RealEnvStore::Get(const char* key) const {
66 Mutex::ScopedLock lock(per_process::env_var_mutex);
67
68 size_t init_sz = 256;
69 MaybeStackBuffer<char, 256> val;
70 int ret = uv_os_getenv(key, *val, &init_sz);
71
72 if (ret == UV_ENOBUFS) {
73 // Buffer is not large enough, reallocate to the updated init_sz
74 // and fetch env value again.
75 val.AllocateSufficientStorage(init_sz);
76 ret = uv_os_getenv(key, *val, &init_sz);
77 }
78
79 if (ret >= 0) { // Env key value fetch success.
80 return v8::Just(std::string(*val, init_sz));
81 }
82
83 return v8::Nothing<std::string>();
84 }
85
Get(Isolate * isolate,Local<String> property) const86 MaybeLocal<String> RealEnvStore::Get(Isolate* isolate,
87 Local<String> property) const {
88 node::Utf8Value key(isolate, property);
89 Maybe<std::string> value = Get(*key);
90
91 if (value.IsJust()) {
92 std::string val = value.FromJust();
93 return String::NewFromUtf8(
94 isolate, val.data(), NewStringType::kNormal, val.size());
95 }
96
97 return MaybeLocal<String>();
98 }
99
Set(Isolate * isolate,Local<String> property,Local<String> value)100 void RealEnvStore::Set(Isolate* isolate,
101 Local<String> property,
102 Local<String> value) {
103 Mutex::ScopedLock lock(per_process::env_var_mutex);
104
105 node::Utf8Value key(isolate, property);
106 node::Utf8Value val(isolate, value);
107
108 #ifdef _WIN32
109 if (key.length() > 0 && key[0] == '=') return;
110 #endif
111 uv_os_setenv(*key, *val);
112 }
113
Query(const char * key) const114 int32_t RealEnvStore::Query(const char* key) const {
115 Mutex::ScopedLock lock(per_process::env_var_mutex);
116
117 char val[2];
118 size_t init_sz = sizeof(val);
119 int ret = uv_os_getenv(key, val, &init_sz);
120
121 if (ret == UV_ENOENT) {
122 return -1;
123 }
124
125 #ifdef _WIN32
126 if (key[0] == '=') {
127 return static_cast<int32_t>(v8::ReadOnly) |
128 static_cast<int32_t>(v8::DontDelete) |
129 static_cast<int32_t>(v8::DontEnum);
130 }
131 #endif
132
133 return 0;
134 }
135
Query(Isolate * isolate,Local<String> property) const136 int32_t RealEnvStore::Query(Isolate* isolate, Local<String> property) const {
137 node::Utf8Value key(isolate, property);
138 return Query(*key);
139 }
140
Delete(Isolate * isolate,Local<String> property)141 void RealEnvStore::Delete(Isolate* isolate, Local<String> property) {
142 Mutex::ScopedLock lock(per_process::env_var_mutex);
143
144 node::Utf8Value key(isolate, property);
145 uv_os_unsetenv(*key);
146 }
147
Enumerate(Isolate * isolate) const148 Local<Array> RealEnvStore::Enumerate(Isolate* isolate) const {
149 Mutex::ScopedLock lock(per_process::env_var_mutex);
150 uv_env_item_t* items;
151 int count;
152
153 auto cleanup = OnScopeLeave([&]() { uv_os_free_environ(items, count); });
154 CHECK_EQ(uv_os_environ(&items, &count), 0);
155
156 MaybeStackBuffer<Local<Value>, 256> env_v(count);
157 int env_v_index = 0;
158 for (int i = 0; i < count; i++) {
159 #ifdef _WIN32
160 // If the key starts with '=' it is a hidden environment variable.
161 // The '\0' check is a workaround for the bug behind
162 // https://github.com/libuv/libuv/pull/2473 and can be removed later.
163 if (items[i].name[0] == '=' || items[i].name[0] == '\0') continue;
164 #endif
165 MaybeLocal<String> str = String::NewFromUtf8(
166 isolate, items[i].name, NewStringType::kNormal);
167 if (str.IsEmpty()) {
168 isolate->ThrowException(ERR_STRING_TOO_LONG(isolate));
169 return Local<Array>();
170 }
171 env_v[env_v_index++] = str.ToLocalChecked();
172 }
173
174 return Array::New(isolate, env_v.out(), env_v_index);
175 }
176
Clone(v8::Isolate * isolate) const177 std::shared_ptr<KVStore> KVStore::Clone(v8::Isolate* isolate) const {
178 HandleScope handle_scope(isolate);
179 Local<Context> context = isolate->GetCurrentContext();
180
181 std::shared_ptr<KVStore> copy = KVStore::CreateMapKVStore();
182 Local<Array> keys = Enumerate(isolate);
183 uint32_t keys_length = keys->Length();
184 for (uint32_t i = 0; i < keys_length; i++) {
185 Local<Value> key = keys->Get(context, i).ToLocalChecked();
186 CHECK(key->IsString());
187 copy->Set(isolate,
188 key.As<String>(),
189 Get(isolate, key.As<String>()).ToLocalChecked());
190 }
191 return copy;
192 }
193
Get(const char * key) const194 Maybe<std::string> MapKVStore::Get(const char* key) const {
195 Mutex::ScopedLock lock(mutex_);
196 auto it = map_.find(key);
197 return it == map_.end() ? v8::Nothing<std::string>() : v8::Just(it->second);
198 }
199
Get(Isolate * isolate,Local<String> key) const200 MaybeLocal<String> MapKVStore::Get(Isolate* isolate, Local<String> key) const {
201 Utf8Value str(isolate, key);
202 Maybe<std::string> value = Get(*str);
203 if (value.IsNothing()) return Local<String>();
204 std::string val = value.FromJust();
205 return String::NewFromUtf8(
206 isolate, val.data(), NewStringType::kNormal, val.size());
207 }
208
Set(Isolate * isolate,Local<String> key,Local<String> value)209 void MapKVStore::Set(Isolate* isolate, Local<String> key, Local<String> value) {
210 Mutex::ScopedLock lock(mutex_);
211 Utf8Value key_str(isolate, key);
212 Utf8Value value_str(isolate, value);
213 if (*key_str != nullptr && key_str.length() > 0 && *value_str != nullptr) {
214 map_[std::string(*key_str, key_str.length())] =
215 std::string(*value_str, value_str.length());
216 }
217 }
218
Query(const char * key) const219 int32_t MapKVStore::Query(const char* key) const {
220 Mutex::ScopedLock lock(mutex_);
221 return map_.find(key) == map_.end() ? -1 : 0;
222 }
223
Query(Isolate * isolate,Local<String> key) const224 int32_t MapKVStore::Query(Isolate* isolate, Local<String> key) const {
225 Utf8Value str(isolate, key);
226 return Query(*str);
227 }
228
Delete(Isolate * isolate,Local<String> key)229 void MapKVStore::Delete(Isolate* isolate, Local<String> key) {
230 Mutex::ScopedLock lock(mutex_);
231 Utf8Value str(isolate, key);
232 map_.erase(std::string(*str, str.length()));
233 }
234
Enumerate(Isolate * isolate) const235 Local<Array> MapKVStore::Enumerate(Isolate* isolate) const {
236 Mutex::ScopedLock lock(mutex_);
237 std::vector<Local<Value>> values;
238 values.reserve(map_.size());
239 for (const auto& pair : map_) {
240 values.emplace_back(
241 String::NewFromUtf8(isolate, pair.first.data(),
242 NewStringType::kNormal, pair.first.size())
243 .ToLocalChecked());
244 }
245 return Array::New(isolate, values.data(), values.size());
246 }
247
Clone(Isolate * isolate) const248 std::shared_ptr<KVStore> MapKVStore::Clone(Isolate* isolate) const {
249 return std::make_shared<MapKVStore>(*this);
250 }
251
CreateMapKVStore()252 std::shared_ptr<KVStore> KVStore::CreateMapKVStore() {
253 return std::make_shared<MapKVStore>();
254 }
255
AssignFromObject(Local<Context> context,Local<Object> entries)256 Maybe<bool> KVStore::AssignFromObject(Local<Context> context,
257 Local<Object> entries) {
258 Isolate* isolate = context->GetIsolate();
259 HandleScope handle_scope(isolate);
260 Local<Array> keys;
261 if (!entries->GetOwnPropertyNames(context).ToLocal(&keys))
262 return Nothing<bool>();
263 uint32_t keys_length = keys->Length();
264 for (uint32_t i = 0; i < keys_length; i++) {
265 Local<Value> key;
266 if (!keys->Get(context, i).ToLocal(&key))
267 return Nothing<bool>();
268 if (!key->IsString()) continue;
269
270 Local<Value> value;
271 Local<String> value_string;
272 if (!entries->Get(context, key).ToLocal(&value) ||
273 !value->ToString(context).ToLocal(&value_string)) {
274 return Nothing<bool>();
275 }
276
277 Set(isolate, key.As<String>(), value_string);
278 }
279 return Just(true);
280 }
281
EnvGetter(Local<Name> property,const PropertyCallbackInfo<Value> & info)282 static void EnvGetter(Local<Name> property,
283 const PropertyCallbackInfo<Value>& info) {
284 Environment* env = Environment::GetCurrent(info);
285 CHECK(env->has_run_bootstrapping_code());
286 if (property->IsSymbol()) {
287 return info.GetReturnValue().SetUndefined();
288 }
289 CHECK(property->IsString());
290 MaybeLocal<String> value_string =
291 env->env_vars()->Get(env->isolate(), property.As<String>());
292 if (!value_string.IsEmpty()) {
293 info.GetReturnValue().Set(value_string.ToLocalChecked());
294 }
295 }
296
EnvSetter(Local<Name> property,Local<Value> value,const PropertyCallbackInfo<Value> & info)297 static void EnvSetter(Local<Name> property,
298 Local<Value> value,
299 const PropertyCallbackInfo<Value>& info) {
300 Environment* env = Environment::GetCurrent(info);
301 CHECK(env->has_run_bootstrapping_code());
302 // calling env->EmitProcessEnvWarning() sets a variable indicating that
303 // warnings have been emitted. It should be called last after other
304 // conditions leading to a warning have been met.
305 if (env->options()->pending_deprecation && !value->IsString() &&
306 !value->IsNumber() && !value->IsBoolean() &&
307 env->EmitProcessEnvWarning()) {
308 if (ProcessEmitDeprecationWarning(
309 env,
310 "Assigning any value other than a string, number, or boolean to a "
311 "process.env property is deprecated. Please make sure to convert "
312 "the "
313 "value to a string before setting process.env with it.",
314 "DEP0104")
315 .IsNothing())
316 return;
317 }
318
319 Local<String> key;
320 Local<String> value_string;
321 if (!property->ToString(env->context()).ToLocal(&key) ||
322 !value->ToString(env->context()).ToLocal(&value_string)) {
323 return;
324 }
325
326 env->env_vars()->Set(env->isolate(), key, value_string);
327
328 // Whether it worked or not, always return value.
329 info.GetReturnValue().Set(value);
330 }
331
EnvQuery(Local<Name> property,const PropertyCallbackInfo<Integer> & info)332 static void EnvQuery(Local<Name> property,
333 const PropertyCallbackInfo<Integer>& info) {
334 Environment* env = Environment::GetCurrent(info);
335 CHECK(env->has_run_bootstrapping_code());
336 if (property->IsString()) {
337 int32_t rc = env->env_vars()->Query(env->isolate(), property.As<String>());
338 if (rc != -1) info.GetReturnValue().Set(rc);
339 }
340 }
341
EnvDeleter(Local<Name> property,const PropertyCallbackInfo<Boolean> & info)342 static void EnvDeleter(Local<Name> property,
343 const PropertyCallbackInfo<Boolean>& info) {
344 Environment* env = Environment::GetCurrent(info);
345 CHECK(env->has_run_bootstrapping_code());
346 if (property->IsString()) {
347 env->env_vars()->Delete(env->isolate(), property.As<String>());
348 }
349
350 // process.env never has non-configurable properties, so always
351 // return true like the tc39 delete operator.
352 info.GetReturnValue().Set(true);
353 }
354
EnvEnumerator(const PropertyCallbackInfo<Array> & info)355 static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) {
356 Environment* env = Environment::GetCurrent(info);
357 CHECK(env->has_run_bootstrapping_code());
358
359 info.GetReturnValue().Set(
360 env->env_vars()->Enumerate(env->isolate()));
361 }
362
CreateEnvVarProxy(Local<Context> context,Isolate * isolate,Local<Object> data)363 MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context,
364 Isolate* isolate,
365 Local<Object> data) {
366 EscapableHandleScope scope(isolate);
367 Local<ObjectTemplate> env_proxy_template = ObjectTemplate::New(isolate);
368 env_proxy_template->SetHandler(NamedPropertyHandlerConfiguration(
369 EnvGetter, EnvSetter, EnvQuery, EnvDeleter, EnvEnumerator, data,
370 PropertyHandlerFlags::kHasNoSideEffect));
371 return scope.EscapeMaybe(env_proxy_template->NewInstance(context));
372 }
373 } // namespace node
374