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