mirror of
https://github.com/oven-sh/bun
synced 2026-03-01 21:11:03 +01:00
411 lines
16 KiB
C++
411 lines
16 KiB
C++
#include "root.h"
|
|
#include "ZigGlobalObject.h"
|
|
|
|
#include "helpers.h"
|
|
|
|
#include <JavaScriptCore/JSObject.h>
|
|
#include <JavaScriptCore/ObjectConstructor.h>
|
|
#include <JavaScriptCore/JSArray.h>
|
|
#include <JavaScriptCore/JSArrayInlines.h>
|
|
#include <JavaScriptCore/JSString.h>
|
|
#include <JavaScriptCore/JSStringInlines.h>
|
|
|
|
#include "BunClientData.h"
|
|
#include "wtf/Compiler.h"
|
|
#include "wtf/Forward.h"
|
|
|
|
using namespace JSC;
|
|
|
|
extern "C" size_t Bun__getEnvCount(JSGlobalObject* globalObject, void** list_ptr);
|
|
extern "C" size_t Bun__getEnvKey(void* list, size_t index, unsigned char** out);
|
|
|
|
extern "C" bool Bun__getEnvValue(JSGlobalObject* globalObject, ZigString* name, ZigString* value);
|
|
|
|
namespace Bun {
|
|
|
|
using namespace WebCore;
|
|
|
|
JSC_DEFINE_CUSTOM_GETTER(jsGetterEnvironmentVariable, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
|
|
if (!thisObject) [[unlikely]]
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
ZigString name = toZigString(propertyName.publicName());
|
|
ZigString value = { nullptr, 0 };
|
|
|
|
if (name.len == 0) [[unlikely]]
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
if (!Bun__getEnvValue(globalObject, &name, &value)) {
|
|
return JSValue::encode(jsUndefined());
|
|
}
|
|
|
|
JSValue result = jsString(vm, Zig::toStringCopy(value));
|
|
thisObject->putDirect(vm, propertyName, result, 0);
|
|
return JSValue::encode(result);
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_SETTER(jsSetterEnvironmentVariable, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
JSC::JSObject* object = JSValue::decode(thisValue).getObject();
|
|
if (!object)
|
|
return false;
|
|
|
|
auto string = JSValue::decode(value).toString(globalObject);
|
|
if (!string) [[unlikely]]
|
|
return false;
|
|
|
|
object->putDirect(vm, propertyName, string, 0);
|
|
return true;
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_GETTER(jsTimeZoneEnvironmentVariableGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
|
|
if (!thisObject) [[unlikely]]
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
auto* clientData = WebCore::clientData(vm);
|
|
|
|
ZigString name = toZigString(propertyName.publicName());
|
|
ZigString value = { nullptr, 0 };
|
|
|
|
auto hasExistingValue = thisObject->getIfPropertyExists(globalObject, clientData->builtinNames().dataPrivateName());
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
if (hasExistingValue) {
|
|
return JSValue::encode(hasExistingValue);
|
|
}
|
|
|
|
if (!Bun__getEnvValue(globalObject, &name, &value) || value.len == 0) {
|
|
return JSValue::encode(jsUndefined());
|
|
}
|
|
|
|
JSValue out = jsString(vm, Zig::toStringCopy(value));
|
|
thisObject->putDirect(vm, clientData->builtinNames().dataPrivateName(), out, 0);
|
|
|
|
return JSValue::encode(out);
|
|
}
|
|
|
|
// In Node.js, the "TZ" environment variable is special.
|
|
// Setting it automatically updates the timezone.
|
|
// We also expose an explicit setTimeZone function in bun:jsc
|
|
JSC_DEFINE_CUSTOM_SETTER(jsTimeZoneEnvironmentVariableSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
JSC::JSObject* object = JSValue::decode(thisValue).getObject();
|
|
if (!object)
|
|
return false;
|
|
|
|
JSValue decodedValue = JSValue::decode(value);
|
|
if (decodedValue.isString()) {
|
|
auto timeZoneName = decodedValue.toWTFString(globalObject);
|
|
if (timeZoneName.length() < 32) {
|
|
if (WTF::setTimeZoneOverride(timeZoneName)) {
|
|
vm.dateCache.resetIfNecessarySlow();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto* clientData = WebCore::clientData(vm);
|
|
auto* builtinNames = &clientData->builtinNames();
|
|
auto privateName = builtinNames->dataPrivateName();
|
|
object->putDirect(vm, privateName, JSValue::decode(value), 0);
|
|
|
|
// TODO: this is an assertion failure
|
|
// Recreate this because the property visibility needs to be set correctly
|
|
// object->putDirectWithoutTransition(vm, propertyName, JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), JSC::PropertyAttribute::CustomAccessor | 0);
|
|
return true;
|
|
}
|
|
|
|
extern "C" int Bun__getTLSRejectUnauthorizedValue();
|
|
extern "C" int Bun__setTLSRejectUnauthorizedValue(int value);
|
|
extern "C" int Bun__getVerboseFetchValue();
|
|
extern "C" int Bun__setVerboseFetchValue(int value);
|
|
|
|
ALWAYS_INLINE static Identifier NODE_TLS_REJECT_UNAUTHORIZED_PRIVATE_PROPERTY(VM& vm)
|
|
{
|
|
auto* clientData = WebCore::clientData(vm);
|
|
auto& builtinNames = clientData->builtinNames();
|
|
// We just pick one to reuse. This will never be exposed to a user. And we
|
|
// don't want to pay the cost of adding another one.
|
|
return builtinNames.textDecoderStreamDecoderPrivateName();
|
|
}
|
|
|
|
ALWAYS_INLINE static Identifier BUN_CONFIG_VERBOSE_FETCH_PRIVATE_PROPERTY(VM& vm)
|
|
{
|
|
auto* clientData = WebCore::clientData(vm);
|
|
auto& builtinNames = clientData->builtinNames();
|
|
// We just pick one to reuse. This will never be exposed to a user. And we
|
|
// don't want to pay the cost of adding another one.
|
|
return builtinNames.textEncoderStreamEncoderPrivateName();
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_GETTER(jsNodeTLSRejectUnauthorizedGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
|
|
if (!thisObject) [[unlikely]]
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
const auto& privateName = NODE_TLS_REJECT_UNAUTHORIZED_PRIVATE_PROPERTY(vm);
|
|
JSValue result = thisObject->getDirect(vm, privateName);
|
|
if (result) [[unlikely]] {
|
|
return JSValue::encode(result);
|
|
}
|
|
|
|
ZigString name = toZigString(propertyName.publicName());
|
|
ZigString value = { nullptr, 0 };
|
|
|
|
if (!Bun__getEnvValue(globalObject, &name, &value) || value.len == 0) {
|
|
return JSValue::encode(jsUndefined());
|
|
}
|
|
|
|
return JSValue::encode(jsString(vm, Zig::toStringCopy(value)));
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_SETTER(jsNodeTLSRejectUnauthorizedSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
JSC::JSObject* object = JSValue::decode(thisValue).getObject();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
if (!object)
|
|
return false;
|
|
|
|
JSValue decodedValue = JSValue::decode(value);
|
|
WTF::String str = decodedValue.toWTFString(globalObject);
|
|
RETURN_IF_EXCEPTION(scope, false);
|
|
|
|
// TODO: only check "0". Node doesn't check both. But we already did. So we
|
|
// should wait to do that until Bun v1.2.0.
|
|
if (str == "0"_s || str == "false"_s) {
|
|
Bun__setTLSRejectUnauthorizedValue(0);
|
|
} else {
|
|
Bun__setTLSRejectUnauthorizedValue(1);
|
|
}
|
|
|
|
const auto& privateName = NODE_TLS_REJECT_UNAUTHORIZED_PRIVATE_PROPERTY(vm);
|
|
object->putDirect(vm, privateName, JSValue::decode(value), 0);
|
|
|
|
// TODO: this is an assertion failure
|
|
// Recreate this because the property visibility needs to be set correctly
|
|
// object->putDirectWithoutTransition(vm, propertyName, JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), JSC::PropertyAttribute::CustomAccessor | 0);
|
|
return true;
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_GETTER(jsBunConfigVerboseFetchGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
|
|
if (!thisObject) [[unlikely]]
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
const auto& privateName = BUN_CONFIG_VERBOSE_FETCH_PRIVATE_PROPERTY(vm);
|
|
JSValue result = thisObject->getDirect(vm, privateName);
|
|
if (result) [[unlikely]] {
|
|
return JSValue::encode(result);
|
|
}
|
|
|
|
ZigString name = toZigString(propertyName.publicName());
|
|
ZigString value = { nullptr, 0 };
|
|
|
|
if (!Bun__getEnvValue(globalObject, &name, &value) || value.len == 0) {
|
|
return JSValue::encode(jsUndefined());
|
|
}
|
|
|
|
return JSValue::encode(jsString(vm, Zig::toStringCopy(value)));
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_SETTER(jsBunConfigVerboseFetchSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
JSC::JSObject* object = JSValue::decode(thisValue).getObject();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
if (!object)
|
|
return false;
|
|
|
|
JSValue decodedValue = JSValue::decode(value);
|
|
WTF::String str = decodedValue.toWTFString(globalObject);
|
|
RETURN_IF_EXCEPTION(scope, false);
|
|
|
|
if (str == "1"_s || str == "true"_s) {
|
|
Bun__setVerboseFetchValue(1);
|
|
} else if (str == "curl"_s) {
|
|
Bun__setVerboseFetchValue(2);
|
|
} else {
|
|
Bun__setVerboseFetchValue(0);
|
|
}
|
|
|
|
const auto& privateName = BUN_CONFIG_VERBOSE_FETCH_PRIVATE_PROPERTY(vm);
|
|
object->putDirect(vm, privateName, JSValue::decode(value), 0);
|
|
|
|
// TODO: this is an assertion failure
|
|
// Recreate this because the property visibility needs to be set correctly
|
|
// object->putDirectWithoutTransition(vm, propertyName, JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), JSC::PropertyAttribute::CustomAccessor | 0);
|
|
return true;
|
|
}
|
|
|
|
#if OS(WINDOWS)
|
|
extern "C" void Bun__Process__editWindowsEnvVar(BunString, BunString);
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::CallFrame* callFrame))
|
|
{
|
|
auto scope = DECLARE_THROW_SCOPE(global->vm());
|
|
ASSERT(callFrame->argumentCount() == 2);
|
|
ASSERT(callFrame->uncheckedArgument(0).isString());
|
|
WTF::String string1 = callFrame->uncheckedArgument(0).toWTFString(global);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
JSValue arg2 = callFrame->uncheckedArgument(1);
|
|
ASSERT(arg2.isNull() || arg2.isString());
|
|
if (arg2.isCell()) {
|
|
WTF::String string2 = arg2.toWTFString(global);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
Bun__Process__editWindowsEnvVar(Bun::toString(string1), Bun::toString(string2));
|
|
} else {
|
|
Bun__Process__editWindowsEnvVar(Bun::toString(string1), { .tag = BunStringTag::Dead });
|
|
}
|
|
RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
|
|
}
|
|
#endif
|
|
|
|
JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
void* list;
|
|
size_t count = Bun__getEnvCount(globalObject, &list);
|
|
JSC::JSObject* object = nullptr;
|
|
if (count < 63) {
|
|
object = constructEmptyObject(globalObject, globalObject->objectPrototype(), count);
|
|
} else {
|
|
object = constructEmptyObject(globalObject, globalObject->objectPrototype());
|
|
}
|
|
|
|
#if OS(WINDOWS)
|
|
JSArray* keyArray = constructEmptyArray(globalObject, nullptr, count);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
#endif
|
|
|
|
static NeverDestroyed<String> TZ = MAKE_STATIC_STRING_IMPL("TZ");
|
|
String NODE_TLS_REJECT_UNAUTHORIZED = String("NODE_TLS_REJECT_UNAUTHORIZED"_s);
|
|
String BUN_CONFIG_VERBOSE_FETCH = String("BUN_CONFIG_VERBOSE_FETCH"_s);
|
|
bool hasTZ = false;
|
|
bool hasNodeTLSRejectUnauthorized = false;
|
|
bool hasBunConfigVerboseFetch = false;
|
|
|
|
auto* cached_getter_setter = JSC::CustomGetterSetter::create(vm, jsGetterEnvironmentVariable, nullptr);
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
unsigned char* chars;
|
|
size_t len = Bun__getEnvKey(list, i, &chars);
|
|
// We can't really trust that the OS gives us valid UTF-8
|
|
auto name = String::fromUTF8ReplacingInvalidSequences(std::span { chars, len });
|
|
#if OS(WINDOWS)
|
|
keyArray->putByIndexInline(globalObject, (unsigned)i, jsString(vm, name), false);
|
|
#endif
|
|
if (name == TZ) {
|
|
hasTZ = true;
|
|
continue;
|
|
}
|
|
if (name == NODE_TLS_REJECT_UNAUTHORIZED) {
|
|
hasNodeTLSRejectUnauthorized = true;
|
|
continue;
|
|
}
|
|
if (name == BUN_CONFIG_VERBOSE_FETCH) {
|
|
hasBunConfigVerboseFetch = true;
|
|
continue;
|
|
}
|
|
ASSERT(len > 0);
|
|
#if OS(WINDOWS)
|
|
String idName = name.convertToASCIIUppercase();
|
|
#else
|
|
String idName = name;
|
|
#endif
|
|
Identifier identifier = Identifier::fromString(vm, idName);
|
|
|
|
// CustomGetterSetter doesn't support indexed properties yet.
|
|
// This causes strange issues when the environment variable name is an integer.
|
|
if (chars[0] >= '0' && chars[0] <= '9') [[unlikely]] {
|
|
if (auto index = parseIndex(identifier)) {
|
|
ZigString valueString = { nullptr, 0 };
|
|
ZigString nameStr = toZigString(name);
|
|
if (Bun__getEnvValue(globalObject, &nameStr, &valueString)) {
|
|
JSValue value = jsString(vm, Zig::toStringCopy(valueString));
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
object->putDirectIndex(globalObject, *index, value, 0, PutDirectIndexLikePutDirect);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// JSC::PropertyAttribute::CustomValue calls the getter ONCE (the first
|
|
// time) and then sets it onto the object, subsequent calls to the
|
|
// getter will not go through the getter and instead will just do the
|
|
// property lookup.
|
|
object->putDirectCustomAccessor(vm, identifier, cached_getter_setter, JSC::PropertyAttribute::CustomValue | 0);
|
|
}
|
|
|
|
unsigned int TZAttrs = JSC::PropertyAttribute::CustomAccessor | 0;
|
|
if (!hasTZ) {
|
|
TZAttrs |= JSC::PropertyAttribute::DontEnum;
|
|
}
|
|
object->putDirectCustomAccessor(
|
|
vm,
|
|
Identifier::fromString(vm, TZ), JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), TZAttrs);
|
|
|
|
unsigned int NODE_TLS_REJECT_UNAUTHORIZED_Attrs = JSC::PropertyAttribute::CustomAccessor | 0;
|
|
if (!hasNodeTLSRejectUnauthorized) {
|
|
NODE_TLS_REJECT_UNAUTHORIZED_Attrs |= JSC::PropertyAttribute::DontEnum;
|
|
}
|
|
object->putDirectCustomAccessor(
|
|
vm,
|
|
Identifier::fromString(vm, NODE_TLS_REJECT_UNAUTHORIZED), JSC::CustomGetterSetter::create(vm, jsNodeTLSRejectUnauthorizedGetter, jsNodeTLSRejectUnauthorizedSetter), NODE_TLS_REJECT_UNAUTHORIZED_Attrs);
|
|
|
|
unsigned int BUN_CONFIG_VERBOSE_FETCH_Attrs = JSC::PropertyAttribute::CustomAccessor | 0;
|
|
if (!hasBunConfigVerboseFetch) {
|
|
BUN_CONFIG_VERBOSE_FETCH_Attrs |= JSC::PropertyAttribute::DontEnum;
|
|
}
|
|
object->putDirectCustomAccessor(
|
|
vm,
|
|
Identifier::fromString(vm, BUN_CONFIG_VERBOSE_FETCH), JSC::CustomGetterSetter::create(vm, jsBunConfigVerboseFetchGetter, jsBunConfigVerboseFetchSetter), BUN_CONFIG_VERBOSE_FETCH_Attrs);
|
|
|
|
#if OS(WINDOWS)
|
|
auto editWindowsEnvVar = JSC::JSFunction::create(vm, globalObject, 0, String("editWindowsEnvVar"_s), jsEditWindowsEnvVar, ImplementationVisibility::Public);
|
|
|
|
JSC::JSFunction* getSourceEvent = JSC::JSFunction::create(vm, globalObject, processObjectInternalsWindowsEnvCodeGenerator(vm), globalObject);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
JSC::MarkedArgumentBuffer args;
|
|
args.append(object);
|
|
args.append(keyArray);
|
|
args.append(editWindowsEnvVar);
|
|
auto clientData = WebCore::clientData(vm);
|
|
JSC::CallData callData = JSC::getCallData(getSourceEvent);
|
|
NakedPtr<JSC::Exception> returnedException = nullptr;
|
|
auto result = JSC::profiledCall(globalObject, JSC::ProfilingReason::API, getSourceEvent, callData, globalObject->globalThis(), args, returnedException);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
if (returnedException) {
|
|
throwException(globalObject, scope, returnedException.get());
|
|
return jsUndefined();
|
|
}
|
|
|
|
RELEASE_AND_RETURN(scope, result);
|
|
#else
|
|
return object;
|
|
#endif
|
|
}
|
|
}
|