kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit 862bf1a5f671d06cba35c6d794d9d7c1bcdaf6be
parent cdd681d602980f8d6862a168eea5f3b40bd5a033
Author: Joris Vink <joris@coders.se>
Date:   Sun, 12 Sep 2021 15:10:06 +0200

Add http_response_json().

Since its an HTTP response function it functions like http_response() but
takes a kore_json_item pointer that it will automatically convert to a kore_buf
and send/free using http_response_stream().

While here fix a problem with http_response_stream() which could end up
not calling the cb() in case of HTTP_METHOD_HEAD. Since the behaviour is
that it should call cb() when done it should do so immediately.

Diffstat:
src/http.c | 705++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 373 insertions(+), 332 deletions(-)

diff --git a/src/http.c b/src/http.c @@ -128,6 +128,7 @@ static const char *pretty_error_fmt = "</body>\n</html>\n"; static int http_body_recv(struct netbuf *); +static int http_release_buffer(struct netbuf *); static void http_error_response(struct connection *, int); static void http_write_response_cookie(struct http_cookie *); static void http_argument_add(struct http_request *, char *, char *, @@ -636,6 +637,35 @@ http_response_close(struct http_request *req, int code, const void *d, size_t l) } void +http_response_json(struct http_request *req, int status, + struct kore_json_item *json) +{ + struct kore_buf *buf; + + if (req->owner == NULL) + return; + + kore_debug("%s(%p, %d)", __func__, req, code); + + buf = kore_buf_alloc(1024); + kore_json_item_tobuf(json, buf); + kore_json_item_free(json); + + req->status = status; + http_response_header(req, "content-type", "application/json"); + + switch (req->owner->proto) { + case CONN_PROTO_HTTP: + http_response_stream(req, status, buf->data, buf->offset, + http_release_buffer, buf); + break; + default: + fatal("%s: bad proto %d", __func__, req->owner->proto); + /* NOTREACHED. */ + } +} + +void http_response_stream(struct http_request *req, int status, void *base, size_t len, int (*cb)(struct netbuf *), void *arg) { @@ -651,13 +681,16 @@ http_response_stream(struct http_request *req, int status, void *base, http_response_normal(req, req->owner, status, NULL, len); break; default: - fatal("http_response_stream() bad proto %d", req->owner->proto); + fatal("%s: bad proto %d", __func__, req->owner->proto); /* NOTREACHED. */ } - if (req->method != HTTP_METHOD_HEAD) { - net_send_stream(req->owner, base, len, cb, &nb); - nb->extra = arg; + net_send_stream(req->owner, base, len, cb, &nb); + nb->extra = arg; + + if (req->method == HTTP_METHOD_HEAD) { + nb->s_off = nb->b_len; + net_remove_netbuf(req->owner, nb); } } @@ -1566,75 +1599,361 @@ http_redirect_add(struct kore_domain *dom, const char *path, int status, return (KORE_RESULT_OK); } -static int -http_check_redirect(struct http_request *req, struct kore_domain *dom) +const char * +http_status_text(int status) { - int idx; - struct http_redirect *rdr; - const char *uri; - char key[4]; - struct kore_buf location; + const char *r; - TAILQ_FOREACH(rdr, &dom->redirects, list) { - if (!regexec(&(rdr->rctx), req->path, - HTTP_CAPTURE_GROUPS, req->cgroups, 0)) - break; + switch (status) { + case HTTP_STATUS_CONTINUE: + r = "Continue"; + break; + case HTTP_STATUS_SWITCHING_PROTOCOLS: + r = "Switching Protocols"; + break; + case HTTP_STATUS_OK: + r = "OK"; + break; + case HTTP_STATUS_CREATED: + r = "Created"; + break; + case HTTP_STATUS_ACCEPTED: + r = "Accepted"; + break; + case HTTP_STATUS_NON_AUTHORITATIVE: + r = "Non-Authoritative Information"; + break; + case HTTP_STATUS_NO_CONTENT: + r = "No Content"; + break; + case HTTP_STATUS_RESET_CONTENT: + r = "Reset Content"; + break; + case HTTP_STATUS_PARTIAL_CONTENT: + r = "Partial Content"; + break; + case HTTP_STATUS_MULTIPLE_CHOICES: + r = "Multiple Choices"; + break; + case HTTP_STATUS_MOVED_PERMANENTLY: + r = "Moved Permanently"; + break; + case HTTP_STATUS_FOUND: + r = "Found"; + break; + case HTTP_STATUS_SEE_OTHER: + r = "See Other"; + break; + case HTTP_STATUS_NOT_MODIFIED: + r = "Not Modified"; + break; + case HTTP_STATUS_USE_PROXY: + r = "Use Proxy"; + break; + case HTTP_STATUS_TEMPORARY_REDIRECT: + r = "Temporary Redirect"; + break; + case HTTP_STATUS_BAD_REQUEST: + r = "Bad Request"; + break; + case HTTP_STATUS_UNAUTHORIZED: + r = "Unauthorized"; + break; + case HTTP_STATUS_PAYMENT_REQUIRED: + r = "Payment Required"; + break; + case HTTP_STATUS_FORBIDDEN: + r = "Forbidden"; + break; + case HTTP_STATUS_NOT_FOUND: + r = "Not Found"; + break; + case HTTP_STATUS_METHOD_NOT_ALLOWED: + r = "Method Not Allowed"; + break; + case HTTP_STATUS_NOT_ACCEPTABLE: + r = "Not Acceptable"; + break; + case HTTP_STATUS_PROXY_AUTH_REQUIRED: + r = "Proxy Authentication Required"; + break; + case HTTP_STATUS_REQUEST_TIMEOUT: + r = "Request Time-out"; + break; + case HTTP_STATUS_CONFLICT: + r = "Conflict"; + break; + case HTTP_STATUS_GONE: + r = "Gone"; + break; + case HTTP_STATUS_LENGTH_REQUIRED: + r = "Length Required"; + break; + case HTTP_STATUS_PRECONDITION_FAILED: + r = "Precondition Failed"; + break; + case HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE: + r = "Request Entity Too Large"; + break; + case HTTP_STATUS_REQUEST_URI_TOO_LARGE: + r = "Request-URI Too Large"; + break; + case HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: + r = "Unsupported Media Type"; + break; + case HTTP_STATUS_REQUEST_RANGE_INVALID: + r = "Requested range not satisfiable"; + break; + case HTTP_STATUS_EXPECTATION_FAILED: + r = "Expectation Failed"; + break; + case HTTP_STATUS_MISDIRECTED_REQUEST: + r = "Misdirected Request"; + break; + case HTTP_STATUS_INTERNAL_ERROR: + r = "Internal Server Error"; + break; + case HTTP_STATUS_NOT_IMPLEMENTED: + r = "Not Implemented"; + break; + case HTTP_STATUS_BAD_GATEWAY: + r = "Bad Gateway"; + break; + case HTTP_STATUS_SERVICE_UNAVAILABLE: + r = "Service Unavailable"; + break; + case HTTP_STATUS_GATEWAY_TIMEOUT: + r = "Gateway Time-out"; + break; + case HTTP_STATUS_BAD_VERSION: + r = "HTTP Version not supported"; + break; + default: + r = ""; + break; } - if (rdr == NULL) - return (KORE_RESULT_ERROR); + return (r); +} - uri = NULL; - kore_buf_init(&location, 128); +const char * +http_method_text(int method) +{ + char *r; - if (rdr->target) { - kore_buf_appendf(&location, "%s", rdr->target); + switch(method) { + case HTTP_METHOD_GET: + r = "GET"; + break; + case HTTP_METHOD_POST: + r = "POST"; + break; + case HTTP_METHOD_PUT: + r = "PUT"; + break; + case HTTP_METHOD_DELETE: + r = "DELETE"; + break; + case HTTP_METHOD_HEAD: + r = "HEAD"; + break; + case HTTP_METHOD_OPTIONS: + r = "OPTIONS"; + break; + case HTTP_METHOD_PATCH: + r = "PATCH"; + break; + default: + r = ""; + break; + } - if (req->query_string != NULL) { - kore_buf_replace_string(&location, "$qs", - req->query_string, strlen(req->query_string)); - } + return (r); +} - /* Starts at 1 to skip the full path. */ - for (idx = 1; idx < HTTP_CAPTURE_GROUPS - 1; idx++) { - if (req->cgroups[idx].rm_so == -1 || - req->cgroups[idx].rm_eo == -1) - break; +int +http_method_value(const char *method) +{ + if (!strcasecmp(method, "GET")) + return (HTTP_METHOD_GET); - (void)snprintf(key, sizeof(key), "$%d", idx); + if (!strcasecmp(method, "POST")) + return (HTTP_METHOD_POST); - kore_buf_replace_string(&location, key, - req->path + req->cgroups[idx].rm_so, - req->cgroups[idx].rm_eo - req->cgroups[idx].rm_so); - } + if (!strcasecmp(method, "PUT")) + return (HTTP_METHOD_PUT); - uri = kore_buf_stringify(&location, NULL); - } + if (!strcasecmp(method, "DELETE")) + return (HTTP_METHOD_DELETE); - if (uri) - http_response_header(req, "location", uri); + if (!strcasecmp(method, "HEAD")) + return (HTTP_METHOD_HEAD); - http_response(req, rdr->status, NULL, 0); - kore_buf_cleanup(&location); + if (!strcasecmp(method, "OPTIONS")) + return (HTTP_METHOD_OPTIONS); - if (dom->accesslog) - kore_accesslog(req); + if (!strcasecmp(method, "PATCH")) + return (HTTP_METHOD_PATCH); - return (KORE_RESULT_OK); + return (0); } -static struct http_request * -http_request_new(struct connection *c, const char *host, - const char *method, char *path, const char *version) +int +http_media_register(const char *ext, const char *type) { - struct kore_domain *dom; - struct http_request *req; - char *p, *hp; - int m, flags, exists; - size_t hostlen, pathlen, qsoff; + struct http_media_type *media; - if (http_request_count >= http_request_limit) { - http_error_response(c, HTTP_STATUS_SERVICE_UNAVAILABLE); + LIST_FOREACH(media, &http_media_types, list) { + if (!strcasecmp(media->ext, ext)) + return (KORE_RESULT_ERROR); + } + + media = kore_calloc(1, sizeof(*media)); + media->ext = kore_strdup(ext); + media->type = kore_strdup(type); + + LIST_INSERT_HEAD(&http_media_types, media, list); + + return (KORE_RESULT_OK); +} + +const char * +http_media_type(const char *path) +{ + const char *p; + struct http_media_type *media; + + if ((p = strrchr(path, '.')) == NULL) + return (NULL); + + p++; + if (*p == '\0') + return (NULL); + + LIST_FOREACH(media, &http_media_types, list) { + if (!strcasecmp(media->ext, p)) + return (media->type); + } + + return (NULL); +} + +char * +http_validate_header(char *header) +{ + u_int8_t idx; + char *p, *value; + + for (p = header; *p != '\0'; p++) { + idx = *p; + if (idx > HTTP_MAP_LIMIT) + return (NULL); + + if (*p == ':') { + *(p)++ = '\0'; + break; + } + + if (http_token[idx] == 0x00) + return (NULL); + } + + while (isspace(*(unsigned char *)p)) + p++; + + if (*p == '\0') + return (NULL); + + value = p; + while (*p != '\0') { + idx = *p; + if (idx > HTTP_MAP_LIMIT) + return (NULL); + if (http_field_content[idx] == 0x00) + return (NULL); + p++; + } + + return (value); +} + +static int +http_release_buffer(struct netbuf *nb) +{ + kore_buf_free(nb->extra); + + return (KORE_RESULT_OK); +} + +static int +http_check_redirect(struct http_request *req, struct kore_domain *dom) +{ + int idx; + struct http_redirect *rdr; + const char *uri; + char key[4]; + struct kore_buf location; + + TAILQ_FOREACH(rdr, &dom->redirects, list) { + if (!regexec(&(rdr->rctx), req->path, + HTTP_CAPTURE_GROUPS, req->cgroups, 0)) + break; + } + + if (rdr == NULL) + return (KORE_RESULT_ERROR); + + uri = NULL; + kore_buf_init(&location, 128); + + if (rdr->target) { + kore_buf_appendf(&location, "%s", rdr->target); + + if (req->query_string != NULL) { + kore_buf_replace_string(&location, "$qs", + req->query_string, strlen(req->query_string)); + } + + /* Starts at 1 to skip the full path. */ + for (idx = 1; idx < HTTP_CAPTURE_GROUPS - 1; idx++) { + if (req->cgroups[idx].rm_so == -1 || + req->cgroups[idx].rm_eo == -1) + break; + + (void)snprintf(key, sizeof(key), "$%d", idx); + + kore_buf_replace_string(&location, key, + req->path + req->cgroups[idx].rm_so, + req->cgroups[idx].rm_eo - req->cgroups[idx].rm_so); + } + + uri = kore_buf_stringify(&location, NULL); + } + + if (uri) + http_response_header(req, "location", uri); + + http_response(req, rdr->status, NULL, 0); + kore_buf_cleanup(&location); + + if (dom->accesslog) + kore_accesslog(req); + + return (KORE_RESULT_OK); +} + +static struct http_request * +http_request_new(struct connection *c, const char *host, + const char *method, char *path, const char *version) +{ + struct kore_domain *dom; + struct http_request *req; + char *p, *hp; + int m, flags, exists; + size_t hostlen, pathlen, qsoff; + + if (http_request_count >= http_request_limit) { + http_error_response(c, HTTP_STATUS_SERVICE_UNAVAILABLE); return (NULL); } @@ -2279,281 +2598,3 @@ http_write_response_cookie(struct http_cookie *ck) kore_buf_appendf(header_buf, "set-cookie: %s\r\n", kore_buf_stringify(ckhdr_buf, NULL)); } - -const char * -http_status_text(int status) -{ - const char *r; - - switch (status) { - case HTTP_STATUS_CONTINUE: - r = "Continue"; - break; - case HTTP_STATUS_SWITCHING_PROTOCOLS: - r = "Switching Protocols"; - break; - case HTTP_STATUS_OK: - r = "OK"; - break; - case HTTP_STATUS_CREATED: - r = "Created"; - break; - case HTTP_STATUS_ACCEPTED: - r = "Accepted"; - break; - case HTTP_STATUS_NON_AUTHORITATIVE: - r = "Non-Authoritative Information"; - break; - case HTTP_STATUS_NO_CONTENT: - r = "No Content"; - break; - case HTTP_STATUS_RESET_CONTENT: - r = "Reset Content"; - break; - case HTTP_STATUS_PARTIAL_CONTENT: - r = "Partial Content"; - break; - case HTTP_STATUS_MULTIPLE_CHOICES: - r = "Multiple Choices"; - break; - case HTTP_STATUS_MOVED_PERMANENTLY: - r = "Moved Permanently"; - break; - case HTTP_STATUS_FOUND: - r = "Found"; - break; - case HTTP_STATUS_SEE_OTHER: - r = "See Other"; - break; - case HTTP_STATUS_NOT_MODIFIED: - r = "Not Modified"; - break; - case HTTP_STATUS_USE_PROXY: - r = "Use Proxy"; - break; - case HTTP_STATUS_TEMPORARY_REDIRECT: - r = "Temporary Redirect"; - break; - case HTTP_STATUS_BAD_REQUEST: - r = "Bad Request"; - break; - case HTTP_STATUS_UNAUTHORIZED: - r = "Unauthorized"; - break; - case HTTP_STATUS_PAYMENT_REQUIRED: - r = "Payment Required"; - break; - case HTTP_STATUS_FORBIDDEN: - r = "Forbidden"; - break; - case HTTP_STATUS_NOT_FOUND: - r = "Not Found"; - break; - case HTTP_STATUS_METHOD_NOT_ALLOWED: - r = "Method Not Allowed"; - break; - case HTTP_STATUS_NOT_ACCEPTABLE: - r = "Not Acceptable"; - break; - case HTTP_STATUS_PROXY_AUTH_REQUIRED: - r = "Proxy Authentication Required"; - break; - case HTTP_STATUS_REQUEST_TIMEOUT: - r = "Request Time-out"; - break; - case HTTP_STATUS_CONFLICT: - r = "Conflict"; - break; - case HTTP_STATUS_GONE: - r = "Gone"; - break; - case HTTP_STATUS_LENGTH_REQUIRED: - r = "Length Required"; - break; - case HTTP_STATUS_PRECONDITION_FAILED: - r = "Precondition Failed"; - break; - case HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE: - r = "Request Entity Too Large"; - break; - case HTTP_STATUS_REQUEST_URI_TOO_LARGE: - r = "Request-URI Too Large"; - break; - case HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: - r = "Unsupported Media Type"; - break; - case HTTP_STATUS_REQUEST_RANGE_INVALID: - r = "Requested range not satisfiable"; - break; - case HTTP_STATUS_EXPECTATION_FAILED: - r = "Expectation Failed"; - break; - case HTTP_STATUS_MISDIRECTED_REQUEST: - r = "Misdirected Request"; - break; - case HTTP_STATUS_INTERNAL_ERROR: - r = "Internal Server Error"; - break; - case HTTP_STATUS_NOT_IMPLEMENTED: - r = "Not Implemented"; - break; - case HTTP_STATUS_BAD_GATEWAY: - r = "Bad Gateway"; - break; - case HTTP_STATUS_SERVICE_UNAVAILABLE: - r = "Service Unavailable"; - break; - case HTTP_STATUS_GATEWAY_TIMEOUT: - r = "Gateway Time-out"; - break; - case HTTP_STATUS_BAD_VERSION: - r = "HTTP Version not supported"; - break; - default: - r = ""; - break; - } - - return (r); -} - -const char * -http_method_text(int method) -{ - char *r; - - switch(method) { - case HTTP_METHOD_GET: - r = "GET"; - break; - case HTTP_METHOD_POST: - r = "POST"; - break; - case HTTP_METHOD_PUT: - r = "PUT"; - break; - case HTTP_METHOD_DELETE: - r = "DELETE"; - break; - case HTTP_METHOD_HEAD: - r = "HEAD"; - break; - case HTTP_METHOD_OPTIONS: - r = "OPTIONS"; - break; - case HTTP_METHOD_PATCH: - r = "PATCH"; - break; - default: - r = ""; - break; - } - - return (r); -} - -int -http_method_value(const char *method) -{ - if (!strcasecmp(method, "GET")) - return (HTTP_METHOD_GET); - - if (!strcasecmp(method, "POST")) - return (HTTP_METHOD_POST); - - if (!strcasecmp(method, "PUT")) - return (HTTP_METHOD_PUT); - - if (!strcasecmp(method, "DELETE")) - return (HTTP_METHOD_DELETE); - - if (!strcasecmp(method, "HEAD")) - return (HTTP_METHOD_HEAD); - - if (!strcasecmp(method, "OPTIONS")) - return (HTTP_METHOD_OPTIONS); - - if (!strcasecmp(method, "PATCH")) - return (HTTP_METHOD_PATCH); - - return (0); -} - -int -http_media_register(const char *ext, const char *type) -{ - struct http_media_type *media; - - LIST_FOREACH(media, &http_media_types, list) { - if (!strcasecmp(media->ext, ext)) - return (KORE_RESULT_ERROR); - } - - media = kore_calloc(1, sizeof(*media)); - media->ext = kore_strdup(ext); - media->type = kore_strdup(type); - - LIST_INSERT_HEAD(&http_media_types, media, list); - - return (KORE_RESULT_OK); -} - -const char * -http_media_type(const char *path) -{ - const char *p; - struct http_media_type *media; - - if ((p = strrchr(path, '.')) == NULL) - return (NULL); - - p++; - if (*p == '\0') - return (NULL); - - LIST_FOREACH(media, &http_media_types, list) { - if (!strcasecmp(media->ext, p)) - return (media->type); - } - - return (NULL); -} - -char * -http_validate_header(char *header) -{ - u_int8_t idx; - char *p, *value; - - for (p = header; *p != '\0'; p++) { - idx = *p; - if (idx > HTTP_MAP_LIMIT) - return (NULL); - - if (*p == ':') { - *(p)++ = '\0'; - break; - } - - if (http_token[idx] == 0x00) - return (NULL); - } - - while (isspace(*(unsigned char *)p)) - p++; - - if (*p == '\0') - return (NULL); - - value = p; - while (*p != '\0') { - idx = *p; - if (idx > HTTP_MAP_LIMIT) - return (NULL); - if (http_field_content[idx] == 0x00) - return (NULL); - p++; - } - - return (value); -}