#include "net/nl_http.h" #include #include #include #include static const char *find(const char *buf, size_t len, const char *pat) { size_t n = strlen(pat); for (size_t i = 0; i + n <= len; i++) if (memcmp(buf + i, pat, n) == 0) return buf + i; return NULL; } /* Case-insensitive "name:" match at the start of a header line; returns the value start. */ static const char *header_value(const char *line, const char *line_end, const char *name) { size_t n = strlen(name); if ((size_t)(line_end - line) <= n || line[n] != ':') return NULL; for (size_t i = 0; i < n; i++) if (tolower((unsigned char)line[i]) != name[i]) return NULL; line += n + 1; while (line < line_end && (*line == ' ' || *line == '\t')) line++; return line; } nl_http_parse_t nl_http_parse(const char *buf, size_t len, nl_http_request_t *req) { const char *hdr_end = find(buf, len, "\r\n\r\n"); const char *p, *line_end, *sp1, *sp2; size_t header_len, content_length = 0, plen; if (!hdr_end) return len >= NL_HTTP_MAX_REQUEST ? NL_HTTP_TOO_LARGE : NL_HTTP_INCOMPLETE; memset(req, 0, sizeof *req); header_len = (size_t)(hdr_end + 4 - buf); /* request line: METHOD SP PATH SP HTTP/1.x */ line_end = find(buf, header_len, "\r\n"); sp1 = memchr(buf, ' ', (size_t)(line_end - buf)); if (!sp1) return NL_HTTP_BAD_REQUEST; sp2 = memchr(sp1 + 1, ' ', (size_t)(line_end - sp1 - 1)); if (!sp2 || (size_t)(line_end - sp2 - 1) < 8 || memcmp(sp2 + 1, "HTTP/1.", 7) != 0) return NL_HTTP_BAD_REQUEST; if (sp1 - buf == 3 && memcmp(buf, "GET", 3) == 0) req->method = NL_HTTP_GET; else if (sp1 - buf == 4 && memcmp(buf, "POST", 4) == 0) req->method = NL_HTTP_POST; else if (sp1 - buf == 3 && memcmp(buf, "PUT", 3) == 0) req->method = NL_HTTP_PUT; else req->method = NL_HTTP_OTHER; p = memchr(sp1 + 1, '?', (size_t)(sp2 - sp1 - 1)); plen = (size_t)((p ? p : sp2) - (sp1 + 1)); if (plen == 0 || plen >= sizeof req->path || sp1[1] != '/') return NL_HTTP_BAD_REQUEST; memcpy(req->path, sp1 + 1, plen); /* headers */ for (p = line_end + 2; p < hdr_end; p = line_end + 2) { const char *v; line_end = find(p, (size_t)(hdr_end + 2 - p), "\r\n"); if ((v = header_value(p, line_end, "content-length")) != NULL) { if (v == line_end) return NL_HTTP_BAD_REQUEST; content_length = 0; for (; v < line_end; v++) { if (!isdigit((unsigned char)*v)) return NL_HTTP_BAD_REQUEST; content_length = content_length * 10u + (size_t)(*v - '0'); if (content_length > NL_HTTP_MAX_BODY) return NL_HTTP_TOO_LARGE; } } else if ((v = header_value(p, line_end, "authorization")) != NULL) { size_t n; if (line_end - v > 7 && strncasecmp(v, "Bearer ", 7) == 0) v += 7; n = (size_t)(line_end - v); if (n >= sizeof req->auth) { req->auth_too_long = true; /* M4: never silently truncate a bearer token */ n = sizeof req->auth - 1; } memcpy(req->auth, v, n); req->auth[n] = '\0'; } } if (header_len + content_length > NL_HTTP_MAX_REQUEST) return NL_HTTP_TOO_LARGE; if (len < header_len + content_length) return NL_HTTP_INCOMPLETE; req->body = buf + header_len; req->body_len = content_length; return NL_HTTP_COMPLETE; } const char *nl_http_reason(int status) { switch (status) { case 200: return "OK"; case 202: return "Accepted"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 413: return "Payload Too Large"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 503: return "Service Unavailable"; default: return "Unknown"; } } size_t nl_http_response(char *out, size_t cap, int status, const char *json_body) { int n = snprintf(out, cap, "HTTP/1.1 %d %s\r\nContent-Type: application/json\r\nContent-Length: %u\r\n" "Connection: close\r\n\r\n%s", status, nl_http_reason(status), (unsigned)strlen(json_body), json_body); return (n < 0 || (size_t)n >= cap) ? 0 : (size_t)n; }