@@ -169,6 +169,115 @@ 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 (" fastly.inspect" );
175+ if (!args.requireAtLeast (cx, " fastly.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+ JS::RootedObject request (cx, &request_value.toObject ());
185+ auto req{Request::request_handle (request)};
186+ auto bod{RequestOrResponse::body_handle (request)};
187+
188+ auto options_value = args.get (1 );
189+ JS::RootedObject options_obj (cx, options_value.isObject () ? &options_value.toObject () : nullptr );
190+
191+ host_api::InspectOptions inspect_options (req.handle , bod.handle );
192+
193+ host_api::HostString corp_str;
194+ JS::RootedValue corp_val (cx);
195+
196+ host_api::HostString workspace_str;
197+ JS::RootedValue workspace_val (cx);
198+
199+ host_api::HostString override_client_ip_str;
200+ JS::RootedValue override_client_ip_val (cx);
201+ alignas (struct in6_addr ) uint8_t octets[sizeof (struct in6_addr )];
202+
203+ if (options_obj != nullptr ) {
204+ if (JS_GetProperty (cx, options_obj, " corp" , &corp_val)) {
205+ if (!corp_val.isNullOrUndefined ()) {
206+ if (!corp_val.isString ()) {
207+ api::throw_error (cx, api::Errors::TypeError, " fastly.inspect" , " corp" , " be a string" );
208+ return false ;
209+ }
210+ corp_str = core::encode (cx, corp_val);
211+ if (!corp_str) {
212+ return false ;
213+ }
214+ inspect_options.corp_len = corp_str.size ();
215+ inspect_options.corp = std::move (corp_str.begin ());
216+ }
217+ }
218+
219+ if (JS_GetProperty (cx, options_obj, " workspace" , &workspace_val)) {
220+ if (!workspace_val.isNullOrUndefined ()) {
221+ if (!workspace_val.isString ()) {
222+ api::throw_error (cx, api::Errors::TypeError, " fastly.inspect" , " workspace" ,
223+ " be a string" );
224+ return false ;
225+ }
226+ workspace_str = core::encode (cx, workspace_val);
227+ if (!workspace_str) {
228+ return false ;
229+ }
230+ inspect_options.workspace_len = workspace_str.size ();
231+ inspect_options.workspace = std::move (workspace_str.begin ());
232+ }
233+ }
234+
235+ if (JS_GetProperty (cx, options_obj, " overrideClientIp" , &override_client_ip_val)) {
236+ if (!override_client_ip_val.isNullOrUndefined ()) {
237+ if (!override_client_ip_val.isString ()) {
238+ api::throw_error (cx, api::Errors::TypeError, " fastly.inspect" , " overrideClientIp" ,
239+ " be a string" );
240+ return false ;
241+ }
242+ override_client_ip_str = core::encode (cx, override_client_ip_val);
243+ if (!override_client_ip_str) {
244+ return false ;
245+ }
246+
247+ // TODO: Remove all of this and rely on the host for validation as the hostcall only takes
248+ // one user-supplied parameter
249+ int format = AF_INET;
250+ size_t octets_len = 4 ;
251+ if (std::find (override_client_ip_str.begin (), override_client_ip_str.end (), ' :' ) !=
252+ override_client_ip_str.end ()) {
253+ format = AF_INET6;
254+ octets_len = 16 ;
255+ }
256+
257+ if (inet_pton (format, override_client_ip_str.begin (), &octets) != 1 ) {
258+ api::throw_error (cx, api::Errors::TypeError, " fastly.inspect" , " overrideClientIp" ,
259+ " be a valid IP address" );
260+ return false ;
261+ }
262+ inspect_options.override_client_ip_len = octets_len;
263+ inspect_options.override_client_ip_ptr = reinterpret_cast <const char *>(&octets);
264+ }
265+ }
266+ }
267+
268+ host_api::Request host_request{req, bod};
269+ auto res = host_request.inspect (&inspect_options);
270+ if (auto *err = res.to_err ()) {
271+ HANDLE_ERROR (cx, *err);
272+ return false ;
273+ }
274+
275+ auto ret{std::move (res.unwrap ())};
276+
277+ JS::RootedString js_str (cx, JS_NewStringCopyUTF8N (cx, JS::UTF8Chars (ret.ptr .release (), ret.len )));
278+ return JS_ParseJSON (cx, js_str, args.rval ());
279+ }
280+
172281// TODO(performance): consider allowing logger creation during initialization, but then throw
173282// when trying to log.
174283// https://github.com/fastly/js-compute-runtime/issues/225
@@ -617,6 +726,7 @@ bool install(api::Engine *engine) {
617726 JS_FN (" enableDebugLogging" , Fastly::enableDebugLogging, 1 , JSPROP_ENUMERATE),
618727 JS_FN (" debugLog" , debugLog, 1 , JSPROP_ENUMERATE),
619728 JS_FN (" getGeolocationForIpAddress" , Fastly::getGeolocationForIpAddress, 1 , JSPROP_ENUMERATE),
729+ JS_FN (" inspect" , Fastly::inspect, 1 , JSPROP_ENUMERATE),
620730 JS_FN (" getLogger" , Fastly::getLogger, 1 , JSPROP_ENUMERATE),
621731 JS_FN (" includeBytes" , Fastly::includeBytes, 1 , JSPROP_ENUMERATE),
622732 JS_FN (" createFanoutHandoff" , Fastly::createFanoutHandoff, 2 , JSPROP_ENUMERATE),
@@ -758,6 +868,21 @@ bool install(api::Engine *engine) {
758868 if (!engine->define_builtin_module (" fastly:fanout" , fanout_val)) {
759869 return false ;
760870 }
871+
872+ // fastly:security
873+ RootedValue inspect_val (engine->cx ());
874+ if (!JS_GetProperty (engine->cx (), fastly, " inspect" , &inspect_val)) {
875+ return false ;
876+ }
877+ RootedObject security_builtin (engine->cx (), JS_NewObject (engine->cx (), nullptr ));
878+ RootedValue security_builtin_val (engine->cx (), JS::ObjectValue (*security_builtin));
879+ if (!JS_SetProperty (engine->cx (), security_builtin, " inspect" , inspect_val)) {
880+ return false ;
881+ }
882+ if (!engine->define_builtin_module (" fastly:security" , security_builtin_val)) {
883+ return false ;
884+ }
885+
761886 // fastly:websocket
762887 RootedObject websocket (engine->cx (), JS_NewObject (engine->cx (), nullptr ));
763888 RootedValue websocket_val (engine->cx (), JS::ObjectValue (*websocket));
0 commit comments