You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
5.7 KiB
186 lines
5.7 KiB
"use strict"; |
|
|
|
var utils = require("./lib/utils"); |
|
var debug = require("debug")("resp-mod"); |
|
|
|
function RespModifier (opts) { |
|
|
|
// options |
|
opts = opts || {}; |
|
opts.blacklist = utils.toArray(opts.blacklist) || []; |
|
opts.whitelist = utils.toArray(opts.whitelist) || []; |
|
opts.hostBlacklist = utils.toArray(opts.hostBlacklist) || []; |
|
opts.rules = opts.rules || []; |
|
opts.ignore = opts.ignore || opts.excludeList || utils.defaultIgnoreTypes; |
|
|
|
// helper functions |
|
opts.regex = (function () { |
|
var matches = opts.rules.map(function (item) { |
|
return item.match.source; |
|
}).join("|"); |
|
return new RegExp(matches); |
|
})(); |
|
|
|
var respMod = this; |
|
|
|
respMod.opts = opts; |
|
respMod.middleware = respModifierMiddleware; |
|
respMod.update = function (key, value) { |
|
if (respMod.opts[key]) { |
|
respMod.opts[key] = value; |
|
} |
|
return respMod; |
|
}; |
|
|
|
function respModifierMiddleware(req, res, next) { |
|
|
|
if (res._respModifier) { |
|
debug("Reject req", req.url); |
|
return next(); |
|
} |
|
debug("Accept req", req.url); |
|
|
|
res._respModifier = true; |
|
|
|
var writeHead = res.writeHead; |
|
var runPatches = true; |
|
var write = res.write; |
|
var end = res.end; |
|
var singlerules = utils.isWhiteListedForSingle(req.url, respMod.opts.rules); |
|
|
|
var withoutSingle = respMod.opts.rules.filter(function (rule) { |
|
if (rule.paths && rule.paths.length) { |
|
return false; |
|
} |
|
return true; |
|
}); |
|
|
|
/** |
|
* Exit early for blacklisted domains |
|
*/ |
|
if (respMod.opts.hostBlacklist.indexOf(req.headers.host) > -1) { |
|
return next(); |
|
} |
|
|
|
if (singlerules.length) { |
|
modifyResponse(singlerules, true); |
|
} else { |
|
if (utils.isWhitelisted(req.url, respMod.opts.whitelist)) { |
|
modifyResponse(withoutSingle, true); |
|
} else { |
|
if (!utils.hasAcceptHeaders(req) || utils.inBlackList(req.url, respMod.opts)) { |
|
debug("Black listed or no text/html headers", req.url); |
|
return next(); |
|
} else { |
|
modifyResponse(withoutSingle); |
|
} |
|
} |
|
} |
|
|
|
next(); |
|
|
|
/** |
|
* Actually do the overwrite |
|
* @param {Array} rules |
|
* @param {Boolean} [force] - if true, will always attempt to perform |
|
* an overwrite - regardless of whether it appears to be HTML or not |
|
*/ |
|
function modifyResponse(rules, force) { |
|
|
|
req.headers["accept-encoding"] = "identity"; |
|
|
|
function restore() { |
|
res.writeHead = writeHead; |
|
res.write = write; |
|
res.end = end; |
|
} |
|
|
|
res.push = function (chunk) { |
|
res.data = (res.data || "") + chunk; |
|
}; |
|
|
|
res.write = function (string, encoding) { |
|
|
|
if (!runPatches) { |
|
return write.call(res, string, encoding); |
|
} |
|
|
|
if (string !== undefined) { |
|
var body = string instanceof Buffer ? string.toString(encoding) : string; |
|
// If this chunk appears to be valid, push onto the res.data stack |
|
if (force || (utils.isHtml(body) || utils.isHtml(res.data))) { |
|
res.push(body); |
|
} else { |
|
restore(); |
|
return write.call(res, string, encoding); |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
res.writeHead = function () { |
|
if (!runPatches) { |
|
return writeHead.apply(res, arguments); |
|
} |
|
|
|
var headers = arguments[arguments.length - 1]; |
|
|
|
if (typeof headers === "object") { |
|
for (var name in headers) { |
|
if (/content-length/i.test(name)) { |
|
delete headers[name]; |
|
} |
|
} |
|
} |
|
|
|
if (res.getHeader("content-length")) { |
|
res.removeHeader("content-length"); |
|
} |
|
|
|
writeHead.apply(res, arguments); |
|
}; |
|
|
|
res.end = function (string, encoding) { |
|
|
|
res.data = res.data || ""; |
|
|
|
if (typeof string === "string") { |
|
res.data += string; |
|
} |
|
|
|
if (string instanceof Buffer) { |
|
res.data += string.toString(); |
|
} |
|
|
|
if (!runPatches) { |
|
return end.call(res, string, encoding); |
|
} |
|
|
|
// Check if our body is HTML, and if it does not already have the snippet. |
|
if (force || utils.isHtml(res.data) && !utils.snip(res.data)) { |
|
// Include, if necessary, replacing the entire res.data with the included snippet. |
|
res.data = utils.applyRules(rules, res.data, req, res); |
|
runPatches = false; |
|
} |
|
if (res.data !== undefined && !res._header) { |
|
res.setHeader("content-length", Buffer.byteLength(res.data, encoding)); |
|
} |
|
end.call(res, res.data, encoding); |
|
}; |
|
} |
|
} |
|
|
|
return respMod; |
|
} |
|
|
|
module.exports = function (opts) { |
|
var resp = new RespModifier(opts); |
|
return resp.middleware; |
|
}; |
|
|
|
module.exports.create = function (opts) { |
|
var resp = new RespModifier(opts); |
|
return resp; |
|
}; |
|
|
|
module.exports.utils = utils;
|
|
|