@@ -169,6 +169,116 @@ bool Fastly::getGeolocationForIpAddress(JSContext *cx, unsigned argc, JS::Value
169169 return JS_ParseJSON (cx, geo_info_str, args.rval ());
170170}
171171
172+ bool Fastly::inspect (JSContext *cx, unsigned argc, JS::Value *vp) {
173+ JS::CallArgs args = CallArgsFromVp (argc, vp);
174+ REQUEST_HANDLER_ONLY (" inspect" );
175+ if (!args.requireAtLeast (cx, " inspect" , 1 )) {
176+ return false ;
177+ }
178+
179+ auto request_value = args.get (0 );
180+ if (!Request::is_instance (request_value)) {
181+ JS_ReportErrorUTF8 (cx, " inspect: request parameter must be an instance of Request" );
182+ return false ;
183+ }
184+ auto inspect_response_obj = &request_value.toObject ();
185+
186+ auto options_value = args.get (1 );
187+ JS::RootedObject options_obj (cx, options_value.isObject () ? &options_value.toObject () : nullptr );
188+
189+ host_api::InspectOptions inspect_options (Request::request_handle (inspect_response_obj),
190+ RequestOrResponse::body_handle (inspect_response_obj));
191+
192+ if (options_value != nullptr ) {
193+
194+ host_api::HostString corp_str;
195+ JS::RootedValue corp_val (cx);
196+ if (!JS_GetProperty (cx, options, " corp" , &corp_val)) {
197+ return false ;
198+ }
199+ if (!corp_val.isNullOrUndefined ()) {
200+ if (!corp_val.isString ()) {
201+ api::throw_error (cx, api::Errors::TypeError, " inspect" , " corp" , " be a string" );
202+ return false ;
203+ }
204+ corp_str = core::encode (cx, corp_val);
205+ if (!corp_str) {
206+ return false ;
207+ }
208+ std::optional<std::string_view> corp = corp_str;
209+ if (corp) {
210+ inspect_options.corp_len = corp->length ();
211+ inspect_options.corp = std::move (corp->data ());
212+ }
213+ }
214+
215+ host_api::HostString workspace_str;
216+ JS::RootedValue workspace_val (cx);
217+ if (!JS_GetProperty (cx, options, " workspace" , &workspace_val)) {
218+ return false ;
219+ }
220+ if (!workspace_val.isNullOrUndefined ()) {
221+ if (!workspace_val.isString ()) {
222+ api::throw_error (cx, api::Errors::TypeError, " inspect" , " workspace" , " be a string" );
223+ return false ;
224+ }
225+ workspace_str = core::encode (cx, workspace_val);
226+ if (!workspace_str) {
227+ return false ;
228+ }
229+ std::optional<std::string_view> workspace = workspace_str;
230+ if (workspace) {
231+ inspect_options.workspace_len = workspace->length ();
232+ inspect_options.workspace = std::move (workspace->data ());
233+ }
234+ }
235+
236+ host_api::HostString override_client_ip_str;
237+ JS::RootedValue override_client_ip_val (cx);
238+ if (!JS_GetProperty (cx, options, " overrideClientIp" , &override_client_ip_val)) {
239+ return false ;
240+ }
241+ if (!override_client_ip_val.isNullOrUndefined ()) {
242+ if (!override_client_ip_val.isString ()) {
243+ api::throw_error (cx, api::Errors::TypeError, " fastly.inspect" , " overrideClientIp" ,
244+ " be a string" );
245+ return false ;
246+ }
247+ override_client_ip_str = core::encode (cx, override_client_ip_val);
248+ if (!override_client_ip_str) {
249+ return false ;
250+ }
251+
252+ // TODO: Remove all of this and rely on the host for validation as the hostcall only takes one
253+ // user-supplied parameter
254+ int format = AF_INET;
255+ size_t octets_len = 4 ;
256+ if (std::find (override_client_ip_str.begin (), override_client_ip_str.end (), ' :' ) !=
257+ override_client_ip_str.end ()) {
258+ format = AF_INET6;
259+ octets_len = 16 ;
260+ }
261+
262+ uint8_t octets[sizeof (struct in6_addr )];
263+ if (inet_pton (format, override_client_ip_str.begin (), octets) != 1 ) {
264+ api::throw_error (cx, api::Errors::TypeError, " fastly.inspect" , " overrideClientIp" ,
265+ " be a valid IP address" );
266+ return false ;
267+ }
268+ inspect_options.override_client_ip_len = octets_len;
269+ inspect_options.override_client_ip = std::move (octets->data ());
270+ }
271+ }
272+
273+ auto res = request_value->inspect (&inspect_options);
274+ if (auto *err = res.to_err ()) {
275+ HANDLE_ERROR (cx, *err);
276+ return false ;
277+ }
278+
279+ return JS_ParseJSON (cx, inspect_info_str, args.rval ());
280+ }
281+
172282// TODO(performance): consider allowing logger creation during initialization, but then throw
173283// when trying to log.
174284// https://github.com/fastly/js-compute-runtime/issues/225
@@ -610,6 +720,7 @@ bool install(api::Engine *engine) {
610720 JS_FN (" enableDebugLogging" , Fastly::enableDebugLogging, 1 , JSPROP_ENUMERATE),
611721 JS_FN (" debugLog" , debugLog, 1 , JSPROP_ENUMERATE),
612722 JS_FN (" getGeolocationForIpAddress" , Fastly::getGeolocationForIpAddress, 1 , JSPROP_ENUMERATE),
723+ JS_FN (" inspect" , Fastly::inspect, 1 , JSPROP_ENUMERATE),
613724 JS_FN (" getLogger" , Fastly::getLogger, 1 , JSPROP_ENUMERATE),
614725 JS_FN (" includeBytes" , Fastly::includeBytes, 1 , JSPROP_ENUMERATE),
615726 JS_FN (" createFanoutHandoff" , Fastly::createFanoutHandoff, 2 , JSPROP_ENUMERATE),
@@ -751,6 +862,21 @@ bool install(api::Engine *engine) {
751862 if (!engine->define_builtin_module (" fastly:fanout" , fanout_val)) {
752863 return false ;
753864 }
865+
866+ // fastly:security
867+ RootedValue inspect_val (engine->cx ());
868+ if (!JS_GetProperty (engine->cx (), fastly, " inspect" , &inspect_val)) {
869+ return false ;
870+ }
871+ RootedObject security_builtin (engine->cx (), JS_NewObject (engine->cx (), nullptr ));
872+ RootedValue security_builtin_val (engine->cx (), JS::ObjectValue (*security_builtin));
873+ if (!JS_SetProperty (engine->cx (), security_builtin, " inspect" , inspect_val)) {
874+ return false ;
875+ }
876+ if (!engine->define_builtin_module (" fastly:security" , security_builtin_val)) {
877+ return false ;
878+ }
879+
754880 // fastly:websocket
755881 RootedObject websocket (engine->cx (), JS_NewObject (engine->cx (), nullptr ));
756882 RootedValue websocket_val (engine->cx (), JS::ObjectValue (*websocket));
0 commit comments