#!/usr/bin/env python3
"""
Parses a Chrome --log-net-log capture and reports ORB outcomes for the
two cross-origin victim resources:
  /single.json  -> 1 x X-Content-Type-Options: nosniff   (control: ORB blocks)
  /double.json  -> 2 x X-Content-Type-Options: nosniff   (bug:     ORB allows)

Proof of the bug:
  - single.json URL_REQUEST reads 0 body bytes (ORB Init() returned kBlock,
    URLLoader::BlockResponseForOrb() called url_request_->AbortAndCloseConnection()).
  - double.json URL_REQUEST reads the full body and the secret JSON appears in
    URL_REQUEST_JOB_FILTERED_BYTES_READ (ORB sniffed, fell through to kAllow,
    body forwarded to renderer).
"""
import base64
import json
import sys

path = sys.argv[1] if len(sys.argv) > 1 else '/tmp/netlog.json'
with open(path) as f:
    data = f.read()
# NetLog may be unterminated if Chrome was killed.
if not data.rstrip().endswith('}'):
    data = data.rstrip().rstrip(',') + ']}'
nl = json.loads(data)

etypes = {v: k for k, v in nl['constants']['logEventTypes'].items()}
src_types = {v: k for k, v in nl['constants']['logSourceType'].items()}

URL_REQUEST = nl['constants']['logSourceType']['URL_REQUEST']

# Find URL_REQUEST sources for the victim URLs.
targets = {}  # sid -> url
for ev in nl['events']:
    if ev['source']['type'] != URL_REQUEST:
        continue
    p = ev.get('params') or {}
    u = p.get('url')
    if u and ('/single.json' in u or '/double.json' in u):
        targets.setdefault(ev['source']['id'], u)

ok = True
for sid, url in sorted(targets.items()):
    body_bytes = 0
    body_chunks = []
    aborted = False
    headers = None
    for ev in nl['events']:
        if ev['source']['id'] != sid:
            continue
        et = etypes.get(ev['type'], str(ev['type']))
        p = ev.get('params') or {}
        if et in ('URL_REQUEST_JOB_BYTES_READ',
                  'URL_REQUEST_JOB_FILTERED_BYTES_READ') and 'byte_count' in p:
            body_bytes += p['byte_count']
            if 'bytes' in p:
                try:
                    body_chunks.append(base64.b64decode(p['bytes']))
                except Exception:
                    pass
        if et == 'CANCELLED' or (p.get('net_error') == -3):  # ERR_ABORTED
            aborted = True
        if et == 'HTTP_TRANSACTION_READ_RESPONSE_HEADERS' and 'headers' in p:
            headers = p['headers']
    body = b''.join(body_chunks)
    name = 'single.json' if 'single' in url else 'double.json'
    expect_blocked = 'single' in url

    print('=' * 70)
    print(f'{name}  ({url})  [URL_REQUEST source {sid}]')
    if headers:
        xcto = [h for h in headers if h.lower().startswith('x-content-type-options')]
        print(f'  Response X-Content-Type-Options headers ({len(xcto)}):')
        for h in xcto:
            print(f'    {h}')
    print(f'  Body bytes read by URL_REQUEST: {body_bytes}')
    if body:
        print(f'  Body content: {body.decode(errors="replace")!r}')
    if expect_blocked:
        verdict = 'ORB BLOCKED (correct)' if body_bytes == 0 else 'ORB DID NOT BLOCK?'
        ok = ok and body_bytes == 0
    else:
        verdict = ('ORB ALLOWED -> body leaked to renderer (BUG TRIGGERED)'
                   if body_bytes > 0 else 'ORB blocked (bug not reproduced)')
        ok = ok and body_bytes > 0
    print(f'  -> {verdict}')

print('=' * 70)
print('RESULT:', 'BUG REPRODUCED' if ok and targets else 'NOT REPRODUCED')
sys.exit(0 if (ok and targets) else 1)
