aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components/panels/StreamsPane.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/panels/StreamsPane.jsx')
-rw-r--r--frontend/src/components/panels/StreamsPane.jsx453
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;