diff options
-rw-r--r-- | frontend/package.json | 1 | ||||
-rw-r--r-- | frontend/src/components/panels/StreamsPane.js | 40 | ||||
-rw-r--r-- | frontend/src/components/panels/StreamsPane.scss | 6 | ||||
-rw-r--r-- | frontend/src/model/rules.js | 43 | ||||
-rw-r--r-- | frontend/yarn.lock | 9 |
5 files changed, 90 insertions, 9 deletions
diff --git a/frontend/package.json b/frontend/package.json index c91665f..b3a4494 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "react-router": "^5.1.2", "react-router-dom": "^5.1.2", "react-scripts": "3.4.1", + "react-string-replace": "^0.4.4", "react-tag-autocomplete": "^6.0.0-beta.6", "react-timeseries-charts": "^0.16.1", "typed.js": "^2.0.11" diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js index 9470d7d..cc3d53a 100644 --- a/frontend/src/components/panels/StreamsPane.js +++ b/frontend/src/components/panels/StreamsPane.js @@ -21,12 +21,14 @@ import {Row} from "react-bootstrap"; import ReactJson from "react-json-view"; import backend from "../../backend"; import log from "../../log"; +import rules from "../../model/rules"; import {downloadBlob, getHeaderValue} from "../../utils"; import ButtonField from "../fields/ButtonField"; import ChoiceField from "../fields/ChoiceField"; import MessageAction from "../objects/MessageAction"; import "./StreamsPane.scss"; +const reactStringReplace = require('react-string-replace') const classNames = require("classnames"); class StreamsPane extends Component { @@ -77,11 +79,9 @@ class StreamsPane extends Component { }; tryParseConnectionMessage = (connectionMessage) => { + const isClient = connectionMessage["from_client"]; if (connectionMessage.metadata == null) { - return connectionMessage.content; - } - if (connectionMessage["is_metadata_continuation"]) { - return <span style={{"fontSize": "12px"}}>**already parsed in previous messages**</span>; + return this.highlightRules(connectionMessage.content, isClient); } let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) => @@ -96,7 +96,7 @@ class StreamsPane extends Component { return <span className="type-http-request"> <p style={{"marginBottom": "7px"}}><strong>{m.method}</strong> {url} {m.protocol}</p> {unrollMap(m.headers)} - <div style={{"margin": "20px 0"}}>{m.body}</div> + <div style={{"margin": "20px 0"}}>{this.highlightRules(m.body, isClient)}</div> {unrollMap(m.trailers)} </span>; case "http-response": @@ -108,20 +108,44 @@ class StreamsPane extends Component { body = <ReactJson src={json} theme="grayscale" collapsed={false} displayDataTypes={false}/>; } catch (e) { log.error(e); + body = m.body; } } return <span className="type-http-response"> <p style={{"marginBottom": "7px"}}>{m.protocol} <strong>{m.status}</strong></p> {unrollMap(m.headers)} - <div style={{"margin": "20px 0"}}>{body}</div> + <div style={{"margin": "20px 0"}}>{this.highlightRules(body, isClient)}</div> {unrollMap(m.trailers)} </span>; default: - return connectionMessage.content; + return this.highlightRules(connectionMessage.content, isClient); } }; + highlightRules = (content, isClient) => { + let streamContent = content; + this.props.connection["matched_rules"].forEach(ruleId => { + const rule = rules.ruleById(ruleId); + rule.patterns.forEach(pattern => { + if ((!isClient && pattern.direction === 1) || (isClient && pattern.direction === 2)) { + return; + } + let flags = ""; + pattern["caseless"] && (flags += "i"); + pattern["dot_all"] && (flags += "s"); + pattern["multi_line"] && (flags += "m"); + pattern["unicode_property"] && (flags += "u"); + const regex = new RegExp("(" + pattern.regex + ")", flags); + streamContent = reactStringReplace(streamContent, regex, (match, i) => ( + <span key={i} className="matched-occurrence" style={{"backgroundColor": rule.color}}>{match}</span> + )); + }); + }); + + return streamContent; + }; + connectionsActions = (connectionMessage) => { if (!connectionMessage.metadata) { return null; @@ -187,7 +211,7 @@ class StreamsPane extends Component { }; const content = this.state.messages || []; - let payload = content.map((c, i) => + let payload = content.filter((c) => !c["is_metadata_continuation"]).map((c, i) => <div key={`content-${i}`} className={classNames("connection-message", c["from_client"] ? "from-client" : "from-server")}> <div className="connection-message-header container-fluid"> diff --git a/frontend/src/components/panels/StreamsPane.scss b/frontend/src/components/panels/StreamsPane.scss index 1d8a250..c44c7eb 100644 --- a/frontend/src/components/panels/StreamsPane.scss +++ b/frontend/src/components/panels/StreamsPane.scss @@ -18,6 +18,12 @@ &:hover::-webkit-scrollbar-thumb { background: $color-secondary-2; } + + .matched-occurrence { + color: $color-primary-4; + font-weight: 500; + border-radius: 2px; + } } .connection-message { diff --git a/frontend/src/model/rules.js b/frontend/src/model/rules.js new file mode 100644 index 0000000..625d610 --- /dev/null +++ b/frontend/src/model/rules.js @@ -0,0 +1,43 @@ +/* + * This file is part of caronte (https://github.com/eciavatta/caronte). + * Copyright (c) 2020 Emiliano Ciavatta. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import backend from "../backend"; +import log from "../log"; + +const _ = require("lodash"); + +class Rules { + + constructor() { + this.rules = []; + this.loadRules(); + } + + loadRules = () => { + backend.get("/api/rules").then((res) => this.rules = res.json) + .catch((err) => log.error("Failed to load rules", err)); + }; + + allRules = () => _.clone(this.rules); + + ruleById = (id) => _.clone(this.rules.find(r => r.id === id)); + +} + +const rules = new Rules(); + +export default rules; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e9f01ac..54cc802 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7248,7 +7248,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.10: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -9743,6 +9743,13 @@ react-scripts@3.4.1: optionalDependencies: fsevents "2.1.2" +react-string-replace@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-0.4.4.tgz#24006fbe0db573d5be583133df38b1a735cb4225" + integrity sha512-FAMkhxmDpCsGTwTZg7p/2v+/GTmxAp73so3fbSvlAcBBX36ujiGRNEaM/1u+jiYQrArhns+7eE92g2pi5E5FUA== + dependencies: + lodash "^4.17.4" + react-tag-autocomplete@^6.0.0-beta.6: version "6.1.0" resolved "https://registry.yarnpkg.com/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz#9fb70149a69b33379013e5255bcd7ad97d8ec06b" |