Skip to content
File

Blob: src/workerd/api/node/util.c++

7.2 KB
1// Copyright (c) 2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4#include "util.h"
5 
6#include <workerd/io/features.h>
7#include <workerd/io/io-context.h>
8#include <workerd/io/tracer.h>
9#include <workerd/jsg/jsg.h>
10 
11#include <kj/vector.h>
12 
13namespace workerd::api::node {
14 
15MIMEParams::MIMEParams(kj::Maybe<MimeType&> mimeType): mimeType(mimeType) {}
16 
17// Oddly, Node.js allows creating MIMEParams directly but it's not actually
18// functional. But, to match, we'll go ahead and allow it.
19jsg::Ref<MIMEParams> MIMEParams::constructor(jsg::Lock& js) {
20 return js.alloc<MIMEParams>(kj::none);
21}
22 
23void MIMEParams::delete_(kj::String name) {
24 KJ_IF_SOME(inner, mimeType) {
25 inner.eraseParam(name);
26 }
27}
28 
29kj::Maybe<kj::StringPtr> MIMEParams::get(kj::String name) {
30 KJ_IF_SOME(inner, mimeType) {
31 return inner.params().find(name);
32 }
33 return kj::none;
34}
35 
36bool MIMEParams::has(kj::String name) {
37 KJ_IF_SOME(inner, mimeType) {
38 return inner.params().find(name) != kj::none;
39 }
40 return false;
41}
42 
43void MIMEParams::set(kj::String name, kj::String value) {
44 KJ_IF_SOME(inner, mimeType) {
45 JSG_REQUIRE(inner.addParam(name, value), TypeError, "Not a valid MIME parameter");
46 }
47}
48 
49kj::String MIMEParams::toString() {
50 KJ_IF_SOME(inner, mimeType) {
51 return inner.paramsToString();
52 }
53 return kj::String();
54}
55 
56jsg::Ref<MIMEParams::EntryIterator> MIMEParams::entries(jsg::Lock& js) {
57 kj::Vector<kj::Array<kj::String>> vec;
58 KJ_IF_SOME(inner, mimeType) {
59 for (const auto& entry: inner.params()) {
60 vec.add(kj::arr(kj::str(entry.key), kj::str(entry.value)));
61 }
62 }
63 return js.alloc<EntryIterator>(IteratorState<kj::Array<kj::String>>{vec.releaseAsArray()});
64}
65 
66jsg::Ref<MIMEParams::KeyIterator> MIMEParams::keys(jsg::Lock& js) {
67 kj::Vector<kj::String> vec;
68 KJ_IF_SOME(inner, mimeType) {
69 for (const auto& entry: inner.params()) {
70 vec.add(kj::str(entry.key));
71 }
72 }
73 return js.alloc<KeyIterator>(IteratorState<kj::String>{vec.releaseAsArray()});
74}
75 
76jsg::Ref<MIMEParams::ValueIterator> MIMEParams::values(jsg::Lock& js) {
77 kj::Vector<kj::String> vec;
78 KJ_IF_SOME(inner, mimeType) {
79 for (const auto& entry: inner.params()) {
80 vec.add(kj::str(entry.value));
81 }
82 }
83 return js.alloc<ValueIterator>(IteratorState<kj::String>{vec.releaseAsArray()});
84}
85 
86MIMEType::MIMEType(jsg::Lock& js, MimeType inner)
87 : inner(kj::mv(inner)),
88 params(js.alloc<MIMEParams>(this->inner)) {}
89 
90MIMEType::~MIMEType() noexcept(false) {
91 // Break the connection with the MIMEParams
92 params->mimeType = kj::none;
93}
94 
95jsg::Ref<MIMEType> MIMEType::constructor(jsg::Lock& js, kj::String input) {
96 auto parsed =
97 JSG_REQUIRE_NONNULL(MimeType::tryParse(input), TypeError, "Not a valid MIME type: ", input);
98 return js.alloc<MIMEType>(js, kj::mv(parsed));
99}
100 
101kj::StringPtr MIMEType::getType() {
102 return inner.type();
103}
104 
105void MIMEType::setType(kj::String type) {
106 JSG_REQUIRE(inner.setType(type), TypeError, "Not a valid MIME type");
107}
108 
109kj::StringPtr MIMEType::getSubtype() {
110 return inner.subtype();
111}
112 
113void MIMEType::setSubtype(kj::String subtype) {
114 JSG_REQUIRE(inner.setSubtype(subtype), TypeError, "Not a valid MIME subtype");
115}
116 
117kj::String MIMEType::getEssence() {
118 return inner.essence();
119}
120 
121jsg::Ref<MIMEParams> MIMEType::getParams() {
122 return params.addRef();
123}
124 
125kj::String MIMEType::toString() {
126 return inner.toString();
127}
128 
129jsg::JsArray UtilModule::getOwnNonIndexProperties(jsg::Lock& js, jsg::JsObject value, int filter) {
130 auto propertyFilter = static_cast<jsg::PropertyFilter>(filter);
131 return value.getPropertyNames(
132 js, jsg::KeyCollectionFilter::OWN_ONLY, propertyFilter, jsg::IndexFilter::SKIP_INDICES);
133}
134 
135jsg::Optional<UtilModule::PromiseDetails> UtilModule::getPromiseDetails(
136 jsg::Lock& js, jsg::JsValue value) {
137 auto promise = KJ_UNWRAP_OR_RETURN(value.tryCast<jsg::JsPromise>(), kj::none);
138 auto state = promise.state();
139 if (state != jsg::PromiseState::PENDING) {
140 auto result = promise.result();
141 return PromiseDetails{
142 .state = state,
143 .result = jsg::JsRef(js, result),
144 };
145 } else {
146 return PromiseDetails{
147 .state = state,
148 .result = kj::none,
149 };
150 }
151}
152 
153jsg::Optional<UtilModule::ProxyDetails> UtilModule::getProxyDetails(
154 jsg::Lock& js, jsg::JsValue value) {
155 auto proxy = KJ_UNWRAP_OR_RETURN(value.tryCast<jsg::JsProxy>(), kj::none);
156 auto target = proxy.target();
157 auto handler = proxy.handler();
158 return ProxyDetails{
159 .target = jsg::JsRef(js, target),
160 .handler = jsg::JsRef(js, handler),
161 };
162}
163 
164jsg::Optional<UtilModule::PreviewedEntries> UtilModule::previewEntries(
165 jsg::Lock& js, jsg::JsValue value) {
166 auto object = KJ_UNWRAP_OR_RETURN(value.tryCast<jsg::JsObject>(), kj::none);
167 bool isKeyValue;
168 auto entries = object.previewEntries(&isKeyValue);
169 return PreviewedEntries{
170 .entries = jsg::JsRef(js, entries),
171 .isKeyValue = isKeyValue,
172 };
173}
174 
175jsg::JsString UtilModule::getConstructorName(jsg::Lock& js, jsg::JsObject value) {
176 return js.str(value.getConstructorName());
177}
178 
179#define V(Type) \
180 bool UtilModule::is##Type(jsg::JsValue value) { \
181 return value.is##Type(); \
182 };
183JS_UTIL_IS_TYPES(V)
184#undef V
185bool UtilModule::isAnyArrayBuffer(jsg::JsValue value) {
186 return value.isArrayBuffer() || value.isSharedArrayBuffer();
187}
188bool UtilModule::isBoxedPrimitive(jsg::JsValue value) {
189 return value.isNumberObject() || value.isStringObject() || value.isBooleanObject() ||
190 value.isBigIntObject() || value.isSymbolObject();
191}
192 
193jsg::Name UtilModule::getResourceTypeInspect(jsg::Lock& js) {
194 return js.newApiSymbol("kResourceTypeInspect"_kj);
195}
196 
197kj::Array<UtilModule::CallSiteEntry> UtilModule::getCallSites(
198 jsg::Lock& js, jsg::Optional<int> frames) {
199 KJ_IF_SOME(f, frames) {
200 JSG_REQUIRE(f >= 1 && f <= 200, Error, "Frame count should be between 1 and 200 inclusive."_kj);
201 }
202 
203 auto stack = v8::StackTrace::CurrentStackTrace(js.v8Isolate, frames.orDefault(10) + 1);
204 const int frameCount = stack->GetFrameCount();
205 auto objects = kj::Vector<CallSiteEntry>();
206 objects.reserve(frameCount - 1);
207 
208 for (int i = 0; i < frameCount; ++i) {
209 auto stack_frame = stack->GetFrame(js.v8Isolate, i);
210 
211 auto function_name = stack_frame->GetFunctionName();
212 auto script_name = stack_frame->GetScriptName();
213 
214 if (!function_name.IsEmpty() && !script_name.IsEmpty()) {
215 
216 objects.add(CallSiteEntry{
217 .functionName = js.toString(function_name),
218 .scriptName = js.toString(script_name),
219 .lineNumber = stack_frame->GetLineNumber(),
220 // Node.js originally implemented the experimental API using the "column" field
221 // then later renamed it to columnNumber. We had already implemented the API
222 // using column. To ensure backwards compat without the complexity of a compat
223 // flag, we just export both.
224 .columnNumber = stack_frame->GetColumn(),
225 .column = stack_frame->GetColumn(),
226 });
227 }
228 }
229 
230 return objects.releaseAsArray();
231}
232 
233} // namespace workerd::api::node