Add Represent method to the Dissector interface and manipulate the UI through this method

This commit is contained in:
M. Mert Yildiran 2021-08-21 08:19:09 +03:00
parent 03099edfc1
commit c5969c267e
No known key found for this signature in database
GPG Key ID: D42ADB236521BF7A
9 changed files with 203 additions and 62 deletions

View File

@ -236,9 +236,12 @@ func GetEntry(c *gin.Context) {
// "msg": "Can't get entry details",
// })
// }
extension := extensionsMap[entryData.ProtocolName]
representation, _ := extension.Dissector.Represent(entryData.Entry)
c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{
Protocol: extensionsMap[entryData.ProtocolName].Protocol,
Data: entryData,
Protocol: extension.Protocol,
Representation: string(representation),
Data: entryData,
})
}

View File

@ -66,6 +66,7 @@ type Dissector interface {
Dissect(b *bufio.Reader, isClient bool, tcpID *TcpID, emitter Emitter)
Analyze(item *OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *MizuEntry
Summarize(entry *MizuEntry) *BaseEntryDetails
Represent(entry string) ([]byte, error)
}
type Emitting struct {
@ -109,8 +110,9 @@ type MizuEntry struct {
}
type MizuEntryWrapper struct {
Protocol Protocol `json:"protocol"`
Data MizuEntry `json:"data"`
Protocol Protocol `json:"protocol"`
Representation string `json:"representation"`
Data MizuEntry `json:"data"`
}
type BaseEntryDetails struct {

View File

@ -47,4 +47,9 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
return nil
}
func (d dissecting) Represent(entry string) ([]byte, error) {
// TODO: Implement
return nil, nil
}
var Dissector dissecting

View File

@ -17,12 +17,12 @@ var responseCounter uint
var protocol api.Protocol = api.Protocol{
Name: "http",
LongName: "Hypertext Transfer Protocol -- HTTP/1.0",
LongName: "Hypertext Transfer Protocol -- HTTP/1.1",
Abbreviation: "HTTP",
BackgroundColor: "#205cf5",
ForegroundColor: "#ffffff",
FontSize: 10,
ReferenceLink: "https://www.ietf.org/rfc/rfc1945.txt",
ReferenceLink: "https://datatracker.ietf.org/doc/html/rfc2616",
OutboundPorts: []string{"80", "8080", "443"},
InboundPorts: []string{},
}
@ -160,4 +160,134 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
}
}
func representRequest(request map[string]interface{}) []interface{} {
repRequest := make([]interface{}, 0)
details, _ := json.Marshal([]map[string]string{
{
"name": "Method",
"value": request["method"].(string),
},
{
"name": "URL",
"value": request["url"].(string),
},
{
"name": "Body Size",
"value": fmt.Sprintf("%g bytes", request["bodySize"].(float64)),
},
})
repRequest = append(repRequest, map[string]string{
"type": "table",
"title": "Details",
"data": string(details),
})
headers, _ := json.Marshal(request["headers"].([]interface{}))
repRequest = append(repRequest, map[string]string{
"type": "table",
"title": "Headers",
"data": string(headers),
})
cookies, _ := json.Marshal(request["cookies"].([]interface{}))
repRequest = append(repRequest, map[string]string{
"type": "table",
"title": "Cookies",
"data": string(cookies),
})
queryString, _ := json.Marshal(request["queryString"].([]interface{}))
repRequest = append(repRequest, map[string]string{
"type": "table",
"title": "Query String",
"data": string(queryString),
})
postData, _ := request["postData"].(map[string]interface{})
mimeType, _ := postData["mimeType"]
if mimeType == nil {
mimeType = "N/A"
}
text, _ := postData["text"]
if text != nil {
repRequest = append(repRequest, map[string]string{
"type": "body",
"title": "POST Data",
"encoding": "base64", // FIXME: Does `request` have it?
"mime_type": mimeType.(string),
"data": text.(string),
})
}
return repRequest
}
func representResponse(response map[string]interface{}) []interface{} {
repResponse := make([]interface{}, 0)
details, _ := json.Marshal([]map[string]string{
{
"name": "Status",
"value": fmt.Sprintf("%g", response["status"].(float64)),
},
{
"name": "Status Text",
"value": response["statusText"].(string),
},
{
"name": "Body Size",
"value": fmt.Sprintf("%g bytes", response["bodySize"].(float64)),
},
})
repResponse = append(repResponse, map[string]string{
"type": "table",
"title": "Details",
"data": string(details),
})
headers, _ := json.Marshal(response["headers"].([]interface{}))
repResponse = append(repResponse, map[string]string{
"type": "table",
"title": "Headers",
"data": string(headers),
})
cookies, _ := json.Marshal(response["cookies"].([]interface{}))
repResponse = append(repResponse, map[string]string{
"type": "table",
"title": "Cookies",
"data": string(cookies),
})
content, _ := response["content"].(map[string]interface{})
mimeType, _ := content["mimeType"]
encoding, _ := content["encoding"]
text, _ := content["text"]
if text != nil {
repResponse = append(repResponse, map[string]string{
"type": "body",
"title": "Body",
"encoding": encoding.(string),
"mime_type": mimeType.(string),
"data": text.(string),
})
}
return repResponse
}
func (d dissecting) Represent(entry string) ([]byte, error) {
var root map[string]interface{}
json.Unmarshal([]byte(entry), &root)
representation := make(map[string]interface{}, 0)
request := root["request"].(map[string]interface{})["payload"].(map[string]interface{})
response := root["response"].(map[string]interface{})["payload"].(map[string]interface{})
repRequest := representRequest(request)
repResponse := representResponse(response)
representation["request"] = repRequest
representation["response"] = repResponse
return json.Marshal(representation)
}
var Dissector dissecting

View File

@ -47,4 +47,9 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
return nil
}
func (d dissecting) Represent(entry string) ([]byte, error) {
// TODO: Implement
return nil, nil
}
var Dissector dissecting

View File

@ -77,10 +77,7 @@ export const HAREntryDetailed: React.FC<HarEntryDetailedProps> = ({classes, harE
<HarEntryTitle protocol={harEntry.protocol} har={har}/>
{har && <HarEntrySummary har={har}/>}
<>
{har && <HAREntryViewer
harObject={har}
className={classes?.root ?? styles.har}
/>}
{har && <HAREntryViewer representation={harEntry.representation}/>}
</>
</>
};

View File

@ -31,7 +31,6 @@
margin: .3rem 0
.dataKey
text-transform: capitalize
color: $blue-gray
margin: 0 0.5rem 0 0
text-align: right

View File

@ -80,9 +80,9 @@ export const HAREntryBodySection: React.FC<HAREntryBodySectionProps> = ({
const bodyBuf = encoding === 'base64' ? atob(chunk) : chunk;
try {
if (jsonLikeFormats.some(format => content?.mimeType?.indexOf(format) > -1)) {
if (jsonLikeFormats.some(format => contentType?.indexOf(format) > -1)) {
return JSON.stringify(JSON.parse(bodyBuf), null, 2);
} else if (protobufFormats.some(format => content?.mimeType?.indexOf(format) > -1)) {
} else if (protobufFormats.some(format => contentType?.indexOf(format) > -1)) {
// Replace all non printable characters (ASCII)
const protobufDecoder = new ProtobufDecoder(bodyBuf, true);
return JSON.stringify(protobufDecoder.decode().toSimple(), null, 2);
@ -94,17 +94,17 @@ export const HAREntryBodySection: React.FC<HAREntryBodySectionProps> = ({
}
const getLanguage = (mimetype) => {
const chunk = content.text?.slice(0, 100);
const chunk = content?.slice(0, 100);
if (chunk.indexOf('html') > 0 || chunk.indexOf('HTML') > 0) return supportedLanguages[0][1];
const language = supportedLanguages.find(el => (mimetype + contentType).indexOf(el[0]) > -1);
return language ? language[1] : 'default';
}
return <React.Fragment>
{content && content.text?.length > 0 && <HAREntrySectionContainer title='Body'>
{content && content?.length > 0 && <HAREntrySectionContainer title='Body'>
<table>
<tbody>
<HAREntryViewLine label={'Mime type'} value={content?.mimeType}/>
<HAREntryViewLine label={'Mime type'} value={contentType}/>
<HAREntryViewLine label={'Encoding'} value={encoding}/>
</tbody>
</table>
@ -118,7 +118,7 @@ export const HAREntryBodySection: React.FC<HAREntryBodySectionProps> = ({
<SyntaxHighlighter
isWrapped={isWrapped}
code={formatTextBody(content.text)}
code={formatTextBody(content)}
language={content?.mimeType ? getLanguage(content.mimeType) : 'default'}
/>
</HAREntrySectionContainer>}
@ -205,52 +205,47 @@ export const HAREntryTablePolicySection: React.FC<HAREntryPolicySectionProps> =
<table>
<tbody>
{arrayToIterate.map(({rule, matched}, index) => {
return (
<HAREntryPolicySectionContainer key={index} label={rule.Name} matched={matched && (rule.Type === 'latency' ? rule.Latency >= latency : true)? "Success" : "Failure"}>
{
<>
{
rule.Key != "" ?
rule.Key !== "" ?
<tr className={styles.dataValue}><td><b>Key</b>:</td><td>{rule.Key}</td></tr>
: null
}
{
rule.Latency != "" ?
rule.Latency !== "" ?
<tr className={styles.dataValue}><td><b>Latency:</b></td> <td>{rule.Latency}</td></tr>
: null
}
{
rule.Method != "" ?
rule.Method !== "" ?
<tr className={styles.dataValue}><td><b>Method:</b></td> <td>{rule.Method}</td></tr>
: null
}
{
rule.Path != "" ?
rule.Path !== "" ?
<tr className={styles.dataValue}><td><b>Path:</b></td> <td>{rule.Path}</td></tr>
: null
}
{
rule.Service != "" ?
rule.Service !== "" ?
<tr className={styles.dataValue}><td><b>Service:</b></td> <td>{service}</td></tr>
: null
}
{
rule.Type != "" ?
rule.Type !== "" ?
<tr className={styles.dataValue}><td><b>Type:</b></td> <td>{rule.Type}</td></tr>
: null
}
{
rule.Value != "" ?
rule.Value !== "" ?
<tr className={styles.dataValue}><td><b>Value:</b></td> <td>{rule.Value}</td></tr>
: null
}
</>
}
</HAREntryPolicySectionContainer>
)
}
@ -259,8 +254,7 @@ export const HAREntryTablePolicySection: React.FC<HAREntryPolicySectionProps> =
</tbody>
</table>
</HAREntrySectionContainer>
</> : <span/>
}
</React.Fragment>
}
}

View File

@ -3,11 +3,34 @@ import styles from './HAREntryViewer.module.sass';
import Tabs from "../Tabs";
import {HAREntryTableSection, HAREntryBodySection, HAREntryTablePolicySection} from "./HAREntrySections";
const MIME_TYPE_KEY = 'mimeType';
const SectionsRepresentation: React.FC<any> = ({data}) => {
const sections = []
const HAREntryDisplay: React.FC<any> = ({har, entry, isCollapsed: initialIsCollapsed, isResponseMocked}) => {
const {request, response} = JSON.parse(entry);
const rulesMatched = har.log.entries[0].rulesMatched
for (const [i, row] of data.entries()) {
switch (row.type) {
case "table":
sections.push(
<HAREntryTableSection title={row.title} arrayToIterate={JSON.parse(row.data)}/>
)
break;
case "body":
sections.push(
<HAREntryBodySection content={row.data} encoding={row.encoding} contentType={row.mime_type}/>
)
break;
default:
break;
}
}
return <>{sections}</>;
}
const AutoRepresentation: React.FC<any> = ({representation, isResponseMocked}) => {
console.log(representation)
const {request, response} = JSON.parse(representation);
const rulesMatched = []
const TABS = [
{tab: 'request'},
{
@ -22,50 +45,33 @@ const HAREntryDisplay: React.FC<any> = ({har, entry, isCollapsed: initialIsColla
const [currentTab, setCurrentTab] = useState(TABS[0].tab);
return <div className={styles.harEntry}>
{!initialIsCollapsed && <div className={styles.body}>
{<div className={styles.body}>
<div className={styles.bodyHeader}>
<Tabs tabs={TABS} currentTab={currentTab} onChange={setCurrentTab} leftAligned/>
{request?.url && <a className={styles.endpointURL} href={request.payload.url} target='_blank' rel="noreferrer">{request.payload.url}</a>}
</div>
{
currentTab === TABS[0].tab && <React.Fragment>
<HAREntryTableSection title={'Headers'} arrayToIterate={request.payload.headers}/>
<HAREntryTableSection title={'Cookies'} arrayToIterate={request.payload.cookies}/>
{request.payload?.postData && <HAREntryBodySection content={request.payload.postData} encoding={request.payload.postData.comment} contentType={request.payload.postData[MIME_TYPE_KEY]}/>}
<HAREntryTableSection title={'Query'} arrayToIterate={request.payload.queryString}/>
</React.Fragment>
}
{currentTab === TABS[0].tab && <React.Fragment>
<SectionsRepresentation data={request}/>
</React.Fragment>}
{currentTab === TABS[1].tab && <React.Fragment>
<HAREntryTableSection title={'Headers'} arrayToIterate={response.payload.headers}/>
<HAREntryBodySection content={response.payload.content} encoding={response.payload.content?.encoding} contentType={response.payload.content?.mimeType}/>
<HAREntryTableSection title={'Cookies'} arrayToIterate={response.payload.cookies}/>
<SectionsRepresentation data={response}/>
</React.Fragment>}
{currentTab === TABS[2].tab && <React.Fragment>
<HAREntryTablePolicySection service={har.log.entries[0].service} title={'Rule'} latency={0} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>
{// FIXME: Fix here
<HAREntryTablePolicySection service={representation.log.entries[0].service} title={'Rule'} latency={0} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>}
</React.Fragment>}
</div>}
</div>;
}
interface Props {
harObject: any;
className?: string;
representation: any;
isResponseMocked?: boolean;
showTitle?: boolean;
}
const HAREntryViewer: React.FC<Props> = ({harObject, className, isResponseMocked, showTitle=true}) => {
const {log: {entries}} = harObject;
const isCollapsed = entries.length > 1;
return <div className={`${className ? className : ''}`}>
{Object.keys(entries).map((entry: any, index) => <HAREntryDisplay har={harObject} isCollapsed={isCollapsed} key={index} entry={entries[entry].entry} isResponseMocked={isResponseMocked} showTitle={showTitle}/>)}
</div>
const HAREntryViewer: React.FC<Props> = ({representation, isResponseMocked, showTitle=true}) => {
return <AutoRepresentation representation={representation} isResponseMocked={isResponseMocked} showTitle={showTitle}/>
};
export default HAREntryViewer;