diff options
Diffstat (limited to 'frontend/src/components/panels/StreamsPane.jsx')
-rw-r--r-- | frontend/src/components/panels/StreamsPane.jsx | 453 |
1 files changed, 0 insertions, 453 deletions
diff --git a/frontend/src/components/panels/StreamsPane.jsx b/frontend/src/components/panels/StreamsPane.jsx deleted file mode 100644 index 9e88f55..0000000 --- a/frontend/src/components/panels/StreamsPane.jsx +++ /dev/null @@ -1,453 +0,0 @@ -/* - * 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 DOMPurify from "dompurify"; -import React, { Component } from "react"; -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 CopyDialog from "../dialogs/CopyDialog"; -import "./StreamsPane.scss"; - -import reactStringReplace from "react-string-replace"; -import classNames from "classnames"; - -class StreamsPane extends Component { - state = { - messages: [], - format: "default", - tryParse: true, - }; - - constructor(props) { - super(props); - - this.validFormats = [ - "default", - "hex", - "hexdump", - "base32", - "base64", - "ascii", - "binary", - "decimal", - "octal", - ]; - } - - componentDidMount() { - if ( - this.props.connection && - this.state.currentId !== this.props.connection.id - ) { - this.setState({ currentId: this.props.connection.id }); - this.loadStream(this.props.connection.id); - } - - document.title = "caronte:~/$"; - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if ( - this.props.connection && - (this.props.connection !== prevProps.connection || - this.state.format !== prevState.format) - ) { - this.closeRenderWindow(); - this.loadStream(this.props.connection.id); - } - } - - componentWillUnmount() { - this.closeRenderWindow(); - } - - loadStream = (connectionId) => { - this.setState({ messages: [], currentId: connectionId }); - backend - .get(`/api/streams/${connectionId}?format=${this.state.format}`) - .then((res) => this.setState({ messages: res.json })); - }; - - setFormat = (format) => { - if (this.validFormats.includes(format)) { - this.setState({ format }); - } - }; - - viewAs = (mode) => { - if (mode === "decoded") { - this.setState({ tryParse: true }); - } else if (mode === "raw") { - this.setState({ tryParse: false }); - } - }; - - tryParseConnectionMessage = (connectionMessage) => { - const isClient = connectionMessage["from_client"]; - if (connectionMessage.metadata == null) { - return this.highlightRules(connectionMessage.content, isClient); - } - - let unrollMap = (obj) => - obj == null - ? null - : Object.entries(obj).map(([key, value]) => ( - <p key={key}> - <strong>{key}</strong>: {value} - </p> - )); - - let m = connectionMessage.metadata; - switch (m.type) { - case "http-request": - let url = ( - <i> - <u> - <a - href={"http://" + m.host + m.url} - target="_blank" - rel="noopener noreferrer" - > - {m.host} - {m.url} - </a> - </u> - </i> - ); - 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" }}> - {this.highlightRules(m.body, isClient)} - </div> - {unrollMap(m.trailers)} - </span> - ); - case "http-response": - const contentType = getHeaderValue(m, "Content-Type"); - let body = m.body; - if (contentType && contentType.includes("application/json")) { - try { - const json = JSON.parse(m.body); - if (typeof json === "object") { - body = ( - <ReactJson - src={json} - theme="grayscale" - collapsed={false} - displayDataTypes={false} - /> - ); - } - } catch (e) { - log.error(e); - } - } - - 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" }}> - {this.highlightRules(body, isClient)} - </div> - {unrollMap(m.trailers)} - </span> - ); - default: - 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.replace(/^\//, "(").replace(/\/$/, ")"), - 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; - } - - const m = connectionMessage.metadata; - switch (m.type) { - case "http-request": - if (!connectionMessage.metadata["reproducers"]) { - return; - } - return Object.entries(connectionMessage.metadata["reproducers"]).map( - ([name, value]) => ( - <ButtonField - small - key={name + "_button"} - name={name} - onClick={() => { - this.setState({ - messageActionDialog: ( - <CopyDialog - actionName={name} - value={value} - onHide={() => - this.setState({ messageActionDialog: null }) - } - /> - ), - }); - }} - /> - ) - ); - case "http-response": - const contentType = getHeaderValue(m, "Content-Type"); - - if (contentType && contentType.includes("text/html")) { - return ( - <ButtonField - small - name="render_html" - onClick={() => { - let w; - if ( - this.state.renderWindow && - !this.state.renderWindow.closed - ) { - w = this.state.renderWindow; - } else { - w = window.open( - "", - "", - "width=900, height=600, scrollbars=yes" - ); - this.setState({ renderWindow: w }); - } - w.document.body.innerHTML = DOMPurify.sanitize(m.body); - w.focus(); - }} - /> - ); - } - break; - default: - return null; - } - }; - - downloadStreamRaw = (value) => { - if (this.state.currentId) { - backend - .download( - `/api/streams/${this.props.connection.id}/download?format=${this.state.format}&type=${value}` - ) - .then((res) => - downloadBlob( - res.blob, - `${this.state.currentId}-${value}-${this.state.format}.txt` - ) - ) - .catch((_) => log.error("Failed to download stream messages")); - } - }; - - closeRenderWindow = () => { - if (this.state.renderWindow) { - this.state.renderWindow.close(); - } - }; - - render() { - const conn = this.props.connection || { - ip_src: "0.0.0.0", - ip_dst: "0.0.0.0", - port_src: "0", - port_dst: "0", - started_at: new Date().toISOString(), - }; - const content = this.state.messages || []; - - let payload = content - .filter( - (c) => - !this.state.tryParse || - (this.state.tryParse && !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"> - <div className="row"> - <div className="connection-message-info col"> - <span> - <strong>offset</strong>: {c.index} - </span>{" "} - |{" "} - <span> - <strong>timestamp</strong>: {c.timestamp} - </span>{" "} - |{" "} - <span> - <strong>retransmitted</strong>:{" "} - {c["is_retransmitted"] ? "yes" : "no"} - </span> - </div> - <div className="connection-message-actions col-auto"> - {this.connectionsActions(c)} - </div> - </div> - </div> - <div className="connection-message-label"> - {c["from_client"] ? "client" : "server"} - </div> - <div className="message-content"> - {this.state.tryParse && this.state.format === "default" - ? this.tryParseConnectionMessage(c) - : c.content} - </div> - </div> - )); - - return ( - <div className="pane-container stream-pane"> - <div className="stream-pane-header container-fluid"> - <Row> - <div className="header-info col"> - <span> - <strong>flow</strong>: {conn["ip_src"]}:{conn["port_src"]} ->{" "} - {conn["ip_dst"]}:{conn["port_dst"]} - </span> - <span> - {" "} - | <strong>timestamp</strong>: {conn["started_at"]} - </span> - </div> - <div className="header-actions col-auto"> - <ChoiceField - name="format" - inline - small - onlyName - keys={[ - "default", - "hex", - "hexdump", - "base32", - "base64", - "ascii", - "binary", - "decimal", - "octal", - ]} - values={[ - "plain", - "hex", - "hexdump", - "base32", - "base64", - "ascii", - "binary", - "decimal", - "octal", - ]} - onChange={this.setFormat} - /> - - <ChoiceField - name="view_as" - inline - small - onlyName - onChange={this.viewAs} - keys={["decoded", "raw"]} - values={["decoded", "raw"]} - /> - - <ChoiceField - name="download_as" - inline - small - onlyName - onChange={this.downloadStreamRaw} - keys={[ - "nl_separated", - "only_client", - "only_server", - "pwntools", - ]} - values={[ - "nl_separated", - "only_client", - "only_server", - "pwntools", - ]} - /> - </div> - </Row> - </div> - - <pre>{payload}</pre> - {this.state.messageActionDialog} - </div> - ); - } -} - -export default StreamsPane; |