whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
140 lines (125 loc) • 3.53 kB
JavaScript
var Transform = require('pipestream').Transform;
var util = require('util');
var iconv = require('iconv-lite');
var fileMgr = require('./file-mgr');
var DOCTYPE = Buffer.from('<!DOCTYPE html>\r\n');
function WhistleTransform(options) {
Transform.call(this);
options = options || {};
var value = parseInt((options.speed * 1000) / 8);
if (value > 0) {
this._speed = value;
}
if ((value = parseInt(options.delay)) > 0) {
this._delay = value;
}
this._noDoctype = options.noDoctype;
var charset = options.charset && String(options.charset);
if (!iconv.encodingExists(charset)) {
charset = 'utf8';
}
this._isHtml = options.isHtml;
this._body = getBuffer(options, 'body', charset);
this._top = getBuffer(options, 'top', charset);
this._bottom = getBuffer(options, 'bottom', charset);
if (this._body || this._top || this._bottom) {
if (options.strictHtml) {
this._strictHtml = true;
} else if (options.safeHtml) {
this._safeHtml = true;
}
}
}
function getBuffer(options, name, charset) {
var buf = options[name];
if (buf == null || Array.isArray(buf) || Buffer.isBuffer(buf)) {
return buf;
}
return iconv.encode(buf + '', charset);
}
util.inherits(WhistleTransform, Transform);
function filterHtml(list, isSafe) {
if (!Array.isArray(list)) {
return list;
}
list = list.filter(function(buf) {
if (!buf || buf._strictHtml) {
return false;
}
if (isSafe || !buf._safeHtml) {
return true;
}
return false;
});
return fileMgr.joinData(list);
}
function joinData(list) {
return Array.isArray(list) ? fileMgr.joinData(list) : list;
}
WhistleTransform.prototype.allowInject = function (chunk) {
if (!this._isHtml) {
return true;
}
var first = chunk && chunk.toString().trim()[0];
var isStrict = !first || first === '<';
if (isStrict || this._strictHtml) {
if (isStrict) {
this._top = joinData(this._top);
this._body = joinData(this._body);
this._bottom = joinData(this._bottom);
}
return isStrict;
}
var isSafe = first !== '{' && first !== '[';
if (this._safeHtml && !isSafe) {
return false;
}
this._top = filterHtml(this._top, isSafe);
this._body = filterHtml(this._body, isSafe);
this._bottom = filterHtml(this._bottom, isSafe);
return true;
};
WhistleTransform.prototype._transform = function (chunk, encoding, callback) {
var self = this;
var cb = function () {
if (self._allowInject && self._ended && self._bottom) {
chunk = chunk ? Buffer.concat([chunk, self._bottom]) : self._bottom;
self._bottom = null;
}
if (chunk && self._speed) {
setTimeout(function () {
callback(null, chunk);
}, Math.round((chunk.length * 1000) / self._speed));
} else {
callback(null, chunk);
}
};
if (!self._ended) {
self._ended = !chunk;
}
if (!self._inited) {
self._allowInject = self.allowInject(chunk);
self._inited = true;
if (self._allowInject) {
if (self._body) {
self._ended = true;
chunk = self._body;
self._body = null;
}
var top = self._top;
if (top) {
if (self._isHtml && !self._noDoctype) {
top = Buffer.concat([DOCTYPE, top]);
}
chunk = chunk ? Buffer.concat([top, chunk]) : top;
self._top = null;
}
}
return self._delay ? setTimeout(cb, self._delay) : cb();
}
if (self._ended) {
chunk = null;
}
cb();
};
module.exports = WhistleTransform;