Newer
Older
/* Synchronet JavaScript "Socket" Object */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "js_request.h"
#include "ssl.h"
#define TLS_ERROR_LEVEL LOG_WARNING // It'd be nice if this was configurable
// TODO: All log output (lprintf calls) go to the terminal server's lprintf (!)
static void dbprintf(bool error, js_socket_private_t* p, char* fmt, ...);
static bool do_CryptFlush(js_socket_private_t *p);
static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val);
static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, void *val, int len);
static void do_js_close(JSContext *cx, js_socket_private_t *p, bool finalize);
static bool js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type);
static JSBool js_accept(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_bind(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_close(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_connect(JSContext *cx, uintN argc, jsval *arglist);
static void js_finalize_socket(JSContext *cx, JSObject *obj);
static JSBool js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_listen(JSContext *cx, uintN argc, jsval *arglist);
static js_callback_t * js_get_callback(JSContext *cx);
static JSBool js_getsockopt(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_peek(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_poll(JSContext *cx, uintN argc, jsval *arglist);
static ushort js_port(JSContext* cx, jsval val, int type);
static JSBool js_recv(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvbin(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvfrom(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvline(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_send(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendbin(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendfile(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendline(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendto(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_setsockopt(JSContext *cx, uintN argc, jsval *arglist);
static int js_sock_read_check(js_socket_private_t *p, time_t start, int32 timeout, int i);
static JSBool js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj);
static bool js_socket_peek_byte(JSContext *cx, js_socket_private_t *p);
static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
static ptrdiff_t js_socket_recv(JSContext *cx, js_socket_private_t *p, void *buf, size_t len, int flags, int timeout);
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id);
static off_t js_socket_sendfilesocket(js_socket_private_t *p, int file);
static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush);
static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp);
static JSBool js_install_event(JSContext *cx, uintN argc, jsval *arglist, bool once);
static JSBool js_on(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_once(JSContext *cx, uintN argc, jsval *arglist);

Rob Swindell
committed
static void store_socket_error(js_socket_private_t* p, int error_num, const char* error_str)
{
p->last_error = error_num;

Rob Swindell
committed
strlcpy(p->last_error_str, error_str, sizeof p->last_error_str);
else
socket_strerror(error_num, p->last_error_str, sizeof p->last_error_str);
}
static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val)
{
case CRYPT_OPTION_NET_READTIMEOUT:
if (val < 0)
val = 0;
if (val > 300)
val = 300;
break;
default:
break;
}
ret = cryptSetAttribute(session, attr, val);
if (ret != CRYPT_OK) {
sprintf(action, "setting attribute %d", attr);
get_crypt_error_string(ret, session, &estr, action, &level);
if (level < TLS_ERROR_LEVEL)
level = TLS_ERROR_LEVEL;
lprintf(level, "TLS %s", estr);
free_crypt_attrstr(estr);
return ret;
}
static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, void *val, int len)
{
int ret = cryptSetAttributeString(session, attr, val, len);
if (ret != CRYPT_OK) {
sprintf(action, "setting attribute string %d", attr);
get_crypt_error_string(ret, session, &estr, "setting attribute string", &level);
if (estr) {
if (level < TLS_ERROR_LEVEL)
level = TLS_ERROR_LEVEL;
lprintf(level, "TLS %s", estr);
free_crypt_attrstr(estr);
#define GCES(status, pdata, estr, action) do { \
int GCES_level; \
get_crypt_error_string(status, pdata->session, &estr, action, &GCES_level); \
if (estr) { \
if (GCES_level < TLS_ERROR_LEVEL) \
GCES_level = TLS_ERROR_LEVEL; \
lprintf(GCES_level, "%04d TLS %s", p->sock, estr); \
free_crypt_attrstr(estr); \
} \
} while (0)
#define GCESH(status, socket, handle, estr, action) do { \
int GCESH_level; \
get_crypt_error_string(status, handle, &estr, action, &GCESH_level); \
if (estr) { \
if (GCESH_level < TLS_ERROR_LEVEL) \
GCESH_level = TLS_ERROR_LEVEL; \
lprintf(GCESH_level, "%04d TLS %s", socket, estr); \
free_crypt_attrstr(estr); \
} \
} while (0)
static bool do_CryptFlush(js_socket_private_t *p)
int ret;
char *estr;
if (ret == CRYPT_OK) {
if (ret != CRYPT_ERROR_COMPLETE)
GCES(ret, p, estr, "flushing data");
static void
remove_js_socket_event(JSContext *cx, js_callback_t *cb, SOCKET sock)
struct js_event_list *ev;
struct js_event_list *nev;
if (!cb->events_supported) {
return;
}
for (ev = cb->events; ev; ev = nev) {
nev = ev->next;
if (ev->type == JS_EVENT_SOCKET_READABLE || ev->type == JS_EVENT_SOCKET_READABLE_ONCE
|| ev->type == JS_EVENT_SOCKET_WRITABLE || ev->type == JS_EVENT_SOCKET_WRITABLE_ONCE) {
cb->events = ev->next;
JS_RemoveObjectRoot(cx, &ev->cx);
free(ev);
}
}
else if (ev->type == JS_EVENT_SOCKET_CONNECT) {
if (ev->data.connect.sock == sock) {
if (ev->next)
ev->next->prev = ev->prev;
if (ev->prev)
ev->prev->next = ev->next;
else
cb->events = ev->next;
closesocket(ev->data.connect.sv[0]);
JS_RemoveObjectRoot(cx, &ev->cx);
free(ev);
}
}
}
}
static void do_js_close(JSContext *cx, js_socket_private_t *p, bool finalize)
{
size_t i;
if (p->session != -1) {
if (p->tls_server)
destroy_session(lprintf, p->session);
else
cryptDestroySession(p->session);
// Delete any event handlers for the socket
if (p->js_cb) {
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
if (p->set->socks[i].sock != INVALID_SOCKET)
remove_js_socket_event(cx, p->js_cb, p->set->socks[i].sock);
}
}
else {
if (p->sock != INVALID_SOCKET)
remove_js_socket_event(cx, p->js_cb, p->sock);
}
}
if (p->sock == INVALID_SOCKET) {
if (p->external == false) {
store_socket_error(p, SOCKET_ERRNO, NULL);
else {
if (!finalize)
shutdown(p->sock, SHUT_RDWR);
}
// This is a lie for external sockets... don't tell anyone.
static bool js_socket_peek_byte(JSContext *cx, js_socket_private_t *p)
{
if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, 0) != CRYPT_OK)
if (p->peeked)
return TRUE;
if (js_socket_recv(cx, p, &p->peeked_byte, 1, 0, 0) == 1) {
p->peeked = TRUE;
return TRUE;
}
}
/* Returns > 0 upon successful data received (even if there was an error or disconnection) */
/* Returns -1 upon error (and no data received) */
/* Returns 0 upon timeout or disconnection (and no data received) */
static ptrdiff_t js_socket_recv(JSContext *cx, js_socket_private_t *p, void *buf, size_t len, int flags, int timeout)
ptrdiff_t total = 0;
int copied, ret;
char * estr;
time_t now = time(NULL);
int status;
if (p->session != -1) {
if (flags & MSG_PEEK)
if (p->peeked) {
*(uint8_t *)buf = p->peeked_byte;
buf = ((uint8_t *)buf) + 1;
if (!(flags & MSG_PEEK))
total++;
len--;
if (len == 0)
return total;
}
if (flags & MSG_PEEK)
return total;
if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, p->nonblocking?0:timeout) != CRYPT_OK)
return -1;
}
if (p->session == -1) {
if (p->sock == INVALID_SOCKET)
ret = -1;
else {
ret = recv(p->sock, buf, len, flags);
}
status = cryptPopData(p->session, buf, len, &copied);
if (cryptStatusOK(status))
ret = copied;
else {
ret = -1;
if (status == CRYPT_ERROR_TIMEOUT)
ret = 0;
GCES(ret, p, estr, "popping data");
}
}
if (ret == -1) {
if (total > 0)
return total;
return ret;
}
if ((!(flags & MSG_WAITALL)) || p->nonblocking)
return total;
if (ret >= (ptrdiff_t)len)
return total;
len -= ret;
buf = ((uint8_t *)buf) + ret;
if (!socket_check(p->sock, NULL, NULL, 0)) {
if (now + timeout > time(NULL))
return total;
return total;
static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush)
ptrdiff_t total = 0;
int copied = 0, ret;
char * estr;
// If we don't limit this, we occasionally get errors on large sends...
if ((ret = cryptPushData(p->session, msg, len > 0x2000 ? 0x2000 : len, &copied)) == CRYPT_OK) {
if (copied >= (ptrdiff_t)len)
msg = ((uint8_t *)msg) + copied;
if (ret != CRYPT_ERROR_COMPLETE)
GCES(ret, p, estr, "pushing data");
if (!socket_check(p->sock, NULL, NULL, 0))
break;
} while (len);
if (flush)
return total;
static off_t js_socket_sendfilesocket(js_socket_private_t *p, int file)
for (;;) {
ssize_t rd = read(file, buf, sizeof(buf));
if (rd < 0) {
if (p->session != -1)
do_CryptFlush(p);
ssize_t sent = 0;
while (sent < rd) {
ptrdiff_t wr = js_socket_sendsocket(p, buf + sent, rd - sent, false);
if (wr > 0) {
sent += wr;
}
else if (wr == SOCKET_ERROR && SOCKET_ERRNO == EWOULDBLOCK) {
else {
if (p->session != -1)
do_CryptFlush(p);
if (sent != rd) {
if (p->session != -1)
do_CryptFlush(p);
if (p->session != -1)
do_CryptFlush(p);
static void dbprintf(bool error, js_socket_private_t* p, char* fmt, ...)
if (p == NULL || (!p->debug /*&& !error */))
va_start(argptr, fmt);
vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
sbuf[sizeof(sbuf) - 1] = 0;
va_end(argptr);
lprintf(LOG_DEBUG, "%04d Socket %s%s", p->sock, error ? "ERROR: ":"", sbuf);
/* Socket Destructor */
static void js_finalize_socket(JSContext *cx, JSObject *obj)
{
if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL)
if (p->tls_psk)
JS_RemoveObjectRoot(cx, &p->tls_psk);
free(p->set);
free(p->hostname);
free(p);
JS_SetPrivate(cx, obj, NULL);
extern JSClass js_socket_class;
js_close(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
js_socket_private_t* p;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
rc = JS_SUSPENDREQUEST(cx);
JS_RESUMEREQUEST(cx, rc);
static ushort js_port(JSContext* cx, jsval val, int type)
{
char* cp;
JSString* str;
int32 i = 0;
struct servent* serv;
jsrefcount rc;
if (JSVAL_IS_NUMBER(val)) {
JS_ValueToInt32(cx, val, &i);
}
if (JSVAL_IS_STRING(val)) {
str = JS_ValueToString(cx, val);
if (cp != NULL) {
if (IS_DIGIT(*cp))
return (ushort)strtol(cp, NULL, 0);
rc = JS_SUSPENDREQUEST(cx);
serv = getservbyname(cp, type == SOCK_STREAM ? "tcp":"udp");
return htons(serv->s_port);
}
}
SOCKET js_socket(JSContext *cx, jsval val)
void* vp;
JSClass* cl;
SOCKET sock = INVALID_SOCKET;
if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
if (cl->flags & JSCLASS_HAS_PRIVATE)
if ((vp = JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL)
sock = *(SOCKET*)vp;
} else if (val != JSVAL_VOID) {
int32 i;
if (JS_ValueToInt32(cx, val, &i))
sock = i;
}
size_t js_socket_numsocks(JSContext *cx, jsval val)
js_socket_private_t *p;
JSClass* cl;
SOCKET sock = INVALID_SOCKET;
size_t i;
int32_t intval;
size_t ret = 0;
if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
if (cl->flags & JSCLASS_HAS_PRIVATE) {
if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
if (p->set->socks[i].sock == INVALID_SOCKET)
if (sock != INVALID_SOCKET)
} else if (val != JSVAL_VOID) {
if (JS_ValueToInt32(cx, val, &intval)) {
if (intval != INVALID_SOCKET)
ret = 1;
size_t js_socket_add(JSContext *cx, jsval val, struct pollfd *fds, short events)
js_socket_private_t *p;
JSClass* cl;
SOCKET sock = INVALID_SOCKET;
size_t i;
int32_t intval;
size_t ret = 0;
if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
if (cl->flags & JSCLASS_HAS_PRIVATE) {
if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
if (p->set->socks[i].sock == INVALID_SOCKET)
fds[ret].events = events;
fds[ret++].fd = p->set->socks[i].sock;
if (sock != INVALID_SOCKET) {
fds[ret].events = events;
fds[ret++].fd = sock;
} else if (val != JSVAL_VOID) {
if (JS_ValueToInt32(cx, val, &intval)) {
if (sock != INVALID_SOCKET) {
fds[ret].events = events;
fds[ret++].fd = sock;
}
SOCKET js_socket_add(JSContext *cx, jsval val, fd_set *fds)
js_socket_private_t *p;
JSClass* cl;
SOCKET sock = INVALID_SOCKET;
size_t i;
int32_t intval;
if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
if (cl->flags & JSCLASS_HAS_PRIVATE) {
if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
if (p->set->socks[i].sock == INVALID_SOCKET)
FD_SET(p->set->socks[i].sock, fds);
if (p->set->socks[i].sock > sock)
sock = p->set->socks[i].sock;
if (sock != INVALID_SOCKET)
} else if (val != JSVAL_VOID) {
if (JS_ValueToInt32(cx, val, &intval)) {
sock = intval;
FD_SET(sock, fds);
bool js_socket_isset(JSContext *cx, jsval val, fd_set *fds)
js_socket_private_t *p;
JSClass* cl;
size_t i;
int intval;
if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
if (cl->flags & JSCLASS_HAS_PRIVATE) {
if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
if (p->set->socks[i].sock == INVALID_SOCKET)
if (FD_ISSET(p->set->socks[i].sock, fds))
if (p->sock == INVALID_SOCKET)
return TRUE;
else {
if (FD_ISSET(p->sock, fds))
} else if (val != JSVAL_VOID) {
if (JS_ValueToInt32(cx, val, &intval)) {
if (FD_ISSET(intval, fds))
void js_timeval(JSContext* cx, jsval val, struct timeval* tv)
if (JSVAL_IS_INT(val))
tv->tv_sec = JSVAL_TO_INT(val);
else if (JSVAL_IS_DOUBLE(val)) {
if (JS_ValueToNumber(cx, val, &jsd)) {
tv->tv_usec = (int)(jsd * 1000000.0) % 1000000;
int js_polltimeout(JSContext* cx, jsval val)
if (JSVAL_IS_INT(val))
if (JSVAL_IS_DOUBLE(val)) {
if (JS_ValueToNumber(cx, val, &jsd))
return (int)(jsd * 1000);
js_bind(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
js_socket_private_t* p;
ushort port = 0;
union xp_sockaddr addr;
jsrefcount rc;
char * cstr = NULL;
char portstr[6];
struct addrinfo hints, *res, *tres;
int ret;
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
memset(&addr, 0, sizeof(addr));
if (argc)
port = js_port(cx, argv[0], p->type);
if (argc > 1 && argv[1] != JSVAL_VOID) {
JSVALUE_TO_ASTRING(cx, argv[1], cstr, INET6_ADDRSTRLEN, NULL);
}
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
hints.ai_socktype = p->type;
/* We need servname to be non-NULL so we can use a NULL hostname */
sprintf(portstr, "%hu", port);
rc = JS_SUSPENDREQUEST(cx);
if ((ret = getaddrinfo(cstr, portstr, &hints, &res)) != 0) {
JS_RESUMEREQUEST(cx, rc);
dbprintf(TRUE, p, "getaddrinfo(%s, %s) failed with error %d", cstr, portstr, ret);

Rob Swindell
committed
store_socket_error(p, ret, gai_strerror(ret));
for (tres = res; tres; tres = tres->ai_next) {
if (bind(p->sock, tres->ai_addr, tres->ai_addrlen) != 0) {
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "bind failed with error %d", SOCKET_ERRNO);
else
break;
dbprintf(false, p, "bound to port %u", port);
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
JS_RESUMEREQUEST(cx, rc);
static JSBool
js_listen(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
js_socket_private_t* p;
int32 backlog = 1;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID)
backlog = JS_ValueToInt32(cx, argv[0], &backlog);
rc = JS_SUSPENDREQUEST(cx);
if (listen(p->sock, backlog) != 0) {
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "listen failed with error %d", SOCKET_ERRNO);
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
JS_RESUMEREQUEST(cx, rc);
dbprintf(false, p, "listening, backlog=%d", backlog);
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
JS_RESUMEREQUEST(cx, rc);
}
static JSBool
js_accept(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
js_socket_private_t* p;
js_socket_private_t* new_p;
JSObject* sockobj;
SOCKET new_socket;
socklen_t addrlen;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
addrlen = sizeof(p->remote_addr);
rc = JS_SUSPENDREQUEST(cx);
if (p->set) {
if ((new_socket = xpms_accept(p->set, &(p->remote_addr), &addrlen, XPMS_FOREVER, XPMS_FLAGS_NONE, NULL)) == INVALID_SOCKET) {
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "accept failed with error %d", SOCKET_ERRNO);
call_socket_open_callback(TRUE);
if ((new_socket = accept_socket(p->sock, &(p->remote_addr), &addrlen)) == INVALID_SOCKET) {
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "accept failed with error %d", SOCKET_ERRNO);
if ((sockobj = js_CreateSocketObject(cx, obj, "new_socket", new_socket, -1)) == NULL) {
closesocket(new_socket);
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Error creating new socket object");
return JS_FALSE;
if ((new_p = (js_socket_private_t*)JS_GetPrivate(cx, sockobj)) == NULL) {
JS_RESUMEREQUEST(cx, rc);
new_p->type = p->type;
new_p->debug = p->debug;
new_p->nonblocking = p->nonblocking;
new_p->external = false; /* let destructor close socket */
new_p->is_connected = TRUE;
dbprintf(false, p, "accepted connection");
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(sockobj));
JS_RESUMEREQUEST(cx, rc);
struct js_connect_event_args {
SOCKET sv[2];
SOCKET sock;
int socktype;
char *host;
ushort port;
};
static void
js_connect_event_thread(void *args)
{
struct js_connect_event_args *a = args;
struct addrinfo hints, *res = NULL, *cur;
int result;
ulong val;
char sresult;
SetThreadName("sbbs/jsConnect");
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = a->socktype;
hints.ai_flags = AI_ADDRCONFIG;
result = getaddrinfo(a->host, NULL, &hints, &res);
goto done;
/* always set to blocking here */
val = 0;
ioctlsocket(a->sock, FIONBIO, &val);
result = SOCKET_ERROR;
for (cur = res; cur != NULL; cur = cur->ai_next) {
inet_setaddrport((void *)cur->ai_addr, a->port);
result = connect(a->sock, cur->ai_addr, cur->ai_addrlen);
break;
}
sresult = result;
/* Restore original setting here */
ioctlsocket(a->sock, FIONBIO, (ulong*)&(a->nonblocking));
send(a->sv[1], &sresult, 1, 0);
done:
closesocket(a->sv[1]);
free(a);
}
static JSBool
js_connect_event(JSContext *cx, uintN argc, jsval *arglist, js_socket_private_t *p, ushort port, JSObject *obj)
{
SOCKET sv[2];
struct js_event_list * ev;
JSFunction * ecb;
js_callback_t * cb = js_get_callback(cx);
jsval * argv = JS_ARGV(cx, arglist);
JS_ReportError(cx, "invalid socket");
return JS_FALSE;
}
if (cb == NULL) {
return JS_FALSE;
}
if (!cb->events_supported) {
JS_ReportError(cx, "events not supported");
return JS_FALSE;
}
ecb = JS_ValueToFunction(cx, argv[2]);
if (ecb == NULL) {
return JS_FALSE;
}
// Create socket pair...
#ifdef _WIN32
if (socketpair(AF_INET, SOCK_STREAM, 0, sv) == -1) {
#else
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
#endif
JS_ReportError(cx, "Error %d creating socket pair", SOCKET_ERRNO);
return JS_FALSE;
}
// Create event
ev = malloc(sizeof(*ev));
if (ev == NULL) {
JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
closesocket(sv[0]);
closesocket(sv[1]);
return JS_FALSE;
}
ev->prev = NULL;
ev->next = cb->events;
if (ev->next)
ev->next->prev = ev;
ev->type = JS_EVENT_SOCKET_CONNECT;
ev->cx = obj;
JS_AddObjectRoot(cx, &ev->cx);
ev->cb = ecb;
ev->data.connect.sv[0] = sv[0];
ev->data.connect.sv[1] = sv[1];
ev->id = cb->next_eid++;
p->js_cb = cb;
cb->events = ev;
// Start thread
args = malloc(sizeof(*args));
if (args == NULL) {
JS_ReportError(cx, "error allocating %lu bytes", sizeof(*args));
closesocket(sv[0]);
closesocket(sv[1]);
return JS_FALSE;
}
args->sv[0] = sv[0];
args->sv[1] = sv[1];
args->sock = p->sock;
args->socktype = p->type;
args->host = strdup(p->hostname);
args->port = port;
args->nonblocking = p->nonblocking;
if (args->host == NULL) {
JS_ReportError(cx, "error duplicating hostname");
closesocket(sv[0]);
closesocket(sv[1]);
free(args);
return JS_FALSE;
}
_beginthread(js_connect_event_thread, 0 /* Can be smaller... */, args);
// Success!
p->is_connected = TRUE;
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
return JS_TRUE;
}
js_connect(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
int result;
ulong val;
ushort port;
JSString* str;
js_socket_private_t* p;
jsrefcount rc;
char ip_str[256];
struct addrinfo hints, *res, *cur;
int timeout = 10000; /* Default time-out */
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
port = js_port(cx, argv[1], p->type);
rc = JS_SUSPENDREQUEST(cx);
if (argc > 2 && JSVAL_IS_OBJECT(argv[2]) && !JSVAL_IS_NULL(argv[2]) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[2]))) {
JSBool bgr = js_connect_event(cx, argc, arglist, p, port, obj);
JS_RESUMEREQUEST(cx, rc);
return bgr;
}
dbprintf(false, p, "resolving hostname: %s", p->hostname);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = p->type;
hints.ai_flags = AI_ADDRCONFIG;
result = getaddrinfo(p->hostname, NULL, &hints, &res);
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

Rob Swindell
committed
store_socket_error(p, result, gai_strerror(result));
dbprintf(TRUE, p, "getaddrinfo(%s) failed with error %d", p->hostname, result);
JS_RESUMEREQUEST(cx, rc);
/* always set to nonblocking here */
val = 1;
ioctlsocket(p->sock, FIONBIO, &val);
result = SOCKET_ERROR;
for (cur = res; cur != NULL; cur = cur->ai_next) {
if (argc > 2) /* time-out value specified */
dbprintf(false, p, "connecting to %s on port %u at %s", ip_str, port, p->hostname);
result = connect(p->sock, cur->ai_addr, cur->ai_addrlen);
if (result == SOCKET_ERROR) {
result = SOCKET_ERRNO;
if (result == EWOULDBLOCK || result == EINPROGRESS) {
result = ETIMEDOUT;
socklen_t optlen = sizeof(so_error);
if (getsockopt(p->sock, SOL_SOCKET, SO_ERROR, (void*)&so_error, &optlen) == 0 && so_error == 0)
result = 0; /* success */
else
result = so_error;
}
/* Restore original setting here */
ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));

Rob Swindell
committed
store_socket_error(p, result, NULL);
dbprintf(TRUE, p, "connect failed with error %d", result);
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
JS_RESUMEREQUEST(cx, rc);
memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
freeaddrinfo(res);
p->is_connected = TRUE;
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
dbprintf(false, p, "connected to %s on port %u at %s", ip_str, port, p->hostname);
JS_RESUMEREQUEST(cx, rc);
js_send(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* cp = NULL;
size_t len;
JSString* str;
js_socket_private_t* p;
jsrefcount rc;
int ret;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
HANDLE_PENDING(cx, cp);
rc = JS_SUSPENDREQUEST(cx);
ret = js_socket_sendsocket(p, cp, len, TRUE);
if (ret >= 0) {
dbprintf(false, p, "sent %d of %lu bytes", ret, len);
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ret));
} else {
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "send of %lu bytes failed", len);
}
JS_RESUMEREQUEST(cx, rc);
static JSBool
js_sendline(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* cp = NULL;
size_t len;
JSString* str;
js_socket_private_t* p;
jsrefcount rc;
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
}
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
str = JS_ValueToString(cx, argv[0]);
JSSTRING_TO_MSTRING(cx, str, cp, &len);
HANDLE_PENDING(cx, cp);
rc = JS_SUSPENDREQUEST(cx);
if (js_socket_sendsocket(p, cp, len, false) == len && js_socket_sendsocket(p, "\r\n", 2, TRUE) == 2) {
dbprintf(false, p, "sent %lu bytes", len + 2);
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "send of %lu bytes failed", len + 2);
js_sendto(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* cp = NULL;
size_t len;
ushort port;
JSString* data_str;
JSString* ip_str;
js_socket_private_t* p;
jsrefcount rc;
struct addrinfo hints, *res, *cur;
int result;
char ip_addr[INET6_ADDRSTRLEN];
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
/* data */
data_str = JS_ValueToString(cx, argv[0]);
HANDLE_PENDING(cx, cp);
/* address */
ip_str = JS_ValueToString(cx, argv[1]);
JSSTRING_TO_MSTRING(cx, ip_str, p->hostname, NULL);
if (JS_IsExceptionPending(cx)) {
if (p->hostname == NULL) {
port = js_port(cx, argv[2], p->type);
rc = JS_SUSPENDREQUEST(cx);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = p->type;
hints.ai_flags = AI_ADDRCONFIG;
dbprintf(false, p, "resolving hostname: %s", p->hostname);
if ((result = getaddrinfo(p->hostname, NULL, &hints, &res) != 0)) {

Rob Swindell
committed
store_socket_error(p, result, gai_strerror(result));
dbprintf(TRUE, p, "getaddrinfo(%s) failed with error %d", p->hostname, result);
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
JS_RESUMEREQUEST(cx, rc);
for (cur = res; cur; cur = cur->ai_next) {
dbprintf(false, p, "sending %lu bytes to %s port %u at %s"
, len, ip_addr, port, p->hostname);
if (sendto(p->sock, cp, len, 0 /* flags */, cur->ai_addr, cur->ai_addrlen) == len) {
dbprintf(false, p, "sent %lu bytes", len);
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "send of %lu bytes failed to %s", len, ip_addr);
JS_RESUMEREQUEST(cx, rc);
js_sendfile(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* fname = NULL;
off_t len;
int file;
js_socket_private_t* p;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
HANDLE_PENDING(cx, fname);
JS_ReportError(cx, "Failure reading filename");
rc = JS_SUSPENDREQUEST(cx);
if ((file = nopen(fname, O_RDONLY | O_BINARY)) == -1) {
JS_RESUMEREQUEST(cx, rc);
len = js_socket_sendfilesocket(p, file);
close(file);
if (len > 0) {
dbprintf(false, p, "sent %" PRIdOFF " bytes", len);
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "send of %s failed", fname);
JS_RESUMEREQUEST(cx, rc);
static JSBool
js_sendbin(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
BYTE b;
WORD w;
DWORD l;
int32 val = 0;
size_t wr = 0;
int32 size = sizeof(DWORD);
js_socket_private_t* p;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[0], &val);
if (argc > 1 && argv[1] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[1], &size);
rc = JS_SUSPENDREQUEST(cx);
switch (size) {
case sizeof(BYTE):
b = (BYTE)val;
wr = js_socket_sendsocket(p, &b, size, TRUE);
break;
case sizeof(WORD):
w = (WORD)val;
if (p->network_byte_order)
w = htons(w);
wr = js_socket_sendsocket(p, (BYTE*)&w, size, TRUE);
break;
case sizeof(DWORD):
l = val;
if (p->network_byte_order)
l = htonl(l);
wr = js_socket_sendsocket(p, (BYTE*)&l, size, TRUE);
/* unknown size */
dbprintf(TRUE, p, "unsupported binary write size: %d", size);
break;
}
if (wr == size) {
dbprintf(false, p, "sent %u bytes (binary)", size);
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "send of %u bytes (binary) failed", size);
JS_RESUMEREQUEST(cx, rc);
}
js_recv(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* buf;
int32 len = 512;
int32 timeout = 120;
JSString* str;
jsrefcount rc;
js_socket_private_t* p;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID) {
JS_ValueToInt32(cx, argv[0], &len);
if (argc > 1 && argv[1] != JSVAL_VOID) {
JS_ValueToInt32(cx, argv[1], &timeout);
if ((buf = (char*)malloc(len + 1)) == NULL) {
JS_ReportError(cx, "Error allocating %u bytes", len + 1);
}
rc = JS_SUSPENDREQUEST(cx);
len = js_socket_recv(cx, p, buf, len, 0, timeout);
JS_RESUMEREQUEST(cx, rc);
store_socket_error(p, SOCKET_ERRNO, NULL);
JS_SET_RVAL(cx, arglist, JSVAL_NULL);
free(buf);
}
str = JS_NewStringCopyN(cx, buf, len);
free(buf);
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
rc = JS_SUSPENDREQUEST(cx);
dbprintf(false, p, "received %u bytes", len);
JS_RESUMEREQUEST(cx, rc);
free(buf);
static JSBool
js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* buf;
char ip_addr[INET6_ADDRSTRLEN];
char port[32];
int rd = 0;
int32 len = 512;
uintN n;
bool binary = false;
BYTE b;
WORD w;
DWORD l;
jsval data_val = JSVAL_NULL;
JSString* str;
JSObject* retobj;
union xp_sockaddr addr;
socklen_t addrlen;
jsrefcount rc;
js_socket_private_t* p;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
JS_SET_RVAL(cx, arglist, JSVAL_NULL);
for (n = 0; n < argc; n++) {
if (JSVAL_IS_BOOLEAN(argv[n])) {
binary = JSVAL_TO_BOOLEAN(argv[n]);
if (binary)
len = sizeof(DWORD);
} else if (argv[n] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[n], &len);
}
addrlen = sizeof(addr.addr);
if (binary) { /* Binary/Integer Data */
rc = JS_SUSPENDREQUEST(cx);
switch (len) {
case sizeof(BYTE):
if ((rd = recvfrom(p->sock, &b, len, 0, &addr.addr, &addrlen)) == len)
data_val = INT_TO_JSVAL(b);
break;
case sizeof(WORD):
if ((rd = recvfrom(p->sock, (BYTE*)&w, len, 0, &addr.addr, &addrlen)) == len) {
if (p->network_byte_order)
w = ntohs(w);
data_val = INT_TO_JSVAL(w);
}
break;
case sizeof(DWORD):
if ((rd = recvfrom(p->sock, (BYTE*)&l, len, 0, &addr.addr, &addrlen)) == len) {
if (p->network_byte_order)
l = ntohl(l);
data_val = UINT_TO_JSVAL(l);
}
break;
}
JS_RESUMEREQUEST(cx, rc);
store_socket_error(p, SOCKET_ERRNO, NULL);
} else { /* String Data */
if ((buf = (char*)malloc(len + 1)) == NULL) {
JS_ReportError(cx, "Error allocating %u bytes", len + 1);
rc = JS_SUSPENDREQUEST(cx);
len = recvfrom(p->sock, buf, len, 0, &addr.addr, &addrlen);
JS_RESUMEREQUEST(cx, rc);
store_socket_error(p, SOCKET_ERRNO, NULL);
free(buf);
str = JS_NewStringCopyN(cx, buf, len);
free(buf);
data_val = STRING_TO_JSVAL(str);
}
if ((retobj = JS_NewObject(cx, NULL, NULL, obj)) == NULL) {
JS_ReportError(cx, "JS_NewObject failed");
JS_DefineProperty(cx, retobj, "data"
, data_val
, NULL, NULL, JSPROP_ENUMERATE);
sprintf(port, "%u", inet_addrport(&addr));
if ((str = JS_NewStringCopyZ(cx, port)) == NULL)
JS_DefineProperty(cx, retobj, "port"
, STRING_TO_JSVAL(str)
, NULL, NULL, JSPROP_ENUMERATE);
if ((str = JS_NewStringCopyZ(cx, ip_addr)) == NULL)
JS_DefineProperty(cx, retobj, "ip_address"
, STRING_TO_JSVAL(str)
, NULL, NULL, JSPROP_ENUMERATE);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(retobj));
rc = JS_SUSPENDREQUEST(cx);
dbprintf(false, p, "received %u bytes from %s:%s", len, ip_addr, port);
JS_RESUMEREQUEST(cx, rc);
}
js_peek(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char* buf;
int32 len = 512;
JSString* str;
jsrefcount rc;
js_socket_private_t* p;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[0], &len);
if ((buf = (char*)malloc(len + 1)) == NULL) {
JS_ReportError(cx, "Error allocating %u bytes", len + 1);
}
rc = JS_SUSPENDREQUEST(cx);
if (p->session == -1)
len = js_socket_recv(cx, p, buf, len, MSG_PEEK, 120);
JS_RESUMEREQUEST(cx, rc);
store_socket_error(p, SOCKET_ERRNO, NULL);
JS_SET_RVAL(cx, arglist, JSVAL_NULL);
free(buf);
}
str = JS_NewStringCopyN(cx, buf, len);
free(buf);
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
rc = JS_SUSPENDREQUEST(cx);
dbprintf(false, p, "received %u bytes, lasterror=%d"
, len, SOCKET_ERRNO);
JS_RESUMEREQUEST(cx, rc);
/* Returns 0 if there is rx data waiting */
/* Returns 1 if the 'timeout' period has elapsed (with no data waiting) */
/* Returns 2 if the socket has been disconnected (regardless of any data waiting) */
/* Returns 3 if there was no rx data waiting and the 'timeout' period has not yet elapsed */
js_sock_read_check(js_socket_private_t *p, time_t start, int32 timeout, int i)
if (!socket_check(p->sock, &rd, NULL, 1000)) {
store_socket_error(p, SOCKET_ERRNO, NULL);
if (!rd) {
if (time(NULL) - start > timeout) {
dbprintf(false, p, "recvline timeout (received: %d)", i);
return 1;
}
return 3;
}
return 0;
}
/* This method is to return null on error/timeout, not void/undefined */
js_recvline(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
char ch;
char* buf;
int i, got;
int32 len = 512;
time_t start;
int32 timeout = 30; /* seconds */
JSString* str;
js_socket_private_t* p;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_NULL);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[0], &len);
if ((buf = (char*)malloc(len + 1)) == NULL) {
JS_ReportError(cx, "Error allocating %u bytes", len + 1);
}
if (argc > 1 && argv[1] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[1], &timeout);

rswindell
committed
start = time(NULL);
rc = JS_SUSPENDREQUEST(cx);
for (i = 0; i < len;) {
if (p->session == -1) {
switch (js_sock_read_check(p, start, timeout, i)) {
case 1: // time-out */
case 2: // disconnected
if (i) { // some data was received before the error/disconnection
len = 0; // so break the loop
continue;
}
// no data received, so just return null
return JS_TRUE;
case 3: // no data and no time-out... yet
}
if ((got = js_socket_recv(cx, p, &ch, 1, 0, i?1:timeout)) != 1) {
if (p->session == -1)
store_socket_error(p, SOCKET_ERRNO, NULL);
if (i == 0) { // no data received
JS_RESUMEREQUEST(cx, rc);
free(buf); // so return null (not an empty string)

rswindell
committed
}
break;
}
if (ch == '\n' /* && i>=1 */) /* Mar-9-2003: terminate on sole LF */
if (i > 0 && buf[i - 1] == '\r')
buf[i - 1] = 0;
JS_RESUMEREQUEST(cx, rc);
str = JS_NewStringCopyZ(cx, buf);
free(buf);
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
rc = JS_SUSPENDREQUEST(cx);
dbprintf(false, p, "received %u bytes (recvline) lasterror=%d"
JS_RESUMEREQUEST(cx, rc);
static JSBool
js_recvbin(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
BYTE b;
WORD w;
DWORD l;
int32 size = sizeof(DWORD);
int rd = 0;
js_socket_private_t* p;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[0], &size);
rc = JS_SUSPENDREQUEST(cx);
switch (size) {
case sizeof(BYTE):
if ((rd = js_socket_recv(cx, p, &b, size, MSG_WAITALL, 120)) == size)
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(b));
break;
case sizeof(WORD):
if ((rd = js_socket_recv(cx, p, (BYTE*)&w, size, MSG_WAITALL, 120)) == size) {
if (p->network_byte_order)
w = ntohs(w);
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(w));
}
break;
case sizeof(DWORD):
if ((rd = js_socket_recv(cx, p, (BYTE*)&l, size, MSG_WAITALL, 120)) == size) {
if (p->network_byte_order)
l = ntohl(l);
JS_SET_RVAL(cx, arglist, UINT_TO_JSVAL(l));
}
break;
}
store_socket_error(p, SOCKET_ERRNO, NULL);
JS_RESUMEREQUEST(cx, rc);
}
js_getsockopt(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
int opt;
int level = 0;
int val = 0;
js_socket_private_t* p;
LINGER linger;
void* vp = &val;
socklen_t len = sizeof(val);
jsrefcount rc;
char * cstr;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
rc = JS_SUSPENDREQUEST(cx);
if ((opt = getSocketOptionByName(cstr, &level)) == -1) {
JS_RESUMEREQUEST(cx, rc);
if (opt == SO_LINGER) {
vp = &linger;
len = sizeof(linger);
if (getsockopt(p->sock, level, opt, vp, &len) == 0) {
if (opt == SO_LINGER) {
if (linger.l_onoff == TRUE)
val = linger.l_linger;
else
val = 0;
}
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(val));
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(TRUE, p, "error %d getting option %d"
, SOCKET_ERRNO, opt);
JS_RESUMEREQUEST(cx, rc);
}
static JSBool
js_setsockopt(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
int opt;
int level;
int32 val = 1;
js_socket_private_t* p;
LINGER linger;
void* vp = &val;
socklen_t len = sizeof(val);
jsrefcount rc;
char * optname;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
JSVALUE_TO_ASTRING(cx, argv[0], optname, 64, NULL);
rc = JS_SUSPENDREQUEST(cx);
opt = getSocketOptionByName(optname, &level);
if (argv[1] != JSVAL_VOID) {
JS_RESUMEREQUEST(cx, rc);
JS_ValueToInt32(cx, argv[1], &val);
rc = JS_SUSPENDREQUEST(cx);
if (opt == SO_LINGER) {
if (val) {
linger.l_onoff = TRUE;
linger.l_linger = (ushort)val;
} else {
ZERO_VAR(linger);
}
vp = &linger;
len = sizeof(linger);
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(
setsockopt(p->sock, level, opt, vp, len) == 0));
store_socket_error(p, SOCKET_ERRNO, NULL);
JS_RESUMEREQUEST(cx, rc);
js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
int32 cmd = 0;
int32 arg = 0;
js_socket_private_t* p;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (argc && argv[0] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[0], &cmd);
if (argc > 1 && argv[1] != JSVAL_VOID)
JS_ValueToInt32(cx, argv[1], &arg);
rc = JS_SUSPENDREQUEST(cx);
if (ioctlsocket(p->sock, cmd, (ulong*)&arg) == 0) {
JS_RESUMEREQUEST(cx, rc);
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(arg));
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
JS_RESUMEREQUEST(cx, rc);
store_socket_error(p, SOCKET_ERRNO, NULL);
static JSBool
js_poll(JSContext *cx, uintN argc, jsval *arglist)
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
js_socket_private_t* p;
bool poll_for_write = false;
uintN argn;
int result;
jsrefcount rc;
int timeout = 0;
struct pollfd * fds;
nfds_t nfds;
jsval objval = OBJECT_TO_JSVAL(obj);
size_t i;
SOCKET high = 0;
fd_set socket_set;
fd_set* rd_set = NULL;
fd_set* wr_set = NULL;
struct timeval tv = {0, 0};
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
if (p->sock == INVALID_SOCKET && p->set == NULL) {
dbprintf(TRUE, p, "INVALID SOCKET in call to poll");
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
for (argn = 0; argn < argc; argn++) {
if (JSVAL_IS_BOOLEAN(argv[argn]))
poll_for_write = JSVAL_TO_BOOLEAN(argv[argn]);
else if (JSVAL_IS_NUMBER(argv[argn])) {
js_timeval(cx, argv[argn], &tv);
}
rc = JS_SUSPENDREQUEST(cx);
#ifdef PREFER_POLL
if (p->peeked && !poll_for_write) {
result = 1;
}
else {
nfds = js_socket_numsocks(cx, objval);
fds = calloc(nfds, sizeof(*fds));
if (fds == NULL) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Error allocating %d elements of %lu bytes at %s:%d"
, nfds, sizeof(*fds), getfname(__FILE__), __LINE__);
return JS_FALSE;
}
nfds = js_socket_add(cx, objval, fds, poll_for_write ? POLLOUT : POLLIN);
result = poll(fds, nfds, timeout);
free(fds);
}
#else
FD_ZERO(&socket_set);
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
FD_SET(p->set->socks[i].sock, &socket_set);
if (p->set->socks[i].sock > high)
high = p->sock;
FD_SET(p->sock, &socket_set);
if (poll_for_write)
wr_set = &socket_set;
else
rd_set = &socket_set;
if (p->peeked && !poll_for_write)
result = 1;
else
result = select(high + 1, rd_set, wr_set, NULL, &tv);
store_socket_error(p, SOCKET_ERRNO, NULL);
dbprintf(false, p, "poll: select/poll returned %d (errno %d)"
, result, p->last_error);
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(result));
JS_RESUMEREQUEST(cx, rc);
}
static js_callback_t *
js_get_callback(JSContext *cx)
{
JSObject* scope = JS_GetScopeChain(cx);
JSObject* pscope;
jsval val = JSVAL_NULL;
while ((!JS_LookupProperty(cx, pscope, "js", &val) || val == JSVAL_VOID || !JSVAL_IS_OBJECT(val)) && pscope != NULL) {
pscope = JS_GetParent(cx, pscope);
if (pscope == NULL) {
JS_ReportError(cx, "Walked to global, no js object!");
return NULL;
}
}
pjs_obj = JSVAL_TO_OBJECT(val);
return NULL;
return JS_GetPrivate(cx, pjs_obj);
}
static void
js_install_one_socket_event(JSContext *cx, JSObject *obj, JSFunction *ecb, js_callback_t *cb, js_socket_private_t *p, SOCKET sock, enum js_event_type et)
{
struct js_event_list *ev;
ev = malloc(sizeof(*ev));
if (ev == NULL) {
JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
return;
}
ev->prev = NULL;
ev->next = cb->events;
if (ev->next)
ev->next->prev = ev;
ev->type = et;
ev->cx = obj;
JS_AddObjectRoot(cx, &ev->cx);
ev->cb = ecb;
ev->data.sock = sock;
ev->id = cb->next_eid;
p->js_cb = cb;
cb->events = ev;
}
static JSBool
js_install_event(JSContext *cx, uintN argc, jsval *arglist, bool once)
jsval * argv = JS_ARGV(cx, arglist);
js_callback_t* cb;
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
JSFunction * ecb;
js_socket_private_t* p;
size_t i;
char operation[16];
enum js_event_type et;
size_t slen;
if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
/*
* NOTE: If you allow a thisObj here, you'll need to deal with js_GetClassPrivate
* in js_internal.c where the object is assumed to be a socket.
*/
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
if (argc != 2) {
JS_ReportError(cx, "js.on() and js.once() require exactly two parameters");
return JS_FALSE;
}
ecb = JS_ValueToFunction(cx, argv[1]);
if (ecb == NULL) {
return JS_FALSE;
}
JSVALUE_TO_STRBUF(cx, argv[0], operation, sizeof(operation), &slen);
HANDLE_PENDING(cx, NULL);
if (strcmp(operation, "read") == 0) {
if (once)
et = JS_EVENT_SOCKET_READABLE_ONCE;
else
et = JS_EVENT_SOCKET_READABLE;
}
else if (strcmp(operation, "write") == 0) {
if (once)
et = JS_EVENT_SOCKET_WRITABLE_ONCE;
else
et = JS_EVENT_SOCKET_WRITABLE;
}
else {
JS_ReportError(cx, "event parameter must be 'read' or 'write'");
return JS_FALSE;
}
cb = js_get_callback(cx);
if (cb == NULL) {
return JS_FALSE;
}
if (!cb->events_supported) {
JS_ReportError(cx, "events not supported");
return JS_FALSE;
}
if (p->set) {
for (i = 0; i < p->set->sock_count; i++) {
if (p->set->socks[i].sock != INVALID_SOCKET)
js_install_one_socket_event(cx, obj, ecb, cb, p, p->set->socks[i].sock, et);
}
}
else {
if (p->sock != INVALID_SOCKET)
js_install_one_socket_event(cx, obj, ecb, cb, p, p->sock, et);
}
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(cb->next_eid));
cb->next_eid++;
if (JS_IsExceptionPending(cx))
return JS_FALSE;
return JS_TRUE;
}
static JSBool
js_clear_socket_event(JSContext *cx, uintN argc, jsval *arglist, bool once)
jsval * argv = JS_ARGV(cx, arglist);
char operation[16];
size_t slen;
js_callback_t* cb;
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
if (argc != 2) {
JS_ReportError(cx, "js.clearOn() and js.clearOnce() require exactly two parameters");
return JS_FALSE;
}
JSVALUE_TO_STRBUF(cx, argv[0], operation, sizeof(operation), &slen);
HANDLE_PENDING(cx, NULL);
if (strcmp(operation, "read") == 0) {
if (once)
et = JS_EVENT_SOCKET_READABLE_ONCE;
else
et = JS_EVENT_SOCKET_READABLE;
}
else if (strcmp(operation, "write") == 0) {
if (once)
et = JS_EVENT_SOCKET_WRITABLE_ONCE;
else
et = JS_EVENT_SOCKET_WRITABLE;
}
else {
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
return JS_TRUE;
}
cb = js_get_callback(cx);
if (cb == NULL) {
return JS_FALSE;
}
return js_clear_event(cx, arglist, cb, et, 1);
static JSBool
js_once(JSContext *cx, uintN argc, jsval *arglist)
{
return js_install_event(cx, argc, arglist, TRUE);
}
static JSBool
js_clearOnce(JSContext *cx, uintN argc, jsval *arglist)
{
return js_clear_socket_event(cx, argc, arglist, TRUE);
}
static JSBool
js_on(JSContext *cx, uintN argc, jsval *arglist)
{
return js_install_event(cx, argc, arglist, false);
}
static JSBool
js_clearOn(JSContext *cx, uintN argc, jsval *arglist)
{
return js_clear_socket_event(cx, argc, arglist, false);
/* Socket Object Properties */
SOCK_PROP_LAST_ERROR
, SOCK_PROP_ERROR_STR
, SOCK_PROP_IS_CONNECTED
, SOCK_PROP_IS_WRITEABLE
, SOCK_PROP_DATA_WAITING
, SOCK_PROP_NREAD
, SOCK_PROP_DEBUG
, SOCK_PROP_DESCRIPTOR
, SOCK_PROP_NONBLOCKING
, SOCK_PROP_LOCAL_IP
, SOCK_PROP_LOCAL_PORT
, SOCK_PROP_REMOTE_IP
, SOCK_PROP_REMOTE_PORT
, SOCK_PROP_TYPE
, SOCK_PROP_FAMILY
, SOCK_PROP_NETWORK_ORDER
, SOCK_PROP_SSL_SESSION
, SOCK_PROP_SSL_SERVER
, SOCK_PROP_TLS_MINVER
#ifdef BUILD_JSDOCS
static const char* socket_prop_desc[] = {
"Error status for the last socket operation that failed - <small>READ ONLY</small>"
, "Error description for the last socket operation that failed - <small>READ ONLY</small>"
, "<tt>true</tt> if socket is in a connected state - <small>READ ONLY</small>"
, "<tt>true</tt> if socket can accept written data - Setting to false will shutdown the write end of the socket."
, "Alias for is_writeable"
, "<tt>true</tt> if data is waiting to be read from socket - <small>READ ONLY</small>"
, "Number of bytes waiting to be read - TLS sockets will never return more than 1 - <small>READ ONLY</small>"
, "Enable debug logging"
, "Socket descriptor (advanced uses only)"
, "Use non-blocking operation (default is <tt>false</tt>)"
, "Local IP address (string in dotted-decimal format)"
, "Local TCP or UDP port number"
, "Remote IP address (string in dotted-decimal format)"
, "Remote TCP or UDP port number"
, "Socket type, <tt>SOCK_STREAM</tt> (TCP) or <tt>SOCK_DGRAM</tt> (UDP)"
, "Socket protocol family, <tt>PF_INET</tt> (IPv4) or <tt>PF_INET6</tt> (IPv6)"
, "<tt>true</tt> if binary data is to be sent in Network Byte Order (big end first), default is <tt>true</tt>"
, "Set to <tt>true</tt> to enable SSL as a client on the socket"
, "Set to <tt>true</tt> to enable SSL as a server on the socket"
, "Set to 100 to support TLS 1.0, 101 to support TLS 1.1 and 102 (default) for TLS 1.2, must be set before enabling TLS"
, "Set to an object with id: key pairs for TLS PSK auth, must be set before enabling TLS"
, "If TLS PSK is used, indicates which entry in <i>tls_psk</i> was used by the remote"
/* statically-defined properties: */
, "Array of socket option names supported by the current platform"
, NULL
static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
jsval idval;
jsint tiny;
js_socket_private_t* p;
jsrefcount rc;
JSBool b;
int32 i;
scfg_t * scfg;
char* estr;
if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL) {
// Prototype access
JS_IdToValue(cx, id, &idval);
tiny = JSVAL_TO_INT(idval);
rc = JS_SUSPENDREQUEST(cx);
dbprintf(false, p, "setting property %d", tiny);
JS_RESUMEREQUEST(cx, rc);
JS_ValueToBoolean(cx, *vp, &(p->debug));
if (p->session != -1) {
if (p->tls_server)
destroy_session(lprintf, p->session);
else
cryptDestroySession(p->session);
if (JS_ValueToInt32(cx, *vp, &i))
p->sock = i;
p->is_connected = TRUE;
break;
case SOCK_PROP_LAST_ERROR:
if (JS_ValueToInt32(cx, *vp, &i))
p->last_error = i;
case SOCK_PROP_NONBLOCKING:
JS_ValueToBoolean(cx, *vp, &(p->nonblocking));
rc = JS_SUSPENDREQUEST(cx);
ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
JS_RESUMEREQUEST(cx, rc);
case SOCK_PROP_NETWORK_ORDER:
JS_ValueToBoolean(cx, *vp, &(p->network_byte_order));
case SOCK_PROP_IS_WRITEABLE:
JS_ValueToBoolean(cx, *vp, &b);
if (!b)
shutdown(p->sock, SHUT_WR);
break;
JS_ValueToBoolean(cx, *vp, &b);
rc = JS_SUSPENDREQUEST(cx);
if (b) {
if (p->session == -1) {
scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
if (ssl_sync(scfg, lprintf)) {
if ((ret = cryptCreateSession(&p->session, CRYPT_UNUSED, tiny == SOCK_PROP_SSL_SESSION ? CRYPT_SESSION_TLS: CRYPT_SESSION_TLS_SERVER)) == CRYPT_OK) {
ulong nb = 0;
ioctlsocket(p->sock, FIONBIO, &nb);
nb = 1;
setsockopt(p->sock, IPPROTO_TCP, TCP_NODELAY, (char*)&nb, sizeof(nb));
if ((ret = do_cryptAttribute(p->session, CRYPT_SESSINFO_NETWORKSOCKET, p->sock)) == CRYPT_OK) {
int minver = CRYPT_TLSOPTION_MINVER_TLS12;
if (p->tls_minver == 100)
minver = CRYPT_TLSOPTION_MINVER_TLS10;
else if (p->tls_minver == 101)
minver = CRYPT_TLSOPTION_MINVER_TLS11;
do_cryptAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, minver);
// Reduced compliance checking... required for acme-staging-v02.api.letsencrypt.org
do_cryptAttribute(p->session, CRYPT_OPTION_CERT_COMPLIANCELEVEL, CRYPT_COMPLIANCELEVEL_REDUCED);
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
// Add TLS PSK pairs
// Iterate object...
if (p->tls_psk) {
JSIdArray *ids = JS_Enumerate(cx, p->tls_psk);
if (ids) {
for (jsint k = 0; k < ids->length; k++) {
jsval js_id;
char *id;
size_t id_sz;
jsval js_key;
char *key;
size_t key_sz;
JS_IdToValue(cx, ids->vector[k], &js_id);
id = NULL;
JSVALUE_TO_MSTRING(cx, js_id, id, &id_sz);
if (id != NULL) {
if (!JS_IsExceptionPending(cx)) {
JS_GetProperty(cx, p->tls_psk, id, &js_key);
JSVALUE_TO_MSTRING(cx, js_key, key, &key_sz);
if (key != NULL) {
if (!JS_IsExceptionPending(cx)) {
if (do_cryptAttributeString(p->session, CRYPT_SESSINFO_USERNAME, id, id_sz) == CRYPT_OK)
do_cryptAttributeString(p->session, CRYPT_SESSINFO_PASSWORD, key, key_sz);
}
free(key);
}
}
free(id);
}
}
JS_DestroyIdArray(cx, ids);
}
}
// Ensure key and value are both strings...
// Set the strings.
// TODO: Make this configurable
do_cryptAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, CRYPT_TLSOPTION_DISABLE_NAMEVERIFY);
ret = do_cryptAttributeString(p->session, CRYPT_SESSINFO_SERVER_NAME, p->hostname, strlen(p->hostname));
if (scfg == NULL) {
ret = CRYPT_ERROR_NOTAVAIL;
}
else {
ret = add_private_key(scfg, lprintf, p->session);
if (ret != CRYPT_OK) {
GCES(ret, p, estr, "setting private key");
if (ret == CRYPT_OK) {
if ((ret = do_cryptAttribute(p->session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
GCES(ret, p, estr, "setting session active");
else
GCESH(ret, p->sock, CRYPT_UNUSED, estr, "creating session");
}
if (ret != CRYPT_OK) {
if (p->session != -1)
cryptDestroySession(p->session);
p->session = -1;
ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
if (p->session != -1) {
if (p->tls_server)
destroy_session(lprintf, p->session);
else
cryptDestroySession(p->session);
p->session = -1;
ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
case SOCK_PROP_TLS_MINVER:
if (JS_ValueToInt32(cx, *vp, &i)) {
switch (i) {
case 100:
case 101:
case 102:
p->tls_minver = i;
break;
}
}
break;
case SOCK_PROP_TLS_PSK:
if (JS_ValueToObject(cx, *vp, &pskobj)) {
if (p->tls_psk) {
JS_RemoveObjectRoot(cx, &p->tls_psk);
p->tls_psk = NULL;
}
p->tls_psk = pskobj;
JS_AddObjectRoot(cx, &p->tls_psk);
}
break;
static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
jsval idval;
jsint tiny;
ulong cnt;
bool rd;
bool wr;
js_socket_private_t* p;
JSString* js_str;
union xp_sockaddr addr;
socklen_t len = sizeof(addr);
jsrefcount rc;
char str[256];
if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL) {
// Protoype access
JS_IdToValue(cx, id, &idval);
tiny = JSVAL_TO_INT(idval);
rc = JS_SUSPENDREQUEST(cx);
dbprintf(false, p, "getting property %d", tiny);
*vp = INT_TO_JSVAL(p->last_error);
if ((js_str = JS_NewStringCopyZ(cx, p->last_error_str)) == NULL)
return JS_FALSE;
*vp = STRING_TO_JSVAL(js_str);
break;
if (!p->is_connected)
*vp = JSVAL_FALSE;
else
*vp = BOOLEAN_TO_JSVAL(socket_check(p->sock, NULL, NULL, 0));
case SOCK_PROP_IS_WRITEABLE:
if (p->sock == INVALID_SOCKET && p->set)
socket_check(p->sock, NULL, &wr, 0);
*vp = BOOLEAN_TO_JSVAL(wr);
break;
case SOCK_PROP_DATA_WAITING:
if (p->sock == INVALID_SOCKET && p->set)
else {
if (p->peeked)
rd = TRUE;
else if (p->session != -1)
else
socket_check(p->sock, &rd, NULL, 0);
}
*vp = BOOLEAN_TO_JSVAL(rd);
break;
case SOCK_PROP_NREAD:
if (p->sock == INVALID_SOCKET && p->set) {
if (p->session != -1) {
*vp = DOUBLE_TO_JSVAL((double)1);
else
*vp = JSVAL_ZERO;
}
else if (ioctlsocket(p->sock, FIONREAD, &cnt) == 0) {
*vp = DOUBLE_TO_JSVAL((double)cnt);
*vp = INT_TO_JSVAL(p->debug);
*vp = INT_TO_JSVAL(p->sock);
case SOCK_PROP_NONBLOCKING:
*vp = BOOLEAN_TO_JSVAL(p->nonblocking);
break;
case SOCK_PROP_LOCAL_IP:
if (p->sock != INVALID_SOCKET) {
if (getsockname(p->sock, (struct sockaddr *)&addr, &len) != 0)
JS_RESUMEREQUEST(cx, rc);
if ((js_str = JS_NewStringCopyZ(cx, str)) == NULL)
*vp = STRING_TO_JSVAL(js_str);
rc = JS_SUSPENDREQUEST(cx);
break;
case SOCK_PROP_LOCAL_PORT:
if (p->local_port != 0) {
*vp = INT_TO_JSVAL(p->local_port);
} else if (p->sock != INVALID_SOCKET) {
if (getsockname(p->sock, &addr.addr, &len) != 0)
JS_RESUMEREQUEST(cx, rc);
rc = JS_SUSPENDREQUEST(cx);
break;
case SOCK_PROP_REMOTE_IP:
if (p->is_connected) {
JS_RESUMEREQUEST(cx, rc);
if ((js_str = JS_NewStringCopyZ(cx, str)) == NULL)
*vp = STRING_TO_JSVAL(js_str);
rc = JS_SUSPENDREQUEST(cx);
break;
case SOCK_PROP_REMOTE_PORT:
case SOCK_PROP_TYPE:
*vp = INT_TO_JSVAL(p->type);
break;
case SOCK_PROP_FAMILY:
if (p->sock != INVALID_SOCKET) {
JS_RESUMEREQUEST(cx, rc);
if (getsockname(p->sock, &addr.addr, &len) != 0)
*vp = INT_TO_JSVAL(addr.addr.sa_family);
rc = JS_SUSPENDREQUEST(cx);
case SOCK_PROP_NETWORK_ORDER:
*vp = BOOLEAN_TO_JSVAL(p->network_byte_order);
case SOCK_PROP_SSL_SESSION:
*vp = BOOLEAN_TO_JSVAL(p->session != -1);
break;
*vp = BOOLEAN_TO_JSVAL(p->session != -1 && p->tls_server);
case SOCK_PROP_TLS_MINVER:
*vp = INT_TO_JSVAL(p->tls_minver);
break;
case SOCK_PROP_TLS_PSK:
if (p->tls_psk == NULL)
*vp = JSVAL_VOID;
else
*vp = OBJECT_TO_JSVAL(p->tls_psk);
break;
case SOCK_PROP_TLS_PSK_ID:
if (p->tls_psk == NULL)
*vp = JSVAL_VOID;
else {
int attrval;
if ((cryptGetAttribute(p->session, CRYPT_SESSINFO_TLS_OPTIONS, &attrval) != CRYPT_OK)
|| ((attrval & CRYPT_TLSOPTION_USED_PSK) == 0))
*vp = JSVAL_VOID;
else {
if ((cryptGetAttributeString(p->session, CRYPT_SESSINFO_USERNAME, NULL, &attrval) == CRYPT_OK) && (attrval > 0)) {
char *id = malloc(attrval);
if (id) {
if (cryptGetAttributeString(p->session, CRYPT_SESSINFO_USERNAME, id, &attrval) == CRYPT_OK) {
if ((js_str = JS_NewStringCopyN(cx, id, attrval)) == NULL) {
free(id);
return JS_FALSE;
}
*vp = STRING_TO_JSVAL(js_str);
JS_RESUMEREQUEST(cx, rc);
#define SOCK_PROP_FLAGS JSPROP_ENUMERATE | JSPROP_READONLY
static jsSyncPropertySpec js_socket_properties[] = {
/* name ,tinyid ,flags, ver */
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
{ "error", SOCK_PROP_LAST_ERROR, SOCK_PROP_FLAGS, 311 },
{ "last_error", SOCK_PROP_LAST_ERROR, JSPROP_READONLY, 310 }, /* alias */
{ "error_str", SOCK_PROP_ERROR_STR, SOCK_PROP_FLAGS, 318 },
{ "is_connected", SOCK_PROP_IS_CONNECTED, SOCK_PROP_FLAGS, 310 },
{ "is_writeable", SOCK_PROP_IS_WRITEABLE, JSPROP_ENUMERATE, 311 },
{ "is_writable", SOCK_PROP_IS_WRITEABLE, JSPROP_ENUMERATE, 312 }, /* alias */
{ "data_waiting", SOCK_PROP_DATA_WAITING, SOCK_PROP_FLAGS, 310 },
{ "nread", SOCK_PROP_NREAD, SOCK_PROP_FLAGS, 310 },
{ "debug", SOCK_PROP_DEBUG, JSPROP_ENUMERATE, 310 },
{ "descriptor", SOCK_PROP_DESCRIPTOR, JSPROP_ENUMERATE, 310 },
{ "nonblocking", SOCK_PROP_NONBLOCKING, JSPROP_ENUMERATE, 310 },
{ "local_ip_address", SOCK_PROP_LOCAL_IP, SOCK_PROP_FLAGS, 310 },
{ "local_port", SOCK_PROP_LOCAL_PORT, SOCK_PROP_FLAGS, 310 },
{ "remote_ip_address", SOCK_PROP_REMOTE_IP, SOCK_PROP_FLAGS, 310 },
{ "remote_port", SOCK_PROP_REMOTE_PORT, SOCK_PROP_FLAGS, 310 },
{ "type", SOCK_PROP_TYPE, SOCK_PROP_FLAGS, 310 },
{ "family", SOCK_PROP_FAMILY, SOCK_PROP_FLAGS, 318 },
{ "network_byte_order", SOCK_PROP_NETWORK_ORDER, JSPROP_ENUMERATE, 311 },
{ "ssl_session", SOCK_PROP_SSL_SESSION, JSPROP_ENUMERATE, 316 },
{ "ssl_server", SOCK_PROP_SSL_SERVER, JSPROP_ENUMERATE, 316 },
{ "tls_minver", SOCK_PROP_TLS_MINVER, JSPROP_ENUMERATE, 320 },
{ "tls_psk", SOCK_PROP_TLS_PSK, JSPROP_ENUMERATE, 32002 },
{ "tls_psk_id", SOCK_PROP_TLS_PSK_ID, JSPROP_ENUMERATE | JSPROP_READONLY, 32002 },
static jsSyncMethodSpec js_socket_functions[] = {
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
{"close", js_close, 0, JSTYPE_VOID, ""
, JSDOCSTR("Close (shutdown) the socket immediately")
, 310},
{"bind", js_bind, 0, JSTYPE_BOOLEAN, JSDOCSTR("[port] [,ip_address]")
, JSDOCSTR("Bind socket to a TCP or UDP <i>port</i> (number or service name), "
"optionally specifying a network interface (via <i>ip_address</i>)")
, 311},
{"connect", js_connect, 2, JSTYPE_BOOLEAN, JSDOCSTR("host, port [,timeout=10] [,callback]")
, JSDOCSTR("Connect to a remote port (number or service name) on the specified host (IP address or host name)"
", default <i>timeout</i> value is <i>10</i> (seconds). Instead of a timeout, you can pass a <i>callback</i> "
"which is called when the connection completes with the Socket object as the only parameter.")
, 311},
{"listen", js_listen, 0, JSTYPE_BOOLEAN, JSDOCSTR("")
, JSDOCSTR("Place socket in a state to listen for incoming connections (use before an accept)")
, 310},
{"accept", js_accept, 0, JSTYPE_OBJECT, JSDOCSTR("")
, JSDOCSTR("Accept an incoming connection, returns a new <i>Socket</i> object representing the new connection")
, 310},
{"write", js_send, 1, JSTYPE_ALIAS },
{"send", js_send, 1, JSTYPE_NUMBER, JSDOCSTR("data")
, JSDOCSTR("Send a string (AKA write). Returns the number of bytes sent or undefined if an error occured. "
"Versions before 3.17 returned a bool true if all bytes were sent and false otherwise.")
, 310},
{"writeln", js_sendline, 1, JSTYPE_ALIAS },
{"sendline", js_sendline, 1, JSTYPE_BOOLEAN, JSDOCSTR("data")
, JSDOCSTR("Send a string (AKA write) with a carriage return line feed appended")
, 317},
{"sendto", js_sendto, 3, JSTYPE_BOOLEAN, JSDOCSTR("data, address, port")
, JSDOCSTR("Send data to a specific host (IP address or host name) and port (number or service name), for UDP sockets")
, 310},
{"sendfile", js_sendfile, 1, JSTYPE_BOOLEAN, JSDOCSTR("path/filename")
, JSDOCSTR("Send an entire file over the socket")
, 310},
{"writeBin", js_sendbin, 1, JSTYPE_ALIAS },
{"sendBin", js_sendbin, 1, JSTYPE_BOOLEAN, JSDOCSTR("value [,bytes=4]")
, JSDOCSTR("Send a binary integer over the socket, default number of bytes is 4 (32-bits)")
, 311},
{"read", js_recv, 1, JSTYPE_ALIAS },
{"recv", js_recv, 1, JSTYPE_STRING, JSDOCSTR("[maxlen=512, [timeout_sec=120]]")
, JSDOCSTR("Receive a string, default maxlen is 512 characters (AKA read)")
, 310},
{"peek", js_peek, 0, JSTYPE_STRING, JSDOCSTR("[maxlen=512]")
, JSDOCSTR("Receive a string, default maxlen is 512 characters, leaves string in receive buffer (TLS sockets will never return more than one byte)")
, 310},
{"readline", js_recvline, 0, JSTYPE_ALIAS },
{"readln", js_recvline, 0, JSTYPE_ALIAS },
{"recvline", js_recvline, 0, JSTYPE_STRING, JSDOCSTR("[maxlen=512] [,timeout=30]")
, JSDOCSTR("Receive a line-feed terminated string, default maxlen is 512 characters, default timeout is 30 seconds (AKA readline and readln)")
, 310},
{"recvfrom", js_recvfrom, 0, JSTYPE_OBJECT, JSDOCSTR("[binary=false] [,maxlen=512 or int_size=4]")
, JSDOCSTR("Receive data (string or integer) from a socket (typically UDP)"
"<p>returns object with <i>ip_address</i> and <i>port</i> of sender along with <i>data</i> properties"
"<p><i>binary</i> defaults to <tt>false</tt>, <i>maxlen</i> defaults to 512 chars, <i>int_size</i> defaults to 4 bytes (32-bits)")
, 311},
{"readBin", js_recvbin, 0, JSTYPE_ALIAS },
{"recvBin", js_recvbin, 0, JSTYPE_NUMBER, JSDOCSTR("[bytes=4]")
, JSDOCSTR("Receive a binary integer from the socket, default number of bytes is 4 (32-bits)")
, 311},
{"getoption", js_getsockopt, 1, JSTYPE_NUMBER, JSDOCSTR("option")
, JSDOCSTR("Get socket option value, option may be socket option name "
"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
, 310},
{"setoption", js_setsockopt, 2, JSTYPE_BOOLEAN, JSDOCSTR("option, value")
, JSDOCSTR("Set socket option value, option may be socket option name "
"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
, 310},
{"ioctl", js_ioctlsocket, 1, JSTYPE_NUMBER, JSDOCSTR("command [,argument=0]")
, JSDOCSTR("Send socket IOCTL (advanced)")
, 310},
{"poll", js_poll, 1, JSTYPE_NUMBER, JSDOCSTR("[timeout=0] [,write=false]")
, JSDOCSTR("Poll socket for read or write ability (default is <i>read</i>), "
"default timeout value is 0.0 seconds (immediate timeout) "
"returns -1 on error, 0 if the timeout passes without the event triggering, and 1 if the socket is ready")
, 310},
{"on", js_on, 2, JSTYPE_NUMBER, JSDOCSTR("('read' | 'write'), callback")
, JSDOCSTR("Execute callback whenever socket is readable/writable. Returns an id to be passed to <tt>js.clearOn()</tt>")
, 31900},
{"once", js_once, 2, JSTYPE_NUMBER, JSDOCSTR("('read' | 'write'), callback")
, JSDOCSTR("Execute callback next time socket is readable/writable Returns and id to be passed to <tt>js.clearOnce()</tt>")
, 31900},
{"clearOn", js_clearOn, 2, JSTYPE_NUMBER, JSDOCSTR("('read' | 'write'), id")
, JSDOCSTR("Remove callback installed by Socket.on()")
, 31900},
{"clearOnce", js_clearOnce, 2, JSTYPE_NUMBER, JSDOCSTR("('read' | 'write'), id")
, JSDOCSTR("Remove callback installed by Socket.once()")
, 31900},
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id)
char* name = NULL;
JSBool ret;
if (id != JSID_VOID && id != JSID_EMPTY) {
if (JSVAL_IS_STRING(idval)) {
JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
HANDLE_PENDING(cx, name);
ret = js_SyncResolve(cx, obj, name, js_socket_properties, js_socket_functions, NULL, 0);
if (name)
}
static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj)
{
return js_socket_resolve(cx, obj, JSID_VOID);
JSClass js_socket_class = {
"Socket" /* name */
, JSCLASS_HAS_PRIVATE /* flags */
, JS_PropertyStub /* addProperty */
, JS_PropertyStub /* delProperty */
, js_socket_get /* getProperty */
, js_socket_set /* setProperty */
, js_socket_enumerate /* enumerate */
, js_socket_resolve /* resolve */
, JS_ConvertStub /* convert */
, js_finalize_socket /* finalize */
JSClass js_connected_socket_class = {
"ConnectedSocket" /* name */
, JSCLASS_HAS_PRIVATE /* flags */
, JS_PropertyStub /* addProperty */
, JS_PropertyStub /* delProperty */
, js_socket_get /* getProperty */
, js_socket_set /* setProperty */
, js_socket_enumerate /* enumerate */
, js_socket_resolve /* resolve */
, JS_ConvertStub /* convert */
, js_finalize_socket /* finalize */
};
JSClass js_listening_socket_class = {
"ListeningSocket" /* name */
, JSCLASS_HAS_PRIVATE /* flags */
, JS_PropertyStub /* addProperty */
, JS_PropertyStub /* delProperty */
, js_socket_get /* getProperty */
, js_socket_set /* setProperty */
, js_socket_enumerate /* enumerate */
, js_socket_resolve /* resolve */
, JS_ConvertStub /* convert */
, js_finalize_socket /* finalize */
static bool js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type)
{
size_t i;
size_t count = 0;
jsval val;
JSObject* array;
socket_option_t* options;
if ((options = getSocketOptionList()) == NULL)
if ((array = JS_NewArrayObject(cx, 0, NULL)) == NULL)
if (!JS_DefineProperty(cx, obj, "option_list", OBJECT_TO_JSVAL(array)
, NULL, NULL, JSPROP_ENUMERATE))
for (i = 0; options[i].name != NULL; i++) {
if (options[i].type && options[i].type != type)
continue;
val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, options[i].name));
JS_SetElement(cx, array, count++, &val);
}
}
/* Socket Constructor (creates socket descriptor) */
JSObject* js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock, CRYPT_CONTEXT session)
JSObject* obj;
js_socket_private_t* p;
int type = SOCK_STREAM;
socklen_t len;
obj = JS_NewObject(cx, &js_socket_class, NULL, NULL);
getsockopt(sock, SOL_SOCKET, SO_TYPE, (void*)&type, &len);
if (!js_DefineSocketOptionsArray(cx, obj, type))
if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL)
memset(p, 0, sizeof(js_socket_private_t));
p->sock = sock;
p->external = TRUE;
p->network_byte_order = TRUE;
p->session = session;
p->tls_minver = 102;
len = sizeof(p->remote_addr);
if (getpeername(p->sock, &p->remote_addr.addr, &len) == 0)
p->is_connected = TRUE;
if (!JS_SetPrivate(cx, obj, p)) {
dbprintf(false, p, "object created");
handle_addrs(char *host, struct sockaddr_in *addr4, socklen_t *addr4len, struct sockaddr_in6 *addr6, socklen_t *addr6len)
char * p, *p2;
struct addrinfo hints;
struct addrinfo * res = NULL;
struct addrinfo * cur;
int i;
// First, clean up for host:port style...
p = strrchr(host, ':');
/*
* If there isn't a [, and the first and last colons aren't the same
* it's assumed to be an IPv6 address
*/
if (host[0] != '[' && p != NULL && strchr(host, ':') != p)
p = NULL;
if (host[0] == '[') {
p2 = strrchr(host, ']');
if (p2)
*p2 = 0;
if (p2 > p)
p = NULL;
if (p != NULL)
*p = 0;
addr4->sin_addr.s_addr = ia;
*addr4len = sizeof(struct sockaddr_in);
}
return TRUE;
}
if (inet_ptoaddr(host, &ia6, sizeof(ia6)) != NULL) {
addr6->sin6_addr = ia6.in6.sin6_addr;
*addr6len = sizeof(struct sockaddr_in6);
}
return TRUE;
}
// So it's a hostname... resolve it into addr4 and addr6 if possible.
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
if ((i = getaddrinfo(host, NULL, &hints, &res)) != 0)
for (cur = res; cur && (*addr4len == 0 || *addr6len == 0); cur = cur->ai_next) {
switch (cur->ai_family) {
case AF_INET:
addr4->sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
*addr4len = sizeof(struct sockaddr_in);
}
break;
case AF_INET6:
addr6->sin6_addr = ((struct sockaddr_in6 *)cur->ai_addr)->sin6_addr;
*addr6len = sizeof(struct sockaddr_in6);
}
break;
}
}
freeaddrinfo(res);
return TRUE;
}
static JSBool
js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj;
JSObject * ao;
jsval * argv = JS_ARGV(cx, arglist);
int32 type = SOCK_STREAM; /* default = TCP */
int32 domain = PF_UNSPEC; /* default = IPvAny */
int32 proto = IPPROTO_IP;
char* protocol = NULL;
uintN i;
js_socket_private_t* p = NULL;
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
jsval v;
struct addrinfo hints;
struct addrinfo * res = NULL;
struct addrinfo * cur;
char * host = NULL;
uint16_t port;
char pstr[6];
jsrefcount rc;
int nonblock;
scfg_t * scfg;
char error[256];
uint16_t bindport = 0;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
socklen_t addr4len;
socklen_t addr6len;
struct sockaddr * addr;
socklen_t * addrlen;
bool sockbind = false;
jsuint count;
int32 timeout = 10;
scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
if (scfg == NULL) {
JS_ReportError(cx, "Unable to get private runtime");
return JS_FALSE;
}
if (argc < 2) {
JS_ReportError(cx, "At least two arguments required (hostname and port)");
return JS_FALSE;
}
// Optional arguments in an object...
if (argc > 2) {
if (!JS_ValueToObject(cx, argv[2], &obj)) {
JS_ReportError(cx, "Invalid third argument");
return JS_FALSE;
}
if (JS_GetProperty(cx, obj, "domain", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &domain)) {
JS_ReportError(cx, "Invalid domain property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "type", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &type)) {
JS_ReportError(cx, "Invalid type property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "proto", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &proto)) {
JS_ReportError(cx, "Invalid proto property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "timeout", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &timeout)) {
JS_ReportError(cx, "Invalid timeout property");
return JS_FALSE;
}
}
if (JS_GetProperty(cx, obj, "protocol", &v) && !JSVAL_IS_VOID(v)) {
JSVALUE_TO_MSTRING(cx, v, protocol, NULL);
HANDLE_PENDING(cx, protocol);
}
if (JS_GetProperty(cx, obj, "bindport", &v) && !JSVAL_IS_VOID(v)) {
bindport = js_port(cx, v, type);
memset(&addr4, 0, sizeof(addr4));
addr4.sin_family = AF_INET;
addr4.sin_addr.s_addr = INADDR_ANY;
addr4.sin_port = htons(bindport);
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_addr = in6addr_any;
addr6.sin6_port = htons(bindport);
sockbind = TRUE;
}
if (JS_GetProperty(cx, obj, "bindaddrs", &v) && !JSVAL_IS_VOID(v)) {
if (!sockbind) {
memset(&addr4, 0, sizeof(addr4));
addr4.sin_family = AF_INET;
addr4.sin_addr.s_addr = INADDR_ANY;
addr4.sin_port = htons(bindport);
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_addr = in6addr_any;
addr6.sin6_port = htons(bindport);
sockbind = TRUE;
}
if (JSVAL_IS_OBJECT(v)) {
ao = JSVAL_TO_OBJECT(v);
if (ao == NULL || !JS_IsArrayObject(cx, ao)) {
JS_ReportError(cx, "Invalid bindaddrs list");
goto fail;
}
if (!JS_GetArrayLength(cx, ao, &count)) {
JS_ReportError(cx, "Unable to get bindaddrs length");
goto fail;
}
for (i = 0; i < count; i++) {
if (!JS_GetElement(cx, ao, i, &v)) {
JS_ReportError(cx, "Invalid bindaddrs entry");
goto fail;
}
JSVALUE_TO_MSTRING(cx, v, host, NULL);
HANDLE_PENDING(cx, host);
rc = JS_SUSPENDREQUEST(cx);
if (!handle_addrs(host, &addr4, &addr4len, &addr6, &addr6len)) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Unparsable bindaddrs entry");
goto fail;
}
FREE_AND_NULL(host);
JS_RESUMEREQUEST(cx, rc);
}
}
else {
JSVALUE_TO_MSTRING(cx, v, host, NULL);
HANDLE_PENDING(cx, host);
rc = JS_SUSPENDREQUEST(cx);
if (!handle_addrs(host, &addr4, &addr4len, &addr6, &addr6len)) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Unparsable bindaddrs entry");
goto fail;
}
FREE_AND_NULL(host);
JS_RESUMEREQUEST(cx, rc);
}
}
JSVALUE_TO_MSTRING(cx, argv[0], host, NULL);
HANDLE_PENDING(cx, host);
port = js_port(cx, argv[1], type);
if (port == 0) {
JS_ReportError(cx, "Invalid port");
goto fail;
obj = JS_NewObject(cx, &js_socket_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL) {
JS_ReportError(cx, "malloc failed");
goto fail;
}
memset(p, 0, sizeof(js_socket_private_t));
rc = JS_SUSPENDREQUEST(cx);
sprintf(pstr, "%hu", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = domain;
hints.ai_socktype = type;
hints.ai_protocol = proto;
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
if ((i = getaddrinfo(host, pstr, &hints, &res)) != 0) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, gai_strerror(i));
goto fail;
}
p->sock = INVALID_SOCKET;
for (cur = res; cur && p->sock == INVALID_SOCKET; cur = cur->ai_next) {
if (p->sock == INVALID_SOCKET) {
p->sock = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if (p->sock == INVALID_SOCKET)
continue;
if (set_socket_options(scfg, p->sock, protocol, error, sizeof(error))) {
freeaddrinfo(res);
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, error);
goto fail;
}
if (sockbind) {
addr = NULL;
switch (cur->ai_family) {
addr = (struct sockaddr *)&addr4;
break;
case PF_INET6:
addr = (struct sockaddr *)&addr6;
if (addr == NULL)
continue;
if (bind(p->sock, addr, *addrlen) != 0) {
lprintf(LOG_WARNING, "Unable to bind to local address");
closesocket(p->sock);
p->sock = INVALID_SOCKET;
continue;
}
}
/* Set to non-blocking for the connect */
ioctlsocket(p->sock, FIONBIO, &nonblock);
}
if (connect(p->sock, cur->ai_addr, cur->ai_addrlen)) {
switch (SOCKET_ERRNO) {
case EINPROGRESS:
case EINTR:
case EAGAIN:
#if (EAGAIN != EWOULDBLOCK)
case EWOULDBLOCK:
#endif
for (; p->sock != INVALID_SOCKET;) {
// TODO: Do clever timeout stuff here.
if (!socket_recvdone(p->sock, 0))
p->sock = INVALID_SOCKET;
p->sock = INVALID_SOCKET;
}
}
connected:
memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
break;
default:
closesocket(p->sock);
p->sock = INVALID_SOCKET;
continue;
}
}
}
freeaddrinfo(res);
JS_RESUMEREQUEST(cx, rc);
if (p->sock == INVALID_SOCKET) {
JS_ReportError(cx, "Unable to connect to %s:%u", host, port);
goto fail;
}
ioctlsocket(p->sock, FIONBIO, (ulong*)&(p->nonblocking));
call_socket_open_callback(TRUE);
free(protocol);
p->hostname = host;
p->type = type;
p->network_byte_order = TRUE;
p->unflushed = 0;
p->is_connected = TRUE;
p->tls_minver = 102;
if (!JS_SetPrivate(cx, obj, p)) {
JS_ReportError(cx, "JS_SetPrivate failed");
}
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx, obj, "Class used for outgoing TCP/IP socket communications", 317);
js_DescribeSyncConstructor(cx, obj, "To create a new ConnectedSocket object: "
"<tt>load('sockdefs.js'); var s = new ConnectedSocket(<i>hostname</i>, <i>port</i>, {domain:<i>domain</i>, proto:<i>proto</i> ,type:<i>type</i>, protocol:<i>protocol</i>, timeout:<i>timeout</i>, bindport:<i>port</i>, bindaddrs:<i>bindaddrs</i>})</tt><br>"
"where <i>domain</i> (optional) = <tt>PF_UNSPEC</tt> (default) for IPv4 or IPv6, <tt>PF_INET</tt> for IPv4, or <tt>PF_INET6</tt> for IPv6<br>"
"<i>proto</i> (optional) = <tt>IPPROTO_IP</tt> (default)<br>"
"<i>type</i> (optional) = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
"<i>protocol</i> (optional) = the name of the protocol or service the socket is to be used for<br>"
"<i>timeout</i> (optional) = 10 (default) the number of seconds to wait for each connect() call to complete.<br>"
"<i>bindport</i> (optional) = the name or number of the source port to bind to<br>"
"<i>bindaddrs</i> (optional) = the name or number of the source addresses to bind to. The first of each IPv4 or IPv6 type is used for that family.<br>"
);
JS_DefineProperty(cx, obj, "_dont_document", JSVAL_TRUE, NULL, NULL, JSPROP_READONLY);
if (!js_DefineSocketOptionsArray(cx, obj, type))
dbprintf(false, p, "object constructed");
fail:
if (p)
free(p);
if (protocol)
free(protocol);
if (host)
free(host);
return JS_FALSE;
}
struct ls_cb_data {
char *protocol;
scfg_t *scfg;
};
static void
ls_cb(SOCKET sock, void *cbptr)
{
struct ls_cb_data *cb = cbptr;
call_socket_open_callback(TRUE);
if (set_socket_options(cb->scfg, sock, cb->protocol, error, sizeof(error)))
lprintf(LOG_ERR, "%04d !ERROR %s", sock, error);
}
static JSBool
js_listening_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj;
jsval * argv = JS_ARGV(cx, arglist);
int32 type = SOCK_STREAM; /* default = TCP */
int32 domain = PF_UNSPEC; /* default = IPvAny */
int32 proto = IPPROTO_IP;
int retry_count = 0;
int retry_delay = 15;
char* protocol = NULL;
char * interface = NULL;
js_socket_private_t* p = NULL;
jsval v;
uint16_t port;
jsrefcount rc;
scfg_t * scfg;
struct xpms_set * set = NULL;
struct ls_cb_data cb;
jsuint count;
int i;
scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
if (scfg == NULL) {
JS_ReportError(cx, "Unable to get private runtime");
goto fail;
}
cb.scfg = scfg;
if (argc < 3) {
JS_ReportError(cx, "At least three arguments required (interfaces, port, and protocol)");
goto fail;
}
if (argc > 3) {
if (!JS_ValueToObject(cx, argv[3], &obj)) {
JS_ReportError(cx, "Invalid fourth argument");
goto fail;
}
if (JS_GetProperty(cx, obj, "domain", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &domain)) {
JS_ReportError(cx, "Invalid domain property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "type", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &type)) {
JS_ReportError(cx, "Invalid type property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "proto", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &proto)) {
JS_ReportError(cx, "Invalid proto property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "retry_count", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &retry_count)) {
JS_ReportError(cx, "Invalid retry_count property");
goto fail;
}
}
if (JS_GetProperty(cx, obj, "retry_delay", &v) && !JSVAL_IS_VOID(v)) {
if (!JS_ValueToInt32(cx, v, &retry_delay)) {
JS_ReportError(cx, "Invalid retry_delay property");
goto fail;
}
}
}
obj = NULL;
port = js_port(cx, argv[1], type);
JSVALUE_TO_MSTRING(cx, argv[2], protocol, NULL);
HANDLE_PENDING(cx, protocol);
cb.protocol = protocol;
if (JSVAL_IS_OBJECT(argv[0])) {
obj = JSVAL_TO_OBJECT(argv[0]);
if (obj == NULL || !JS_IsArrayObject(cx, obj)) {
JS_ReportError(cx, "Invalid interface list");
goto fail;
}
}
set = xpms_create(retry_count, retry_delay, lprintf);
if (set == NULL) {
JS_ReportError(cx, "Unable to create socket set");
goto fail;
}
if (obj == NULL) {
JSVALUE_TO_MSTRING(cx, argv[0], interface, NULL);
HANDLE_PENDING(cx, interface);
rc = JS_SUSPENDREQUEST(cx);

Rob Swindell
committed
if (!xpms_add(set, domain, type, proto, interface, port, protocol, NULL, ls_cb, NULL, &cb)) {
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Unable to add host to socket set");
goto fail;
}
JS_RESUMEREQUEST(cx, rc);
}
else {
if (!JS_GetArrayLength(cx, obj, &count)) {
JS_ReportError(cx, "zero-length array");
for (i = 0; (jsuint)i < count; i++) {
if (!JS_GetElement(cx, obj, i, &v)) {
lprintf(LOG_WARNING, "Unable to get element %d from interface array", i);
continue;
}
JSVALUE_TO_MSTRING(cx, v, interface, NULL);
HANDLE_PENDING(cx, interface);
rc = JS_SUSPENDREQUEST(cx);

Rob Swindell
committed
if (!xpms_add(set, domain, type, proto, interface, port, protocol, NULL, ls_cb, NULL, &cb)) {
free(interface);
JS_RESUMEREQUEST(cx, rc);
JS_ReportError(cx, "Unable to add host to socket set");
goto fail;
}
free(interface);
JS_RESUMEREQUEST(cx, rc);
}
}
obj = JS_NewObject(cx, &js_socket_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL) {
JS_ReportError(cx, "malloc failed");
memset(p, 0, sizeof(js_socket_private_t));
p->type = type;
p->set = set;
p->sock = INVALID_SOCKET;
p->external = TRUE;
p->network_byte_order = TRUE;
p->unflushed = 0;
p->local_port = port;
p->tls_minver = 102;
if (!JS_SetPrivate(cx, obj, p)) {
JS_ReportError(cx, "JS_SetPrivate failed");
if (!js_DefineSocketOptionsArray(cx, obj, type)) {
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx, obj, "Class used for incoming TCP/IP socket communications", 317);
js_DescribeSyncConstructor(cx, obj, "To create a new ListeningSocket object: "
"<tt>load('sockdefs.js'); var s = new ListeningSocket(<i>interface</i>, <i>port</i> ,<i>protocol</i>, {domain:<i>domain</i>, type:<i>type</i>, proto:<i>proto</i>, retry_count:<i>retry_count</i>, retry_delay:<i>retry_delay</i>})</tt><br>"
"where <i>interface</i> = A array or strings or a single string of hostnames or address optionally including a :port suffix<br>"
"<i>port</i> = a port to use when the interface doesn't specify one<br>"
"<i>protocol</i> = protocol name, used for socket options and logging.<br>"
"<i>domain</i> (optional) = <tt>PF_UNSPEC</tt> (default) for IPv4 or IPv6, <tt>AF_INET</tt> for IPv4, or <tt>AF_INET6</tt> for IPv6<br>"
"<i>proto</i> (optional) = <tt>IPPROTO_IP</tt> (default)<br>"
"<i>type</i> (optional) = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
"<i>retry_count</i> (optional) = <tt>0</tt> (default) number of times to retry binding<br>"
"and <i>retry_delay</i> (optional) = <tt>15</tt> (default) seconds to wait before a retry<br>"
);
JS_DefineProperty(cx, obj, "_dont_document", JSVAL_TRUE, NULL, NULL, JSPROP_READONLY);
dbprintf(false, p, "object constructed");
return JS_FALSE;
}
static JSBool
js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj;
jsval * argv = JS_ARGV(cx, arglist);
int32 type = SOCK_STREAM; /* default = TCP */
int domain = AF_INET; /* default = IPv4 */
uintN i;
char* protocol = NULL;
bool from_descriptor = false;
i = 0;
if (JSVAL_IS_BOOLEAN(argv[i]) && argc > 1) {
from_descriptor = JSVAL_TO_BOOLEAN(argv[i]);
i++;
if (from_descriptor) {
uint32 sock;
if (!JS_ValueToECMAUint32(cx, argv[i], &sock)) {
JS_ReportError(cx, "Failed to convert socket descriptor to uint32");
return JS_FALSE;
obj = js_CreateSocketObjectWithoutParent(cx, sock, -1);
if (obj == NULL) {
JS_ReportError(cx, "Failed to create external socket object");
return JS_FALSE;
}
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
FREE_AND_NULL(protocol);
return JS_TRUE;
}
for (; i < argc; i++) {
if (JSVAL_IS_NUMBER(argv[i])) {
JS_ValueToInt32(cx, argv[i], &type);
else if (JSVAL_IS_BOOLEAN(argv[i])) {
if (argv[i] == JSVAL_TRUE)
domain = AF_INET6;
}
else if (JSVAL_IS_STRING(argv[i])) {
if (protocol == NULL) {
JSVALUE_TO_MSTRING(cx, argv[i], protocol, NULL);
HANDLE_PENDING(cx, protocol);
}
}
obj = JS_NewObject(cx, &js_socket_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL) {
JS_ReportError(cx, "malloc failed");
if (protocol)
}
memset(p, 0, sizeof(js_socket_private_t));
if ((p->sock = open_socket(domain, type, protocol)) == INVALID_SOCKET) {
JS_ReportError(cx, "open_socket failed with error %d", SOCKET_ERRNO);
if (protocol)
}
p->type = type;
p->network_byte_order = TRUE;
p->tls_minver = 102;
if (!JS_SetPrivate(cx, obj, p)) {
JS_ReportError(cx, "JS_SetPrivate failed");
}
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx, obj, "Class used for TCP/IP socket communications", 310);
js_DescribeSyncConstructor(cx, obj, "To create a new Socket object: "
"<tt>load('sockdefs.js'); var s = new Socket(<i>type</i>, <i>protocol</i> ,<i>ipv6</i>=false)</tt><br>"
"where <i>type</i> = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
"and <i>protocol</i> (optional) = the name of the protocol or service the socket is to be used for<br>"
"To create a socket from an existing socket descriptor: "
"<tt>var s = new Socket(true, <i>descriptor</i>)</tt><br>"
"where <i>descriptor</i> is the numerical value of an existing valid socket descriptor"
);
js_CreateArrayOfStrings(cx, obj, "_property_desc_list", socket_prop_desc, JSPROP_READONLY);
#endif
if (!js_DefineSocketOptionsArray(cx, obj, type))
dbprintf(false, p, "object constructed");
}
JSObject* js_CreateSocketClass(JSContext* cx, JSObject* parent)
JSObject* sockobj;
JSObject* sockproto;
JSObject* csockobj;
JSObject* lsockobj;
jsval val;
JSObject* constructor;
, &js_socket_class
, js_socket_constructor
, 0 /* number of constructor args */
, NULL /* props, specified in constructor */
, NULL /* funcs, specified in constructor */
, NULL, NULL);
if (sockobj == NULL)
return sockobj;
if (JS_GetProperty(cx, parent, js_socket_class.name, &val) && !JSVAL_NULL_OR_VOID(val)) {
JS_ValueToObject(cx, val, &constructor);
JS_DefineProperty(cx, constructor, "AF_INET", INT_TO_JSVAL(AF_INET), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "AF_INET6", INT_TO_JSVAL(AF_INET6), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "IPPROTO_IP", INT_TO_JSVAL(IPPROTO_IP), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "PF_INET", INT_TO_JSVAL(PF_INET), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "PF_INET6", INT_TO_JSVAL(PF_INET6), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "PF_UNSPEC", INT_TO_JSVAL(PF_UNSPEC), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "SOCK_DGRAM", INT_TO_JSVAL(SOCK_STREAM), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
JS_DefineProperty(cx, constructor, "SOCK_STREAM", INT_TO_JSVAL(SOCK_STREAM), NULL, NULL
, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
sockproto = JS_GetPrototype(cx, sockobj);
csockobj = JS_InitClass(cx, parent, sockproto
, &js_connected_socket_class
, js_connected_socket_constructor
, 2 /* number of constructor args */
, NULL /* props, specified in constructor */
, NULL /* funcs, specified in constructor */
, NULL, NULL);
lsockobj = JS_InitClass(cx, parent, sockproto
, &js_listening_socket_class
, js_listening_socket_constructor
, 2 /* number of constructor args */
, NULL /* props, specified in constructor */
, NULL /* funcs, specified in constructor */
, NULL, NULL);
JSObject* js_CreateSocketObject(JSContext* cx, JSObject* parent, char *name, SOCKET sock, CRYPT_CONTEXT session)
obj = js_CreateSocketObjectWithoutParent(cx, sock, session);
JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY);
JSObject* js_CreateSocketObjectFromSet(JSContext* cx, JSObject* parent, char *name, struct xpms_set *set)
JSObject* obj;
js_socket_private_t* p;
int type = SOCK_STREAM;
socklen_t len;
obj = JS_DefineObject(cx, parent, name, &js_socket_class, NULL
, JSPROP_ENUMERATE | JSPROP_READONLY);
if (set->sock_count < 1)
getsockopt(set->socks[0].sock, SOL_SOCKET, SO_TYPE, (void*)&type, &len);
if (!js_DefineSocketOptionsArray(cx, obj, type))
if ((p = (js_socket_private_t*)malloc(sizeof(js_socket_private_t))) == NULL)
memset(p, 0, sizeof(js_socket_private_t));
p->set = set;
p->sock = INVALID_SOCKET;
p->external = TRUE;
p->network_byte_order = TRUE;
p->tls_minver = 102;
if (!JS_SetPrivate(cx, obj, p)) {
dbprintf(TRUE, p, "JS_SetPrivate failed");
dbprintf(false, p, "object created");
#endif /* JAVSCRIPT */