import React, {Component} from 'react'; import './ConnectionContent.scss'; import {Row} from 'react-bootstrap'; import MessageAction from "./MessageAction"; import backend from "../backend"; import ButtonField from "./fields/ButtonField"; import ChoiceField from "./fields/ChoiceField"; import DOMPurify from 'dompurify'; import ReactJson from 'react-json-view' import {getHeaderValue} from "../utils"; const classNames = require('classnames'); class ConnectionContent extends Component { constructor(props) { super(props); this.state = { loading: false, connectionContent: null, format: "default", tryParse: true, messageActionDialog: null }; this.validFormats = ["default", "hex", "hexdump", "base32", "base64", "ascii", "binary", "decimal", "octal"]; } componentDidMount() { if (this.props.connection != null) { this.loadStream(); } } componentDidUpdate(prevProps, prevState, snapshot) { if (this.props.connection != null && ( this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) { this.closeRenderWindow(); this.loadStream(); } } componentWillUnmount() { this.closeRenderWindow(); } loadStream = () => { this.setState({loading: true}); // TODO: limit workaround. backend.get(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => { this.setState({ connectionContent: res.json, loading: false }); }); }; setFormat = (format) => { if (this.validFormats.includes(format)) { this.setState({format: format}); } }; tryParseConnectionMessage = (connectionMessage) => { if (connectionMessage.metadata == null) { return connectionMessage.content; } if (connectionMessage["is_metadata_continuation"]) { return **already parsed in previous messages**; } let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) =>

{key}: {value}

); let m = connectionMessage.metadata; switch (m.type) { case "http-request": let url = {m.host}{m.url}; return

{m.method} {url} {m.protocol}

{unrollMap(m.headers)}
{m.body}
{unrollMap(m.trailers)}
; 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); body = ; } catch (e) { console.log(e); } } return

{m.protocol} {m.status}

{unrollMap(m.headers)}
{body}
{unrollMap(m.trailers)}
; default: return connectionMessage.content; } }; connectionsActions = (connectionMessage) => { if (connectionMessage.metadata == null) { //} || !connectionMessage.metadata["reproducers"]) { return null; } const m = connectionMessage.metadata; switch (m.type) { case "http-request" : if (!connectionMessage.metadata["reproducers"]) { return; } return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) => { this.setState({ messageActionDialog: this.setState({messageActionDialog: null})}/> }); }} /> ); case "http-response": const contentType = getHeaderValue(m, "Content-Type"); if (contentType && contentType.includes("text/html")) { return { 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; } }; closeRenderWindow = () => { if (this.state.renderWindow) { this.state.renderWindow.close(); } }; render() { let content = this.state.connectionContent; if (content == null) { return
select a connection to view
; } let payload = content.map((c, i) =>
offset: {c.index} | timestamp: {c.timestamp} | retransmitted: {c["is_retransmitted"] ? "yes" : "no"}
{this.connectionsActions(c)}
{c.from_client ? "client" : "server"}
{this.state.tryParse && this.state.format === "default" ? this.tryParseConnectionMessage(c) : c.content}
); return (
flow: {this.props.connection.ip_src}:{this.props.connection.port_src} -> {this.props.connection.ip_dst}:{this.props.connection.port_dst} | timestamp: {this.props.connection.started_at}
{payload}
{this.state.messageActionDialog}
); } } export default ConnectionContent;