Files
Viper_VRTE/requests/https.vp
T
TermiNexus 0055ab7ea7 refactor: rawptr[T] -> T* 与 t.CConst 前缀迁移(R14/R16)
R14:rawptr[X] -> X*(41 文件 / 2037 行改动),X 含模块限定名与嵌套下标(rawptr[GSList[NameEntry]] -> GSList[NameEntry]*)。保留 6 处 rawptr[t.CPtr](规范例外:存纯裸指针);# 注释与字符串里的 rawptr[...] 属元语法描述,未动。 R16(保守子集):t.CConst | str/bytes -> const str/bytes,t.CConst | char | t.CPtr -> const str;typedef 右值位置豁免(const str 在该处无法注册,与 R1 同源)。 验证:TPC 自举三重重编 SUCCESS;TransPyV.exe 全量 77/77 顺序回归通过。
2026-10-05 17:38:21 +08:00

1045 lines
35 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import string
import socket
import stdio
import stdlib
import requests
import openssl
import json
import memhub
# ============================================================
# requests/https.vp — HTTPS 客户端(openssl SSL/TLS + 证书核验)
#
# 与 requests(HTTP)同名 API,但走 TLS 并默认校验对端证书:
# import requests.https as rhttps
# resp = rhttps.get("https://example.com/", verify=True)
# if resp is not None:
# print(resp.status_code)
# print(resp.text)
# requests.release(resp)
#
# 说明:
# - verify=True 时校验证书链 + 主机名(hostname 校验 SSL_set1_host),
# 提供 ca_file 指向 PEM CA bundle;不提供则走系统默认路径
# (Windows 上通常为空,真实站点会因"无法验证证书链"失败返回 None)。
# - 失败时返回 None,可用 last_error() 获取原因文本。
# - 握手成功后可用 last_tls_info() 查看 TLS 版本 / 加密套件 / 对端证书主题。
# ============================================================
define HTTPS_PORT = 443
# Module-internal state
_ossl_ready: bool = False
_socket_ready: bool = False
_err_store: t.CArray[char, 512] # last failure reason (static buffer)
_info_store: t.CArray[char, 512] # TLS metadata of last successful handshake
_scratch_store: t.CArray[char, 512] # scratch concat buffer
_last_verify_val: INT = -1 # last SSL_get_verify_result (0=ok; -1=disabled)
_last_verify_flag: INT = -1 # last request verify flag (1=on; 0=off; -1=unknown)
# ============================================================
# SSE streaming receive area (post_stream only; single request, polled by UI thread).
# _st_len written bytes; _st_consumed consumed cursor; _st_state 0=running 1=done(2xx) 2=fail; _st_status HTTP code.
# ============================================================
define STREAM_CAP = 262144
_st_buf: t.CArray[char, 262144]
_st_len: sizet = 0
_st_consumed: sizet = 0
_st_state: INT = 0
_st_status: INT = 0
_st_err: t.CArray[char, 512]
def stream_begin():
"""Start a new streaming receive: reset buffer/cursor/state."""
global _st_len, _st_consumed, _st_state, _st_status
_st_len = 0
_st_consumed = 0
_st_state = 0
_st_status = 0
_st_err[0] = 0
def stream_pull(out: str, cap: INT) -> INT:
"""Copy unconsumed delta into out (NUL-terminated); return bytes copied."""
global _st_consumed
if out is None or cap <= 1:
return 0
avail: INT = 0
if _st_len > _st_consumed:
avail = INT(_st_len - _st_consumed)
if avail > cap - 1:
avail = cap - 1
for i in range(sizet(avail)):
out[i] = _st_buf[_st_consumed + i]
out[sizet(avail)] = 0
_st_consumed += sizet(avail)
return avail
def stream_state() -> INT:
"""0=running 1=done(2xx) 2=failed."""
return _st_state
def stream_status() -> INT:
"""HTTP status code; negative means network/parse error."""
return _st_status
def stream_error_text() -> str:
"""Failure reason text (when _st_state==2)."""
return _st_err
def last_verify_result() -> int:
"""Last cert verify result: 0=ok; >0=failure code; -1=verify disabled."""
return _last_verify_val
def last_verify_enabled() -> int:
"""Whether last request enabled verify (1=yes; 0=no; -1=unknown)."""
return _last_verify_flag
# ============================================================
# Internal helpers
# ============================================================
def last_error() -> str:
"""Last failure reason text (empty string if none)."""
return _err_store
def last_tls_info() -> str:
"""Last successful handshake TLS info (version/cipher/peer subject), empty if none."""
return _info_store
def _RecordErrorText(text: str):
"""Write failure reason into static buffer _err_store."""
if text is None: return
_err_store[0] = 0
stdio.snprintf(_err_store, 512, "%s", text)
def _RecordInfoText(text: str):
"""Write TLS metadata into static buffer _info_store."""
if text is None: return
_info_store[0] = 0
stdio.snprintf(_info_store, 512, "%s", text)
def _EnsureReady() -> INT:
"""Initialize socket and openssl (both idempotent)."""
global _socket_ready, _ossl_ready
if not _socket_ready:
if socket.SocketInit() != 0:
_RecordErrorText("socket init failed")
return -1
_socket_ready = True
if not _ossl_ready:
if openssl.OPENSSL_init_ssl(0, None) != 1:
_RecordErrorText("OPENSSL_init_ssl failed")
return -1
_ossl_ready = True
return 0
def _ParseHttpsUrl(url: str, host_buf: str, host_size: INT, path_buf: str, path_size: INT, port: INT*) -> INT:
"""Parse https://host[:port][/path]; default port 443."""
if url is None: return -1
if host_buf is None or path_buf is None or port is None: return -1
p: str = url
# Skip "https://" (8 bytes)
if string.strncmp(p, "https://", 8) == 0:
p += 8
# Parse host
host_start: str = p
host_len: INT = 0
while p[0] != '\0' and p[0] != ':' and p[0] != '/':
host_len += 1
p += 1
if host_len == 0 or host_len >= host_size: return -1
string.strncpy(host_buf, host_start, host_len)
host_buf[host_len] = 0
# Parse port
if p[0] == ':':
p += 1
port_val: INT = 0
while p[0] >= '0' and p[0] <= '9':
port_val = port_val * 10 + INT(p[0] - '0')
p += 1
c.DerefAs(port, port_val)
else:
c.DerefAs(port, HTTPS_PORT)
# Parse path
if p[0] == '/':
path_start: str = p
path_len: INT = 0
while p[0] != '\0':
path_len += 1
p += 1
if path_len >= path_size: return -1
string.strncpy(path_buf, path_start, path_len)
path_buf[path_len] = 0
else:
if path_size < 2: return -1
path_buf[0] = '/'
path_buf[1] = 0
return 0
def _ScratchBuf() -> str:
"""Return static concat buffer (512B, reused)."""
return _scratch_store
def _TlsErrText():
"""Write last openssl error-queue text into static scratch buffer."""
e: uint32 = openssl.ERR_get_error()
if e == 0:
_scratch_store[0] = 0
string.strncpy(_scratch_store, "openssl error (no detail)", 26)
return
openssl.ERR_error_string_n(e, _scratch_store, 512)
# ============================================================
# Core request
# ============================================================
def _HttpsRequest(method: str, url: str, verify: bool, ca_file: str, data: str, content_type: str, auth: str = None) -> requests.Response*:
"""HTTPS GET/POST core. verify=False skips cert chain/hostname checks (not recommended);
verify=True uses ca_file PEM bundle (else system default); auth adds Authorization header on POST only.
"""
global _last_verify_val, _last_verify_flag
if requests._mbuddy is None: return None
_last_verify_flag = 1 if verify else 0
resp: requests.Response* = requests.create_response()
if resp is None: return None
if _EnsureReady() != 0:
requests.release(resp)
return None
# Temp buffer for error text; openssl details go to module scratch.
errmsg: str = requests._mbuddy.alloc(512)
if errmsg is None:
requests.release(resp)
return None
# ---- Parse URL ----
host_buf: t.CArray[char, 256]
path_buf: t.CArray[char, 1024]
port: INT = 0
if _ParseHttpsUrl(url, host_buf, 256, path_buf, 1024, &port) != 0:
stdio.snprintf(errmsg, 512, "bad https url: %s", url)
_RecordErrorText(errmsg)
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- Establish TCP connection ----
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
_RecordErrorText("socket() failed")
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
if sock.connect(host_buf, port) != 0:
stdio.snprintf(errmsg, 512, "connect %s:%d failed", host_buf, port)
_RecordErrorText(errmsg)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- SSL_CTX / SSL ----
ctx: openssl.SslCtxPtr = openssl.SSL_CTX_new(openssl.TLS_client_method())
if ctx is None:
_RecordErrorText("SSL_CTX_new failed")
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
if verify:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyPeer, None)
if ca_file is not None and ca_file[0] != 0:
if openssl.SSL_CTX_load_verify_locations(ctx, ca_file, None) != 1:
_TlsErrText()
stdio.snprintf(errmsg, 512, "load CA file failed: %s (%s)", ca_file, _ScratchBuf())
_RecordErrorText(errmsg)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
else:
# System default paths (usually empty on Windows; cert chain check will fail).
openssl.SSL_CTX_set_default_verify_paths(ctx)
else:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyNone, None)
ssl: openssl.SslPtr = openssl.SSL_new(ctx)
if ssl is None:
_RecordErrorText("SSL_new failed")
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
openssl.SSL_set_fd(ssl, sock.fd)
# SNI (SSL_set_tlsext_host_name is a macro -> call SSL_ctrl directly)
openssl.SSL_ctrl(ssl, openssl.SslCtrlSetTlsextHostname, openssl.TlsextNametypeHostName, host_buf)
# Hostname verification (TLS core compares cert SAN/CN against host)
if verify:
openssl.SSL_set1_host(ssl, host_buf)
# ---- Handshake ----
hret: INT = openssl.SSL_connect(ssl)
if hret <= 0:
want: INT = openssl.SSL_get_error(ssl, hret)
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_connect failed (want=%d): %s", want, _ScratchBuf())
_RecordErrorText(errmsg)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- Record last cert verify result (for last_verify_result) ----
if verify:
_last_verify_val = INT(openssl.SSL_get_verify_result(ssl))
else:
_last_verify_val = -1
# ---- Re-check cert chain verify result ----
if verify:
vres: int32 = openssl.SSL_get_verify_result(ssl)
if vres != 0:
reason: str = openssl.X509_verify_cert_error_string(vres)
if reason is None:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed: error %d", INT(vres))
else:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed (%d): %s", INT(vres), reason)
_RecordErrorText(errmsg)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- Record TLS metadata (version / cipher / peer subject) ----
name_buf: str = requests._mbuddy.alloc(512)
if name_buf is not None:
peer: openssl.X509Ptr = openssl.SSL_get1_peer_certificate(ssl)
if peer is not None:
subj: openssl.X509NamePtr = openssl.X509_get_subject_name(peer)
if subj is not None:
name_buf[0] = 0
openssl.X509_NAME_oneline(subj, name_buf, 512)
name_buf[511] = 0
openssl.X509_free(peer)
else:
name_buf[0] = 0
string.strncpy(name_buf, "(no peer certificate)", 21)
name_buf[21] = 0
ver: str = openssl.SSL_get_version(ssl)
cipher: str = openssl.SSL_CIPHER_get_name(openssl.SSL_get_current_cipher(ssl))
stdio.snprintf(errmsg, 512, "%s | cipher %s | peer: %s",
ver if ver is not None else "?", cipher if cipher is not None else "?", name_buf)
_RecordInfoText(errmsg)
requests._mbuddy.free(name_buf)
# ---- Build request headers and send ----
req_buf: str = requests._mbuddy.alloc(requests.HTTP_BUFSIZ)
if req_buf is None:
_RecordErrorText("alloc request buffer failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
body_len: INT = 0
is_post: bool = string.strcmp(method, "POST") == 0
if is_post:
body_len = INT(string.strlen(data)) if data is not None else 0
ct: str = content_type if content_type is not None else "application/x-www-form-urlencoded"
# Build header in stages so Authorization can be inserted on demand (only when auth non-empty).
hdr_off: INT = 0
hdr_cap: INT = requests.HTTP_BUFSIZ
stdio.snprintf(req_buf, hdr_cap, "POST %s HTTP/1.1\r\nHost: %s\r\n", path_buf, host_buf)
hdr_off = INT(string.strlen(req_buf))
if auth is not None and auth[0] != 0:
stdio.snprintf(req_buf + hdr_off, hdr_cap - hdr_off, "Authorization: %s\r\n", auth)
hdr_off = INT(string.strlen(req_buf))
stdio.snprintf(req_buf + hdr_off, hdr_cap - hdr_off,
"Content-Type: %s\r\nContent-Length: %d\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n",
ct, body_len)
else:
stdio.snprintf(req_buf, requests.HTTP_BUFSIZ,
"GET %s HTTP/1.1\r\nHost: %s\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n",
path_buf, host_buf)
hdr_len: INT = INT(string.strlen(req_buf))
sent: INT = openssl.SSL_write(ssl, req_buf, hdr_len)
if sent <= 0:
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_write header failed: %s", _ScratchBuf())
_RecordErrorText(errmsg)
requests._mbuddy.free(req_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
if is_post and body_len > 0 and data is not None:
openssl.SSL_write(ssl, data, body_len)
requests._mbuddy.free(req_buf)
# ---- Receive response (buffer auto-doubles, supports MB bodies) ----
resp_cap: INT = requests.HTTP_BUFSIZ
resp_buf: str = requests._mbuddy.alloc(sizet(resp_cap))
if resp_buf is None:
_RecordErrorText("alloc response buffer failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
total: INT = 0
while True:
room: INT = resp_cap - 1 - total
if room <= 0:
# Buffer full but more data may follow: double capacity (hard cap 256MB, runaway-guard).
if resp_cap >= 268435456:
break
resp_cap = resp_cap * 2
nb: t.CPtr = requests._mbuddy.realloc(resp_buf, sizet(resp_cap))
if nb is None:
_RecordErrorText("realloc response buffer failed")
requests._mbuddy.free(resp_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
resp_buf = char(uint64(nb), t.CPtr)
room = resp_cap - 1 - total
n: INT = openssl.SSL_read(ssl, resp_buf + total, room)
if n <= 0:
break
total += n
resp_buf[total] = 0
# Close TLS and TCP
openssl.SSL_shutdown(ssl)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
resp._raw_buf = resp_buf
requests.parse_response(resp, total)
requests._mbuddy.free(errmsg)
return resp
# ============================================================
# Public API
# ============================================================
def _HttpsRedirectLoop(method: str, url: str, verify: bool, ca_file: str, data: str, content_type: str, auth: str = None) -> requests.Response*:
"""HTTPS GET/POST with redirect following (cross http/https, max 5 hops).
auth is passed through to _HttpsRequest; note Authorization also follows cross-host redirects.
"""
if requests._mbuddy is None: return None
cur: str = requests._mbuddy.alloc(2048)
if cur is None: return None
if INT(string.strlen(url)) >= 2047:
_RecordErrorText("url too long")
requests._mbuddy.free(cur)
return None
string.strcpy(cur, url)
for hop in range(5):
resp: requests.Response* = _HttpsRequest(method, cur, verify, ca_file, data, content_type, auth)
if resp is None:
requests._mbuddy.free(cur)
return None
code: INT = resp.status_code
if not (code >= 300 and code < 400):
requests._mbuddy.free(cur)
return resp
loc: str = requests._mbuddy.alloc(2048)
if loc is None:
requests._mbuddy.free(cur)
return resp
if requests.header_value(resp, "Location", loc, 2048) != 0:
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
return resp
nxt: str = requests._mbuddy.alloc(2048)
if nxt is None:
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
return resp
if requests._ResolveRedirectUrl(cur, loc, nxt, 2048) != 0:
_RecordErrorText("unresolvable redirect Location")
requests.release(resp)
requests._mbuddy.free(nxt)
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
return None
requests.release(resp)
# Cross-scheme dispatch: https -> continue loop; http -> hand to requests (http) chain
if string.strncmp(nxt, "https://", 8) != 0:
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
http_resp: requests.Response* = requests.get(nxt)
requests._mbuddy.free(nxt)
return http_resp
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
cur = nxt
_RecordErrorText("too many redirects")
requests._mbuddy.free(cur)
return None
def get(url: str, verify: bool = True, ca_file: str = None) -> requests.Response*:
"""HTTPS GET (follows 3xx redirects, max 5 hops). verify checks cert chain + hostname;
ca_file is a PEM CA bundle path, None uses system default. Returns Response* or None.
"""
return _HttpsRedirectLoop("GET", url, verify, ca_file, None, None)
def post(url: str, data: str = None, content_type: str = "application/x-www-form-urlencoded",
verify: bool = True, ca_file: str = None, auth: str = None) -> requests.Response*:
"""HTTPS POST (follows 3xx redirects, max 5 hops). verify checks cert chain + hostname;
ca_file PEM CA bundle (None = system default); auth optional "Bearer <key>", sent when non-empty.
"""
return _HttpsRedirectLoop("POST", url, verify, ca_file, data, content_type, auth)
# ============================================================
# SSE streaming Chat Completions (post_stream): sends Accept: text/event-stream and extracts
# choices[0].delta.content from data:{json} lines into _st_buf; blocking, returns HTTP status or -1/-2.
# ============================================================
def _SseDeltaContent(root: json.JsonValue*) -> str:
"""Extract choices[0].delta.content from a stream data event; None if absent."""
if root is None or not root.is_object():
return None
cv: json.JsonValue* = root["choices"]
if cv is None or not cv.is_array():
return None
m0: json.JsonValue* = cv.get_item(0)
if m0 is None or not m0.is_object():
return None
dv: json.JsonValue* = m0["delta"]
if dv is None or not dv.is_object():
return None
content_v: json.JsonValue* = dv["content"]
if content_v is None or not content_v.is_string():
return None
return content_v.as_string()
def _StreamAppendContent(txt: str):
"""Append delta content to _st_buf (truncate on overflow)."""
global _st_len
if txt is None:
return
pos: sizet = _st_len
i: sizet = 0
while txt[i] != 0:
if pos >= sizet(STREAM_CAP) - 1:
break
_st_buf[pos] = txt[i]
pos += 1
i += 1
_st_len = pos
def _SetStreamFail(rc: INT):
"""Finish stream failure: copy reason from _err_store and set state."""
global _st_state, _st_status
_st_state = 2
_st_status = rc
stdio.snprintf(_st_err, 512, "%s", _err_store)
def post_stream(url: str, data: str, content_type: str = "application/json",
verify: bool = True, ca_file: str = None, auth: str = None) -> INT:
"""SSE streaming POST (blocking; call from a background thread). data must be a JSON body
containing "stream":true. Returns HTTP status; negative means network/parse failure.
"""
global _st_state, _st_status
if requests._mbuddy is None:
return -1
_st_state = 0
_st_status = 0
# Winsock/openssl init (idempotent; first call may run on a background thread)
if _EnsureReady() != 0:
_SetStreamFail(-1)
return -1
# ---- Connect/TLS (same as _HttpsRequest) ----
errmsg: str = requests._mbuddy.alloc(512)
if errmsg is None:
return -1
host_buf: t.CArray[char, 256]
path_buf: t.CArray[char, 1024]
port: INT = 0
if _ParseHttpsUrl(url, host_buf, 256, path_buf, 1024, &port) != 0:
stdio.snprintf(errmsg, 512, "bad https url: %s", url)
_RecordErrorText(errmsg)
_SetStreamFail(-1)
requests._mbuddy.free(errmsg)
return -1
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
_RecordErrorText("socket() failed")
_SetStreamFail(-1)
requests._mbuddy.free(errmsg)
return -1
if sock.connect(host_buf, port) != 0:
stdio.snprintf(errmsg, 512, "connect %s:%d failed", host_buf, port)
_RecordErrorText(errmsg)
_SetStreamFail(-1)
sock.close()
requests._mbuddy.free(errmsg)
return -1
ctx: openssl.SslCtxPtr = openssl.SSL_CTX_new(openssl.TLS_client_method())
if ctx is None:
_RecordErrorText("SSL_CTX_new failed")
_SetStreamFail(-1)
sock.close()
requests._mbuddy.free(errmsg)
return -1
if verify:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyPeer, None)
if ca_file is not None and ca_file[0] != 0:
if openssl.SSL_CTX_load_verify_locations(ctx, ca_file, None) != 1:
_TlsErrText()
stdio.snprintf(errmsg, 512, "load CA file failed: %s (%s)", ca_file, _ScratchBuf())
_RecordErrorText(errmsg)
_SetStreamFail(-1)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
else:
openssl.SSL_CTX_set_default_verify_paths(ctx)
else:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyNone, None)
ssl: openssl.SslPtr = openssl.SSL_new(ctx)
if ssl is None:
_RecordErrorText("SSL_new failed")
_SetStreamFail(-1)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
openssl.SSL_set_fd(ssl, sock.fd)
openssl.SSL_ctrl(ssl, openssl.SslCtrlSetTlsextHostname, openssl.TlsextNametypeHostName, host_buf)
if verify:
openssl.SSL_set1_host(ssl, host_buf)
hret: INT = openssl.SSL_connect(ssl)
if hret <= 0:
want: INT = openssl.SSL_get_error(ssl, hret)
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_connect failed (want=%d): %s", want, _ScratchBuf())
_RecordErrorText(errmsg)
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
if verify:
vres: int32 = openssl.SSL_get_verify_result(ssl)
if vres != 0:
reason: str = openssl.X509_verify_cert_error_string(vres)
if reason is None:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed: error %d", INT(vres))
else:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed (%d): %s", INT(vres), reason)
_RecordErrorText(errmsg)
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
# ---- Build request headers (POST + Accept: text/event-stream) and send ----
req_buf: str = requests._mbuddy.alloc(requests.HTTP_BUFSIZ)
if req_buf is None:
_RecordErrorText("alloc request buffer failed")
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
body_len: INT = INT(string.strlen(data)) if data is not None else 0
ct: str = content_type if content_type is not None else "application/json"
hdr_off: INT = 0
stdio.snprintf(req_buf, requests.HTTP_BUFSIZ, "POST %s HTTP/1.1\r\nHost: %s\r\n", path_buf, host_buf)
hdr_off = INT(string.strlen(req_buf))
if auth is not None and auth[0] != 0:
stdio.snprintf(req_buf + hdr_off, requests.HTTP_BUFSIZ - hdr_off, "Authorization: %s\r\n", auth)
hdr_off = INT(string.strlen(req_buf))
stdio.snprintf(req_buf + hdr_off, requests.HTTP_BUFSIZ - hdr_off,
"Content-Type: %s\r\nContent-Length: %d\r\nAccept: text/event-stream\r\nConnection: close\r\n\r\n",
ct, body_len)
hdr_len: INT = INT(string.strlen(req_buf))
sent: INT = openssl.SSL_write(ssl, req_buf, hdr_len)
if sent <= 0:
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_write header failed: %s", _ScratchBuf())
_RecordErrorText(errmsg)
_SetStreamFail(-1)
requests._mbuddy.free(req_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
if body_len > 0 and data is not None:
openssl.SSL_write(ssl, data, body_len)
requests._mbuddy.free(req_buf)
# ---- Read response: skip headers -> parse SSE lines -> extract delta.content ----
rd: str = requests._mbuddy.alloc(16384)
if rd is None:
_RecordErrorText("alloc stream buffer failed")
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
# JSON pool (reset per event, sized for one data event)
pmem: str = stdlib.malloc(524288)
if pmem is None:
_RecordErrorText("alloc json pool failed")
_SetStreamFail(-1)
requests._mbuddy.free(rd)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
jpool: memhub.MemPool* = memhub.MemPool(pmem, 524288)
leftover: t.CArray[char, 16384]
llen: INT = 0
header_done: INT = 0
status_code: INT = 0
had_read: INT = 0
while True:
n: INT = openssl.SSL_read(ssl, rd, 16384)
if n <= 0:
break
had_read = 1
# 1) Append to leftover
ai: INT = 0
while ai < n and llen < 16383:
leftover[llen] = rd[ai]
llen += 1
ai += 1
# 2) Process line by line
while True:
# Find line end \n
nl: INT = -1
for k in range(llen):
if leftover[k] == 10:
nl = k
break
if nl < 0:
break
e: INT = nl
if e > 0 and leftover[e - 1] == 13:
e -= 1
leftover[e] = 0
line: str = leftover
if header_done == 0:
if string.strncmp(line, "HTTP/", 5) == 0:
# Status code: HTTP/1.1 200 OK
sp: INT = 0
while line[sp] != 0 and line[sp] != ' ':
sp += 1
if line[sp] == ' ':
sp += 1
code_val: INT = 0
while line[sp] >= '0' and line[sp] <= '9':
code_val = code_val * 10 + INT(line[sp] - '0')
sp += 1
status_code = code_val
if e == 0:
header_done = 1
else:
if e > 0 and line[0] != ':':
if string.strncmp(line, "data:", 5) == 0:
pl: str = line + 5
while pl[0] == ' ':
pl += 1
if pl[0] != 0:
if string.strncmp(pl, "[DONE]", 6) != 0 or pl[6] != 0:
jpool.reset()
ev: json.JsonValue* = json.parse(jpool, pl)
dtx: str = _SseDeltaContent(ev)
if dtx is not None:
_StreamAppendContent(dtx)
# 3) Remove processed line
rem: INT = llen - (nl + 1)
for j2 in range(rem):
leftover[j2] = leftover[nl + 1 + j2]
llen = rem
# ---- Wrap up ----
openssl.SSL_shutdown(ssl)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(rd)
stdlib.free(pmem)
requests._mbuddy.free(errmsg)
if status_code == 0:
if had_read == 0:
_RecordErrorText("no response data")
else:
_RecordErrorText("missing HTTP status line")
_SetStreamFail(-2)
return -2
_st_status = status_code
if status_code >= 200 and status_code < 300:
_st_state = 1
else:
_st_state = 2
stdio.snprintf(_st_err, 512, "HTTP %d(请检查 Key/模型/地址或账号额度)", status_code)
return status_code
# ============================================================
# Large-file Range download primitive for requests.download. Thread-safe: no _mbuddy,
# independent SSL_CTX/SSL/socket per connection. Returns body bytes>=0; -1 arg; -2 redirect; -3 failure.
# ============================================================
def _DlOnce(url: str, start: sizet, count: INT, verify: bool, ca_file: str,
dst: str, dst_cap: INT, out_status: INT*,
out_loc: str, out_loccap: INT,
out_total: sizet*) -> INT:
"""HTTPS single Range fetch (thread-safe; see module comment)."""
global _last_verify_val, _last_verify_flag
if url is None or dst is None or dst_cap <= 0 \
or out_status is None or out_total is None:
return -1
*out_status = 0
*out_total = 0
if out_loc is not None and out_loccap > 0:
out_loc[0] = 0
_last_verify_flag = 1 if verify else 0
if _EnsureReady() != 0:
return -3
host_buf: t.CArray[char, 256]
path_buf: t.CArray[char, 1024]
port: INT = 0
if _ParseHttpsUrl(url, host_buf, 256, path_buf, 1024, &port) != 0:
_RecordErrorText("bad https url")
return -3
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
_RecordErrorText("socket() failed")
return -3
if sock.connect(host_buf, port) != 0:
_RecordErrorText("connect failed")
sock.close()
return -3
ctx: openssl.SslCtxPtr = openssl.SSL_CTX_new(openssl.TLS_client_method())
if ctx is None:
_RecordErrorText("SSL_CTX_new failed")
sock.close()
return -3
if verify:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyPeer, None)
if ca_file is not None and ca_file[0] != 0:
if openssl.SSL_CTX_load_verify_locations(ctx, ca_file, None) != 1:
_RecordErrorText("load CA file failed")
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
else:
openssl.SSL_CTX_set_default_verify_paths(ctx)
else:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyNone, None)
ssl: openssl.SslPtr = openssl.SSL_new(ctx)
if ssl is None:
_RecordErrorText("SSL_new failed")
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
openssl.SSL_set_fd(ssl, sock.fd)
openssl.SSL_ctrl(ssl, openssl.SslCtrlSetTlsextHostname, openssl.TlsextNametypeHostName, host_buf)
if verify:
openssl.SSL_set1_host(ssl, host_buf)
hret: INT = openssl.SSL_connect(ssl)
if hret <= 0:
_RecordErrorText("SSL_connect failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
if verify:
_last_verify_val = INT(openssl.SSL_get_verify_result(ssl))
if _last_verify_val != 0:
_RecordErrorText("SSL certificate verify failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
else:
_last_verify_val = -1
# Build Range request header (no Accept-Encoding, so response is raw bytes)
req: str = stdlib.malloc(1024)
if req is None:
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
cnt: INT = count
if cnt < 0:
cnt = 0
end: sizet = start + sizet(cnt) - 1
if cnt <= 0:
end = start
stdio.snprintf(req, 1024,
"GET %s HTTP/1.1\r\nHost: %s\r\nRange: bytes=%zu-%zu\r\nConnection: close\r\n\r\n",
path_buf, host_buf, start, end)
sent: INT = openssl.SSL_write(ssl, req, INT(string.strlen(req)))
stdlib.free(req)
if sent <= 0:
_RecordErrorText("SSL_write failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
total: INT = 0
tgt: INT = -1
while total < dst_cap - 1:
room: INT = dst_cap - 1 - total
if room > 4096:
room = 4096
n: INT = openssl.SSL_read(ssl, dst + total, room)
if n <= 0:
break
total += n
if tgt < 0:
bo: INT = requests._FindBodyStart(dst, total)
if bo >= 0:
cl: INT = requests._CLenOf(dst, total)
if cl >= 0:
tgt = bo + cl
if tgt >= 0 and total >= tgt:
break
openssl.SSL_shutdown(ssl)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
dst[total] = 0
st: INT = requests._StatusRaw(dst)
*out_status = st
if st == 0:
_RecordErrorText("empty response")
return -3
if requests._IsHttpRedirect(st):
if out_loc is not None and out_loccap > 0:
if requests._RawHeaderValue(dst, total, "Location", out_loc, out_loccap) != 0:
out_loc[0] = 0
return -2
if st != 200 and st != 206:
_RecordErrorText("http status not 200/206")
return -3
if st == 206:
*out_total = requests._CRTotalOf(dst, total)
else:
cl2: INT = requests._CLenOf(dst, total)
if cl2 > 0:
*out_total = sizet(cl2)
bo: INT = requests._FindBodyStart(dst, total)
if bo < 0 or bo >= total:
return 0
blen: INT = total - bo
if blen > cnt and cnt > 0:
blen = cnt
return blen