aboutsummaryrefslogtreecommitdiffstats
path: root/examples/complex/har_dump.py
blob: 21bcc3412d366a873b398efb8a6b11acde0675e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""
This inline script can be used to dump flows as HAR files.
"""


import json
import base64
import zlib
import os

from datetime import datetime
from datetime import timezone

import mitmproxy

from mitmproxy import version
from mitmproxy import ctx
from mitmproxy.utils import strutils
from mitmproxy.net.http import cookies

HAR = {}

# A list of server seen till now is maintained so we can avoid
# using 'connect' time for entries that use an existing connection.
SERVERS_SEEN = set()


def load(l):
    l.add_option(
        "hardump", str, "", "HAR dump path.",
    )


def configure(updated):
    HAR.update({
        "log": {
            "version": "1.2",
            "creator": {
                "name": "mitmproxy har_dump",
                "version": "0.1",
                "comment": "mitmproxy version %s" % version.MITMPROXY
            },
            "entries": []
        }
    })


def response(flow):
    """
       Called when a server response has been received.
    """

    # -1 indicates that these values do not apply to current request
    ssl_time = -1
    connect_time = -1

    if flow.server_conn and flow.server_conn not in SERVERS_SEEN:
        connect_time = (flow.server_conn.timestamp_tcp_setup -
                        flow.server_conn.timestamp_start)

        if flow.server_conn.timestamp_ssl_setup is not None:
            ssl_time = (flow.server_conn.timestamp_ssl_setup -
                        flow.server_conn.timestamp_tcp_setup)

        SERVERS_SEEN.add(flow.server_conn)

    # Calculate raw timings from timestamps. DNS timings can not be calculated
    # for lack of a way to measure it. The same goes for HAR blocked.
    # mitmproxy will open a server connection as soon as it receives the host
    # and port from the client connection. So, the time spent waiting is actually
    # spent waiting between request.timestamp_end and response.timestamp_start
    # thus it correlates to HAR wait instead.
    timings_raw = {
        'send': flow.request.timestamp_end - flow.request.timestamp_start,
        'receive': flow.response.timestamp_end - flow.response.timestamp_start,
        'wait': flow.response.timestamp_start - flow.request.timestamp_end,
        'connect': connect_time,
        'ssl': ssl_time,
    }

    # HAR timings are integers in ms, so we re-encode the raw timings to that format.
    timings = dict([(k, int(1000 * v)) for k, v in timings_raw.items()])

    # full_time is the sum of all timings.
    # Timings set to -1 will be ignored as per spec.
    full_time = sum(v for v in timings.values() if v > -1)

    started_date_time = datetime.fromtimestamp(flow.request.timestamp_start, timezone.utc).isoformat()

    # Response body size and encoding
    response_body_size = len(flow.response.raw_content)
    response_body_decoded_size = len(flow.response.content)
    response_body_compression = response_body_decoded_size - response_body_size

    entry = {
        "startedDateTime": started_date_time,
        "time": full_time,
        "request": {
            "method": flow.request.method,
            "url": flow.request.url,
            "httpVersion": flow.request.http_version,
            "cookies": format_request_cookies(flow.request.cookies.fields),
            "headers": name_value(flow.request.headers),
            "queryString": name_value(flow.request.query or {}),
            "headersSize": len(str(flow.request.headers)),
            "bodySize": len(flow.request.content),
        },
        "response": {
            "status": flow.response.status_code,
            "statusText": flow.response.reason,
            "httpVersion": flow.response.http_version,
            "cookies": format_response_cookies(flow.response.cookies.fields),
            "headers": name_value(flow.response.headers),
            "content": {
                "size": response_body_size,
                "compression": response_body_compression,
                "mimeType": flow.response.headers.get('Content-Type', '')
            },
            "redirectURL": flow.response.headers.get('Location', ''),
            "headersSize": len(str(flow.response.headers)),
            "bodySize": response_body_size,
        },
        "cache": {},
        "timings": timings,
    }

    # Store binary data as base64
    if strutils.is_mostly_bin(flow.response.content):
        entry["response"]["content"]["text"] = base64.b64encode(flow.response.content).decode()
        entry["response"]["content"]["encoding"] = "base64"
    else:
        entry["response"]["content"]["text"] = flow.response.get_text(strict=False)

    if flow.request.method in ["POST", "PUT", "PATCH"]:
        params = [
            {"name": a, "value": b}
            for a, b in flow.request.urlencoded_form.items(multi=True)
        ]
        entry["request"]["postData"] = {
            "mimeType": flow.request.headers.get("Content-Type", ""),
            "text": flow.request.get_text(strict=False),
            "params": params
        }

    if flow.server_conn.connected():
        entry["serverIPAddress"] = str(flow.server_conn.ip_address[0])

    HAR["log"]["entries"].append(entry)


def done():
    """
        Called once on script shutdown, after any other events.
    """
    if ctx.options.hardump:
        json_dump = json.dumps(HAR, indent=2)  # type: str

        if ctx.options.hardump == '-':
            mitmproxy.ctx.log(json_dump)
        else:
            raw = json_dump.encode()  # type: bytes
            if ctx.options.hardump.endswith('.zhar'):
                raw = zlib.compress(raw, 9)

            with open(os.path.expanduser(ctx.options.hardump), "wb") as f:
                f.write(raw)

            mitmproxy.ctx.log("HAR dump finished (wrote %s bytes to file)" % len(json_dump))


def format_cookies(cookie_list):
    rv = []

    for name, value, attrs in cookie_list:
        cookie_har = {
            "name": name,
            "value": value,
        }

        # HAR only needs some attributes
        for key in ["path", "domain", "comment"]:
            if key in attrs:
                cookie_har[key] = attrs[key]

        # These keys need to be boolean!
        for key in ["httpOnly", "secure"]:
            cookie_har[key] = bool(key in attrs)

        # Expiration time needs to be formatted
        expire_ts = cookies.get_expiration_ts(attrs)
        if expire_ts is not None:
            cookie_har["expires"] = datetime.fromtimestamp(expire_ts, timezone.utc).isoformat()

        rv.append(cookie_har)

    return rv


def format_request_cookies(fields):
    return format_cookies(cookies.group_cookies(fields))


def format_response_cookies(fields):
    return format_cookies((c[0], c[1][0], c[1][1]) for c in fields)


def name_value(obj):
    """
        Convert (key, value) pairs to HAR format.
    """
    return [{"name": k, "value": v} for k, v in obj.items()]
(Blk : Ghdl_Rtin_Block_Acc; Ctxt : Rti_Context; Pfx : String) is Child : Ghdl_Rti_Access; Child2 : Ghdl_Rti_Access; Index : Ghdl_Index_Type; procedure Disp_Header (Nctxt : Rti_Context; Force_Cont : Boolean := False) is begin Put (Pfx); if Blk.Common.Kind /= Ghdl_Rtik_Entity and Child2 = null and Force_Cont = False then Put ("`-"); else Put ("+-"); end if; Disp_Tree_Child (Child, Nctxt); New_Line; end Disp_Header; procedure Disp_Sub_Block (Sub_Blk : Ghdl_Rtin_Block_Acc; Nctxt : Rti_Context) is Npfx : String (1 .. Pfx'Length + 2); begin Npfx (1 .. Pfx'Length) := Pfx; Npfx (Pfx'Length + 2) := ' '; if Child2 = null then Npfx (Pfx'Length + 1) := ' '; else Npfx (Pfx'Length + 1) := '|'; end if; Disp_Tree_Block (Sub_Blk, Nctxt, Npfx); end Disp_Sub_Block; begin Index := 0; Get_Tree_Child (Blk, Index, Child); while Child /= null loop Get_Tree_Child (Blk, Index, Child2); case Child.Kind is when Ghdl_Rtik_Process | Ghdl_Rtik_Block => declare Nblk : constant Ghdl_Rtin_Block_Acc := To_Ghdl_Rtin_Block_Acc (Child); Nctxt : Rti_Context; begin Nctxt := (Base => Ctxt.Base + Nblk.Loc, Block => Child); Disp_Header (Nctxt, False); Disp_Sub_Block (Nblk, Nctxt); end; when Ghdl_Rtik_For_Generate => declare Gen : constant Ghdl_Rtin_Generate_Acc := To_Ghdl_Rtin_Generate_Acc (Child); Nctxt : Rti_Context; Length : Ghdl_Index_Type; Old_Child2 : Ghdl_Rti_Access; begin Nctxt := (Base => To_Addr_Acc (Ctxt.Base + Gen.Loc).all, Block => Gen.Child); Length := Get_For_Generate_Length (Gen, Ctxt); Disp_Header (Nctxt, Length > 1); Old_Child2 := Child2; if Length > 1 then Child2 := Child; end if; for I in 1 .. Length loop Disp_Sub_Block (To_Ghdl_Rtin_Block_Acc (Gen.Child), Nctxt); if I /= Length then Nctxt.Base := Nctxt.Base + Gen.Size; if I = Length - 1 then Child2 := Old_Child2; end if; Disp_Header (Nctxt); end if; end loop; Child2 := Old_Child2; end; when Ghdl_Rtik_If_Generate | Ghdl_Rtik_Case_Generate => declare Nctxt : constant Rti_Context := Get_If_Case_Generate_Child (Ctxt, Child); begin Disp_Header (Nctxt); if Nctxt.Base /= Null_Address then Disp_Sub_Block (To_Ghdl_Rtin_Block_Acc (Nctxt.Block), Nctxt); end if; end; when Ghdl_Rtik_Instance => declare Inst : Ghdl_Rtin_Instance_Acc; Sub_Ctxt : Rti_Context; Sub_Blk : Ghdl_Rtin_Block_Acc; Npfx : String (1 .. Pfx'Length + 4); Comp : Ghdl_Rtin_Component_Acc; Ch : Ghdl_Rti_Access; begin Disp_Header (Ctxt); Inst := To_Ghdl_Rtin_Instance_Acc (Child); Get_Instance_Context (Inst, Ctxt, Sub_Ctxt); Sub_Blk := To_Ghdl_Rtin_Block_Acc (Sub_Ctxt.Block); if Inst.Instance.Kind = Ghdl_Rtik_Component and then Disp_Tree_Flag >= Disp_Tree_Port then -- Disp generics and ports of the component. Comp := To_Ghdl_Rtin_Component_Acc (Inst.Instance); for I in 1 .. Comp.Nbr_Child loop Ch := Comp.Children (I - 1); if Ch.Kind = Ghdl_Rtik_Port then -- Disp only port (and not generics). Put (Pfx); if Child2 = null then Put (" "); else Put ("| "); end if; if I = Comp.Nbr_Child and then Sub_Blk = null then Put ("`-"); else Put ("+-"); end if; Disp_Tree_Child (Ch, Sub_Ctxt); New_Line; end if; end loop; end if; if Sub_Blk /= null then Npfx (1 .. Pfx'Length) := Pfx; if Child2 = null then Npfx (Pfx'Length + 1) := ' '; else Npfx (Pfx'Length + 1) := '|'; end if; Npfx (Pfx'Length + 2) := ' '; Npfx (Pfx'Length + 3) := '`'; Npfx (Pfx'Length + 4) := '-'; Put (Npfx); Disp_Tree_Child (Sub_Blk.Parent, Sub_Ctxt); New_Line; Npfx (Pfx'Length + 3) := ' '; Npfx (Pfx'Length + 4) := ' '; Disp_Tree_Block (Sub_Blk, Sub_Ctxt, Npfx); end if; end; when others => Disp_Header (Ctxt); end case; Child := Child2; end loop; end Disp_Tree_Block1; procedure Disp_Tree_Block (Blk : Ghdl_Rtin_Block_Acc; Ctxt : Rti_Context; Pfx : String) is begin case Blk.Common.Kind is when Ghdl_Rtik_Architecture => declare Npfx : String (1 .. Pfx'Length + 2); Nctxt : Rti_Context; begin -- The entity. Nctxt := (Base => Ctxt.Base, Block => Blk.Parent); Disp_Tree_Block1 (To_Ghdl_Rtin_Block_Acc (Blk.Parent), Nctxt, Pfx); -- Then the architecture. Put (Pfx); Put ("`-"); Disp_Tree_Child (To_Ghdl_Rti_Access (Blk), Ctxt); New_Line; Npfx (1 .. Pfx'Length) := Pfx; Npfx (Pfx'Length + 1) := ' '; Npfx (Pfx'Length + 2) := ' '; Disp_Tree_Block1 (Blk, Ctxt, Npfx); end; when Ghdl_Rtik_Package_Body => Disp_Tree_Block1 (To_Ghdl_Rtin_Block_Acc (Blk.Parent), Ctxt, Pfx); when others => Disp_Tree_Block1 (Blk, Ctxt, Pfx); end case; end Disp_Tree_Block; procedure Disp_Hierarchy is Ctxt : Rti_Context; Parent : Ghdl_Rtin_Block_Acc; Child : Ghdl_Rti_Access; begin if Disp_Tree_Flag = Disp_Tree_None then return; end if; Ctxt := Get_Top_Context; Parent := To_Ghdl_Rtin_Block_Acc (Ctxt.Block); Disp_Tree_Child (Parent.Parent, Ctxt); New_Line; Disp_Tree_Block (Parent, Ctxt, ""); for I in 1 .. Ghdl_Rti_Top.Nbr_Child loop Child := Ghdl_Rti_Top.Children (I - 1); Ctxt := (Base => Null_Address, Block => Child); Disp_Tree_Child (Child, Ctxt); New_Line; Disp_Tree_Block (To_Ghdl_Rtin_Block_Acc (Child), Ctxt, ""); end loop; end Disp_Hierarchy; function Disp_Tree_Option (Option : String) return Boolean is Opt : constant String (1 .. Option'Length) := Option; begin if Opt'Length >= 11 and then Opt (1 .. 11) = "--disp-tree" then if Opt'Length = 11 then Disp_Tree_Flag := Disp_Tree_Port; elsif Opt (12 .. Opt'Last) = "=port" then Disp_Tree_Flag := Disp_Tree_Port; elsif Opt (12 .. Opt'Last) = "=proc" then Disp_Tree_Flag := Disp_Tree_Proc; elsif Opt (12 .. Opt'Last) = "=inst" then Disp_Tree_Flag := Disp_Tree_Inst; elsif Opt (12 .. Opt'Last) = "=none" then Disp_Tree_Flag := Disp_Tree_None; else Error ("bad argument for --disp-tree option, try --help"); end if; return True; else return False; end if; end Disp_Tree_Option; procedure Disp_Tree_Help is procedure P (Str : String) renames Put_Line; begin P (" --disp-tree[=KIND] disp the design hierarchy after elaboration"); P (" KIND is inst, proc, port (default)"); end Disp_Tree_Help; Disp_Tree_Hooks : aliased constant Hooks_Type := (Desc => new String' ("disp-tree: display design hierarchy (--disp-tree)"), Option => Disp_Tree_Option'Access, Help => Disp_Tree_Help'Access, Init => null, Start => Disp_Hierarchy'Access, Finish => null); procedure Register is begin Register_Hooks (Disp_Tree_Hooks'Access); end Register; end Grt.Disp_Tree;