diff options
Diffstat (limited to 'src/supplemental/http/http_conn.c')
| -rw-r--r-- | src/supplemental/http/http_conn.c | 744 |
1 files changed, 702 insertions, 42 deletions
diff --git a/src/supplemental/http/http_conn.c b/src/supplemental/http/http_conn.c index 9ed882f1..20ae2f05 100644 --- a/src/supplemental/http/http_conn.c +++ b/src/supplemental/http/http_conn.c @@ -1,5 +1,5 @@ // -// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2025 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> // Copyright 2019 Devolutions <info@devolutions.net> // @@ -11,9 +11,12 @@ #include <ctype.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> +#include "core/list.h" #include "core/nng_impl.h" +#include "nng/http.h" #include "supplemental/tls/tls_api.h" #include "http_api.h" @@ -62,20 +65,20 @@ struct nng_http_conn { size_t rd_put; size_t rd_bufsz; bool rd_buffered; - - bool res_sent; + bool client; // true if this is a client's connection + bool res_sent; enum write_flavor wr_flavor; }; nng_http_req * -nni_http_conn_req(nng_http_conn *conn) +nni_http_conn_req(nng_http *conn) { return (&conn->req); } nng_http_res * -nni_http_conn_res(nng_http_conn *conn) +nni_http_conn_res(nng_http *conn) { return (&conn->res); } @@ -138,6 +141,20 @@ nni_http_conn_close(nni_http_conn *conn) nni_mtx_unlock(&conn->mtx); } +// http_rd_buf_pull_up pulls the content of the read buffer back to the +// beginning, so that the next read can go at the end. This avoids the problem +// of dealing with a read that might wrap. +static void +http_rd_buf_pull_up(nni_http_conn *conn) +{ + if (conn->rd_get != 0) { + memmove(conn->rd_buf, conn->rd_buf + conn->rd_get, + conn->rd_put - conn->rd_get); + conn->rd_put -= conn->rd_get; + conn->rd_get = 0; + } +} + // http_rd_buf attempts to satisfy the read from data in the buffer. static int http_rd_buf(nni_http_conn *conn, nni_aio *aio) @@ -196,34 +213,44 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) return (NNG_EAGAIN); case HTTP_RD_REQ: - rv = nni_http_req_parse( - nni_aio_get_prov_data(aio), rbuf, cnt, &n); + conn->client = true; + rv = nni_http_req_parse(conn, rbuf, cnt, &n); + conn->client = false; conn->rd_get += n; if (conn->rd_get == conn->rd_put) { conn->rd_get = conn->rd_put = 0; } if (rv == NNG_EAGAIN) { nni_iov iov1; + http_rd_buf_pull_up(conn); iov1.iov_buf = conn->rd_buf + conn->rd_put; iov1.iov_len = conn->rd_bufsz - conn->rd_put; conn->rd_buffered = true; + if (iov1.iov_len == 0) { + return (NNG_EMSGSIZE); + } nni_aio_set_iov(&conn->rd_aio, 1, &iov1); nng_stream_recv(conn->sock, &conn->rd_aio); } return (rv); case HTTP_RD_RES: - rv = nni_http_res_parse( - nni_aio_get_prov_data(aio), rbuf, cnt, &n); + conn->client = false; + rv = nni_http_res_parse(conn, rbuf, cnt, &n); + conn->client = true; conn->rd_get += n; if (conn->rd_get == conn->rd_put) { conn->rd_get = conn->rd_put = 0; } if (rv == NNG_EAGAIN) { nni_iov iov1; + http_rd_buf_pull_up(conn); iov1.iov_buf = conn->rd_buf + conn->rd_put; iov1.iov_len = conn->rd_bufsz - conn->rd_put; conn->rd_buffered = true; + if (iov1.iov_len == 0) { + return (NNG_EMSGSIZE); + } nni_aio_set_iov(&conn->rd_aio, 1, &iov1); nng_stream_recv(conn->sock, &conn->rd_aio); } @@ -511,23 +538,29 @@ http_wr_submit(nni_http_conn *conn, nni_aio *aio, enum write_flavor flavor) } void -nni_http_read_req(nni_http_conn *conn, nni_aio *aio) +nni_http_conn_reset(nng_http *conn) { - nni_aio_set_prov_data(aio, &conn->req); + nni_http_req_reset(&conn->req); + nni_http_res_reset(&conn->res); + if (strlen(conn->req.host)) { + nni_http_set_host(conn, conn->req.host); + } +} +void +nni_http_read_req(nni_http_conn *conn, nni_aio *aio) +{ // clear the sent flag (used for the server) conn->res_sent = false; - nni_http_req_reset(&conn->req); + nni_http_conn_reset(conn); nni_mtx_lock(&conn->mtx); http_rd_submit(conn, aio, HTTP_RD_REQ); nni_mtx_unlock(&conn->mtx); } void -nni_http_read_res(nni_http_conn *conn, nni_http_res *res, nni_aio *aio) +nni_http_read_res(nni_http_conn *conn, nni_aio *aio) { - nni_aio_set_prov_data(aio, res); - nni_mtx_lock(&conn->mtx); http_rd_submit(conn, aio, HTTP_RD_RES); nni_mtx_unlock(&conn->mtx); @@ -564,28 +597,25 @@ nni_http_read(nni_http_conn *conn, nni_aio *aio) } void -nni_http_write_req(nni_http_conn *conn, nni_http_req *req, nni_aio *aio) +nni_http_write_req(nni_http_conn *conn, nni_aio *aio) { int rv; void *buf; size_t bufsz; - void *data; - size_t size; nni_iov iov[2]; int niov; - if ((rv = nni_http_req_get_buf(req, &buf, &bufsz)) != 0) { + if ((rv = nni_http_req_get_buf(&conn->req, &buf, &bufsz)) != 0) { nni_aio_finish_error(aio, rv); return; } - nni_http_req_get_data(req, &data, &size); niov = 1; iov[0].iov_len = bufsz; iov[0].iov_buf = buf; - if ((size > 0) && (data != NULL)) { + iov[1].iov_len = conn->req.data.size; + iov[1].iov_buf = conn->req.data.data; + if ((iov[1].iov_len > 0) && (iov[1].iov_buf != NULL)) { niov++; - iov[1].iov_len = size; - iov[1].iov_buf = data; } nni_aio_set_iov(aio, niov, iov); @@ -597,28 +627,24 @@ nni_http_write_req(nni_http_conn *conn, nni_http_req *req, nni_aio *aio) void nni_http_write_res(nni_http_conn *conn, nni_aio *aio) { - int rv; - void *buf; - size_t bufsz; - void *data; - size_t size; - nni_iov iov[2]; - int nio; - nng_http_res *res = nng_http_conn_res(conn); + int rv; + void *buf; + size_t bufsz; + nni_iov iov[2]; + int nio; conn->res_sent = true; - if ((rv = nni_http_res_get_buf(res, &buf, &bufsz)) != 0) { + if ((rv = nni_http_res_get_buf(conn, &buf, &bufsz)) != 0) { nni_aio_finish_error(aio, rv); return; } - nni_http_res_get_data(res, &data, &size); nio = 1; iov[0].iov_len = bufsz; iov[0].iov_buf = buf; - if ((size > 0) && (data != NULL)) { + iov[1].iov_len = conn->res.data.size; + iov[1].iov_buf = conn->res.data.data; + if ((iov[1].iov_len > 0) && (iov[1].iov_buf != NULL)) { nio++; - iov[1].iov_len = size; - iov[1].iov_buf = data; } nni_aio_set_iov(aio, nio, iov); @@ -643,6 +669,632 @@ nni_http_write_full(nni_http_conn *conn, nni_aio *aio) nni_mtx_unlock(&conn->mtx); } +const char * +nni_http_get_version(nng_http *conn) +{ + return (conn->req.vers); +} + +int +nni_http_set_version(nng_http *conn, const char *vers) +{ + static const char *http_versions[] = { + // for efficiency, we order in most likely first + "HTTP/1.1", + "HTTP/2", + "HTTP/3", + "HTTP/1.0", + "HTTP/0.9", + NULL, + }; + + vers = vers != NULL ? vers : NNG_HTTP_VERSION_1_1; + for (int i = 0; http_versions[i] != NULL; i++) { + if (strcmp(vers, http_versions[i]) == 0) { + conn->req.vers = http_versions[i]; + conn->res.vers = http_versions[i]; + return (0); + } + } + return (NNG_ENOTSUP); +} + +void +nni_http_set_method(nng_http *conn, const char *method) +{ + if (method == NULL) { + method = "GET"; + } + // this may truncate the method, but nobody should be sending + // methods so long. + (void) snprintf(conn->req.meth, sizeof(conn->req.meth), "%s", method); +} + +const char * +nni_http_get_method(nng_http *conn) +{ + return (conn->req.meth); +} + +uint16_t +nni_http_get_status(nng_http *conn) +{ + return (conn->res.code); +} + +const char * +nni_http_reason(uint16_t code) +{ + static struct { + uint16_t code; + const char *mesg; + } http_status[] = { + // 200, listed first because most likely + { NNG_HTTP_STATUS_OK, "OK" }, + + // 100 series -- informational + { NNG_HTTP_STATUS_CONTINUE, "Continue" }, + { NNG_HTTP_STATUS_SWITCHING, "Switching Protocols" }, + { NNG_HTTP_STATUS_PROCESSING, "Processing" }, + + // 200 series -- successful + { NNG_HTTP_STATUS_CREATED, "Created" }, + { NNG_HTTP_STATUS_ACCEPTED, "Accepted" }, + { NNG_HTTP_STATUS_NOT_AUTHORITATIVE, "Not Authoritative" }, + { NNG_HTTP_STATUS_NO_CONTENT, "No Content" }, + { NNG_HTTP_STATUS_RESET_CONTENT, "Reset Content" }, + { NNG_HTTP_STATUS_PARTIAL_CONTENT, "Partial Content" }, + { NNG_HTTP_STATUS_MULTI_STATUS, "Multi-Status" }, + { NNG_HTTP_STATUS_ALREADY_REPORTED, "Already Reported" }, + { NNG_HTTP_STATUS_IM_USED, "IM Used" }, + + // 300 series -- redirection + { NNG_HTTP_STATUS_MULTIPLE_CHOICES, "Multiple Choices" }, + { NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY, + "Moved Permanently" }, + { NNG_HTTP_STATUS_FOUND, "Found" }, + { NNG_HTTP_STATUS_SEE_OTHER, "See Other" }, + { NNG_HTTP_STATUS_NOT_MODIFIED, "Not Modified" }, + { NNG_HTTP_STATUS_USE_PROXY, "Use Proxy" }, + { NNG_HTTP_STATUS_TEMPORARY_REDIRECT, "Temporary Redirect" }, + { NNG_HTTP_STATUS_PERMANENT_REDIRECT, "Permanent Redirect" }, + + // 400 series -- client errors + { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Request" }, + { NNG_HTTP_STATUS_UNAUTHORIZED, "Unauthorized" }, + { NNG_HTTP_STATUS_PAYMENT_REQUIRED, "Payment Required" }, + { NNG_HTTP_STATUS_FORBIDDEN, "Forbidden" }, + { NNG_HTTP_STATUS_NOT_FOUND, "Not Found" }, + { NNG_HTTP_STATUS_METHOD_NOT_ALLOWED, "Method Not Allowed" }, + { NNG_HTTP_STATUS_NOT_ACCEPTABLE, "Not Acceptable" }, + { NNG_HTTP_STATUS_PROXY_AUTH_REQUIRED, + "Proxy Authentication Required" }, + { NNG_HTTP_STATUS_REQUEST_TIMEOUT, "Request Timeout" }, + { NNG_HTTP_STATUS_CONFLICT, "Conflict" }, + { NNG_HTTP_STATUS_GONE, "Gone" }, + { NNG_HTTP_STATUS_LENGTH_REQUIRED, "Length Required" }, + { NNG_HTTP_STATUS_PRECONDITION_FAILED, "Precondition Failed" }, + { NNG_HTTP_STATUS_CONTENT_TOO_LARGE, "Content Too Large" }, + { NNG_HTTP_STATUS_URI_TOO_LONG, "URI Too Long" }, + { NNG_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + "Unsupported Media Type" }, + { NNG_HTTP_STATUS_RANGE_NOT_SATISFIABLE, + "Range Not Satisfiable" }, + { NNG_HTTP_STATUS_EXPECTATION_FAILED, "Expectation Failed" }, + { NNG_HTTP_STATUS_TEAPOT, "I Am A Teapot" }, + { NNG_HTTP_STATUS_LOCKED, "Locked" }, + { NNG_HTTP_STATUS_FAILED_DEPENDENCY, "Failed Dependency" }, + { NNG_HTTP_STATUS_UPGRADE_REQUIRED, "Upgrade Required" }, + { NNG_HTTP_STATUS_PRECONDITION_REQUIRED, + "Precondition Required" }, + { NNG_HTTP_STATUS_TOO_MANY_REQUESTS, "Too Many Requests" }, + { NNG_HTTP_STATUS_HEADERS_TOO_LARGE, "Headers Too Large" }, + { NNG_HTTP_STATUS_UNAVAIL_LEGAL_REASONS, + "Unavailable For Legal Reasons" }, + + // 500 series -- server errors + { NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR, + "Internal Server Error" }, + { NNG_HTTP_STATUS_NOT_IMPLEMENTED, "Not Implemented" }, + { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Gateway" }, + { NNG_HTTP_STATUS_SERVICE_UNAVAILABLE, "Service Unavailable" }, + { NNG_HTTP_STATUS_GATEWAY_TIMEOUT, "Gateway Timeout" }, + { NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP, + "HTTP Version Not Supported" }, + { NNG_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES, + "Variant Also Negotiates" }, + { NNG_HTTP_STATUS_INSUFFICIENT_STORAGE, + "Insufficient Storage" }, + { NNG_HTTP_STATUS_LOOP_DETECTED, "Loop Detected" }, + { NNG_HTTP_STATUS_NOT_EXTENDED, "Not Extended" }, + { NNG_HTTP_STATUS_NETWORK_AUTH_REQUIRED, + "Network Authentication Required" }, + + // Terminator + { 0, NULL }, + }; + + for (int i = 0; http_status[i].code != 0; i++) { + if (http_status[i].code == code) { + return (http_status[i].mesg); + } + } + return ("Unknown HTTP Status"); +} + +const char * +nni_http_get_reason(nng_http *conn) +{ +#ifdef NNG_SUPP_HTTP + return ( + conn->res.rsn ? conn->res.rsn : nni_http_reason(conn->res.code)); +#else + return (NULL); +#endif +} + +int +nni_http_set_status(nng_http *conn, uint16_t status, const char *reason) +{ + conn->res.code = status; + char *dup = NULL; + if ((reason != NULL) && + (strcmp(reason, nni_http_reason(conn->res.code)) == 0)) { + reason = NULL; + return (0); + } + if ((reason != NULL) && (dup = nni_strdup(reason)) == NULL) { + return (NNG_ENOMEM); + } + if (conn->res.rsn != NULL) { + nni_strfree(conn->res.rsn); + } + conn->res.rsn = dup; + return (0); +} + +static int +http_conn_set_error(nng_http *conn, uint16_t status, const char *reason, + const char *body, const char *redirect) +{ + int rv; + char content[1024]; + const char *prefix = "<!DOCTYPE html>\n" + "<html><head><title>%d %s</title>\n" + "<style>" + "body { font-family: Arial, sans serif; " + "text-align: center }\n" + "h1 { font-size: 36px; }" + "span { background-color: gray; color: white; " + "padding: 7px; " + "border-radius: 5px }" + "h2 { font-size: 24px; }" + "p { font-size: 20px; }" + "</style></head>" + "<body><p> </p>" + "<h1><span>%d</span></h1>" + "<h2>%s</h2><p>"; + const char *suffix = "</p></body></html>"; + + conn->res.iserr = true; + + if ((rv = nni_http_set_status(conn, status, reason)) != 0) { + return (rv); + } + reason = nni_http_get_reason(conn); + + if (body == NULL) { + snprintf(content, sizeof(content), prefix, status, reason, + status, reason); + size_t avail = sizeof(content) - strlen(content); + + if (redirect != NULL && strlen(redirect) > 200 && + strlen(reason) < 40) { + // URL is too long for buffer and unlikely to be useful + // to humans anyway. 600 bytes will fit in the 1K + // buffer without issue. (Our prelude and trailer are + // less than 400 bytes.) + snprintf(content + strlen(content), avail, + "You should be automatically redirected."); + avail = sizeof(content) - strlen(content); + } else if (redirect != NULL) { + // TODO: redirect should be URL encoded. + snprintf(content + strlen(content), avail, + "You should be automatically redirected to <a " + "href=\"%s\">%s</a>.", + redirect, redirect); + avail = sizeof(content) - strlen(content); + } + snprintf(content + strlen(content), avail, "%s", suffix); + body = content; + } + if (strlen(body) > 0) { + nni_http_set_content_type(conn, "text/html; charset=UTF-8"); + return (nni_http_copy_body(conn, body, strlen(body))); + } + return (0); +} + +int +nni_http_set_error( + nng_http *conn, uint16_t status, const char *reason, const char *body) +{ + return (http_conn_set_error(conn, status, reason, body, NULL)); +} + +int +nni_http_set_redirect( + nng_http *conn, uint16_t status, const char *reason, const char *redirect) +{ + char *loc; + if ((loc = nni_strdup(redirect)) == NULL) { + return (NNG_ENOMEM); + } + (void) nni_http_del_header(conn, "Location"); + nni_list_node_remove(&conn->res.location.node); + nni_http_free_header(&conn->res.location); + conn->res.location.name = "Location"; + conn->res.location.value = loc; + conn->res.location.static_name = true; + conn->res.location.static_value = false; + nni_list_prepend(&conn->res.data.hdrs, &conn->res.location); + return (http_conn_set_error(conn, status, reason, NULL, redirect)); +} + +void +nni_http_set_host(nng_http *conn, const char *host) +{ + if (host != conn->req.host) { + snprintf(conn->req.host, sizeof(conn->req.host), "%s", host); + } + nni_list_node_remove(&conn->req.host_header.node); + conn->req.host_header.name = "Host"; + conn->req.host_header.value = conn->req.host; + conn->req.host_header.static_name = true; + conn->req.host_header.static_value = true; + conn->req.host_header.alloc_header = false; + nni_list_prepend(&conn->req.data.hdrs, &conn->req.host_header); +} + +void +nni_http_set_content_length(nng_http *conn, size_t size) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + + snprintf(data->clen, sizeof(data->clen), "%lu", (unsigned long) size); + nni_http_set_static_header( + conn, &data->content_length, "Content-Length", data->clen); +} + +void +nni_http_set_content_type(nng_http *conn, const char *ctype) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + snprintf(data->ctype, sizeof(data->ctype), "%s", ctype); + nni_http_set_static_header( + conn, &data->content_type, "Content-Type", data->ctype); +} + +const char * +nni_http_get_uri(nng_http *conn) +{ + return (conn->req.uri); +} + +int +nni_http_set_uri(nng_http *conn, const char *uri, const char *query) +{ + size_t needed; + const char *fmt; + + if (query != NULL) { + fmt = strchr(uri, '?') != NULL ? "%s&%s" : "%s?%s"; + needed = strlen(uri) + strlen(query) + 1; + } else { + fmt = "%s%s"; + query = ""; + needed = strlen(uri); + } + + if (conn->req.uri != NULL && (strcmp(uri, conn->req.uri) == 0) && + strlen(query) == 0) { + // no change, do nothing + return (0); + } + if (conn->req.uri != NULL && conn->req.uri != conn->req.ubuf) { + nni_strfree(conn->req.uri); + } + + // fast path, small size URI fits in our buffer + if (needed < sizeof(conn->req.ubuf)) { + snprintf( + conn->req.ubuf, sizeof(conn->req.ubuf), fmt, uri, query); + conn->req.uri = conn->req.ubuf; + return (0); + } + + // too big, we have to allocate it (slow path) + if (nni_asprintf(&conn->req.uri, fmt, uri, query) != 0) { + return (NNG_ENOMEM); + } + return (0); +} + +static int +http_set_header(nng_http *conn, const char *key, const char *val) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + http_header *h; + + NNI_LIST_FOREACH (&data->hdrs, h) { + if (nni_strcasecmp(key, h->name) == 0) { + char *news; + if ((news = nni_strdup(val)) == NULL) { + return (NNG_ENOMEM); + } + if (!h->static_value) { + nni_strfree(h->value); + h->value = NULL; + } + h->value = news; + return (0); + } + } + + if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { + return (NNG_ENOMEM); + } + h->alloc_header = true; + if ((h->name = nni_strdup(key)) == NULL) { + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + if ((h->value = nni_strdup(val)) == NULL) { + nni_strfree(h->name); + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + nni_list_append(&data->hdrs, h); + return (0); +} + +static int +http_add_header(nng_http *conn, const char *key, const char *val) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + http_header *h; + NNI_LIST_FOREACH (&data->hdrs, h) { + if (nni_strcasecmp(key, h->name) == 0) { + char *news; + int rv; + rv = nni_asprintf(&news, "%s, %s", h->value, val); + if (rv != 0) { + return (rv); + } + if (!h->static_value) { + nni_strfree(h->value); + } + h->value = news; + return (0); + } + } + + if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { + return (NNG_ENOMEM); + } + h->alloc_header = true; + if ((h->name = nni_strdup(key)) == NULL) { + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + if ((h->value = nni_strdup(val)) == NULL) { + nni_strfree(h->name); + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + nni_list_append(&data->hdrs, h); + return (0); +} + +static bool +http_set_known_header(nng_http *conn, const char *key, const char *val) +{ + if (nni_strcasecmp(key, "Content-Type") == 0) { + nni_http_set_content_type(conn, val); + return (true); + } + if (nni_strcasecmp(key, "Content-Length") == 0) { + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + snprintf(data->clen, sizeof(data->clen), "%s", val); + nni_http_set_static_header( + conn, &data->content_length, "Content-Length", data->clen); + return (true); + } + + if (conn->client) { + if (nni_strcasecmp(key, "Host") == 0) { + nni_http_set_host(conn, val); + return (true); + } + } + return (false); +} + +int +nni_http_add_header(nng_http *conn, const char *key, const char *val) +{ + if (http_set_known_header(conn, key, val)) { + return (0); + } + + return (http_add_header(conn, key, val)); +} + +void +nni_http_set_static_header( + nng_http *conn, nni_http_header *h, const char *key, const char *val) +{ + nni_list *headers; + if (conn->client) { + headers = &conn->req.data.hdrs; + } else { + headers = &conn->res.data.hdrs; + } + + nni_http_del_header(conn, key); + nni_list_node_remove(&h->node); + h->alloc_header = false; + h->static_name = true; + h->static_value = true; + h->name = (char *) key; + h->value = (char *) val; + nni_list_append(headers, h); +} + +int +nni_http_set_header(nng_http *conn, const char *key, const char *val) +{ + if (http_set_known_header(conn, key, val)) { + return (0); + } + return (http_set_header(conn, key, val)); +} + +static bool +http_del_header_one(nni_list *hdrs, const char *key) +{ + http_header *h; + NNI_LIST_FOREACH (hdrs, h) { + if (nni_strcasecmp(key, h->name) == 0) { + nni_http_free_header(h); + return (true); + } + } + return (false); +} + +void +nni_http_del_header(nng_http *conn, const char *key) +{ + nni_list *hdrs = + conn->client ? &conn->req.data.hdrs : &conn->res.data.hdrs; + while (http_del_header_one(hdrs, key)) { + continue; + } +} + +static const char * +http_get_header(const nni_list *hdrs, const char *key) +{ + http_header *h; + NNI_LIST_FOREACH (hdrs, h) { + if (nni_strcasecmp(h->name, key) == 0) { + return (h->value); + } + } + return (NULL); +} + +const char * +nni_http_get_header(nng_http *conn, const char *key) +{ + if (conn->client) { + return (http_get_header(&conn->res.data.hdrs, key)); + } else { + return (http_get_header(&conn->req.data.hdrs, key)); + } +} + +void +nni_http_get_body(nng_http *conn, void **datap, size_t *sizep) +{ + if (conn->client) { + *datap = conn->res.data.data; + *sizep = conn->res.data.size; + } else { + *datap = conn->req.data.data; + *sizep = conn->req.data.size; + } +} + +static void +http_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_alloc_data(nni_http_entity *entity, size_t size) +{ + void *newdata; + if (size != 0) { + if ((newdata = nni_zalloc(size)) == NULL) { + return (NNG_ENOMEM); + } + } + http_set_data(entity, newdata, size); + entity->own = true; + return (0); +} + +static int +http_copy_data(nni_http_entity *entity, const void *data, size_t size) +{ + int rv; + if ((rv = http_alloc_data(entity, size)) == 0) { + memcpy(entity->data, data, size); + } + return (rv); +} + +void +nni_http_set_body(nng_http *conn, void *data, size_t size) +{ + if (conn->client) { + http_set_data(&conn->req.data, data, size); + } else { + http_set_data(&conn->res.data, data, size); + } + nni_http_set_content_length(conn, size); +} + +void +nni_http_prune_body(nng_http *conn) +{ + // prune body but leave content-length header intact. + // This is for HEAD. + if (conn->client) { + http_set_data(&conn->req.data, NULL, 0); + } else { + http_set_data(&conn->res.data, NULL, 0); + } +} + +int +nni_http_copy_body(nng_http *conn, const void *data, size_t size) +{ + int rv; + if (conn->client) { + rv = http_copy_data(&conn->req.data, data, size); + } else { + rv = http_copy_data(&conn->res.data, data, size); + } + if (rv == 0) { + nni_http_set_content_length(conn, size); + } + return (rv); +} + int nni_http_conn_getopt( nni_http_conn *conn, const char *name, void *buf, size_t *szp, nni_type t) @@ -674,26 +1326,28 @@ nni_http_conn_fini(nni_http_conn *conn) nni_aio_fini(&conn->wr_aio); nni_aio_fini(&conn->rd_aio); - nni_http_req_reset(&conn->req); - nni_http_res_reset(&conn->res); + nni_http_conn_reset(conn); nni_free(conn->rd_buf, conn->rd_bufsz); nni_mtx_fini(&conn->mtx); NNI_FREE_STRUCT(conn); } static int -http_init(nni_http_conn **connp, nng_stream *data) +http_init(nni_http_conn **connp, nng_stream *data, bool client) { nni_http_conn *conn; if ((conn = NNI_ALLOC_STRUCT(conn)) == NULL) { return (NNG_ENOMEM); } + conn->client = client; nni_mtx_init(&conn->mtx); nni_aio_list_init(&conn->rdq); nni_aio_list_init(&conn->wrq); nni_http_req_init(&conn->req); nni_http_res_init(&conn->res); + nni_http_set_version(conn, NNG_HTTP_VERSION_1_1); + nni_http_set_method(conn, NULL); if ((conn->rd_buf = nni_alloc(HTTP_BUFSIZE)) == NULL) { nni_http_conn_fini(conn); @@ -712,10 +1366,10 @@ http_init(nni_http_conn **connp, nng_stream *data) } int -nni_http_conn_init(nni_http_conn **connp, nng_stream *stream) +nni_http_conn_init(nni_http_conn **connp, nng_stream *stream, bool client) { int rv; - if ((rv = http_init(connp, stream)) != 0) { + if ((rv = http_init(connp, stream, client)) != 0) { nng_stream_free(stream); } return (rv); @@ -723,7 +1377,13 @@ nni_http_conn_init(nni_http_conn **connp, nng_stream *stream) // private to the HTTP framework, used on the server bool -nni_http_conn_res_sent(nni_http_conn *conn) +nni_http_res_sent(nni_http_conn *conn) { return (conn->res_sent); } + +bool +nni_http_parsed(nng_http *conn) +{ + return (conn->client ? conn->res.data.parsed : conn->req.data.parsed); +} |
