aboutsummaryrefslogtreecommitdiff
path: root/src/supplemental/http/http_msg.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/supplemental/http/http_msg.c')
-rw-r--r--src/supplemental/http/http_msg.c318
1 files changed, 179 insertions, 139 deletions
diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c
index da8c70f3..81c1b453 100644
--- a/src/supplemental/http/http_msg.c
+++ b/src/supplemental/http/http_msg.c
@@ -27,14 +27,14 @@ typedef struct http_header {
nni_list_node node;
} http_header;
-struct nni_http_entity {
+typedef struct nni_http_entity {
char * data;
size_t size; // allocated/expected size
size_t len; // current length
bool own; // if true, data is "ours", and should be freed
-};
+} nni_http_entity;
-struct nni_http_req {
+struct nng_http_req {
nni_list hdrs;
nni_http_entity data;
char * meth;
@@ -42,23 +42,27 @@ struct nni_http_req {
char * vers;
char * buf;
size_t bufsz;
+ bool parsed;
};
-struct nni_http_res {
+struct nng_http_res {
nni_list hdrs;
nni_http_entity data;
- int code;
+ uint16_t code;
char * rsn;
char * vers;
char * buf;
size_t bufsz;
+ bool parsed;
};
static int
http_set_string(char **strp, const char *val)
{
char *news;
- if ((news = nni_strdup(val)) == NULL) {
+ if (val == NULL) {
+ news = NULL;
+ } else if ((news = nni_strdup(val)) == NULL) {
return (NNG_ENOMEM);
}
nni_strfree(*strp);
@@ -114,6 +118,8 @@ nni_http_res_reset(nni_http_res *res)
http_entity_reset(&res->data);
nni_strfree(res->rsn);
nni_strfree(res->vers);
+ res->vers = NULL;
+ res->rsn = NULL;
res->code = 0;
if (res->bufsz) {
res->buf[0] = '\0';
@@ -121,7 +127,7 @@ nni_http_res_reset(nni_http_res *res)
}
void
-nni_http_req_fini(nni_http_req *req)
+nni_http_req_free(nni_http_req *req)
{
nni_http_req_reset(req);
if (req->bufsz) {
@@ -131,7 +137,7 @@ nni_http_req_fini(nni_http_req *req)
}
void
-nni_http_res_fini(nni_http_res *res)
+nni_http_res_free(nni_http_res *res)
{
nni_http_res_reset(res);
if (res->bufsz) {
@@ -157,13 +163,13 @@ http_del_header(nni_list *hdrs, const char *key)
}
int
-nni_req_del_header(nni_http_req *req, const char *key)
+nni_http_req_del_header(nni_http_req *req, const char *key)
{
return (http_del_header(&req->hdrs, key));
}
int
-nni_res_del_header(nni_http_res *res, const char *key)
+nni_http_res_del_header(nni_http_res *res, const char *key)
{
return (http_del_header(&res->hdrs, key));
}
@@ -398,12 +404,6 @@ nni_http_res_copy_data(nni_http_res *res, const void *data, size_t size)
return (0);
}
-int
-nni_http_res_alloc_data(nni_http_res *res, size_t size)
-{
- return (http_entity_alloc_data(&res->data, size));
-}
-
static int
http_parse_header(nni_list *hdrs, void *line)
{
@@ -501,11 +501,12 @@ static int
http_req_prepare(nni_http_req *req)
{
int rv;
- if ((req->uri == NULL) || (req->meth == NULL)) {
+ if (req->uri == NULL) {
return (NNG_EINVAL);
}
rv = http_asprintf(&req->buf, &req->bufsz, &req->hdrs, "%s %s %s\r\n",
- req->meth, req->uri, req->vers != NULL ? req->vers : "HTTP/1.1");
+ req->meth != NULL ? req->meth : "GET", req->uri,
+ req->vers != NULL ? req->vers : "HTTP/1.1");
return (rv);
}
@@ -514,8 +515,8 @@ http_res_prepare(nni_http_res *res)
{
int rv;
rv = http_asprintf(&res->buf, &res->bufsz, &res->hdrs, "%s %d %s\r\n",
- res->vers != NULL ? res->vers : "HTTP/1.1", res->code,
- res->rsn != NULL ? res->rsn : "Unknown Error");
+ nni_http_res_get_version(res), nni_http_res_get_status(res),
+ nni_http_res_get_reason(res));
return (rv);
}
@@ -572,7 +573,7 @@ nni_http_res_get_buf(nni_http_res *res, void **data, size_t *szp)
}
int
-nni_http_req_init(nni_http_req **reqp)
+nni_http_req_alloc(nni_http_req **reqp, const nni_url *url)
{
nni_http_req *req;
if ((req = NNI_ALLOC_STRUCT(req)) == NULL) {
@@ -587,12 +588,33 @@ nni_http_req_init(nni_http_req **reqp)
req->vers = NULL;
req->meth = NULL;
req->uri = NULL;
- *reqp = req;
+ if (url != NULL) {
+ const char *host;
+ int rv;
+ if ((req->uri = nni_strdup(url->u_rawpath)) == NULL) {
+ NNI_FREE_STRUCT(req);
+ return (NNG_ENOMEM);
+ }
+
+ // Add a Host: header since we know that from the URL. Also,
+ // only include the :port portion if it isn't the default port.
+ if (strcmp(nni_url_default_port(url->u_scheme), url->u_port) ==
+ 0) {
+ host = url->u_hostname;
+ } else {
+ host = url->u_host;
+ }
+ if ((rv = nni_http_req_add_header(req, "Host", host)) != 0) {
+ nni_http_req_free(req);
+ return (rv);
+ }
+ }
+ *reqp = req;
return (0);
}
int
-nni_http_res_init(nni_http_res **resp)
+nni_http_res_alloc(nni_http_res **resp)
{
nni_http_res *res;
if ((res = NNI_ALLOC_STRUCT(res)) == NULL) {
@@ -614,7 +636,7 @@ nni_http_res_init(nni_http_res **resp)
const char *
nni_http_req_get_method(nni_http_req *req)
{
- return (req->meth != NULL ? req->meth : "");
+ return (req->meth != NULL ? req->meth : "GET");
}
const char *
@@ -626,24 +648,30 @@ nni_http_req_get_uri(nni_http_req *req)
const char *
nni_http_req_get_version(nni_http_req *req)
{
- return (req->vers != NULL ? req->vers : "");
+ return (req->vers != NULL ? req->vers : "HTTP/1.1");
}
const char *
nni_http_res_get_version(nni_http_res *res)
{
- return (res->vers != NULL ? res->vers : "");
+ return (res->vers != NULL ? res->vers : "HTTP/1.1");
}
int
nni_http_req_set_version(nni_http_req *req, const char *vers)
{
+ if (strcmp(vers, "HTTP/1.1") == 0) {
+ vers = NULL;
+ }
return (http_set_string(&req->vers, vers));
}
int
nni_http_res_set_version(nni_http_res *res, const char *vers)
{
+ if (strcmp(vers, "HTTP/1.1") == 0) {
+ vers = NULL;
+ }
return (http_set_string(&res->vers, vers));
}
@@ -656,31 +684,25 @@ nni_http_req_set_uri(nni_http_req *req, const char *uri)
int
nni_http_req_set_method(nni_http_req *req, const char *meth)
{
+ if (strcmp(meth, "GET") == 0) {
+ meth = NULL;
+ }
return (http_set_string(&req->meth, meth));
}
int
-nni_http_res_set_status(nni_http_res *res, int status, const char *reason)
+nni_http_res_set_status(nni_http_res *res, uint16_t status)
{
- int rv;
- if ((rv = http_set_string(&res->rsn, reason)) == 0) {
- res->code = status;
- }
- return (rv);
+ res->code = status;
+ return (0);
}
-int
+uint16_t
nni_http_res_get_status(nni_http_res *res)
{
return (res->code);
}
-const char *
-nni_http_res_get_reason(nni_http_res *res)
-{
- return (res->rsn != NULL ? res->rsn : "");
-}
-
static int
http_scan_line(void *vbuf, size_t n, size_t *lenp)
{
@@ -740,6 +762,7 @@ http_req_parse_line(nni_http_req *req, void *line)
((rv = nni_http_req_set_version(req, version)) != 0)) {
return (rv);
}
+ req->parsed = true;
return (0);
}
@@ -770,10 +793,12 @@ http_res_parse_line(nni_http_res *res, uint8_t *line)
return (NNG_EPROTO);
}
- if (((rv = nni_http_res_set_status(res, status, reason)) != 0) ||
- ((rv = nni_http_res_set_version(res, version)) != 0)) {
+ if (((rv = nni_http_res_set_status(res, (uint16_t) status)) != 0) ||
+ ((rv = nni_http_res_set_version(res, version)) != 0) ||
+ ((rv = nni_http_res_set_reason(res, reason)) != 0)) {
return (rv);
}
+ res->parsed = true;
return (0);
}
@@ -806,7 +831,7 @@ nni_http_req_parse(nni_http_req *req, void *buf, size_t n, size_t *lenp)
break;
}
- if (req->vers != NULL) {
+ if (req->parsed) {
rv = http_parse_header(&req->hdrs, line);
} else {
rv = http_req_parse_line(req, line);
@@ -843,7 +868,7 @@ nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp)
break;
}
- if (res->vers != NULL) {
+ if (res->parsed) {
rv = http_parse_header(&res->hdrs, line);
} else {
rv = http_res_parse_line(res, line);
@@ -858,105 +883,121 @@ nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp)
return (rv);
}
+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, "Swithching 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" },
+
+ // 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" },
+
+ // 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_ENTITY_TOO_LONG, "Request Entity Too Long" },
+ { NNG_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" },
+ { NNG_HTTP_STATUS_RANGE_NOT_SATISFIABLE,
+ "Requested 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 },
+};
+
+const char *
+http_reason(uint16_t code)
+{
+ 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_res_get_reason(nni_http_res *res)
+{
+ return (res->rsn ? res->rsn : http_reason(res->code));
+}
+
int
-nni_http_res_init_error(nni_http_res **resp, uint16_t err)
+nni_http_res_set_reason(nni_http_res *res, const char *reason)
{
- char * rsn;
- char rsnbuf[80];
- char html[1024];
+ if (strcmp(reason, http_reason(res->code)) == 0) {
+ reason = NULL;
+ }
+ return (http_set_string(&res->rsn, reason));
+}
+
+int
+nni_http_res_alloc_error(nni_http_res **resp, uint16_t err)
+{
+ char html[512];
int rv;
nni_http_res *res;
- if ((rv = nni_http_res_init(&res)) != 0) {
+ if ((rv = nni_http_res_alloc(&res)) != 0) {
return (rv);
}
- // Note that it is expected that redirect URIs will update the
- // payload to reflect the target location.
- switch (err) {
- case NNI_HTTP_STATUS_STATUS_MOVED_PERMANENTLY:
- rsn = "Moved Permanently";
- break;
- case NNI_HTTP_STATUS_MULTIPLE_CHOICES:
- rsn = "Multiple Choices";
- break;
- case NNI_HTTP_STATUS_FOUND:
- rsn = "Found";
- break;
- case NNI_HTTP_STATUS_SEE_OTHER:
- rsn = "See Other";
- break;
- case NNI_HTTP_STATUS_TEMPORARY_REDIRECT:
- rsn = "Temporary Redirect";
- break;
- case NNI_HTTP_STATUS_BAD_REQUEST:
- rsn = "Bad Request";
- break;
- case NNI_HTTP_STATUS_UNAUTHORIZED:
- rsn = "Unauthorized";
- break;
- case NNI_HTTP_STATUS_PAYMENT_REQUIRED:
- rsn = "Payment Required";
- break;
- case NNI_HTTP_STATUS_NOT_FOUND:
- rsn = "Not Found";
- break;
- case NNI_HTTP_STATUS_METHOD_NOT_ALLOWED:
- // Caller must also supply an Allow: header
- rsn = "Method Not Allowed";
- break;
- case NNI_HTTP_STATUS_NOT_ACCEPTABLE:
- rsn = "Not Acceptable";
- break;
- case NNI_HTTP_STATUS_REQUEST_TIMEOUT:
- rsn = "Request Timeout";
- break;
- case NNI_HTTP_STATUS_CONFLICT:
- rsn = "Conflict";
- break;
- case NNI_HTTP_STATUS_GONE:
- rsn = "Gone";
- break;
- case NNI_HTTP_STATUS_LENGTH_REQUIRED:
- rsn = "Length Required";
- break;
- case NNI_HTTP_STATUS_PAYLOAD_TOO_LARGE:
- rsn = "Payload Too Large";
- break;
- case NNI_HTTP_STATUS_FORBIDDEN:
- rsn = "Forbidden";
- break;
- case NNI_HTTP_STATUS_URI_TOO_LONG:
- rsn = "URI Too Long";
- break;
- case NNI_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE:
- rsn = "Unsupported Media Type";
- break;
- case NNI_HTTP_STATUS_EXPECTATION_FAILED:
- rsn = "Expectation Failed";
- break;
- case NNI_HTTP_STATUS_UPGRADE_REQUIRED:
- // Caller must add "Upgrade:" header.
- rsn = "Upgrade Required";
- break;
- case NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR:
- rsn = "Internal Server Error";
- break;
- case NNI_HTTP_STATUS_HTTP_VERSION_NOT_SUPP:
- rsn = "HTTP version not supported";
- break;
- case NNI_HTTP_STATUS_NOT_IMPLEMENTED:
- rsn = "Not Implemented";
- break;
- case NNI_HTTP_STATUS_SERVICE_UNAVAILABLE:
- rsn = "Service Unavailable";
- break;
- default:
- snprintf(rsnbuf, sizeof(rsnbuf), "HTTP error code %d", err);
- rsn = rsnbuf;
- break;
- }
-
// very simple builtin error page
(void) snprintf(html, sizeof(html),
"<head><title>%d %s</title></head>"
@@ -967,14 +1008,13 @@ nni_http_res_init_error(nni_http_res **resp, uint16_t err)
"<p align=\"center\">"
"<span style=\"font-size: 24px; font-family: Arial, sans serif;\">"
"%s</span></p></body>",
- err, rsn, err, rsn);
+ err, http_reason(err), err, http_reason(err));
- if (((rv = nni_http_res_set_status(res, err, rsn)) != 0) ||
- ((rv = nni_http_res_set_version(res, "HTTP/1.1")) != 0) ||
- ((rv = nni_http_res_set_header(
+ res->code = err;
+ if (((rv = nni_http_res_set_header(
res, "Content-Type", "text/html; charset=UTF-8")) != 0) ||
((rv = nni_http_res_copy_data(res, html, strlen(html))) != 0)) {
- nni_http_res_fini(res);
+ nni_http_res_free(res);
} else {
*resp = res;
}