UNPKG

whistle

Version:

HTTP, HTTPS, Websocket debugging proxy

331 lines (291 loc) 8 kB
var iconv = require('iconv-lite'); var zlib = require('zlib'); var STATUS_CODES = require('http').STATUS_CODES || {}; var CRLF = new Buffer('\r\n'); var TYPES = ['whistle', 'Fiddler']; var CHARSET_RE = /charset=([\w-]+)/i; function getCharset(type) { return CHARSET_RE.test(type) ? RegExp.$1 : ''; } function dechunkify(body) { var result = []; var index; while((index = indexOfBuffer(body, CRLF)) > 0) { var size = parseInt(body.slice(0, index).toString(), 16) || 0; if (!size) { break; } index += 2; result.push(body.slice(index, index += size)); body = body.slice(index + 2); } return result.length ? Buffer.concat(result) : body; } function unzip(encoding, body, callback) { if (body && typeof encoding === 'string') { encoding = encoding.trim().toLowerCase(); if (encoding === 'gzip') { return zlib.gunzip(body, callback); } if (encoding === 'deflate') { return zlib.inflate(body, function(err, data) { err ? zlib.inflateRaw(body, callback) : callback(null, data); }); } } return callback(null, body); } function getMethod(method) { if (typeof method !== 'string') { return 'GET'; } return method.toUpperCase() || 'GET'; } function getHeadersRaw(headers, rawHeaderNames) { var result = []; if (headers) { rawHeaderNames = rawHeaderNames || {}; Object.keys(headers).forEach(function(name) { var value = headers[name]; var key = rawHeaderNames[name] || name; if (!Array.isArray(value)) { result.push(key + ': ' + value); return; } value.forEach(function(val) { result.push(key + ': ' + val); }); }); } return result; } function decodeRaw(type, headers, body) { var charset = getCharset(type).toLowerCase(); var raw = new Buffer(headers.join('\r\n') + '\r\n\r\n'); if (body) { if (charset && charset !== 'utf8' && charset !== 'utf-8') { try { return Buffer.concat([raw, iconv.encode(body, charset)]); } catch(e) {} } raw = Buffer.concat([raw, new Buffer(body)]); } return raw; } function removeEncodingFields(headers) { if (headers) { delete headers['content-encoding']; delete headers['transfer-encoding']; } } function getReqRaw(req) { removeEncodingFields(req.headers); var headers = getHeadersRaw(req.headers, req.rawHeaderNames); var url = String(req.url || '').replace(/^ws/, 'http'); headers.unshift([getMethod(req.method), url, 'HTTP/1.1'].join(' ')); return decodeRaw(req.headers && req.headers['content-type'], headers, req.body); } exports.getReqRaw = getReqRaw; function getResRaw(res) { removeEncodingFields(res.headers); var headers = getHeadersRaw(res.headers, res.rawHeaderNames); var statusCode = res.statusCode === 'aborted' ? 502 : res.statusCode; var statusMessage = !statusCode ? '' : res.statusMessage || STATUS_CODES[statusCode] || 'unknown'; headers.unshift(['HTTP/1.1', statusCode, statusMessage].join(' ')); return decodeRaw(res.headers && res.headers['content-type'], headers, res.body); } exports.getResRaw = getResRaw; var BODY_SEP = new Buffer('\r\n\r\n'); function getBodyOffset(raw) { var index = indexOfBuffer(raw, BODY_SEP); if (index !== -1) { return [index, index + 4]; } } function indexOfBuffer(buf, subBuf, start) { start = start || 0; if (buf.indexOf) { return buf.indexOf(subBuf, start); } var subLen = subBuf.length; if (subLen) { for (var i = start, len = buf.length - subLen; i <= len; i++) { var j = 0; for (; j < subLen; j++) { if (subBuf[j] !== buf[i + j]) { break; } } if (j == subLen) { return i; } } } return -1; } function getBody(body, headers, callback) { if (body) { var chunked = headers['transfer-encoding']; if (typeof chunked === 'string') { chunked = chunked.trim().toLowerCase(); } if (chunked === 'chunked') { body = dechunkify(body); } } unzip(headers['content-encoding'], body, function(err, result) { if (!err && result) { body = result; } var charset = getCharset(headers['content-type']); if (charset) { try { body = iconv.decode(body, charset); } catch(e) {} body += ''; } else { var str = body.toString(); if (str.indexOf('�') != -1) { body = iconv.decode(body, 'gbk'); } else { body = str; } } callback(body); }); } function parseRawData(raw, callback, isRes) { var offset = getBodyOffset(raw); var body = ''; if (offset) { body = raw.slice(offset[1]); raw = raw.slice(0, offset[0]); } raw = raw.toString(); raw = raw.trim().split(/\r\n?|\n/g); var firstLine = raw.shift().split(/\s+/g); var headers = {}; var rawHeaderNames = {}; raw.forEach(function(line) { var index = line.indexOf(':'); if (index === -1) { return; } var name = line.substring(0, index).trim(); if (!name) { return; } var value = headers[name]; var val = line.substring(index + 1).trim(); if (value != null) { if (Array.isArray(value)) { value.push(val); } else { value = [value, val]; } } else { value = val; } var key = name.toLowerCase(); rawHeaderNames[key] = name; headers[key] = value; }); var type = 'TEXT'; if (isRes) { type = getType(headers['content-type']); } getBody(type ? body : '', headers, function(body) { callback({ firstLine: firstLine, headers: headers, size: body ? body.length : 0, rawHeaderNames: rawHeaderNames, body: body }); }); } function getReq(raw, callback) { raw = parseRawData(raw, function(raw) { var method = raw.firstLine[0] || 'GET'; callback(raw ? { method: method, httpVersion: '1.1', rawHeaderNames: raw.rawHeaderNames, url: raw.firstLine[1], headers: raw.headers, size: /^get$/i.test(method) ? 0 : raw.size, body: raw.body } : null); }); } exports.getReq = getReq; function getType(type) { if (typeof type == 'string') { type = type.toLowerCase(); if (type.indexOf('javascript') != -1) { return 'JS'; } if (type.indexOf('css') != -1) { return 'CSS'; } if (type.indexOf('html') != -1) { return 'HTML'; } if (type.indexOf('json') != -1) { return 'JSON'; } if (type.indexOf('xml') != -1) { return 'XML'; } if (type.indexOf('text/') != -1) { return 'TEXT'; } } else if (!type) { return 'TEXT'; } return null; } function getRes(raw, callback) { parseRawData(raw, function(raw) { callback(raw ? { statusCode: raw.firstLine[1], httpVersion: '1.1', rawHeaderNames: raw.rawHeaderNames, statusMessage: raw.firstLine[2], headers: raw.headers, size: raw.size, body: raw.body } : {}); }, true); } exports.getRes = getRes; function parseJSON(str) { try { return JSON.parse(str); } catch(e) {} } exports.parseJSON = parseJSON; function padding(num) { return num < 10 ? '0' + num : num; } function getFilename(type) { if (!~TYPES.indexOf(type)) { type = 'whistle'; } var date = new Date(); var filename = [date.getFullYear(), padding(date.getMonth() + 1), padding(date.getDate())].join('-') + '_' + [padding(date.getHours()), padding(date.getMinutes()), padding(date.getSeconds())].join('-'); return filename + (type === 'whistle' ? '.txt' : '.saz'); } exports.getFilename = getFilename; var ONE_MINUTE = 60 * 1000; function toISOString(time) { var date = new Date(); var offet = -date.getTimezoneOffset(); time += offet * ONE_MINUTE; offet /= 60; time = time >= 0 ? new Date(time) : new Date(); return time.toISOString().slice(0, -1) + '0000' + (offet >= 0 ? '+' : '-') + padding(Math.abs(offet)) + ':00'; } exports.toISOString = toISOString;