Files
kcgi/kcgi.c
T

1447 lines
32 KiB
C

/* $Id$ */
/*
* Copyright (c) 2012, 2014--2017 Kristaps Dzonsons <kristaps@bsd.lv>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h> /* HUGE_VAL */
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "kcgi.h"
#include "extern.h"
/*
* Maximum size of printing a signed 64-bit integer.
*/
#define INT_MAXSZ 22
const char *const kschemes[KSCHEME__MAX] = {
"aaa", /* KSCHEME_AAA */
"aaas", /* KSCHEME_AAAS */
"about", /* KSCHEME_ABOUT */
"acap", /* KSCHEME_ACAP */
"acct", /* KSCHEME_ACCT */
"cap", /* KSCHEME_CAP */
"cid", /* KSCHEME_CID */
"coap", /* KSCHEME_COAP */
"coaps", /* KSCHEME_COAPS */
"crid", /* KSCHEME_CRID */
"data", /* KSCHEME_DATA */
"dav", /* KSCHEME_DAV */
"dict", /* KSCHEME_DICT */
"dns", /* KSCHEME_DNS */
"file", /* KSCHEME_FILE */
"ftp", /* KSCHEME_FTP */
"geo", /* KSCHEME_GEO */
"go", /* KSCHEME_GO */
"gopher", /* KSCHEME_GOPHER */
"h323", /* KSCHEME_H323 */
"http", /* KSCHEME_HTTP */
"https", /* KSCHEME_HTTPS */
"iax", /* KSCHEME_IAX */
"icap", /* KSCHEME_ICAP */
"im", /* KSCHEME_IM */
"imap", /* KSCHEME_IMAP */
"info", /* KSCHEME_INFO */
"ipp", /* KSCHEME_IPP */
"iris", /* KSCHEME_IRIS */
"iris.beep", /* KSCHEME_IRIS_BEEP */
"iris.xpc", /* KSCHEME_IRIS_XPC */
"iris.xpcs", /* KSCHEME_IRIS_XPCS */
"iris.lwz", /* KSCHEME_IRIS_LWZ */
"jabber", /* KSCHEME_JABBER */
"ldap", /* KSCHEME_LDAP */
"mailto", /* KSCHEME_MAILTO */
"mid", /* KSCHEME_MID */
"msrp", /* KSCHEME_MSRP */
"msrps", /* KSCHEME_MSRPS */
"mtqp", /* KSCHEME_MTQP */
"mupdate", /* KSCHEME_MUPDATE */
"news", /* KSCHEME_NEWS */
"nfs", /* KSCHEME_NFS */
"ni", /* KSCHEME_NI */
"nih", /* KSCHEME_NIH */
"nntp", /* KSCHEME_NNTP */
"opaquelocktoken", /* KSCHEME_OPAQUELOCKTOKEN */
"pop", /* KSCHEME_POP */
"pres", /* KSCHEME_PRES */
"reload", /* KSCHEME_RELOAD */
"rtsp", /* KSCHEME_RTSP */
"rtsps", /* KSCHEME_RTSPS */
"rtspu", /* KSCHEME_RTSPU */
"service", /* KSCHEME_SERVICE */
"session", /* KSCHEME_SESSION */
"shttp", /* KSCHEME_SHTTP */
"sieve", /* KSCHEME_SIEVE */
"sip", /* KSCHEME_SIP */
"sips", /* KSCHEME_SIPS */
"sms", /* KSCHEME_SMS */
"snmp", /* KSCHEME_SNMP */
"soap.beep", /* KSCHEME_SOAP_BEEP */
"soap.beeps", /* KSCHEME_SOAP_BEEPS */
"stun", /* KSCHEME_STUN */
"stuns", /* KSCHEME_STUNS */
"tag", /* KSCHEME_TAG */
"tel", /* KSCHEME_TEL */
"telnet", /* KSCHEME_TELNET */
"tftp", /* KSCHEME_TFTP */
"thismessage", /* KSCHEME_THISMESSAGE */
"tn3270", /* KSCHEME_TN3270 */
"tip", /* KSCHEME_TIP */
"turn", /* KSCHEME_TURN */
"turns", /* KSCHEME_TURNS */
"tv", /* KSCHEME_TV */
"urn", /* KSCHEME_URN */
"vemmi", /* KSCHEME_VEMMI */
"ws", /* KSCHEME_WS */
"wss", /* KSCHEME_WSS */
"xcon", /* KSCHEME_XCON */
"xcon-userid", /* KSCHEME_XCON_USERID */
"xmlrpc.beep", /* KSCHEME_XMLRPC_BEEP */
"xmlrpc.beeps", /* KSCHEME_XMLRPC_BEEPS */
"xmpp", /* KSCHEME_XMPP */
"z39.50r", /* KSCHEME_Z39_50R */
"z39.50s", /* KSCHEME_Z39_50S */
};
const char *const kresps[KRESP__MAX] = {
"Access-Control-Allow-Origin", /* KRESP_ACCESS_CONTROL_ALLOW_ORIGIN */
"Accept-Ranges", /* KRESP_ACCEPT_RANGES */
"Age", /* KRESP_AGE */
"Allow", /* KRESP_ALLOW */
"Cache-Control", /* KRESP_CACHE_CONTROL */
"Connection", /* KRESP_CONNECTION */
"Content-Encoding", /* KRESP_CONTENT_ENCODING */
"Content-Language", /* KRESP_CONTENT_LANGUAGE */
"Content-Length", /* KRESP_CONTENT_LENGTH */
"Content-Location", /* KRESP_CONTENT_LOCATION */
"Content-MD5", /* KRESP_CONTENT_MD5 */
"Content-Disposition", /* KRESP_CONTENT_DISPOSITION */
"Content-Range", /* KRESP_CONTENT_RANGE */
"Content-Type", /* KRESP_CONTENT_TYPE */
"Date", /* KRESP_DATE */
"ETag", /* KRESP_ETAG */
"Expires", /* KRESP_EXPIRES */
"Last-Modified", /* KRESP_LAST_MODIFIED */
"Link", /* KRESP_LINK */
"Location", /* KRESP_LOCATION */
"P3P", /* KRESP_P3P */
"Pragma", /* KRESP_PRAGMA */
"Proxy-Authenticate", /* KRESP_PROXY_AUTHENTICATE */
"Refresh", /* KRESP_REFRESH */
"Retry-After", /* KRESP_RETRY_AFTER */
"Server", /* KRESP_SERVER */
"Set-Cookie", /* KRESP_SET_COOKIE */
"Status", /* KRESP_STATUS */
"Strict-Transport-Security", /* KRESP_STRICT_TRANSPORT_SECURITY */
"Trailer", /* KRESP_TRAILER */
"Transfer-Encoding", /* KRESP_TRANSFER_ENCODING */
"Upgrade", /* KRESP_UPGRADE */
"Vary", /* KRESP_VARY */
"Via", /* KRESP_VIA */
"Warning", /* KRESP_WARNING */
"WWW-Authenticate", /* KRESP_WWW_AUTHENTICATE */
"X-Frame-Options", /* KRESP_X_FRAME_OPTIONS */
};
const char *const kmimetypes[KMIME__MAX] = {
"application/x-javascript", /* KMIME_APP_JAVASCRIPT */
"application/json", /* KMIME_APP_JSON */
"application/octet-stream", /* KMIME_APP_OCTET_STREAM */
"application/pdf", /* KMIME_APP_PDF */
"application/xml", /* KMIME_APP_XML */
"application/zip", /* KMIME_APP_ZIP */
"image/gif", /* KMIME_IMAGE_GIF */
"image/jpeg", /* KMIME_IMAGE_JPEG */
"image/png", /* KMIME_IMAGE_PNG */
"image/svg+xml", /* KMIME_IMAGE_SVG_XML */
"text/calendar", /* KMIME_TEXT_CALENDAR */
"text/css", /* KMIME_TEXT_CSS */
"text/csv", /* KMIME_TEXT_CSV */
"text/html", /* KMIME_TEXT_HTML */
"text/plain", /* KMIME_TEXT_PLAIN */
"text/xml", /* KMIME_TEXT_XML */
};
const char *const khttps[KHTTP__MAX] = {
"100 Continue", /* KHTTP_100 */
"101 Switching Protocols", /* KHTTP_101 */
"103 Checkpoint", /* KHTTP_103 */
"200 OK", /* KHTTP_200 */
"201 Created", /* KHTTP_201 */
"202 Accepted", /* KHTTP_202 */
"203 Non-Authoritative Information", /* KHTTP_203 */
"204 No Content", /* KHTTP_204 */
"205 Reset Content", /* KHTTP_205 */
"206 Partial Content", /* KHTTP_206 */
"207 Multi-Status", /* KHTTP_207 */
"300 Multiple Choices", /* KHTTP_300 */
"301 Moved Permanently", /* KHTTP_301 */
"302 Found", /* KHTTP_302 */
"303 See Other", /* KHTTP_303 */
"304 Not Modified", /* KHTTP_304 */
"306 Switch Proxy", /* KHTTP_306 */
"307 Temporary Redirect", /* KHTTP_307 */
"308 Resume Incomplete", /* KHTTP_308 */
"400 Bad Request", /* KHTTP_400 */
"401 Unauthorized", /* KHTTP_401 */
"402 Payment Required", /* KHTTP_402 */
"403 Forbidden", /* KHTTP_403 */
"404 Not Found", /* KHTTP_404 */
"405 Method Not Allowed", /* KHTTP_405 */
"406 Not Acceptable", /* KHTTP_406 */
"407 Proxy Authentication Required", /* KHTTP_407 */
"408 Request Timeout", /* KHTTP_408 */
"409 Conflict", /* KHTTP_409 */
"410 Gone", /* KHTTP_410 */
"411 Length Required", /* KHTTP_411 */
"412 Precondition Failed", /* KHTTP_412 */
"413 Request Entity Too Large", /* KHTTP_413 */
"414 Request-URI Too Long", /* KHTTP_414 */
"415 Unsupported Media Type", /* KHTTP_415 */
"416 Requested Range Not Satisfiable", /* KHTTP_416 */
"417 Expectation Failed", /* KHTTP_417 */
"424 Failed Dependency", /* KHTTP_424 */
"428 Precondition Required", /* KHTTP_428 */
"429 Too Many Requests", /* KHTTP_429 */
"431 Request Header Fields Too Large", /* KHTTP_431 */
"500 Internal Server Error", /* KHTTP_500 */
"501 Not Implemented", /* KHTTP_501 */
"502 Bad Gateway", /* KHTTP_502 */
"503 Service Unavailable", /* KHTTP_503 */
"504 Gateway Timeout", /* KHTTP_504 */
"505 HTTP Version Not Supported", /* KHTTP_505 */
"507 Insufficient Storage", /* KHTTP_507 */
"511 Network Authentication Required", /* KHTTP_511 */
};
/*
* This doesn't have a preset size.
*/
const struct kmimemap ksuffixmap[] = {
{ "css", KMIME_TEXT_CSS },
{ "csv", KMIME_TEXT_CSV },
{ "gif", KMIME_IMAGE_GIF },
{ "htm", KMIME_TEXT_HTML },
{ "html", KMIME_TEXT_HTML },
{ "ical", KMIME_TEXT_CALENDAR },
{ "icalendar", KMIME_TEXT_CALENDAR },
{ "ics", KMIME_TEXT_CALENDAR },
{ "ifb", KMIME_TEXT_CALENDAR },
{ "jpg", KMIME_IMAGE_JPEG },
{ "jpeg", KMIME_IMAGE_JPEG },
{ "js", KMIME_APP_JAVASCRIPT },
{ "json", KMIME_APP_JSON },
{ "pdf", KMIME_APP_PDF },
{ "png", KMIME_IMAGE_PNG },
{ "shtml", KMIME_TEXT_HTML },
{ "svg", KMIME_IMAGE_SVG_XML },
{ "svgz", KMIME_IMAGE_SVG_XML },
{ "txt", KMIME_TEXT_PLAIN },
{ "xml", KMIME_TEXT_XML },
{ "zip", KMIME_APP_ZIP },
{ NULL, KMIME__MAX },
};
/*
* Default MIME suffix per type.
*/
const char *const ksuffixes[KMIME__MAX] = {
"js", /* KMIME_APP_JAVASCRIPT */
"json", /* KMIME_APP_JSON */
NULL, /* KMIME_APP_OCTET_STREAM */
"pdf", /* KMIME_APP_PDF */
"xml", /* KMIME_APP_XML */
"zip", /* KMIME_APP_ZIP */
"gif", /* KMIME_IMAGE_GIF */
"jpg", /* KMIME_IMAGE_JPEG */
"png", /* KMIME_IMAGE_PNG */
"svg", /* KMIME_IMAGE_PNG */
"ics", /* KMIME_TEXT_CALENDAR */
"css", /* KMIME_TEXT_CSS */
"csv", /* KMIME_TEXT_CSV */
"html", /* KMIME_TEXT_HTML */
"txt", /* KMIME_TEXT_PLAIN */
"xml", /* KMIME_TEXT_XML */
};
const char *const kerrors[] = {
"success", /* KCGI_OK */
"cannot allocate memory", /* KCGI_ENOMEM */
"FastCGI exit", /* KCGI_EXIT */
"end-point connection closed", /* KCGI_HUP */
"too many open sockets", /* KCGI_ENFILE */
"failed to fork child", /* KCGI_EAGAIN */
"internal error", /* KCGI_FORM */
"system error", /* KCGI_SYSTEM */
"writer error", /* KCGI_WRITER */
};
const char *
kcgi_strerror(enum kcgi_err er)
{
assert(er <= KCGI_WRITER);
return kerrors[er];
}
/*
* Safe strdup(): don't return on exhaustion.
*/
char *
kstrdup(const char *cp)
{
char *p;
if ((p = kxstrdup(cp)) != NULL)
return p;
exit(EXIT_FAILURE);
}
/*
* Safe realloc(): don't return on exhaustion.
*/
void *
krealloc(void *pp, size_t sz)
{
char *p;
if ((p = kxrealloc(pp, sz)) != NULL)
return p;
exit(EXIT_FAILURE);
}
/*
* Safe reallocarray(): don't return on exhaustion.
*/
void *
kreallocarray(void *pp, size_t nm, size_t sz)
{
char *p;
if ((p = kxreallocarray(pp, nm, sz)) != NULL)
return p;
exit(EXIT_FAILURE);
}
/*
* Safe asprintf(): don't return on exhaustion.
*/
int
kasprintf(char **p, const char *fmt, ...)
{
va_list ap;
int len;
va_start(ap, fmt);
len = XVASPRINTF(p, fmt, ap);
va_end(ap);
if (len >= 0)
return len;
exit(EXIT_FAILURE);
}
/*
* Safe vasprintf(): don't return on exhaustion.
*/
int
kvasprintf(char **p, const char *fmt, va_list ap)
{
int len;
if ((len = XVASPRINTF(p, fmt, ap)) >= 0)
return len;
exit(EXIT_FAILURE);
}
/*
* Safe calloc(): don't return on exhaustion.
*/
void *
kcalloc(size_t nm, size_t sz)
{
char *p;
if ((p = kxcalloc(nm, sz)) != NULL)
return p;
exit(EXIT_FAILURE);
}
/*
* Safe malloc(): don't return on exhaustion.
*/
void *
kmalloc(size_t sz)
{
char *p;
if ((p = kxmalloc(sz)) != NULL)
return p;
exit(EXIT_FAILURE);
}
/*
* Deprecated form.
*/
char *
kutil_urlencode(const char *cp)
{
return (cp == NULL) ? NULL : khttp_urlencode(cp);
}
char *
khttp_urlencode(const char *cp)
{
char *p;
char ch;
size_t sz, cur;
if (cp == NULL)
return kxstrdup("");
/*
* Leave three bytes per input byte for encoding.
* This ensures we needn't range-check.
* First check whether our size overflows.
* We do this here because we need our size!
*/
sz = strlen(cp) + 1;
if (SIZE_MAX / 3 < sz) {
kutil_warnx(NULL, NULL, "multiplicative overflow");
return NULL;
}
if ((p = kxcalloc(sz, 3)) == NULL)
return NULL;
for (cur = 0; (ch = *cp) != '\0'; cp++) {
if (isalnum((unsigned char)ch) || ch == '-' ||
ch == '_' || ch == '.' || ch == '~') {
p[cur++] = ch;
continue;
} else if (' ' == ch) {
p[cur++] = '+';
continue;
}
cur += snprintf(p + cur, 4, "%%%.2hhX",
(unsigned char)ch);
}
return p;
}
/*
* Deprecated form.
*/
enum kcgi_err
kutil_urldecode_inplace(char *p)
{
return khttp_urldecode_inplace(p);
}
enum kcgi_err
khttp_urldecode_inplace(char *p)
{
char c, d;
const char *tail;
if (p == NULL)
return KCGI_FORM;
/*
* Keep track of two positions: "p", where we'll write the
* decoded results, and "tail", which is from where we'll
* decode hex or copy data.
*/
for (tail = p; (c = *tail) != '\0'; *p++ = c) {
if (c != '%') {
if (c == '+')
c = ' ';
tail++;
continue;
}
/*
* Read hex '%xy' as two unsigned chars "c" and "d" then
* combine them back into "c".
*/
if (sscanf(tail + 1, "%1hhx%1hhx", &d, &c) != 2 ||
(c |= d << 4) == '\0') {
kutil_warnx(NULL, NULL,
"malformed percent-encoded sequence");
return KCGI_FORM;
}
tail += 3;
}
*p = '\0';
return KCGI_OK;
}
/*
* Deprecated form.
*/
enum kcgi_err
kutil_urldecode(const char *src, char **dst)
{
return khttp_urldecode(src, dst);
}
enum kcgi_err
khttp_urldecode(const char *src, char **dst)
{
enum kcgi_err er;
/* In case of error. */
if (dst != NULL)
*dst = NULL;
if (src == NULL || dst == NULL)
return KCGI_FORM;
if ((*dst = kxstrdup(src)) == NULL)
return KCGI_ENOMEM;
if ((er = khttp_urldecode_inplace(*dst)) == KCGI_OK)
return KCGI_OK;
/* If we have decoding errors, clear the output. */
free(*dst);
*dst = NULL;
return er;
}
/*
* Append key-value triplets in "ap" to "p".
* Reallocate as necessary.
* A NULL key signifies termination of the list.
* Returns the URL string or NULL on memory failure.
*/
static char *
khttp_url_query_string(char *p, va_list ap)
{
char *pp, *keyp, *valp;
size_t total, count = 0;
total = strlen(p) + 1;
while ((pp = va_arg(ap, char *)) != NULL) {
if ((keyp = khttp_urlencode(pp)) == NULL) {
free(p);
return NULL;
}
valp = khttp_urlencode(va_arg(ap, char *));
if (valp == NULL) {
free(p);
free(keyp);
return NULL;
}
/* Size for key, value, ? or &, and =. */
/* FIXME: check for overflow! */
total += strlen(keyp) + strlen(valp) + 2;
if ((pp = kxrealloc(p, total)) == NULL) {
free(p);
free(keyp);
free(valp);
return NULL;
}
p = pp;
if (count > 0)
(void)strlcat(p, "&", total);
else
(void)strlcat(p, "?", total);
(void)strlcat(p, keyp, total);
(void)strlcat(p, "=", total);
(void)strlcat(p, valp, total);
free(keyp);
free(valp);
count++;
}
return p;
}
/*
* Append key-type-value triplets in "ap" to "p".
* Reallocate as necessary.
* A NULL key signifies termination of the list.
* Returns the URL string or NULL on memory failure.
*/
static char *
khttp_url_query_stringx(char *p, va_list ap)
{
char *pp, *keyp, *valp, *valpp;
size_t total, count = 0;
char buf[256]; /* max double/int64_t */
total = strlen(p) + 1;
while ((pp = va_arg(ap, char *)) != NULL) {
if ((keyp = khttp_urlencode(pp)) == NULL) {
free(p);
return NULL;
}
valp = valpp = NULL;
switch (va_arg(ap, enum kattrx)) {
case KATTRX_STRING:
valp = khttp_urlencode(va_arg(ap, char *));
valpp = valp;
break;
case KATTRX_INT:
(void)snprintf(buf, sizeof(buf),
"%" PRId64, va_arg(ap, int64_t));
valp = buf;
break;
case KATTRX_DOUBLE:
(void)snprintf(buf, sizeof(buf),
"%g", va_arg(ap, double));
valp = buf;
break;
default:
free(p);
free(keyp);
return NULL;
}
if (valp == NULL) {
free(p);
free(keyp);
return NULL;
}
/* Size for key, value, ? or &, and =. */
/* FIXME: check for overflow! */
total += strlen(keyp) + strlen(valp) + 2;
if ((pp = kxrealloc(p, total)) == NULL) {
free(p);
free(keyp);
free(valpp);
return NULL;
}
p = pp;
if (count > 0)
(void)strlcat(p, "&", total);
else
(void)strlcat(p, "?", total);
(void)strlcat(p, keyp, total);
(void)strlcat(p, "=", total);
(void)strlcat(p, valp, total);
free(keyp);
free(valpp);
count++;
}
return p;
}
/*
* Deprecated form.
*/
char *
kutil_urlabs(enum kscheme scheme,
const char *host, uint16_t port, const char *path)
{
char *p;
XASPRINTF(&p, "%s://%s:%" PRIu16 "%s",
kschemes[scheme], host, port, path);
return p;
}
char *
khttp_urlabs(enum kscheme scheme, const char *host,
uint16_t port, const char *path, ...)
{
char *p;
va_list ap;
va_start(ap, path);
p = khttp_vurlabs(scheme, host, port, path, ap);
va_end(ap);
return p;
}
char *
khttp_vurlabs(enum kscheme scheme, const char *host,
uint16_t port, const char *path, va_list ap)
{
char *p;
int len;
if (host == NULL || host[0] == '\0') {
len = XASPRINTF(&p, "%s:%s", kschemes[scheme],
path == NULL ? "" : path);
} else if (port == 0) {
len = XASPRINTF(&p, "%s://%s%s%s",
kschemes[scheme], host,
path != NULL && path[0] != '\0' &&
path[0] != '/' ? "/" : "",
path == NULL ? "" : path);
} else {
len = XASPRINTF(&p, "%s://%s:%" PRIu16 "%s%s",
kschemes[scheme], host, port,
path != NULL && path[0] != '\0' &&
path[0] != '/' ? "/" : "",
path == NULL ? "" : path);
}
if (len == -1)
return NULL;
return khttp_url_query_string(p, ap);
}
/*
* Deprecated form.
*/
char *
kutil_urlpartx(struct kreq *req, const char *path,
const char *mime, const char *page, ...)
{
char *ret;
va_list ap;
if (page == NULL)
return NULL;
va_start(ap, page);
ret = khttp_vurlpartx(path, mime, page, ap);
va_end(ap);
return ret;
}
char *
khttp_urlpartx(const char *path,
const char *mime, const char *page, ...)
{
char *ret;
va_list ap;
va_start(ap, page);
ret = khttp_vurlpartx(path, mime, page, ap);
va_end(ap);
return ret;
}
char *
khttp_vurlpartx(const char *path,
const char *mime, const char *page, va_list ap)
{
int len;
char *p, *pageenc = NULL;
if (page != NULL && (pageenc = khttp_urlencode(page)) == NULL)
return NULL;
if ((mime == NULL || mime[0] == '\0') ||
(page == NULL || page[0] == '\0'))
len = XASPRINTF(&p, "%s%s%s",
path != NULL ? path : "",
path != NULL ? "/" : "",
pageenc != NULL ? pageenc : "");
else {
assert(pageenc != NULL);
len = XASPRINTF(&p, "%s%s%s.%s",
path != NULL ? path : "",
path != NULL ? "/" : "", pageenc, mime);
}
free(pageenc);
pageenc = NULL;
if (len == -1)
return NULL;
return khttp_url_query_stringx(p, ap);
}
/*
* Deprecated form.
*/
char *
kutil_urlpart(struct kreq *req, const char *path,
const char *mime, const char *page, ...)
{
char *ret;
va_list ap;
if (page == NULL)
return NULL;
va_start(ap, page);
ret = khttp_vurlpart(path, mime, page, ap);
va_end(ap);
return ret;
}
char *
khttp_urlpart(const char *path,
const char *mime, const char *page, ...)
{
char *ret;
va_list ap;
va_start(ap, page);
ret = khttp_vurlpart(path, mime, page, ap);
va_end(ap);
return ret;
}
char *
khttp_vurlpart(const char *path,
const char *mime, const char *page, va_list ap)
{
char *p, *pageenc = NULL;
int len;
if (page != NULL && (pageenc = khttp_urlencode(page)) == NULL)
return NULL;
/*
* Only append the MIME suffix if we have it AND if the page is
* non-NULL and non-empty.
*/
if ((mime == NULL || mime[0] == '\0') ||
(page == NULL || page[0] == '\0'))
len = XASPRINTF(&p, "%s%s%s",
path != NULL ? path : "",
path != NULL ? "/" : "",
pageenc != NULL ? pageenc : "");
else {
assert(pageenc != NULL);
len = XASPRINTF(&p, "%s%s%s.%s",
path != NULL ? path : "",
path != NULL ? "/" : "", pageenc, mime);
}
free(pageenc);
pageenc = NULL;
if (len == -1)
return NULL;
return khttp_url_query_string(p, ap);
}
static void
kpair_free(struct kpair *p, size_t sz)
{
size_t i;
for (i = 0; i < sz; i++) {
free(p[i].key);
free(p[i].val);
free(p[i].file);
free(p[i].ctype);
free(p[i].xcode);
}
free(p);
}
void
kreq_free(struct kreq *req)
{
size_t i;
for (i = 0; i < req->reqsz; i++) {
free(req->reqs[i].key);
free(req->reqs[i].val);
}
free(req->reqs);
kpair_free(req->cookies, req->cookiesz);
kpair_free(req->fields, req->fieldsz);
free(req->path);
free(req->fullpath);
free(req->remote);
free(req->host);
free(req->cookiemap);
free(req->cookienmap);
free(req->fieldmap);
free(req->fieldnmap);
free(req->suffix);
free(req->pagename);
free(req->pname);
free(req->rawauth.digest);
if (req->rawauth.type == KAUTH_DIGEST) {
free(req->rawauth.d.digest.user);
free(req->rawauth.d.digest.uri);
free(req->rawauth.d.digest.realm);
free(req->rawauth.d.digest.nonce);
free(req->rawauth.d.digest.cnonce);
free(req->rawauth.d.digest.response);
free(req->rawauth.d.digest.opaque);
} else if (req->rawauth.type == KAUTH_BASIC)
free(req->rawauth.d.basic.response);
}
enum kcgi_err
khttp_parse(struct kreq *req,
const struct kvalid *keys, size_t keysz,
const char *const *pages, size_t pagesz,
size_t defpage)
{
return khttp_parsex(req, ksuffixmap, kmimetypes,
KMIME__MAX, keys, keysz, pages, pagesz,
KMIME_TEXT_HTML, defpage, NULL, NULL, 0, NULL);
}
enum kcgi_err
khttp_parsex(struct kreq *req,
const struct kmimemap *suffixmap,
const char *const *mimes, size_t mimesz,
const struct kvalid *keys, size_t keysz,
const char *const *pages, size_t pagesz,
size_t defmime, size_t defpage, void *arg,
void (*argfree)(void *arg), unsigned debugging,
const struct kopts *opts)
{
const struct kmimemap *mm;
enum kcgi_err kerr;
int er;
struct kopts kopts;
int work_dat[2];
pid_t work_pid;
memset(req, 0, sizeof(struct kreq));
/*
* We'll be using poll(2) for reading our HTTP document, so this
* must be non-blocking in order to make the reads not spin the
* CPU.
*/
if (kxsocketprep(STDIN_FILENO) != KCGI_OK)
return KCGI_SYSTEM;
if (kxsocketpair(AF_UNIX, SOCK_STREAM, 0, work_dat) != KCGI_OK)
return KCGI_SYSTEM;
if ((work_pid = fork()) == -1) {
er = errno;
kutil_warn(NULL, NULL, "fork");
close(work_dat[KWORKER_PARENT]);
close(work_dat[KWORKER_CHILD]);
return (er == EAGAIN) ? KCGI_EAGAIN : KCGI_ENOMEM;
} else if (work_pid == 0) {
if (argfree != NULL)
(*argfree)(arg);
close(STDOUT_FILENO);
close(work_dat[KWORKER_PARENT]);
er = EXIT_SUCCESS;
if (!ksandbox_init_child(SAND_WORKER,
work_dat[KWORKER_CHILD], -1, -1, -1))
er = EXIT_FAILURE;
else if (kworker_child(work_dat[KWORKER_CHILD],
keys, keysz, mimes, mimesz, debugging) != KCGI_OK)
er = EXIT_FAILURE;
close(work_dat[KWORKER_CHILD]);
_exit(er);
/* NOTREACHED */
}
close(work_dat[KWORKER_CHILD]);
work_dat[KWORKER_CHILD] = -1;
if (opts == NULL)
kopts.sndbufsz = -1;
else
memcpy(&kopts, opts, sizeof(struct kopts));
if (kopts.sndbufsz < 0)
kopts.sndbufsz = 1024 * 8;
kerr = KCGI_ENOMEM;
/*
* After this point, all errors should use "goto err", which
* will properly free up our memory and close any extant file
* descriptors.
* Also, we're running our child in the background, so make sure
* that it gets killed!
*/
req->arg = arg;
req->keys = keys;
req->keysz = keysz;
req->kdata = kdata_alloc(-1, -1, 0, debugging, &kopts);
if (req->kdata == NULL)
goto err;
if (keysz) {
req->cookiemap = kxcalloc(keysz, sizeof(struct kpair *));
if (req->cookiemap == NULL)
goto err;
req->cookienmap = kxcalloc(keysz, sizeof(struct kpair *));
if (req->cookienmap == NULL)
goto err;
req->fieldmap = kxcalloc(keysz, sizeof(struct kpair *));
if (req->fieldmap == NULL)
goto err;
req->fieldnmap = kxcalloc(keysz, sizeof(struct kpair *));
if (req->fieldnmap == NULL)
goto err;
}
/*
* Now read the input fields from the child and conditionally
* assign them to our lookup table.
*/
kerr = kworker_parent
(work_dat[KWORKER_PARENT], req, 1, mimesz);
if (kerr != KCGI_OK)
goto err;
/* Look up page type from component. */
req->page = defpage;
if (*req->pagename != '\0')
for (req->page = 0; req->page < pagesz; req->page++)
if (strcasecmp
(pages[req->page], req->pagename) == 0)
break;
/*
* Look up the MIME type, defaulting to defmime if none.
* If we can't find it, use the maximum (mimesz).
*/
req->mime = defmime;
if (*req->suffix != '\0') {
for (mm = suffixmap; mm->name != NULL; mm++)
if (strcasecmp(mm->name, req->suffix) == 0) {
req->mime = mm->mime;
break;
}
if (mm->name == NULL)
req->mime = mimesz;
}
close(work_dat[KWORKER_PARENT]);
work_dat[KWORKER_PARENT] = -1;
kerr = kxwaitpid(work_pid);
work_pid = -1;
if (kerr != KCGI_OK)
goto err;
return kerr;
err:
assert(kerr != KCGI_OK);
if (work_dat[KWORKER_PARENT] != -1)
close(work_dat[KWORKER_PARENT]);
if (work_pid != -1)
kxwaitpid(work_pid);
kdata_free(req->kdata, 0);
req->kdata = NULL;
kreq_free(req);
return kerr;
}
void
kutil_invalidate(struct kreq *r, struct kpair *kp)
{
struct kpair *p, *lastp;
size_t i;
if (kp == NULL)
return;
kp->type = KPAIR__MAX;
kp->state = KPAIR_INVALID;
memset(&kp->parsed, 0, sizeof(union parsed));
/* We're not bucketed. */
if ((i = kp->keypos) == r->keysz)
return;
/* Is it in our fieldmap? */
if (r->fieldmap[i] != NULL) {
if (kp == r->fieldmap[i]) {
r->fieldmap[i] = kp->next;
kp->next = r->fieldnmap[i];
r->fieldnmap[i] = kp;
return;
}
lastp = r->fieldmap[i];
p = lastp->next;
for ( ; p != NULL; lastp = p, p = p->next)
if (kp == p) {
lastp->next = kp->next;
kp->next = r->fieldnmap[i];
r->fieldnmap[i] = kp;
return;
}
}
/* ...cookies? */
if (r->cookiemap[i] != NULL) {
if (kp == r->cookiemap[i]) {
r->cookiemap[i] = kp->next;
kp->next = r->cookienmap[i];
r->cookienmap[i] = kp;
return;
}
lastp = r->cookiemap[i];
p = lastp->next;
for ( ; p != NULL; lastp = p, p = p->next)
if (kp == p) {
lastp->next = kp->next;
kp->next = r->cookienmap[i];
r->cookienmap[i] = kp;
return;
}
}
}
void
khttp_child_free(struct kreq *req)
{
kdata_free(req->kdata, 0);
req->kdata = NULL;
kreq_free(req);
}
void
khttp_free(struct kreq *req)
{
kdata_free(req->kdata, 1);
req->kdata = NULL;
kreq_free(req);
}
/*
* Trim leading and trailing whitespace from a word.
* Note that this returns a pointer within "val" and optionally sets the
* NUL-terminator, so don't free() the returned value.
*/
static char *
trim(char *val)
{
char *cp;
while (isspace((unsigned char)*val))
val++;
cp = strchr(val, '\0') - 1;
while (cp > val && isspace((unsigned char)*cp))
*cp-- = '\0';
return val;
}
/*
* Simple email address validation: this is NOT according to the spec,
* but a simple heuristic look at the address.
* Note that this lowercases the mail address.
*/
static char *
valid_email(char *p)
{
char *cp, *start;
size_t sz;
/*
* Trim all white-space before and after.
* Length check (min: a@b, max: 254 bytes).
* Make sure we have an at-sign.
* Make sure at signs aren't at the start or end.
*/
cp = start = trim(p);
if ((sz = strlen(cp)) < 3 || sz > 254)
return NULL;
if (cp[0] == '@' || cp[sz - 1] == '@')
return NULL;
if (strchr(cp, '@') == NULL)
return NULL;
for (cp = start; *cp != '\0'; cp++)
*cp = tolower((unsigned char)*cp);
return start;
}
int
kvalid_date(struct kpair *kp)
{
int mday, mon, year;
if (kp->valsz != 10 || kp->val[10] != '\0' ||
!isdigit((unsigned char)kp->val[0]) ||
!isdigit((unsigned char)kp->val[1]) ||
!isdigit((unsigned char)kp->val[2]) ||
!isdigit((unsigned char)kp->val[3]) ||
kp->val[4] != '-' ||
!isdigit((unsigned char)kp->val[5]) ||
!isdigit((unsigned char)kp->val[6]) ||
kp->val[7] != '-' ||
!isdigit((unsigned char)kp->val[8]) ||
!isdigit((unsigned char)kp->val[9]))
return 0;
/*
* We know these are all positive integer values, so no need to
* use the more complicated interface for this.
*/
year = atoi(&kp->val[0]);
mon = atoi(&kp->val[5]);
mday = atoi(&kp->val[8]);
if (!khttp_date2epoch(&kp->parsed.i, mday, mon, year))
return 0;
kp->type = KPAIR_INTEGER;
return 1;
}
int
kvalid_stringne(struct kpair *p)
{
/*
* To check if we're a valid string, simply make sure that the
* NUL-terminator is where we expect it to be.
*/
if (strlen(p->val) != p->valsz || p->valsz == 0)
return 0;
p->type = KPAIR_STRING;
p->parsed.s = p->val;
return 1;
}
int
kvalid_string(struct kpair *p)
{
/*
* To check if we're a valid string, simply make sure that the
* NUL-terminator is where we expect it to be.
*/
if (strlen(p->val) != p->valsz)
return 0;
p->type = KPAIR_STRING;
p->parsed.s = p->val;
return 1;
}
int
kvalid_email(struct kpair *p)
{
if (!kvalid_stringne(p))
return 0;
return (p->parsed.s = valid_email(p->val)) != NULL;
}
int
kvalid_udouble(struct kpair *p)
{
return kvalid_double(p) && p->parsed.d > 0.0;
}
int
kvalid_double(struct kpair *p)
{
char *ep;
const char *nval;
double lval;
int er;
if (!kvalid_stringne(p))
return 0;
/*
* We might get an empty string from trim, which constitutes a
* valid double (!?), so double check that the string is
* non-empty after trimming whitespace.
* We trim white-space because strtod(3) accepts white-space
* before but not after the string.
*/
nval = trim(p->val);
if (nval[0] == '\0')
return 0;
/* Save errno so we can restore it later. */
er = errno;
errno = 0;
lval = strtod(nval, &ep);
if (errno == ERANGE)
return 0;
/* Restore errno. */
errno = er;
if (*ep != '\0')
return 0;
p->parsed.d = lval;
p->type = KPAIR_DOUBLE;
return 1;
}
int
kvalid_int(struct kpair *p)
{
const char *ep;
if (!kvalid_stringne(p))
return 0;
p->parsed.i = strtonum
(trim(p->val), INT64_MIN, INT64_MAX, &ep);
p->type = KPAIR_INTEGER;
return ep == NULL;
}
int
kvalid_bit(struct kpair *p)
{
if (!kvalid_uint(p))
return 0;
return p->parsed.i <= 64;
}
int
kvalid_uint(struct kpair *p)
{
const char *ep;
p->parsed.i = strtonum(trim(p->val), 0, INT64_MAX, &ep);
p->type = KPAIR_INTEGER;
return ep == NULL;
}
enum kcgi_err
kcgi_buf_write(const char *s, size_t sz, void *arg)
{
struct kcgi_buf *b = arg;
void *pp;
/* Let empty/NULL pass through. */
if (s == NULL || sz == 0)
return KCGI_OK;
/* Grow the buffer and leave some slop space. */
if (b->sz + sz + 1 > b->maxsz) {
b->maxsz = b->sz + sz + 1 +
(b->growsz == 0 ? 1024 : b->growsz);
if ((pp = kxrealloc(b->buf, b->maxsz)) == NULL)
return KCGI_ENOMEM;
b->buf = pp;
}
/* Always NUL-terminate even though we accept binary data. */
memcpy(&b->buf[b->sz], s, sz);
b->sz += sz;
b->buf[b->sz] = '\0';
return KCGI_OK;
}
enum kcgi_err
kcgi_buf_printf(struct kcgi_buf *buf, const char *fmt, ...)
{
char *nbuf;
int len;
va_list ap;
enum kcgi_err er;
/* Let this bogus case pass through. */
if (fmt == NULL)
return KCGI_OK;
/* Allocate temporary buffer. */
va_start(ap, fmt);
len = XVASPRINTF(&nbuf, fmt, ap);
va_end(ap);
if (len == -1)
return KCGI_ENOMEM;
/* Write and free. */
er = kcgi_buf_write(nbuf, (size_t)len, buf);
free(nbuf);
return er;
}
enum kcgi_err
kcgi_buf_putc(struct kcgi_buf *buf, char c)
{
return kcgi_buf_write(&c, 1, buf);
}
enum kcgi_err
kcgi_buf_puts(struct kcgi_buf *buf, const char *cp)
{
if (cp == NULL)
return KCGI_OK;
return kcgi_buf_write(cp, strlen(cp), buf);
}