castrum 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +73 -0
- package/README.md +432 -0
- package/bench.ts +3 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/index.ts +24 -0
- package/package.json +94 -0
- package/rust/batch.rs +195 -0
- package/rust/cookie_parser.rs +70 -0
- package/rust/cors.rs +263 -0
- package/rust/hashing.rs +164 -0
- package/rust/headers.rs +234 -0
- package/rust/hmac_sha256.rs +113 -0
- package/rust/http_parser.rs +132 -0
- package/rust/ingress.rs +616 -0
- package/rust/ingress_constants.rs +89 -0
- package/rust/ip_trust.rs +185 -0
- package/rust/json_ops.rs +66 -0
- package/rust/json_patch_ops.rs +25 -0
- package/rust/json_schema.rs +151 -0
- package/rust/json_ser.rs +429 -0
- package/rust/lib.rs +48 -0
- package/rust/method.rs +141 -0
- package/rust/mime_lookup.rs +76 -0
- package/rust/output.rs +238 -0
- package/rust/proxy.rs +90 -0
- package/rust/query_parser.rs +141 -0
- package/rust/random_token.rs +52 -0
- package/rust/rate_limit.rs +203 -0
- package/rust/terminal.rs +95 -0
- package/rust/unit_tests.rs +843 -0
- package/rust/url_codec.rs +133 -0
- package/rust/util.rs +537 -0
- package/rust/validation.rs +107 -0
- package/rust/websocket.rs +23 -0
- package/src/baseline/index.ts +12 -0
- package/src/baseline/tasks/cookie.ts +22 -0
- package/src/baseline/tasks/hashing.ts +18 -0
- package/src/baseline/tasks/hmac.ts +38 -0
- package/src/baseline/tasks/http.ts +62 -0
- package/src/baseline/tasks/json-patch.ts +12 -0
- package/src/baseline/tasks/json.ts +50 -0
- package/src/baseline/tasks/mime.ts +5 -0
- package/src/baseline/tasks/query.ts +14 -0
- package/src/baseline/tasks/token.ts +15 -0
- package/src/baseline/tasks/url.ts +11 -0
- package/src/baseline/tasks/validation.ts +19 -0
- package/src/baseline/tasks/websocket.ts +14 -0
- package/src/bench/assert.ts +70 -0
- package/src/bench/checks.ts +227 -0
- package/src/bench/checksum.ts +27 -0
- package/src/bench/comparisons.ts +70 -0
- package/src/bench/concurrent-worker.ts +135 -0
- package/src/bench/fixtures.ts +233 -0
- package/src/bench/index.ts +2 -0
- package/src/bench/measure.ts +177 -0
- package/src/bench/now.ts +3 -0
- package/src/bench/report.ts +79 -0
- package/src/bench/run.ts +57 -0
- package/src/bench/tasks/complex.ts +232 -0
- package/src/bench/tasks/concurrent.ts +213 -0
- package/src/bench/tasks/cookie.ts +44 -0
- package/src/bench/tasks/hashing.ts +33 -0
- package/src/bench/tasks/hmac.ts +36 -0
- package/src/bench/tasks/http.ts +50 -0
- package/src/bench/tasks/index.ts +52 -0
- package/src/bench/tasks/json-patch.ts +21 -0
- package/src/bench/tasks/json.ts +33 -0
- package/src/bench/tasks/mime.ts +21 -0
- package/src/bench/tasks/query.ts +44 -0
- package/src/bench/tasks/stress.ts +142 -0
- package/src/bench/tasks/token.ts +20 -0
- package/src/bench/tasks/url.ts +34 -0
- package/src/bench/tasks/validation.ts +57 -0
- package/src/bench/tasks/websocket.ts +21 -0
- package/src/bench/types.ts +60 -0
- package/src/data/json-rows.ts +33 -0
- package/src/ingress/constants.ts +56 -0
- package/src/ingress/fast.ts +1082 -0
- package/src/ingress/handlers.ts +1202 -0
- package/src/ingress/index.ts +457 -0
- package/src/ingress/packed-input.ts +59 -0
- package/src/native/index.ts +207 -0
- package/src/rust-ffi/index.ts +555 -0
- package/src/shared/bytes.ts +36 -0
- package/src/shared/json.ts +18 -0
- package/src/shared/packed.ts +316 -0
package/rust/headers.rs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// rust/headers.rs — HeaderRefs: zero-alloc packed header parser
|
|
2
|
+
// Extracted from ingress.rs for composability
|
|
3
|
+
// Uses u64 word-at-a-time comparison for header name matching
|
|
4
|
+
|
|
5
|
+
use napi::{Error, Result};
|
|
6
|
+
|
|
7
|
+
// ── Header presence flags ─────────────────────────────────────────
|
|
8
|
+
const HAS_ORIGIN: u8 = 1 << 0;
|
|
9
|
+
const HAS_COOKIE: u8 = 1 << 1;
|
|
10
|
+
const HAS_XFF: u8 = 1 << 2;
|
|
11
|
+
const HAS_X_REAL_IP: u8 = 1 << 3;
|
|
12
|
+
const HAS_XFP: u8 = 1 << 4;
|
|
13
|
+
const HAS_ACRM: u8 = 1 << 5;
|
|
14
|
+
const HAS_ACRH: u8 = 1 << 6;
|
|
15
|
+
|
|
16
|
+
/// Zero-alloc reference-based representation of interesting headers.
|
|
17
|
+
#[derive(Clone, Copy)]
|
|
18
|
+
pub struct HeaderRefs<'a> {
|
|
19
|
+
origin: Option<&'a [u8]>,
|
|
20
|
+
cookie: Option<&'a [u8]>,
|
|
21
|
+
xff: Option<&'a [u8]>,
|
|
22
|
+
x_real_ip: Option<&'a [u8]>,
|
|
23
|
+
x_forwarded_proto: Option<&'a [u8]>,
|
|
24
|
+
acrm: Option<&'a [u8]>,
|
|
25
|
+
acrh: Option<&'a [u8]>,
|
|
26
|
+
flags: u8,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Load a u64 from the first N bytes of a byte slice, zero-padded.
|
|
30
|
+
#[inline(always)]
|
|
31
|
+
fn load_u64_padded(bytes: &[u8]) -> u64 {
|
|
32
|
+
let mut buf = [0u8; 8];
|
|
33
|
+
let len = bytes.len().min(8);
|
|
34
|
+
buf[..len].copy_from_slice(&bytes[..len]);
|
|
35
|
+
u64::from_le_bytes(buf)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Compare two byte slices using u64 word comparison for case-insensitive matching.
|
|
39
|
+
/// Both inputs should be lowercase already (or we compute lowercase u64).
|
|
40
|
+
#[inline(always)]
|
|
41
|
+
fn byte_slice_eq_ignore_case(a: &[u8], b: &[u8]) -> bool {
|
|
42
|
+
if a.len() != b.len() {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// For short names (up to 8 bytes) use single u64 comparison
|
|
46
|
+
if a.len() <= 8 {
|
|
47
|
+
let wa = load_u64_padded(a) | 0x2020_2020_2020_2020;
|
|
48
|
+
let wb = load_u64_padded(b) | 0x2020_2020_2020_2020;
|
|
49
|
+
return wa == wb;
|
|
50
|
+
}
|
|
51
|
+
// For longer names: compare first 8 bytes, then remaining
|
|
52
|
+
if a.len() <= 16 {
|
|
53
|
+
let wa = load_u64_padded(a) | 0x2020_2020_2020_2020;
|
|
54
|
+
let wb = load_u64_padded(b) | 0x2020_2020_2020_2020;
|
|
55
|
+
if wa != wb {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
let wa2 = load_u64_padded(&a[8..]) | 0x2020_2020_2020_2020;
|
|
59
|
+
let wb2 = load_u64_padded(&b[8..]) | 0x2020_2020_2020_2020;
|
|
60
|
+
return wa2 == wb2;
|
|
61
|
+
}
|
|
62
|
+
// Fallback
|
|
63
|
+
a.eq_ignore_ascii_case(b)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl<'a> HeaderRefs<'a> {
|
|
67
|
+
#[inline(always)]
|
|
68
|
+
pub fn empty() -> Self {
|
|
69
|
+
Self {
|
|
70
|
+
origin: None,
|
|
71
|
+
cookie: None,
|
|
72
|
+
xff: None,
|
|
73
|
+
x_real_ip: None,
|
|
74
|
+
x_forwarded_proto: None,
|
|
75
|
+
acrm: None,
|
|
76
|
+
acrh: None,
|
|
77
|
+
flags: 0,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Parse packed headers from the input buffer.
|
|
82
|
+
/// Format: [u16 count] repeated { [u16 name_len] [name] [u32 value_len] [value] }
|
|
83
|
+
#[inline]
|
|
84
|
+
pub fn parse(packed: &'a [u8], is_options: bool, max_headers: usize) -> Result<Self> {
|
|
85
|
+
let mut h = Self::empty();
|
|
86
|
+
|
|
87
|
+
if packed.len() < 2 {
|
|
88
|
+
return Ok(h);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let count = u16::from_le_bytes([packed[0], packed[1]]) as usize;
|
|
92
|
+
if count > max_headers {
|
|
93
|
+
return Err(Error::from_reason("too many headers"));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let mut pos = 2usize;
|
|
97
|
+
|
|
98
|
+
for _ in 0..count {
|
|
99
|
+
if pos + 2 > packed.len() {
|
|
100
|
+
return Err(Error::from_reason("malformed packed headers"));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let name_len = u16::from_le_bytes([packed[pos], packed[pos + 1]]) as usize;
|
|
104
|
+
pos += 2;
|
|
105
|
+
|
|
106
|
+
if pos + name_len > packed.len() {
|
|
107
|
+
return Err(Error::from_reason("malformed packed header name"));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let name = &packed[pos..pos + name_len];
|
|
111
|
+
pos += name_len;
|
|
112
|
+
|
|
113
|
+
if pos + 4 > packed.len() {
|
|
114
|
+
return Err(Error::from_reason("malformed packed header value length"));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let value_len = u32::from_le_bytes([
|
|
118
|
+
packed[pos],
|
|
119
|
+
packed[pos + 1],
|
|
120
|
+
packed[pos + 2],
|
|
121
|
+
packed[pos + 3],
|
|
122
|
+
]) as usize;
|
|
123
|
+
pos += 4;
|
|
124
|
+
|
|
125
|
+
if pos + value_len > packed.len() {
|
|
126
|
+
return Err(Error::from_reason("malformed packed header value"));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let value = &packed[pos..pos + value_len];
|
|
130
|
+
pos += value_len;
|
|
131
|
+
|
|
132
|
+
if name.is_empty() {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let first = name[0] | 0x20;
|
|
137
|
+
|
|
138
|
+
// Fast path: dispatch based on first character + name length
|
|
139
|
+
if first == b'o' && name.len() == 6 {
|
|
140
|
+
if byte_slice_eq_ignore_case(name, b"origin") {
|
|
141
|
+
h.flags |= HAS_ORIGIN;
|
|
142
|
+
h.origin = Some(value);
|
|
143
|
+
}
|
|
144
|
+
} else if first == b'c' && name.len() == 6 {
|
|
145
|
+
if byte_slice_eq_ignore_case(name, b"cookie") {
|
|
146
|
+
h.flags |= HAS_COOKIE;
|
|
147
|
+
h.cookie = Some(value);
|
|
148
|
+
}
|
|
149
|
+
} else if first == b'x' {
|
|
150
|
+
let name_len = name.len();
|
|
151
|
+
if name_len == 15 && byte_slice_eq_ignore_case(name, b"x-forwarded-for") {
|
|
152
|
+
h.flags |= HAS_XFF;
|
|
153
|
+
h.xff = Some(value);
|
|
154
|
+
} else if name_len == 9 && byte_slice_eq_ignore_case(name, b"x-real-ip") {
|
|
155
|
+
h.flags |= HAS_X_REAL_IP;
|
|
156
|
+
h.x_real_ip = Some(value);
|
|
157
|
+
} else if name_len == 17 && byte_slice_eq_ignore_case(name, b"x-forwarded-proto") {
|
|
158
|
+
h.flags |= HAS_XFP;
|
|
159
|
+
h.x_forwarded_proto = Some(value);
|
|
160
|
+
}
|
|
161
|
+
} else if first == b'a' && is_options {
|
|
162
|
+
if name.len() == 29 && byte_slice_eq_ignore_case(name, b"access-control-request-method") {
|
|
163
|
+
h.flags |= HAS_ACRM;
|
|
164
|
+
h.acrm = Some(value);
|
|
165
|
+
} else if name.len() == 30 && byte_slice_eq_ignore_case(name, b"access-control-request-headers") {
|
|
166
|
+
h.flags |= HAS_ACRH;
|
|
167
|
+
h.acrh = Some(value);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Ok(h)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[inline(always)]
|
|
176
|
+
pub fn origin(&self) -> Option<&[u8]> {
|
|
177
|
+
self.origin
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[inline(always)]
|
|
181
|
+
pub fn cookie(&self) -> Option<&[u8]> {
|
|
182
|
+
self.cookie
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[inline(always)]
|
|
186
|
+
pub fn xff(&self) -> Option<&[u8]> {
|
|
187
|
+
self.xff
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#[inline(always)]
|
|
191
|
+
pub fn x_real_ip(&self) -> Option<&[u8]> {
|
|
192
|
+
self.x_real_ip
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[inline(always)]
|
|
196
|
+
pub fn x_forwarded_proto(&self) -> Option<&[u8]> {
|
|
197
|
+
self.x_forwarded_proto
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[inline(always)]
|
|
201
|
+
pub fn acrm(&self) -> Option<&[u8]> {
|
|
202
|
+
self.acrm
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#[inline(always)]
|
|
206
|
+
pub fn acrh(&self) -> Option<&[u8]> {
|
|
207
|
+
self.acrh
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[inline(always)]
|
|
211
|
+
pub fn has_origin(&self) -> bool {
|
|
212
|
+
(self.flags & HAS_ORIGIN) != 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[inline(always)]
|
|
216
|
+
pub fn has_cookie(&self) -> bool {
|
|
217
|
+
(self.flags & HAS_COOKIE) != 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#[inline(always)]
|
|
221
|
+
pub fn has_xfp(&self) -> bool {
|
|
222
|
+
(self.flags & HAS_XFP) != 0
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[inline(always)]
|
|
226
|
+
pub fn has_acrm(&self) -> bool {
|
|
227
|
+
(self.flags & HAS_ACRM) != 0
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[inline(always)]
|
|
231
|
+
pub fn has_acrh(&self) -> bool {
|
|
232
|
+
(self.flags & HAS_ACRH) != 0
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
use napi::bindgen_prelude::*;
|
|
2
|
+
use napi_derive::napi;
|
|
3
|
+
use aws_lc_rs::hmac;
|
|
4
|
+
use crate::util::{hex_val, trim_ascii_whitespace};
|
|
5
|
+
|
|
6
|
+
const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
#[napi]
|
|
11
|
+
pub fn hmac_sha256(key: Uint8Array, data: Buffer) -> Buffer {
|
|
12
|
+
let key = hmac::Key::new(hmac::HMAC_SHA256, key.as_ref());
|
|
13
|
+
let tag = hmac::sign(&key, data.as_ref());
|
|
14
|
+
|
|
15
|
+
let mut out = [0u8; 64];
|
|
16
|
+
hex_encode_32(tag.as_ref(), &mut out);
|
|
17
|
+
|
|
18
|
+
Buffer::from(out.to_vec())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[napi]
|
|
22
|
+
pub fn hmac_sha256_verify(key: Uint8Array, data: Buffer, sig: Buffer) -> bool {
|
|
23
|
+
let sig = trim_ascii_whitespace(sig.as_ref());
|
|
24
|
+
|
|
25
|
+
if sig.len() != 64 {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let mut sig_bytes = [0u8; 32];
|
|
30
|
+
|
|
31
|
+
let mut i = 0;
|
|
32
|
+
while i < 32 {
|
|
33
|
+
let hi = match hex_val(sig[2 * i]) {
|
|
34
|
+
Some(v) => v,
|
|
35
|
+
None => return false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let lo = match hex_val(sig[2 * i + 1]) {
|
|
39
|
+
Some(v) => v,
|
|
40
|
+
None => return false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
sig_bytes[i] = (hi << 4) | lo;
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let key = hmac::Key::new(hmac::HMAC_SHA256, key.as_ref());
|
|
48
|
+
hmac::verify(&key, data.as_ref(), &sig_bytes).is_ok()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[napi]
|
|
52
|
+
pub struct HmacSigner {
|
|
53
|
+
key: hmac::Key,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[napi]
|
|
57
|
+
impl HmacSigner {
|
|
58
|
+
#[napi(constructor)]
|
|
59
|
+
pub fn new(key: Uint8Array) -> Self {
|
|
60
|
+
Self {
|
|
61
|
+
key: hmac::Key::new(hmac::HMAC_SHA256, key.as_ref()),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[napi]
|
|
66
|
+
pub fn sign(&self, data: Buffer) -> Buffer {
|
|
67
|
+
let tag = hmac::sign(&self.key, data.as_ref());
|
|
68
|
+
|
|
69
|
+
let mut out = [0u8; 64];
|
|
70
|
+
hex_encode_32(tag.as_ref(), &mut out);
|
|
71
|
+
|
|
72
|
+
Buffer::from(out.to_vec())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[napi]
|
|
76
|
+
pub fn verify(&self, data: Buffer, sig: Buffer) -> bool {
|
|
77
|
+
let sig = trim_ascii_whitespace(sig.as_ref());
|
|
78
|
+
|
|
79
|
+
if sig.len() != 64 {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let mut sig_bytes = [0u8; 32];
|
|
84
|
+
|
|
85
|
+
let mut i = 0;
|
|
86
|
+
|
|
87
|
+
while i < 32 {
|
|
88
|
+
let hi = match hex_val(sig[2 * i]) {
|
|
89
|
+
Some(v) => v,
|
|
90
|
+
None => return false,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let lo = match hex_val(sig[2 * i + 1]) {
|
|
94
|
+
Some(v) => v,
|
|
95
|
+
None => return false,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
sig_bytes[i] = (hi << 4) | lo;
|
|
99
|
+
i += 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
hmac::verify(&self.key, data.as_ref(), &sig_bytes).is_ok()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn hex_encode_32(bytes: &[u8], out: &mut [u8; 64]) {
|
|
107
|
+
debug_assert_eq!(bytes.len(), 32);
|
|
108
|
+
|
|
109
|
+
for (i, b) in bytes.iter().enumerate() {
|
|
110
|
+
out[2 * i] = HEX_LOWER[(b >> 4) as usize];
|
|
111
|
+
out[2 * i + 1] = HEX_LOWER[(b & 0x0f) as usize];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// rust/http_parser.rs
|
|
2
|
+
use crate::util::{write_bytes, write_u32_le, VecWriter};
|
|
3
|
+
use napi::bindgen_prelude::*;
|
|
4
|
+
use napi_derive::napi;
|
|
5
|
+
|
|
6
|
+
pub fn http_parse_request_packed_into_slice(input: &[u8], out: &mut [u8]) -> Result<usize> {
|
|
7
|
+
let mut headers = [httparse::EMPTY_HEADER; 64];
|
|
8
|
+
let mut req = httparse::Request::new(&mut headers);
|
|
9
|
+
|
|
10
|
+
match req.parse(input) {
|
|
11
|
+
Ok(httparse::Status::Complete(_)) => {}
|
|
12
|
+
Ok(_) => return Err(Error::new(Status::InvalidArg, "incomplete HTTP request")),
|
|
13
|
+
Err(e) => return Err(Error::new(Status::InvalidArg, e.to_string())),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let method = req
|
|
17
|
+
.method
|
|
18
|
+
.ok_or_else(|| Error::new(Status::InvalidArg, "missing HTTP method"))?
|
|
19
|
+
.as_bytes();
|
|
20
|
+
|
|
21
|
+
let path = req
|
|
22
|
+
.path
|
|
23
|
+
.ok_or_else(|| Error::new(Status::InvalidArg, "missing HTTP path"))?
|
|
24
|
+
.as_bytes();
|
|
25
|
+
|
|
26
|
+
let version: &[u8] = match req.version {
|
|
27
|
+
Some(0) => b"HTTP/1.0",
|
|
28
|
+
Some(1) => b"HTTP/1.1",
|
|
29
|
+
_ => b"HTTP/1.1",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let mut pos = 0usize;
|
|
33
|
+
|
|
34
|
+
write_u32_le(out, &mut pos, method.len() as u32)?;
|
|
35
|
+
write_bytes(out, &mut pos, method)?;
|
|
36
|
+
|
|
37
|
+
write_u32_le(out, &mut pos, path.len() as u32)?;
|
|
38
|
+
write_bytes(out, &mut pos, path)?;
|
|
39
|
+
|
|
40
|
+
write_u32_le(out, &mut pos, version.len() as u32)?;
|
|
41
|
+
write_bytes(out, &mut pos, version)?;
|
|
42
|
+
|
|
43
|
+
write_u32_le(out, &mut pos, req.headers.len() as u32)?;
|
|
44
|
+
|
|
45
|
+
for header in req.headers.iter() {
|
|
46
|
+
let name = header.name.as_bytes();
|
|
47
|
+
let value = header.value;
|
|
48
|
+
|
|
49
|
+
write_u32_le(out, &mut pos, name.len() as u32)?;
|
|
50
|
+
crate::util::write_bytes_lowercase(out, &mut pos, name)?;
|
|
51
|
+
|
|
52
|
+
write_u32_le(out, &mut pos, value.len() as u32)?;
|
|
53
|
+
crate::util::write_bytes(out, &mut pos, value)?;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Ok(pos)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Allocating, zero-copy-input, non-zeroing-output parser.
|
|
60
|
+
pub fn http_parse_request_packed_vec(input: &[u8]) -> Result<Vec<u8>> {
|
|
61
|
+
let mut headers = [httparse::EMPTY_HEADER; 64];
|
|
62
|
+
let mut req = httparse::Request::new(&mut headers);
|
|
63
|
+
|
|
64
|
+
match req.parse(input) {
|
|
65
|
+
Ok(httparse::Status::Complete(_)) => {}
|
|
66
|
+
Ok(_) => return Err(Error::new(Status::InvalidArg, "incomplete HTTP request")),
|
|
67
|
+
Err(e) => return Err(Error::new(Status::InvalidArg, e.to_string())),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let method = req
|
|
71
|
+
.method
|
|
72
|
+
.ok_or_else(|| Error::new(Status::InvalidArg, "missing HTTP method"))?
|
|
73
|
+
.as_bytes();
|
|
74
|
+
|
|
75
|
+
let path = req
|
|
76
|
+
.path
|
|
77
|
+
.ok_or_else(|| Error::new(Status::InvalidArg, "missing HTTP path"))?
|
|
78
|
+
.as_bytes();
|
|
79
|
+
|
|
80
|
+
let version: &[u8] = match req.version {
|
|
81
|
+
Some(0) => b"HTTP/1.0",
|
|
82
|
+
Some(1) => b"HTTP/1.1",
|
|
83
|
+
_ => b"HTTP/1.1",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Max 64 headers.
|
|
87
|
+
// Metadata overhead:
|
|
88
|
+
// 4 bytes method len
|
|
89
|
+
// 4 bytes path len
|
|
90
|
+
// 4 bytes version len
|
|
91
|
+
// 4 bytes header count
|
|
92
|
+
// 8 bytes per header name/value length prefix
|
|
93
|
+
//
|
|
94
|
+
// 16 + 64 * 8 = 528
|
|
95
|
+
let mut w = VecWriter::with_capacity(input.len().saturating_add(528));
|
|
96
|
+
|
|
97
|
+
w.write_u32(method.len() as u32);
|
|
98
|
+
w.write_bytes(method);
|
|
99
|
+
|
|
100
|
+
w.write_u32(path.len() as u32);
|
|
101
|
+
w.write_bytes(path);
|
|
102
|
+
|
|
103
|
+
w.write_u32(version.len() as u32);
|
|
104
|
+
w.write_bytes(version);
|
|
105
|
+
|
|
106
|
+
w.write_u32(req.headers.len() as u32);
|
|
107
|
+
|
|
108
|
+
for header in req.headers.iter() {
|
|
109
|
+
let name = header.name.as_bytes();
|
|
110
|
+
let value = header.value;
|
|
111
|
+
|
|
112
|
+
w.write_u32(name.len() as u32);
|
|
113
|
+
w.write_bytes_ascii_lowercase(name);
|
|
114
|
+
|
|
115
|
+
w.write_u32(value.len() as u32);
|
|
116
|
+
w.write_bytes(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Ok(w.into_bytes())
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[napi]
|
|
123
|
+
pub fn http_parse_request_packed(input: Uint8Array) -> Result<Buffer> {
|
|
124
|
+
Ok(Buffer::from(http_parse_request_packed_vec(
|
|
125
|
+
input.as_ref(),
|
|
126
|
+
)?))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[napi]
|
|
130
|
+
pub fn http_parse_request_packed_into(input: Uint8Array, mut output: Uint8Array) -> Result<u32> {
|
|
131
|
+
crate::util::run_packed_into(&input, &mut output, http_parse_request_packed_into_slice)
|
|
132
|
+
}
|