mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
1447 lines
32 KiB
C
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);
|
|
}
|