diff --git a/README.md b/README.md index f97cf3d..8aa011e 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,8 @@ Note: The **=** sign is important here. Replacing the equal sign with a space wi * **firewall**: An object (map) of type { allow: [ list... ], deny: [ list... ] }, where [ list... ] means an array of strings or regular expressions which are tested against the domain connected to. ONLY One of the 2 (deny or allow) shall be used depending on which array has values. The one that is non-empty shall be used. If both are empty (default), all connections are allowed. If both are non-empty, then the ALLOW list is used and ONLY connections to the domains listed in ALLOW are connected to **(default: { allow: [ ], deny: [ ] })** +* **echo_origin_in_cors_header**: Set to `true` if you want to use the value of the `Origin` request header instead of `*` in the `Access-Control-Allow-Origin` response header. + * **route_filter**: If the route attribute is set, allow connections ONLY if the route attribute matches the regex below **(default: /.\*/)** * **pidgin_compatible**: Set to 'true' if you want to be able to use pidgin (any any other libpurple based client) with node-xmpp-bosh. If you set this to 'true', then you lose the ability to create multiple streams on a session **(default: false)** diff --git a/bosh.conf.example.js b/bosh.conf.example.js index 18bd3c0..41bfb71 100644 --- a/bosh.conf.example.js +++ b/bosh.conf.example.js @@ -36,6 +36,7 @@ exports.config = { // The maximum number of active streams allowed per BOSH session max_streams_per_session: 8, + // Headers applied to every http response http_headers: { }, // @@ -66,6 +67,10 @@ exports.config = { deny: [ /* 'gmail.com' */ ] }, + // Set to 'true' if you want to use the value of the Origin request header + // instead of '*' in the 'Access-Control-Allow-Origin' response header. + echo_origin_in_cors_header: false, + // If the route attribute is set, allow connections ONLY if the // route attribute matches the regex below. This can be used in // conjunction with 'firewall' to disallow connections if an IP diff --git a/src/bosh-headers.js b/src/bosh-headers.js new file mode 100644 index 0000000..9fc9077 --- /dev/null +++ b/src/bosh-headers.js @@ -0,0 +1,75 @@ +"use strict"; + +var dutil = require('./dutil.js'); +var path = require('path'); + +var filename = path.basename(path.normalize(__filename)); +var log = require('./log.js').getLogger(filename); + +function add_to_headers(dest, src) { + var acah = dest['Access-Control-Allow-Headers'].split(', '); + var k; + for (k in src) { + if (src.hasOwnProperty(k)) { + dest[k] = src[k]; + acah.push(k); + } + } + dest['Access-Control-Allow-Headers'] = acah.join(', '); +} + +function BOSHHeaders(options) { + var _options = options; + + var _echo_origin_in_cors_header = _options.echo_origin_in_cors_header || false; + log.debug('ECHO_ORIGIN_IN_CORS_HEADER: %s', _echo_origin_in_cors_header); + + var _default_headers = {}; + _default_headers['GET'] = { + 'Content-Type': 'text/html; charset=UTF-8', + 'Cache-Control': 'no-cache, no-store', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, x-requested-with, Set-Cookie', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Max-Age': '14400' + }; + _default_headers['POST'] = { + 'Content-Type': 'text/xml; charset=UTF-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, x-requested-with, Set-Cookie', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Max-Age': '14400' + }; + _default_headers['OPTIONS'] = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, x-requested-with, Set-Cookie', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Max-Age': '14400' + }; + + if (_options.http_headers) { + add_to_headers(_default_headers['GET'], _options.http_headers); + add_to_headers(_default_headers['POST'], _options.http_headers); + add_to_headers(_default_headers['OPTIONS'], _options.http_headers); + } + + ['GET', 'POST', 'OPTIONS'].forEach(function(method) { + var headers = _default_headers[method]; + Object.keys(headers).forEach(function(header_key) { + log.debug('HTTP_RESPONSE_HEADERS:%s::%s => %s', method, header_key, headers[header_key]); + }) + }); + + this.make_headers = function(http_method, request_headers) { + var _headers = {}; + dutil.copy(_headers, _default_headers[http_method]); + + if (_echo_origin_in_cors_header && request_headers['origin']) { + _headers['Access-Control-Allow-Origin'] = request_headers['origin']; + } + + return _headers; + } +} + +exports.BOSHHeaders = BOSHHeaders; diff --git a/src/bosh.js b/src/bosh.js index be3c2c7..84cda7f 100644 --- a/src/bosh.js +++ b/src/bosh.js @@ -72,6 +72,7 @@ var log = require('./log.js').getLogger(filename); // * max_inactivity // * http_socket_keepalive // * http_headers +// * echo_origin_in_cors_header // @@ -230,7 +231,7 @@ exports.createServer = function (options) { //Called when the 'end' event for the request is fired by the HTTP request handler function bosh_request_handler(res, node) { if (!node) { - res.writeHead(200, bosh_options.HTTP_POST_RESPONSE_HEADERS); + res.writeHead(200, bosh_options.http_post_response_headers(res.request_headers)); res.end(helper.$terminate({ condition: 'bad-request' }).toString()); return; } diff --git a/src/helper.js b/src/helper.js index dd57b1b..9bec9db 100644 --- a/src/helper.js +++ b/src/helper.js @@ -56,18 +56,6 @@ function $terminate(attrs) { // Begin HTTP header helpers -function add_to_headers(dest, src) { - var acah = dest['Access-Control-Allow-Headers'].split(', '); - var k; - for (k in src) { - if (src.hasOwnProperty(k)) { - dest[k] = src[k]; - acah.push(k); - } - } - dest['Access-Control-Allow-Headers'] = acah.join(', '); -} - function JSONPResponseProxy(req, res) { this.req_ = req; this.res_ = res; @@ -213,7 +201,6 @@ function is_session_creation_packet(node) { // End misc. helpers -exports.add_to_headers = add_to_headers; exports.JSONPResponseProxy = JSONPResponseProxy; exports.route_parse = route_parse; exports.save_terminate_condition_for_wait_time = save_terminate_condition_for_wait_time; diff --git a/src/http-server.js b/src/http-server.js index b618852..3d24704 100644 --- a/src/http-server.js +++ b/src/http-server.js @@ -187,7 +187,7 @@ function HTTPServer(port, host, stat_func, system_info_func, function handle_options(req, res, u) { if (req.method === 'OPTIONS') { - res.writeHead(200, bosh_options.HTTP_OPTIONS_RESPONSE_HEADERS); + res.writeHead(200, bosh_options.http_options_response_headers(req.headers)); res.end(); return false; } @@ -206,8 +206,7 @@ function HTTPServer(port, host, stat_func, system_info_func, function handle_get_statistics(req, res, u) { var ppos = u.pathname.search(bosh_options.path); if (req.method === 'GET' && ppos !== -1 && !u.query.hasOwnProperty('data')) { - var _headers = { }; - dutil.copy(_headers, bosh_options.HTTP_GET_RESPONSE_HEADERS); + var _headers = bosh_options.http_get_response_headers(req.headers) _headers['Content-Type'] = 'text/html; charset=utf-8'; res.writeHead(200, _headers); @@ -227,8 +226,7 @@ function HTTPServer(port, host, stat_func, system_info_func, var spos = path.basename(u.pathname).search("sysinfo"); if (req.method === 'GET' && ppos !== -1 && spos === 0) { - var _headers = { }; - dutil.copy(_headers, bosh_options.HTTP_GET_RESPONSE_HEADERS); + var _headers = bosh_options.http_get_response_headers(req.headers); _headers['Content-Type'] = 'text/html; charset=utf-8'; if (bosh_options.SYSTEM_INFO_PASSWORD.length === 0) { @@ -268,7 +266,7 @@ function HTTPServer(port, host, stat_func, system_info_func, // function handle_get_crossdomainXML(req, res, u) { if (req.method === 'GET' && req.url === "/crossdomain.xml") { - res.writeHead(200, bosh_options.HTTP_GET_RESPONSE_HEADERS); + res.writeHead(200, bosh_options.http_get_response_headers(req.headers)); var crossdomain = ''; crossdomain += ''; crossdomain += ''; @@ -283,8 +281,7 @@ function HTTPServer(port, host, stat_func, system_info_func, function handle_unhandled_request(req, res, u) { log.trace("Invalid request, method: %s path: %s", req.method, u.pathname); - var _headers = { }; - dutil.copy(_headers, bosh_options.HTTP_POST_RESPONSE_HEADERS); + var _headers = bosh_options.http_post_response_headers(req.headers); _headers['Content-Type'] = 'text/plain; charset=utf-8'; res.writeHead(404, _headers); res.end(); diff --git a/src/options.js b/src/options.js index 7eaf75c..b523be6 100644 --- a/src/options.js +++ b/src/options.js @@ -27,59 +27,19 @@ var helper = require('./helper.js'); var path = require('path'); +var bheaders = require('./bosh-headers.js'); + var filename = path.basename(path.normalize(__filename)); var log = require('./log.js').getLogger(filename); function BOSH_Options(opts) { var _opts = opts; - log.debug("Node.js version: %s", process.version); - - this.HTTP_GET_RESPONSE_HEADERS = { - 'Content-Type': 'text/html; charset=UTF-8', - 'Cache-Control': 'no-cache, no-store', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'Content-Type, x-requested-with, Set-Cookie', - 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', - 'Access-Control-Max-Age': '14400' - }; - - this.HTTP_POST_RESPONSE_HEADERS = { - 'Content-Type': 'text/xml; charset=UTF-8', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'Content-Type, x-requested-with, Set-Cookie', - 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', - 'Access-Control-Max-Age': '14400' - }; - - this.HTTP_OPTIONS_RESPONSE_HEADERS = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'Content-Type, x-requested-with, Set-Cookie', - 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', - 'Access-Control-Max-Age': '14400' - }; - - if (_opts.http_headers) { - helper.add_to_headers(this.HTTP_GET_RESPONSE_HEADERS, _opts.http_headers); - helper.add_to_headers(this.HTTP_POST_RESPONSE_HEADERS, _opts.http_headers); - helper.add_to_headers(this.HTTP_OPTIONS_RESPONSE_HEADERS, _opts.http_headers); - } - - (function debug_print_HTTP_headers(header_types) { - header_types.forEach(function(header_type) { - var hobj = this[header_type]; - Object.keys(hobj).forEach(function(header_key) { - log.debug("%s::%s => %s", header_type, header_key, hobj[header_key]); - }); - }.bind(this)); - }.bind(this))(['HTTP_GET_RESPONSE_HEADERS', - 'HTTP_POST_RESPONSE_HEADERS', - 'HTTP_OPTIONS_RESPONSE_HEADERS'] - ); + log.debug("Node.js version: %s", process.version); this.path = _opts.path; - log.debug("path: %s", this.path); + log.debug("path: %s", this.path); // The maximum number of bytes that the BOSH server will // "hold" from the client. @@ -116,6 +76,11 @@ function BOSH_Options(opts) { log.debug("MAX_STREAMS_PER_SESSION: %s", this.MAX_STREAMS_PER_SESSION); log.debug("PIDGIN_COMPATIBLE: %s", this.PIDGIN_COMPATIBLE); log.debug("SYSTEM_INFO_PASSWORD: %s", (this.SYSTEM_INFO_PASSWORD ? "[SET]" : "[NOT SET]")); + + var bosh_headers = new bheaders.BOSHHeaders(_opts); + this.http_get_response_headers = bosh_headers.make_headers.bind(undefined, 'GET'); + this.http_post_response_headers = bosh_headers.make_headers.bind(undefined, 'POST'); + this.http_options_response_headers = bosh_headers.make_headers.bind(undefined, 'OPTIONS'); } exports.BOSH_Options = BOSH_Options; diff --git a/src/response.js b/src/response.js index 9b5af7a..a766be5 100644 --- a/src/response.js +++ b/src/response.js @@ -82,7 +82,7 @@ Response.prototype = { } // According to the spec. we need to send a Content-Length header this._res.setHeader("Content-Length", Buffer.byteLength(msg, 'utf8')); - this._res.writeHead(200, this._options.HTTP_POST_RESPONSE_HEADERS); + this._res.writeHead(200, this._options.http_post_response_headers(this._res.request_headers)); this._res.end(msg); log.debug("%s SENT(%s): %s", this._sid, this.rid, dutil.replace_promise(dutil.trim_promise(msg), '\n', ' ')); }, @@ -105,4 +105,4 @@ Response.prototype = { } }; -exports.Response = Response; \ No newline at end of file +exports.Response = Response;