aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiliano Ciavatta2020-09-16 15:49:50 +0000
committerEmiliano Ciavatta2020-09-16 15:49:50 +0000
commitdfd6d543074b4a30c2fc990063ca69ebf8a734e1 (patch)
treea022ad9861b35c38ad1d0945d79470adfe26ae52
parentec949ffea86a14526a7142d048022a4a07f684ff (diff)
Fix body decoding bugs. Improve frontend
-rw-r--r--connection_streams_controller.go37
-rw-r--r--frontend/src/components/ConnectionContent.js82
-rw-r--r--frontend/src/components/ConnectionContent.scss20
-rw-r--r--frontend/src/components/MessageAction.js31
-rw-r--r--frontend/src/components/MessageAction.scss11
-rw-r--r--parsers/http_request_parser.go21
-rw-r--r--parsers/http_response_parser.go31
7 files changed, 144 insertions, 89 deletions
diff --git a/connection_streams_controller.go b/connection_streams_controller.go
index c4876b1..9d73b0e 100644
--- a/connection_streams_controller.go
+++ b/connection_streams_controller.go
@@ -93,7 +93,7 @@ func (csc ConnectionStreamsController) GetConnectionPayload(c context.Context, c
if clientBlocksIndex < len(clientStream.BlocksIndexes)-1 {
end = clientStream.BlocksIndexes[clientBlocksIndex+1]
} else {
- end = len(clientStream.Payload) - 1
+ end = len(clientStream.Payload)
}
size := uint64(end - start)
@@ -117,7 +117,7 @@ func (csc ConnectionStreamsController) GetConnectionPayload(c context.Context, c
if serverBlocksIndex < len(serverStream.BlocksIndexes)-1 {
end = serverStream.BlocksIndexes[serverBlocksIndex+1]
} else {
- end = len(serverStream.Payload) - 1
+ end = len(serverStream.Payload)
}
size := uint64(end - start)
@@ -137,7 +137,18 @@ func (csc ConnectionStreamsController) GetConnectionPayload(c context.Context, c
sideChanged, lastClient, lastServer = lastClient, false, true
}
- if sideChanged {
+ if !hasClientBlocks() {
+ clientDocumentIndex++
+ clientBlocksIndex = 0
+ clientStream = csc.getConnectionStream(c, connectionID, true, clientDocumentIndex)
+ }
+ if !hasServerBlocks() {
+ serverDocumentIndex++
+ serverBlocksIndex = 0
+ serverStream = csc.getConnectionStream(c, connectionID, false, serverDocumentIndex)
+ }
+
+ updateMetadata := func() {
metadata := parsers.Parse(contentChunkBuffer.Bytes())
var isMetadataContinuation bool
for _, elem := range payloadsBuffer {
@@ -149,28 +160,26 @@ func (csc ConnectionStreamsController) GetConnectionPayload(c context.Context, c
payloadsBuffer = payloadsBuffer[:0]
contentChunkBuffer.Reset()
}
+
+ if sideChanged {
+ updateMetadata()
+ }
payloadsBuffer = append(payloadsBuffer, payload)
contentChunkBuffer.Write(lastContentSlice)
+ if clientStream.ID.IsZero() && serverStream.ID.IsZero() {
+ updateMetadata()
+ }
+
if globalIndex > format.Skip {
// problem: waste of time if the payload is discarded
payloads = append(payloads, payload)
}
if globalIndex > format.Skip+format.Limit {
// problem: the last chunk is not parsed, but can be ok because it is not finished
+ updateMetadata()
return payloads
}
-
- if !hasClientBlocks() {
- clientDocumentIndex++
- clientBlocksIndex = 0
- clientStream = csc.getConnectionStream(c, connectionID, true, clientDocumentIndex)
- }
- if !hasServerBlocks() {
- serverDocumentIndex++
- serverBlocksIndex = 0
- serverStream = csc.getConnectionStream(c, connectionID, false, serverDocumentIndex)
- }
}
return payloads
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
index 51dbb67..20ec92b 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -2,7 +2,6 @@ import React, {Component} from 'react';
import './ConnectionContent.scss';
import {Button, Dropdown, Row} from 'react-bootstrap';
import axios from 'axios';
-import {timestampToDateTime, timestampToTime} from "../utils";
import MessageAction from "./MessageAction";
const classNames = require('classnames');
@@ -15,7 +14,7 @@ class ConnectionContent extends Component {
loading: false,
connectionContent: null,
format: "default",
- decoded: false,
+ tryParse: true,
messageActionDialog: null
};
@@ -43,16 +42,12 @@ class ConnectionContent extends Component {
}
}
- toggleDecoded() {
- this.setState({decoded: !this.state.decoded});
- }
-
tryParseConnectionMessage(connectionMessage) {
if (connectionMessage.metadata == null) {
return connectionMessage.content;
}
if (connectionMessage["is_metadata_continuation"]) {
- return <span>already parsed in previous messages</span>;
+ return <span style={{"fontSize": "12px"}}>**already parsed in previous messages**</span>;
}
let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) =>
@@ -62,16 +57,17 @@ class ConnectionContent extends Component {
let m = connectionMessage.metadata;
switch (m.type) {
case "http-request":
- let url = <i><u><a href={"http://" + m.host + m.url} target="_blank">{m.host}{m.url}</a></u></i>;
+ 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={{"margin-bottom": "7px"}}><strong>{m.method}</strong> {url} {m.protocol}</p>
+ <p style={{"marginBottom": "7px"}}><strong>{m.method}</strong> {url} {m.protocol}</p>
{unrollMap(m.headers)}
<div style={{"margin": "20px 0"}}>{m.body}</div>
{unrollMap(m.trailers)}
</span>;
case "http-response":
return <span className="type-http-response">
- <p style={{"margin-bottom": "7px"}}>{m.protocol} <strong>{m.status}</strong></p>
+ <p style={{"marginBottom": "7px"}}>{m.protocol} <strong>{m.status}</strong></p>
{unrollMap(m.headers)}
<div style={{"margin": "20px 0"}}>{m.body}</div>
{unrollMap(m.trailers)}
@@ -87,9 +83,10 @@ class ConnectionContent extends Component {
}
return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) =>
- <Button size="sm" onClick={() => {
+ <Button size="sm" key={actionName + "_button"} onClick={() => {
this.setState({
- messageActionDialog: <MessageAction actionName={actionName} actionValue={actionValue} onHide={() => this.setState({messageActionDialog: null})}/>
+ messageActionDialog: <MessageAction actionName={actionName} actionValue={actionValue}
+ onHide={() => this.setState({messageActionDialog: null})}/>
});
}}>{actionName}</Button>
);
@@ -115,8 +112,9 @@ class ConnectionContent extends Component {
</div>
</div>
<div className="connection-message-label">{c.from_client ? "client" : "server"}</div>
- <div className={classNames("message-content", this.state.decoded ? "message-parsed" : "message-original")}>
- {this.state.decoded ? this.tryParseConnectionMessage(c) : c.content}
+ <div
+ className={classNames("message-content", this.state.decoded ? "message-parsed" : "message-original")}>
+ {this.state.tryParse && this.state.format === "default" ? this.tryParseConnectionMessage(c) : c.content}
</div>
</div>
);
@@ -129,25 +127,55 @@ class ConnectionContent extends Component {
<span><strong>flow</strong>: {this.props.connection.ip_src}:{this.props.connection.port_src} -> {this.props.connection.ip_dst}:{this.props.connection.port_dst}</span>
<span> | <strong>timestamp</strong>: {this.props.connection.started_at}</span>
</div>
- <div className="col-auto">
- <Dropdown onSelect={this.setFormat} >
- <Dropdown.Toggle size="sm" id="dropdown-basic">
+ <div className="header-actions col-auto">
+ <Dropdown onSelect={this.setFormat}>
+ <Dropdown.Toggle size="sm" id="connection-content-format">
format
</Dropdown.Toggle>
<Dropdown.Menu>
- <Dropdown.Item eventKey="default" active={this.state.format === "default"}>plain</Dropdown.Item>
- <Dropdown.Item eventKey="hex" active={this.state.format === "hex"}>hex</Dropdown.Item>
- <Dropdown.Item eventKey="hexdump" active={this.state.format === "hexdump"}>hexdump</Dropdown.Item>
- <Dropdown.Item eventKey="base32" active={this.state.format === "base32"}>base32</Dropdown.Item>
- <Dropdown.Item eventKey="base64" active={this.state.format === "base64"}>base64</Dropdown.Item>
- <Dropdown.Item eventKey="ascii" active={this.state.format === "ascii"}>ascii</Dropdown.Item>
- <Dropdown.Item eventKey="binary" active={this.state.format === "binary"}>binary</Dropdown.Item>
- <Dropdown.Item eventKey="decimal" active={this.state.format === "decimal"}>decimal</Dropdown.Item>
- <Dropdown.Item eventKey="octal" active={this.state.format === "octal"}>octal</Dropdown.Item>
+ <Dropdown.Item eventKey="default"
+ active={this.state.format === "default"}>plain</Dropdown.Item>
+ <Dropdown.Item eventKey="hex"
+ active={this.state.format === "hex"}>hex</Dropdown.Item>
+ <Dropdown.Item eventKey="hexdump"
+ active={this.state.format === "hexdump"}>hexdump</Dropdown.Item>
+ <Dropdown.Item eventKey="base32"
+ active={this.state.format === "base32"}>base32</Dropdown.Item>
+ <Dropdown.Item eventKey="base64"
+ active={this.state.format === "base64"}>base64</Dropdown.Item>
+ <Dropdown.Item eventKey="ascii"
+ active={this.state.format === "ascii"}>ascii</Dropdown.Item>
+ <Dropdown.Item eventKey="binary"
+ active={this.state.format === "binary"}>binary</Dropdown.Item>
+ <Dropdown.Item eventKey="decimal"
+ active={this.state.format === "decimal"}>decimal</Dropdown.Item>
+ <Dropdown.Item eventKey="octal"
+ active={this.state.format === "octal"}>octal</Dropdown.Item>
+ </Dropdown.Menu>
+ </Dropdown>
+
+ <Dropdown>
+ <Dropdown.Toggle size="sm" id="connection-content-view">
+ view_as
+ </Dropdown.Toggle>
+
+ <Dropdown.Menu>
+ <Dropdown.Item eventKey="default" active={true}>default</Dropdown.Item>
</Dropdown.Menu>
- <Button size="sm" onClick={() => this.toggleDecoded()}>{this.state.decoded ? "Encode" : "Decode"}</Button>
+ </Dropdown>
+
+ <Dropdown>
+ <Dropdown.Toggle size="sm" id="connection-content-download">
+ download_as
+ </Dropdown.Toggle>
+
+ <Dropdown.Menu>
+ <Dropdown.Item eventKey="nl_separated">nl_separated</Dropdown.Item>
+ <Dropdown.Item eventKey="only_client">only_client</Dropdown.Item>
+ <Dropdown.Item eventKey="only_server">only_server</Dropdown.Item>
+ </Dropdown.Menu>
</Dropdown>
</div>
diff --git a/frontend/src/components/ConnectionContent.scss b/frontend/src/components/ConnectionContent.scss
index 6354bee..8ee31ec 100644
--- a/frontend/src/components/ConnectionContent.scss
+++ b/frontend/src/components/ConnectionContent.scss
@@ -11,6 +11,11 @@
overflow-x: hidden;
height: calc(100% - 31px);
padding: 0 10px;
+
+ p {
+ margin: 0;
+ padding: 0;
+ }
}
.connection-message {
@@ -45,13 +50,6 @@
padding: 10px;
}
- .message-parsed {
- p {
- margin: 0;
- padding: 0;
- }
- }
-
&:hover .connection-message-actions {
display: block;
}
@@ -96,7 +94,13 @@
.header-info {
padding-top: 5px;
padding-left: 20px;
- font-size: 13px;
+ font-size: 12px;
+ }
+
+ .header-actions {
+ .dropdown {
+ display: inline-block;
+ }
}
}
diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js
index 66350c6..2c85d84 100644
--- a/frontend/src/components/MessageAction.js
+++ b/frontend/src/components/MessageAction.js
@@ -4,7 +4,21 @@ import {Button, FormControl, InputGroup, Modal} from "react-bootstrap";
class MessageAction extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ copyButtonText: "copy"
+ };
+ this.actionValue = React.createRef();
+ this.copyActionValue = this.copyActionValue.bind(this);
+ }
+ copyActionValue() {
+ this.actionValue.current.select();
+ document.execCommand('copy');
+ this.setState({copyButtonText: "copied!"});
+ setTimeout(() => this.setState({copyButtonText: "copy"}), 3000);
+ }
render() {
return (
@@ -21,20 +35,13 @@ class MessageAction extends Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <div className="message-action-value">
- <pre>
- {this.props.actionValue}
- </pre>
- </div>
-
- {/*<InputGroup>*/}
- {/* <FormControl as="textarea" className="message-action-value" readOnly={true}*/}
- {/* style={{"height": "300px"}}*/}
- {/* value={this.props.actionValue}/>*/}
- {/*</InputGroup>*/}
+ <InputGroup>
+ <FormControl as="textarea" className="message-action-value" readOnly={true}
+ style={{"height": "300px"}} value={this.props.actionValue} ref={this.actionValue} />
+ </InputGroup>
</Modal.Body>
<Modal.Footer className="dialog-footer">
- <Button variant="green" onClick={this.copyActionValue}>copy</Button>
+ <Button variant="green" onClick={this.copyActionValue}>{this.state.copyButtonText}</Button>
<Button variant="red" onClick={this.props.onHide}>close</Button>
</Modal.Footer>
</Modal>
diff --git a/frontend/src/components/MessageAction.scss b/frontend/src/components/MessageAction.scss
index df3af8d..f3a8772 100644
--- a/frontend/src/components/MessageAction.scss
+++ b/frontend/src/components/MessageAction.scss
@@ -1,11 +1,8 @@
@import '../colors.scss';
.message-action-value {
- pre {
- font-size: 13px;
- padding: 15px;
- background-color: $color-primary-2;
- color: $color-primary-4;
- }
-
+ font-size: 13px;
+ padding: 15px;
+ background-color: $color-primary-2;
+ color: $color-primary-4;
} \ No newline at end of file
diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go
index cfac196..e2224b8 100644
--- a/parsers/http_request_parser.go
+++ b/parsers/http_request_parser.go
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"encoding/json"
+ log "github.com/sirupsen/logrus"
"io/ioutil"
"moul.io/http2curl"
"net/http"
@@ -41,12 +42,13 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata {
return nil
}
var body string
- if request.Body != nil {
- if buffer, err := ioutil.ReadAll(request.Body); err == nil {
- body = string(buffer)
- }
- _ = request.Body.Close()
+ if buffer, err := ioutil.ReadAll(request.Body); err == nil {
+ body = string(buffer)
+ } else {
+ log.WithError(err).Error("failed to read body in http_request_parser")
+ return nil
}
+ _ = request.Body.Close()
_ = request.ParseForm()
return HttpRequestMetadata{
@@ -62,18 +64,21 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata {
Body: body,
Trailer: JoinArrayMap(request.Trailer),
Reproducers: HttpRequestMetadataReproducers{
- CurlCommand: curlCommand(request),
+ CurlCommand: curlCommand(content),
RequestsCode: requestsCode(request),
FetchRequest: fetchRequest(request, body),
},
}
}
-func curlCommand(request *http.Request) string {
+func curlCommand(content []byte) string {
+ // a new reader is required because all the body is read before and GetBody() doesn't works
+ reader := bufio.NewReader(bytes.NewReader(content))
+ request, _ := http.ReadRequest(reader)
if command, err := http2curl.GetCurlCommand(request); err == nil {
return command.String()
} else {
- return "invalid-request"
+ return err.Error()
}
}
diff --git a/parsers/http_response_parser.go b/parsers/http_response_parser.go
index a639dec..1770116 100644
--- a/parsers/http_response_parser.go
+++ b/parsers/http_response_parser.go
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"compress/gzip"
+ log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
)
@@ -33,23 +34,27 @@ func (p HttpResponseParser) TryParse(content []byte) Metadata {
}
var body string
var compressed bool
- if response.Body != nil {
- switch response.Header.Get("Content-Encoding") {
- case "gzip":
- if gzipReader, err := gzip.NewReader(response.Body); err == nil {
- if buffer, err := ioutil.ReadAll(gzipReader); err == nil {
- body = string(buffer)
- compressed = true
- }
- _ = gzipReader.Close()
- }
- default:
- if buffer, err := ioutil.ReadAll(response.Body); err == nil {
+ switch response.Header.Get("Content-Encoding") {
+ case "gzip":
+ if gzipReader, err := gzip.NewReader(response.Body); err == nil {
+ if buffer, err := ioutil.ReadAll(gzipReader); err == nil {
body = string(buffer)
+ compressed = true
+ } else {
+ log.WithError(err).Error("failed to read gzipped body in http_response_parser")
+ return nil
}
+ _ = gzipReader.Close()
+ }
+ default:
+ if buffer, err := ioutil.ReadAll(response.Body); err == nil {
+ body = string(buffer)
+ } else {
+ log.WithError(err).Error("failed to read body in http_response_parser")
+ return nil
}
- _ = response.Body.Close()
}
+ _ = response.Body.Close()
var location string
if locationUrl, err := response.Location(); err == nil {