DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
http_delivery.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file http_delivery.cpp
6 * @brief HTTP delivery optimizations (see http_delivery.h).
7 */
8
10
11#if DETWS_ENABLE_HTTP_DELIVERY
12
13#include <string.h>
14
15namespace
16{
17// Parse a run of decimal digits at *p (advancing it); write *out. Returns false on no digit or
18// > 10 digits (uint32 overflow guard).
19bool read_u32(const char **p, uint32_t *out)
20{
21 const char *s = *p;
22 if (*s < '0' || *s > '9')
23 return false; // GCOVR_EXCL_LINE both call sites pre-check *p is a digit before calling, so this never fires
24 uint64_t v = 0;
25 int n = 0;
26 while (*s >= '0' && *s <= '9')
27 {
28 v = v * 10 + (uint32_t)(*s - '0');
29 s++;
30 n++;
31 if (n > 10 || v > 0xFFFFFFFFu)
32 return false;
33 }
34 *p = s;
35 *out = (uint32_t)v;
36 return true;
37}
38
39struct Buf
40{
41 char *p;
42 size_t cap;
43 size_t len;
44 bool ok;
45};
46
47void put(Buf *b, const char *s)
48{
49 if (!b->ok)
50 return;
51 size_t sl = strnlen(s, b->cap + 1);
52 if (b->len + sl >= b->cap)
53 {
54 b->ok = false;
55 return;
56 }
57 memcpy(b->p + b->len, s, sl);
58 b->len += sl;
59}
60
61void put_u32(Buf *b, uint32_t v)
62{
63 char t[10];
64 int n = 0;
65 do
66 {
67 t[n++] = (char)('0' + v % 10);
68 v /= 10;
69 } while (v);
70 char o[11];
71 for (int i = 0; i < n; i++)
72 o[i] = t[n - 1 - i];
73 o[n] = '\0';
74 put(b, o);
75}
76
77void put_json_str(Buf *b, const char *s)
78{
79 put(b, "\"");
80 for (const char *p = s ? s : ""; *p; p++)
81 {
82 if (*p == '"' || *p == '\\')
83 {
84 char esc[3] = {'\\', *p, '\0'};
85 put(b, esc);
86 }
87 else if (b->len + 1 < b->cap)
88 b->p[b->len++] = *p;
89 else
90 b->ok = false;
91 }
92 put(b, "\"");
93}
94
95// Parse "bytes=<s>-<e>" (either bound optional) from a Range header. Fills the out-params and returns
96// true, or returns false for a malformed / multi-range / non-bytes header. *have_start / *have_end flag
97// which bounds were present; resolving them against the resource size is the caller's job.
98bool parse_range_spec(const char *range_header, bool *have_start, bool *have_end, uint32_t *s, uint32_t *e)
99{
100 const char *p = range_header;
101 while (*p == ' ' || *p == '\t')
102 p++;
103 if (strncmp(p, "bytes=", 6) != 0)
104 return false;
105 p += 6;
106 if (strchr(p, ',')) // multi-range: unsupported
107 return false;
108
109 *have_start = false;
110 *have_end = false;
111 *s = 0;
112 *e = 0;
113 if (*p >= '0' && *p <= '9')
114 {
115 if (!read_u32(&p, s))
116 return false;
117 *have_start = true;
118 }
119 if (*p != '-')
120 return false;
121 p++;
122 if (*p >= '0' && *p <= '9')
123 {
124 if (!read_u32(&p, e))
125 return false;
126 *have_end = true;
127 }
128 while (*p == ' ' || *p == '\t')
129 p++;
130 if (*p != '\0' && *p != '\r' && *p != '\n')
131 return false;
132 return true;
133}
134} // namespace
135
136DeliveryVerdict detws_delivery_swr(uint32_t age_s, uint32_t max_age_s, uint32_t swr_s)
137{
138 if (age_s <= max_age_s)
139 return DeliveryVerdict::DELIVERY_FRESH;
140 uint64_t window = (uint64_t)max_age_s + swr_s;
141 if ((uint64_t)age_s <= window)
142 return DeliveryVerdict::DELIVERY_STALE_REVALIDATE;
143 return DeliveryVerdict::DELIVERY_EXPIRED;
144}
145
146size_t detws_delivery_cache_control(uint32_t max_age_s, uint32_t swr_s, char *out, size_t cap)
147{
148 if (!out || cap == 0)
149 return 0;
150 Buf b = {out, cap, 0, true};
151 put(&b, "public, max-age=");
152 put_u32(&b, max_age_s);
153 if (swr_s)
154 {
155 put(&b, ", stale-while-revalidate=");
156 put_u32(&b, swr_s);
157 }
158 if (!b.ok)
159 return 0;
160 out[b.len] = '\0';
161 return b.len;
162}
163
164int detws_delivery_range(const char *range_header, uint32_t total, uint32_t *start, uint32_t *end)
165{
166 if (!range_header || !start || !end || total == 0)
167 return 0;
168
169 bool have_start = false;
170 bool have_end = false;
171 uint32_t s = 0;
172 uint32_t e = 0;
173 if (!parse_range_spec(range_header, &have_start, &have_end, &s, &e))
174 return 0;
175
176 uint32_t rs = 0;
177 uint32_t re = 0;
178 if (!have_start)
179 {
180 if (!have_end || e == 0) // "bytes=-" or "bytes=-0" are unsatisfiable
181 return 0;
182 rs = (e >= total) ? 0 : (total - e); // last e bytes
183 re = total - 1;
184 }
185 else
186 {
187 rs = s;
188 if (rs >= total) // 416: start past end of resource
189 return 0;
190 re = have_end ? e : (total - 1);
191 if (re >= total)
192 re = total - 1;
193 if (rs > re)
194 return 0;
195 }
196 *start = rs;
197 *end = re;
198 return 1;
199}
200
201size_t detws_delivery_content_range(uint32_t start, uint32_t end, uint32_t total, char *out, size_t cap)
202{
203 if (!out || cap == 0)
204 return 0;
205 Buf b = {out, cap, 0, true};
206 put(&b, "bytes ");
207 put_u32(&b, start);
208 put(&b, "-");
209 put_u32(&b, end);
210 put(&b, "/");
211 put_u32(&b, total);
212 if (!b.ok)
213 return 0;
214 out[b.len] = '\0';
215 return b.len;
216}
217
218size_t detws_delivery_sw_manifest(const char *const *paths, size_t n, const char *version, char *out, size_t cap)
219{
220 if (!out || cap == 0 || (n && !paths))
221 return 0;
222 Buf b = {out, cap, 0, true};
223 put(&b, "{\"version\":");
224 put_json_str(&b, version ? version : "");
225 put(&b, ",\"precache\":[");
226 for (size_t i = 0; i < n; i++)
227 {
228 if (i)
229 put(&b, ",");
230 put_json_str(&b, paths[i]);
231 }
232 put(&b, "]}");
233 if (!b.ok)
234 return 0;
235 out[b.len] = '\0';
236 return b.len;
237}
238
239#endif // DETWS_ENABLE_HTTP_DELIVERY
HTTP delivery optimizations: stale-while-revalidate, Range/206 delta fetch, SW precache (DETWS_ENABLE...