Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406#include "bind.h"#include "isolate.h"#include "inspector.h"#include "http/websocket.h"#include "json.h"#include "modules/crypto.h"#include "reactor.h"
#include <limits.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>
static void inspector_process_ws(inspector_client_t *client) { for (;;) { ant_ws_frame_t frame = {0}; ant_ws_frame_result_t r = ant_ws_parse_frame( (uint8_t *)client->read_buf, client->read_len, true, false, false, &frame ); if (r == ANT_WS_FRAME_INCOMPLETE) return; if (r == ANT_WS_FRAME_PROTOCOL_ERROR) { uv_close((uv_handle_t *)&client->handle, NULL); return; } if (frame.opcode == ANT_WS_OPCODE_TEXT) { inspector_handle_message(client, (const char *)frame.payload, frame.payload_len); } else if (frame.opcode == ANT_WS_OPCODE_CLOSE) { ant_ws_frame_clear(&frame); uv_close((uv_handle_t *)&client->handle, NULL); return; } else if (frame.opcode == ANT_WS_OPCODE_PING) { size_t len = 0; uint8_t *pong = ant_ws_encode_frame(ANT_WS_OPCODE_PONG, frame.payload, frame.payload_len, false, false, &len); if (pong) { inspector_send_raw(client, (const char *)pong, len); free(pong); } } size_t consumed = frame.consumed_len; ant_ws_frame_clear(&frame); if (consumed >= client->read_len) { client->read_len = 0; return; } memmove(client->read_buf, client->read_buf + consumed, client->read_len - consumed); client->read_len -= consumed; }}
static const char *header_value(const char *headers, const char *name, char *buf, size_t bufsz) { size_t name_len = strlen(name); const char *p = headers; while ((p = strstr(p, "\n")) != NULL) { p++; while (*p == '\r' || *p == '\n') p++; if (strncasecmp(p, name, name_len) == 0 && p[name_len] == ':') { p += name_len + 1; while (*p == ' ' || *p == '\t') p++; const char *end = p; while (*end && *end != '\r' && *end != '\n') end++; size_t len = (size_t)(end - p); if (len >= bufsz) len = bufsz - 1; memcpy(buf, p, len); buf[len] = '\0'; return buf; } } return NULL;}
static bool host_allowed(const char *headers) { char host[256]; const char *value = header_value(headers, "Host", host, sizeof(host)); if (!value) return true; if (strncmp(value, "127.0.0.1", 9) == 0) return true; if (strncmp(value, "localhost", 9) == 0) return true; if (strncmp(value, "[::1]", 5) == 0) return true; return false;}
static bool header_token_eq(const char *headers, const char *name, const char *expected) { char value[256]; const char *p = header_value(headers, name, value, sizeof(value)); if (!p) return false; size_t expected_len = strlen(expected); while (*p) { while (*p == ' ' || *p == '\t' || *p == ',') p++; const char *start = p; while (*p && *p != ',') p++; const char *end = p; while (end > start && (end[-1] == ' ' || end[-1] == '\t')) end--; if ((size_t)(end - start) == expected_len && strncasecmp(start, expected, expected_len) == 0) return true; } return false;}
static bool request_path_matches_uuid(const char *req) { char expected[80]; snprintf(expected, sizeof(expected), "GET /%s ", g_inspector.uuid); return strncmp(req, expected, strlen(expected)) == 0;}
static bool inspector_append_target_url(sbuf_t *b, const char *file) { if (!file || !*file || strcmp(file, "[repl]") == 0 || strcmp(file, "ant") == 0) return sbuf_append(b, "file:///"); if (inspector_is_url_like(file)) return sbuf_append(b, file); if (file[0] == '/') { return sbuf_append(b, "file://") && sbuf_append(b, file); } return sbuf_append(b, "file:///") && sbuf_append(b, file);}
static bool inspector_append_devtools_url(sbuf_t *b) { return sbuf_append(b, "devtools://devtools/bundled/js_app.html?v8only=true&ws=") && sbuf_append(b, g_inspector.host) && sbuf_appendf(b, ":%d/%s", g_inspector.port, g_inspector.uuid);}
static bool inspector_append_browser_devtools_url(sbuf_t *b) { return sbuf_append(b, "http://") && sbuf_append(b, g_inspector.host) && sbuf_appendf(b, ":%d/devtools", g_inspector.port);}
static char *json_list_response(void) { sbuf_t b = {0}; sbuf_t devtools = {0}; sbuf_t target = {0}; sbuf_t websocket = {0}; inspector_script_t *entry = inspector_entry_script(); const char *file = entry && entry->url ? entry->url : (g_inspector.js && g_inspector.js->filename ? g_inspector.js->filename : "ant"); uv_pid_t pid = uv_os_getpid();
if (!inspector_append_devtools_url(&devtools)) goto fail; if (!inspector_append_target_url(&target, file)) goto fail; if (!sbuf_append(&websocket, "ws://")) goto fail; if (!sbuf_append(&websocket, g_inspector.host)) goto fail; if (!sbuf_appendf(&websocket, ":%d/%s", g_inspector.port, g_inspector.uuid)) goto fail;
char title[64]; snprintf(title, sizeof(title), "ant[%d]", (int)pid);
inspector_json_t json; inspector_json_init(&json, &b); if (!inspector_json_begin_array(&json)) goto fail; if (!inspector_json_begin_object(&json)) goto fail; if (!inspector_json_key(&json, "description")) goto fail; if (!inspector_json_string(&json, title)) goto fail; if (!inspector_json_key(&json, "devtoolsFrontendUrl")) goto fail; if (!inspector_json_string(&json, devtools.data)) goto fail; if (!inspector_json_key(&json, "id")) goto fail; if (!inspector_json_string(&json, g_inspector.uuid)) goto fail; if (!inspector_json_key(&json, "title")) goto fail; if (!inspector_json_string(&json, title)) goto fail; if (!inspector_json_key(&json, "type")) goto fail; if (!inspector_json_string(&json, "node")) goto fail; if (!inspector_json_key(&json, "url")) goto fail; if (!inspector_json_string(&json, target.data)) goto fail; if (!inspector_json_key(&json, "webSocketDebuggerUrl")) goto fail; if (!inspector_json_string(&json, websocket.data)) goto fail; if (!inspector_json_end_object(&json)) goto fail; if (!inspector_json_end_array(&json)) goto fail;
free(devtools.data); free(target.data); free(websocket.data); return b.data;fail: free(devtools.data); free(target.data); free(websocket.data); free(b.data); return NULL;}
static void inspector_http_response(inspector_client_t *client, const char *status, const char *type, const char *body) { sbuf_t b = {0}; size_t body_len = body ? strlen(body) : 0; if (sbuf_appendf( &b, "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", status, type ? type : "text/plain", body_len ) && sbuf_append_len(&b, body, body_len)) { inspector_send_raw(client, b.data, b.len); } free(b.data); uv_close((uv_handle_t *)&client->handle, NULL);}
static void inspector_http_devtools_page(inspector_client_t *client) { sbuf_t devtools = {0}; sbuf_t body = {0}; if ( inspector_append_devtools_url(&devtools) && sbuf_append(&body, "<!doctype html><meta charset=\"utf-8\"><title>Ant DevTools</title>") && sbuf_append(&body, "<style>body{font:84%/1.4 'Segoe UI',Tahoma,sans-serif;color:#000;background:#fff;margin:0;padding:24px;max-width:800px}h1{font-size:22px;font-weight:normal;margin:0 0 4px}.subtitle{color:#5f6368;font-size:13px;margin-bottom:24px}table{border-collapse:collapse;width:100%}td{padding:6px 14px 6px 0;vertical-align:top;font-size:13px}td.label{text-align:right;color:#5f6368;white-space:nowrap;width:1%}td.value{font-family:ui-monospace,Menlo,Consolas,monospace;word-break:break-all}@media (prefers-color-scheme:dark){body{background:#202124;color:#e8eaed}.subtitle,td.label{color:#9aa0a6}}</style>") && sbuf_append(&body, "<h1>Ant DevTools</h1><div class=\"subtitle\">Paste into Chrome's address bar.</div><table><tr><td class=\"label\">DevTools frontend</td><td class=\"value\">") && sbuf_append(&body, devtools.data) && sbuf_append(&body, "</td></tr></table>") ) inspector_http_response(client, "200 OK", "text/html; charset=UTF-8", body.data); else inspector_http_response(client, "500 Internal Server Error", "text/plain", "Out of memory\n"); free(devtools.data); free(body.data);}
static void inspector_upgrade(inspector_client_t *client, const char *headers) { char key[256]; if (!host_allowed(headers) || !request_path_matches_uuid(headers)) { inspector_http_response(client, "403 Forbidden", "text/plain", "Forbidden\n"); return; } if (!header_value(headers, "Sec-WebSocket-Key", key, sizeof(key))) { inspector_http_response(client, "400 Bad Request", "text/plain", "Missing WebSocket key\n"); return; } char *accept = ant_ws_accept_key(key); if (!accept) { inspector_http_response(client, "400 Bad Request", "text/plain", "Invalid WebSocket key\n"); return; } sbuf_t b = {0}; if (sbuf_appendf( &b, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n", accept )) inspector_send_raw(client, b.data, b.len); free(accept); free(b.data); client->websocket = true; g_inspector.attached = true; client->read_len = 0;}
static void inspector_process_http(inspector_client_t *client) { char *end = strstr(client->read_buf, "\r\n\r\n"); if (!end) return; if (!host_allowed(client->read_buf)) { inspector_http_response(client, "403 Forbidden", "text/plain", "Forbidden\n"); return; } if (strncmp(client->read_buf, "GET /json/list ", 15) == 0 || strncmp(client->read_buf, "GET /json ", 10) == 0) { char *body = json_list_response(); inspector_http_response(client, "200 OK", "application/json; charset=UTF-8", body ? body : "[]"); free(body); } else if (strncmp(client->read_buf, "GET /devtools ", 14) == 0 || strncmp(client->read_buf, "GET /devtools/ ", 15) == 0) { inspector_http_devtools_page(client); } else if (strncmp(client->read_buf, "GET /json/version ", 18) == 0) { inspector_http_response( client, "200 OK", "application/json; charset=UTF-8", "{\"Browser\":\"Ant/v" ANT_VERSION "\",\"Protocol-Version\":\"1.3\",\"V8-Version\":\"ant\",\"WebKit-Version\":\"ant\"}" ); } else if ( strncasecmp(client->read_buf, "GET /", 5) == 0 && header_token_eq(client->read_buf, "Upgrade", "websocket") && header_token_eq(client->read_buf, "Connection", "upgrade") ) inspector_upgrade(client, client->read_buf); else inspector_http_response(client, "404 Not Found", "text/plain", "Not found\n");}
static void inspector_alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { size_t len = suggested_size > UINT_MAX ? UINT_MAX : suggested_size; buf->base = malloc(len); *buf = uv_buf_init(buf->base, buf->base ? (unsigned int)len : 0);}
static void inspector_client_closed(uv_handle_t *handle) { inspector_client_t *client = (inspector_client_t *)handle; inspector_cancel_client_awaits(client->id); inspector_client_t **p = &g_inspector.clients; while (*p) { if (*p == client) { *p = client->next; break; } p = &(*p)->next; } free(client->read_buf); free(client);}
static void inspector_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { inspector_client_t *client = (inspector_client_t *)stream; if (nread <= 0) { free(buf->base); if (nread < 0) uv_close((uv_handle_t *)&client->handle, inspector_client_closed); return; } if (client->read_len + (size_t)nread + 1 > client->read_cap) { size_t next_cap = client->read_cap ? client->read_cap * 2 : 8192; while (next_cap < client->read_len + (size_t)nread + 1) next_cap *= 2; char *next = realloc(client->read_buf, next_cap); if (!next) { free(buf->base); uv_close((uv_handle_t *)&client->handle, inspector_client_closed); return; } client->read_buf = next; client->read_cap = next_cap; } memcpy(client->read_buf + client->read_len, buf->base, (size_t)nread); client->read_len += (size_t)nread; client->read_buf[client->read_len] = '\0'; free(buf->base);
if (client->websocket) inspector_process_ws(client); else inspector_process_http(client);}
static void inspector_connection_cb(uv_stream_t *server, int status) { if (status < 0) return; inspector_client_t *client = calloc(1, sizeof(*client)); if (!client) return; client->js = g_inspector.js; client->id = ++g_inspector.next_client_id; if (client->id == 0) client->id = ++g_inspector.next_client_id; uv_tcp_init(server->loop, &client->handle); client->handle.data = client; if (uv_accept(server, (uv_stream_t *)&client->handle) != 0) { uv_close((uv_handle_t *)&client->handle, inspector_client_closed); return; } client->next = g_inspector.clients; g_inspector.clients = client; uv_read_start((uv_stream_t *)&client->handle, inspector_alloc_cb, inspector_read_cb);}
static bool inspector_parse_sockaddr(const char *host, int port, struct sockaddr_in *addr) { const char *bind_host = host && *host ? host : "127.0.0.1"; if (uv_ip4_addr(bind_host, port, addr) == 0) return true; if (strcmp(bind_host, "localhost") == 0 && uv_ip4_addr("127.0.0.1", port, addr) == 0) return true; return false;}
bool ant_inspector_start(ant_t *js, const ant_inspector_options_t *options) { if (!options || !options->enabled || g_inspector.started) return true; memset(&g_inspector, 0, sizeof(g_inspector)); g_inspector.js = js; g_inspector.port = options->port > 0 ? options->port : 9229; snprintf(g_inspector.host, sizeof(g_inspector.host), "%s", options->host[0] ? options->host : "127.0.0.1"); if (crypto_random_uuid(g_inspector.uuid) < 0) return false;
struct sockaddr_in addr; if (!inspector_parse_sockaddr(g_inspector.host, g_inspector.port, &addr)) return false; if (uv_tcp_init(js->loop, &g_inspector.server) != 0) return false; if (uv_tcp_bind(&g_inspector.server, (const struct sockaddr *)&addr, 0) != 0) return false; if (uv_listen((uv_stream_t *)&g_inspector.server, 16, inspector_connection_cb) != 0) return false; if (!options->wait_for_session) uv_unref((uv_handle_t *)&g_inspector.server); g_inspector.waiting_for_debugger = options->wait_for_session; g_inspector.started = true;
sbuf_t devtools = {0}; if (inspector_append_browser_devtools_url(&devtools)) fprintf(stderr, "Debugger listening on %s\n", devtools.data); free(devtools.data); return true;}
void ant_inspector_stop(void) { if (!g_inspector.started) return; for (inspector_client_t *c = g_inspector.clients; c; c = c->next) uv_close((uv_handle_t *)&c->handle, inspector_client_closed); uv_close((uv_handle_t *)&g_inspector.server, NULL); inspector_clear_console_events(); inspector_clear_pending_awaits(); while (g_inspector.scripts) { inspector_script_t *script = g_inspector.scripts; g_inspector.scripts = script->next; free(script->url); free(script->source); free(script); } while (g_inspector.network_entries) { inspector_network_entry_t *entry = g_inspector.network_entries; g_inspector.network_entries = entry->next; free(entry->request_body); free(entry->response_body); free(entry); } g_inspector.network_entry_count = 0; g_inspector.started = false;}
void ant_inspector_wait_for_session(void) { while (g_inspector.started && (!g_inspector.attached || g_inspector.waiting_for_debugger)) { ant_uv_run(g_inspector.js->loop, UV_RUN_ONCE); js_poll_events(g_inspector.js); } if (g_inspector.started) uv_unref((uv_handle_t *)&g_inspector.server);}