Skip to content
Snippets Groups Projects
js_socket.c 95.9 KiB
Newer Older
			JS_RESUMEREQUEST(cx, rc);
			JS_ReportError(cx, __FUNCTION__ ": 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
deuce's avatar
deuce committed
	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->set->socks[i].sock;
		}
	}
	else {
		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);
Deucе's avatar
Deucе committed
#endif
Deucе's avatar
Deucе committed
	dbprintf(FALSE, p, "poll: select/poll returned %d (errno %d)"
	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(result));
Deucе's avatar
Deucе committed
static js_callback_t *
js_get_callback(JSContext *cx)
{
	JSObject*	scope = JS_GetScopeChain(cx);
	JSObject*	pscope;
	jsval val = JSVAL_NULL;
	JSObject *pjs_obj;

	pscope = scope;
	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, __FUNCTION__ ": Walked to global, no js object!");
Deucе's avatar
Deucе committed
			return NULL;
		}
	}
	pjs_obj = JSVAL_TO_OBJECT(val);
	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, __FUNCTION__ ": error allocating %lu bytes", sizeof(*ev));
Deucе's avatar
Deucе committed
		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) {
		return(JS_FALSE);
	}

	/*
	 * 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.
	 */
Deucе's avatar
Deucе committed
	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);
	enum js_event_type et;
	char operation[16];
	size_t slen;
Deucе's avatar
Deucе committed
	js_callback_t*	cb;
Deucе's avatar
Deucе committed

	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;
	}

Deucе's avatar
Deucе committed
	cb = js_get_callback(cx);
	if (cb == NULL) {
		return JS_FALSE;
	}
	return js_clear_event(cx, arglist, cb, et, 1);
Deucе's avatar
Deucе committed
}

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);
rswindell's avatar
rswindell committed
enum {
	 SOCK_PROP_LAST_ERROR
	,SOCK_PROP_ERROR_STR
rswindell's avatar
rswindell committed
	,SOCK_PROP_IS_CONNECTED
	,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
deuce's avatar
deuce committed
	,SOCK_PROP_SSL_SESSION
deuce's avatar
deuce committed
	,SOCK_PROP_SSL_SERVER
static char* socket_prop_desc[] = {
deuce's avatar
deuce committed
	/* Regular properties */
Rob Swindell's avatar
Rob Swindell committed
	 "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."
Rob Swindell's avatar
Rob Swindell committed
	,"Alias for is_writeable"
	,"<tt>true</tt> if data is waiting to be read from socket - <small>READ ONLY</small>"
Rob Swindell's avatar
Rob Swindell committed
	,"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>)"
Rob Swindell's avatar
Rob Swindell committed
	,"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"
Rob Swindell's avatar
Rob Swindell committed
	,"Array of socket option names supported by the current platform"
	,NULL
rswindell's avatar
rswindell committed
};
#endif
rswindell's avatar
rswindell committed

static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
rswindell's avatar
rswindell committed
{
deuce's avatar
deuce committed
	js_socket_private_t*	p;
deuce's avatar
deuce committed
	jsrefcount	rc;
	char* estr;
	int level;
deuce's avatar
deuce committed
	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
		// Prototype access
		return(JS_TRUE);
	JS_IdToValue(cx, id, &idval);
	tiny = JSVAL_TO_INT(idval);
	dbprintf(FALSE, p, "setting property %d",tiny);

	switch(tiny) {
		case SOCK_PROP_DEBUG:
			JS_ValueToBoolean(cx,*vp,&(p->debug));
		case SOCK_PROP_DESCRIPTOR:
deuce's avatar
deuce committed
			if(p->session != -1) {
				cryptDestroySession(p->session);
				p->session=-1;
			}
			if(JS_ValueToInt32(cx,*vp,&i))
				p->sock = i;
			if(JS_ValueToInt32(cx,*vp,&i))
				p->last_error = i;
		case SOCK_PROP_NONBLOCKING:
			JS_ValueToBoolean(cx,*vp,&(p->nonblocking));
rswindell's avatar
rswindell committed
			ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
		case SOCK_PROP_NETWORK_ORDER:
			JS_ValueToBoolean(cx,*vp,&(p->network_byte_order));
			break;
		case SOCK_PROP_IS_WRITEABLE:
			JS_ValueToBoolean(cx,*vp,&b);
			if(!b)
				shutdown(p->sock,SHUT_WR);
			break;
deuce's avatar
deuce committed
		case SOCK_PROP_SSL_SERVER:
deuce's avatar
deuce committed
		case SOCK_PROP_SSL_SESSION:
			JS_ValueToBoolean(cx,*vp,&b);
			rc=JS_SUSPENDREQUEST(cx);
			if(b) {
				if(p->session==-1) {
deuce's avatar
deuce committed
					int ret = CRYPT_ERROR_NOTINITED;
deuce's avatar
deuce committed

deuce's avatar
deuce committed
					if(do_cryptInit()) {
deuce's avatar
deuce committed
						if((ret=cryptCreateSession(&p->session, CRYPT_UNUSED, tiny == SOCK_PROP_SSL_SESSION ? CRYPT_SESSION_SSL: CRYPT_SESSION_SSL_SERVER))==CRYPT_OK) {
deuce's avatar
deuce committed
							ulong nb=0;
							ioctlsocket(p->sock,FIONBIO,&nb);
							nb=1;
							setsockopt(p->sock,IPPROTO_TCP,TCP_NODELAY,(char*)&nb,sizeof(nb));
deuce's avatar
deuce committed
							if((ret=do_cryptAttribute(p->session, CRYPT_SESSINFO_NETWORKSOCKET, p->sock))==CRYPT_OK) {
								// Reduced compliance checking... required for acme-staging-v02.api.letsencrypt.org
								do_cryptAttribute(p->session, CRYPT_OPTION_CERT_COMPLIANCELEVEL, CRYPT_COMPLIANCELEVEL_REDUCED);
deuce's avatar
deuce committed
								if (tiny == SOCK_PROP_SSL_SESSION) {
									// TODO: Make this configurable
									do_cryptAttribute(p->session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_NAMEVERIFY);
deuce's avatar
deuce committed
									ret=do_cryptAttributeString(p->session, CRYPT_SESSINFO_SERVER_NAME, p->hostname, strlen(p->hostname));
									scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));

									if (scfg == NULL) {
										ret = CRYPT_ERROR_NOTAVAIL;
									}
deuce's avatar
deuce committed
									else {
										if (get_ssl_cert(scfg, &estr, &level) == -1) {
											if (estr) {
												lprintf(level, "%04d %s", p->sock, estr);
												free_crypt_attrstr(estr);
										if (scfg->tls_certificate == -1)
											ret = CRYPT_ERROR_NOTAVAIL;
										else {
											ret = cryptSetAttribute(p->session, CRYPT_SESSINFO_PRIVATEKEY, scfg->tls_certificate);
											if (ret != CRYPT_OK) {
												unlock_ssl_cert();
												GCES(ret, p, estr, "setting private key");
deuce's avatar
deuce committed
									}
deuce's avatar
deuce committed
								}
								if(ret==CRYPT_OK) {
									if((ret=do_cryptAttribute(p->session, CRYPT_SESSINFO_ACTIVE, 1))!=CRYPT_OK) {
										GCES(ret, p, estr, "setting session active");
									if (tiny == SOCK_PROP_SSL_SERVER)
										unlock_ssl_cert();
deuce's avatar
deuce committed
							}
						}
						else
							GCESH(ret, p->sock, CRYPT_UNUSED, estr, "creating session");
deuce's avatar
deuce committed
					}
					if (ret != CRYPT_OK) {
						if (p->session != -1)
							cryptDestroySession(p->session);
						p->session=-1;
						ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
Deucе's avatar
Deucе committed
						do_js_close(cx, p, false);
deuce's avatar
deuce committed
					}
				}
			}
			else {
				if(p->session != -1) {
					cryptDestroySession(p->session);
					p->session=-1;
					ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
Deucе's avatar
Deucе committed
					do_js_close(cx, p, false);
deuce's avatar
deuce committed
				}
			}
			JS_RESUMEREQUEST(cx, rc);
			break;
rswindell's avatar
rswindell committed
	return(JS_TRUE);
}

static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
rswindell's avatar
rswindell committed
{
rswindell's avatar
rswindell committed
    jsint       tiny;
deuce's avatar
deuce committed
	js_socket_private_t*	p;
	JSString*	js_str;
deuce's avatar
deuce committed
	union xp_sockaddr	addr;
	socklen_t	len=sizeof(addr);
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	char		str[256];
rswindell's avatar
rswindell committed

deuce's avatar
deuce committed
	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
		// Protoype access
		return(JS_TRUE);
rswindell's avatar
rswindell committed

    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
rswindell's avatar
rswindell committed

#if 0 /* just too much */
rswindell's avatar
rswindell committed
	dbprintf(FALSE, p, "getting property %d",tiny);
rswindell's avatar
rswindell committed
	switch(tiny) {
		case SOCK_PROP_LAST_ERROR:
rswindell's avatar
rswindell committed
			break;
		case SOCK_PROP_ERROR_STR:
			if((js_str=JS_NewStringCopyZ(cx, socket_strerror(p->last_error, str, sizeof(str))))==NULL)
				return JS_FALSE;
			*vp = STRING_TO_JSVAL(js_str);
			break;
rswindell's avatar
rswindell committed
		case SOCK_PROP_IS_CONNECTED:
				*vp = BOOLEAN_TO_JSVAL(socket_check(p->sock,NULL,NULL,0));
rswindell's avatar
rswindell committed
			break;
deuce's avatar
deuce committed
			if(p->sock==INVALID_SOCKET && p->set)
				wr = FALSE;
			else
				socket_check(p->sock,NULL,&wr,0);
		case SOCK_PROP_DATA_WAITING:
deuce's avatar
deuce committed
			if(p->sock==INVALID_SOCKET && p->set)
				rd = FALSE;
			else {
				if (p->peeked)
					rd = TRUE;
				else if (p->session != -1)
Deucе's avatar
Deucе committed
					rd = js_socket_peek_byte(cx, p);
			*vp = BOOLEAN_TO_JSVAL(rd);
			break;
		case SOCK_PROP_NREAD:
deuce's avatar
deuce committed
			if(p->sock==INVALID_SOCKET && p->set) {
				*vp = JSVAL_ZERO;
				break;
			}
Deucе's avatar
Deucе committed
				if (js_socket_peek_byte(cx, p))
					*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 = JSVAL_ZERO;
		case SOCK_PROP_DEBUG:
		case SOCK_PROP_DESCRIPTOR:
		case SOCK_PROP_NONBLOCKING:
			*vp = BOOLEAN_TO_JSVAL(p->nonblocking);
			break;
			if(p->sock != INVALID_SOCKET) {
				if(getsockname(p->sock, (struct sockaddr *)&addr,&len)!=0)
					return(JS_FALSE);
deuce's avatar
deuce committed
				inet_addrtop(&addr, str, sizeof(str));
				if((js_str=JS_NewStringCopyZ(cx,str))==NULL)
					return(JS_FALSE);
				*vp = STRING_TO_JSVAL(js_str);
			if(p->local_port != 0) {
				*vp = INT_TO_JSVAL(p->local_port);
			} else if(p->sock != INVALID_SOCKET) {
deuce's avatar
deuce committed
				if(getsockname(p->sock, &addr.addr,&len)!=0)
					return(JS_FALSE);
deuce's avatar
deuce committed
				*vp = INT_TO_JSVAL(inet_addrport(&addr));
			break;
		case SOCK_PROP_REMOTE_IP:
deuce's avatar
deuce committed
				inet_addrtop(&p->remote_addr, str, sizeof(str));
				if((js_str=JS_NewStringCopyZ(cx,str))==NULL)
					return(JS_FALSE);
				*vp = STRING_TO_JSVAL(js_str);
			break;
		case SOCK_PROP_REMOTE_PORT:
deuce's avatar
deuce committed
				*vp = INT_TO_JSVAL(inet_addrport(&p->remote_addr));
		case SOCK_PROP_TYPE:
			*vp = INT_TO_JSVAL(p->type);
			break;
		case SOCK_PROP_FAMILY:
			if(p->sock != INVALID_SOCKET) {
				if(getsockname(p->sock, &addr.addr, &len)!=0)
					return(JS_FALSE);
				*vp = INT_TO_JSVAL(addr.addr.sa_family);
				rc=JS_SUSPENDREQUEST(cx);
			}
			else
				*vp=JSVAL_VOID;
			break;
		case SOCK_PROP_NETWORK_ORDER:
			*vp = BOOLEAN_TO_JSVAL(p->network_byte_order);
deuce's avatar
deuce committed
		case SOCK_PROP_SSL_SESSION:
			*vp = BOOLEAN_TO_JSVAL(p->session != -1);
			break;
deuce's avatar
deuce committed
		case SOCK_PROP_SSL_SERVER:
			*vp = BOOLEAN_TO_JSVAL(p->session != -1 && p->tls_server);
deuce's avatar
deuce committed
			break;
rswindell's avatar
rswindell committed
	return(TRUE);
}

#define SOCK_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
rswindell's avatar
rswindell committed

static jsSyncPropertySpec js_socket_properties[] = {
/*		 name				,tinyid					,flags,				ver	*/

	{	"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 },
deuce's avatar
deuce committed
	{	"ssl_session"		,SOCK_PROP_SSL_SESSION	,JSPROP_ENUMERATE,	316	},
deuce's avatar
deuce committed
	{	"ssl_server"		,SOCK_PROP_SSL_SERVER	,JSPROP_ENUMERATE,	316	},
static jsSyncMethodSpec js_socket_functions[] = {
	{"close",		js_close,		0,	JSTYPE_VOID,	""
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Close (shutdown) the socket immediately")
	{"bind",		js_bind,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("[port] [,ip_address]")
Rob Swindell's avatar
Rob Swindell committed
	,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.0]")
Rob Swindell's avatar
Rob Swindell committed
	,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.0</i> (seconds)")
	{"listen",		js_listen,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Place socket in a state to listen for incoming connections (use before an accept)")
	{"accept",		js_accept,		0,	JSTYPE_OBJECT,	JSDOCSTR("")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Accept an incoming connection, returns a new <i>Socket</i> object representing the new connection")
	{"write",		js_send,		1,	JSTYPE_ALIAS },
	{"send",		js_send,		1,	JSTYPE_NUMBER,	JSDOCSTR("data")
Rob Swindell's avatar
Rob Swindell committed
	,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.")
deuce's avatar
deuce committed
	{"writeln",		js_sendline,		1,	JSTYPE_ALIAS },
	{"sendline",	js_sendline,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("data")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Send a string (AKA write) with a carriage return line feed appended")
deuce's avatar
deuce committed
	,317
	},
	{"sendto",		js_sendto,		3,	JSTYPE_BOOLEAN,	JSDOCSTR("data, address, port")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Send data to a specific host (IP address or host name) and port (number or service name), for UDP sockets")
	{"sendfile",	js_sendfile,	1,	JSTYPE_BOOLEAN,	JSDOCSTR("path/filename")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Send an entire file over the socket")
	{"writeBin",	js_sendbin,		1,	JSTYPE_ALIAS },
	{"sendBin",		js_sendbin,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("value [,bytes=4]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Send a binary integer over the socket, default number of bytes is 4 (32-bits)")
	{"read",		js_recv,		1,	JSTYPE_ALIAS },
	{"recv",		js_recv,		1,	JSTYPE_STRING,	JSDOCSTR("[maxlen=512, [timeout_sec=120]]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Receive a string, default maxlen is 512 characters (AKA read)")
	{"peek",		js_peek,		0,	JSTYPE_STRING,	JSDOCSTR("[maxlen=512]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Receive a string, default maxlen is 512 characters, leaves string in receive buffer (TLS sockets will never return more than one byte)")
	{"readline",	js_recvline,	0,	JSTYPE_ALIAS },
	{"readln",		js_recvline,	0,	JSTYPE_ALIAS },
	{"recvline",	js_recvline,	0,	JSTYPE_STRING,	JSDOCSTR("[maxlen=512] [,timeout=30]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Receive a line-feed terminated string, default maxlen is 512 characters, default timeout is 30 seconds (AKA readline and readln)")
	{"recvfrom",	js_recvfrom,	0,	JSTYPE_OBJECT,	JSDOCSTR("[binary=false] [,maxlen=512 or int_size=4]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Receive data (string or integer) from a socket (typically UDP)"
rswindell's avatar
rswindell committed
	"<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)")
	},
	{"readBin",		js_recvbin,		0,	JSTYPE_ALIAS },
	{"recvBin",		js_recvbin,		0,	JSTYPE_NUMBER,	JSDOCSTR("[bytes=4]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Receive a binary integer from the socket, default number of bytes is 4 (32-bits)")
	{"getoption",	js_getsockopt,	1,	JSTYPE_NUMBER,	JSDOCSTR("option")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Get socket option value, option may be socket option name "
	"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
	{"setoption",	js_setsockopt,	2,	JSTYPE_BOOLEAN,	JSDOCSTR("option, value")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Set socket option value, option may be socket option name "
	"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
	{"ioctl",		js_ioctlsocket,	1,	JSTYPE_NUMBER,	JSDOCSTR("command [,argument=0]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Send socket IOCTL (advanced)")
	{"poll",		js_poll,		1,	JSTYPE_NUMBER,	JSDOCSTR("[timeout=0] [,write=false]")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Poll socket for read or write ability (default is <i>read</i>), "
	"default timeout value is 0.0 seconds (immediate timeout)")
Deucе's avatar
Deucе committed
	{"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>")
Deucе's avatar
Deucе committed
	,31900
Deucе's avatar
Deucе committed
	},
	{"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>")
Deucе's avatar
Deucе committed
	,31900
Deucе's avatar
Deucе committed
	},
	{"clearOn",	js_clearOn,	2,	JSTYPE_NUMBER,	JSDOCSTR("('read' | 'write'), id")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Remove callback installed by Socket.on()")
Deucе's avatar
Deucе committed
	,31900
Deucе's avatar
Deucе committed
	},
	{"clearOnce",	js_clearOnce,	2,	JSTYPE_NUMBER,	JSDOCSTR("('read' | 'write'), id")
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Remove callback installed by Socket.once()")
Deucе's avatar
Deucе committed
	,31900
Deucе's avatar
Deucе committed
	},
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id)
{
	char*			name=NULL;
deuce's avatar
deuce committed
	JSBool			ret;
deuce's avatar
deuce committed
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
deuce's avatar
deuce committed
		JS_IdToValue(cx, id, &idval);
deuce's avatar
deuce committed
		if(JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
			HANDLE_PENDING(cx, name);
deuce's avatar
deuce committed
		}
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	ret=js_SyncResolve(cx, obj, name, js_socket_properties, js_socket_functions, NULL, 0);
	if(name)
		free(name);
	return ret;
}

static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
	return(js_socket_resolve(cx, obj, JSID_VOID));
     "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)
		return(FALSE);

	if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)
		return(FALSE);

	if(!JS_DefineProperty(cx, obj, "option_list", OBJECT_TO_JSVAL(array)
		, NULL, NULL, JSPROP_ENUMERATE))
		return(FALSE);

	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);
	}
	return(TRUE);
}

/* 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);

	if(obj==NULL)
		return(NULL);

	len = sizeof(type);
	getsockopt(sock,SOL_SOCKET,SO_TYPE,(void*)&type,&len);

	if(!js_DefineSocketOptionsArray(cx, obj, type))
		return(NULL);

	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL)
		return(NULL);
	memset(p,0,sizeof(js_socket_private_t));

	p->sock = sock;
	p->external = TRUE;
	p->network_byte_order = TRUE;
deuce's avatar
deuce committed
	p->unflushed = 0;

	if (p->sock != INVALID_SOCKET) {
		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(TRUE, p, "JS_SetPrivate failed");
		return(NULL);
	}

	dbprintf(FALSE, p, "object created");

	return(obj);
}

handle_addrs(char *host, struct sockaddr_in *addr4, socklen_t *addr4len, struct sockaddr_in6 *addr6, socklen_t *addr6len)
{
	in_addr_t ia;
	union xp_sockaddr ia6;
	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]=='[') {
		host++;
		p2=strrchr(host,']');
		if(p2)
			*p2=0;
		if(p2 > p)
			p=NULL;
	}
	if(p!=NULL)
		*p=0;

	ia = inet_addr(host);
	if (ia != INADDR_NONE) {
		if (*addr4len == 0) {
			addr4->sin_addr.s_addr = ia;
			*addr4len = sizeof(struct sockaddr_in);
		}
		return TRUE;
	}

	if (inet_ptoaddr(host, &ia6, sizeof(ia6)) != NULL) {
deuce's avatar
deuce committed
		if (*addr6len == 0) {
			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)
		return FALSE;
	for(cur=res; cur && (*addr4len == 0 || *addr6len == 0); cur=cur->ai_next) {
		switch (cur->ai_family) {
			case AF_INET:
				if (*addr4len == 0) {
					addr4->sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
					*addr4len = sizeof(struct sockaddr_in);
				if (*addr6len == 0) {
					addr6->sin6_addr = ((struct sockaddr_in6 *)cur->ai_addr)->sin6_addr;
					*addr6len = sizeof(struct sockaddr_in6);
static JSBool
js_connected_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;
	char*	protocol=NULL;
	uintN	i;
	js_socket_private_t* p = NULL;
	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];
	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;

	scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
	if (scfg == NULL) {
		JS_ReportError(cx, __FUNCTION__ ": Unable to get private runtime");
			JS_ReportError(cx, __FUNCTION__ ": 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, __FUNCTION__ ": 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, __FUNCTION__ ": 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, __FUNCTION__ ": 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, __FUNCTION__ ": Invalid proto property");