Skip to content
Snippets Groups Projects
Commit aae575d1 authored by Deucе's avatar Deucе :ok_hand_tone4:
Browse files

Merge branch 'echicken-less-grumpy' into 'master'

Implement support for callback style programming

See merge request !118
parents 87e73efe 19289739
No related branches found
No related tags found
No related merge requests found
/* DNS protocol library
* Uses events, so the calling script *MUST* set js.do_callbacks
* Example usage:
*
* js.do_callbacks = true;
* load('dns.js');
*
* function handle(resp)
* {
* log(LOG_ERROR, JSON.stringify(resp));
* exit(0);
* }
*
* dns.resolve('example.com', handle);
*
*/
require('sockdefs.js', 'SOCK_DGRAM');
function DNS(servers) {
if (servers === undefined)
servers = system.name_servers;
var nextid = 0;
var outstanding = {};
this.sockets = [];
handle_response = function() {
var resp;
var id;
var offset = 0;
var queries;
var answers;
var nameservers;
var arecords;
var q;
var i;
var ret = {'queries':[], 'answers':[], 'nameservers':[], 'additional':[]};
var rdata;
var rdlen;
var tmp;
string_to_int16 = function(str) {
return ((ascii(str[0])<<8) | (ascii(str[1])));
}
string_to_int32 = function(str) {
return ((ascii(str[0])<<24) | (ascii(str[1]) << 16) | (ascii(str[1]) << 8) | (ascii(str[1])));
}
function get_string(resp, offset) {
var len = ascii(resp[offset]);
return {'len':len + 1, 'string':resp.substr(offset + 1, len)};
}
function get_name(resp, offset) {
var len;
var name = '';
var ret = 0;
var compressed = false;
do {
len = ascii(resp[offset]);
if (!compressed)
ret++;
offset++;
if (len > 63) {
offset = ((len & 0x3f) << 8) | ascii(resp[offset]);
if (!compressed)
ret++;
compressed = true;
}
else {
if (!compressed)
ret += len;
if (name.length > 0 && len > 0)
name += '.';
name += resp.substr(offset, len);
offset += len;
}
} while (len != 0);
return {'len':ret, 'name':name};
}
function parse_rdata(type, resp, offset, len) {
var tmp;
var tmp2;
var tmp3;
var tmp4;
switch(type) {
case 1: // A
return ascii(resp[offset]) + '.' +
ascii(resp[offset + 1]) + '.' +
ascii(resp[offset + 2]) + '.' +
ascii(resp[offset + 3]);
case 2: // NS
return get_name(resp, offset).name;
case 5: // CNAME
return get_name(resp, offset).name;
case 6: // SOA
tmp = {};
tmp2 = 0;
tmp3 = get_name(resp, offset + tmp2);
tmp.mname = tmp3.name;
tmp2 += tmp3.len;
tmp3 = get_name(resp, offset + tmp2);
tmp.rname = tmp3.name;
tmp2 += tmp3.len;
tmp.serial = string_to_int32(resp.substr(offset + tmp2, 4));
tmp2 += 4;
tmp.refresh = string_to_int32(resp.substr(offset + tmp2, 4));
tmp2 += 4;
tmp.retry = string_to_int32(resp.substr(offset + tmp2, 4));
tmp2 += 4;
tmp.export = string_to_int32(resp.substr(offset + tmp2, 4));
tmp2 += 4;
tmp.minimum = string_to_int32(resp.substr(offset + tmp2, 4));
tmp2 += 4;
return tmp;
case 11: // WKS
tmp = {};
tmp.address = ascii(resp[offset]) + '.' +
ascii(resp[offset + 1]) + '.' +
ascii(resp[offset + 2]) + '.' +
ascii(resp[offset + 3]);
tmp.protocol = ascii(resp[offset + 4]);
tmp2 = 5;
tmp.ports = [];
while (tmp2 < len) {
tmp3 = ascii(resp[offset + tmp2]);
for (tmp4 = 0; tmp4 < 8; tmp4++) {
if (tmp3 & (1 << tmp4))
tmp.ports.push(8 * (tmp2 - 5) + tmp3);
}
}
return tmp;
case 12: // PTR
return get_name(resp, offset).name;
case 13: // HINFO
tmp = get_string(resp, offset);
return {'cpu':tmp.string, 'os':get_string(resp, offset + tmp.len).string};
case 15: // MX
tmp = {};
tmp.preference = string_to_int16(resp.substr(offset, 2));
tmp.exchange = get_name(resp, offset + 2).name;
return tmp;
case 16: // TXT
tmp = [];
tmp2 = 0;
do {
tmp3 = get_string(resp, offset + tmp2);
tmp.push(tmp3.string);
tmp2 += tmp3.len;
} while (tmp2 < len);
return tmp;
case 28:
return format("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
ascii(resp[offset + 0]), ascii(resp[offset + 1]),
ascii(resp[offset + 2]), ascii(resp[offset + 3]),
ascii(resp[offset + 4]), ascii(resp[offset + 5]),
ascii(resp[offset + 6]), ascii(resp[offset + 7]),
ascii(resp[offset + 8]), ascii(resp[offset + 9]),
ascii(resp[offset + 10]), ascii(resp[offset + 11]),
ascii(resp[offset + 12]), ascii(resp[offset + 13]),
ascii(resp[offset + 14]), ascii(resp[offset + 15])
).replace(/(0000:)+/, ':').replace(/(^|:)0{1,3}/g, '$1');
case 3: // MD
case 4: // MF
case 7: // MB
case 8: // MG
case 9: // MR
case 10: // NULL
case 14: // MINFO
return {'raw':resp.substr(offset, len)};
}
}
resp = this.recv(10000);
id = string_to_int16(resp);
if (this.outstanding[id] === undefined)
return false;
q = this.outstanding[id];
delete this.outstanding[id];
ret.id = id;
ret.response = !!(ascii(resp[2]) & 1);
ret.opcode = (ascii(resp[2]) & 0x1e) >> 1;
ret.authoritative = !!(ascii(resp[2]) & (1<<5));
ret.truncation = !!(ascii(resp[2]) & (1<<6));
ret.recusrion = !!(ascii(resp[2]) & (1<<7));
ret.reserved = ascii(resp[3]) & 7;
ret.rcode = ascii(resp[3] & 0xf1) >> 3;
queries = string_to_int16(resp.substr(4, 2));
answers = string_to_int16(resp.substr(6, 2));
nameservers = string_to_int16(resp.substr(8, 2));
arecords = string_to_int16(resp.substr(10, 2));
if (answers === 0)
return false;
js.clearTimeout(q.timeoutid);
offset = 12;
for (i = 0; i < queries; i++) {
rdata = {};
tmp = get_name(resp, offset);
rdata.name = tmp.name;
offset += tmp.len;
rdata.type = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.class = string_to_int16(resp.substr(offset, 2));
offset += 2;
ret.queries.push(rdata);
}
for (i = 0; i < answers; i++) {
rdata = {};
tmp = get_name(resp, offset);
rdata.name = tmp.name;
offset += tmp.len;
rdata.type = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.class = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.ttl = string_to_int32(resp.substr(offset, 4));
offset += 4;
rdlen = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.rdata = parse_rdata(rdata.type, resp, offset, rdlen);
offset += rdlen;
ret.answers.push(rdata);
}
for (i = 0; i < nameservers; i++) {
rdata = {};
tmp = get_name(resp, offset);
rdata.name = tmp.name;
offset += tmp.len;
rdata.type = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.class = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.ttl = string_to_int32(resp.substr(offset, 4));
offset += 4;
rdlen = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.rdata = parse_rdata(rdata.type, resp, offset, rdlen);
offset += rdlen;
ret.nameservers.push(rdata);
}
for (i = 0; i < arecords; i++) {
rdata = {};
tmp = get_name(resp, offset);
rdata.name = tmp.name;
offset += tmp.len;
rdata.type = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.class = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.ttl = string_to_int32(resp.substr(offset, 4));
offset += 4;
rdlen = string_to_int16(resp.substr(offset, 2));
offset += 2;
rdata.rdata = parse_rdata(rdata.type, resp, offset, rdlen);
offset += rdlen;
ret.additional.push(rdata);
}
q.callback.call(q.thisObj, ret);
return true;
}
servers.forEach(function(server) {
var sock = new Socket(SOCK_DGRAM, "dns", server.indexOf(':') >= 0);
sock.bind();
sock.connect(server, 53);
sock.on('read', handle_response);
sock.outstanding = outstanding;
this.sockets.push(sock);
}, this);
if (this.sockets.length < 1)
throw('Unable to create any sockets');
increment_id = function() {
var ret = nextid;
do {
nextid++;
if (nextid > 65535)
nextid = 0;
} while (outstanding[nextid] !== undefined);
return ret;
}
}
DNS.types = {
'A':1,
'NS':2,
'MD':3,
'MF':4,
'CNAME':5,
'SOA':6,
'MB':7,
'MG':8,
'MR':9,
'NULL':10,
'WKS':11,
'PTR':12,
'HINFO':13,
'MINFO':14,
'MX':15,
'TXT':16,
'AAAA':28
};
DNS.classes = {
'IN':1,
'CS':2,
'CH':3,
'HS':4
};
DNS.prototype.query = function(queries, /* queryStr, type, class, */callback, thisObj, recursive, timeout, failures, failed) {
var id;
var namebits = {};
function int16_to_string(id) {
return ascii((id & 0xff00) >> 8) + ascii(id & 0xff);
}
function handle_timeout() {
this.failed++;
delete outstanding[this.id];
if (this.failed > 2)
this.callback.call(this);
else
this.obj.query(this.query, this.callback, this.thisObj, this.recursive, this.timeout, this.failures, this.failed);
}
queries.forEach(function(query) {
if (query.str === undefined)
query.str = 'example.com';
if (query.type === undefined)
query.type = 'AAAA';
if (DNS.types[query.type] !== undefined)
query.type = DNS.types[query.type];
query.type = parseInt(query.type, 10);
if (isNaN(query.type)) {
throw new Error('Invalid type');
}
if (query.class === undefined)
query.class = 'IN';
if (DNS.classes[query.class] !== undefined)
query.class = DNS.classes[query.class];
query.class = parseInt(query.class, 10);
if (isNaN(query.class)) {
throw new Error('Invalid class');
}
});
if (callback === undefined)
return;
if (recursive === undefined)
recursive = true;
if (timeout === undefined)
timeout = 1000;
if (failures === undefined)
failures = 1;
if (failed === undefined)
failed = 0;
var query = '';
// Header
id = increment_id();
query = int16_to_string(id);
query += ascii(recursive ? 1 : 0);
query += ascii(0);
query += int16_to_string(queries.length); // Questions
query += int16_to_string(0); // Answers
query += int16_to_string(0); // Name Servers
query += int16_to_string(0); // Additional Records
// Queries
queries.forEach(function(oneQuery) {
var thisname = '';
var fields = oneQuery.query.split(/\./).reverse();
var matched;
fields.forEach(function(field) {
if (field.length > 63)
throw new Error('invalid field in query "'+field+'" (longer than 63 characters)');
thisname = ascii(field.length) + field + thisname;
if (namebits[thisname] !== undefined && namebits[thisname] > 0) {
matched = {'offset':namebits[thisname], 'length':thisname.length};
}
else {
namebits[thisname] = 0 - thisname.length;
}
});
// Now fix up negative namebits...
Object.keys(namebits).forEach(function(name) {
if (namebits[name] < 0) {
namebits[name] = query.length + thisname.length + namebits[name];
}
});
// And use the match
if (matched !== undefined) {
thisname = thisname.substr(0, thisname.length - matched.length);
thisname = thisname + ascii(0xc0 | ((matched.offset & 0x3f) >> 8)) + ascii(matched.offset & 0xff);
}
else
thisname += ascii(0);
query += thisname;
query += int16_to_string(oneQuery.type);
query += int16_to_string(oneQuery.class);
}, this);
this.sockets[0].outstanding[id] = {'callback':callback, 'query':queries, 'recursive':recursive, 'timeout':timeout, 'thisObj':thisObj, 'id':id, 'obj':this, 'failed':failed};
this.sockets[0].outstanding[id].timeoutid = js.setTimeout(handle_timeout, timeout, this.sockets[0].outstanding[id]);
this.sockets.forEach(function(sock) {
sock.write(query);
});
}
DNS.prototype.resolve = function(host, callback, thisObj)
{
var ctx = {A:{}, AAAA:{}, unique_id:'DNS.prototype.resolve'};
var final;
var respA;
var respAAAA;
this.sockets.forEach(function(sock) {
ctx.unique_id += '.'+sock.local_port;
});
if (host === undefined)
throw new Error('No host specified');
if (thisObj === undefined)
thisObj = this;
ctx.callback = callback;
ctx.thisObj = thisObj;
ctx.final = js.addEventListener(ctx.unique_id+'.final', function() {
var ret = [];
this.AAAA.addrs.forEach(function(addr) {
ret.push(addr);
});
this.A.addrs.forEach(function(addr) {
ret.push(addr);
});
js.removeEventListener(this.final);
js.removeEventListener(this.respA);
js.removeEventListener(this.respAAAA);
this.callback.call(this.thisObj, ret);
});
ctx.respA = js.addEventListener(ctx.unique_id+'.respA', function() {
this.A.done = true;
if (this.AAAA.done)
js.dispatchEvent(this.unique_id + '.final', this);
});
ctx.respAAAA = js.addEventListener(ctx.unique_id+'.respAAAA', function() {
this.AAAA.done = true;
if (this.A.done)
js.dispatchEvent(ctx.unique_id + '.final', this);
});
function handle_response(resp) {
var rectype;
switch(resp.queries[0].type) {
case DNS.types.A:
rectype = 'A';
break;
case DNS.types.AAAA:
rectype = 'AAAA';
break;
};
if (rectype === undefined)
return;
this[rectype].addrs = [];
resp.answers.forEach(function(ans) {
if (resp.queries[0].type != ans.type || resp.queries[0].class != ans.class)
return;
this[rectype].addrs.push(ans.rdata);
}, this);
js.dispatchEvent(this.unique_id + '.resp'+rectype, this);
}
this.query([{query:host, type:'AAAA'}], handle_response, ctx);
this.query([{query:host, type:'A'}], handle_response, ctx);
}
DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
{
var ctx = {};
var final;
var respA;
var respAAAA;
this.sockets.forEach(function(sock) {
ctx.unique_id += '.'+sock.local_port;
});
if (host === undefined)
throw new Error('No host specified');
if (type === undefined)
throw new Error('No type specified');
if (class === undefined)
throw new Error('No class specified');
if (callback === undefined)
throw new Error('No callback specified');
ctx.callback = callback;
if (thisObj === undefined)
thisObj = this;
ctx.thisObj = thisObj;
function handle_response(resp) {
ret = [];
if (resp !== undefined && resp.answers !== undefined) {
resp.answers.forEach(function(ans) {
if (resp.queries[0].type != ans.type || resp.queries[0].class != ans.class)
return;
ret.push(ans.rdata);
});
}
this.callback.call(this.thisObj, ret);
}
this.query([{query:host, type:type, class:class}], handle_response, ctx);
}
DNS.prototype.reverse = function(ip, callback, thisObj)
{
var qstr;
var m;
var a;
var fillpos;
if (ip === undefined)
throw new Error('No IP specified');
if (thisObj === undefined)
thisObj = this;
// Sure, this doesn't deal with terrible ipv4-mapped representations. Suck it.
m = ip.match(/^(?:::ffff:)?([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i);
if (m !== null) {
// IPv4 Address
if (parseInt(m[1], 10) > 255 || parseInt(m[2], 10) > 255 || parseInt(m[3], 10) > 255 || parseInt(m[4], 10) > 255)
throw new Error('Malformed IP address '+ip);
qstr = m[4] + '.' + m[3] + '.' + m[2] + '.' + m[1] + '.in-addr.arpa';
}
else {
a = ip.split(/:/);
if (a.length < 3 || a.length > 8)
throw new Error('Malformed IP address '+ip);
if (ip.search(/^[a-fA-F0-9:]+$/) != 0)
throw new Error('Malformed IP address '+ip);
a.forEach(function(piece, idx, arr) {
if (piece !== '') {
while (piece.length < 4)
piece = '0'+piece;
}
arr[idx] = piece;
});
if (a[0] == '')
a[0] = '0000';
if (a[a.length - 1] == '')
a[a.length] = '0000';
while (a.length < 8) {
fillpos = a.indexOf('');
if (fillpos === -1)
throw('Unable to expand IPv6 address');
a.splice(fillpos, 0, '0000');
}
fillpos = a.indexOf('');
if (fillpos != -1)
a.splice(fillpos, 1, '0000');
a.reverse();
qstr = '';
a.forEach(function(piece) {
qstr += piece[3] + '.' + piece[2] + '.' + piece[1] + '.' + piece[0] + '.';
});
qstr += 'ip6.arpa';
}
this.resolveTypeClass(qstr, 'PTR', 'IN', callback, thisObj);
}
DNS.prototype.resolveMX = function(host, callback, thisObj)
{
var ctx = {callback:callback};
var qstr;
var m;
var a;
var fillpos;
if (host === undefined)
throw new Error('No host specified');
if (thisObj === undefined)
thisObj = this;
ctx.thisObj = thisObj;
function handler(resp) {
var ret = [];
resp.sort(function(a, b) {
return a.preference - b.preference;
});
resp.forEach(function(r) {
ret.push(r.exchange);
});
this.callback.call(this.thisObj, ret);
}
this.resolveTypeClass(host, 'MX', 'IN', handler, ctx);
}
......@@ -683,6 +683,7 @@ long sbbs_t::js_execfile(const char *cmd, const char* startup_dir, JSObject* sco
}
js_PrepareToExecute(js_cx, js_glob, path, startup_dir, js_scope);
JS_ExecuteScript(js_cx, js_scope, js_script, &rval);
js_handle_events(js_cx, &js_callback, &terminated);
sys_status &=~ SS_ABORT;
JS_GetProperty(js_cx, js_scope, "exit_code", &rval);
......
......@@ -1998,6 +1998,21 @@ js_getlines(JSContext *cx, uintN argc, jsval *arglist)
return(JS_TRUE);
}
void
js_do_lock_input(JSContext *cx, JSBool lock)
{
sbbs_t* sbbs;
if ((sbbs = (sbbs_t*)JS_GetContextPrivate(cx)) == NULL)
return;
if(lock) {
pthread_mutex_lock(&sbbs->input_thread_mutex);
} else {
pthread_mutex_unlock(&sbbs->input_thread_mutex);
}
}
static JSBool
js_lock_input(JSContext *cx, uintN argc, jsval *arglist)
{
......@@ -2109,6 +2124,136 @@ js_term_updated(JSContext *cx, uintN argc, jsval *arglist)
return JS_TRUE;
}
size_t
js_cx_input_pending(JSContext *cx)
{
sbbs_t* sbbs;
if ((sbbs = (sbbs_t*)JS_GetContextPrivate(cx)) == NULL)
return 0;
return sbbs->keybuf_level() + RingBufFull(&sbbs->inbuf);
}
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;
char operation[16];
enum js_event_type et;
size_t slen;
struct js_event_list *ev;
sbbs_t *sbbs;
if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
return(JS_FALSE);
if (argc != 2) {
JS_ReportError(cx, "console.on() and console.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_CONSOLE_INPUT_ONCE;
else
et = JS_EVENT_CONSOLE_INPUT;
}
else {
JS_ReportError(cx, "event parameter must be 'read'");
return JS_FALSE;
}
cb = &sbbs->js_callback;
if (cb == NULL) {
return JS_FALSE;
}
if (!cb->events_supported) {
JS_ReportError(cx, "events not supported");
return JS_FALSE;
}
ev = (struct js_event_list *)malloc(sizeof(*ev));
if (ev == NULL) {
JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
return JS_FALSE;
}
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->id = cb->next_eid++;
ev->data.sock = sbbs->client_socket;
cb->events = ev;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
return JS_TRUE;
}
static JSBool
js_clear_console_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;
if (argc != 2) {
JS_ReportError(cx, "console.clearOn() and console.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_CONSOLE_INPUT_ONCE;
else
et = JS_EVENT_CONSOLE_INPUT;
}
else {
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
return JS_TRUE;
}
return js_clear_event(cx, argc, arglist, et);
}
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_console_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_console_event(cx, argc, arglist, TRUE);
}
static jsSyncMethodSpec js_console_functions[] = {
{"inkey", js_inkey, 0, JSTYPE_STRING, JSDOCSTR("[mode=<tt>K_NONE</tt>] [,timeout=<tt>0</tt>]")
,JSDOCSTR("get a single key with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait).<br>"
......@@ -2362,6 +2507,22 @@ static jsSyncMethodSpec js_console_functions[] = {
,JSDOCSTR("scroll all current mouse hot-spots by the specific number of rows")
,31800
},
{"on", js_on, 2, JSTYPE_NUMBER, JSDOCSTR("type, callback")
,JSDOCSTR("calls callback whenever the condition type specifies is possible. Currently, only 'read' is supported for type. Returns an id suitable for use with clearOn")
,31802
},
{"once", js_once, 2, JSTYPE_NUMBER, JSDOCSTR("type, callback")
,JSDOCSTR("calls callback the first time the condition type specifies is possible. Currently, only 'read' is supported for type. Returns an id suitable for use with clearOnce")
,31802
},
{"clearOn", js_clearOn, 2, JSTYPE_VOID, JSDOCSTR("type, id")
,JSDOCSTR("removes a callback installed by on")
,31802
},
{"clearOnce", js_clearOnce, 2, JSTYPE_VOID, JSDOCSTR("type, id")
,JSDOCSTR("removes a callback installed by once")
,31802
},
{0}
};
......
......@@ -20,7 +20,9 @@
****************************************************************************/
#include "sbbs.h"
#include "sockwrap.h"
#include "js_request.h"
#include "js_socket.h"
/* SpiderMonkey: */
#include <jsdbgapi.h>
......@@ -42,6 +44,7 @@ enum {
#endif
,PROP_GLOBAL
,PROP_OPTIONS
,PROP_KEEPGOING
};
static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
......@@ -111,6 +114,12 @@ static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
case PROP_OPTIONS:
*vp = UINT_TO_JSVAL(JS_GetOptions(cx));
break;
case PROP_KEEPGOING:
if (cb->events_supported)
*vp = BOOLEAN_TO_JSVAL(cb->keepGoing);
else
*vp = JSVAL_FALSE;
break;
}
return(JS_TRUE);
......@@ -158,6 +167,10 @@ static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval
return JS_FALSE;
break;
#endif
case PROP_KEEPGOING:
if (cb->events_supported)
JS_ValueToBoolean(cx, *vp, &cb->keepGoing);
break;
}
return(JS_TRUE);
......@@ -186,6 +199,7 @@ static jsSyncPropertySpec js_properties[] = {
#endif
{ "global", PROP_GLOBAL, PROP_FLAGS, 314 },
{ "options", PROP_OPTIONS, PROP_FLAGS, 31802 },
{ "do_callbacks", PROP_KEEPGOING, JSPROP_ENUMERATE, 31802 },
{0}
};
......@@ -207,6 +221,7 @@ static char* prop_desc[] = {
#endif
,"global (top level) object - <small>READ ONLY</small>"
,"option flags - <small>READ ONLY</small>"
,"do callbacks after script finishes running"
/* New properties go here... */
,"full path and filename of JS file executed"
,"directory of executed JS file"
......@@ -690,6 +705,659 @@ static JSBool js_flatten(JSContext *cx, uintN argc, jsval *arglist)
return(JS_TRUE);
}
static JSBool
js_setTimeout(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
struct js_event_list *ev;
js_callback_t* cb;
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
JSFunction *ecb;
uint64_t now = xp_timer() * 1000;
jsdouble timeout;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
if (!cb->events_supported) {
JS_ReportError(cx, "events not supported");
return JS_FALSE;
}
if (argc < 2) {
JS_ReportError(cx, "js.setTimeout() requires two parameters");
return JS_FALSE;
}
ecb = JS_ValueToFunction(cx, argv[0]);
if (ecb == NULL) {
return JS_FALSE;
}
if (argc > 2) {
if (!JS_ValueToObject(cx, argv[2], &obj))
return JS_FALSE;
}
if (!JS_ValueToNumber(cx, argv[1], &timeout)) {
return JS_FALSE;
}
ev = malloc(sizeof(*ev));
if (ev == NULL) {
JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
return JS_FALSE;
}
ev->prev = NULL;
ev->next = cb->events;
if (ev->next)
ev->next->prev = ev;
ev->type = JS_EVENT_TIMEOUT;
ev->cx = obj;
JS_AddObjectRoot(cx, &ev->cx);
ev->cb = ecb;
ev->data.timeout.end = now + timeout;
ev->id = cb->next_eid++;
cb->events = ev;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
return JS_TRUE;
}
JSBool
js_clear_event(JSContext *cx, uintN argc, jsval *arglist, enum js_event_type et)
{
jsval *argv=JS_ARGV(cx, arglist);
int32 id;
js_callback_t* cb;
struct js_event_list *ev;
struct js_event_list *nev;
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if (argc < 1) {
JS_ReportError(cx, "js.clearTimeout() requires an id");
return JS_FALSE;
}
if (!JS_ValueToInt32(cx, argv[0], &id)) {
return JS_FALSE;
}
if((cb=(js_callback_t*)JS_GetPrivate(cx, obj))==NULL)
return(JS_FALSE);
if (!cb->events_supported) {
JS_ReportError(cx, "events not supported");
return JS_FALSE;
}
for (ev = cb->events; ev; ev = nev) {
nev = ev->next;
if (ev->type == et && ev->id == id) {
if (ev->next)
ev->next->prev = ev->prev;
if (ev->prev)
ev->prev->next = ev->next;
else
cb->events = ev->next;
JS_RemoveObjectRoot(cx, &ev->cx);
free(ev);
}
}
return JS_TRUE;
}
static JSBool
js_clearTimeout(JSContext *cx, uintN argc, jsval *arglist)
{
return js_clear_event(cx, argc, arglist, JS_EVENT_TIMEOUT);
}
static JSBool
js_clearInterval(JSContext *cx, uintN argc, jsval *arglist)
{
return js_clear_event(cx, argc, arglist, JS_EVENT_INTERVAL);
}
static JSBool
js_setInterval(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
struct js_event_list *ev;
js_callback_t* cb;
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
JSFunction *ecb;
uint64_t now = xp_timer() * 1000;
jsdouble period;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
if (!cb->events_supported) {
JS_ReportError(cx, "events not supported");
return JS_FALSE;
}
if (argc < 2) {
JS_ReportError(cx, "js.setInterval() requires two parameters");
return JS_FALSE;
}
ecb = JS_ValueToFunction(cx, argv[0]);
if (ecb == NULL) {
return JS_FALSE;
}
if (!JS_ValueToNumber(cx, argv[1], &period)) {
return JS_FALSE;
}
if (argc > 2) {
if (!JS_ValueToObject(cx, argv[2], &obj))
return JS_FALSE;
}
ev = malloc(sizeof(*ev));
if (ev == NULL) {
JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
return JS_FALSE;
}
ev->prev = NULL;
ev->next = cb->events;
if (ev->next)
ev->next->prev = ev;
ev->type = JS_EVENT_INTERVAL;
ev->cx = obj;
JS_AddObjectRoot(cx, &ev->cx);
ev->cb = ecb;
ev->data.interval.last = now;
ev->data.interval.period = period;
ev->id = cb->next_eid++;
cb->events = ev;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
return JS_TRUE;
}
static JSBool
js_addEventListener(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
char *name;
JSFunction *cbf;
struct js_listener_entry *listener;
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
js_callback_t *cb;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
if (argc < 2) {
JS_ReportError(cx, "js.addEventListener() requires exactly two parameters");
return JS_FALSE;
}
cbf = JS_ValueToFunction(cx, argv[1]);
if (cbf == NULL) {
return JS_FALSE;
}
JSVALUE_TO_MSTRING(cx, argv[0], name, NULL);
HANDLE_PENDING(cx, name);
listener = malloc(sizeof(*listener));
if (listener == NULL) {
free(name);
JS_ReportError(cx, "error allocating %ul bytes", sizeof(*listener));
return JS_FALSE;
}
listener->func = cbf;
listener->id = cb->next_eid++;
listener->next = cb->listeners;
listener->name = name;
cb->listeners = listener;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(listener->id));
return JS_TRUE;
}
static JSBool
js_removeEventListener(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
int32 id;
struct js_listener_entry *listener;
struct js_listener_entry **plistener;
char *name;
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
js_callback_t *cb;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
if (argc < 1) {
JS_ReportError(cx, "js.removeEventListener() requires exactly one parameter");
return JS_FALSE;
}
if (JSVAL_IS_INT(argv[0])) {
// Remove by ID
if (!JS_ValueToInt32(cx, argv[0], &id))
return JS_FALSE;
for (listener = cb->listeners, plistener = &cb->listeners; listener; listener = (*plistener)->next) {
if (listener->id == id) {
(*plistener)->next = listener->next;
free(listener);
}
else
*plistener = listener;
}
}
else {
// Remove by name
JSVALUE_TO_MSTRING(cx, argv[0], name, NULL);
HANDLE_PENDING(cx, name);
for (listener = cb->listeners, plistener = &cb->listeners; listener; listener = (*plistener)->next) {
if (strcmp(listener->name, name) == 0) {
(*plistener)->next = listener->next;
free(listener);
}
else
*plistener = listener;
}
free(name);
}
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
js_dispatchEvent(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
struct js_listener_entry *listener;
struct js_runq_entry *rqe;
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
char *name;
js_callback_t *cb;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
if (argc < 1) {
JS_ReportError(cx, "js.dispatchEvent() requires a event name");
return JS_FALSE;
}
if (argc > 1) {
if (!JSVAL_IS_OBJECT(argv[1])) {
JS_ReportError(cx, "second argument must be an object");
return JS_FALSE;
}
obj = JSVAL_TO_OBJECT(argv[1]);
}
JSVALUE_TO_MSTRING(cx, argv[0], name, NULL);
HANDLE_PENDING(cx, name);
for (listener = cb->listeners; listener; listener = listener->next) {
if (strcmp(name, listener->name) == 0) {
rqe = malloc(sizeof(*rqe));
if (rqe == NULL) {
JS_ReportError(cx, "error allocating %ul bytes", sizeof(*rqe));
free(name);
return JS_FALSE;
}
rqe->func = listener->func;
rqe->cx = obj;
JS_AddObjectRoot(cx, &rqe->cx);
rqe->next = NULL;
if (cb->rq_tail != NULL)
cb->rq_tail->next = rqe;
cb->rq_tail = rqe;
if (cb->rq_head == NULL)
cb->rq_head = rqe;
}
}
free(name);
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
return JS_TRUE;
}
static void
js_clear_events(JSContext *cx, js_callback_t *cb)
{
struct js_event_list *ev;
struct js_event_list *nev;
struct js_runq_entry *rq;
struct js_runq_entry *nrq;
struct js_listener_entry *le;
struct js_listener_entry *nle;
for (ev = cb->events, nev = NULL; ev; ev = nev) {
nev = ev->next;
JS_RemoveObjectRoot(cx, &ev->cx);
free(ev);
}
cb->next_eid = 0;
cb->events = NULL;
for (rq = cb->rq_head; rq; rq = nrq) {
nrq = rq->next;
JS_RemoveObjectRoot(cx, &rq->cx);
free(rq);
}
cb->rq_head = NULL;
cb->rq_tail = NULL;
for (le = cb->listeners; le; le = nle) {
nle = le->next;
free(le);
}
cb->listeners = NULL;
}
JSBool
js_handle_events(JSContext *cx, js_callback_t *cb, volatile int *terminated)
{
struct js_event_list **head = &cb->events;
int timeout;
int i;
uint64_t now;
struct js_event_list *ev;
struct js_event_list *tev;
struct js_event_list *cev;
jsval rval = JSVAL_NULL;
jsrefcount rc;
JSBool ret = JS_TRUE;
BOOL input_locked = FALSE;
#ifdef PREFER_POLL
struct pollfd *fds;
nfds_t sc;
nfds_t cfd;
#else
fd_set rfds;
fd_set wfds;
struct timeval tv;
SOCKET hsock;
#endif
if (!cb->events_supported)
return JS_FALSE;
while (cb->keepGoing && !JS_IsExceptionPending(cx) && cb->events && !*terminated) {
timeout = -1; // Infinity by default...
now = xp_timer() * 1000;
ev = NULL;
tev = NULL;
cev = NULL;
#ifdef PREFER_POLL
fds = NULL;
sc = 0;
cfd = 0;
#else
hsock = 0;
#endif
#ifdef PREFER_POLL
for (ev = *head; ev; ev = 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
|| ev->type == JS_EVENT_SOCKET_CONNECT || ev->type == JS_EVENT_CONSOLE_INPUT
|| ev->type == JS_EVENT_CONSOLE_INPUT_ONCE)
sc++;
}
if (sc) {
fds = calloc(sc, sizeof(*fds));
if (fds == NULL) {
JS_ReportError(cx, "error allocating %d elements of %ul bytes", sc, sizeof(*fds));
return JS_FALSE;
}
}
#else
FD_ZERO(&rfds);
FD_ZERO(&wfds);
#endif
rc = JS_SUSPENDREQUEST(cx);
if (cb->rq_head)
timeout = 0;
for (ev = *head; ev; ev = ev->next) {
switch (ev->type) {
case JS_EVENT_SOCKET_READABLE_ONCE:
case JS_EVENT_SOCKET_READABLE:
#ifdef PREFER_POLL
fds[cfd].fd = ev->data.sock;
fds[cfd].events = POLLIN;
cfd++;
#else
FD_SET(ev->data.sock, &rfds);
if (ev->data.sock > hsock)
hsock = ev->data.sock;
#endif
break;
case JS_EVENT_SOCKET_WRITABLE_ONCE:
case JS_EVENT_SOCKET_WRITABLE:
#ifdef PREFER_POLL
fds[cfd].fd = ev->data.sock;
fds[cfd].events = POLLOUT;
cfd++;
#else
FD_SET(ev->data.sock, &wfds);
if (ev->data.sock > hsock)
hsock = ev->data.sock;
#endif
break;
case JS_EVENT_SOCKET_CONNECT:
#ifdef PREFER_POLL
fds[cfd].fd = ev->data.connect.sv[0];
fds[cfd].events = POLLIN;
cfd++;
#else
FD_SET(ev->data.connect.sv[0], &rfds);
if (ev->data.sock > hsock)
hsock = ev->data.sock;
#endif
break;
case JS_EVENT_INTERVAL:
// TODO: Handle clock wrapping
if (ev->data.interval.last + ev->data.interval.period <= now) {
timeout = 0;
tev = ev;
}
else {
i = ev->data.interval.last + ev->data.interval.period - now;
if (timeout == -1 || i < timeout) {
timeout = i;
tev = ev;
}
}
break;
case JS_EVENT_TIMEOUT:
// TODO: Handle clock wrapping
if (now >= ev->data.timeout.end) {
timeout = 0;
tev = ev;
}
else {
i = ev->data.timeout.end - now;
if (timeout == -1 || i < timeout) {
timeout = i;
tev = ev;
}
}
break;
case JS_EVENT_CONSOLE_INPUT_ONCE:
case JS_EVENT_CONSOLE_INPUT:
if (!input_locked)
js_do_lock_input(cx, TRUE);
if (js_cx_input_pending(cx) != 0) {
js_do_lock_input(cx, FALSE);
timeout = 0;
cev = ev;
}
else {
input_locked = TRUE;
#ifdef PREFER_POLL
fds[cfd].fd = ev->data.sock;
fds[cfd].events = POLLIN;
cfd++;
#else
FD_SET(ev->data.sock, &rfds);
if (ev->data.sock > hsock)
hsock = ev->data.sock;
#endif
}
break;
}
}
// TODO: suspend/resume request
#ifdef PREFER_POLL
switch (poll(fds, cfd, timeout)) {
#else
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
switch (select(hsock + 1, &rfds, &wfds, NULL, &tv)) {
#endif
case 0: // Timeout
JS_RESUMEREQUEST(cx, rc);
if (tev && tev->cx && tev->cb)
ev = tev;
else if (cev && cev->cx && cev->cb)
ev = cev;
else {
if (cb->rq_head == NULL) {
JS_ReportError(cx, "Timeout with no event!");
ret = JS_FALSE;
goto done;
}
}
break;
case -1: // Error...
JS_RESUMEREQUEST(cx, rc);
if (ERROR_VALUE != EINTR) {
JS_ReportError(cx, "poll() returned with error %d", ERROR_VALUE);
ret = JS_FALSE;
goto done;
}
break;
default: // Zero or more sockets ready
JS_RESUMEREQUEST(cx, rc);
#ifdef PREFER_POLL
cfd = 0;
#endif
for (ev = *head; ev; ev = ev->next) {
if (ev->type == JS_EVENT_SOCKET_READABLE || ev->type == JS_EVENT_SOCKET_READABLE_ONCE) {
#ifdef PREFER_POLL
if (fds[cfd].revents & ~(POLLOUT | POLLWRNORM | POLLWRBAND)) {
#else
if (FD_ISSET(ev->data.sock, &rfds)) {
#endif
break;
}
#ifdef PREFER_POLL
cfd++;
#endif
}
else if (ev->type == JS_EVENT_SOCKET_WRITABLE || ev->type == JS_EVENT_SOCKET_WRITABLE_ONCE) {
#ifdef PREFER_POLL
if (fds[cfd].revents & ~(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
#else
if (FD_ISSET(ev->data.sock, &wfds)) {
#endif
break;
}
#ifdef PREFER_POLL
cfd++;
#endif
}
else if (ev->type == JS_EVENT_SOCKET_CONNECT) {
#ifdef PREFER_POLL
if (fds[cfd].revents & ~(POLLOUT | POLLWRNORM | POLLWRBAND)) {
#else
if (FD_ISSET(ev->data.connect.sv[0], &wfds)) {
#endif
closesocket(ev->data.connect.sv[0]);
break;
}
#ifdef PREFER_POLL
cfd++;
#endif
}
else if (ev->type == JS_EVENT_CONSOLE_INPUT) {
#ifdef PREFER_POLL
if (fds[cfd].revents & ~(POLLOUT | POLLWRNORM | POLLWRBAND)) {
#else
if (FD_ISSET(ev->data.sock, &wfds)) {
#endif
break;
}
#ifdef PREFER_POLL
cfd++;
#endif
}
}
break;
}
if (ev == NULL) {
if (cb->rq_head == NULL) {
JS_ReportError(cx, "poll() returned ready, but didn't find anything");
ret = JS_FALSE;
goto done;
}
else {
struct js_runq_entry *rqe = cb->rq_head;
cb->rq_head = rqe->next;
if (cb->rq_head == NULL)
cb->rq_tail = NULL;
ret = JS_CallFunction(cx, rqe->cx, rqe->func, 0, NULL, &rval);
JS_RemoveObjectRoot(cx, &rqe->cx);
free(rqe);
}
}
else {
// Deal with things before running the callback
if (ev->type == JS_EVENT_CONSOLE_INPUT) {
if (input_locked)
js_do_lock_input(cx, FALSE);
}
ret = JS_CallFunction(cx, ev->cx, ev->cb, 0, NULL, &rval);
// Clean up/update after call
if (ev->type == JS_EVENT_SOCKET_CONNECT) {
// TODO: call recv() and pass result to callback?
closesocket(ev->data.connect.sv[0]);
}
else if (ev->type == JS_EVENT_INTERVAL)
ev->data.interval.last += ev->data.interval.period;
// Remove one-shot events
if (ev->type == JS_EVENT_SOCKET_READABLE_ONCE || ev->type == JS_EVENT_SOCKET_WRITABLE_ONCE
|| ev->type == JS_EVENT_SOCKET_CONNECT || ev->type == JS_EVENT_TIMEOUT
|| ev->type == JS_EVENT_CONSOLE_INPUT_ONCE) {
if (ev->next)
ev->next->prev = ev->prev;
if (ev->prev)
ev->prev->next = ev->next;
else
*head = ev->next;
JS_RemoveObjectRoot(cx, &ev->cx);
free(ev);
}
}
done:
#ifdef PREFER_POLL
free(fds);
#endif
if (input_locked)
js_do_lock_input(cx, FALSE);
if (!ret)
break;
}
js_clear_events(cx, cb);
return ret;
}
static jsSyncMethodSpec js_functions[] = {
{"eval", js_eval, 0, JSTYPE_UNDEF, JSDOCSTR("script")
,JSDOCSTR("evaluate a JavaScript string in its own (secure) context, returning the result")
......@@ -732,6 +1400,36 @@ static jsSyncMethodSpec js_functions[] = {
"NOTE: Use <tt>js.exec.apply()</tt> if you need to pass a variable number of arguments to the executed script.")
,31702
},
{"setTimeout", js_setTimeout, 2, JSTYPE_NUMBER, JSDOCSTR("callback, time[, thisObj]")
,JSDOCSTR("install a timeout. callback() will be called time ms after the function is called. "
"returns an id which can be passed to clearTimeout()")
,31802
},
{"setInterval", js_setInterval, 2, JSTYPE_OBJECT, JSDOCSTR("callback, period[, thisObj]")
,JSDOCSTR("install a timeout. callback() will be called every period ms after setInterval() is called."
"returns and id which can be passed to clearIntervat()")
,31802
},
{"clearTimeout", js_clearTimeout, 1, JSTYPE_VOID, JSDOCSTR("id")
,JSDOCSTR("remove a timeout")
,31802
},
{"clearInterval", js_clearInterval, 1, JSTYPE_VOID, JSDOCSTR("id")
,JSDOCSTR("remove an interval")
,31802
},
{"addEventListener", js_addEventListener, 2, JSTYPE_NUMBER, JSDOCSTR("eventName, callback")
,JSDOCSTR("Add a listener that is ran after js.dispatchEvent(eventName) is called. Returns an id to be passed to js.removeEventListener")
,31802
},
{"removeEventListener", js_removeEventListener, 1, JSTYPE_VOID, JSDOCSTR("id")
,JSDOCSTR("Remove listeners added with js.addEventListener(). id can be a string or an id returned by addEventListener. This does not remove already triggered callbacks from the runqueue.")
,31802
},
{"dispatchEvent", js_dispatchEvent, 1, JSTYPE_VOID, JSDOCSTR("eventName [, thisObj]")
,JSDOCSTR("Add all listeners of eventName to the runqueue. If obj is passed, specifies this in the callback (the js object is used otherwise).")
,31802
},
{0}
};
......
......@@ -33,7 +33,7 @@ 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(js_socket_private_t *p, bool finalize);
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);
......@@ -42,6 +42,7 @@ 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);
......@@ -59,13 +60,16 @@ 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(js_socket_private_t *p);
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(js_socket_private_t *p, void *buf, size_t len, int flags, int timeout);
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 int js_socket_sendfilesocket(js_socket_private_t *p, int file, off_t *offset, off_t count);
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);
static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val)
{
......@@ -153,12 +157,70 @@ static bool do_CryptFlush(js_socket_private_t *p)
return true;
}
static void do_js_close(js_socket_private_t *p, bool finalize)
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_READABLE || ev->type == JS_EVENT_SOCKET_READABLE_ONCE) {
if (ev->data.sock == sock) {
if (ev->next)
ev->next->prev = ev->prev;
if (ev->prev)
ev->prev->next = ev->next;
else
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) {
cryptDestroySession(p->session);
p->session=-1;
}
// 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) {
p->is_connected = FALSE;
return;
......@@ -171,18 +233,19 @@ static void do_js_close(js_socket_private_t *p, bool finalize)
if (!finalize)
shutdown(p->sock, SHUT_RDWR);
}
// This is a lie for external sockets... don't tell anyone.
p->sock = INVALID_SOCKET;
p->is_connected = FALSE;
}
static BOOL js_socket_peek_byte(js_socket_private_t *p)
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)
return FALSE;
if (p->peeked)
return TRUE;
if (js_socket_recv(p, &p->peeked_byte, 1, 0, 0) == 1) {
if (js_socket_recv(cx, p, &p->peeked_byte, 1, 0, 0) == 1) {
p->peeked = TRUE;
return TRUE;
}
......@@ -192,7 +255,7 @@ static BOOL js_socket_peek_byte(js_socket_private_t *p)
/* 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(js_socket_private_t *p, void *buf, size_t len, int flags, int timeout)
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;
......@@ -204,7 +267,7 @@ static ptrdiff_t js_socket_recv(js_socket_private_t *p, void *buf, size_t len, i
return total;
if (p->session != -1) {
if (flags & MSG_PEEK)
js_socket_peek_byte(p);
js_socket_peek_byte(cx, p);
if (p->peeked) {
*(uint8_t *)buf = p->peeked_byte;
buf=((uint8_t *)buf) + 1;
......@@ -240,7 +303,7 @@ static ptrdiff_t js_socket_recv(js_socket_private_t *p, void *buf, size_t len, i
ret = 0;
else if (status != CRYPT_ERROR_COMPLETE) {
GCES(ret, p, estr, "popping data");
do_js_close(p, false);
do_js_close(cx, p, false);
}
}
}
......@@ -392,7 +455,7 @@ static void js_finalize_socket(JSContext *cx, JSObject *obj)
if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL)
return;
do_js_close(p, true);
do_js_close(cx, p, true);
if(!p->external)
free(p->set);
......@@ -420,7 +483,7 @@ js_close(JSContext *cx, uintN argc, jsval *arglist)
}
rc=JS_SUSPENDREQUEST(cx);
do_js_close(p, false);
do_js_close(cx, p, false);
dbprintf(FALSE, p, "closed");
JS_RESUMEREQUEST(cx, rc);
......@@ -809,6 +872,144 @@ js_accept(JSContext *cx, uintN argc, jsval *arglist)
return(JS_TRUE);
}
struct js_connect_event_args {
SOCKET sv[2];
SOCKET sock;
int socktype;
char *host;
BOOL nonblocking;
ushort port;
};
static void
js_connect_event_thread(void *args)
{
struct js_connect_event_args *a = args;
struct addrinfo hints,*res,*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);
if(result != 0)
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);
if(result == 0)
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);
struct js_connect_event_args *args;
jsval *argv=JS_ARGV(cx, arglist);
if (p->sock == INVALID_SOCKET) {
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", ERROR_VALUE);
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->data.sock = p->sock;
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;
}
static JSBool
js_connect(JSContext *cx, uintN argc, jsval *arglist)
{
......@@ -834,6 +1035,13 @@ js_connect(JSContext *cx, uintN argc, jsval *arglist)
JSSTRING_TO_MSTRING(cx, str, p->hostname, NULL);
port = js_port(cx,argv[1],p->type);
rc=JS_SUSPENDREQUEST(cx);
if (argc > 2 && JSVAL_IS_OBJECT(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));
......@@ -1201,7 +1409,7 @@ js_recv(JSContext *cx, uintN argc, jsval *arglist)
}
rc=JS_SUSPENDREQUEST(cx);
len = js_socket_recv(p,buf,len,0,timeout);
len = js_socket_recv(cx,p,buf,len,0,timeout);
JS_RESUMEREQUEST(cx, rc);
if(len<0) {
p->last_error=ERROR_VALUE;
......@@ -1386,7 +1594,7 @@ js_peek(JSContext *cx, uintN argc, jsval *arglist)
}
rc=JS_SUSPENDREQUEST(cx);
if(p->session==-1)
len = js_socket_recv(p,buf,len,MSG_PEEK,120);
len = js_socket_recv(cx, p,buf,len,MSG_PEEK,120);
else
len=0;
JS_RESUMEREQUEST(cx, rc);
......@@ -1494,7 +1702,7 @@ js_recvline(JSContext *cx, uintN argc, jsval *arglist)
}
}
if((got=js_socket_recv(p, &ch, 1, 0, i?1:timeout))!=1) {
if((got=js_socket_recv(cx, p, &ch, 1, 0, i?1:timeout))!=1) {
if(p->session == -1)
p->last_error = ERROR_VALUE;
if (i == 0) { // no data received
......@@ -1555,18 +1763,18 @@ js_recvbin(JSContext *cx, uintN argc, jsval *arglist)
rc=JS_SUSPENDREQUEST(cx);
switch(size) {
case sizeof(BYTE):
if((rd=js_socket_recv(p,&b,size,MSG_WAITALL,120))==size)
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(p,(BYTE*)&w,size,MSG_WAITALL,120))==size) {
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(p,(BYTE*)&l,size,MSG_WAITALL,120))==size) {
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));
......@@ -1821,6 +2029,179 @@ js_poll(JSContext *cx, uintN argc, jsval *arglist)
return(JS_TRUE);
}
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, "Walked to global, no js object!");
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, "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) {
return(JS_FALSE);
}
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;
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;
}
return js_clear_event(cx, argc, arglist, et);
}
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, TRUE);
}
/* Socket Object Properties */
enum {
......@@ -1994,7 +2375,7 @@ static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict
cryptDestroySession(p->session);
p->session=-1;
ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
do_js_close(p, false);
do_js_close(cx, p, false);
}
}
}
......@@ -2003,7 +2384,7 @@ static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict
cryptDestroySession(p->session);
p->session=-1;
ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
do_js_close(p, false);
do_js_close(cx, p, false);
}
}
JS_RESUMEREQUEST(cx, rc);
......@@ -2069,7 +2450,7 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
if (p->peeked)
rd = TRUE;
else if (p->session != -1)
rd = js_socket_peek_byte(p);
rd = js_socket_peek_byte(cx, p);
else
socket_check(p->sock,&rd,NULL,0);
}
......@@ -2082,7 +2463,7 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
}
cnt=0;
if (p->session != -1) {
if (js_socket_peek_byte(p))
if (js_socket_peek_byte(cx, p))
*vp=DOUBLE_TO_JSVAL((double)1);
else
*vp = JSVAL_ZERO;
......@@ -2296,6 +2677,22 @@ static jsSyncMethodSpec js_socket_functions[] = {
"default timeout value is 0.0 seconds (immediate timeout)")
,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 js.clearOn()")
,31802
},
{"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 js.clearOnce()")
,31802
},
{"clearOn", js_clearOn, 2, JSTYPE_NUMBER, JSDOCSTR("('read' | 'write'), id")
,JSDOCSTR("remove callback installed by Socket.on()")
,31802
},
{"clearOnce", js_clearOnce, 2, JSTYPE_NUMBER, JSDOCSTR("('read' | 'write'), id")
,JSDOCSTR("remove callback installed by Socket.once()")
,31802
},
{0}
};
......
......@@ -25,6 +25,7 @@ typedef struct
char peeked_byte;
BOOL peeked;
uint16_t local_port;
js_callback_t *js_cb;
} js_socket_private_t;
#ifdef __cplusplus
......
......@@ -103,8 +103,9 @@ enum {
,SYS_PROP_TEMP_PATH
,SYS_PROP_CMD_SHELL
/* last */
,SYS_PROP_LOCAL_HOSTNAME
/* last */
,SYS_PROP_NAME_SERVERS
};
static JSBool js_system_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
......@@ -116,6 +117,10 @@ static JSBool js_system_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
JSString* js_str;
ulong val;
jsrefcount rc;
JSObject *robj;
jsval jval;
str_list_t list;
int i;
js_system_private_t* sys;
if((sys = (js_system_private_t*)js_GetClassPrivate(cx,obj,&js_system_class))==NULL)
......@@ -321,6 +326,23 @@ static JSBool js_system_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
JS_RESUMEREQUEST(cx, rc);
p=str;
break;
case SYS_PROP_NAME_SERVERS:
rc=JS_SUSPENDREQUEST(cx);
robj = JS_NewArrayObject(cx, 0, NULL);
if (robj == NULL)
return JS_FALSE;
*vp = OBJECT_TO_JSVAL(robj);
list = getNameServerList();
if (list != NULL) {
for (i = 0; list[i]; i++) {
jval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, list[i]));
if (!JS_SetElement(cx, robj, i, &jval))
break;
}
}
freeNameServerList(list);
JS_RESUMEREQUEST(cx, rc);
break;
}
if(p!=NULL) { /* string property */
......@@ -440,8 +462,9 @@ static jsSyncPropertySpec js_system_properties[] = {
{ "clock_ticks_per_second", SYS_PROP_CLOCK_PER_SEC ,SYSOBJ_FLAGS, 311 },
{ "timer", SYS_PROP_TIMER ,SYSOBJ_FLAGS, 314 },
/* last */
{ "local_host_name", SYS_PROP_LOCAL_HOSTNAME ,SYSOBJ_FLAGS, 311 },
{ "name_servers", SYS_PROP_NAME_SERVERS,SYSOBJ_FLAGS, 31802 },
/* last */
{0}
};
......@@ -515,8 +538,9 @@ static char* sys_prop_desc[] = {
,"number of clock ticks per second"
,"high-resolution timer, in seconds (fractional seconds supported)"
/* INSERT new tabled properties here */
,"private host name that uniquely identifies this system on the local network"
,"array of nameservers in use by the system"
/* INSERT new tabled properties here */
/* Manually created (non-tabled) properties */
,"public host name that uniquely identifies this system on the Internet (usually the same as <i>system.inet_addr</i>)"
......
......@@ -53,6 +53,16 @@
scfg_t scfg;
void js_do_lock_input(JSContext *cx, JSBool lock)
{
return;
}
size_t js_cx_input_pending(JSContext *cx)
{
return 0;
}
void* DLLCALL js_GetClassPrivate(JSContext *cx, JSObject *obj, JSClass* cls)
{
void *ret = JS_GetInstancePrivate(cx, obj, cls, NULL);
......
......@@ -1078,7 +1078,11 @@ long js_exec(const char *fname, const char* buf, char** args)
if (abort) {
result = EXIT_FAILURE;
} else {
cb.keepGoing = FALSE;
cb.events_supported = TRUE;
exec_result = JS_ExecuteScript(js_cx, js_glob, js_script, &rval);
js_handle_events(js_cx, &cb, &terminated);
char *p;
if(buf != NULL) {
JSVALUE_TO_MSTRING(js_cx, rval, p, NULL);
......@@ -1213,6 +1217,7 @@ int main(int argc, char **argv, char** env)
cb.yield_interval=JAVASCRIPT_YIELD_INTERVAL;
cb.gc_interval=JAVASCRIPT_GC_INTERVAL;
cb.auto_terminate=TRUE;
cb.events = NULL;
DESCRIBE_COMPILER(compiler);
......
......@@ -1285,6 +1285,7 @@ JSContext* sbbs_t::js_init(JSRuntime** runtime, JSObject** glob, const char* des
js_callback.yield_interval = startup->js.yield_interval;
js_callback.terminated = &terminated;
js_callback.auto_terminate = TRUE;
js_callback.events_supported = TRUE;
bool success=false;
bool rooted=false;
......@@ -1887,16 +1888,9 @@ void input_thread(void *arg)
while(sbbs->online && sbbs->client_socket!=INVALID_SOCKET
&& node_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) {
if(pthread_mutex_lock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_LOCK,"input_thread_mutex",0);
#ifdef _WIN32 // No spy sockets
if (!socket_readable(sbbs->client_socket, 1000)) {
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
YIELD(); /* This kludge is necessary on some Linux distros */
continue; /* to allow other threads to lock the input_thread_mutex */
}
if (!socket_readable(sbbs->client_socket, 1000))
continue;
#else
#ifdef PREFER_POLL
fds[0].fd = sbbs->client_socket;
......@@ -1910,22 +1904,15 @@ void input_thread(void *arg)
nfds++;
}
if (poll(fds, nfds, 1000) < 1) {
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
YIELD(); /* This kludge is necessary on some Linux distros */
continue; /* to allow other threads to lock the input_thread_mutex */
}
if (poll(fds, nfds, 1000) < 1)
continue;
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
#endif
if(sbbs->client_socket==INVALID_SOCKET) {
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
if(sbbs->client_socket==INVALID_SOCKET)
break;
}
/* ^ ^
* \______ ______/
......@@ -1949,22 +1936,17 @@ void input_thread(void *arg)
close_socket(uspy_socket[sbbs->cfg.node_num-1]);
lprintf(LOG_NOTICE,"Closing local spy socket: %d",uspy_socket[sbbs->cfg.node_num-1]);
uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
continue;
}
sock=uspy_socket[sbbs->cfg.node_num-1];
}
else {
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
continue;
}
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
#endif
rd=RingBufFree(&sbbs->inbuf);
if(rd==0) { // input buffer full
......@@ -1974,16 +1956,16 @@ void input_thread(void *arg)
while((rd=RingBufFree(&sbbs->inbuf))==0 && time(NULL)-start<5) {
YIELD();
}
if(rd==0) { /* input buffer still full */
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
if(rd==0) /* input buffer still full */
continue;
}
}
if(rd > (int)sizeof(inbuf))
rd=sizeof(inbuf);
if(pthread_mutex_lock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_LOCK,"input_thread_mutex",0);
#ifdef USE_CRYPTLIB
if(sbbs->ssh_mode && sock==sbbs->client_socket) {
int err;
......@@ -2015,6 +1997,9 @@ void input_thread(void *arg)
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
if (rd == 0 && !socket_recvdone(sock, 0))
continue;
if(rd == SOCKET_ERROR)
{
#ifdef __unix__
......@@ -2163,10 +2148,8 @@ void passthru_thread(void* arg)
thread_up(FALSE /* setuid */);
while(sbbs->online && sbbs->passthru_socket!=INVALID_SOCKET && !terminate_server) {
if (!socket_readable(sbbs->passthru_socket, 1000)) {
YIELD(); /* This kludge is necessary on some Linux distros */
continue; /* to allow other threads to lock the input_thread_mutex */
}
if (!socket_readable(sbbs->passthru_socket, 1000))
continue;
if(sbbs->passthru_socket==INVALID_SOCKET)
break;
......
......@@ -297,6 +297,82 @@ extern int thread_suid_broken; /* NPTL is no longer broken */
#include "getmail.h"
#include "msg_id.h"
#if defined(JAVASCRIPT)
enum js_event_type {
JS_EVENT_SOCKET_READABLE_ONCE,
JS_EVENT_SOCKET_READABLE,
JS_EVENT_SOCKET_WRITABLE_ONCE,
JS_EVENT_SOCKET_WRITABLE,
JS_EVENT_SOCKET_CONNECT,
JS_EVENT_INTERVAL,
JS_EVENT_TIMEOUT,
JS_EVENT_CONSOLE_INPUT_ONCE,
JS_EVENT_CONSOLE_INPUT
};
struct js_event_interval {
uint64_t last; // The tick the last event should have triggered at
uint64_t period;
};
struct js_event_timeout {
uint64_t end; // Time the timeout expires
};
struct js_event_connect {
SOCKET sv[2];
SOCKET sock;
};
struct js_event_list {
struct js_event_list *prev;
struct js_event_list *next;
JSFunction *cb;
JSObject *cx;
union {
SOCKET sock;
struct js_event_connect connect;
struct js_event_interval interval;
struct js_event_timeout timeout;
} data;
int32 id;
enum js_event_type type;
};
struct js_runq_entry {
JSFunction *func;
JSObject *cx;
struct js_runq_entry *next;
};
struct js_listener_entry {
char *name;
JSFunction *func;
int32 id;
struct js_listener_entry *next;
};
typedef struct js_callback {
struct js_event_list *events;
struct js_runq_entry *rq_head;
struct js_runq_entry *rq_tail;
struct js_listener_entry *listeners;
volatile BOOL* terminated;
struct js_callback *parent_cb;
uint32_t counter;
uint32_t limit;
uint32_t yield_interval;
uint32_t gc_interval;
uint32_t gc_attempts;
uint32_t offline_counter;
int32 next_eid;
BOOL auto_terminate;
BOOL keepGoing;
BOOL bg;
BOOL events_supported;
} js_callback_t;
#endif
/* Synchronet Node Instance class definition */
#if defined(__cplusplus) && defined(JAVASCRIPT)
......@@ -1305,6 +1381,8 @@ extern "C" {
DLLEXPORT void DLLCALL js_EvalOnExit(JSContext*, JSObject*, js_callback_t*);
DLLEXPORT void DLLCALL js_PrepareToExecute(JSContext*, JSObject*, const char *filename, const char* startup_dir, JSObject *);
DLLEXPORT char* DLLCALL js_getstring(JSContext *cx, JSString *str);
DLLEXPORT JSBool js_handle_events(JSContext *cx, js_callback_t *cb, volatile int *terminated);
DLLEXPORT JSBool js_clear_event(JSContext *cx, uintN argc, jsval *arglist, enum js_event_type et);
/* js_system.c */
DLLEXPORT JSObject* DLLCALL js_CreateSystemObject(JSContext* cx, JSObject* parent
......@@ -1379,6 +1457,8 @@ extern "C" {
/* js_console.cpp */
JSObject* js_CreateConsoleObject(JSContext* cx, JSObject* parent);
DLLEXPORT size_t js_cx_input_pending(JSContext *cx);
DLLEXPORT void js_do_lock_input(JSContext *cx, JSBool lock);
/* js_bbs.cpp */
JSObject* js_CreateBbsObject(JSContext* cx, JSObject* parent);
......
......@@ -68,20 +68,6 @@
#define JAVASCRIPT_LOAD_PATH_LIST "load_path_list"
#define JAVASCRIPT_OPTIONS 0x810 // JSOPTION_JIT | JSOPTION_COMPILE_N_GO
struct js_callback;
typedef struct js_callback {
uint32_t counter;
uint32_t limit;
uint32_t yield_interval;
uint32_t gc_interval;
uint32_t gc_attempts;
uint32_t offline_counter;
BOOL auto_terminate;
volatile BOOL* terminated;
BOOL bg;
struct js_callback *parent_cb;
} js_callback_t;
#define JSVAL_NULL_OR_VOID(val) (JSVAL_IS_NULL(val) || JSVAL_IS_VOID(val))
#ifndef MAX
......
......@@ -1147,9 +1147,11 @@ static void js_service_thread(void* arg)
if(service->log_level >= LOG_ERR)
lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)",socket,spath);
} else {
service_client.callback.events_supported = TRUE;
js_PrepareToExecute(js_cx, js_glob, spath, /* startup_dir */NULL, js_glob);
JS_SetOperationCallback(js_cx, js_OperationCallback);
JS_ExecuteScript(js_cx, js_glob, js_script, &rval);
js_handle_events(js_cx, &service_client.callback, &terminated);
js_EvalOnExit(js_cx, js_glob, &service_client.callback);
}
JS_RemoveObjectRoot(js_cx, &js_glob);
......@@ -1259,8 +1261,10 @@ static void js_static_service_thread(void* arg)
break;
}
service_client.callback.events_supported = TRUE;
js_PrepareToExecute(js_cx, js_glob, spath, /* startup_dir */NULL, js_glob);
JS_ExecuteScript(js_cx, js_glob, js_script, &rval);
js_handle_events(js_cx, &service_client.callback, &terminated);
js_EvalOnExit(js_cx, js_glob, &service_client.callback);
JS_RemoveObjectRoot(js_cx, &js_glob);
JS_ENDREQUEST(js_cx);
......
......@@ -7157,6 +7157,7 @@ void DLLCALL web_server(void* arg)
session->js_callback.limit=startup->js.time_limit;
session->js_callback.gc_interval=startup->js.gc_interval;
session->js_callback.yield_interval=startup->js.yield_interval;
session->js_callback.events_supported = FALSE;
pthread_mutex_unlock(&session->struct_filled);
session=NULL;
served++;
......
......@@ -733,3 +733,81 @@ DLLEXPORT int xp_inet_pton(int af, const char *src, void *dst)
freeaddrinfo(res);
return 1;
}
#ifdef _WIN32
DLLEXPORT int
socketpair(int domain, int type, int protocol, SOCKET *sv)
{
union xp_sockaddr la = {0};
const int ra = 1;
SOCKET ls;
SOCKET *check;
fd_set rfd;
struct timeval tv;
size_t sa_len;
sv[0] = sv[1] = INVALID_SOCKET;
ls = socket(domain, type, protocol);
if (ls == INVALID_SOCKET)
goto fail;
switch (domain) {
case PF_INET:
if (inet_ptoaddr("127.0.0.1", &la, sizeof(la)) == NULL)
goto fail;
sa_len = sizeof(la.in);
break;
case PF_INET6:
if (inet_ptoaddr("::1", &la, sizeof(la)) == NULL)
goto fail;
sa_len = sizeof(la.in6);
break;
default:
goto fail;
}
inet_setaddrport(&la, 0);
if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (const char *)&ra, sizeof(ra)) == -1)
goto fail;
if (bind(ls, &la.addr, sa_len) == -1)
goto fail;
if (getsockname(ls, &la.addr, &sa_len) == -1)
goto fail;
if (listen(ls, 1) == -1)
goto fail;
sv[0] = socket(la.addr.sa_family, type, protocol);
if (sv[0] == INVALID_SOCKET)
goto fail;
if (connect(sv[0], &la.addr, sa_len) == -1)
goto fail;
sv[1] = accept(ls, NULL, NULL);
if (sv[1] == INVALID_SOCKET)
goto fail;
closesocket(ls);
ls = INVALID_SOCKET;
if (send(sv[1], (const char *)&sv, sizeof(sv), 0) != sizeof(sv))
goto fail;
tv.tv_sec = 0;
tv.tv_usec = 50000;
FD_ZERO(&rfd);
FD_SET(sv[0], &rfd);
if (select(sv[0] + 1, &rfd, NULL, NULL, &tv) != 1)
goto fail;
if (recv(sv[0], (char *)&check, sizeof(check), 0) != sizeof(check))
goto fail;
if (check != sv)
goto fail;
return 0;
fail:
if (ls != INVALID_SOCKET)
closesocket(ls);
if (sv[0] != INVALID_SOCKET)
closesocket(sv[0]);
sv[0] = INVALID_SOCKET;
if (sv[1] != INVALID_SOCKET)
closesocket(sv[1]);
sv[1] = INVALID_SOCKET;
return -1;
}
#endif
......@@ -230,6 +230,7 @@ DLLEXPORT void set_socket_errno(int);
DLLEXPORT int xp_inet_pton(int af, const char *src, void *dst);
#if defined(_WIN32) // mingw and WinXP's WS2_32.DLL don't have inet_pton():
#define inet_pton xp_inet_pton
DLLEXPORT int socketpair(int domain, int type, int protocol, SOCKET *sv);
#endif
/*
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment