UNPKG

whistle

Version:

HTTP, HTTPS, Websocket debugging proxy

449 lines (393 loc) 13.1 kB
var qs = require('querystring'); var iconv = require('iconv-lite'); var url = require('url'); var util = require('../util'); var extend = require('util')._extend; var Transform = require('pipestream').Transform; var WhistleTransform = util.WhistleTransform; var ReplacePatternTransform = util.ReplacePatternTransform; var ReplaceStringTransform = util.ReplaceStringTransform; var FileWriterTransform = util.FileWriterTransform; var MultiPartParser = util.MultiPartParser; var MAX_REQ_SIZE = 256 * 1024; var REQ_TYPE = { urlencoded: 'application/x-www-form-urlencoded', form: 'application/x-www-form-urlencoded', json: 'application/json', xml: 'text/xml', text: 'text/plain', upload: 'multipart/form-data', multipart: 'multipart/form-data', defaultType: 'application/octet-stream' }; /** * 处理请求数据 * * @param req:method、body、headers,top,bottom,speed、delay,charset,timeout * @param data */ function handleReq(req, data) { req.method = data.method || req.method; req.timeout = parseInt(data.timeout, 10); extend(req.headers, data.headers); if (typeof data.charset == 'string') { var type = req.headers['content-type']; var charset = '; charset=' + data.charset; if (typeof type == 'string') { req.headers['content-type'] = type.split(';')[0] + charset; } else { req.headers['content-type'] = charset; } } else { delete data.charset; } var method = req.method = typeof req.method != 'string' ? 'GET' : req.method.toUpperCase(); if (!util.hasRequestBody(method)) { delete data.top; delete data.bottom; delete data.body; } else if (data.top || data.bottom || data.body) { delete req.headers['content-length']; } !util.isEmptyObject(data) && req.addZipTransform(new WhistleTransform(data)); } function handleCors(data, cors) { if (!cors) { return; } if (cors.origin !== undefined) { util.setHeader(data, 'origin', cors.origin); } if (cors.method !== undefined) { util.setHeader(data, 'access-control-request-method', cors.method); } if (cors.headers !== undefined) { util.setHeader(data, 'access-control-request-headers', cors.headers); } } function handleAuth(data, auth) { if (!auth) { return; } var basic = []; auth.username != null && basic.push(auth.username); auth.password != null && basic.push(auth.password); if (basic = basic.join(':')) { util.setHeader(data, 'authorization', 'Basic ' + util.toBuffer(basic).toString('base64')); } } function handleCookies(data, cookies, curCookies) { var list = cookies && Object.keys(cookies); if (list && list.length) { var result = {}; if (curCookies && typeof curCookies == 'string') { curCookies.split(/;\s*/g) .forEach(function(cookie) { var index = cookie.indexOf('='); if (index == -1) { result[cookie] = null; } else { result[cookie.substring(0, index)] = cookie.substring(index + 1); } }); } list.forEach(function(name) { var value = cookies[name]; value = value && typeof value == 'object' ? value.value : value; result[util.encodeURIComponent(name)] = value ? util.encodeURIComponent(value) : value; }); cookies = Object.keys(result).map(function(name) { var value = result[name]; return name + (value == null ? '' : '=' + value); }).join('; '); util.setHeader(data, 'cookie', cookies); } } function handleParams(req, params) { var _params = params; if (!(params = params && qs.stringify(params))) { return; } var transform; if (util.isUrlEncoded(req)) { delete req.headers['content-length']; transform = new Transform(); var buffer, interrupt; transform._transform = function(chunk, encoding, callback) { if (chunk) { if (!interrupt) { buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk; chunk = null; if (buffer.length > MAX_REQ_SIZE) { interrupt = true; chunk = buffer; buffer = null; } } } else if (buffer) { var body = buffer + ''; var isGBK = body.indexOf('�') == -1; if (isGBK) { body = iconv.decode(buffer, 'GB18030'); body = util.replaceQueryString(body, params); chunk = iconv.encode(body, 'GB18030'); } else { chunk = util.toBuffer(body); } buffer = null; } else { chunk = util.toBuffer(params); } callback(null, chunk); }; req.addZipTransform(transform); } else if (util.isMultipart(req) && /boundary=(?:"([^"]+)"|([^;]+))/i.test(req.headers['content-type'])) { delete req.headers['content-length']; var boundaryStr = '--' + (RegExp.$1 || RegExp.$2); var startBoundary = util.toBuffer(boundaryStr + '\r\n'); var boundary = util.toBuffer('\r\n' + boundaryStr); var sepBoundary = util.toBuffer('\r\n' + boundaryStr + '\r\n'); var endBoudary = util.toBuffer('\r\n' + boundaryStr + '--'); var length = startBoundary.length; var sepLength = sepBoundary.length; transform = new Transform(); transform.parse = function(chunk) { var index, result, sep, data; while((index = util.indexOfList(chunk, boundary)) != -1 && ((sep = util.startWithList(chunk, sepBoundary, index)) || util.startWithList(chunk, endBoudary, index))) { data = this.parser.transform(chunk.slice(0, index)); result = result && data ? Buffer.concat([result, data]) : (result || data); if (!sep) { data = util.toMultiparts(_params, boundaryStr); result = result ? Buffer.concat([result, data]) : data; } sep = sep ? sepBoundary : endBoudary; result = result ? Buffer.concat([result, sep]) : sep; chunk = chunk.slice(index + sepLength); this.parser = new MultiPartParser(_params); } var len = chunk.length; if (len >= sepLength) { var lastIndex = len - sepLength + 1; data = this.parser.transform(chunk.slice(0, lastIndex)); chunk = chunk.slice(lastIndex); result = result && data ? Buffer.concat([result, data]) : (result || data); } this.buffer = chunk; return result; }; transform._transform = function(chunk, encoding, callback) { if (this.badMultipart) { return callback(null, chunk); } var end; if (chunk) { chunk = this.buffer ? Buffer.concat([this.buffer, chunk]) : chunk; } else { end = true; chunk = this.buffer; } if (chunk) { this.buffer = null; if (!this.parser) { if (util.startWithList(chunk, startBoundary)) { this.parser = new MultiPartParser(_params); chunk = this.parse(chunk.slice(length)); chunk = chunk ? Buffer.concat([startBoundary, chunk]) : startBoundary; } else if(end || chunk.length >= length) { this.badMultipart = true; } else { this.buffer = chunk; chunk = null; } } else { chunk = this.parse(chunk); } } if (end && this.buffer) { chunk = chunk ? Buffer.concat([chunk, this.buffer]) : this.buffer; } callback(null, chunk); }; req.addZipTransform(transform); } else if (/^https?:/.test(req.options.href)) { req.options =url.parse(util.replaceUrlQueryString(req.options.href, params)); } } function handleUrlReplace(req, params) { if (!params || !/^https?:/.test(req.options.href)) { return; } var path = req.options.href; var index = path.indexOf('://'); if (index == -1) { return; } index = path.indexOf('/', index + 3) + 1; if (index <= 0) { return; } var root = path.substring(0, index); path = path.substring(index); Object.keys(params).forEach(function(pattern) { var value = params[pattern]; value = value == null ? '' : value + ''; if (util.isOriginalRegExp(pattern) && (pattern = util.toOriginalRegExp(pattern))) { path = path.replace(pattern, value); } else if (pattern) { path = path.split(pattern).join(value); } }); req.options = url.parse(root + path); } function removeDisableProps(req) { var disable = req.disable; var headers = req.headers; if (disable.ua) { delete headers['user-agent']; } if (disable.cookie || disable.cookies || disable.reqCookie || disable.reqCookies) { delete headers.cookie; } if (disable.referer) { delete headers.referer; } if (disable.ajax) { delete headers['x-requested-with']; } if (disable.cache) { util.disableReqCache(headers); } } function handleReplace(req, replacement) { if (!util.hasRequestBody(req) || !replacement) { return; } var type = req.headers['content-type']; type = util.isUrlEncoded(req) ? 'FORM' : util.getContentType(type); if (!type || type == 'IMG') { return; } Object.keys(replacement).forEach(function(pattern) { var value = replacement[pattern]; if (util.isOriginalRegExp(pattern) && (pattern = util.toOriginalRegExp(pattern))) { req.addTextTransform(new ReplacePatternTransform(pattern, value)); } else if (pattern) { req.addTextTransform(new ReplaceStringTransform(pattern, value)); } }); } module.exports = function(req, res, next) { var reqRules = req.rules; var authObj; if (reqRules.auth) { authObj = util.getMatcherValue(reqRules.auth); if (/[\\\/]/.test(authObj)) { authObj = null; } else { var index = authObj.indexOf(':'); authObj = { username: index == -1 ? authObj : authObj.substring(0, index), password: index == -1 ? null : authObj.substring(index + 1) }; } } util.parseRuleJson([reqRules.req, reqRules.reqHeaders, reqRules.reqCookies, authObj ? null : reqRules.auth, reqRules.params, reqRules.reqCors, reqRules.reqReplace, reqRules.urlReplace], function(data, headers, cookies, auth, params, cors, replacement, urlReplace) { if (reqRules.head && reqRules.head.req) { data = extend(reqRules.head.req, data); } data = data || {}; if (headers) { data.headers = extend(data.headers || {}, headers); } if (data.headers) { data.headers = util.lowerCaseify(data.headers, req.rawHeaderNames); } if (reqRules.method) { var method = util.getMatcherValue(reqRules.method); data.method = method; } if (reqRules.reqType) { var newType = util.getMatcherValue(reqRules.reqType).split(';'); var type = newType[0]; newType[0] = (!type || type.indexOf('/') != -1) ? type : (REQ_TYPE[type] || REQ_TYPE.defaultType); type = newType.join(';'); if (type.indexOf(';') == -1) { var origType = req.headers['content-type']; if (typeof origType == 'string' && origType.indexOf(';') != -1) { origType = origType.split(';'); origType[0] = type; type = origType.join(';'); } } util.setHeader(data, 'content-type', type); } if (reqRules.reqCharset) { data.charset = util.getMatcherValue(reqRules.reqCharset); } if (reqRules.referer) { var referer = util.getMatcherValue(reqRules.referer); util.setHeader(data, 'referer', referer); } if (reqRules.accept) { var accept = util.getMatcherValue(reqRules.accept); util.setHeader(data, 'accept', accept); } if (reqRules.ua) { var ua = util.getMatcherValue(reqRules.ua); util.setHeader(data, 'user-agent', ua); } if (reqRules.etag) { var etag = util.getMatcherValue(reqRules.etag); util.setHeader(data, 'etag', etag); } var reqDelay = util.getMatcherValue(reqRules.reqDelay); reqDelay = reqDelay && parseInt(reqDelay, 10); if (reqDelay > 0) { data.delay = reqDelay; } var reqSpeed = util.getMatcherValue(reqRules.reqSpeed); reqSpeed = reqSpeed && parseFloat(reqSpeed); if (reqSpeed > 0) { data.speed = reqSpeed; } handleCookies(data, cookies, req.headers.cookie); handleAuth(data, auth || authObj); handleCors(data, cors); util.readInjectFiles(data, function(data) { util.getRuleValue([reqRules.reqBody, reqRules.reqPrepend, reqRules.reqAppend], function(reqBody, reqPrepend, reqAppend) { if (reqBody) { data.body = reqBody; } if (reqPrepend) { data.top = reqPrepend; } if (reqAppend) { data.bottom = reqAppend; } handleReq(req, data); handleParams(req, params); handleUrlReplace(req, urlReplace); util.removeUnsupportsHeaders(req.headers); removeDisableProps(req); handleReplace(req, replacement); util.getFileWriters([util.hasRequestBody(req) ? util.getRuleFile(reqRules.reqWrite) : null, util.getRuleFile(reqRules.reqWriteRaw)], function(writer, rawWriter) { if (writer) { req.addZipTransform(new FileWriterTransform(writer, req, false, false, true)); } if (rawWriter) { req.addZipTransform(new FileWriterTransform(rawWriter, req, true, false, true)); } next(); }); }, !util.hasRequestBody(req.method)); }); }); };