// // Copyright 2025 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this // file was obtained (LICENSE.txt). A copy of the license may also be // found online at https://opensource.org/licenses/MIT. // #include #include #include #include #include #include "core/list.h" #include "core/nng_impl.h" #include "http_api.h" #include "http_msg.h" #include "nng/http.h" void nni_http_free_header(http_header *h) { nni_list_node_remove(&h->node); if (!h->static_name) { nni_strfree(h->name); h->name = NULL; } if (!h->static_value) { nni_strfree(h->value); h->value = NULL; } if (h->alloc_header) { NNI_FREE_STRUCT(h); } } static void http_headers_reset(nni_list *hdrs) { http_header *h; while ((h = nni_list_first(hdrs)) != NULL) { nni_http_free_header(h); } } static void http_entity_reset(nni_http_entity *entity) { if (entity->own && entity->size) { nni_free(entity->data, entity->size); } http_headers_reset(&entity->hdrs); nni_free(entity->buf, entity->bufsz); entity->data = NULL; entity->size = 0; entity->own = false; entity->parsed = false; entity->buf = NULL; entity->bufsz = 0; } void nni_http_req_reset(nni_http_req *req) { http_entity_reset(&req->data); if (req->uri != req->ubuf) { nni_strfree(req->uri); } req->uri = NULL; (void) snprintf(req->meth, sizeof(req->meth), "GET"); } void nni_http_res_reset(nni_http_res *res) { http_entity_reset(&res->data); nni_strfree(res->rsn); res->vers = NNG_HTTP_VERSION_1_1; res->rsn = NULL; res->code = 0; } // http_entity_set_data sets the entity, but does not update the // content-length header. static void http_entity_set_data(nni_http_entity *entity, const void *data, size_t size) { if (entity->own) { nni_free(entity->data, entity->size); } entity->data = (void *) data; entity->size = size; entity->own = false; } static int http_entity_alloc_data(nni_http_entity *entity, size_t size) { void *newdata; if (size != 0) { if ((newdata = nni_zalloc(size)) == NULL) { return (NNG_ENOMEM); } } http_entity_set_data(entity, newdata, size); entity->own = true; return (0); } int nni_http_req_alloc_data(nni_http_req *req, size_t size) { int rv; if ((rv = http_entity_alloc_data(&req->data, size)) != 0) { return (rv); } return (0); } // nni_http_res_alloc_data allocates the data region, but does not update any // headers. The intended use is for client implementations that want to // allocate a buffer to receive the entity into. int nni_http_res_alloc_data(nni_http_res *res, size_t size) { int rv; if ((rv = http_entity_alloc_data(&res->data, size)) != 0) { return (rv); } return (0); } bool nni_http_res_is_error(nni_http_res *res) { return (res->iserr); } static int http_parse_header(nng_http *conn, void *line) { char *key = line; char *val; char *end; // Find separation between key and value if ((val = strchr(key, ':')) == NULL) { return (NNG_EPROTO); } // Trim leading and trailing whitespace from header *val = '\0'; val++; while (*val == ' ' || *val == '\t') { val++; } end = val + strlen(val); end--; while ((end > val) && (*end == ' ' || *end == '\t')) { *end = '\0'; end--; } return (nni_http_add_header(conn, key, val)); } // http_sprintf_headers makes headers for an HTTP request or an HTTP response // object. Each header is dumped from the list. If the buf is NULL, // or the sz is 0, then a dryrun is done, in order to allow the caller to // determine how much space is needed. Returns the size of the space needed, // not including the terminating NULL byte. Truncation occurs if the size // returned is >= the requested size. static size_t http_sprintf_headers(char *buf, size_t sz, nni_list *list) { size_t rv = 0; http_header *h; if (buf == NULL) { sz = 0; } NNI_LIST_FOREACH (list, h) { size_t l; l = snprintf(buf, sz, "%s: %s\r\n", h->name, h->value); if (buf != NULL) { buf += l; } sz = (sz > l) ? sz - l : 0; rv += l; } return (rv); } static int http_asprintf(char **bufp, size_t *szp, nni_list *hdrs, const char *fmt, ...) { va_list ap; size_t len; size_t n; char *buf; va_start(ap, fmt); len = vsnprintf(NULL, 0, fmt, ap); va_end(ap); len += http_sprintf_headers(NULL, 0, hdrs); len += 3; // \r\n\0 if (len <= *szp) { buf = *bufp; } else { if ((buf = nni_alloc(len)) == NULL) { return (NNG_ENOMEM); } nni_free(*bufp, *szp); *bufp = buf; *szp = len; } va_start(ap, fmt); n = vsnprintf(buf, len, fmt, ap); va_end(ap); buf += n; len -= n; n = http_sprintf_headers(buf, len, hdrs); buf += n; len -= n; snprintf(buf, len, "\r\n"); NNI_ASSERT(len == 3); return (0); } static int http_req_prepare(nni_http_req *req) { int rv; if (req->uri == NULL) { return (NNG_EINVAL); } rv = http_asprintf(&req->data.buf, &req->data.bufsz, &req->data.hdrs, "%s %s %s\r\n", req->meth, req->uri, req->vers); return (rv); } static int http_res_prepare(nng_http *conn) { int rv; nng_http_res *res = nni_http_conn_res(conn); if (res->code == 0) { res->code = NNG_HTTP_STATUS_OK; } rv = http_asprintf(&res->data.buf, &res->data.bufsz, &res->data.hdrs, "%s %d %s\r\n", res->vers, res->code, nni_http_get_reason(conn)); return (rv); } int nni_http_req_get_buf(nni_http_req *req, void **data, size_t *szp) { int rv; if ((req->data.buf == NULL) && (rv = http_req_prepare(req)) != 0) { return (rv); } *data = req->data.buf; *szp = req->data.bufsz - 1; // exclude terminating NUL return (0); } int nni_http_res_get_buf(nng_http *conn, void **data, size_t *szp) { int rv; nni_http_res *res = nni_http_conn_res(conn); if ((res->data.buf == NULL) && (rv = http_res_prepare(conn)) != 0) { return (rv); } *data = res->data.buf; *szp = res->data.bufsz - 1; // exclude terminating NUL return (0); } void nni_http_req_init(nni_http_req *req) { NNI_LIST_INIT(&req->data.hdrs, http_header, node); req->data.buf = NULL; req->data.bufsz = 0; req->data.data = NULL; req->data.size = 0; req->data.own = false; req->uri = NULL; (void) snprintf(req->meth, sizeof(req->meth), "GET"); } void nni_http_res_init(nni_http_res *res) { NNI_LIST_INIT(&res->data.hdrs, http_header, node); res->data.buf = NULL; res->data.bufsz = 0; res->data.data = NULL; res->data.size = 0; res->data.own = false; res->vers = NNG_HTTP_VERSION_1_1; res->rsn = NULL; res->code = 0; } static int http_scan_line(void *vbuf, size_t n, size_t *lenp) { size_t len; char lc; char *buf = vbuf; lc = 0; for (len = 0; len < n; len++) { char c = buf[len]; if (c == '\n') { // Technically we should be receiving CRLF, but // debugging is easier with just LF, so we behave // following Postel's Law. if (lc != '\r') { buf[len] = '\0'; } else { buf[len - 1] = '\0'; } *lenp = len + 1; return (0); } // If we have a control character (other than CR), or a CR // followed by anything other than LF, then its an error. if (((c < ' ') && (c != '\r')) || (lc == '\r')) { return (NNG_EPROTO); } lc = c; } // Scanned the entire content, but did not find a line. return (NNG_EAGAIN); } static int http_req_parse_line(nng_http *conn, void *line) { int rv; char *method; char *uri; char *version; method = line; if ((uri = strchr(method, ' ')) == NULL) { return (NNG_EPROTO); } *uri = '\0'; uri++; if ((version = strchr(uri, ' ')) == NULL) { return (NNG_EPROTO); } *version = '\0'; version++; nni_http_set_method(conn, method); if (((rv = nni_http_set_uri(conn, uri, NULL)) != 0) || ((rv = nni_http_set_version(conn, version)) != 0)) { return (rv); } return (0); } static int http_res_parse_line(nng_http *conn, uint8_t *line) { int rv; char *reason; char *codestr; char *version; int status; version = (char *) line; if ((codestr = strchr(version, ' ')) == NULL) { return (NNG_EPROTO); } *codestr = '\0'; codestr++; if ((reason = strchr(codestr, ' ')) == NULL) { return (NNG_EPROTO); } *reason = '\0'; reason++; status = atoi(codestr); if ((status < 100) || (status > 999)) { return (NNG_EPROTO); } if ((rv = nni_http_set_status(conn, (uint16_t) status, reason)) != 0) { return (rv); } if ((rv = nni_http_set_version(conn, version)) != 0) { return (rv); } return (0); } // nni_http_req_parse parses a request (but not any attached entity data). // The amount of data consumed is returned in lenp. Returns zero on // success, NNG_EPROTO on parse failure, NNG_EAGAIN if more data is // required, or NNG_ENOMEM on memory exhaustion. Note that lenp may // be updated even in the face of errors (esp. NNG_EAGAIN, which is // not an error so much as a request for more data.) int nni_http_req_parse(nng_http *conn, void *buf, size_t n, size_t *lenp) { size_t len = 0; size_t cnt; int rv = 0; nni_http_req *req = nni_http_conn_req(conn); for (;;) { uint8_t *line; if ((rv = http_scan_line(buf, n, &cnt)) != 0) { break; } len += cnt; line = buf; buf = line + cnt; n -= cnt; if (*line == '\0') { break; } if (req->data.parsed) { rv = http_parse_header(conn, line); } else if ((rv = http_req_parse_line(conn, line)) == 0) { req->data.parsed = true; } if (rv != 0) { break; } } if (rv == 0) { req->data.parsed = false; } *lenp = len; return (rv); } int nni_http_res_parse(nng_http *conn, void *buf, size_t n, size_t *lenp) { size_t len = 0; size_t cnt; int rv = 0; nng_http_res *res = nni_http_conn_res(conn); for (;;) { uint8_t *line; if ((rv = http_scan_line(buf, n, &cnt)) != 0) { break; } len += cnt; line = buf; buf = line + cnt; n -= cnt; if (*line == '\0') { break; } if (res->data.parsed) { rv = http_parse_header(conn, line); } else if ((rv = http_res_parse_line(conn, line)) == 0) { res->data.parsed = true; } if (rv != 0) { break; } } if (rv == 0) { res->data.parsed = false; } *lenp = len; return (rv); }