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);
-}