Skip to main content

simoom_http1/
write.rs

1//! Serialization: the cached `Date`, pre-serialized heads, a head writer and the chunked
2//! encoder. Everything writes into caller-owned buffers or returns small stack values, so a
3//! response can go out as one `writev`: head, body, and for chunked bodies the chunk header and
4//! the CRLF around each piece.
5
6use crate::Error;
7use simoom_sys::scan::{scan, FIELD_VALUE, TCHAR};
8
9/// `HTTP/1.1 100 Continue` with its empty line.
10pub const CONTINUE: &[u8] = b"HTTP/1.1 100 Continue\r\n\r\n";
11/// The CRLF that ends each chunk's data.
12pub const CRLF: &[u8] = b"\r\n";
13/// The last chunk with no trailers.
14pub const LAST_CHUNK: &[u8] = b"0\r\n\r\n";
15
16const DAYS: [&[u8; 3]; 7] = [b"Thu", b"Fri", b"Sat", b"Sun", b"Mon", b"Tue", b"Wed"];
17const MONTHS: [&[u8; 3]; 12] =
18    [b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec"];
19
20fn two(out: &mut [u8], n: u64) {
21    out[0] = b'0' + (n / 10) as u8;
22    out[1] = b'0' + (n % 10) as u8;
23}
24
25/// The IMF-fixdate for a Unix time, e.g. `Sun, 06 Nov 1994 08:49:37 GMT` (RFC 9110 ยง5.6.7).
26pub fn http_date(unix_secs: u64) -> [u8; 29] {
27    let days = unix_secs / 86_400;
28    let rem = unix_secs % 86_400;
29    // Civil date from days since 1970-01-01 (H. Hinnant's algorithm).
30    let z = days as i64 + 719_468;
31    let era = z.div_euclid(146_097);
32    let doe = z.rem_euclid(146_097);
33    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
34    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
35    let mp = (5 * doy + 2) / 153;
36    let day = (doy - (153 * mp + 2) / 5 + 1) as u64;
37    let month = if mp < 10 { mp + 3 } else { mp - 9 } as usize;
38    let year = (yoe + era * 400 + i64::from(month <= 2)) as u64;
39    let mut b = *b"Thu, 01 Jan 1970 00:00:00 GMT";
40    b[..3].copy_from_slice(DAYS[(days % 7) as usize]);
41    two(&mut b[5..7], day);
42    b[8..11].copy_from_slice(MONTHS[month - 1]);
43    two(&mut b[12..14], (year / 100) % 100);
44    two(&mut b[14..16], year % 100);
45    two(&mut b[17..19], rem / 3600);
46    two(&mut b[20..22], rem / 60 % 60);
47    two(&mut b[23..25], rem % 60);
48    b
49}
50
51/// The `Date` value, formatted once per second. The caller owns the clock: call
52/// [`update`](DateCache::update) with the current Unix time, typically once per loop tick.
53#[derive(Clone, Copy, Debug)]
54pub struct DateCache {
55    secs: u64,
56    buf: [u8; 29],
57}
58
59impl DateCache {
60    /// A cache holding the date for `unix_secs`.
61    pub fn new(unix_secs: u64) -> Self {
62        DateCache { secs: unix_secs, buf: http_date(unix_secs) }
63    }
64
65    /// Moves to `unix_secs`; formats only when the second changed. Returns true if it did.
66    pub fn update(&mut self, unix_secs: u64) -> bool {
67        if unix_secs == self.secs {
68            return false;
69        }
70        *self = DateCache::new(unix_secs);
71        true
72    }
73
74    /// The formatted date (29 bytes).
75    pub fn bytes(&self) -> &[u8; 29] {
76        &self.buf
77    }
78
79    /// The Unix second it holds.
80    pub fn secs(&self) -> u64 {
81        self.secs
82    }
83}
84
85/// The reason phrase for a status code (empty for codes without one).
86pub fn reason(status: u16) -> &'static str {
87    match status {
88        100 => "Continue",
89        101 => "Switching Protocols",
90        200 => "OK",
91        201 => "Created",
92        202 => "Accepted",
93        204 => "No Content",
94        206 => "Partial Content",
95        301 => "Moved Permanently",
96        302 => "Found",
97        303 => "See Other",
98        304 => "Not Modified",
99        307 => "Temporary Redirect",
100        308 => "Permanent Redirect",
101        400 => "Bad Request",
102        401 => "Unauthorized",
103        403 => "Forbidden",
104        404 => "Not Found",
105        405 => "Method Not Allowed",
106        406 => "Not Acceptable",
107        408 => "Request Timeout",
108        409 => "Conflict",
109        411 => "Length Required",
110        413 => "Content Too Large",
111        414 => "URI Too Long",
112        415 => "Unsupported Media Type",
113        417 => "Expectation Failed",
114        426 => "Upgrade Required",
115        429 => "Too Many Requests",
116        431 => "Request Header Fields Too Large",
117        500 => "Internal Server Error",
118        501 => "Not Implemented",
119        502 => "Bad Gateway",
120        503 => "Service Unavailable",
121        504 => "Gateway Timeout",
122        505 => "HTTP Version Not Supported",
123        _ => "",
124    }
125}
126
127/// Decimal digits of `n` in a stack buffer.
128fn decimal(n: u64, buf: &mut [u8; 20]) -> &[u8] {
129    let mut i = 20;
130    let mut n = n;
131    loop {
132        i -= 1;
133        buf[i] = b'0' + (n % 10) as u8;
134        n /= 10;
135        if n == 0 {
136            return &buf[i..];
137        }
138    }
139}
140
141fn check_field(name: &[u8], value: &[u8]) -> Result<(), Error> {
142    if name.is_empty() || scan(name, &TCHAR) != name.len() {
143        return Err(Error::InvalidHeaderName);
144    }
145    if name.eq_ignore_ascii_case(b"content-length") || name.eq_ignore_ascii_case(b"transfer-encoding") {
146        return Err(Error::FramingHeader);
147    }
148    if scan(value, &FIELD_VALUE) != value.len() {
149        return Err(Error::InvalidHeaderValue);
150    }
151    Ok(())
152}
153
154/// Writes a message head into a caller-owned buffer (reuse it to avoid allocation). Field names
155/// and values are validated, so a value cannot split the response; framing goes through
156/// [`content_length`](HeadWriter::content_length) and [`chunked`](HeadWriter::chunked) only.
157#[derive(Debug)]
158pub struct HeadWriter<'v> {
159    out: &'v mut Vec<u8>,
160}
161
162impl<'v> HeadWriter<'v> {
163    /// Starts a response: `HTTP/1.1 <status> <reason>`. `status` must be 100 to 999.
164    pub fn response(out: &'v mut Vec<u8>, status: u16) -> Self {
165        let status = status.clamp(100, 999);
166        let mut d = [0u8; 20];
167        out.extend_from_slice(b"HTTP/1.1 ");
168        out.extend_from_slice(decimal(u64::from(status), &mut d));
169        out.push(b' ');
170        out.extend_from_slice(reason(status).as_bytes());
171        out.extend_from_slice(CRLF);
172        HeadWriter { out }
173    }
174
175    /// Starts a request: `<method> <target> HTTP/1.1`.
176    pub fn request(out: &'v mut Vec<u8>, method: &[u8], target: &[u8]) -> Result<Self, Error> {
177        if method.is_empty() || scan(method, &TCHAR) != method.len() {
178            return Err(Error::InvalidMethod);
179        }
180        if target.is_empty() || target.iter().any(|&b| b <= b' ' || b >= 0x7f) {
181            return Err(Error::InvalidTarget);
182        }
183        out.extend_from_slice(method);
184        out.push(b' ');
185        out.extend_from_slice(target);
186        out.extend_from_slice(b" HTTP/1.1\r\n");
187        Ok(HeadWriter { out })
188    }
189
190    /// Adds a field.
191    pub fn header(&mut self, name: &[u8], value: &[u8]) -> Result<&mut Self, Error> {
192        check_field(name, value)?;
193        self.out.extend_from_slice(name);
194        self.out.extend_from_slice(b": ");
195        self.out.extend_from_slice(value);
196        self.out.extend_from_slice(CRLF);
197        Ok(self)
198    }
199
200    /// Adds `Date` from the cache.
201    pub fn date(&mut self, date: &DateCache) -> &mut Self {
202        self.out.extend_from_slice(b"Date: ");
203        self.out.extend_from_slice(date.bytes());
204        self.out.extend_from_slice(CRLF);
205        self
206    }
207
208    /// Adds `Content-Length`.
209    pub fn content_length(&mut self, n: u64) -> &mut Self {
210        let mut d = [0u8; 20];
211        self.out.extend_from_slice(b"Content-Length: ");
212        self.out.extend_from_slice(decimal(n, &mut d));
213        self.out.extend_from_slice(CRLF);
214        self
215    }
216
217    /// Adds `Transfer-Encoding: chunked`.
218    pub fn chunked(&mut self) -> &mut Self {
219        self.out.extend_from_slice(b"Transfer-Encoding: chunked\r\n");
220        self
221    }
222
223    /// Adds `Connection: close`.
224    pub fn close(&mut self) -> &mut Self {
225        self.out.extend_from_slice(b"Connection: close\r\n");
226        self
227    }
228
229    /// Ends the head with the empty line.
230    pub fn finish(self) {
231        self.out.extend_from_slice(CRLF);
232    }
233}
234
235/// A response serialized once, with a `Date` slot patched at most once a second: the fast path
236/// for fixed replies. The body (possibly empty) follows the head in the same buffer, so the
237/// whole reply is one slice.
238#[derive(Clone, Debug)]
239pub struct StaticHead {
240    bytes: Vec<u8>,
241    date_at: usize,
242    head_len: usize,
243    secs: u64,
244}
245
246impl StaticHead {
247    /// Serializes `status`, `headers`, `Content-Length: body.len()`, a `Date` slot and the body.
248    pub fn new(status: u16, headers: &[(&[u8], &[u8])], body: &[u8]) -> Result<Self, Error> {
249        let mut bytes = Vec::with_capacity(128 + body.len());
250        let mut w = HeadWriter::response(&mut bytes, status);
251        for &(n, v) in headers {
252            w.header(n, v)?;
253        }
254        w.content_length(body.len() as u64);
255        w.out.extend_from_slice(b"Date: ");
256        let date_at = w.out.len();
257        w.date_slot();
258        w.finish();
259        let head_len = bytes.len();
260        bytes.extend_from_slice(body);
261        Ok(StaticHead { bytes, date_at, head_len, secs: u64::MAX })
262    }
263
264    fn patch(&mut self, date: &DateCache) {
265        if self.secs != date.secs() {
266            self.bytes[self.date_at..self.date_at + 29].copy_from_slice(date.bytes());
267            self.secs = date.secs();
268        }
269    }
270
271    /// The whole reply, head and body.
272    pub fn full(&mut self, date: &DateCache) -> &[u8] {
273        self.patch(date);
274        &self.bytes
275    }
276
277    /// The head alone (for HEAD requests).
278    pub fn head(&mut self, date: &DateCache) -> &[u8] {
279        self.patch(date);
280        &self.bytes[..self.head_len]
281    }
282}
283
284impl HeadWriter<'_> {
285    fn date_slot(&mut self) {
286        self.out.extend_from_slice(&[b' '; 29]);
287        self.out.extend_from_slice(CRLF);
288    }
289}
290
291/// A chunk header, `<hex length>\r\n`, on the stack.
292#[derive(Clone, Copy, Debug)]
293pub struct ChunkHeader {
294    buf: [u8; 18],
295    start: u8,
296}
297
298impl ChunkHeader {
299    /// The header for a chunk of `len` bytes (`len` must not be 0: that is the last chunk).
300    pub fn new(len: usize) -> Self {
301        let mut buf = [0u8; 18];
302        buf[16] = b'\r';
303        buf[17] = b'\n';
304        let mut i = 16;
305        let mut n = len as u64;
306        loop {
307            i -= 1;
308            buf[i] = b"0123456789abcdef"[(n & 15) as usize];
309            n >>= 4;
310            if n == 0 {
311                break;
312            }
313        }
314        ChunkHeader { buf, start: i as u8 }
315    }
316
317    /// The header bytes.
318    pub fn as_bytes(&self) -> &[u8] {
319        &self.buf[self.start as usize..]
320    }
321}
322
323/// Appends one chunk (header, data, CRLF). Empty data writes nothing, since an empty chunk
324/// would end the body.
325pub fn write_chunk(out: &mut Vec<u8>, data: &[u8]) {
326    if data.is_empty() {
327        return;
328    }
329    out.extend_from_slice(ChunkHeader::new(data.len()).as_bytes());
330    out.extend_from_slice(data);
331    out.extend_from_slice(CRLF);
332}
333
334/// Appends the last chunk, the trailer fields and the final empty line.
335pub fn write_last_chunk(out: &mut Vec<u8>, trailers: &[(&[u8], &[u8])]) -> Result<(), Error> {
336    for &(n, v) in trailers {
337        check_field(n, v)?;
338        if n.eq_ignore_ascii_case(b"host") {
339            return Err(Error::ForbiddenTrailer);
340        }
341    }
342    out.extend_from_slice(b"0\r\n");
343    for &(n, v) in trailers {
344        out.extend_from_slice(n);
345        out.extend_from_slice(b": ");
346        out.extend_from_slice(v);
347        out.extend_from_slice(CRLF);
348    }
349    out.extend_from_slice(CRLF);
350    Ok(())
351}