1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
use core::marker::PhantomData;
use core::mem;
use crate::AnyResponse;
use crate::response::Response;
/// Response types that can drive a `BodyReaderMixin`: must support registering
/// data/abort callbacks and converting to `AnyResponse`. Stands in for the Zig
/// `anytype` parameter on `readBody`.
///
/// Only `Response<SSL>` is wired today (DevServer's only consumer is HTTP/1.x);
/// `h3::Response` can be added once its callback signatures are unified.
pub trait BodyResponse: Sized + 'static {
fn on_data<U, H>(&mut self, handler: H, ctx: *mut U)
where
H: Fn(*mut U, &mut Self, &[u8], bool) + Copy + 'static;
fn on_aborted<U, H>(&mut self, handler: H, ctx: *mut U)
where
H: Fn(*mut U, &mut Self) + Copy + 'static;
fn to_any(&mut self) -> AnyResponse;
}
impl<const SSL: bool> BodyResponse for Response<SSL> {
#[inline]
fn on_data<U, H>(&mut self, handler: H, ctx: *mut U)
where
H: Fn(*mut U, &mut Self, &[u8], bool) + Copy + 'static,
{
Response::<SSL>::on_data(self, handler, ctx)
}
#[inline]
fn on_aborted<U, H>(&mut self, handler: H, ctx: *mut U)
where
H: Fn(*mut U, &mut Self) + Copy + 'static,
{
Response::<SSL>::on_aborted(self, handler, ctx)
}
#[inline]
fn to_any(&mut self) -> AnyResponse {
// `From<*mut Response<{true,false}>>` exist as two concrete impls, not a
// const-generic one, so dispatch on `SSL` here. Same shape as Zig's
// `AnyResponse.init` switching on @TypeOf.
if SSL {
AnyResponse::SSL(std::ptr::from_mut::<Self>(self).cast())
} else {
AnyResponse::TCP(std::ptr::from_mut::<Self>(self).cast())
}
}
}
/// Mixin to read an entire request body into memory and run a callback.
/// Consumers should make sure a reference count is held on the server,
/// and is unreferenced after one of the two callbacks are called.
///
/// See `DevServer`'s `ErrorReportRequest` for an example.
///
/// In Zig this was a `fn(...) type` taking a comptime `field` name and two
/// comptime fn pointers (`onBody`, `onError`). In Rust those are expressed as
/// a trait the wrapper type implements; the comptime `field` name used by
/// `@fieldParentPtr` is the [`bun_core::IntrusiveField`] supertrait (implement
/// via `bun_core::intrusive_field!`).
pub trait BodyReaderHandler: bun_core::IntrusiveField<BodyReaderMixin<Self>> + 'static {
/// `body` is freed after this function returns.
///
/// Receives the original `heap::alloc`'d pointer (full-allocation
/// provenance) rather than `&mut self`: implementors typically free `Self`
/// (`heap::take`) on the success path, and doing so through a
/// `&mut self`-derived pointer is UB under Stacked/Tree Borrows. This
/// mirrors Zig's `fn(*Wrap, ...)` callback shape exactly.
///
/// SAFETY: `this` is the pointer previously passed to
/// `BodyReaderMixin::read_body`; it is live and uniquely owned by the
/// mixin until this call (no other `&mut` into the allocation is live).
unsafe fn on_body(
this: *mut Self,
body: &[u8],
resp: AnyResponse,
) -> Result<(), bun_core::Error>;
/// Called on error or request abort. Same provenance contract as `on_body`.
///
/// SAFETY: see `on_body`.
unsafe fn on_error(this: *mut Self);
}
pub struct BodyReaderMixin<Wrap: BodyReaderHandler> {
body: Vec<u8>,
_wrap: PhantomData<Wrap>,
}
const MAX_BODY_SIZE: usize = 1024 * 1024 * 128;
impl<Wrap: BodyReaderHandler> BodyReaderMixin<Wrap> {
pub fn init() -> Self {
Self {
body: Vec::new(),
_wrap: PhantomData,
}
}
/// Memory is freed after the callback returns, or automatically on failure.
///
/// Takes `*mut Wrap` (not `&mut self`) so the registered C user_data carries
/// provenance for the *entire* enclosing `Wrap`, not just the mixin field.
/// Zig used `@fieldParentPtr(field, ctx)` which has no provenance/aliasing
/// restriction; in Rust, deriving the parent by `.byte_sub(OFFSET)` from a
/// `&mut self`-sourced pointer is out-of-provenance under Stacked Borrows
/// and the resulting `&mut Wrap` would overlap a live `&mut Self`. Callers
/// pass the `heap::alloc`'d wrapper pointer directly; trampolines below
/// reach the mixin via *forward* offset (`mixin_of`), so the stored pointer
/// already has full-Wrap provenance and no overlapping `&mut` are formed.
pub fn read_body<R: BodyResponse>(wrap: *mut Wrap, resp: &mut R) {
resp.on_data(Self::on_data_generic::<R>, wrap);
resp.on_aborted(Self::on_aborted_handler::<R>, wrap);
}
/// Forward offset `Wrap` → its embedded mixin field, materialised as `&mut`.
/// Inverse direction of Zig's `@fieldParentPtr` — we go parent→field because
/// the stored user_data is the parent (full provenance), never the field.
///
/// Single nonnull-asref accessor for the set-once `wrap` user-data.
///
/// Type invariant (encapsulated `unsafe`): every `*mut Wrap` reaching this
/// fn is the heap-allocated pointer registered by [`Self::read_body`] as
/// the uWS user-data; uWS dispatch is single-threaded and the only access
/// path to the allocation is via these crate-private trampolines, so no
/// other `&`/`&mut` into `*wrap` is live for the returned borrow's
/// duration. Each caller drops the returned `&mut Self` (NLL temporary)
/// before any `Wrap::on_body`/`on_error` call that may `heap::take(wrap)`.
/// Crate-private — collapses the per-call-site proof into this one block.
#[inline]
fn mixin_of<'a>(wrap: *mut Wrap) -> &'a mut Self {
// SAFETY: type invariant — see doc comment above. `IntrusiveField::OFFSET`
// is `offset_of!(Wrap, <field>)`, so the result is in-bounds and inherits
// `wrap`'s provenance over the whole allocation.
unsafe { &mut *Wrap::field_of(wrap) }
}
fn on_data_generic<R: BodyResponse>(wrap: *mut Wrap, r: &mut R, chunk: &[u8], last: bool) {
let any = r.to_any();
match Self::on_data(wrap, any, chunk, last) {
Ok(()) => {}
// Match Zig's `error.OutOfMemory => onOOM, else => onInvalid` by error
// *kind* only — `bun_core::Error`'s derived `PartialEq` compares all
// fields (syscall/fd/path), and `err!("OutOfMemory")` is currently a
// TODO sentinel, so a full-struct compare would invert the branch.
Err(e) if e == bun_core::Error::OUT_OF_MEMORY => Self::on_oom(wrap, any),
Err(_) => Self::on_invalid(wrap, any),
}
}
fn on_aborted_handler<R>(wrap: *mut Wrap, _r: &mut R) {
// The temporary `&mut` from `mixin_of` ends at the `;`, before
// `on_error` (which may `heap::take(wrap)`).
Self::mixin_of(wrap).body = Vec::new();
// SAFETY: wrap is the original heap-allocated pointer; the temporary
// &mut to the mixin field above has ended, so on_error receives sole
// ownership of the allocation and may heap::take it.
unsafe { Wrap::on_error(wrap) };
}
fn on_data(
wrap: *mut Wrap,
resp: AnyResponse,
chunk: &[u8],
last: bool,
) -> Result<(), bun_core::Error> {
if last {
// Free everything after. Take via the mixin field first — no
// `&mut Wrap` is live yet, and the temporary `&mut Self` ends at
// the `;` (before `on_body`, which may heap::take(wrap)).
let mut body = mem::take(&mut Self::mixin_of(wrap).body);
resp.clear_on_data();
if !body.is_empty() {
if body.len().saturating_add(chunk.len()) > MAX_BODY_SIZE {
return Err(bun_core::err!(RequestBodyTooLarge));
}
// TODO(port): Zig handled OOM gracefully here; Vec::extend_from_slice aborts.
// Consider try_reserve if graceful 500 on OOM is required.
body.extend_from_slice(chunk);
// SAFETY: wrap is the original heap-allocated pointer; the &mut to
// mixin.body has ended, so on_body receives sole ownership of the
// allocation and may heap::take it on success.
unsafe { Wrap::on_body(wrap, body.as_slice(), resp)? };
} else {
if chunk.len() > MAX_BODY_SIZE {
return Err(bun_core::err!(RequestBodyTooLarge));
}
// SAFETY: wrap is the original heap-allocated pointer; the &mut to
// mixin.body has ended, so on_body receives sole ownership of the
// allocation and may heap::take it on success.
unsafe { Wrap::on_body(wrap, chunk, resp)? };
}
// `body` drops here (was `defer body.deinit()` in Zig)
Ok(())
} else {
let body = &mut Self::mixin_of(wrap).body;
if body.len().saturating_add(chunk.len()) > MAX_BODY_SIZE {
return Err(bun_core::err!(RequestBodyTooLarge));
}
body.extend_from_slice(chunk);
Ok(())
}
}
fn on_oom(wrap: *mut Wrap, r: AnyResponse) {
// The temporary `&mut` from `mixin_of` ends at the `;`, before
// `on_error` (which may `heap::take(wrap)`).
drop(mem::take(&mut Self::mixin_of(wrap).body));
r.clear_aborted();
r.clear_on_data();
r.clear_on_writable();
r.write_status(b"500 Internal Server Error");
r.end_without_body(false);
// SAFETY: wrap is the original heap-allocated pointer; the &mut to
// mixin.body above has ended; on_error may heap::take it.
unsafe { Wrap::on_error(wrap) };
}
fn on_invalid(wrap: *mut Wrap, r: AnyResponse) {
// The temporary `&mut` from `mixin_of` ends at the `;`, before
// `on_error` (which may `heap::take(wrap)`).
drop(mem::take(&mut Self::mixin_of(wrap).body));
r.clear_aborted();
r.clear_on_data();
r.clear_on_writable();
r.write_status(b"400 Bad Request");
r.end_without_body(false);
// SAFETY: wrap is the original heap-allocated pointer; the &mut to
// mixin.body above has ended; on_error may heap::take it.
unsafe { Wrap::on_error(wrap) };
}
}
// ported from: src/uws_sys/BodyReaderMixin.zig