~ober/jerboa-https

Imported from ~/mine/jerboa-https

download snapshot

about

# chez-https

Native HTTP/1.1 client over TLS for Chez Scheme. No subprocesses. No curl. Pure in-process HTTPS.

## Dependencies

- `jerboa-ssl` — TLS transport layer, locked by commit and tree in
  `dependencies.lock`
- Chez Scheme 10.x
- OpenSSL (`libssl`, `libcrypto`)

## Installation

```bash
# From this checkout, fetch the reviewed dependency revision.
support/locked-dependency.sh fetch dependencies.lock jerboa-ssl ~/mine/jerboa-ssl
make
```

CI uses the same lock verifier. Updating `jerboa-ssl` requires a reviewed
commit and tree-hash change in `dependencies.lock`; default branches are never
consumed by the build.

## Usage

```scheme
(import (chez-https))

;; Simple GET
(let ([req (http-get "https://example.com/")])
  (display (request-status req))    ;; 200
  (display (request-text req))      ;; HTML body as string
  (request-close req))

;; With headers and query parameters (S3-compatible format)
(let ([headers (list (list "Authorization" ':: "AWS4-HMAC-SHA256 ...")
                     '::
                     (list "Host" ':: "bucket.s3.amazonaws.com")
                     (list "x-amz-date" ':: "20260306T120000Z"))]
      [params  (list (list "list-type" ':: "2")
                     (list "prefix" ':: "AWSLogs/"))])
  (let ([req (http-get url 'headers: headers 'params: params)])
    (display (request-status req))
    (request-close req)))

;; PUT with body
(let ([req (http-put url
             'headers: headers
             'params: #f
             'data: (string->utf8 "request body"))])
  (display (request-status req))
  (request-close req))
```

## API

### HTTP Methods

```scheme
(http-get    url ['headers: hdrs] ['params: params])
(http-post   url ['headers: hdrs] ['params: params] ['data: body])
(http-put    url ['headers: hdrs] ['params: params] ['data: body])
(http-delete url ['headers: hdrs] ['params: params])
(http-head   url ['headers: hdrs] ['params: params])
```

### Response Accessors

| Function | Returns | Description |
|----------|---------|-------------|
| `(request-status req)` | integer | HTTP status code (200, 404, etc.) |
| `(request-text req)` | string | Response body as UTF-8 string |
| `(request-content req)` | bytevector | Response body as raw bytes |
| `(request-headers req)` | alist | Response headers (lowercase keys) |
| `(request-header req name)` | string or `#f` | Single header value lookup |
| `(request-close req)` | void | No-op (connection already closed) |

### Utilities

| Function | Description |
|----------|-------------|
| `(parse-url url)` | Returns `(values host port path)` |
| `(url-encode str)` | RFC 3986 percent-encoding |
| `(flatten-request-headers hdrs)` | Flatten `(name :: value)` format to alist |
| `(build-query-string params)` | Build URL query string from params |

### Response policy

`(http-client-config)` returns the active bounded response policy. Set startup
limits with options such as `max-header-bytes:`, `max-headers:`, `max-body:`,
`max-chunk:`, `max-chunked-wire:`, `idle-timeout:`, and `total-timeout:`.
Values must be positive exact integers. Configure the policy before issuing
concurrent requests.

## Features

- HTTPS via OpenSSL (TLS 1.2/1.3)
- SNI and hostname verification
- Streaming, bounded chunked transfer decoding
- Strict duplicate/conflicting response-framing rejection
- Header, decoded-body, raw-chunked-body, chunk-size, idle, and total limits
- RFC-token/control-character validation before request serialization
- 100 Continue handling
- Binary response body preservation (critical for gzip/binary downloads)
- Configurable HTTPD worker count, listen backlog, queue capacity, request
  timeout, response timeout, header count, and body size limits
- Bounded TLS handshake workers, queue, short handshake deadline, and
  global/per-IP pre-authentication limits
- Descriptor-pinned static-file serving with canonical containment and inode
  identity verification
- S3-compatible header format (`::` separator convention)
- URL encoding (RFC 3986)
- Automatic SSL initialization

## Testing

```bash
make test
```

The tests include a local self-signed TLS peer for hostile response framing and
deadline cases. Interpreter-based development explicitly opts into loading
`jerboa-ssl` from its canonical absolute directory; production binaries should
statically register the TLS shim.

## Release Evidence

The HTTPD surface is private until the daemon release gate is complete. Run:

```bash
make release-evidence
```

The release bundle includes SBOM, reproducibility, TLS dependency, HTTPD
fuzz-evidence, and HTTPD soak status under `dist/release-evidence/`. Default
fuzz evidence records fixed adversarial cases, a 2048-case generated
deterministic HTTPD corpus, and a present cargo-fuzz harness for the sandboxed
HTTP parser. Run with `JHTTPS_RUN_COVERAGE_FUZZ=1` to record bounded local
coverage evidence; production release still requires longer release-host fuzz
evidence or a signed exception. Default local soak evidence records load and queue-exhaustion
soak as blocked/not-run. Opt-in local soak evidence records deterministic
slow-client coverage plus bounded load/queue smokes, but production release
still requires target-host sustained load evidence.
HTTPD TLS/proxy policy is documented in `docs/tls-proxy-policy.md` and copied
into `dist/release-evidence/tls-proxy-policy.txt`.

## Architecture

```
chez-https (this library)
    │
    └── jerboa-ssl (TLS transport)
            │
            └── OpenSSL (libssl, libcrypto)
```

`chez-https` is Scheme on top of `chez-ssl` for I/O. The HTTPD parser boundary
uses a small Rust/WASM sandbox for request lines, header lines, and chunk sizes
when the Jerboa WASM sandbox is available; `JHTTPS_REQUIRE_WASM_HTTP=1` makes
that boundary fail closed for production deployments.

recent commits