master ohmu/fastfetch-openbsd / src / common / impl / networking_common.c
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}