ohmu
·
2026-10-15
1#include "common/networking.h"
2#include "common/strutil.h"
3#include "common/debug.h"
4
5const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen) {
6 uint32_t nameLen = (uint32_t) strlen(name);
7 uint32_t pos = 0;
8
9 while (pos < headerEnd) {
10 uint32_t eol = pos;
11 while (eol < headerEnd && headers[eol] != '\n') {
12 ++eol;
13 }
14 uint32_t lineEnd = (eol > pos && headers[eol - 1] == '\r') ? (eol - 1) : eol;
15
16 // Only match at the beginning of a line, so that a value can never be mistaken
17 // for a field name (obs-fold continuation lines included)
18 if (lineEnd - pos >= nameLen && strncasecmp(headers + pos, name, nameLen) == 0) {
19 uint32_t valueStart = pos + nameLen;
20 while (valueStart < lineEnd && (headers[valueStart] == ' ' || headers[valueStart] == '\t')) {
21 ++valueStart;
22 }
23 *valueLen = lineEnd - valueStart;
24 return headers + valueStart;
25 }
26
27 if (eol >= headerEnd) {
28 break;
29 }
30 pos = eol + 1;
31 }
32
33 return nullptr;
34}
35
36FFNetworkingTransferEncoding ffNetworkingParseTransferEncoding(const char* value, uint32_t valueLen) {
37 // The value is a comma-separated list of transfer codings (RFC 9112 6.1)
38 uint32_t codingCount = 0;
39 const char* coding = nullptr;
40 uint32_t codingLen = 0;
41
42 for (uint32_t i = 0, start = 0; i <= valueLen; ++i) {
43 if (i < valueLen && value[i] != ',') {
44 continue;
45 }
46
47 // trim the optional whitespace around the coding
48 uint32_t from = start;
49 uint32_t to = i;
50 while (from < to && (value[from] == ' ' || value[from] == '\t')) {
51 ++from;
52 }
53 while (to > from && (value[to - 1] == ' ' || value[to - 1] == '\t')) {
54 --to;
55 }
56
57 if (to > from) {
58 ++codingCount;
59 coding = value + from;
60 codingLen = to - from;
61 }
62 start = i + 1;
63 }
64
65 if (codingCount == 0) {
66 return FF_NETWORKING_TE_NONE;
67 }
68
69 if (codingCount == 1 && codingLen == 7 && strncasecmp(coding, "chunked", 7) == 0) {
70 return FF_NETWORKING_TE_CHUNKED;
71 }
72
73 return FF_NETWORKING_TE_UNSUPPORTED;
74}
75
76int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* consumed) {
77 uint32_t pos = 0;
78
79 for (;;) {
80 // chunk-size [ chunk-ext ] CRLF
81 uint32_t eol = pos;
82 while (eol < bodyLen && body[eol] != '\n') {
83 ++eol;
84 }
85 if (eol >= bodyLen) {
86 return 0; // the chunk-size line is not complete yet
87 }
88
89 if (eol == pos || !isxdigit((unsigned char) body[pos])) {
90 return -1;
91 }
92
93 char* stop = nullptr;
94 unsigned long size = strtoul(body + pos, &stop, 16);
95 if (stop == body + pos) {
96 return -1;
97 }
98 pos = eol + 1; // skips the chunk extension, which ends at the CRLF
99
100 if (size == 0) {
101 // last-chunk, followed by an optional trailer section and an empty line
102 uint32_t p = pos;
103 while (p < bodyLen) {
104 uint32_t e = p;
105 while (e < bodyLen && body[e] != '\n') {
106 ++e;
107 }
108 if (e >= bodyLen) {
109 return 0;
110 }
111 if (e == p || (e == p + 1 && body[p] == '\r')) {
112 *consumed = e + 1;
113 return 1;
114 }
115 p = e + 1;
116 }
117 return 0;
118 }
119
120 if (size > (unsigned long) (bodyLen - pos)) {
121 return 0; // the chunk data is not complete yet
122 }
123 pos += (uint32_t) size;
124
125 if (bodyLen - pos < 2) {
126 return 0;
127 }
128 if (body[pos] != '\r' || body[pos + 1] != '\n') {
129 return -1;
130 }
131 pos += 2;
132 }
133}
134
135// Keeps the status line and every header except `dropHeader` and `Content-Length`,
136// then emits a `Content-Length` matching the (already decoded) body.
137// `body` may point into `buffer->chars`; it is copied before `buffer` is released.
138//
139// Returns the offset of the `\r` that opens the terminating CRLF CRLF of the new response. The
140// rebuild drops a header line and rewrites another, so the header it produces is shorter than the
141// one that went in: the offset the caller held before is no longer the one that describes this
142// buffer, and handing it on would point into the middle of the body.
143static uint32_t rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* body, uint32_t bodyLen, const char* dropHeader) {
144 FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerEnd + bodyLen + 64);
145
146 uint32_t pos = 0;
147 while (pos < headerEnd) {
148 uint32_t eol = pos;
149 while (eol < headerEnd && buffer->chars[eol] != '\n') {
150 ++eol;
151 }
152 uint32_t lineLen = (eol < headerEnd) ? (eol - pos + 1) : (headerEnd - pos);
153
154 if (!ffStrStartsWithIgnCase(buffer->chars + pos, "Content-Length:") &&
155 !ffStrStartsWithIgnCase(buffer->chars + pos, dropHeader)) {
156 ffStrbufAppendNS(&newBuffer, lineLen, buffer->chars + pos);
157 }
158
159 pos += lineLen;
160 }
161
162 ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n\r\n", bodyLen);
163 ffStrbufAppendNS(&newBuffer, bodyLen, body);
164
165 ffStrbufDestroy(buffer);
166 ffStrbufInitMove(buffer, &newBuffer);
167
168 // The terminating CRLF CRLF is the four bytes in front of the body
169 return buffer->length - bodyLen - 4;
170}
171
172bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t* headerEnd) {
173 assert(buffer->allocated > 0);
174
175 // Held in a local because the rebuild below hands a different value back
176 const uint32_t headerLength = *headerEnd;
177
178 // The header block is terminated by CR LF CR LF
179 if (headerLength + 4 > buffer->length) {
180 return false;
181 }
182
183 char* body = buffer->chars + headerLength + 4;
184 uint32_t bodyLen = buffer->length - headerLength - 4;
185
186 uint32_t consumed = 0;
187 if (ffNetworkingChunkedComplete(body, bodyLen, &consumed) != 1) {
188 FF_DEBUG("Incomplete or malformed chunked body");
189 return false;
190 }
191
192 // Decoding in place is safe: the encoded form is never shorter than the payload
193 char* out = body;
194 uint32_t outLen = 0;
195 uint32_t pos = 0;
196
197 while (pos < bodyLen) {
198 uint32_t eol = pos;
199 while (eol < bodyLen && body[eol] != '\n') {
200 ++eol;
201 }
202
203 unsigned long size = strtoul(body + pos, nullptr, 16);
204 pos = eol + 1;
205 if (size == 0) {
206 break; // last-chunk; trailers are dropped
207 }
208
209 memmove(out + outLen, body + pos, size);
210 outLen += (uint32_t) size;
211 pos += (uint32_t) size + 2; // trailing CRLF
212 }
213
214 FF_DEBUG("Decoded chunked body: %u bytes encoded, %u bytes decoded", bodyLen, outLen);
215 *headerEnd = rebuildResponse(buffer, headerLength, out, outLen, "Transfer-Encoding:");
216 return true;
217}