Page MenuHomePhorge

server.c
No OneTemporary

Size
14 KB
Referenced Files
None
Subscribers
None

server.c

#include "bind.h"
#include "internal.h"
#include "inspector.h"
#include "http/websocket.h"
#include "json.h"
#include "modules/crypto.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(
(const uint8_t *)client->read_buf,
client->read_len,
true,
&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, &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_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;
uv_tcp_init(uv_default_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(uv_default_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();
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))
uv_run(uv_default_loop(), UV_RUN_ONCE);
if (g_inspector.started) uv_unref((uv_handle_t *)&g_inspector.server);
}

File Metadata

Mime Type
text/x-c
Expires
Sun, May 17, 5:45 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
554517
Default Alt Text
server.c (14 KB)

Event Timeline