mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
PR-URL: https://github.com/nodejs/node/pull/52406 Fixes: https://github.com/nodejs/node/issues/52248 Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
275 lines
7.8 KiB
C++
275 lines
7.8 KiB
C++
#include "node_dotenv.h"
|
|
#include <unordered_set>
|
|
#include "env-inl.h"
|
|
#include "node_file.h"
|
|
#include "uv.h"
|
|
|
|
namespace node {
|
|
|
|
using v8::Local;
|
|
using v8::NewStringType;
|
|
using v8::Object;
|
|
using v8::String;
|
|
|
|
std::vector<std::string> Dotenv::GetPathFromArgs(
|
|
const std::vector<std::string>& args) {
|
|
const auto find_match = [](const std::string& arg) {
|
|
const std::string_view flag = "--env-file";
|
|
return strncmp(arg.c_str(), flag.data(), flag.size()) == 0;
|
|
};
|
|
std::vector<std::string> paths;
|
|
auto path = std::find_if(args.begin(), args.end(), find_match);
|
|
|
|
while (path != args.end()) {
|
|
auto equal_char = path->find('=');
|
|
|
|
if (equal_char != std::string::npos) {
|
|
paths.push_back(path->substr(equal_char + 1));
|
|
} else {
|
|
auto next_path = std::next(path);
|
|
|
|
if (next_path == args.end()) {
|
|
return paths;
|
|
}
|
|
|
|
paths.push_back(*next_path);
|
|
}
|
|
|
|
path = std::find_if(++path, args.end(), find_match);
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
void Dotenv::SetEnvironment(node::Environment* env) {
|
|
if (store_.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto isolate = env->isolate();
|
|
|
|
for (const auto& entry : store_) {
|
|
auto key = entry.first;
|
|
auto value = entry.second;
|
|
|
|
auto existing = env->env_vars()->Get(key.data());
|
|
|
|
if (existing.IsNothing()) {
|
|
env->env_vars()->Set(
|
|
isolate,
|
|
v8::String::NewFromUtf8(
|
|
isolate, key.data(), NewStringType::kNormal, key.size())
|
|
.ToLocalChecked(),
|
|
v8::String::NewFromUtf8(
|
|
isolate, value.data(), NewStringType::kNormal, value.size())
|
|
.ToLocalChecked());
|
|
}
|
|
}
|
|
}
|
|
|
|
Local<Object> Dotenv::ToObject(Environment* env) {
|
|
Local<Object> result = Object::New(env->isolate());
|
|
|
|
for (const auto& entry : store_) {
|
|
auto key = entry.first;
|
|
auto value = entry.second;
|
|
|
|
result
|
|
->Set(
|
|
env->context(),
|
|
v8::String::NewFromUtf8(
|
|
env->isolate(), key.data(), NewStringType::kNormal, key.size())
|
|
.ToLocalChecked(),
|
|
v8::String::NewFromUtf8(env->isolate(),
|
|
value.data(),
|
|
NewStringType::kNormal,
|
|
value.size())
|
|
.ToLocalChecked())
|
|
.Check();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string_view trim_spaces(std::string_view input) {
|
|
if (input.empty()) return "";
|
|
if (input.front() == ' ') {
|
|
input.remove_prefix(input.find_first_not_of(' '));
|
|
}
|
|
if (!input.empty() && input.back() == ' ') {
|
|
input = input.substr(0, input.find_last_not_of(' ') + 1);
|
|
}
|
|
return input;
|
|
}
|
|
|
|
void Dotenv::ParseContent(const std::string_view input) {
|
|
std::string lines(input);
|
|
|
|
// Handle windows newlines "\r\n": remove "\r" and keep only "\n"
|
|
lines.erase(std::remove(lines.begin(), lines.end(), '\r'), lines.end());
|
|
|
|
std::string_view content = lines;
|
|
content = trim_spaces(content);
|
|
|
|
std::string_view key;
|
|
std::string_view value;
|
|
|
|
while (!content.empty()) {
|
|
// Skip empty lines and comments
|
|
if (content.front() == '\n' || content.front() == '#') {
|
|
auto newline = content.find('\n');
|
|
if (newline != std::string_view::npos) {
|
|
content.remove_prefix(newline + 1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If there is no equal character, then ignore everything
|
|
auto equal = content.find('=');
|
|
if (equal == std::string_view::npos) {
|
|
break;
|
|
}
|
|
|
|
key = content.substr(0, equal);
|
|
content.remove_prefix(equal + 1);
|
|
key = trim_spaces(key);
|
|
|
|
if (key.empty()) {
|
|
break;
|
|
}
|
|
|
|
// Remove export prefix from key
|
|
auto have_export = key.compare(0, 7, "export ") == 0;
|
|
if (have_export) {
|
|
key.remove_prefix(7);
|
|
}
|
|
|
|
// SAFETY: Content is guaranteed to have at least one character
|
|
if (content.empty()) {
|
|
// In case the last line is a single key without value
|
|
// Example: KEY= (without a newline at the EOF)
|
|
store_.insert_or_assign(std::string(key), "");
|
|
break;
|
|
}
|
|
|
|
// Expand new line if \n it's inside double quotes
|
|
// Example: EXPAND_NEWLINES = 'expand\nnew\nlines'
|
|
if (content.front() == '"') {
|
|
auto closing_quote = content.find(content.front(), 1);
|
|
if (closing_quote != std::string_view::npos) {
|
|
value = content.substr(1, closing_quote - 1);
|
|
std::string multi_line_value = std::string(value);
|
|
|
|
size_t pos = 0;
|
|
while ((pos = multi_line_value.find("\\n", pos)) !=
|
|
std::string_view::npos) {
|
|
multi_line_value.replace(pos, 2, "\n");
|
|
pos += 1;
|
|
}
|
|
|
|
store_.insert_or_assign(std::string(key), multi_line_value);
|
|
content.remove_prefix(content.find('\n', closing_quote + 1));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check if the value is wrapped in quotes, single quotes or backticks
|
|
if ((content.front() == '\'' || content.front() == '"' ||
|
|
content.front() == '`')) {
|
|
auto closing_quote = content.find(content.front(), 1);
|
|
|
|
// Check if the closing quote is not found
|
|
// Example: KEY="value
|
|
if (closing_quote == std::string_view::npos) {
|
|
// Check if newline exist. If it does, take the entire line as the value
|
|
// Example: KEY="value\nKEY2=value2
|
|
// The value pair should be `"value`
|
|
auto newline = content.find('\n');
|
|
if (newline != std::string_view::npos) {
|
|
value = content.substr(0, newline);
|
|
store_.insert_or_assign(std::string(key), value);
|
|
content.remove_prefix(newline);
|
|
}
|
|
} else {
|
|
// Example: KEY="value"
|
|
value = content.substr(1, closing_quote - 1);
|
|
store_.insert_or_assign(std::string(key), value);
|
|
// Select the first newline after the closing quotation mark
|
|
// since there could be newline characters inside the value.
|
|
content.remove_prefix(content.find('\n', closing_quote + 1));
|
|
}
|
|
} else {
|
|
// Regular key value pair.
|
|
// Example: `KEY=this is value`
|
|
auto newline = content.find('\n');
|
|
|
|
if (newline != std::string_view::npos) {
|
|
value = content.substr(0, newline);
|
|
auto hash_character = value.find('#');
|
|
// Check if there is a comment in the line
|
|
// Example: KEY=value # comment
|
|
// The value pair should be `value`
|
|
if (hash_character != std::string_view::npos) {
|
|
value = content.substr(0, hash_character);
|
|
}
|
|
content.remove_prefix(newline);
|
|
} else {
|
|
// In case the last line is a single key/value pair
|
|
// Example: KEY=VALUE (without a newline at the EOF)
|
|
value = content.substr(0);
|
|
}
|
|
|
|
value = trim_spaces(value);
|
|
store_.insert_or_assign(std::string(key), value);
|
|
}
|
|
}
|
|
}
|
|
|
|
Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
|
|
uv_fs_t req;
|
|
auto defer_req_cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
|
|
|
uv_file file = uv_fs_open(nullptr, &req, path.data(), 0, 438, nullptr);
|
|
if (req.result < 0) {
|
|
// req will be cleaned up by scope leave.
|
|
return ParseResult::FileError;
|
|
}
|
|
uv_fs_req_cleanup(&req);
|
|
|
|
auto defer_close = OnScopeLeave([file]() {
|
|
uv_fs_t close_req;
|
|
CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr));
|
|
uv_fs_req_cleanup(&close_req);
|
|
});
|
|
|
|
std::string result{};
|
|
char buffer[8192];
|
|
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
|
|
|
|
while (true) {
|
|
auto r = uv_fs_read(nullptr, &req, file, &buf, 1, -1, nullptr);
|
|
if (req.result < 0) {
|
|
// req will be cleaned up by scope leave.
|
|
return ParseResult::InvalidContent;
|
|
}
|
|
uv_fs_req_cleanup(&req);
|
|
if (r <= 0) {
|
|
break;
|
|
}
|
|
result.append(buf.base, r);
|
|
}
|
|
|
|
ParseContent(result);
|
|
return ParseResult::Valid;
|
|
}
|
|
|
|
void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
|
|
auto match = store_.find("NODE_OPTIONS");
|
|
|
|
if (match != store_.end()) {
|
|
*node_options = match->second;
|
|
}
|
|
}
|
|
|
|
} // namespace node
|