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

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