Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,60 @@

All notable changes to Raven are documented in this file.

## [2.19.10] - 2026-07-02

### Fixed

- `std/json.parse` now rejects raw invalid UTF-8 bytes inside JSON string literals instead of accepting malformed JSON text as a `JsonValue.Str`. Valid raw UTF-8 and escaped Unicode still decode normally. (#845)

## [2.19.9] - 2026-07-02

### Fixed

- The `std/json` guide and spec now match the parser's surrogate policy: valid high/low surrogate pairs decode to the astral code point, while unpaired high or low surrogates are parse errors rather than documented as U+FFFD replacement. (#844)

## [2.19.8] - 2026-07-02

### Fixed

- The standard-library module charter now reflects the shipped `std/sync` concurrency primitives and bundled `std/tls` transport support instead of describing concurrency and TLS as deferred or package-only. (#843)

## [2.19.7] - 2026-07-02

### Fixed

- HTTP responses now validate field names and sanitize field values again immediately before serialization, so direct mutation of `Response.headers` cannot bypass the checked `Response.header` builder and write malformed header names or raw control bytes to the wire. (#842)

## [2.19.6] - 2026-07-02

### Added

- Added the missing `std/tls` standard-library guide page and navigation entries, covering verified TLS connections, custom trust, client certificates, STARTTLS upgrades, stream methods, and checked configuration builders. (#841)

## [2.19.5] - 2026-07-02

### Fixed

- `std/tls.TlsConfig` now exposes checked `add_ca_file_checked` and `client_cert_checked` builders that return `Result<TlsConfig, Error>` when CA, certificate, or key files cannot be read or loaded. The chaining builders remain available for compatibility. (#840)

## [2.19.4] - 2026-07-02

### Fixed

- `Rng.weighted_choice` no longer saturates huge positive cumulative weights at `Int::MAX`, which made later positive items unreachable. Selection now sums positive weights as `Float` and keeps all positive entries reachable even when the exact integer total would overflow. (#839)

## [2.19.3] - 2026-07-02

### Fixed

- `std/fmt.format_float` now clamps excessive fractional precision before its scaling factor or scaled value becomes infinite, so finite inputs such as `format_float(1.0, 400)` return a bounded string instead of hanging in digit extraction. (#837)

## [2.19.2] - 2026-07-02

### Fixed

- `std/encoding.base32_decode` rejects padding-only and impossible RFC 4648 base32 padding shapes instead of silently decoding them as empty output. (#838)

## [2.19.1] - 2026-06-27

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
members = [".", "raven-runtime"]

[workspace.package]
version = "2.19.1"
version = "2.19.10"
edition = "2021"
authors = ["martian56"]
license = "MIT"
Expand Down
1 change: 1 addition & 0 deletions docs/v2/guide/standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ helpers.
|--------|----------------|
| [std/net](stdlib/net.md) | TCP sockets |
| [std/http](stdlib/http.md) | an HTTP client and server |
| [std/tls](stdlib/tls.md) | verified TLS streams and STARTTLS upgrades |
| [std/json](stdlib/json.md) | JSON parse and stringify |

### Concurrency, errors, FFI, testing
Expand Down
2 changes: 2 additions & 0 deletions docs/v2/guide/stdlib/fmt.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ fun main() {

`x` rendered with exactly `decimals` digits after the decimal point, rounded
half up. A `decimals` of `0` or less produces no fractional part and no point.
Very large precision requests are clamped before the internal scale would
overflow; the result stays bounded instead of hanging on finite input.

```rust
import std/fmt { format_float }
Expand Down
2 changes: 1 addition & 1 deletion docs/v2/guide/stdlib/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ offset.
String escapes decode as in standard JSON: the two-character escapes
`\" \\ \/ \b \f \n \r \t`, and a `\uXXXX` escape for a code point (UTF-8
encoded into the result). A high surrogate followed by a low surrogate
decodes to the astral code point; a lone surrogate decodes to U+FFFD.
decodes to the astral code point; an unpaired surrogate is a parse error.

## Navigating a parsed value

Expand Down
4 changes: 4 additions & 0 deletions docs/v2/guide/stdlib/random.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ Choose an element of `xs` with probability proportional to the matching entry
of `weights`. Returns `None` when the lists are empty or the total weight is
not positive.

Non-positive weights are ignored. Very large positive weights are handled
without saturating the cumulative range, so later positive items remain
reachable even when the exact integer total would overflow.

```rust
import std/random

Expand Down
123 changes: 123 additions & 0 deletions docs/v2/guide/stdlib/tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# std/tls

Client-side TLS streams over the raven-runtime TLS backend. Use `std/tls` when
you need a raw encrypted byte stream for a protocol such as Redis, Postgres,
MySQL, or SMTP after STARTTLS negotiation. For ordinary HTTPS requests, use
[std/http](http.md); its client already handles TLS.

## Importing

```rust
import std/tls { connect, connect_with, upgrade, upgrade_with, config }
```

`TlsStream` and `TlsConfig` methods come in with their types.

## Connecting

### `connect(addr: String, server_name: String) -> Result<TlsStream, Error>`

Open a TCP connection to `addr` (`"host:port"`), start TLS immediately, send
`server_name` as SNI, and verify the peer against the bundled Mozilla root
store.

```rust
import std/tls { connect }

fun main() {
match connect("example.com:443", "example.com") {
Ok(stream) -> {
let _ = stream.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
print(stream.read_all()?)
stream.close()
},
Err(e) -> print(e.message()),
}
}
```

### `connect_with(addr, server_name, cfg) -> Result<TlsStream, Error>`

Connect with a custom `TlsConfig`, for private CAs, client certificates, or a
development-only skip of certificate verification.

## Configuring Trust

### `config() -> TlsConfig`

Create a TLS client configuration. It starts with the bundled public roots.

### `add_ca_file(path: String) -> TlsConfig`

Trust PEM certificate(s) from `path` in addition to the bundled roots and return
the config for chaining.

### `add_ca_file_checked(path: String) -> Result<TlsConfig, Error>`

The checked form of `add_ca_file`. Missing, unreadable, or malformed CA files
return an `Err` immediately.

### `client_cert(cert_path: String, key_path: String) -> TlsConfig`

Configure a client certificate chain and private key for mutual TLS and return
the config for chaining.

### `client_cert_checked(cert_path: String, key_path: String) -> Result<TlsConfig, Error>`

The checked form of `client_cert`. Missing, unreadable, or malformed certificate
or key files return an `Err` immediately.

### `insecure_skip_verify() -> TlsConfig`

Disable certificate verification. The connection is encrypted but the peer is
not authenticated, so this is only for local development.

```rust
import std/tls { config, connect_with }

fun main() {
let cfg = config().add_ca_file_checked("dev-ca.pem")?
match connect_with("db.internal:5432", "db.internal", cfg) {
Ok(stream) -> {
// speak the protocol
stream.close()
},
Err(e) -> print(e.message()),
}
cfg.free()
}
```

## STARTTLS

### `upgrade(stream: TcpStream, server_name: String) -> Result<TlsStream, Error>`

Turn an already-connected `std/net` TCP stream into a TLS stream on the same
socket. This is for protocols that negotiate in plaintext and then switch to
TLS. On success the original TCP stream is consumed.

### `upgrade_with(stream, server_name, cfg) -> Result<TlsStream, Error>`

The configured form of `upgrade`.

## Stream Methods

### `read(max: Int) -> Result<String, Error>`

Read up to `max` decrypted bytes. A clean close returns `Ok("")`.

### `read_all() -> Result<String, Error>`

Read decrypted bytes until the peer closes the connection.

### `write(data: String) -> Result<Int, Error>`

Encrypt and send all bytes in `data`, returning the count written.

### `set_read_timeout_ms(ms: Int)`, `set_write_timeout_ms(ms: Int)`

Set per-stream read or write timeouts. `0` disables the timeout.

### `close()`

Send TLS close-notify and release the runtime-side connection.
6 changes: 3 additions & 3 deletions docs/v2/specs/std-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ bytes in the result.
A high surrogate (`\uD800` to `\uDBFF`) immediately followed by a low
surrogate (`\uDC00` to `\uDFFF`) decodes to the astral code point and is
UTF-8 encoded. A high surrogate not followed by a low surrogate, or a lone
low surrogate, decodes to U+FFFD (the replacement character). Note that the
Raven lexer rejects surrogate `\u` escapes inside a source string literal,
so a surrogate input reaches `parse` only from data read at runtime.
low surrogate, is a parse error. Note that the Raven lexer rejects surrogate
`\u` escapes inside a source string literal, so a surrogate input reaches
`parse` only from data read at runtime.

### Numbers

Expand Down
7 changes: 6 additions & 1 deletion docs/v2/specs/std-tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ checks that the certificate matches `server_name`, which is also sent as SNI.

- `add_ca_file(path)` trusts the PEM certificate(s) in `path` in addition to the
bundled roots, for a server signed by a private CA.
- `add_ca_file_checked(path)` is the fallible form, returning
`Result<TlsConfig, Error>` when the CA file cannot be read or loaded.
- `client_cert(cert_path, key_path)` presents a client certificate chain and
private key (PEM) for mutual TLS.
- `client_cert_checked(cert_path, key_path)` is the fallible form, returning
`Result<TlsConfig, Error>` when either file cannot be read or loaded.
- `insecure_skip_verify()` accepts any certificate. The session is still
encrypted, but the peer is not authenticated, so this is for local
development only.
Expand Down Expand Up @@ -70,7 +74,8 @@ prefix (the operation name) joined to the runtime error text. As in `std/net`,
no error structs cross the FFI: the runtime keeps a thread-local last-error
string that a fallible op clears on success and sets on failure, and
`raven_tls_last_error()` returns it. An id of 0 from a connect, a negative count
from a write, or a non-empty last error becomes an `Err`.
from a write, a checked config builder returning 0, or a non-empty last error
becomes an `Err`.

## Bytes

Expand Down
16 changes: 9 additions & 7 deletions docs/v2/specs/stdlib-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,18 @@ Every module implicitly imports the prelude.
| `std/hash` | non-crypto hashing (FNV, xxHash) backing the `Hash` trait, checksums | Rust hash, Go hash |
| `std/net` | TCP, UDP, addresses, DNS | Go net, Rust net |
| `std/http` | HTTP client plus a minimal server | Go net/http |
| `std/tls` | verified TLS streams, custom trust, STARTTLS upgrade | Rustls, Go crypto/tls |
| `std/regex` | regular expressions | Go regexp, Python re, Java regex |
| `std/ffi` | `CStr`, `CInt`, `CPtr<T>`, conversions | C interop |
| `std/sync` | goroutine channels, sleep, yield, mutex, wait groups | Go sync/channel |

## Deferred to v2.x
## Future extensions

Concurrency is its own milestone; v2.0 is single-threaded.
Raven v2 now ships goroutines, channels, and the `std/sync` primitives that
support them. The remaining concurrency surface can grow in follow-up releases
without changing the shipped channel API.

- `std/sync` (Mutex, RwLock, Once)
- `std/thread` (spawn, join)
- `std/channel` (message passing)
- `std/atomic` (atomic primitives)

A lazy/async runtime, if added, is also a v2.x discussion.
Expand All @@ -79,7 +81,7 @@ A lazy/async runtime, if added, is also a v2.x discussion.

Delivered through rvpm rather than the standard library, because they are security-sensitive, fast-moving, opinionated, or niche:

- Cryptography and TLS signing (sha2, hmac, ed25519, and so on)
- Cryptography primitives and signing (sha2, hmac, ed25519, and so on)
- Compression (gzip, zstd, brotli)
- Serialization beyond JSON (YAML, TOML, msgpack, protobuf)
- Database drivers
Expand All @@ -105,5 +107,5 @@ Implementation proceeds in that dependency order, then the modules above, then t
1. Compiler foundations: methods on built-ins, capturing closures, core trait prelude, lazy iterators.
2. Convert the already-shipped `std/io` and `std/string` to the method-first API.
3. Core modules: collections, math, cmp, fmt, error, test.
4. System and data modules: time, fs, path, env, process, random, json, encoding, hash, net, http, regex.
5. v2.x: concurrency.
4. System, data, and network modules: time, fs, path, env, process, random, json, encoding, hash, net, http, tls, regex.
5. Concurrency primitives: goroutines plus `std/sync`.
25 changes: 15 additions & 10 deletions examples/v2/http_server_header_validation.rv
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import std/time { sleep_millis }
import std/io { println }

fun handler(req: Request) -> Response {
return Response.text("body")
.header("Bad Name", "dropped")
.header("X-Ctrl", "a".concat(__str_from_byte(1)).concat("b"))
.header("X-Good", "fine")
let r = Response.text("body").header("Bad Name", "dropped").header(
"X-Ctrl",
"a".concat(__str_from_byte(1)).concat("b"),
).header("X-Good", "fine")
r.headers.set("Direct Bad", "dropped")
r.headers.set("X-Direct-Ctrl", "c".concat(__str_from_byte(1)).concat("d"))
return r
}

fun headers_block(s: String) -> String {
Expand All @@ -30,10 +33,10 @@ fun connect_retry(addr: String) -> Result<TcpStream, Error> {
match connect(addr) {
Ok(s) -> {
return Ok(s)
}
},
Err(_) -> {
sleep_millis(20)
}
},
}
attempt += 1
}
Expand All @@ -48,12 +51,12 @@ fun send(addr: String, request: String) -> String {
match s.read_all() {
Ok(r) -> {
result = r
}
Err(_) -> {}
},
Err(_) -> {},
}
s.close()
}
Err(_) -> {}
},
Err(_) -> {},
}
return result
}
Expand All @@ -69,8 +72,10 @@ fun main() {
let resp = send(addr, "GET / HTTP/1.1\r\nHost: t\r\nConnection: close\r\n\r\n")
let block = headers_block(resp)
println("bad-name dropped: ${block.index_of("Bad Name") < 0}")
println("direct bad-name dropped: ${block.index_of("Direct Bad") < 0}")
println("good present: ${block.index_of("X-Good: fine") >= 0}")
println("control stripped: ${block.index_of("X-Ctrl: ab") >= 0}")
println("direct control stripped: ${block.index_of("X-Direct-Ctrl: cd") >= 0}")

app.shutdown()
}
Loading
Loading