From f460cb53975b9f0cf8c104d5f6b5738444b98dfe Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 02:54:47 +0100 Subject: [PATCH 01/19] added support for mime module to ghost-server --- package.json | 3 ++- plugins/ghost-server.js | 23 ++++++----------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 381b935..b73c1bd 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "js-beautify": "0.2.x", "cheerio": "0.10.x", "PrettyCSS": "0.3.x", - "html": "0.0.x" + "html": "0.0.x", + "mime": "1.2.x" }, "license": "MIT", "engines": { diff --git a/plugins/ghost-server.js b/plugins/ghost-server.js index 73de295..78fc7a9 100644 --- a/plugins/ghost-server.js +++ b/plugins/ghost-server.js @@ -29,6 +29,7 @@ use that instead of 'index.html', for example: @ghost-server('/some/path','home. var PATH = require('path'); var URL = require('url'); var FS = require('fs'); +var MIME = require('mime'); exports.run = function(api){ var htdocs = api.arg(0); @@ -118,20 +119,6 @@ exports.run = function(api){ }); }; -// todo: use an actual mime types lib -var ctypes = { - '.html':'text/html', - '.shtml':'text/html', - '.htm':'text/html', - '.css':'text/css', - '.js':'text/javascript', - '.gif':'image/gif', - '.png':'image/png', - '.jpg':'image/jpeg', - '.jpeg':'image/jpeg', - '.xml':'application/xml', - '.xsl':'application/xml', -}; function isText(ctype) { return ctype.indexOf('text') > -1 || ctype.indexOf('xml') > -1; @@ -139,8 +126,10 @@ function isText(ctype) { function getContentType(path, accept){ accept = accept || 'text/plain'; accept = accept.split(',')[0]; - var ext = PATH.extname(path).toLowerCase() || accept; - var ctype = ctypes[ext]; - if(ctype && isText(ctype)){ ctype += '; charset=utf-8'; } + var ctype = MIME.lookup(path) || accept, + cset = MIME.charsets.lookup(ctype); + if(ctype && isText(ctype) && cset){ + ctype += '; charset=' + cset; + } return ctype; } From d34d75d0f5e8830fc378e740e8073deb252342c7 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 03:05:18 +0100 Subject: [PATCH 02/19] - added a second stage to the plugin-load-mechnanism stage 1: require('hoxy-' + name) stage 2: require('../plugins/' + name + '.js') - added a plugin cache --- lib/http-transaction-state.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/http-transaction-state.js b/lib/http-transaction-state.js index 582ff12..b458c7a 100644 --- a/lib/http-transaction-state.js +++ b/lib/http-transaction-state.js @@ -554,6 +554,8 @@ exports.printDocs = function(){ var INFO = require('./http-info.js'); +var pluginRegister = {}; + // represents the ongoing state of an HTTP transaction exports.HttpTransactionState = function(){ var htState = this, @@ -665,12 +667,24 @@ exports.HttpTransactionState = function(){ notifier.notify(); }, }; - var filename = '../plugins/' + name + '.js'; - try{ - var plugin = require(filename); - }catch(err){ - notifier.notify(); - throw new Error('failed to load plugin "'+name+'": '+err.message); + var plugin = pluginRegister[name] || null; + if (plugin === null) { + try{ + plugin = require('hoxy-' + name); + }catch(err){ + plugin = null; + } + pluginRegister[name] = plugin; + } + if (plugin === null) { + var filename = '../plugins/' + name + '.js'; + try{ + plugin = require(filename); + }catch(err){ + notifier.notify(); + throw new Error('failed to load plugin "'+name+'": '+err.message); + } + pluginRegister[name] = plugin; } try{ plugin.run(api); From 9ddcf9962807f1acf9d6e49c3564f8c78f7a7969 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 09:31:44 +0100 Subject: [PATCH 03/19] =?UTF-8?q?added=20read-only=20$hoxy-configuration[k?= =?UTF-8?q?ey]=20aka=20$hoxy[key]=20rule=20=E2=80=9Cthing=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http-transaction-state.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/http-transaction-state.js b/lib/http-transaction-state.js index b458c7a..d9b409c 100644 --- a/lib/http-transaction-state.js +++ b/lib/http-transaction-state.js @@ -8,6 +8,7 @@ http://github.com/greim // imports var URL = require('url'); +var opts = require('tav'); // ############################################################################# // subroutines @@ -482,6 +483,24 @@ var thingSchema = { aliases:['body'], description:'Response body in its entirety, represented as a string. Beware binary data.', }, + 'hoxy-configuration':{ + keys:1, + get:function(reqi, respi, key){ + var keys = key.split(','), + opt = opts; + while ((key = keys.shift()) && !(typeof opts[key] == 'undefined')) { + opt = opts[key] + } + return opt; + }, + map:function(reqi, respi, mapper, key) { + var val = mapper(this.get(reqi, respi, key)); + if (!val) { /* // intentionally left blank // delete respi.headers[name]; */ } + else { /* // intentionally left blank // respi.headers[name] = val; */ } + }, + availability:['request','response'], + aliases:['hoxy'], + } }; // gen docs from schema @@ -658,6 +677,9 @@ exports.HttpTransactionState = function(){ getResponseInfo:function(){ return respInf; }, + getHoxyConfiguration:function(){ + return opts; + }, state: htState, notify: function(err){ if (err && err.message) { From 9a3fddc5c94d8667278207b7ae483fbed6eebda1 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 17:45:42 +0100 Subject: [PATCH 04/19] removed node_modules from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index d7c4b02..615dd70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -node_modules/ rules/rules.txt hoxy-rules.txt From c0cce97a311103f203ab84b4c00e1bd480f34472 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 17:54:15 +0100 Subject: [PATCH 05/19] added (temporary) cheerio git-submodule --- .gitmodules | 3 +++ node_modules/cheerio | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 node_modules/cheerio diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..62863d9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "node_modules/cheerio"] + path = node_modules/cheerio + url = git@github.com:sjorek/cheerio.git diff --git a/node_modules/cheerio b/node_modules/cheerio new file mode 160000 index 0000000..2e71d37 --- /dev/null +++ b/node_modules/cheerio @@ -0,0 +1 @@ +Subproject commit 2e71d3725c37b86d406d65e049bd98ccc91d89ea From ac4896dbf8494114603a70d489cf1e34ab98bbd2 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 18:07:42 +0100 Subject: [PATCH 06/19] ignore static node-modules --- node_modules/.gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 node_modules/.gitignore diff --git a/node_modules/.gitignore b/node_modules/.gitignore new file mode 100644 index 0000000..3d9a205 --- /dev/null +++ b/node_modules/.gitignore @@ -0,0 +1,7 @@ +/await +/html +/js-beautify +/mime +/PrettyCSS +/tav +/.bin From d8a056867f23e5693e54dbe3173bde77f91e8015 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 18:10:10 +0100 Subject: [PATCH 07/19] added option to prepend or append the banner to the body --- plugins/banner.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/banner.js b/plugins/banner.js index ea4aa72..e05f4f0 100644 --- a/plugins/banner.js +++ b/plugins/banner.js @@ -43,9 +43,14 @@ exports.run = function(api){ for (var q in defs){ styleString += q+':'+defs[q]+';' } + var append = api.arg(2) || false; try { var banner = '
'+contents+'
'; - html=html.replace(/]*)>/, ''+banner); + if (append) { + html=html.replace(/<\/body([^>]*)>/, ''+banner); + } else { + html=html.replace(/]*)>/, ''+banner); + } api.setResponseBody(html); } catch (ex) { console.log("banner error: "+ex.message); From 72bc7411964f7e82cdd361501b2de7e23b22e63d Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 18:11:10 +0100 Subject: [PATCH 08/19] added xmlMode and ignoreWhitespace options to both cheerio-plugins --- plugins/cheerio-eval.js | 9 +++++---- plugins/cheerio-script.js | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/cheerio-eval.js b/plugins/cheerio-eval.js index 0013637..fa3b71f 100644 --- a/plugins/cheerio-eval.js +++ b/plugins/cheerio-eval.js @@ -25,13 +25,14 @@ var VM = require('vm'); exports.run = function(api){ var respInf = api.getResponseInfo(); if (/html/.test(respInf.headers['content-type'])) { - var code = api.arg(0); - var html = api.getResponseBody(); + var code = api.arg(0), + html = api.getResponseBody(), + opts = {xmlMode:!!api.arg(1), ignoreWhitespace:!!api.arg(2)}; try{ var script = VM.createScript(code); - var window = {$:CHEERIO.load(html)}; + var window = {$:CHEERIO.load(html, opts)}; script.runInNewContext(window); - var newHTML = window.$.html(); + var newHTML = window.$.html(null, opts); api.setResponseBody(newHTML); api.notify(); }catch(err){ diff --git a/plugins/cheerio-script.js b/plugins/cheerio-script.js index 665351c..38e3d94 100644 --- a/plugins/cheerio-script.js +++ b/plugins/cheerio-script.js @@ -34,15 +34,17 @@ function getScript(path){ exports.run = function(api){ var respInf = api.getResponseInfo(); if (/html/.test(respInf.headers['content-type'])) { - var path = api.arg(0); + var path = api.arg(0), + opts = {xmlMode:!!api.arg(1), ignoreWhitespace:!!api.arg(2)}; + getScript(path) .onkeep(function(got){ var html = api.getResponseBody(); try{ var script = VM.createScript(got.code); - var window = {$:CHEERIO.load(html)}; + var window = {$:CHEERIO.load(html, opts)}; script.runInNewContext(window); - var newHTML = window.$.html(); + var newHTML = window.$.html(null, opts); api.setResponseBody(newHTML); api.notify(); }catch(err){ From be5fd6e180fe9a2f7c1508a170e5a9e52808eec9 Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 19:44:01 +0100 Subject: [PATCH 09/19] tiny bugfix in lib/http-transaction-state.js --- lib/http-transaction-state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http-transaction-state.js b/lib/http-transaction-state.js index d9b409c..d15caba 100644 --- a/lib/http-transaction-state.js +++ b/lib/http-transaction-state.js @@ -486,7 +486,7 @@ var thingSchema = { 'hoxy-configuration':{ keys:1, get:function(reqi, respi, key){ - var keys = key.split(','), + var keys = key.split('.'), opt = opts; while ((key = keys.shift()) && !(typeof opts[key] == 'undefined')) { opt = opts[key] From f6be834f5cc9c44cd1b8e38e94383f37469895fa Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 20:13:14 +0100 Subject: [PATCH 10/19] added * options to -headers and -headers rules --- lib/http-transaction-state.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/http-transaction-state.js b/lib/http-transaction-state.js index d15caba..015260f 100644 --- a/lib/http-transaction-state.js +++ b/lib/http-transaction-state.js @@ -243,11 +243,17 @@ var thingSchema = { }, 'request-headers':{ keys:1, - get:function(reqi, respi, name){ return reqi.headers[name]; }, + get:function(reqi, respi, name){ + return name == '*' ? reqi.headers : reqi.headers[name]; + }, map:function(reqi, respi, mapper, name){ - var val = mapper(reqi.headers[name]); - if (!val) { delete reqi.headers[name]; } - else { reqi.headers[name] = val; } + if (name == '*') { + mapper(reqi.headers); + } else { + var val = mapper(reqi.headers[name]); + if (!val) { delete reqi.headers[name]; } + else { reqi.headers[name] = val; } + } }, availability:['request','response'], aliases:['qh'], @@ -393,11 +399,18 @@ var thingSchema = { }, 'response-headers':{ keys:1, - get:function(reqi, respi, name){ return respi.headers[name]; }, + get:function(reqi, respi, name){ + return name == '*' ? respi.headers : respi.headers[name]; + }, map:function(reqi, respi, mapper, name){ - var val = mapper(respi.headers[name]); - if (!val) { delete respi.headers[name]; } - else { respi.headers[name] = val; } + if (name == '*') { + mapper(respi.headers); + } else { + var val = mapper(respi.headers[name]); + if (!val) { delete respi.headers[name]; } + else { respi.headers[name] = val; } + } + }, availability:['response'], aliases:['sh'], From ad859276344d281822c4b45eaeccafe2622046fc Mon Sep 17 00:00:00 2001 From: Stephan Jorek Date: Sun, 2 Dec 2012 22:48:18 +0100 Subject: [PATCH 11/19] bugfix in banner plugin --- plugins/banner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/banner.js b/plugins/banner.js index e05f4f0..4114d15 100644 --- a/plugins/banner.js +++ b/plugins/banner.js @@ -47,7 +47,7 @@ exports.run = function(api){ try { var banner = '
'+contents+'
'; if (append) { - html=html.replace(/<\/body([^>]*)>/, '
'+banner); + html=html.replace(/<\/body([^>]*)>/, banner+''); } else { html=html.replace(/]*)>/, ''+banner); } From 53de3617b1f1b879b72dfb2ab190f588de8c80b4 Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Wed, 24 Apr 2013 17:59:04 -0400 Subject: [PATCH 12/19] added ability to require and run hoxy inside of another nodejs project --- .idea/.name | 1 + .idea/encodings.xml | 5 + .idea/hoxy.iml | 9 + .idea/misc.xml | 5 + .idea/modules.xml | 9 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + .idea/workspace.xml | 408 ++++++++++++++++++++++++++++++++ hoxy.js | 252 +------------------- index.js | 25 ++ runner.js | 247 +++++++++++++++++++ 11 files changed, 727 insertions(+), 246 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/encodings.xml create mode 100644 .idea/hoxy.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 index.js create mode 100644 runner.js diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..d2d5d0b --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +hoxy \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/hoxy.iml b/.idea/hoxy.iml new file mode 100644 index 0000000..6b8184f --- /dev/null +++ b/.idea/hoxy.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1162f43 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9d9b6f2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..bb1f243 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1366836182596 + 1366836182596 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hoxy.js b/hoxy.js index 5cbc39a..9bd7023 100644 --- a/hoxy.js +++ b/hoxy.js @@ -33,253 +33,13 @@ var opts = require('tav').set({ }, }, "Hoxy, the web-hacking proxy.\nusage: node hoxy.js [--debug] [--rules=file] [--port=port]"); -var HTTP = require('http'); -var URL = require('url'); -var HTS = require('./lib/http-transaction-state.js'); -var Q = require('./lib/asynch-queue.js'); -var RULES = require('./lib/rules.js'); -var RDB = require('./lib/rules-db.js'); -var proxyPort = opts.port || 8080; -var debug = opts.debug; - -if (opts.args.length && parseInt(opts.args[0])) { - console.error('!!! old: please use --port=something to specify port. thank you. exiting.'); - process.exit(1); -} - -if (opts.stage && !(/^[a-z0-9-]+(\.[a-z0-9-]+)*(:\d+)?$/i).test(opts.stage)) { - console.error('error: stage must be of the form or : exiting.'); - process.exit(1); -} - -// done -// ############################################################################# -// startup version check - -(function(){ - /* - Requiring v0.4.x or higher because we depend on http client connection pooling. - Also because of jsdom. - */ - var requiredVer = [0,4]; - var actualVer = process.version.split('.').map(function(s){ - return parseInt(s.replace(/\D/g,'')); - }); - if (!(function(){ - for (var i=0;i requiredVer[i]) { - return true; - } - } - return true; - })() && !opts['no-version-check']){ - console.error('Error: '+projectName+' requires Node.js v'+requiredVer.join('.') - +' or higher but you\'re running '+process.version); - console.error('Use --no-version-check to attempt to run '+projectName+' without this check.'); - console.error('Quitting.'); - process.exit(1); - } -})(); - -// done -// ############################################################################# -// environment proxy config - -var useProxy, envProxy = process.env.HTTP_PROXY || process.env.http_proxy; -if(useProxy = !!envProxy) { - if(!/^http:\/\//.test(envProxy)) { envProxy = 'http://'+envProxy; } - var pEnvProxy = URL.parse(envProxy); - console.log('hoxy using proxy '+envProxy); -} - -// done -// ############################################################################# -// error handling and subs - -// truncates a URL -function turl(url){ - if (url.length > 64) { - var pUrl = URL.parse(url); - var nurl = pUrl.protocol + '//' + pUrl.host; - nurl += '/...'+url.substring(url.length-10, url.length); - url = nurl; - } - return url; -} - -// debug-flag-aware error logger -function logError(err, errType, url) { - if (debug) { - console.error(errType+' error: '+turl(url)+': '+err.message); - } -} - -// end err handling -// ############################################################################# -// create proxy server - -var stripRqHdrs = [ - 'accept-encoding', - 'proxy-connection', // causes certain sites to hang - 'proxy-authorization', -]; - -HTTP.createServer(function handleRequest(request, response) { - - // Handle the case where people put http://hoxy.host:port/ directly into - // their browser's location field, rather than configuring hoxy.host:port in - // their browser's proxy settings. In such cases, the URL won't have a - // scheme or host. This is what staging mode is for, since it provides a - // scheme and host in the absence of one. - if (/^\//.test(request.url) && opts.stage){ - request.url = 'http://'+opts.stage+request.url; - request.headers.host = opts.stage; - } - - // strip out certain request headers - stripRqHdrs.forEach(function(name){ - delete request.headers[name]; - }); - - // grab fresh copy of rules for each request - var rules = RDB.getRules(); - - var hts = new HTS.HttpTransactionState(); - hts.setRequest(request, function(reqInfo){ - // entire request body is now loaded - // process request phase rules - var reqPhaseRulesQ = new Q.AsynchQueue(); - rules.filter(function(rule){ - return rule.phase==='request'; - }).forEach(function(rule){ - reqPhaseRulesQ.push(rule.getExecuter(hts)); - }); - - reqPhaseRulesQ.execute(function(){ - - // request phase rules are now done processing. try to send the - // response directly without hitting up the server for a response. - // obviously, this will only work if the response was somehow - // already populated, e.g. during request-phase rule processing - // otherwise it throws an error and we send for the response. - try { - hts.doResponse(sendResponse); - } catch (ex) { - - // make sure content-length jibes - if (!reqInfo.body.length) { - reqInfo.headers['content-length'] = 0; - } else if (reqInfo.headers['content-length']!==undefined) { - var len = 0; - reqInfo.body.forEach(function(chunk){ - len += chunk.length; - }); - reqInfo.headers['content-length'] = len; - } else { /* node will send a chunked request */ } - - // make sure host header jibes - if(reqInfo.headers.host){ - reqInfo.headers.host = reqInfo.hostname; - if (reqInfo.port !== 80) { - reqInfo.headers.host += ':'+reqInfo.port; - } - } - - // this method makes node re-use client objects if needed - var proxyReq = HTTP.request({ - method: reqInfo.method, - host: useProxy ? pEnvProxy.hostname : reqInfo.hostname, - port: useProxy ? pEnvProxy.port : reqInfo.port, - path: useProxy ? reqInfo.absUrl : reqInfo.url, - headers: reqInfo.headers, - },function(proxyResp){ - hts.setResponse(proxyResp, sendResponse); - }); - - // write out to dest server - var reqBodyQ = new Q.AsynchQueue(); - reqInfo.body.forEach(function(chunk){ - reqBodyQ.push(function(notifier){ - proxyReq.write(chunk); - setTimeout(function(){ - notifier.notify(); - }, reqInfo.throttle); - }); - }); - reqBodyQ.execute(function(){ - proxyReq.end(); - }); - } - - // same subroutine used in either case - function sendResponse(respInfo) { - - // entire response body is now available - // do response phase rule processing - var respPhaseRulesQ = new Q.AsynchQueue(); - rules.filter(function(rule){ - return rule.phase==='response'; - }).forEach(function(rule){ - respPhaseRulesQ.push(rule.getExecuter(hts)); - }); - - respPhaseRulesQ.execute(function(){ - - // response phase rules are now done processing - // send response, but first drop this little hint - // to let client know something fishy's going on - respInfo.headers['x-manipulated-by'] = projectName; - - // shore up the content-length situation - if (!respInfo.body.length) { - respInfo.headers['content-length'] = 0; - } else if (respInfo.headers['content-length']!==undefined) { - var len = 0; - respInfo.body.forEach(function(chunk){ - len += chunk.length; - }); - respInfo.headers['content-length'] = len; - } else { /* node will send a chunked response */ } - - // write headers, queue up body writes, send, end and done - response.writeHead(respInfo.statusCode, respInfo.headers); - var respBodyQ = new Q.AsynchQueue(); - respInfo.body.forEach(function(chunk){ - respBodyQ.push(function(notifier){ - response.write(chunk); - setTimeout(function(){ - notifier.notify(); - }, respInfo.throttle); - }); - }); - respBodyQ.execute(function(){ - response.end(); - }); - }); - } - }); - }); -}).listen(proxyPort); - -// done creating proxy -// ############################################################################# -// print a nice info message - -console.log(projectName+' running at localhost:'+proxyPort); -if (opts.stage) console.log('staging mode is on. http://localhost:'+proxyPort+'/ will stage for http://'+opts.stage+'/'); -if (debug) console.log('debug mode is on.'); - -// done with message -// ############################################################################# -// start catching errors +require('./runner.js')(projectName, opts); // helps to ensure the proxy stays up and running process.on('uncaughtException',function(err){ - if (debug) { - console.error('uncaught exception: '+err.message); - console.error(err.stack); - } -}); + if (debug) { + console.error('uncaught exception: '+err.message); + console.error(err.stack); + } +}); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..2ee5294 --- /dev/null +++ b/index.js @@ -0,0 +1,25 @@ +module.exports = function(opts){ + + opts = opts || {}; + var defaults = { + projectName: 'Hoxy', + debug: false, //Turn on debug mode, print errors to console. + rules: './hoxy-rules.txt', //Specify rules file location + port: 8080, //Specify port to listen on. + stage: false, //Host that proxy will act as a staging server for. + 'no-version-check': false //Attempt to run proxy without the startup version check. + }; + + for(var key in defaults){ + if(defaults.hasOwnProperty(key)){ + if(typeof opts[key] == 'undefined'){ + opts[key] = defaults[key]; + } + } + } + var projectName = opts.projectName; + delete opts.projectName; + require('./runner.js')(projectName, opts); + + +} \ No newline at end of file diff --git a/runner.js b/runner.js new file mode 100644 index 0000000..2aa5aac --- /dev/null +++ b/runner.js @@ -0,0 +1,247 @@ +var HTTP = require('http'); +var URL = require('url'); +var HTS = require('./lib/http-transaction-state.js'); +var Q = require('./lib/asynch-queue.js'); +var RULES = require('./lib/rules.js'); +var RDB = require('./lib/rules-db.js'); + + +module.exports = function(projectName, opts){ + var proxyPort = opts.port || 8080; + var debug = opts.debug; + + if (opts.args.length && parseInt(opts.args[0])) { + console.error('!!! old: please use --port=something to specify port. thank you. exiting.'); + process.exit(1); + } + + if (opts.stage && !(/^[a-z0-9-]+(\.[a-z0-9-]+)*(:\d+)?$/i).test(opts.stage)) { + console.error('error: stage must be of the form or : exiting.'); + process.exit(1); + } + + // done + // ############################################################################# + // startup version check + + (function(){ + /* + Requiring v0.4.x or higher because we depend on http client connection pooling. + Also because of jsdom. + */ + var requiredVer = [0,4]; + var actualVer = process.version.split('.').map(function(s){ + return parseInt(s.replace(/\D/g,'')); + }); + if (!(function(){ + for (var i=0;i requiredVer[i]) { + return true; + } + } + return true; + })() && !opts['no-version-check']){ + console.error('Error: '+projectName+' requires Node.js v'+requiredVer.join('.') + +' or higher but you\'re running '+process.version); + console.error('Use --no-version-check to attempt to run '+projectName+' without this check.'); + console.error('Quitting.'); + process.exit(1); + } + })(); + + // done + // ############################################################################# + // environment proxy config + + var useProxy, envProxy = process.env.HTTP_PROXY || process.env.http_proxy; + if(useProxy = !!envProxy) { + if(!/^http:\/\//.test(envProxy)) { envProxy = 'http://'+envProxy; } + var pEnvProxy = URL.parse(envProxy); + console.log('hoxy using proxy '+envProxy); + } + + // done + // ############################################################################# + // error handling and subs + + // truncates a URL + function turl(url){ + if (url.length > 64) { + var pUrl = URL.parse(url); + var nurl = pUrl.protocol + '//' + pUrl.host; + nurl += '/...'+url.substring(url.length-10, url.length); + url = nurl; + } + return url; + } + + // debug-flag-aware error logger + function logError(err, errType, url) { + if (debug) { + console.error(errType+' error: '+turl(url)+': '+err.message); + } + } + + // end err handling + // ############################################################################# + // create proxy server + + var stripRqHdrs = [ + 'accept-encoding', + 'proxy-connection', // causes certain sites to hang + 'proxy-authorization', + ]; + + HTTP.createServer(function handleRequest(request, response) { + + // Handle the case where people put http://hoxy.host:port/ directly into + // their browser's location field, rather than configuring hoxy.host:port in + // their browser's proxy settings. In such cases, the URL won't have a + // scheme or host. This is what staging mode is for, since it provides a + // scheme and host in the absence of one. + if (/^\//.test(request.url) && opts.stage){ + request.url = 'http://'+opts.stage+request.url; + request.headers.host = opts.stage; + } + + // strip out certain request headers + stripRqHdrs.forEach(function(name){ + delete request.headers[name]; + }); + + // grab fresh copy of rules for each request + var rules = RDB.getRules(); + + var hts = new HTS.HttpTransactionState(); + hts.setRequest(request, function(reqInfo){ + // entire request body is now loaded + // process request phase rules + var reqPhaseRulesQ = new Q.AsynchQueue(); + rules.filter(function(rule){ + return rule.phase==='request'; + }).forEach(function(rule){ + reqPhaseRulesQ.push(rule.getExecuter(hts)); + }); + + reqPhaseRulesQ.execute(function(){ + + // request phase rules are now done processing. try to send the + // response directly without hitting up the server for a response. + // obviously, this will only work if the response was somehow + // already populated, e.g. during request-phase rule processing + // otherwise it throws an error and we send for the response. + try { + hts.doResponse(sendResponse); + } catch (ex) { + + // make sure content-length jibes + if (!reqInfo.body.length) { + reqInfo.headers['content-length'] = 0; + } else if (reqInfo.headers['content-length']!==undefined) { + var len = 0; + reqInfo.body.forEach(function(chunk){ + len += chunk.length; + }); + reqInfo.headers['content-length'] = len; + } else { /* node will send a chunked request */ } + + // make sure host header jibes + if(reqInfo.headers.host){ + reqInfo.headers.host = reqInfo.hostname; + if (reqInfo.port !== 80) { + reqInfo.headers.host += ':'+reqInfo.port; + } + } + + // this method makes node re-use client objects if needed + var proxyReq = HTTP.request({ + method: reqInfo.method, + host: useProxy ? pEnvProxy.hostname : reqInfo.hostname, + port: useProxy ? pEnvProxy.port : reqInfo.port, + path: useProxy ? reqInfo.absUrl : reqInfo.url, + headers: reqInfo.headers, + },function(proxyResp){ + hts.setResponse(proxyResp, sendResponse); + }); + + // write out to dest server + var reqBodyQ = new Q.AsynchQueue(); + reqInfo.body.forEach(function(chunk){ + reqBodyQ.push(function(notifier){ + proxyReq.write(chunk); + setTimeout(function(){ + notifier.notify(); + }, reqInfo.throttle); + }); + }); + reqBodyQ.execute(function(){ + proxyReq.end(); + }); + } + + // same subroutine used in either case + function sendResponse(respInfo) { + + // entire response body is now available + // do response phase rule processing + var respPhaseRulesQ = new Q.AsynchQueue(); + rules.filter(function(rule){ + return rule.phase==='response'; + }).forEach(function(rule){ + respPhaseRulesQ.push(rule.getExecuter(hts)); + }); + + respPhaseRulesQ.execute(function(){ + + // response phase rules are now done processing + // send response, but first drop this little hint + // to let client know something fishy's going on + respInfo.headers['x-manipulated-by'] = projectName; + + // shore up the content-length situation + if (!respInfo.body.length) { + respInfo.headers['content-length'] = 0; + } else if (respInfo.headers['content-length']!==undefined) { + var len = 0; + respInfo.body.forEach(function(chunk){ + len += chunk.length; + }); + respInfo.headers['content-length'] = len; + } else { /* node will send a chunked response */ } + + // write headers, queue up body writes, send, end and done + response.writeHead(respInfo.statusCode, respInfo.headers); + var respBodyQ = new Q.AsynchQueue(); + respInfo.body.forEach(function(chunk){ + respBodyQ.push(function(notifier){ + response.write(chunk); + setTimeout(function(){ + notifier.notify(); + }, respInfo.throttle); + }); + }); + respBodyQ.execute(function(){ + response.end(); + }); + }); + } + }); + }); + }).listen(proxyPort); + + // done creating proxy + // ############################################################################# + // print a nice info message + + console.log(projectName+' running at localhost:'+proxyPort); + if (opts.stage) console.log('staging mode is on. http://localhost:'+proxyPort+'/ will stage for http://'+opts.stage+'/'); + if (debug) console.log('debug mode is on.'); + + // done with message + // ############################################################################# + // start catching errors + + +} \ No newline at end of file From 0af155f620bb350043c057bffa67779d29ccc94a Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Thu, 25 Apr 2013 12:05:30 -0400 Subject: [PATCH 13/19] trying to make npm be ok with installing from my github url --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index b73c1bd..a7002a0 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,6 @@ "bin": { "hoxy": "./bin/hoxy" }, - "repository": { - "type": "git", - "url": "git://github.com/greim/hoxy.git" - }, "keywords": [ "web", "traffic", From ac7544ce391cac47fe77ce3a85de8c36519371ec Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Thu, 25 Apr 2013 12:42:33 -0400 Subject: [PATCH 14/19] removed some deps on tav --- hoxy.js | 4 ++ lib/rules-db.js | 107 ++++++++++++++++++++++++++---------------------- runner.js | 8 ++-- test_require.js | 8 ++++ 4 files changed, 72 insertions(+), 55 deletions(-) create mode 100644 test_require.js diff --git a/hoxy.js b/hoxy.js index 9bd7023..37f3f48 100644 --- a/hoxy.js +++ b/hoxy.js @@ -33,6 +33,10 @@ var opts = require('tav').set({ }, }, "Hoxy, the web-hacking proxy.\nusage: node hoxy.js [--debug] [--rules=file] [--port=port]"); +if (opts.args.length && parseInt(opts.args[0])) { + console.error('!!! old: please use --port=something to specify port. thank you. exiting.'); + process.exit(1); +} require('./runner.js')(projectName, opts); diff --git a/lib/rules-db.js b/lib/rules-db.js index 63bcfd0..5289629 100644 --- a/lib/rules-db.js +++ b/lib/rules-db.js @@ -8,61 +8,68 @@ var FS = require('fs'); var PATH = require('path'); var RULES = require('./rules.js'); -var opts = require('tav'); + var xmpFile = PATH.normalize(__dirname + '/../rules/rules-example.txt'); -var file = opts.rules; + var rules = []; var overrideRules = false; -FS.exists(file, function(fileExists){ - FS.exists(xmpFile, function(xmpFileExists){ - if (!fileExists) { - if (!xmpFileExists) { - console.error('error: '+file+' doesn\'t exist, exiting'); - process.exit(1); - } else { - try { - var xmpCont = FS.readFileSync(xmpFile, 'utf8'); - FS.writeFileSync(file, xmpCont, 'utf8'); - console.log('copying '+xmpFile+' to '+file); - } catch(err){ - console.error(err.message); - process.exit(1); - } - } - } - var opts = {persistent: true, interval: 500}; - FS.watchFile(file, opts, loadRules); - loadRules(); - }); -}); -var emt = /\S/; -var comment = /^\s*#/; -function loadRules(cur, prev) { - if (cur && (cur.mtime.getTime() === prev.mtime.getTime())) { - return; - } - FS.readFile(file, 'utf8', function(err, data){ - if (err) { throw err; } - rules = data.split('\n') - .filter(function(ruleStr){ - return emt.test(ruleStr) && !comment.test(ruleStr); - }) - .map(function(ruleStr){ - try { return new RULES.Rule(ruleStr); } - catch (ex) { - console.error( - 'error parsing '+file+': '+ex.message - +'\nignoring entire rule, please fix' - ); - return false; - } - }) - .filter(function(rule){ - return !!rule; - }); - }); + +exports.init = function(opts){ + + var file = opts.rules; + + FS.exists(file, function(fileExists){ + FS.exists(xmpFile, function(xmpFileExists){ + if (!fileExists) { + if (!xmpFileExists) { + console.error('error: '+file+' doesn\'t exist, exiting'); + process.exit(1); + } else { + try { + var xmpCont = FS.readFileSync(xmpFile, 'utf8'); + FS.writeFileSync(file, xmpCont, 'utf8'); + console.log('copying '+xmpFile+' to '+file); + } catch(err){ + console.error(err.message); + process.exit(1); + } + } + } + var opts = {persistent: true, interval: 500}; + FS.watchFile(file, opts, loadRules); + loadRules(); + }); + }); + + var emt = /\S/; + var comment = /^\s*#/; + function loadRules(cur, prev) { + if (cur && (cur.mtime.getTime() === prev.mtime.getTime())) { + return; + } + FS.readFile(file, 'utf8', function(err, data){ + if (err) { throw err; } + rules = data.split('\n') + .filter(function(ruleStr){ + return emt.test(ruleStr) && !comment.test(ruleStr); + }) + .map(function(ruleStr){ + try { return new RULES.Rule(ruleStr); } + catch (ex) { + console.error( + 'error parsing '+file+': '+ex.message + +'\nignoring entire rule, please fix' + ); + return false; + } + }) + .filter(function(rule){ + return !!rule; + }); + }); + } } exports.getRules = function(){return overrideRules || rules;}; diff --git a/runner.js b/runner.js index 2aa5aac..b646edf 100644 --- a/runner.js +++ b/runner.js @@ -10,16 +10,14 @@ module.exports = function(projectName, opts){ var proxyPort = opts.port || 8080; var debug = opts.debug; - if (opts.args.length && parseInt(opts.args[0])) { - console.error('!!! old: please use --port=something to specify port. thank you. exiting.'); - process.exit(1); - } if (opts.stage && !(/^[a-z0-9-]+(\.[a-z0-9-]+)*(:\d+)?$/i).test(opts.stage)) { console.error('error: stage must be of the form or : exiting.'); process.exit(1); } + RDB.init(opts); + // done // ############################################################################# // startup version check @@ -244,4 +242,4 @@ module.exports = function(projectName, opts){ // start catching errors -} \ No newline at end of file +}; \ No newline at end of file diff --git a/test_require.js b/test_require.js new file mode 100644 index 0000000..3439412 --- /dev/null +++ b/test_require.js @@ -0,0 +1,8 @@ +var hoxy = require('./index.js') + +hoxy({ + projectName: 'Test require', + debug: true, + port: 8080, + stage: 'programmingdrunk.com:80' +}); From 3f74f6428d89d2a3bf62c8867d675378b215351f Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Thu, 25 Apr 2013 15:01:29 -0400 Subject: [PATCH 15/19] added some plugins that i want to have --- plugins/change-location.js | 36 ++++++++++++++++++++++++++++++++++ plugins/rewrite-static.js | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 plugins/change-location.js create mode 100644 plugins/rewrite-static.js diff --git a/plugins/change-location.js b/plugins/change-location.js new file mode 100644 index 0000000..53eb261 --- /dev/null +++ b/plugins/change-location.js @@ -0,0 +1,36 @@ +/** + * using a regexp replacement, replace the location header value as well as do a global replacement in the response body + * + * useful for repointing static file refs to your proxy + * + * use: + * response: @change-location('programmingdrunk\.com', 'localhost:8080') + * @param api + */ + +exports.run = function(api){ + var res = api.getResponseInfo(); + + + var fromRegexp = new RegExp(api.arg(0), 'gi'); + var to = api.arg(1); + + if(res.headers['location']){ + res.headers['location'] = res.headers['location'].replace(fromRegexp, to); + } + + var ct = api.getResponseInfo().headers['content-type']; + if (ct && ct.indexOf('html')>-1) { + var html = api.getResponseBody(); + try { + api.setResponseBody(html.replace(fromRegexp, to)); + api.notify(); + } catch (ex) { + api.notify(ex); + } + } else { + api.notify(); + } + + +}; diff --git a/plugins/rewrite-static.js b/plugins/rewrite-static.js new file mode 100644 index 0000000..9c9bc17 --- /dev/null +++ b/plugins/rewrite-static.js @@ -0,0 +1,40 @@ +var HTTP = require('http'); +var URL = require('url'); +var HTS = require('../lib/http-transaction-state.js'); + +/** + * + * specialized version of internal-redirect + * + * say the static url for a resource is: //localhost:8080/static/2931fd4/css/stuff.css + * you have a local server ready to server up that4 resource at //localhost:7777/css/stuff.css + * + * then do this: + * + * request: if $ext eq "css", @rewrite-static('http://localhost:7777', '/staticVer/12342354/css', '/css') + * + * @param api + */ +exports.run = function(api) { + var url = api.arg(0); + var replaceRegexp = new RegExp(api.arg(1)); + var replaceWith = api.arg(2); + + var reqUrl = api.getRequestInfo().url.replace(replaceRegexp, replaceWith); + + var pUrl = URL.parse(url); + var port = parseInt(pUrl.port) || 80; + var hostname = pUrl.hostname; + + var client = HTTP.createClient(port, hostname); + + var clientReq = client.request('GET', reqUrl, { host: hostname }); + clientReq.end(); + clientReq.on('response', function(resp) { + var hts = new HTS.HttpTransactionState(); + hts.setResponse(resp, function(respInf){ + api.setResponseInfo(respInf); + api.notify(); + }); + }); +}; \ No newline at end of file From 9661d12cacea00b237d48f4ceecd2ce9316fcb77 Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Mon, 29 Apr 2013 12:33:38 -0400 Subject: [PATCH 16/19] remove .idea --- .idea/.name | 1 - .idea/encodings.xml | 5 - .idea/hoxy.iml | 9 - .idea/misc.xml | 5 - .idea/modules.xml | 9 - .idea/scopes/scope_settings.xml | 5 - .idea/vcs.xml | 7 - .idea/workspace.xml | 408 -------------------------------- 8 files changed, 449 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/hoxy.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/scopes/scope_settings.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index d2d5d0b..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -hoxy \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/hoxy.iml b/.idea/hoxy.iml deleted file mode 100644 index 6b8184f..0000000 --- a/.idea/hoxy.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 1162f43..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9d9b6f2..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b..0000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c80f219..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index bb1f243..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1366836182596 - 1366836182596 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From fdd2e168a5ea13efe40bb937b4b3d990e08cd08d Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Tue, 28 May 2013 12:30:05 -0400 Subject: [PATCH 17/19] add ability to specify a dir of where to find plugins --- lib/http-transaction-state.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/http-transaction-state.js b/lib/http-transaction-state.js index 015260f..afb6fb3 100644 --- a/lib/http-transaction-state.js +++ b/lib/http-transaction-state.js @@ -113,6 +113,10 @@ var actionSchema = { }, }; +exports.pluginPath = function(name) { + return '../plugins/' + name + '.js' +} + exports.getActionValidator = function(action) { if (!actionSchema[action]) { return false; } return { @@ -716,8 +720,13 @@ exports.HttpTransactionState = function(){ try{ plugin = require(filename); }catch(err){ - notifier.notify(); - throw new Error('failed to load plugin "'+name+'": '+err.message); + var filename = exports.pluginPath(name); + try{ + plugin = require(filename); + }catch(err){ + notifier.notify(); + throw new Error('failed to load plugin "'+name+'": '+err.message); + } } pluginRegister[name] = plugin; } From d12680d873c55a270663072b19d8093e792e7438 Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Tue, 28 May 2013 12:31:38 -0400 Subject: [PATCH 18/19] ability to specify a path where custom plugins are found --- index.js | 3 ++- runner.js | 2 ++ test-plugin.js | 9 +++++++++ test_require.js | 6 +++++- 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 test-plugin.js diff --git a/index.js b/index.js index 2ee5294..437c19e 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,8 @@ module.exports = function(opts){ rules: './hoxy-rules.txt', //Specify rules file location port: 8080, //Specify port to listen on. stage: false, //Host that proxy will act as a staging server for. - 'no-version-check': false //Attempt to run proxy without the startup version check. + 'no-version-check': false, //Attempt to run proxy without the startup version check. + pluginPath: null //function that takes a name and returns path to plugin that can be required }; for(var key in defaults){ diff --git a/runner.js b/runner.js index b646edf..73b6335 100644 --- a/runner.js +++ b/runner.js @@ -16,6 +16,8 @@ module.exports = function(projectName, opts){ process.exit(1); } + HTS.pluginPath = opts.pluginPath || function(name){ return name}; + RDB.init(opts); // done diff --git a/test-plugin.js b/test-plugin.js new file mode 100644 index 0000000..0ffcb94 --- /dev/null +++ b/test-plugin.js @@ -0,0 +1,9 @@ +/* +this is just a plugin that tests the ability to add a path of where plugins are found. see test.js +*/ + +exports.run = function(api) { + api.notify() +}; + + diff --git a/test_require.js b/test_require.js index 3439412..95a7f87 100644 --- a/test_require.js +++ b/test_require.js @@ -1,8 +1,12 @@ var hoxy = require('./index.js') +var path = require('path') hoxy({ projectName: 'Test require', debug: true, port: 8080, - stage: 'programmingdrunk.com:80' + stage: 'programmingdrunk.com:80', + pluginPath: function(name) { + return path.join(__dirname, name + '.js'); + } }); From 3c1e637229ddeeb0803e729d32c8e7564f8f9375 Mon Sep 17 00:00:00 2001 From: Misha Koryak Date: Thu, 21 Nov 2013 17:31:09 -0500 Subject: [PATCH 19/19] add http-transaction-state as a 2nd arg to plugin's run method --- lib/http-transaction-state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http-transaction-state.js b/lib/http-transaction-state.js index afb6fb3..a541bd3 100644 --- a/lib/http-transaction-state.js +++ b/lib/http-transaction-state.js @@ -731,7 +731,7 @@ exports.HttpTransactionState = function(){ pluginRegister[name] = plugin; } try{ - plugin.run(api); + plugin.run(api, module.exports); }catch(err){ // WARNING: plugin is always responsible to notify, even if it throws errors throw new Error('error running plugin "'+name+'": '+err.message);