var Key = { UP: 38, DOWN: 40, PAGE_UP: 33, PAGE_DOWN: 34, HOME: 36, END: 35, LEFT: 37, RIGHT: 39, ENTER: 13, ESC: 27, TAB: 9, SPACE: 32, BACKSPACE: 8, }; // Add A-Z for (var i = 65; i <= 90; i++) { Key[String.fromCharCode(i)] = i; } var formatSize = function (bytes) { var size = bytes; var prefix = ["B", "KB", "MB", "GB", "TB"]; var i = 0; while (Math.abs(size) >= 1024 && i < prefix.length - 1) { i++; size = size / 1024; } return (Math.floor(size * 100) / 100.0).toFixed(2) + prefix[i]; }; var formatTimeDelta = function (milliseconds) { var time = milliseconds; var prefix = ["ms", "s", "min", "h"]; var div = [1000, 60, 60]; var i = 0; while (Math.abs(time) >= div[i] && i < div.length) { time = time / div[i]; i++; } return Math.round(time) + prefix[i]; }; var formatTimeStamp = function (seconds) { var ts = (new Date(seconds * 1000)).toISOString(); return ts.replace("T", " ").replace("Z", ""); }; function EventEmitter() { this.listeners = {}; } EventEmitter.prototype.emit = function (event) { if (!(event in this.listeners)) { return; } var args = Array.prototype.slice.call(arguments, 1); this.listeners[event].forEach(function (listener) { listener.apply(this, args); }.bind(this)); }; EventEmitter.prototype.addListener = function (events, f) { events.split(" ").forEach(function (event) { this.listeners[event] = this.listeners[event] || []; this.listeners[event].push(f); }.bind(this)); }; EventEmitter.prototype.removeListener = function (events, f) { if (!(events in this.listeners)) { return false; } events.split(" ").forEach(function (event) { var index = this.listeners[event].indexOf(f); if (index >= 0) { this.listeners[event].splice(index, 1); } }.bind(this)); }; function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } var xsrf = $.param({_xsrf: getCookie("_xsrf")}); //Tornado XSRF Protection. jQuery.ajaxPrefilter(function (options) { if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") { if (options.data) { options.data += ("&" + xsrf); } else { options.data = xsrf; } } }); // Log AJAX Errors $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { var message = jqXHR.responseText; console.error(message, arguments); EventLogActions.add_event(thrownError + ": " + message); window.alert(message); }); const PayloadSources = { VIEW: "view", SERVER: "server" }; function Dispatcher() { this.callbacks = []; } Dispatcher.prototype.register = function (callback) { this.callbacks.push(callback); }; Dispatcher.prototype.unregister = function (callback) { var index = this.callbacks.indexOf(callback); if (index >= 0) { this.callbacks.splice(index, 1); } }; Dispatcher.prototype.dispatch = function (payload) { console.debug("dispatch", payload); for (var i = 0; i < this.callbacks.length; i++) { this.callbacks[i](payload); } }; AppDispatcher = new Dispatcher(); AppDispatcher.dispatchViewAction = function (action) { action.source = PayloadSources.VIEW; this.dispatch(action); }; AppDispatcher.dispatchServerAction = function (action) { action.source = PayloadSources.SERVER; this.dispatch(action); }; var ActionTypes = { // Connection CONNECTION_OPEN: "connection_open", CONNECTION_CLOSE: "connection_close", CONNECTION_ERROR: "connection_error", // Stores SETTINGS_STORE: "settings", EVENT_STORE: "events", FLOW_STORE: "flows", }; var StoreCmds = { ADD: "add", UPDATE: "update", REMOVE: "remove", RESET: "reset" }; var ConnectionActions = { open: function () { AppDispatcher.dispatchViewAction({ type: ActionTypes.CONNECTION_OPEN }); }, close: function () { AppDispatcher.dispatchViewAction({ type: ActionTypes.CONNECTION_CLOSE }); }, error: function () { AppDispatcher.dispatchViewAction({ type: ActionTypes.CONNECTION_ERROR }); } }; var SettingsActions = { update: function (settings) { jQuery.ajax({ type: "PUT", url: "/settings", data: settings }); /* //Facebook Flux: We do an optimistic update on the client already. AppDispatcher.dispatchViewAction({ type: ActionTypes.SETTINGS_STORE, cmd: StoreCmds.UPDATE, data: settings }); */ } }; var EventLogActions_event_id = 0; var EventLogActions = { add_event: function (message) { AppDispatcher.dispatchViewAction({ type: ActionTypes.EVENT_STORE, cmd: StoreCmds.ADD, data: { message: message, level: "web", id: "viewAction-" + EventLogActions_event_id++ } }); } }; var FlowActions = { accept: function (flow) { jQuery.post("/flows/" + flow.id + "/accept"); }, accept_all: function(){ jQuery.post("/flows/accept"); }, "delete": function(flow){ jQuery.ajax({ type:"DELETE", url: "/flows/" + flow.id }); }, duplicate: function(flow){ jQuery.post("/flows/" + flow.id + "/duplicate"); }, replay: function(flow){ jQuery.post("/flows/" + flow.id + "/replay"); }, update: function (flow) { AppDispatcher.dispatchViewAction({ type: ActionTypes.FLOW_STORE, cmd: StoreCmds.UPDATE, data: flow }); }, clear: function(){ jQuery.post("/clear"); } }; Query = { FILTER: "f", HIGHLIGHT: "h", SHOW_EVENTLOG: "e" }; Filt = (function() { /* * Generated by PEG.js 0.8.0. * * http://pegjs.majda.cz/ */ function peg$subclass(child, parent) { function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); } function SyntaxError(message, expected, found, offset, line, column) { this.message = message; this.expected = expected; this.found = found; this.offset = offset; this.line = line; this.column = column; this.name = "SyntaxError"; } peg$subclass(SyntaxError, Error); function parse(input) { var options = arguments.length > 1 ? arguments[1] : {}, peg$FAILED = {}, peg$startRuleFunctions = { start: peg$parsestart }, peg$startRuleFunction = peg$parsestart, peg$c0 = { type: "other", description: "filter expression" }, peg$c1 = peg$FAILED, peg$c2 = function(orExpr) { return orExpr; }, peg$c3 = [], peg$c4 = function() {return trueFilter; }, peg$c5 = { type: "other", description: "whitespace" }, peg$c6 = /^[ \t\n\r]/, peg$c7 = { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" }, peg$c8 = { type: "other", description: "control character" }, peg$c9 = /^[|&!()~"]/, peg$c10 = { type: "class", value: "[|&!()~\"]", description: "[|&!()~\"]" }, peg$c11 = { type: "other", description: "optional whitespace" }, peg$c12 = "|", peg$c13 = { type: "literal", value: "|", description: "\"|\"" }, peg$c14 = function(first, second) { return or(first, second); }, peg$c15 = "&", peg$c16 = { type: "literal", value: "&", description: "\"&\"" }, peg$c17 = function(first, second) { return and(first, second); }, peg$c18 = "!", peg$c19 = { type: "literal", value: "!", description: "\"!\"" }, peg$c20 = function(expr) { return not(expr); }, peg$c21 = "(", peg$c22 = { type: "literal", value: "(", description: "\"(\"" }, peg$c23 = ")", peg$c24 = { type: "literal", value: ")", description: "\")\"" }, peg$c25 = function(expr) { return binding(expr); }, peg$c26 = "~a", peg$c27 = { type: "literal", value: "~a", description: "\"~a\"" }, peg$c28 = function() { return assetFilter; }, peg$c29 = "~e", peg$c30 = { type: "literal", value: "~e", description: "\"~e\"" }, peg$c31 = function() { return errorFilter; }, peg$c32 = "~q", peg$c33 = { type: "literal", value: "~q", description: "\"~q\"" }, peg$c34 = function() { return noResponseFilter; }, peg$c35 = "~s", peg$c36 = { type: "literal", value: "~s", description: "\"~s\"" }, peg$c37 = function() { return responseFilter; }, peg$c38 = "true", peg$c39 = { type: "literal", value: "true", description: "\"true\"" }, peg$c40 = function() { return trueFilter; }, peg$c41 = "false", peg$c42 = { type: "literal", value: "false", description: "\"false\"" }, peg$c43 = function() { return falseFilter; }, peg$c44 = "~c", peg$c45 = { type: "literal", value: "~c", description: "\"~c\"" }, peg$c46 = function(s) { return responseCode(s); }, peg$c47 = "~d", peg$c48 = { type: "literal", value: "~d", description: "\"~d\"" }, peg$c49 = function(s) { return domain(s); }, peg$c50 = "~h", peg$c51 = { type: "literal", value: "~h", description: "\"~h\"" }, peg$c52 = function(s) { return header(s); }, peg$c53 = "~hq", peg$c54 = { type: "literal", value: "~hq", description: "\"~hq\"" }, peg$c55 = function(s) { return requestHeader(s); }, peg$c56 = "~hs", peg$c57 = { type: "literal", value: "~hs", description: "\"~hs\"" }, peg$c58 = function(s) { return responseHeader(s); }, peg$c59 = "~m", peg$c60 = { type: "literal", value: "~m", description: "\"~m\"" }, peg$c61 = function(s) { return method(s); }, peg$c62 = "~t", peg$c63 = { type: "literal", value: "~t", description: "\"~t\"" }, peg$c64 = function(s) { return contentType(s); }, peg$c65 = "~tq", peg$c66 = { type: "literal", value: "~tq", description: "\"~tq\"" }, peg$c67 = function(s) { return requestContentType(s); }, peg$c68 = "~ts", peg$c69 = { type: "literal", value: "~ts", description: "\"~ts\"" }, peg$c70 = function(s) { return responseContentType(s); }, peg$c71 = "~u", peg$c72 = { type: "literal", value: "~u", description: "\"~u\"" }, peg$c73 = function(s) { return url(s); }, peg$c74 = { type: "other", description: "integer" }, peg$c75 = null, peg$c76 = /^['"]/, peg$c77 = { type: "class", value: "['\"]", description: "['\"]" }, peg$c78 = /^[0-9]/, peg$c79 = { type: "class", value: "[0-9]", description: "[0-9]" }, peg$c80 = function(digits) { return parseInt(digits.join(""), 10); }, peg$c81 = { type: "other", description: "string" }, peg$c82 = "\"", peg$c83 = { type: "literal", value: "\"", description: "\"\\\"\"" }, peg$c84 = function(chars) { return chars.join(""); }, peg$c85 = "'", peg$c86 = { type: "literal", value: "'", description: "\"'\"" }, peg$c87 = void 0, peg$c88 = /^["\\]/, peg$c89 = { type: "class", value: "[\"\\\\]", description: "[\"\\\\]" }, peg$c90 = { type: "any", description: "any character" }, peg$c91 = function(char) { return char; }, peg$c92 = "\\", peg$c93 = { type: "literal", value: "\\", description: "\"\\\\\"" }, peg$c94 = /^['\\]/, peg$c95 = { type: "class", value: "['\\\\]", description: "['\\\\]" }, peg$c96 = /^['"\\]/, peg$c97 = { type: "class", value: "['\"\\\\]", description: "['\"\\\\]" }, peg$c98 = "n", peg$c99 = { type: "literal", value: "n", description: "\"n\"" }, peg$c100 = function() { return "\n"; }, peg$c101 = "r", peg$c102 = { type: "literal", value: "r", description: "\"r\"" }, peg$c103 = function() { return "\r"; }, peg$c104 = "t", peg$c105 = { type: "literal", value: "t", description: "\"t\"" }, peg$c106 = function() { return "\t"; }, peg$currPos = 0, peg$reportedPos = 0, peg$cachedPos = 0, peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, peg$maxFailPos = 0, peg$maxFailExpected = [], peg$silentFails = 0, peg$result; if ("startRule" in options) { if (!(options.startRule in peg$startRuleFunctions)) { throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); } peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; } function text() { return input.substring(peg$reportedPos, peg$currPos); } function offset() { return peg$reportedPos; } function line() { return peg$computePosDetails(peg$reportedPos).line; } function column() { return peg$computePosDetails(peg$reportedPos).column; } function expected(description) { throw peg$buildException( null, [{ type: "other", description: description }], peg$reportedPos ); } function error(message) { throw peg$buildException(message, null, peg$reportedPos); } function peg$computePosDetails(pos) { function advance(details, startPos, endPos) { var p, ch; for (p = startPos; p < endPos; p++) { ch = input.charAt(p); if (ch === "\n") { if (!details.seenCR) { details.line++; } details.column = 1; details.seenCR = false; } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { details.line++; details.column = 1; details.seenCR = true; } else { details.column++; details.seenCR = false; } } } if (peg$cachedPos !== pos) { if (peg$cachedPos > pos) { peg$cachedPos = 0; peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; } advance(peg$cachedPosDetails, peg$cachedPos, pos); peg$cachedPos = pos; } return peg$cachedPosDetails; } function peg$fail(expected) { if (peg$currPos < peg$maxFailPos) { return; } if (peg$currPos > peg$maxFailPos) { peg$maxFailPos = peg$currPos; peg$maxFailExpected = []; } peg$maxFailExpected.push(expected); } function peg$buildException(message, expected, pos) { function cleanupExpected(expected) { var i = 1; expected.sort(function(a, b) { if (a.description < b.description) { return -1; } else if (a.description > b.description) { return 1; } else { return 0; } }); while (i < expected.length) { if (expected[i - 1] === expected[i]) { expected.splice(i, 1); } else { i++; } } } function buildMessage(expected, found) { function stringEscape(s) { function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } return s .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\x08/g, '\\b') .replace(/\t/g, '\\t') .replace(/\n/g, '\\n') .replace(/\f/g, '\\f') .replace(/\r/g, '\\r') .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); } var expectedDescs = new Array(expected.length), expectedDesc, foundDesc, i; for (i = 0; i < expected.length; i++) { expectedDescs[i] = expected[i].description; } expectedDesc = (expected.length > 1 ? expectedDescs.slice(0, -1).join(", ") + " or " + expectedDescs[expected.length - 1] : expectedDescs[0]); foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; return "Expected " + expectedDesc + " but " + foundDesc + " found."; } var posDetails = peg$computePosDetails(pos), found = pos < input.length ? input.charAt(pos) : null; if (expected !== null) { cleanupExpected(expected); } return new SyntaxError( message !== null ? message : buildMessage(expected, found), expected, found, pos, posDetails.line, posDetails.column ); } function peg$parsestart() { var s0, s1, s2, s3; peg$silentFails++; s0 = peg$currPos; s1 = peg$parse__(); if (s1 !== peg$FAILED) { s2 = peg$parseOrExpr(); if (s2 !== peg$FAILED) { s3 = peg$parse__(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c2(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; s1 = []; if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c4(); } s0 = s1; } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c0); } } return s0; } function peg$parsews() { var s0, s1; peg$silentFails++; if (peg$c6.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c7); } } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c5); } } return s0; } function peg$parsecc() { var s0, s1; peg$silentFails++; if (peg$c9.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c10); } } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c8); } } return s0; } function peg$parse__() { var s0, s1; peg$silentFails++; s0 = []; s1 = peg$parsews(); while (s1 !== peg$FAILED) { s0.push(s1); s1 = peg$parsews(); } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c11); } } return s0; } function peg$parseOrExpr() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; s1 = peg$parseAndExpr(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 124) { s3 = peg$c12; peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c13); } } if (s3 !== peg$FAILED) { s4 = peg$parse__(); if (s4 !== peg$FAILED) { s5 = peg$parseOrExpr(); if (s5 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c14(s1, s5); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$parseAndExpr(); } return s0; } function peg$parseAndExpr() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; s1 = peg$parseNotExpr(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 38) { s3 = peg$c15; peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c16); } } if (s3 !== peg$FAILED) { s4 = peg$parse__(); if (s4 !== peg$FAILED) { s5 = peg$parseAndExpr(); if (s5 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c17(s1, s5); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; s1 = peg$parseNotExpr(); if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseAndExpr(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c17(s1, s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$parseNotExpr(); } } return s0; } function peg$parseNotExpr() { var s0, s1, s2, s3; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 33) { s1 = peg$c18; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c19); } } if (s1 !== peg$FAILED) { s2 = peg$parse__(); if (s2 !== peg$FAILED) { s3 = peg$parseNotExpr(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c20(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$parseBindingExpr(); } return s0; } function peg$parseBindingExpr() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 40) { s1 = peg$c21; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c22); } } if (s1 !== peg$FAILED) { s2 = peg$parse__(); if (s2 !== peg$FAILED) { s3 = peg$parseOrExpr(); if (s3 !== peg$FAILED) { s4 = peg$parse__(); if (s4 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 41) { s5 = peg$c23; peg$currPos++; } else { s5 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c24); } } if (s5 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c25(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$parseExpr(); } return s0; } function peg$parseExpr() { var s0; s0 = peg$parseNullaryExpr(); if (s0 === peg$FAILED) { s0 = peg$parseUnaryExpr(); } return s0; } function peg$parseNullaryExpr() { var s0, s1; s0 = peg$parseBooleanLiteral(); if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c26) { s1 = peg$c26; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c27); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c28(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c29) { s1 = peg$c29; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c30); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c31(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c32) { s1 = peg$c32; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c33); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c34(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c35) { s1 = peg$c35; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c36); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c37(); } s0 = s1; } } } } return s0; } function peg$parseBooleanLiteral() { var s0, s1; s0 = peg$currPos; if (input.substr(peg$currPos, 4) === peg$c38) { s1 = peg$c38; peg$currPos += 4; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c40(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 5) === peg$c41) { s1 = peg$c41; peg$currPos += 5; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c42); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c43(); } s0 = s1; } return s0; } function peg$parseUnaryExpr() { var s0, s1, s2, s3; s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c44) { s1 = peg$c44; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c45); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseIntegerLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c46(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c47) { s1 = peg$c47; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c48); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c49(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c50) { s1 = peg$c50; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c51); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c52(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 3) === peg$c53) { s1 = peg$c53; peg$currPos += 3; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c54); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c55(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 3) === peg$c56) { s1 = peg$c56; peg$currPos += 3; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c57); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c58(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c59) { s1 = peg$c59; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c60); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c61(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c62) { s1 = peg$c62; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c63); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c64(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 3) === peg$c65) { s1 = peg$c65; peg$currPos += 3; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c66); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c67(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 3) === peg$c68) { s1 = peg$c68; peg$currPos += 3; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c69); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c70(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c71) { s1 = peg$c71; peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c72); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parsews(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parsews(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { s3 = peg$parseStringLiteral(); if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c73(s3); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; s1 = peg$parseStringLiteral(); if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c73(s1); } s0 = s1; } } } } } } } } } } return s0; } function peg$parseIntegerLiteral() { var s0, s1, s2, s3; peg$silentFails++; s0 = peg$currPos; if (peg$c76.test(input.charAt(peg$currPos))) { s1 = input.charAt(peg$currPos); peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c77); } } if (s1 === peg$FAILED) { s1 = peg$c75; } if (s1 !== peg$FAILED) { s2 = []; if (peg$c78.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c79); } } if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); if (peg$c78.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c79); } } } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { if (peg$c76.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c77); } } if (s3 === peg$FAILED) { s3 = peg$c75; } if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c80(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c74); } } return s0; } function peg$parseStringLiteral() { var s0, s1, s2, s3; peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { s1 = peg$c82; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c83); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parseDoubleStringChar(); while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parseDoubleStringChar(); } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { s3 = peg$c82; peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c83); } } if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c84(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 39) { s1 = peg$c85; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c86); } } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parseSingleStringChar(); while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parseSingleStringChar(); } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 39) { s3 = peg$c85; peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c86); } } if (s3 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c84(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; s1 = peg$currPos; peg$silentFails++; s2 = peg$parsecc(); peg$silentFails--; if (s2 === peg$FAILED) { s1 = peg$c87; } else { peg$currPos = s1; s1 = peg$c1; } if (s1 !== peg$FAILED) { s2 = []; s3 = peg$parseUnquotedStringChar(); if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$parseUnquotedStringChar(); } } else { s2 = peg$c1; } if (s2 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c84(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c81); } } return s0; } function peg$parseDoubleStringChar() { var s0, s1, s2; s0 = peg$currPos; s1 = peg$currPos; peg$silentFails++; if (peg$c88.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c89); } } peg$silentFails--; if (s2 === peg$FAILED) { s1 = peg$c87; } else { peg$currPos = s1; s1 = peg$c1; } if (s1 !== peg$FAILED) { if (input.length > peg$currPos) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c90); } } if (s2 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c91(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { s1 = peg$c92; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c93); } } if (s1 !== peg$FAILED) { s2 = peg$parseEscapeSequence(); if (s2 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c91(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } return s0; } function peg$parseSingleStringChar() { var s0, s1, s2; s0 = peg$currPos; s1 = peg$currPos; peg$silentFails++; if (peg$c94.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c95); } } peg$silentFails--; if (s2 === peg$FAILED) { s1 = peg$c87; } else { peg$currPos = s1; s1 = peg$c1; } if (s1 !== peg$FAILED) { if (input.length > peg$currPos) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c90); } } if (s2 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c91(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { s1 = peg$c92; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c93); } } if (s1 !== peg$FAILED) { s2 = peg$parseEscapeSequence(); if (s2 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c91(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } } return s0; } function peg$parseUnquotedStringChar() { var s0, s1, s2; s0 = peg$currPos; s1 = peg$currPos; peg$silentFails++; s2 = peg$parsews(); peg$silentFails--; if (s2 === peg$FAILED) { s1 = peg$c87; } else { peg$currPos = s1; s1 = peg$c1; } if (s1 !== peg$FAILED) { if (input.length > peg$currPos) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c90); } } if (s2 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c91(s2); s0 = s1; } else { peg$currPos = s0; s0 = peg$c1; } } else { peg$currPos = s0; s0 = peg$c1; } return s0; } function peg$parseEscapeSequence() { var s0, s1; if (peg$c96.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 110) { s1 = peg$c98; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c99); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c100(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 114) { s1 = peg$c101; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c102); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c103(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 116) { s1 = peg$c104; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$c105); } } if (s1 !== peg$FAILED) { peg$reportedPos = s0; s1 = peg$c106(); } s0 = s1; } } } return s0; } function or(first, second) { // Add explicit function names to ease debugging. function orFilter() { return first.apply(this, arguments) || second.apply(this, arguments); } orFilter.desc = first.desc + " or " + second.desc; return orFilter; } function and(first, second) { function andFilter() { return first.apply(this, arguments) && second.apply(this, arguments); } andFilter.desc = first.desc + " and " + second.desc; return andFilter; } function not(expr) { function notFilter() { return !expr.apply(this, arguments); } notFilter.desc = "not " + expr.desc; return notFilter; } function binding(expr) { function bindingFilter() { return expr.apply(this, arguments); } bindingFilter.desc = "(" + expr.desc + ")"; return bindingFilter; } function trueFilter(flow) { return true; } trueFilter.desc = "true"; function falseFilter(flow) { return false; } falseFilter.desc = "false"; var ASSET_TYPES = [ new RegExp("text/javascript"), new RegExp("application/x-javascript"), new RegExp("application/javascript"), new RegExp("text/css"), new RegExp("image/.*"), new RegExp("application/x-shockwave-flash") ]; function assetFilter(flow) { if (flow.response) { var ct = ResponseUtils.getContentType(flow.response); var i = ASSET_TYPES.length; while (i--) { if (ASSET_TYPES[i].test(ct)) { return true; } } } return false; } assetFilter.desc = "is asset"; function responseCode(code){ function responseCodeFilter(flow){ return flow.response && flow.response.code === code; } responseCodeFilter.desc = "resp. code is " + code; return responseCodeFilter; } function domain(regex){ regex = new RegExp(regex, "i"); function domainFilter(flow){ return flow.request && regex.test(flow.request.host); } domainFilter.desc = "domain matches " + regex; return domainFilter; } function errorFilter(flow){ return !!flow.error; } errorFilter.desc = "has error"; function header(regex){ regex = new RegExp(regex, "i"); function headerFilter(flow){ return ( (flow.request && RequestUtils.match_header(flow.request, regex)) || (flow.response && ResponseUtils.match_header(flow.response, regex)) ); } headerFilter.desc = "header matches " + regex; return headerFilter; } function requestHeader(regex){ regex = new RegExp(regex, "i"); function requestHeaderFilter(flow){ return (flow.request && RequestUtils.match_header(flow.request, regex)); } requestHeaderFilter.desc = "req. header matches " + regex; return requestHeaderFilter; } function responseHeader(regex){ regex = new RegExp(regex, "i"); function responseHeaderFilter(flow){ return (flow.response && ResponseUtils.match_header(flow.response, regex)); } responseHeaderFilter.desc = "resp. header matches " + regex; return responseHeaderFilter; } function method(regex){ regex = new RegExp(regex, "i"); function methodFilter(flow){ return flow.request && regex.test(flow.request.method); } methodFilter.desc = "method matches " + regex; return methodFilter; } function noResponseFilter(flow){ return flow.request && !flow.response; } noResponseFilter.desc = "has no response"; function responseFilter(flow){ return !!flow.response; } responseFilter.desc = "has response"; function contentType(regex){ regex = new RegExp(regex, "i"); function contentTypeFilter(flow){ return ( (flow.request && regex.test(RequestUtils.getContentType(flow.request))) || (flow.response && regex.test(ResponseUtils.getContentType(flow.response))) ); } contentTypeFilter.desc = "content type matches " + regex; return contentTypeFilter; } function requestContentType(regex){ regex = new RegExp(regex, "i"); function requestContentTypeFilter(flow){ return flow.request && regex.test(RequestUtils.getContentType(flow.request)); } requestContentTypeFilter.desc = "req. content type matches " + regex; return requestContentTypeFilter; } function responseContentType(regex){ regex = new RegExp(regex, "i"); function responseContentTypeFilter(flow){ return flow.response && regex.test(ResponseUtils.getContentType(flow.response)); } responseContentTypeFilter.desc = "resp. content type matches " + regex; return responseContentTypeFilter; } function url(regex){ regex = new RegExp(regex, "i"); function urlFilter(flow){ return flow.request && regex.test(RequestUtils.pretty_url(flow.request)); } urlFilter.desc = "url matches " + regex; return urlFilter; } peg$result = peg$startRuleFunction(); if (peg$result !== peg$FAILED && peg$currPos === input.length) { return peg$result; } else { if (peg$result !== peg$FAILED && peg$currPos < input.length) { peg$fail({ type: "end", description: "end of input" }); } throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos); } } return { SyntaxError: SyntaxError, parse: parse }; })(); var _MessageUtils = { getContentType: function (message) { return this.get_first_header(message, /^Content-Type$/i); }, get_first_header: function (message, regex) { //FIXME: Cache Invalidation. if (!message._headerLookups) Object.defineProperty(message, "_headerLookups", { value: {}, configurable: false, enumerable: false, writable: false }); if (!(regex in message._headerLookups)) { var header; for (var i = 0; i < message.headers.length; i++) { if (!!message.headers[i][0].match(regex)) { header = message.headers[i]; break; } } message._headerLookups[regex] = header ? header[1] : undefined; } return message._headerLookups[regex]; }, match_header: function (message, regex) { var headers = message.headers; var i = headers.length; while (i--) { if (regex.test(headers[i].join(" "))) { return headers[i]; } } return false; } }; var defaultPorts = { "http": 80, "https": 443 }; var RequestUtils = _.extend(_MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader return request.host; }, pretty_url: function (request) { var port = ""; if (defaultPorts[request.scheme] !== request.port) { port = ":" + request.port; } return request.scheme + "://" + this.pretty_host(request) + port + request.path; } }); var ResponseUtils = _.extend(_MessageUtils, {}); function ListStore() { EventEmitter.call(this); this.reset(); } _.extend(ListStore.prototype, EventEmitter.prototype, { add: function (elem) { if (elem.id in this._pos_map) { return; } this._pos_map[elem.id] = this.list.length; this.list.push(elem); this.emit("add", elem); }, update: function (elem) { if (!(elem.id in this._pos_map)) { return; } this.list[this._pos_map[elem.id]] = elem; this.emit("update", elem); }, remove: function (elem_id) { if (!(elem_id in this._pos_map)) { return; } this.list.splice(this._pos_map[elem_id], 1); this._build_map(); this.emit("remove", elem_id); }, reset: function (elems) { this.list = elems || []; this._build_map(); this.emit("recalculate"); }, _build_map: function () { this._pos_map = {}; for (var i = 0; i < this.list.length; i++) { var elem = this.list[i]; this._pos_map[elem.id] = i; } }, get: function (elem_id) { return this.list[this._pos_map[elem_id]]; }, index: function (elem_id) { return this._pos_map[elem_id]; } }); function DictStore() { EventEmitter.call(this); this.reset(); } _.extend(DictStore.prototype, EventEmitter.prototype, { update: function (dict) { _.merge(this.dict, dict); this.emit("recalculate"); }, reset: function (dict) { this.dict = dict || {}; this.emit("recalculate"); } }); function LiveStoreMixin(type) { this.type = type; this._updates_before_fetch = undefined; this._fetchxhr = false; this.handle = this.handle.bind(this); AppDispatcher.register(this.handle); // Avoid double-fetch on startup. if (!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { this.fetch(); } } _.extend(LiveStoreMixin.prototype, { handle: function (event) { if (event.type === ActionTypes.CONNECTION_OPEN) { return this.fetch(); } if (event.type === this.type) { if (event.cmd === StoreCmds.RESET) { this.fetch(event.data); } else if (this._updates_before_fetch) { console.log("defer update", event); this._updates_before_fetch.push(event); } else { this[event.cmd](event.data); } } }, close: function () { AppDispatcher.unregister(this.handle); }, fetch: function (data) { console.log("fetch " + this.type); if (this._fetchxhr) { this._fetchxhr.abort(); } this._updates_before_fetch = []; // (JS: empty array is true) if (data) { this.handle_fetch(data); } else { this._fetchxhr = $.getJSON("/" + this.type) .done(function (message) { this.handle_fetch(message.data); }.bind(this)) .fail(function () { EventLogActions.add_event("Could not fetch " + this.type); }.bind(this)); } }, handle_fetch: function (data) { this._fetchxhr = false; console.log(this.type + " fetched.", this._updates_before_fetch); this.reset(data); var updates = this._updates_before_fetch; this._updates_before_fetch = false; for (var i = 0; i < updates.length; i++) { this.handle(updates[i]); } }, }); function LiveListStore(type) { ListStore.call(this); LiveStoreMixin.call(this, type); } _.extend(LiveListStore.prototype, ListStore.prototype, LiveStoreMixin.prototype); function LiveDictStore(type) { DictStore.call(this); LiveStoreMixin.call(this, type); } _.extend(LiveDictStore.prototype, DictStore.prototype, LiveStoreMixin.prototype); function FlowStore() { return new LiveListStore(ActionTypes.FLOW_STORE); } function SettingsStore() { return new LiveDictStore(ActionTypes.SETTINGS_STORE); } function EventLogStore() { LiveListStore.call(this, ActionTypes.EVENT_STORE); } _.extend(EventLogStore.prototype, LiveListStore.prototype, { fetch: function(){ LiveListStore.prototype.fetch.apply(this, arguments); // Make sure to display updates even if fetching all events failed. // This way, we can send "fetch failed" log messages to the log. if(this._fetchxhr){ this._fetchxhr.fail(function(){ this.handle_fetch(null); }.bind(this)); } } }); function SortByStoreOrder(elem) { return this.store.index(elem.id); } var default_sort = SortByStoreOrder; var default_filt = function(elem){ return true; }; function StoreView(store, filt, sortfun) { EventEmitter.call(this); filt = filt || default_filt; sortfun = sortfun || default_sort; this.store = store; this.add = this.add.bind(this); this.update = this.update.bind(this); this.remove = this.remove.bind(this); this.recalculate = this.recalculate.bind(this); this.store.addListener("add", this.add); this.store.addListener("update", this.update); this.store.addListener("remove", this.remove); this.store.addListener("recalculate", this.recalculate); this.recalculate(filt, sortfun); } _.extend(StoreView.prototype, EventEmitter.prototype, { close: function () { this.store.removeListener("add", this.add); this.store.removeListener("update", this.update); this.store.removeListener("remove", this.remove); this.store.removeListener("recalculate", this.recalculate); }, recalculate: function (filt, sortfun) { if (filt) { this.filt = filt.bind(this); } if (sortfun) { this.sortfun = sortfun.bind(this); } this.list = this.store.list.filter(this.filt); this.list.sort(function (a, b) { return this.sortfun(a) - this.sortfun(b); }.bind(this)); this.emit("recalculate"); }, index: function (elem) { return _.sortedIndex(this.list, elem, this.sortfun); }, add: function (elem) { if (this.filt(elem)) { var idx = this.index(elem); if (idx === this.list.length) { //happens often, .push is way faster. this.list.push(elem); } else { this.list.splice(idx, 0, elem); } this.emit("add", elem, idx); } }, update: function (elem) { var idx; var i = this.list.length; // Search from the back, we usually update the latest entries. while (i--) { if (this.list[i].id === elem.id) { idx = i; break; } } if (idx === -1) { //not contained in list this.add(elem); } else if (!this.filt(elem)) { this.remove(elem.id); } else { if (this.sortfun(this.list[idx]) !== this.sortfun(elem)) { //sortpos has changed this.remove(this.list[idx]); this.add(elem); } else { this.list[idx] = elem; this.emit("update", elem, idx); } } }, remove: function (elem_id) { var idx = this.list.length; while (idx--) { if (this.list[idx].id === elem_id) { this.list.splice(idx, 1); this.emit("remove", elem_id, idx); break; } } } }); function Connection(url) { if (url[0] === "/") { url = location.origin.replace("http", "ws") + url; } var ws = new WebSocket(url); ws.onopen = function () { ConnectionActions.open(); }; ws.onmessage = function (message) { var m = JSON.parse(message.data); AppDispatcher.dispatchServerAction(m); }; ws.onerror = function () { ConnectionActions.error(); EventLogActions.add_event("WebSocket connection error."); }; ws.onclose = function () { ConnectionActions.close(); EventLogActions.add_event("WebSocket connection closed."); }; return ws; } //React utils. For other utilities, see ../utils.js // http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) var AutoScrollMixin = { componentWillUpdate: function () { var node = this.getDOMNode(); this._shouldScrollBottom = ( node.scrollTop !== 0 && node.scrollTop + node.clientHeight === node.scrollHeight ); }, componentDidUpdate: function () { if (this._shouldScrollBottom) { var node = this.getDOMNode(); node.scrollTop = node.scrollHeight; } }, }; var StickyHeadMixin = { adjustHead: function () { // Abusing CSS transforms to set the element // referenced as head into some kind of position:sticky. var head = this.refs.head.getDOMNode(); head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; } }; var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { var q = this.context.getCurrentQuery(); for(var i in dict){ if(dict.hasOwnProperty(i)){ q[i] = dict[i] || undefined; //falsey values shall be removed. } } q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); }, replaceWith: function(routeNameOrPath, params, query) { if(routeNameOrPath === undefined){ routeNameOrPath = this.context.getCurrentPath(); } if(params === undefined){ params = this.context.getCurrentParams(); } if(query === undefined){ query = this.context.getCurrentQuery(); } ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); } }); _.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); var State = _.extend({}, ReactRouter.State, { getInitialState: function () { this._query = this.context.getCurrentQuery(); this._queryWatches = []; return null; }, onQueryChange: function (key, callback) { this._queryWatches.push({ key: key, callback: callback }); }, componentWillReceiveProps: function (nextProps, nextState) { var q = this.context.getCurrentQuery(); for (var i = 0; i < this._queryWatches.length; i++) { var watch = this._queryWatches[i]; if (this._query[watch.key] !== q[watch.key]) { watch.callback(this._query[watch.key], q[watch.key], watch.key); } } this._query = q; } }); var Splitter = React.createClass({displayName: 'Splitter', getDefaultProps: function () { return { axis: "x" }; }, getInitialState: function () { return { applied: false, startX: false, startY: false }; }, onMouseDown: function (e) { this.setState({ startX: e.pageX, startY: e.pageY }); window.addEventListener("mousemove", this.onMouseMove); window.addEventListener("mouseup", this.onMouseUp); // Occasionally, only a dragEnd event is triggered, but no mouseUp. window.addEventListener("dragend", this.onDragEnd); }, onDragEnd: function () { this.getDOMNode().style.transform = ""; window.removeEventListener("dragend", this.onDragEnd); window.removeEventListener("mouseup", this.onMouseUp); window.removeEventListener("mousemove", this.onMouseMove); }, onMouseUp: function (e) { this.onDragEnd(); var node = this.getDOMNode(); var prev = node.previousElementSibling; var next = node.nextElementSibling; var dX = e.pageX - this.state.startX; var dY = e.pageY - this.state.startY; var flexBasis; if (this.props.axis === "x") { flexBasis = prev.offsetWidth + dX; } else { flexBasis = prev.offsetHeight + dY; } prev.style.flex = "0 0 " + Math.max(0, flexBasis) + "px"; next.style.flex = "1 1 auto"; this.setState({ applied: true }); this.onResize(); }, onMouseMove: function (e) { var dX = 0, dY = 0; if (this.props.axis === "x") { dX = e.pageX - this.state.startX; } else { dY = e.pageY - this.state.startY; } this.getDOMNode().style.transform = "translate(" + dX + "px," + dY + "px)"; }, onResize: function () { // Trigger a global resize event. This notifies components that employ virtual scrolling // that their viewport may have changed. window.setTimeout(function () { window.dispatchEvent(new CustomEvent("resize")); }, 1); }, reset: function (willUnmount) { if (!this.state.applied) { return; } var node = this.getDOMNode(); var prev = node.previousElementSibling; var next = node.nextElementSibling; prev.style.flex = ""; next.style.flex = ""; if (!willUnmount) { this.setState({ applied: false }); } this.onResize(); }, componentWillUnmount: function () { this.reset(true); }, render: function () { var className = "splitter"; if (this.props.axis === "x") { className += " splitter-x"; } else { className += " splitter-y"; } return ( React.createElement("div", {className: className}, React.createElement("div", {onMouseDown: this.onMouseDown, draggable: "true"}) ) ); } }); var VirtualScrollMixin = { getInitialState: function () { return { start: 0, stop: 0 }; }, componentWillMount: function () { if (!this.props.rowHeight) { console.warn("VirtualScrollMixin: No rowHeight specified", this); } }, getPlaceholderTop: function (total) { var Tag = this.props.placeholderTagName || "tr"; // When a large trunk of elements is removed from the button, start may be far off the viewport. // To make this issue less severe, limit the top placeholder to the total number of rows. var style = { height: Math.min(this.state.start, total) * this.props.rowHeight }; var spacer = React.createElement(Tag, {key: "placeholder-top", style: style}); if (this.state.start % 2 === 1) { // fix even/odd rows return [spacer, React.createElement(Tag, {key: "placeholder-top-2"})]; } else { return spacer; } }, getPlaceholderBottom: function (total) { var Tag = this.props.placeholderTagName || "tr"; var style = { height: Math.max(0, total - this.state.stop) * this.props.rowHeight }; return React.createElement(Tag, {key: "placeholder-bottom", style: style}); }, componentDidMount: function () { this.onScroll(); window.addEventListener('resize', this.onScroll); }, componentWillUnmount: function(){ window.removeEventListener('resize', this.onScroll); }, onScroll: function () { var viewport = this.getDOMNode(); var top = viewport.scrollTop; var height = viewport.offsetHeight; var start = Math.floor(top / this.props.rowHeight); var stop = start + Math.ceil(height / (this.props.rowHeightMin || this.props.rowHeight)); this.setState({ start: start, stop: stop }); }, renderRows: function (elems) { var rows = []; var max = Math.min(elems.length, this.state.stop); for (var i = this.state.start; i < max; i++) { var elem = elems[i]; rows.push(this.renderRow(elem)); } return rows; }, scrollRowIntoView: function (index, head_height) { var row_top = (index * this.props.rowHeight) + head_height; var row_bottom = row_top + this.props.rowHeight; var viewport = this.getDOMNode(); var viewport_top = viewport.scrollTop; var viewport_bottom = viewport_top + viewport.offsetHeight; // Account for pinned thead if (row_top - head_height < viewport_top) { viewport.scrollTop = row_top - head_height; } else if (row_bottom > viewport_bottom) { viewport.scrollTop = row_bottom - viewport.offsetHeight; } }, }; var FilterInput = React.createClass({displayName: 'FilterInput', getInitialState: function () { // Consider both focus and mouseover for showing/hiding the tooltip, // because onBlur of the input is triggered before the click on the tooltip // finalized, hiding the tooltip just as the user clicks on it. return { value: this.props.value, focus: false, mousefocus: false }; }, componentWillReceiveProps: function (nextProps) { this.setState({value: nextProps.value}); }, onChange: function (e) { var nextValue = e.target.value; this.setState({ value: nextValue }); // Only propagate valid filters upwards. if (this.isValid(nextValue)) { this.props.onChange(nextValue); } }, isValid: function (filt) { try { Filt.parse(filt || this.state.value); return true; } catch (e) { return false; } }, getDesc: function () { var desc; try { desc = Filt.parse(this.state.value).desc; } catch (e) { desc = "" + e; } if (desc !== "true") { return desc; } else { return ( React.createElement("a", {href: "https://mitmproxy.org/doc/features/filters.html", target: "_blank"}, React.createElement("i", {className: "fa fa-external-link"}), "Filter Documentation" ) ); } }, onFocus: function () { this.setState({focus: true}); }, onBlur: function () { this.setState({focus: false}); }, onMouseEnter: function () { this.setState({mousefocus: true}); }, onMouseLeave: function () { this.setState({mousefocus: false}); }, onKeyDown: function (e) { if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) { this.blur(); // If closed using ESC/ENTER, hide the tooltip. this.setState({mousefocus: false}); } }, blur: function () { this.refs.input.getDOMNode().blur(); }, focus: function () { this.refs.input.getDOMNode().select(); }, render: function () { var isValid = this.isValid(); var icon = "fa fa-fw fa-" + this.props.type; var groupClassName = "filter-input input-group" + (isValid ? "" : " has-error"); var popover; if (this.state.focus || this.state.mousefocus) { popover = ( React.createElement("div", {className: "popover bottom", onMouseEnter: this.onMouseEnter, onMouseLeave: this.onMouseLeave}, React.createElement("div", {className: "arrow"}), React.createElement("div", {className: "popover-content"}, this.getDesc() ) ) ); } return ( React.createElement("div", {className: groupClassName}, React.createElement("span", {className: "input-group-addon"}, React.createElement("i", {className: icon, style: {color: this.props.color}}) ), React.createElement("input", {type: "text", placeholder: this.props.placeholder, className: "form-control", ref: "input", onChange: this.onChange, onFocus: this.onFocus, onBlur: this.onBlur, onKeyDown: this.onKeyDown, value: this.state.value}), popover ) ); } }); var MainMenu = React.createClass({displayName: 'MainMenu', mixins: [Navigation, State], statics: { title: "Start", route: "flows" }, onFilterChange: function (val) { var d = {}; d[Query.FILTER] = val; this.setQuery(d); }, onHighlightChange: function (val) { var d = {}; d[Query.HIGHLIGHT] = val; this.setQuery(d); }, onInterceptChange: function (val) { SettingsActions.update({intercept: val}); }, render: function () { var filter = this.getQuery()[Query.FILTER] || ""; var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; var intercept = this.props.settings.intercept || ""; return ( React.createElement("div", null, React.createElement("form", {className: "form-inline", style: {display: "inline"}}, React.createElement(FilterInput, { placeholder: "Filter", type: "filter", color: "black", value: filter, onChange: this.onFilterChange}), React.createElement("span", null, " "), React.createElement(FilterInput, { placeholder: "Highlight", type: "tag", color: "hsl(48, 100%, 50%)", value: highlight, onChange: this.onHighlightChange}), React.createElement("span", null, " "), React.createElement(FilterInput, { placeholder: "Intercept", type: "pause", color: "hsl(208, 56%, 53%)", value: intercept, onChange: this.onInterceptChange}) ) ) ); } }); var ViewMenu = React.createClass({displayName: 'ViewMenu', statics: { title: "View", route: "flows" }, mixins: [Navigation, State], toggleEventLog: function () { var d = {}; if (this.getQuery()[Query.SHOW_EVENTLOG]) { d[Query.SHOW_EVENTLOG] = undefined; } else { d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short } this.setQuery(d); }, render: function () { var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; return ( React.createElement("div", null, React.createElement("button", { className: "btn " + (showEventLog ? "btn-primary" : "btn-default"), onClick: this.toggleEventLog}, React.createElement("i", {className: "fa fa-database"}), " Show Eventlog" ), React.createElement("span", null, " ") ) ); } }); var ReportsMenu = React.createClass({displayName: 'ReportsMenu', statics: { title: "Visualization", route: "reports" }, render: function () { return React.createElement("div", null, "Reports Menu"); } }); var FileMenu = React.createClass({displayName: 'FileMenu', getInitialState: function () { return { showFileMenu: false }; }, handleFileClick: function (e) { e.preventDefault(); if (!this.state.showFileMenu) { var close = function () { this.setState({showFileMenu: false}); document.removeEventListener("click", close); }.bind(this); document.addEventListener("click", close); this.setState({ showFileMenu: true }); } }, handleNewClick: function (e) { e.preventDefault(); if (confirm("Delete all flows?")) { FlowActions.clear(); } }, handleOpenClick: function (e) { e.preventDefault(); console.error("unimplemented: handleOpenClick"); }, handleSaveClick: function (e) { e.preventDefault(); console.error("unimplemented: handleSaveClick"); }, handleShutdownClick: function (e) { e.preventDefault(); console.error("unimplemented: handleShutdownClick"); }, render: function () { var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : ""); return ( React.createElement("div", {className: fileMenuClass}, React.createElement("a", {href: "#", className: "special", onClick: this.handleFileClick}, " File "), React.createElement("ul", {className: "dropdown-menu", role: "menu"}, React.createElement("li", null, React.createElement("a", {href: "#", onClick: this.handleNewClick}, React.createElement("i", {className: "fa fa-fw fa-file"}), "New" ) ), React.createElement("li", {role: "presentation", className: "divider"}), React.createElement("li", null, React.createElement("a", {href: "http://mitm.it/", target: "_blank"}, React.createElement("i", {className: "fa fa-fw fa-lock"}), "Install Certificates..." ) ) /*
  • Open
  • Save
  • Shutdown
  • */ ) ) ); } }); var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; var Header = React.createClass({displayName: 'Header', mixins: [Navigation], getInitialState: function () { return { active: header_entries[0] }; }, handleClick: function (active, e) { e.preventDefault(); this.replaceWith(active.route); this.setState({active: active}); }, render: function () { var header = header_entries.map(function (entry, i) { var classes = React.addons.classSet({ active: entry == this.state.active }); return ( React.createElement("a", {key: i, href: "#", className: classes, onClick: this.handleClick.bind(this, entry) }, entry.title ) ); }.bind(this)); return ( React.createElement("header", null, React.createElement("div", {className: "title-bar"}, "mitmproxy ", this.props.settings.version ), React.createElement("nav", {className: "nav-tabs nav-tabs-lg"}, React.createElement(FileMenu, null), header ), React.createElement("div", {className: "menu"}, React.createElement(this.state.active, {settings: this.props.settings}) ) ) ); } }); var TLSColumn = React.createClass({displayName: 'TLSColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "tls", className: "col-tls"}); } }, render: function () { var flow = this.props.flow; var ssl = (flow.request.scheme == "https"); var classes; if (ssl) { classes = "col-tls col-tls-https"; } else { classes = "col-tls col-tls-http"; } return React.createElement("td", {className: classes}); } }); var IconColumn = React.createClass({displayName: 'IconColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "icon", className: "col-icon"}); } }, render: function () { var flow = this.props.flow; var icon; if (flow.response) { var contentType = ResponseUtils.getContentType(flow.response); //TODO: We should assign a type to the flow somewhere else. if (flow.response.code == 304) { icon = "resource-icon-not-modified"; } else if (300 <= flow.response.code && flow.response.code < 400) { icon = "resource-icon-redirect"; } else if (contentType && contentType.indexOf("image") >= 0) { icon = "resource-icon-image"; } else if (contentType && contentType.indexOf("javascript") >= 0) { icon = "resource-icon-js"; } else if (contentType && contentType.indexOf("css") >= 0) { icon = "resource-icon-css"; } else if (contentType && contentType.indexOf("html") >= 0) { icon = "resource-icon-document"; } } if (!icon) { icon = "resource-icon-plain"; } icon += " resource-icon"; return React.createElement("td", {className: "col-icon"}, React.createElement("div", {className: icon}) ); } }); var PathColumn = React.createClass({displayName: 'PathColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "path", className: "col-path"}, "Path"); } }, render: function () { var flow = this.props.flow; return React.createElement("td", {className: "col-path"}, flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, flow.request.scheme + "://" + flow.request.host + flow.request.path ); } }); var MethodColumn = React.createClass({displayName: 'MethodColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "method", className: "col-method"}, "Method"); } }, render: function () { var flow = this.props.flow; return React.createElement("td", {className: "col-method"}, flow.request.method); } }); var StatusColumn = React.createClass({displayName: 'StatusColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "status", className: "col-status"}, "Status"); } }, render: function () { var flow = this.props.flow; var status; if (flow.response) { status = flow.response.code; } else { status = null; } return React.createElement("td", {className: "col-status"}, status); } }); var SizeColumn = React.createClass({displayName: 'SizeColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "size", className: "col-size"}, "Size"); } }, render: function () { var flow = this.props.flow; var total = flow.request.contentLength; if (flow.response) { total += flow.response.contentLength || 0; } var size = formatSize(total); return React.createElement("td", {className: "col-size"}, size); } }); var TimeColumn = React.createClass({displayName: 'TimeColumn', statics: { renderTitle: function () { return React.createElement("th", {key: "time", className: "col-time"}, "Time"); } }, render: function () { var flow = this.props.flow; var time; if (flow.response) { time = formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { time = "..."; } return React.createElement("td", {className: "col-time"}, time); } }); var all_columns = [ TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, SizeColumn, TimeColumn]; var FlowRow = React.createClass({displayName: 'FlowRow', render: function () { var flow = this.props.flow; var columns = this.props.columns.map(function (Column) { return React.createElement(Column, {key: Column.displayName, flow: flow}); }.bind(this)); var className = ""; if (this.props.selected) { className += " selected"; } if (this.props.highlighted) { className += " highlighted"; } if (flow.intercepted) { className += " intercepted"; } if (flow.request) { className += " has-request"; } if (flow.response) { className += " has-response"; } return ( React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, columns )); }, shouldComponentUpdate: function (nextProps) { return true; // Further optimization could be done here // by calling forceUpdate on flow updates, selection changes and column changes. //return ( //(this.props.columns.length !== nextProps.columns.length) || //(this.props.selected !== nextProps.selected) //); } }); var FlowTableHead = React.createClass({displayName: 'FlowTableHead', render: function () { var columns = this.props.columns.map(function (column) { return column.renderTitle(); }.bind(this)); return React.createElement("thead", null, React.createElement("tr", null, columns) ); } }); var ROW_HEIGHT = 32; var FlowTable = React.createClass({displayName: 'FlowTable', mixins: [StickyHeadMixin, AutoScrollMixin, VirtualScrollMixin], getInitialState: function () { return { columns: all_columns }; }, componentWillMount: function () { if (this.props.view) { this.props.view.addListener("add update remove recalculate", this.onChange); } }, componentWillReceiveProps: function (nextProps) { if (nextProps.view !== this.props.view) { if (this.props.view) { this.props.view.removeListener("add update remove recalculate"); } nextProps.view.addListener("add update remove recalculate", this.onChange); } }, getDefaultProps: function () { return { rowHeight: ROW_HEIGHT }; }, onScrollFlowTable: function () { this.adjustHead(); this.onScroll(); }, onChange: function () { this.forceUpdate(); }, scrollIntoView: function (flow) { this.scrollRowIntoView( this.props.view.index(flow), this.refs.body.getDOMNode().offsetTop ); }, renderRow: function (flow) { var selected = (flow === this.props.selected); var highlighted = ( this.props.view._highlight && this.props.view._highlight[flow.id] ); return React.createElement(FlowRow, {key: flow.id, ref: flow.id, flow: flow, columns: this.state.columns, selected: selected, highlighted: highlighted, selectFlow: this.props.selectFlow} ); }, render: function () { //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); var flows = this.props.view ? this.props.view.list : []; var rows = this.renderRows(flows); return ( React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, React.createElement("table", null, React.createElement(FlowTableHead, {ref: "head", columns: this.state.columns}), React.createElement("tbody", {ref: "body"}, this.getPlaceholderTop(flows.length), rows, this.getPlaceholderBottom(flows.length) ) ) ) ); } }); var DeleteButton = React.createClass({displayName: 'DeleteButton', onClick: function (e) { e.preventDefault(); FlowActions.delete(this.props.flow); }, render: function () { return ( React.createElement("a", {title: "[d]elete Flow", href: "#", className: "nav-action", onClick: this.onClick}, React.createElement("i", {className: "fa fa-fw fa-trash"}) ) ); } }); var DuplicateButton = React.createClass({displayName: 'DuplicateButton', onClick: function (e) { e.preventDefault(); FlowActions.duplicate(this.props.flow); }, render: function () { return ( React.createElement("a", {title: "[D]uplicate Flow", href: "#", className: "nav-action", onClick: this.onClick}, React.createElement("i", {className: "fa fa-fw fa-edit"}) ) ); } }); var ReplayButton = React.createClass({displayName: 'ReplayButton', onClick: function (e) { e.preventDefault(); FlowActions.replay(this.props.flow); }, render: function () { return ( React.createElement("a", {title: "[r]eplay Flow", href: "#", className: "nav-action", onClick: this.onClick}, React.createElement("i", {className: "fa fa-fw fa-close"}) ) ); } }); var AcceptButton = React.createClass({displayName: 'AcceptButton', onClick: function (e) { e.preventDefault(); FlowActions.accept(this.props.flow); }, render: function () { return ( React.createElement("a", {title: "[a]ccept (resume) Flow", href: "#", className: "nav-action", onClick: this.onClick}, React.createElement("i", {className: "fa fa-fw fa-play"}) ) ); } }); var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav', render: function () { var flow = this.props.flow; var tabs = this.props.tabs.map(function (e) { var str = e.charAt(0).toUpperCase() + e.slice(1); var className = this.props.active === e ? "active" : ""; var onClick = function (event) { this.props.selectTab(e); event.preventDefault(); }.bind(this); return React.createElement("a", {key: e, href: "#", className: className, onClick: onClick}, str); }.bind(this)); return ( React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, tabs, React.createElement(DeleteButton, {flow: flow}), React.createElement(DuplicateButton, {flow: flow}), React.createElement(ReplayButton, {flow: flow}), flow.intercepted ? React.createElement(AcceptButton, {flow: this.props.flow}) : null ) ); } }); var Headers = React.createClass({displayName: 'Headers', render: function () { var rows = this.props.message.headers.map(function (header, i) { return ( React.createElement("tr", {key: i}, React.createElement("td", {className: "header-name"}, header[0] + ":"), React.createElement("td", {className: "header-value"}, header[1]) ) ); }); return ( React.createElement("table", {className: "header-table"}, React.createElement("tbody", null, rows ) ) ); } }); var FlowDetailRequest = React.createClass({displayName: 'FlowDetailRequest', render: function () { var flow = this.props.flow; var first_line = [ flow.request.method, RequestUtils.pretty_url(flow.request), "HTTP/" + flow.request.httpversion.join(".") ].join(" "); var content = null; if (flow.request.contentLength > 0) { content = "Request Content Size: " + formatSize(flow.request.contentLength); } else { content = React.createElement("div", {className: "alert alert-info"}, "No Content"); } //TODO: Styling return ( React.createElement("section", null, React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.request}), React.createElement("hr", null), content ) ); } }); var FlowDetailResponse = React.createClass({displayName: 'FlowDetailResponse', render: function () { var flow = this.props.flow; var first_line = [ "HTTP/" + flow.response.httpversion.join("."), flow.response.code, flow.response.msg ].join(" "); var content = null; if (flow.response.contentLength > 0) { content = "Response Content Size: " + formatSize(flow.response.contentLength); } else { content = React.createElement("div", {className: "alert alert-info"}, "No Content"); } //TODO: Styling return ( React.createElement("section", null, React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.response}), React.createElement("hr", null), content ) ); } }); var FlowDetailError = React.createClass({displayName: 'FlowDetailError', render: function () { var flow = this.props.flow; return ( React.createElement("section", null, React.createElement("div", {className: "alert alert-warning"}, flow.error.msg, React.createElement("div", null, React.createElement("small", null, formatTimeStamp(flow.error.timestamp) ) ) ) ) ); } }); var TimeStamp = React.createClass({displayName: 'TimeStamp', render: function () { if (!this.props.t) { //should be return null, but that triggers a React bug. return React.createElement("tr", null); } var ts = formatTimeStamp(this.props.t); var delta; if (this.props.deltaTo) { delta = formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")"); } else { delta = null; } return React.createElement("tr", null, React.createElement("td", null, this.props.title + ":"), React.createElement("td", null, ts, " ", delta) ); } }); var ConnectionInfo = React.createClass({displayName: 'ConnectionInfo', render: function () { var conn = this.props.conn; var address = conn.address.address.join(":"); var sni = React.createElement("tr", {key: "sni"}); //should be null, but that triggers a React bug. if (conn.sni) { sni = React.createElement("tr", {key: "sni"}, React.createElement("td", null, React.createElement("abbr", {title: "TLS Server Name Indication"}, "TLS SNI:") ), React.createElement("td", null, conn.sni) ); } return ( React.createElement("table", {className: "connection-table"}, React.createElement("tbody", null, React.createElement("tr", {key: "address"}, React.createElement("td", null, "Address:"), React.createElement("td", null, address) ), sni ) ) ); } }); var CertificateInfo = React.createClass({displayName: 'CertificateInfo', render: function () { //TODO: We should fetch human-readable certificate representation // from the server var flow = this.props.flow; var client_conn = flow.client_conn; var server_conn = flow.server_conn; var preStyle = {maxHeight: 100}; return ( React.createElement("div", null, client_conn.cert ? React.createElement("h4", null, "Client Certificate") : null, client_conn.cert ? React.createElement("pre", {style: preStyle}, client_conn.cert) : null, server_conn.cert ? React.createElement("h4", null, "Server Certificate") : null, server_conn.cert ? React.createElement("pre", {style: preStyle}, server_conn.cert) : null ) ); } }); var Timing = React.createClass({displayName: 'Timing', render: function () { var flow = this.props.flow; var sc = flow.server_conn; var cc = flow.client_conn; var req = flow.request; var resp = flow.response; var timestamps = [ { title: "Server conn. initiated", t: sc.timestamp_start, deltaTo: req.timestamp_start }, { title: "Server conn. TCP handshake", t: sc.timestamp_tcp_setup, deltaTo: req.timestamp_start }, { title: "Server conn. SSL handshake", t: sc.timestamp_ssl_setup, deltaTo: req.timestamp_start }, { title: "Client conn. established", t: cc.timestamp_start, deltaTo: req.timestamp_start }, { title: "Client conn. SSL handshake", t: cc.timestamp_ssl_setup, deltaTo: req.timestamp_start }, { title: "First request byte", t: req.timestamp_start, }, { title: "Request complete", t: req.timestamp_end, deltaTo: req.timestamp_start } ]; if (flow.response) { timestamps.push( { title: "First response byte", t: resp.timestamp_start, deltaTo: req.timestamp_start }, { title: "Response complete", t: resp.timestamp_end, deltaTo: req.timestamp_start } ); } //Add unique key for each row. timestamps.forEach(function (e) { e.key = e.title; }); timestamps = _.sortBy(timestamps, 't'); var rows = timestamps.map(function (e) { return React.createElement(TimeStamp, React.__spread({}, e)); }); return ( React.createElement("div", null, React.createElement("h4", null, "Timing"), React.createElement("table", {className: "timing-table"}, React.createElement("tbody", null, rows ) ) ) ); } }); var FlowDetailConnectionInfo = React.createClass({displayName: 'FlowDetailConnectionInfo', render: function () { var flow = this.props.flow; var client_conn = flow.client_conn; var server_conn = flow.server_conn; return ( React.createElement("section", null, React.createElement("h4", null, "Client Connection"), React.createElement(ConnectionInfo, {conn: client_conn}), React.createElement("h4", null, "Server Connection"), React.createElement(ConnectionInfo, {conn: server_conn}), React.createElement(CertificateInfo, {flow: flow}), React.createElement(Timing, {flow: flow}) ) ); } }); var allTabs = { request: FlowDetailRequest, response: FlowDetailResponse, error: FlowDetailError, details: FlowDetailConnectionInfo }; var FlowDetail = React.createClass({displayName: 'FlowDetail', mixins: [StickyHeadMixin, Navigation, State], getTabs: function (flow) { var tabs = []; ["request", "response", "error"].forEach(function (e) { if (flow[e]) { tabs.push(e); } }); tabs.push("details"); return tabs; }, nextTab: function (i) { var tabs = this.getTabs(this.props.flow); var currentIndex = tabs.indexOf(this.getParams().detailTab); // JS modulo operator doesn't correct negative numbers, make sure that we are positive. var nextIndex = (currentIndex + i + tabs.length) % tabs.length; this.selectTab(tabs[nextIndex]); }, selectTab: function (panel) { this.replaceWith( "flow", { flowId: this.getParams().flowId, detailTab: panel } ); }, render: function () { var flow = this.props.flow; var tabs = this.getTabs(flow); var active = this.getParams().detailTab; if (!_.contains(tabs, active)) { if (active === "response" && flow.error) { active = "error"; } else if (active === "error" && flow.response) { active = "response"; } else { active = tabs[0]; } this.selectTab(active); } var Tab = allTabs[active]; return ( React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead}, React.createElement(FlowDetailNav, {ref: "head", flow: flow, tabs: tabs, active: active, selectTab: this.selectTab}), React.createElement(Tab, {flow: flow}) ) ); } }); var MainView = React.createClass({displayName: 'MainView', mixins: [Navigation, State], getInitialState: function () { this.onQueryChange(Query.FILTER, function () { this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); }.bind(this)); this.onQueryChange(Query.HIGHLIGHT, function () { this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); }.bind(this)); return { flows: [] }; }, getViewFilt: function () { try { var filt = Filt.parse(this.getQuery()[Query.FILTER] || ""); var highlightStr = this.getQuery()[Query.HIGHLIGHT]; var highlight = highlightStr ? Filt.parse(highlightStr) : false; } catch (e) { console.error("Error when processing filter: " + e); } return function filter_and_highlight(flow) { if (!this._highlight) { this._highlight = {}; } this._highlight[flow.id] = highlight && highlight(flow); return filt(flow); }; }, getViewSort: function () { }, componentWillReceiveProps: function (nextProps) { if (nextProps.flowStore !== this.props.flowStore) { this.closeView(); this.openView(nextProps.flowStore); } }, openView: function (store) { var view = new StoreView(store, this.getViewFilt(), this.getViewSort()); this.setState({ view: view }); view.addListener("recalculate", this.onRecalculate); view.addListener("add update remove", this.onUpdate); view.addListener("remove", this.onRemove); }, onRecalculate: function () { this.forceUpdate(); var selected = this.getSelected(); if (selected) { this.refs.flowTable.scrollIntoView(selected); } }, onUpdate: function (flow) { if (flow.id === this.getParams().flowId) { this.forceUpdate(); } }, onRemove: function (flow_id, index) { if (flow_id === this.getParams().flowId) { var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length -1)]; this.selectFlow(flow_to_select); } }, closeView: function () { this.state.view.close(); }, componentWillMount: function () { this.openView(this.props.flowStore); }, componentWillUnmount: function () { this.closeView(); }, selectFlow: function (flow) { if (flow) { this.replaceWith( "flow", { flowId: flow.id, detailTab: this.getParams().detailTab || "request" } ); this.refs.flowTable.scrollIntoView(flow); } else { this.replaceWith("flows", {}); } }, selectFlowRelative: function (shift) { var flows = this.state.view.list; var index; if (!this.getParams().flowId) { if (shift > 0) { index = flows.length - 1; } else { index = 0; } } else { var currFlowId = this.getParams().flowId; var i = flows.length; while (i--) { if (flows[i].id === currFlowId) { index = i; break; } } index = Math.min( Math.max(0, index + shift), flows.length - 1); } this.selectFlow(flows[index]); }, onKeyDown: function (e) { var flow = this.getSelected(); if (e.ctrlKey) { return; } switch (e.keyCode) { case Key.K: case Key.UP: this.selectFlowRelative(-1); break; case Key.J: case Key.DOWN: this.selectFlowRelative(+1); break; case Key.SPACE: case Key.PAGE_DOWN: this.selectFlowRelative(+10); break; case Key.PAGE_UP: this.selectFlowRelative(-10); break; case Key.END: this.selectFlowRelative(+1e10); break; case Key.HOME: this.selectFlowRelative(-1e10); break; case Key.ESC: this.selectFlow(null); break; case Key.H: case Key.LEFT: if (this.refs.flowDetails) { this.refs.flowDetails.nextTab(-1); } break; case Key.L: case Key.TAB: case Key.RIGHT: if (this.refs.flowDetails) { this.refs.flowDetails.nextTab(+1); } break; case Key.C: if (e.shiftKey) { FlowActions.clear(); } break; case Key.D: if (flow) { if (e.shiftKey) { FlowActions.duplicate(flow); } else { FlowActions.delete(flow); } } break; case Key.A: if (e.shiftKey) { FlowActions.accept_all(); } else if (flow) { FlowActions.accept(flow); } break; case Key.R: if (!e.shiftKey && flow) { FlowActions.replay(flow); } break; default: console.debug("keydown", e.keyCode); return; } e.preventDefault(); }, getSelected: function () { return this.props.flowStore.get(this.getParams().flowId); }, render: function () { var selected = this.getSelected(); var details; if (selected) { details = [ React.createElement(Splitter, {key: "splitter"}), React.createElement(FlowDetail, {key: "flowDetails", ref: "flowDetails", flow: selected}) ]; } else { details = null; } return ( React.createElement("div", {className: "main-view", onKeyDown: this.onKeyDown, tabIndex: "0"}, React.createElement(FlowTable, {ref: "flowTable", view: this.state.view, selectFlow: this.selectFlow, selected: selected}), details ) ); } }); var LogMessage = React.createClass({displayName: 'LogMessage', render: function () { var entry = this.props.entry; var indicator; switch (entry.level) { case "web": indicator = React.createElement("i", {className: "fa fa-fw fa-html5"}); break; case "debug": indicator = React.createElement("i", {className: "fa fa-fw fa-bug"}); break; default: indicator = React.createElement("i", {className: "fa fa-fw fa-info"}); } return ( React.createElement("div", null, indicator, " ", entry.message ) ); }, shouldComponentUpdate: function () { return false; // log entries are immutable. } }); var EventLogContents = React.createClass({displayName: 'EventLogContents', mixins: [AutoScrollMixin, VirtualScrollMixin], getInitialState: function () { return { log: [] }; }, componentWillMount: function () { this.openView(this.props.eventStore); }, componentWillUnmount: function () { this.closeView(); }, openView: function (store) { var view = new StoreView(store, function (entry) { return this.props.filter[entry.level]; }.bind(this)); this.setState({ view: view }); view.addListener("add recalculate", this.onEventLogChange); }, closeView: function () { this.state.view.close(); }, onEventLogChange: function () { this.setState({ log: this.state.view.list }); }, componentWillReceiveProps: function (nextProps) { if (nextProps.filter !== this.props.filter) { this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. this.state.view.recalculate(); } if (nextProps.eventStore !== this.props.eventStore) { this.closeView(); this.openView(nextProps.eventStore); } }, getDefaultProps: function () { return { rowHeight: 45, rowHeightMin: 15, placeholderTagName: "div" }; }, renderRow: function (elem) { return React.createElement(LogMessage, {key: elem.id, entry: elem}); }, render: function () { var rows = this.renderRows(this.state.log); return React.createElement("pre", {onScroll: this.onScroll}, this.getPlaceholderTop(this.state.log.length), rows, this.getPlaceholderBottom(this.state.log.length) ); } }); var ToggleFilter = React.createClass({displayName: 'ToggleFilter', toggle: function (e) { e.preventDefault(); return this.props.toggleLevel(this.props.name); }, render: function () { var className = "label "; if (this.props.active) { className += "label-primary"; } else { className += "label-default"; } return ( React.createElement("a", { href: "#", className: className, onClick: this.toggle}, this.props.name ) ); } }); var EventLog = React.createClass({displayName: 'EventLog', getInitialState: function () { return { filter: { "debug": false, "info": true, "web": true } }; }, close: function () { var d = {}; d[Query.SHOW_EVENTLOG] = undefined; this.setQuery(d); }, toggleLevel: function (level) { var filter = _.extend({}, this.state.filter); filter[level] = !filter[level]; this.setState({filter: filter}); }, render: function () { return ( React.createElement("div", {className: "eventlog"}, React.createElement("div", null, "Eventlog", React.createElement("div", {className: "pull-right"}, React.createElement(ToggleFilter, {name: "debug", active: this.state.filter.debug, toggleLevel: this.toggleLevel}), React.createElement(ToggleFilter, {name: "info", active: this.state.filter.info, toggleLevel: this.toggleLevel}), React.createElement(ToggleFilter, {name: "web", active: this.state.filter.web, toggleLevel: this.toggleLevel}), React.createElement("i", {onClick: this.close, className: "fa fa-close"}) ) ), React.createElement(EventLogContents, {filter: this.state.filter, eventStore: this.props.eventStore}) ) ); } }); var Footer = React.createClass({displayName: 'Footer', render: function () { var mode = this.props.settings.mode; var intercept = this.props.settings.intercept; return ( React.createElement("footer", null, mode != "regular" ? React.createElement("span", {className: "label label-success"}, mode, " mode") : null, " ", intercept ? React.createElement("span", {className: "label label-success"}, "Intercept: ", intercept) : null ) ); } }); //TODO: Move out of here, just a stub. var Reports = React.createClass({displayName: 'Reports', render: function () { return React.createElement("div", null, "ReportEditor"); } }); var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain', mixins: [State], getInitialState: function () { var eventStore = new EventLogStore(); var flowStore = new FlowStore(); var settings = new SettingsStore(); // Default Settings before fetch _.extend(settings.dict,{ }); return { settings: settings, flowStore: flowStore, eventStore: eventStore }; }, componentDidMount: function () { this.state.settings.addListener("recalculate", this.onSettingsChange); window.app = this; }, componentWillUnmount: function () { this.state.settings.removeListener("recalculate", this.onSettingsChange); }, onSettingsChange: function(){ this.setState({ settings: this.state.settings }); }, render: function () { var eventlog; if (this.getQuery()[Query.SHOW_EVENTLOG]) { eventlog = [ React.createElement(Splitter, {key: "splitter", axis: "y"}), React.createElement(EventLog, {key: "eventlog", eventStore: this.state.eventStore}) ]; } else { eventlog = null; } return ( React.createElement("div", {id: "container"}, React.createElement(Header, {settings: this.state.settings.dict}), React.createElement(RouteHandler, {settings: this.state.settings.dict, flowStore: this.state.flowStore}), eventlog, React.createElement(Footer, {settings: this.state.settings.dict}) ) ); } }); var Route = ReactRouter.Route; var RouteHandler = ReactRouter.RouteHandler; var Redirect = ReactRouter.Redirect; var DefaultRoute = ReactRouter.DefaultRoute; var NotFoundRoute = ReactRouter.NotFoundRoute; var routes = ( React.createElement(Route, {path: "/", handler: ProxyAppMain}, React.createElement(Route, {name: "flows", path: "flows", handler: MainView}), React.createElement(Route, {name: "flow", path: "flows/:flowId/:detailTab", handler: MainView}), React.createElement(Route, {name: "reports", handler: Reports}), React.createElement(Redirect, {path: "/", to: "flows"}) ) ); $(function () { window.ws = new Connection("/updates"); ReactRouter.run(routes, function (Handler) { React.render(React.createElement(Handler, null), document.body); }); }); //# sourceMappingURL=app.js.map