mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Validate untyped listen ports and give net errors Node's system properties (#2619)
This commit is contained in:
@@ -178,6 +178,8 @@ declare module "net" {
|
||||
getConnections(callback: (error: Error | null, count: number) => void): Server;
|
||||
/* Node answers the server itself (`return this` chaining). */
|
||||
listen(port: number, callback?: () => void): Server;
|
||||
/* No port: an arbitrary unused one. */
|
||||
listen(callback?: () => void): Server;
|
||||
/* The positional bind address (the options form's host in argument
|
||||
* spelling). */
|
||||
listen(port: number, host: string, callback?: () => void): Server;
|
||||
|
||||
@@ -1488,6 +1488,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
// the client request forms have non-generic shapes — special-cased.
|
||||
"net.serverPort": "scr_net_server_port",
|
||||
"net.serverListening": "scr_net_server_listening",
|
||||
"net.listenPortDyn": "scr_net_listen_port_dyn",
|
||||
"net.sockSetRef": "scr_net_sock_set_ref",
|
||||
"net.serverSetRef": "scr_net_server_set_ref",
|
||||
"net.serverOff": "scr_net_server_off",
|
||||
|
||||
@@ -1531,11 +1531,11 @@ function lowerNetServerMethodCall(
|
||||
return { kind: "libCall", fn, args: [receiver], type: VOID, loc };
|
||||
}
|
||||
if (name === "listen") {
|
||||
if (args.length < 1 || args.length > 3) {
|
||||
if (args.length > 3) {
|
||||
lowerer.noLowering(
|
||||
`listen with ${args.length} arguments`,
|
||||
call,
|
||||
"the supported form is listen(port[, host][, callback]) — port 0 binds an ephemeral port",
|
||||
"the supported form is listen([port][, host][, callback]) — port 0 binds an ephemeral port",
|
||||
);
|
||||
}
|
||||
// Node answers the server (`return this` chaining — `const s =
|
||||
@@ -1549,6 +1549,90 @@ function lowerNetServerMethodCall(
|
||||
return receiverReturningCall(lowerer, fn, callArgs, NETSERVER_T, loc);
|
||||
};
|
||||
const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T);
|
||||
// listen() and listen(callback): Node binds an arbitrary unused port
|
||||
// (normalizeArgs takes a function first argument as the callback).
|
||||
if (args.length === 0) return listenResult("net.listen", [receiver, numLit(0, loc)]);
|
||||
const first = lowerer.mapTypeOf(lowerer.typeOf(args[0]!));
|
||||
// An untyped first argument: a checked-dynamic value, a JavaScript value
|
||||
// read into an untyped slot, or a checker `any`.
|
||||
const untyped =
|
||||
args.length > 0 &&
|
||||
(first?.kind === "dyn" ||
|
||||
first?.kind === "jsval" ||
|
||||
(lowerer.typeOf(args[0]!).flags & ts.TypeFlags.Any) !== 0);
|
||||
if (args.length === 1 && first?.kind === "func") {
|
||||
const { cb } = lowerCallbackArg(
|
||||
lowerer,
|
||||
args[0]!,
|
||||
"listen callbacks",
|
||||
0,
|
||||
() => false,
|
||||
"use ()",
|
||||
[],
|
||||
);
|
||||
return listenResult("net.listenCb", [receiver, numLit(0, loc), cb]);
|
||||
}
|
||||
// An untyped first argument takes its port from net.listenPortDyn,
|
||||
// Node's normalizeArgs and validatePort at run time: 0 for a function,
|
||||
// undefined or null, the number of a port number or numeric string,
|
||||
// ERR_SOCKET_BAD_PORT for a bad port, and the fence for an options
|
||||
// object or a pipe path, which have no lowering from untyped values.
|
||||
const portOfUntyped = (value: IrExpr): IrExpr => ({
|
||||
kind: "libCall",
|
||||
fn: "net.listenPortDyn",
|
||||
args: [
|
||||
value,
|
||||
ladderFenceExpr(
|
||||
lowerer,
|
||||
"listen with a runtime options object or pipe path",
|
||||
args[0]!,
|
||||
"pass the port as a number, the path as a string, or the options as an object literal",
|
||||
),
|
||||
],
|
||||
type: F64,
|
||||
loc,
|
||||
});
|
||||
if (args.length === 1 && untyped) {
|
||||
// An untyped single argument: a function is the callback on an
|
||||
// arbitrary port; any other value is the port.
|
||||
const server = lowerer.declareHiddenLocal("%listenServer", NETSERVER_T);
|
||||
const arg = lowerer.declareHiddenLocal("%listenArg", DYN);
|
||||
const serverRef = (): IrExpr => varRef(server.id, NETSERVER_T, loc);
|
||||
const argRef = (): IrExpr => varRef(arg.id, DYN, loc);
|
||||
const listenStmt = (fn: IrLibFn, callArgs: IrExpr[]): IrStmt => ({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn, args: callArgs, type: VOID, loc },
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: server.id, init: receiver, loc },
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: arg.id,
|
||||
init: lowerer.coerceToExpected(lowerer.lowerExpr(args[0]!), DYN),
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "if",
|
||||
cond: { kind: "dynTest", test: "function", value: argRef(), type: BOOL, loc },
|
||||
then: [
|
||||
listenStmt("net.listenCb", [
|
||||
serverRef(),
|
||||
numLit(0, loc),
|
||||
{ kind: "dynCheck", value: argRef(), type: funcOf([], VOID), loc },
|
||||
]),
|
||||
],
|
||||
else_: [listenStmt("net.listen", [serverRef(), portOfUntyped(argRef())])],
|
||||
loc,
|
||||
},
|
||||
],
|
||||
result: serverRef(),
|
||||
type: NETSERVER_T,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
// The options-object form — listen({ port, host?, ipv6Only?, reusePort? }[, cb]),
|
||||
// the portless listenOnProxyInterface shape: host binds that ONE
|
||||
// address (an IP literal — the runtime has no resolver here; absent
|
||||
@@ -1762,7 +1846,9 @@ function lowerNetServerMethodCall(
|
||||
);
|
||||
return listenResult("net.listenPathCb", [receiver, path, cb]);
|
||||
}
|
||||
const port = lowerer.lowerExprExpecting(args[0]!, F64);
|
||||
const port = untyped
|
||||
? portOfUntyped(lowerer.coerceToExpected(lowerer.lowerExpr(args[0]!), DYN))
|
||||
: lowerer.lowerExprExpecting(args[0]!, F64);
|
||||
// The optional middle host — listen(port, '127.0.0.1'[, cb]): a
|
||||
// string-typed second argument is the bind address (the net.connect
|
||||
// disambiguation), routed through the options-form entry.
|
||||
|
||||
@@ -992,6 +992,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"net.connectCb",
|
||||
"net.connectPath",
|
||||
"net.connectPathCb",
|
||||
// listen's port from a checked-dynamic first argument: ERR_SOCKET_BAD_PORT,
|
||||
// or the fence for forms without a lowering.
|
||||
"net.listenPortDyn",
|
||||
// resetAndDestroy on a TLS socket throws ERR_INVALID_HANDLE_TYPE.
|
||||
"net.sockResetAndDestroy",
|
||||
// SocketAddress's option checks and parse()'s input check.
|
||||
|
||||
@@ -1711,6 +1711,7 @@ function buildLibFnSignatures() {
|
||||
* compiler-rendered fence — ALWAYS THROWS (the error.nodeThrow
|
||||
* polymorphic-result carve-out). May-throw seed. */
|
||||
"net.connectOptsChk": { argTypes: [DYN, STRING], result: VOID },
|
||||
"net.listenPortDyn": { argTypes: [DYN, STRING], result: F64 },
|
||||
"net.connectCb": {
|
||||
argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }],
|
||||
result: NETSOCKET_T,
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/http-server-listen-forms.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/http-server-listen-untyped.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/net-error-system-properties.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/net-listen-untyped-arguments.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+171
-20
@@ -1,6 +1,7 @@
|
||||
|
||||
#include "scr_platform.h"
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_system_errors.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
@@ -413,6 +414,95 @@ void scr_net_fire_err_this(ScrNetLs *l, ScrStr *msg, void *self, ScrDynHandleTag
|
||||
|
||||
void scr_net_fire_err(ScrNetLs *l, ScrStr *msg) { scr_net_fire_err_this(l, msg, NULL, 0); }
|
||||
|
||||
/* The libuv errno of an error code name (Node's err.errno), 0 for a name
|
||||
* outside libuv's map. */
|
||||
static double scr_net_uv_errno(const char *code, size_t len) {
|
||||
#define SCR_NET_UV_ERRNO(name, text) \
|
||||
if (len == sizeof #name - 1 && memcmp(code, #name, len) == 0) return SCR_NODE_UV__##name;
|
||||
SCR_NODE_UV_ERRNO_MAP(SCR_NET_UV_ERRNO)
|
||||
#undef SCR_NET_UV_ERRNO
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The libuv message of an error code name, or NULL. */
|
||||
static const char *scr_net_uv_text(const char *code, size_t len) {
|
||||
#define SCR_NET_UV_TEXT(name, text) \
|
||||
if (len == sizeof #name - 1 && memcmp(code, #name, len) == 0) return text;
|
||||
SCR_NODE_UV_ERRNO_MAP(SCR_NET_UV_TEXT)
|
||||
#undef SCR_NET_UV_TEXT
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Node's system error for a net failure message this unit writes:
|
||||
* "<syscall> <CODE>[ <detail>]" (errnoException, exceptionWithHostPort) or
|
||||
* "<syscall> <CODE>: <libuv message>[ <detail>]" (uvExceptionWithHostPort).
|
||||
* The error carries the code, the libuv errno and the syscall; for
|
||||
* connect, listen and bind, the detail is the address, as "<address>:<port>"
|
||||
* with a port, and a listen on a pipe path has port -1. NULL for any other
|
||||
* message; +1 otherwise. */
|
||||
static ScrError *scr_net_system_error(ScrStr *msg) {
|
||||
const char *data = msg->data;
|
||||
const char *end = data + msg->len;
|
||||
const char *space = memchr(data, ' ', msg->len);
|
||||
if (space == NULL || space == data) return NULL;
|
||||
size_t syscall_len = (size_t)(space - data);
|
||||
const char *code = space + 1;
|
||||
const char *code_end = code;
|
||||
while (code_end < end && *code_end != ' ' && *code_end != ':') code_end++;
|
||||
size_t code_len = (size_t)(code_end - code);
|
||||
if (code_len < 2 || code[0] != 'E') return NULL;
|
||||
for (size_t i = 0; i < code_len; i++) {
|
||||
char c = code[i];
|
||||
if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) return NULL;
|
||||
}
|
||||
double uv = scr_net_uv_errno(code, code_len);
|
||||
if (uv == 0) return NULL;
|
||||
const char *detail = code_end;
|
||||
if (detail < end && *detail == ':') {
|
||||
/* the libuv form: skip ": <message>" */
|
||||
const char *text = scr_net_uv_text(code, code_len);
|
||||
size_t text_len = text != NULL ? strlen(text) : 0;
|
||||
if (text == NULL || (size_t)(end - detail) < 2 + text_len ||
|
||||
memcmp(detail + 2, text, text_len) != 0)
|
||||
return NULL;
|
||||
detail += 2 + text_len;
|
||||
}
|
||||
if (detail < end && *detail == ' ') detail++;
|
||||
ScrError *e = scr_error_new(SCR_ERR_ERROR, msg);
|
||||
char code_buf[32];
|
||||
size_t n = code_len < sizeof code_buf - 1 ? code_len : sizeof code_buf - 1;
|
||||
memcpy(code_buf, code, n);
|
||||
code_buf[n] = '\0';
|
||||
scr_error_set_code(e, code_buf);
|
||||
e->system_errno = uv;
|
||||
e->system_call = scr_str_new(data, syscall_len);
|
||||
bool with_address = (syscall_len == 7 && memcmp(data, "connect", 7) == 0) ||
|
||||
(syscall_len == 6 && memcmp(data, "listen", 6) == 0) ||
|
||||
(syscall_len == 4 && memcmp(data, "bind", 4) == 0);
|
||||
if (with_address && detail < end) {
|
||||
const char *colon = NULL;
|
||||
for (const char *q = detail; q < end; q++)
|
||||
if (*q == ':') colon = q;
|
||||
bool digits = colon != NULL && colon + 1 < end;
|
||||
for (const char *q = colon != NULL ? colon + 1 : end; q < end; q++)
|
||||
if (*q < '0' || *q > '9') digits = false;
|
||||
const char *address_end = digits ? colon : end;
|
||||
ScrStr *address = scr_str_new(detail, (size_t)(address_end - detail));
|
||||
scr_error_set_prop(e, "address", 7, scr_dyn_new_str(address));
|
||||
/* a pipe listen: Node's listenInCluster passes port -1 with the path */
|
||||
bool pipe_listen = false;
|
||||
if (!digits && syscall_len == 6) {
|
||||
unsigned char ip[16];
|
||||
pipe_listen = inet_pton(AF_INET, address->data, ip) != 1 &&
|
||||
inet_pton(AF_INET6, address->data, ip) != 1;
|
||||
}
|
||||
scr_str_release(address);
|
||||
if (digits) scr_error_set_prop(e, "port", 4, scr_dyn_new_num(strtod(colon + 1, NULL)));
|
||||
else if (pipe_listen) scr_error_set_prop(e, "port", 4, scr_dyn_new_num(-1));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/* ── the handles ─────────────────────────────────────────────────────── */
|
||||
|
||||
enum { SCR_NET_K_SERVER = 1, SCR_NET_K_SOCKET = 2 };
|
||||
@@ -3075,7 +3165,7 @@ void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence) {
|
||||
char detail[64], msg[160];
|
||||
const char *d = scr_dyn_specific_type(port, detail, sizeof detail);
|
||||
int len = snprintf(msg, sizeof msg,
|
||||
"options.port should be >= 0 and < 65536. Received %s", d);
|
||||
"Port should be >= 0 and < 65536. Received %s.", d);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_SOCKET_BAD_PORT");
|
||||
return;
|
||||
}
|
||||
@@ -3098,6 +3188,60 @@ void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence) {
|
||||
scr_throw_lowering_fence(fence);
|
||||
}
|
||||
|
||||
/* The port of listen(arg[, ...]) for a checked-dynamic first argument, as
|
||||
* Node's Server.prototype.listen normalizes it: a function (listen(cb)),
|
||||
* undefined and null bind an ephemeral port (0); a number, or a string
|
||||
* whose Number() is >= 0, passes validatePort and answers its integer.
|
||||
* Other values take forms that have no lowering here (an options object, a
|
||||
* pipe path, and the ERR_INVALID_ARG_VALUE kinds) and throw the
|
||||
* compiler-rendered fence. */
|
||||
/* Node's validatePort over listen's port value: 1 with *out set for a
|
||||
* function, undefined or null (0) or a valid port; 0 with ERR_SOCKET_BAD_PORT
|
||||
* pending for a bad port; -1 for a value that is no port form here (an
|
||||
* options object, a pipe path, and the ERR_INVALID_ARG_VALUE kinds). */
|
||||
static int scr_net_listen_port_check(const ScrDyn *v, double *out) {
|
||||
*out = 0;
|
||||
if (v == NULL || v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL ||
|
||||
v->kind == SCR_DYN_FUNC)
|
||||
return 1;
|
||||
double n;
|
||||
bool ok;
|
||||
if (v->kind == SCR_DYN_NUM) {
|
||||
n = v->v.num;
|
||||
ok = n >= 0 && n <= 65535 && trunc(n) == n;
|
||||
} else if (v->kind == SCR_DYN_STR) {
|
||||
ScrStr *ps = scr_str_retain(v->v.str);
|
||||
n = scr_string_to_number(ps);
|
||||
if (!(n >= 0)) { /* Node's isPipeName: a path */
|
||||
scr_str_release(ps);
|
||||
return -1;
|
||||
}
|
||||
ScrStr *trimmed = scr_str_trim(ps);
|
||||
ok = trimmed->len > 0 && n <= 65535 && trunc(n) == n;
|
||||
scr_str_release(trimmed);
|
||||
scr_str_release(ps);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (!ok) {
|
||||
char detail[64], msg[160];
|
||||
const char *d = scr_dyn_specific_type(v, detail, sizeof detail);
|
||||
int len = snprintf(msg, sizeof msg,
|
||||
"options.port should be >= 0 and < 65536. Received %s.", d);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, len < 0 ? 0 : (size_t)len,
|
||||
"ERR_SOCKET_BAD_PORT");
|
||||
return 0;
|
||||
}
|
||||
*out = n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
double scr_net_listen_port_dyn(const ScrDyn *v, const ScrStr *fence) {
|
||||
double port;
|
||||
if (scr_net_listen_port_check(v, &port) < 0) scr_throw_lowering_fence(fence);
|
||||
return port;
|
||||
}
|
||||
|
||||
/* A dial the caller starts LATER (the http agent's maxSockets queue):
|
||||
* the socket exists, registers, and buffers writes as "connecting" —
|
||||
* scr_net_sock_dial_start runs the actual dial when a slot frees. */
|
||||
@@ -3290,6 +3434,8 @@ void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/,
|
||||
err = errno;
|
||||
}
|
||||
#endif
|
||||
/* libuv's uv_pipe_bind reports ENOENT as EACCES, as Windows does */
|
||||
if (err == ENOENT) err = EACCES;
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "listen ");
|
||||
@@ -6060,8 +6206,10 @@ static bool scr_net_sweep_sock(ScrNetSocket *sock) {
|
||||
if (sock->pending_code != NULL) {
|
||||
coded = scr_error_new(SCR_ERR_ERROR, msg);
|
||||
coded->code = scr_str_new(sock->pending_code, strlen(sock->pending_code));
|
||||
scr_event_error_payload = coded;
|
||||
} else {
|
||||
coded = scr_net_system_error(msg);
|
||||
}
|
||||
if (coded != NULL) scr_event_error_payload = coded;
|
||||
scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
|
||||
scr_event_error_payload = saved;
|
||||
if (coded != NULL) scr_error_release(coded);
|
||||
@@ -6126,7 +6274,12 @@ static void scr_net_sweep(void) {
|
||||
ScrStr *msg = srv->pending_err;
|
||||
srv->pending_err = NULL;
|
||||
ScrAlsCtx *prev = scr_als_dispatch_enter(srv->listen_ctx);
|
||||
ScrError *system = scr_net_system_error(msg);
|
||||
ScrError *saved = scr_event_error_payload;
|
||||
if (system != NULL) scr_event_error_payload = system;
|
||||
scr_net_fire_err_this(&srv->err_ls, msg, srv, SCR_DYNH_NET_SERVER);
|
||||
scr_event_error_payload = saved;
|
||||
if (system != NULL) scr_error_release(system);
|
||||
scr_als_dispatch_leave(prev);
|
||||
scr_str_release(msg);
|
||||
/* A failed listen SETTLES the handle: listeners drop (an error
|
||||
@@ -6875,29 +7028,27 @@ static ScrDyn *scr_net_dynh_srv_invoke(void *h, ScrDyn *self, const char *method
|
||||
return scr_dyn_retain(self);
|
||||
}
|
||||
if (strcmp(method, "listen") == 0) {
|
||||
/* listen([port][, host][, cb]) — the static lowering's shapes over
|
||||
* dyn arguments; the options-object form throws the 'not supported yet'
|
||||
/* listen([port][, host][, backlog][, cb]) and listen(path[, cb]) over
|
||||
* dyn arguments, as Node's normalizeArgs reads them: the last argument
|
||||
* is the callback when it is a function, a first argument that is a
|
||||
* function, undefined or null binds an unused port, a string whose
|
||||
* Number() is not >= 0 is a pipe path, and a string second argument is
|
||||
* the host. The options-object form throws the 'not supported yet'
|
||||
* Error. */
|
||||
const ScrDyn *first = argc > 0 ? args[0] : NULL;
|
||||
const ScrDyn *cb = argc > 0 && args[argc - 1]->kind == SCR_DYN_FUNC ? args[argc - 1] : NULL;
|
||||
double port = 0;
|
||||
ScrStr *host = NULL;
|
||||
const ScrDyn *cb = NULL;
|
||||
size_t i = 0;
|
||||
if (i < argc && args[i]->kind == SCR_DYN_NUM) { port = args[i]->v.num; i++; }
|
||||
else if (i < argc && args[i]->kind == SCR_DYN_STR && args[i]->v.str->len > 0 &&
|
||||
args[i]->v.str->data[0] >= '0' && args[i]->v.str->data[0] <= '9') {
|
||||
/* Node coerces a numeric port string */
|
||||
port = strtod(args[i]->v.str->data, NULL);
|
||||
i++;
|
||||
} else if (i < argc && args[i]->kind == SCR_DYN_OBJ) {
|
||||
int form = scr_net_listen_port_check(first, &port);
|
||||
if (form == 0) return NULL;
|
||||
if (form < 0 && first->kind == SCR_DYN_STR) {
|
||||
scr_net_listen_path(s, first->v.str, cb ? scr_dyn_listener_closure0(cb) : NULL);
|
||||
return scr_dyn_retain(self);
|
||||
}
|
||||
if (form < 0) {
|
||||
scr_net_dynh_srv_unsupported("listen", "the options-object form on a dynamic value is not modeled (use listen(port[, host][, cb]))");
|
||||
return NULL;
|
||||
}
|
||||
if (i < argc && args[i]->kind == SCR_DYN_STR) { host = args[i]->v.str; i++; }
|
||||
if (i < argc && args[i]->kind == SCR_DYN_FUNC) { cb = args[i]; i++; }
|
||||
if (i < argc && args[i]->kind != SCR_DYN_UNDEF && args[i]->kind != SCR_DYN_NULL) {
|
||||
scr_net_dynh_srv_unsupported("listen", "unrecognized arguments (the modeled shape is listen(port[, host][, cb]))");
|
||||
return NULL;
|
||||
}
|
||||
ScrStr *host = argc > 1 && args[1]->kind == SCR_DYN_STR ? args[1]->v.str : NULL;
|
||||
ScrClosure *clo = cb ? scr_dyn_listener_closure0(cb) : NULL;
|
||||
if (host) scr_net_listen_opts(s, port, host, false, clo);
|
||||
else scr_net_listen(s, port, clo);
|
||||
|
||||
@@ -229,6 +229,11 @@ ScrNetSocket *scr_net_connect_attempt(double port, ScrStr *host /*borrowed*/, co
|
||||
* autoSelectFamily, attempt budget), then the compiler-rendered fence —
|
||||
* always leaves an exception pending. Borrowed. */
|
||||
void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence);
|
||||
/* The port of listen(arg[, ...]) for a checked-dynamic first argument
|
||||
* (Node's normalizeArgs and validatePort): 0 for a function, undefined or
|
||||
* null; ERR_SOCKET_BAD_PORT pending for a bad port; the fence pending for
|
||||
* an options object or a pipe path. Borrowed. */
|
||||
double scr_net_listen_port_dyn(const ScrDyn *v, const ScrStr *fence);
|
||||
/* connect with a caller lookup (net.connect({ ..., lookup })): invokes
|
||||
* lookup(hostname, options, answer-closure) synchronously; answer_fn is
|
||||
* the emitted per-shape thunk that decodes the answer down to
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// server.listen() and server.listen(callback) bind an arbitrary unused port,
|
||||
// as Node's normalizeArgs takes a function first argument as the callback.
|
||||
import * as http from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
|
||||
const first = http.createServer((req, res) => res.end("first"));
|
||||
first.listen(() => {
|
||||
const port = (first.address() as AddressInfo).port;
|
||||
console.log("callback form port > 0", port > 0);
|
||||
http.get({ host: "127.0.0.1", port }, (res) => {
|
||||
res.setEncoding("utf8");
|
||||
res.on("data", (chunk: string) => console.log("body", chunk));
|
||||
res.on("end", () => {
|
||||
first.close();
|
||||
const second = http.createServer((req2, res2) => res2.end("second"));
|
||||
second.on("listening", () => {
|
||||
const port2 = (second.address() as AddressInfo).port;
|
||||
console.log("no-argument form port > 0", port2 > 0, port2 !== port);
|
||||
second.close();
|
||||
});
|
||||
second.listen();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// server.listen(value) with an untyped value: a function is the 'listening'
|
||||
// callback on an arbitrary unused port, as in Node; a number is the port.
|
||||
'use strict';
|
||||
const http = require('http');
|
||||
|
||||
function wrap(fn) {
|
||||
return function (...args) {
|
||||
return fn.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => res.end('ok'));
|
||||
server.listen(wrap(() => {
|
||||
const port = server.address().port;
|
||||
console.log('port > 0', port > 0);
|
||||
server.close(() => {
|
||||
const other = http.createServer();
|
||||
const chosen = [0][0];
|
||||
other.listen(chosen, wrap(() => {
|
||||
console.log('numeric port > 0', other.address().port > 0);
|
||||
other.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
@@ -0,0 +1,52 @@
|
||||
// net 'error' events carry Node's system error properties: errno, code
|
||||
// and syscall, and for listen and connect the address and port (a Unix
|
||||
// socket's path, with port -1 for listen). A reset connection's read error
|
||||
// carries errno, code and syscall only.
|
||||
"use strict";
|
||||
const net = require("net");
|
||||
|
||||
function describe(label, err, port) {
|
||||
const hide = (text) => (port === undefined ? text : text.replace(String(port), "<port>"));
|
||||
console.log(
|
||||
label,
|
||||
JSON.stringify({
|
||||
message: hide(err.message),
|
||||
code: err.code,
|
||||
errno: err.errno,
|
||||
syscall: err.syscall,
|
||||
address: err.address,
|
||||
port: port !== undefined && err.port === port ? "<port>" : err.port,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const taken = net.createServer();
|
||||
taken.listen(0, "127.0.0.1", () => {
|
||||
const port = taken.address().port;
|
||||
const second = net.createServer();
|
||||
second.on("error", (err) => {
|
||||
describe("listen in use", err, port);
|
||||
const unix = net.createServer();
|
||||
unix.on("error", (err2) => {
|
||||
describe("listen path", err2);
|
||||
const client = net.connect(1, "127.0.0.1");
|
||||
client.on("error", (err3) => {
|
||||
describe("connect refused", err3);
|
||||
const resetting = net.createServer((socket) => {
|
||||
socket.on("error", (err4) => {
|
||||
describe("read reset", err4);
|
||||
resetting.close();
|
||||
taken.close();
|
||||
});
|
||||
});
|
||||
resetting.listen(0, "127.0.0.1", () => {
|
||||
const victim = net.connect(resetting.address().port, "127.0.0.1", () => {
|
||||
setTimeout(() => victim.resetAndDestroy(), 20);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
unix.listen("/nonexistent-dir/sock");
|
||||
});
|
||||
second.listen(port, "127.0.0.1");
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
// server.listen with untyped arguments follows Node's normalizeArgs and
|
||||
// validatePort: a function is the 'listening' callback on an unused port,
|
||||
// undefined and null take an unused port, a numeric string is a port, and
|
||||
// a port out of range or not an integer throws ERR_SOCKET_BAD_PORT, as a
|
||||
// bad port in a connect options object checked at run time does.
|
||||
"use strict";
|
||||
const net = require("net");
|
||||
|
||||
// Values read from JSON are untyped, as a test helper's results are.
|
||||
const raw = JSON.parse(
|
||||
'{"none":null,"zero":0,"text":"0","bad":[-1,65536,1.5,""," ","70000"]}',
|
||||
);
|
||||
const wrap = (fn) => (...args) => fn(...args);
|
||||
const forms = [
|
||||
["callback only", (server, done) => server.listen(wrap(done))],
|
||||
["undefined port", (server, done) => server.listen(raw.missing, done)],
|
||||
["null port", (server, done) => server.listen(raw.none, done)],
|
||||
["numeric string", (server, done) => server.listen(raw.text, done)],
|
||||
["host and callback", (server, done) => server.listen(raw.zero, "127.0.0.1", done)],
|
||||
];
|
||||
|
||||
function next(i) {
|
||||
if (i === forms.length) {
|
||||
for (const bad of raw.bad) {
|
||||
const server = net.createServer();
|
||||
try {
|
||||
server.listen(bad);
|
||||
console.log(JSON.stringify(bad), "listened");
|
||||
server.close();
|
||||
} catch (err) {
|
||||
console.log(JSON.stringify(bad), err.name, err.code, err.message);
|
||||
}
|
||||
}
|
||||
// A connect options object with a computed key is checked at run time.
|
||||
const key = ["po", "rt"].join("");
|
||||
try {
|
||||
net.connect({ [key]: -1 }).destroy();
|
||||
console.log("connect", "connected");
|
||||
} catch (err) {
|
||||
console.log("connect", err.name, err.code, err.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const [label, listen] = forms[i];
|
||||
const server = net.createServer();
|
||||
listen(server, () => {
|
||||
const port = server.address().port;
|
||||
console.log(label, typeof port, port > 0);
|
||||
server.close(() => next(i + 1));
|
||||
});
|
||||
}
|
||||
next(0);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2000, "san": 3000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2000, "san": 3000 }
|
||||
Reference in New Issue
Block a user