ksgi
/
kcgi.c
1456 строк · 31.7 Кб
1/* $Id$ */
2/*
3* Copyright (c) 2012, 2014--2017 Kristaps Dzonsons <kristaps@bsd.lv>
4*
5* Permission to use, copy, modify, and distribute this software for any
6* purpose with or without fee is hereby granted, provided that the above
7* copyright notice and this permission notice appear in all copies.
8*
9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17#include "config.h"18
19#include <sys/mman.h>20#include <sys/stat.h>21#include <sys/socket.h>22
23#include <assert.h>24#include <ctype.h>25#include <errno.h>26#include <fcntl.h>27#include <inttypes.h>28#include <limits.h>29#include <math.h> /* HUGE_VAL */30#include <signal.h>31#include <stdarg.h>32#include <stdint.h>33#include <stdio.h>34#include <stdlib.h>35#include <string.h>36#include <unistd.h>37
38#include "kcgi.h"39#include "extern.h"40
41/*
42* Maximum size of printing a signed 64-bit integer.
43*/
44#define INT_MAXSZ 2245
46const char *const kschemes[KSCHEME__MAX] = {47"aaa", /* KSCHEME_AAA */48"aaas", /* KSCHEME_AAAS */49"about", /* KSCHEME_ABOUT */50"acap", /* KSCHEME_ACAP */51"acct", /* KSCHEME_ACCT */52"cap", /* KSCHEME_CAP */53"cid", /* KSCHEME_CID */54"coap", /* KSCHEME_COAP */55"coaps", /* KSCHEME_COAPS */56"crid", /* KSCHEME_CRID */57"data", /* KSCHEME_DATA */58"dav", /* KSCHEME_DAV */59"dict", /* KSCHEME_DICT */60"dns", /* KSCHEME_DNS */61"file", /* KSCHEME_FILE */62"ftp", /* KSCHEME_FTP */63"geo", /* KSCHEME_GEO */64"go", /* KSCHEME_GO */65"gopher", /* KSCHEME_GOPHER */66"h323", /* KSCHEME_H323 */67"http", /* KSCHEME_HTTP */68"https", /* KSCHEME_HTTPS */69"iax", /* KSCHEME_IAX */70"icap", /* KSCHEME_ICAP */71"im", /* KSCHEME_IM */72"imap", /* KSCHEME_IMAP */73"info", /* KSCHEME_INFO */74"ipp", /* KSCHEME_IPP */75"iris", /* KSCHEME_IRIS */76"iris.beep", /* KSCHEME_IRIS_BEEP */77"iris.xpc", /* KSCHEME_IRIS_XPC */78"iris.xpcs", /* KSCHEME_IRIS_XPCS */79"iris.lwz", /* KSCHEME_IRIS_LWZ */80"jabber", /* KSCHEME_JABBER */81"ldap", /* KSCHEME_LDAP */82"mailto", /* KSCHEME_MAILTO */83"mid", /* KSCHEME_MID */84"msrp", /* KSCHEME_MSRP */85"msrps", /* KSCHEME_MSRPS */86"mtqp", /* KSCHEME_MTQP */87"mupdate", /* KSCHEME_MUPDATE */88"news", /* KSCHEME_NEWS */89"nfs", /* KSCHEME_NFS */90"ni", /* KSCHEME_NI */91"nih", /* KSCHEME_NIH */92"nntp", /* KSCHEME_NNTP */93"opaquelocktoken", /* KSCHEME_OPAQUELOCKTOKEN */94"pop", /* KSCHEME_POP */95"pres", /* KSCHEME_PRES */96"reload", /* KSCHEME_RELOAD */97"rtsp", /* KSCHEME_RTSP */98"rtsps", /* KSCHEME_RTSPS */99"rtspu", /* KSCHEME_RTSPU */100"service", /* KSCHEME_SERVICE */101"session", /* KSCHEME_SESSION */102"shttp", /* KSCHEME_SHTTP */103"sieve", /* KSCHEME_SIEVE */104"sip", /* KSCHEME_SIP */105"sips", /* KSCHEME_SIPS */106"sms", /* KSCHEME_SMS */107"snmp", /* KSCHEME_SNMP */108"soap.beep", /* KSCHEME_SOAP_BEEP */109"soap.beeps", /* KSCHEME_SOAP_BEEPS */110"stun", /* KSCHEME_STUN */111"stuns", /* KSCHEME_STUNS */112"tag", /* KSCHEME_TAG */113"tel", /* KSCHEME_TEL */114"telnet", /* KSCHEME_TELNET */115"tftp", /* KSCHEME_TFTP */116"thismessage", /* KSCHEME_THISMESSAGE */117"tn3270", /* KSCHEME_TN3270 */118"tip", /* KSCHEME_TIP */119"turn", /* KSCHEME_TURN */120"turns", /* KSCHEME_TURNS */121"tv", /* KSCHEME_TV */122"urn", /* KSCHEME_URN */123"vemmi", /* KSCHEME_VEMMI */124"ws", /* KSCHEME_WS */125"wss", /* KSCHEME_WSS */126"xcon", /* KSCHEME_XCON */127"xcon-userid", /* KSCHEME_XCON_USERID */128"xmlrpc.beep", /* KSCHEME_XMLRPC_BEEP */129"xmlrpc.beeps", /* KSCHEME_XMLRPC_BEEPS */130"xmpp", /* KSCHEME_XMPP */131"z39.50r", /* KSCHEME_Z39_50R */132"z39.50s", /* KSCHEME_Z39_50S */133};134
135const char *const kresps[KRESP__MAX] = {136"Access-Control-Allow-Origin", /* KRESP_ACCESS_CONTROL_ALLOW_ORIGIN */137"Accept-Ranges", /* KRESP_ACCEPT_RANGES */138"Age", /* KRESP_AGE */139"Allow", /* KRESP_ALLOW */140"Cache-Control", /* KRESP_CACHE_CONTROL */141"Connection", /* KRESP_CONNECTION */142"Content-Encoding", /* KRESP_CONTENT_ENCODING */143"Content-Language", /* KRESP_CONTENT_LANGUAGE */144"Content-Length", /* KRESP_CONTENT_LENGTH */145"Content-Location", /* KRESP_CONTENT_LOCATION */146"Content-MD5", /* KRESP_CONTENT_MD5 */147"Content-Disposition", /* KRESP_CONTENT_DISPOSITION */148"Content-Range", /* KRESP_CONTENT_RANGE */149"Content-Type", /* KRESP_CONTENT_TYPE */150"Date", /* KRESP_DATE */151"ETag", /* KRESP_ETAG */152"Expires", /* KRESP_EXPIRES */153"Last-Modified", /* KRESP_LAST_MODIFIED */154"Link", /* KRESP_LINK */155"Location", /* KRESP_LOCATION */156"P3P", /* KRESP_P3P */157"Pragma", /* KRESP_PRAGMA */158"Proxy-Authenticate", /* KRESP_PROXY_AUTHENTICATE */159"Refresh", /* KRESP_REFRESH */160"Retry-After", /* KRESP_RETRY_AFTER */161"Server", /* KRESP_SERVER */162"Set-Cookie", /* KRESP_SET_COOKIE */163"Status", /* KRESP_STATUS */164"Strict-Transport-Security", /* KRESP_STRICT_TRANSPORT_SECURITY */165"Trailer", /* KRESP_TRAILER */166"Transfer-Encoding", /* KRESP_TRANSFER_ENCODING */167"Upgrade", /* KRESP_UPGRADE */168"Vary", /* KRESP_VARY */169"Via", /* KRESP_VIA */170"Warning", /* KRESP_WARNING */171"WWW-Authenticate", /* KRESP_WWW_AUTHENTICATE */172"X-Frame-Options", /* KRESP_X_FRAME_OPTIONS */173};174
175const char *const kmimetypes[KMIME__MAX] = {176"application/x-javascript", /* KMIME_APP_JAVASCRIPT */177"application/json", /* KMIME_APP_JSON */178"application/octet-stream", /* KMIME_APP_OCTET_STREAM */179"application/pdf", /* KMIME_APP_PDF */180"application/xml", /* KMIME_APP_XML */181"application/zip", /* KMIME_APP_ZIP */182"image/gif", /* KMIME_IMAGE_GIF */183"image/jpeg", /* KMIME_IMAGE_JPEG */184"image/png", /* KMIME_IMAGE_PNG */185"image/svg+xml", /* KMIME_IMAGE_SVG_XML */186"text/calendar", /* KMIME_TEXT_CALENDAR */187"text/css", /* KMIME_TEXT_CSS */188"text/csv", /* KMIME_TEXT_CSV */189"text/html", /* KMIME_TEXT_HTML */190"text/plain", /* KMIME_TEXT_PLAIN */191"text/xml", /* KMIME_TEXT_XML */192};193
194const char *const khttps[KHTTP__MAX] = {195"100 Continue", /* KHTTP_100 */196"101 Switching Protocols", /* KHTTP_101 */197"103 Checkpoint", /* KHTTP_103 */198"200 OK", /* KHTTP_200 */199"201 Created", /* KHTTP_201 */200"202 Accepted", /* KHTTP_202 */201"203 Non-Authoritative Information", /* KHTTP_203 */202"204 No Content", /* KHTTP_204 */203"205 Reset Content", /* KHTTP_205 */204"206 Partial Content", /* KHTTP_206 */205"207 Multi-Status", /* KHTTP_207 */206"300 Multiple Choices", /* KHTTP_300 */207"301 Moved Permanently", /* KHTTP_301 */208"302 Found", /* KHTTP_302 */209"303 See Other", /* KHTTP_303 */210"304 Not Modified", /* KHTTP_304 */211"306 Switch Proxy", /* KHTTP_306 */212"307 Temporary Redirect", /* KHTTP_307 */213"308 Resume Incomplete", /* KHTTP_308 */214"400 Bad Request", /* KHTTP_400 */215"401 Unauthorized", /* KHTTP_401 */216"402 Payment Required", /* KHTTP_402 */217"403 Forbidden", /* KHTTP_403 */218"404 Not Found", /* KHTTP_404 */219"405 Method Not Allowed", /* KHTTP_405 */220"406 Not Acceptable", /* KHTTP_406 */221"407 Proxy Authentication Required", /* KHTTP_407 */222"408 Request Timeout", /* KHTTP_408 */223"409 Conflict", /* KHTTP_409 */224"410 Gone", /* KHTTP_410 */225"411 Length Required", /* KHTTP_411 */226"412 Precondition Failed", /* KHTTP_412 */227"413 Request Entity Too Large", /* KHTTP_413 */228"414 Request-URI Too Long", /* KHTTP_414 */229"415 Unsupported Media Type", /* KHTTP_415 */230"416 Requested Range Not Satisfiable", /* KHTTP_416 */231"417 Expectation Failed", /* KHTTP_417 */232"424 Failed Dependency", /* KHTTP_424 */233"428 Precondition Required", /* KHTTP_428 */234"429 Too Many Requests", /* KHTTP_429 */235"431 Request Header Fields Too Large", /* KHTTP_431 */236"500 Internal Server Error", /* KHTTP_500 */237"501 Not Implemented", /* KHTTP_501 */238"502 Bad Gateway", /* KHTTP_502 */239"503 Service Unavailable", /* KHTTP_503 */240"504 Gateway Timeout", /* KHTTP_504 */241"505 HTTP Version Not Supported", /* KHTTP_505 */242"507 Insufficient Storage", /* KHTTP_507 */243"511 Network Authentication Required", /* KHTTP_511 */244};245
246/*
247* This doesn't have a preset size.
248*/
249const struct kmimemap ksuffixmap[] = {250{ "css", KMIME_TEXT_CSS },251{ "csv", KMIME_TEXT_CSV },252{ "gif", KMIME_IMAGE_GIF },253{ "htm", KMIME_TEXT_HTML },254{ "html", KMIME_TEXT_HTML },255{ "ical", KMIME_TEXT_CALENDAR },256{ "icalendar", KMIME_TEXT_CALENDAR },257{ "ics", KMIME_TEXT_CALENDAR },258{ "ifb", KMIME_TEXT_CALENDAR },259{ "jpg", KMIME_IMAGE_JPEG },260{ "jpeg", KMIME_IMAGE_JPEG },261{ "js", KMIME_APP_JAVASCRIPT },262{ "json", KMIME_APP_JSON },263{ "pdf", KMIME_APP_PDF },264{ "png", KMIME_IMAGE_PNG },265{ "shtml", KMIME_TEXT_HTML },266{ "svg", KMIME_IMAGE_SVG_XML },267{ "svgz", KMIME_IMAGE_SVG_XML },268{ "txt", KMIME_TEXT_PLAIN },269{ "xml", KMIME_TEXT_XML },270{ "zip", KMIME_APP_ZIP },271{ NULL, KMIME__MAX },272};273
274/*
275* Default MIME suffix per type.
276*/
277const char *const ksuffixes[KMIME__MAX] = {278"js", /* KMIME_APP_JAVASCRIPT */279"json", /* KMIME_APP_JSON */280NULL, /* KMIME_APP_OCTET_STREAM */281"pdf", /* KMIME_APP_PDF */282"xml", /* KMIME_APP_XML */283"zip", /* KMIME_APP_ZIP */284"gif", /* KMIME_IMAGE_GIF */285"jpg", /* KMIME_IMAGE_JPEG */286"png", /* KMIME_IMAGE_PNG */287"svg", /* KMIME_IMAGE_PNG */288"ics", /* KMIME_TEXT_CALENDAR */289"css", /* KMIME_TEXT_CSS */290"csv", /* KMIME_TEXT_CSV */291"html", /* KMIME_TEXT_HTML */292"txt", /* KMIME_TEXT_PLAIN */293"xml", /* KMIME_TEXT_XML */294};295
296const char *const kerrors[] = {297"success", /* KCGI_OK */298"cannot allocate memory", /* KCGI_ENOMEM */299"FastCGI exit", /* KCGI_EXIT */300"end-point connection closed", /* KCGI_HUP */301"too many open sockets", /* KCGI_ENFILE */302"failed to fork child", /* KCGI_EAGAIN */303"internal error", /* KCGI_FORM */304"system error", /* KCGI_SYSTEM */305"writer error", /* KCGI_WRITER */306};307
308const char *309kcgi_strerror(enum kcgi_err er)310{
311
312assert(er <= KCGI_WRITER);313return kerrors[er];314}
315
316/*
317* Safe strdup(): don't return on exhaustion.
318*/
319char *320kstrdup(const char *cp)321{
322char *p;323
324if ((p = kxstrdup(cp)) == NULL)325exit(EXIT_FAILURE);326return p;327}
328
329/*
330* Safe realloc(): don't return on exhaustion.
331*/
332void *333krealloc(void *pp, size_t sz)334{
335char *p;336
337if ((p = kxrealloc(pp, sz)) == NULL)338exit(EXIT_FAILURE);339return p;340}
341
342/*
343* Safe reallocarray(): don't return on exhaustion.
344*/
345void *346kreallocarray(void *pp, size_t nm, size_t sz)347{
348char *p;349
350if ((p = kxreallocarray(pp, nm, sz)) == NULL)351exit(EXIT_FAILURE);352return p;353}
354
355/*
356* Safe asprintf(): don't return on exhaustion.
357*/
358int
359kasprintf(char **p, const char *fmt, ...)360{
361va_list ap;362int len;363
364va_start(ap, fmt);365len = kxvasprintf(p, fmt, ap);366va_end(ap);367if (len == -1)368exit(EXIT_FAILURE);369return len;370}
371
372/*
373* Safe vasprintf(): don't return on exhaustion.
374*/
375int
376kvasprintf(char **p, const char *fmt, va_list ap)377{
378int len;379
380if ((len = kxvasprintf(p, fmt, ap)) == -1)381exit(EXIT_FAILURE);382return len;383}
384
385/*
386* Safe calloc(): don't return on exhaustion.
387*/
388void *389kcalloc(size_t nm, size_t sz)390{
391char *p;392
393if ((p = kxcalloc(nm, sz)) == NULL)394exit(EXIT_FAILURE);395return p;396}
397
398/*
399* Safe malloc(): don't return on exhaustion.
400*/
401void *402kmalloc(size_t sz)403{
404char *p;405
406if ((p = kxmalloc(sz)) == NULL)407exit(EXIT_FAILURE);408return p;409}
410
411/*
412* Deprecated form.
413*/
414char *415kutil_urlencode(const char *cp)416{
417
418return (cp == NULL) ? NULL : khttp_urlencode(cp);419}
420
421char *422khttp_urlencode(const char *cp)423{
424char *p;425char ch;426size_t sz, cur;427int rc;428
429if (cp == NULL)430return kxstrdup("");431
432/*433* Leave three bytes per input byte for encoding.
434* This ensures we needn't range-check.
435* First check whether our size overflows.
436* We do this here because we need our size!
437*/
438
439sz = strlen(cp) + 1;440if (SIZE_MAX / 3 < sz) {441kutil_warnx(NULL, NULL, "multiplicative overflow");442return NULL;443}444if ((p = kxcalloc(sz, 3)) == NULL)445return NULL;446
447for (cur = 0; (ch = *cp) != '\0'; cp++) {448if (isalnum((unsigned char)ch) || ch == '-' ||449ch == '_' || ch == '.' || ch == '~') {450p[cur++] = ch;451continue;452} else if (' ' == ch) {453p[cur++] = '+';454continue;455}456rc = snprintf(p + cur, 4, "%%%.2hhX",457(unsigned char)ch);458if (rc != 3) {459kutil_warnx(NULL, NULL, "snprintf");460free(p);461return NULL;462}463cur += 3;464}465
466return p;467}
468
469/*
470* Deprecated form.
471*/
472enum kcgi_err473kutil_urldecode_inplace(char *p)474{
475
476return khttp_urldecode_inplace(p);477}
478
479enum kcgi_err480khttp_urldecode_inplace(char *p)481{
482char c, d;483const char *tail;484
485if (p == NULL)486return KCGI_FORM;487
488/*489* Keep track of two positions: "p", where we'll write the
490* decoded results, and "tail", which is from where we'll
491* decode hex or copy data.
492*/
493
494for (tail = p; (c = *tail) != '\0'; *p++ = c) {495if (c != '%') {496if (c == '+')497c = ' ';498tail++;499continue;500}501
502/*503* Read hex '%xy' as two unsigned chars "c" and "d" then
504* combine them back into "c".
505*/
506
507if (sscanf(tail + 1, "%1hhx%1hhx", &d, &c) != 2 ||508(c |= d << 4) == '\0') {509kutil_warnx(NULL, NULL,510"malformed percent-encoded sequence");511return KCGI_FORM;512}513tail += 3;514}515
516*p = '\0';517return KCGI_OK;518}
519
520/*
521* Deprecated form.
522*/
523enum kcgi_err524kutil_urldecode(const char *src, char **dst)525{
526
527return khttp_urldecode(src, dst);528}
529
530enum kcgi_err531khttp_urldecode(const char *src, char **dst)532{
533enum kcgi_err er;534
535/* In case of error. */536
537if (dst != NULL)538*dst = NULL;539
540if (src == NULL || dst == NULL)541return KCGI_FORM;542if ((*dst = kxstrdup(src)) == NULL)543return KCGI_ENOMEM;544if ((er = khttp_urldecode_inplace(*dst)) == KCGI_OK)545return KCGI_OK;546
547/* If we have decoding errors, clear the output. */548
549free(*dst);550*dst = NULL;551return er;552}
553
554/*
555* Append key-value triplets in "ap" to "p".
556* Reallocate as necessary.
557* A NULL key signifies termination of the list.
558* Returns the URL string or NULL on memory failure.
559*/
560static char *561khttp_url_query_string(char *p, va_list ap)562{
563char *pp, *keyp, *valp;564size_t total, count = 0;565
566total = strlen(p) + 1;567
568while ((pp = va_arg(ap, char *)) != NULL) {569if ((keyp = khttp_urlencode(pp)) == NULL) {570free(p);571return NULL;572}573
574valp = khttp_urlencode(va_arg(ap, char *));575if (valp == NULL) {576free(p);577free(keyp);578return NULL;579}580
581/* Size for key, value, ? or &, and =. */582/* FIXME: check for overflow! */583
584total += strlen(keyp) + strlen(valp) + 2;585
586if ((pp = kxrealloc(p, total)) == NULL) {587free(p);588free(keyp);589free(valp);590return NULL;591}592p = pp;593
594if (count > 0)595(void)strlcat(p, "&", total);596else597(void)strlcat(p, "?", total);598
599(void)strlcat(p, keyp, total);600(void)strlcat(p, "=", total);601(void)strlcat(p, valp, total);602
603free(keyp);604free(valp);605count++;606}607
608return p;609}
610
611/*
612* Append key-type-value triplets in "ap" to "p".
613* Reallocate as necessary.
614* A NULL key signifies termination of the list.
615* Returns the URL string or NULL on memory failure.
616*/
617static char *618khttp_url_query_stringx(char *p, va_list ap)619{
620char *pp, *keyp, *valp, *valpp;621size_t total, count = 0;622char buf[256]; /* max double/int64_t */623
624total = strlen(p) + 1;625
626while ((pp = va_arg(ap, char *)) != NULL) {627if ((keyp = khttp_urlencode(pp)) == NULL) {628free(p);629return NULL;630}631
632valp = valpp = NULL;633
634switch (va_arg(ap, enum kattrx)) {635case KATTRX_STRING:636valp = khttp_urlencode(va_arg(ap, char *));637valpp = valp;638break;639case KATTRX_INT:640(void)snprintf(buf, sizeof(buf),641"%" PRId64, va_arg(ap, int64_t));642valp = buf;643break;644case KATTRX_DOUBLE:645(void)snprintf(buf, sizeof(buf),646"%g", va_arg(ap, double));647valp = buf;648break;649default:650free(p);651free(keyp);652return NULL;653}654
655if (valp == NULL) {656free(p);657free(keyp);658return NULL;659}660
661/* Size for key, value, ? or &, and =. */662/* FIXME: check for overflow! */663
664total += strlen(keyp) + strlen(valp) + 2;665
666if ((pp = kxrealloc(p, total)) == NULL) {667free(p);668free(keyp);669free(valpp);670return NULL;671}672p = pp;673
674if (count > 0)675(void)strlcat(p, "&", total);676else677(void)strlcat(p, "?", total);678
679(void)strlcat(p, keyp, total);680(void)strlcat(p, "=", total);681(void)strlcat(p, valp, total);682
683free(keyp);684free(valpp);685count++;686}687
688return p;689}
690
691/*
692* Deprecated form.
693*/
694char *695kutil_urlabs(enum kscheme scheme,696const char *host, uint16_t port, const char *path)697{
698char *p;699
700kxasprintf(&p, "%s://%s:%" PRIu16 "%s",701kschemes[scheme], host, port, path);702return p;703}
704
705char *706khttp_urlabs(enum kscheme scheme, const char *host,707uint16_t port, const char *path, ...)708{
709char *p;710va_list ap;711
712va_start(ap, path);713p = khttp_vurlabs(scheme, host, port, path, ap);714va_end(ap);715return p;716}
717
718char *719khttp_vurlabs(enum kscheme scheme, const char *host,720uint16_t port, const char *path, va_list ap)721{
722char *p;723int len;724
725if (host == NULL || host[0] == '\0') {726len = kxasprintf(&p, "%s:%s", kschemes[scheme],727path == NULL ? "" : path);728} else if (port == 0) {729len = kxasprintf(&p, "%s://%s%s%s",730kschemes[scheme], host,731path != NULL && path[0] != '\0' &&732path[0] != '/' ? "/" : "",733path == NULL ? "" : path);734} else {735len = kxasprintf(&p, "%s://%s:%" PRIu16 "%s%s",736kschemes[scheme], host, port,737path != NULL && path[0] != '\0' &&738path[0] != '/' ? "/" : "",739path == NULL ? "" : path);740}741
742if (len == -1)743return NULL;744return khttp_url_query_string(p, ap);745}
746
747/*
748* Deprecated form.
749*/
750char *751kutil_urlpartx(struct kreq *req, const char *path,752const char *mime, const char *page, ...)753{
754char *ret;755va_list ap;756
757if (page == NULL)758return NULL;759
760va_start(ap, page);761ret = khttp_vurlpartx(path, mime, page, ap);762va_end(ap);763return ret;764}
765
766char *767khttp_urlpartx(const char *path,768const char *mime, const char *page, ...)769{
770char *ret;771va_list ap;772
773va_start(ap, page);774ret = khttp_vurlpartx(path, mime, page, ap);775va_end(ap);776return ret;777}
778
779char *780khttp_vurlpartx(const char *path,781const char *mime, const char *page, va_list ap)782{
783int len;784char *p, *pageenc = NULL;785
786if (page != NULL && (pageenc = khttp_urlencode(page)) == NULL)787return NULL;788
789if ((mime == NULL || mime[0] == '\0') ||790(page == NULL || page[0] == '\0'))791len = kxasprintf(&p, "%s%s%s",792path != NULL ? path : "",793path != NULL ? "/" : "",794pageenc != NULL ? pageenc : "");795else {796assert(pageenc != NULL);797len = kxasprintf(&p, "%s%s%s.%s",798path != NULL ? path : "",799path != NULL ? "/" : "", pageenc, mime);800}801
802free(pageenc);803pageenc = NULL;804
805if (len == -1)806return NULL;807return khttp_url_query_stringx(p, ap);808}
809
810/*
811* Deprecated form.
812*/
813char *814kutil_urlpart(struct kreq *req, const char *path,815const char *mime, const char *page, ...)816{
817char *ret;818va_list ap;819
820if (page == NULL)821return NULL;822va_start(ap, page);823ret = khttp_vurlpart(path, mime, page, ap);824va_end(ap);825return ret;826}
827
828char *829khttp_urlpart(const char *path,830const char *mime, const char *page, ...)831{
832char *ret;833va_list ap;834
835va_start(ap, page);836ret = khttp_vurlpart(path, mime, page, ap);837va_end(ap);838return ret;839}
840
841char *842khttp_vurlpart(const char *path,843const char *mime, const char *page, va_list ap)844{
845char *p, *pageenc = NULL;846int len;847
848if (page != NULL && (pageenc = khttp_urlencode(page)) == NULL)849return NULL;850
851/*852* Only append the MIME suffix if we have it AND if the page is
853* non-NULL and non-empty.
854*/
855
856if ((mime == NULL || mime[0] == '\0') ||857(page == NULL || page[0] == '\0'))858len = kxasprintf(&p, "%s%s%s",859path != NULL ? path : "",860path != NULL ? "/" : "",861pageenc != NULL ? pageenc : "");862else {863assert(pageenc != NULL);864len = kxasprintf(&p, "%s%s%s.%s",865path != NULL ? path : "",866path != NULL ? "/" : "", pageenc, mime);867}868
869free(pageenc);870pageenc = NULL;871
872if (len == -1)873return NULL;874return khttp_url_query_string(p, ap);875}
876
877static void878kpair_free(struct kpair *p, size_t sz)879{
880size_t i;881
882for (i = 0; i < sz; i++) {883free(p[i].key);884free(p[i].val);885free(p[i].file);886free(p[i].ctype);887free(p[i].xcode);888}889free(p);890}
891
892void
893kreq_free(struct kreq *req)894{
895size_t i;896
897for (i = 0; i < req->reqsz; i++) {898free(req->reqs[i].key);899free(req->reqs[i].val);900}901
902free(req->reqs);903kpair_free(req->cookies, req->cookiesz);904kpair_free(req->fields, req->fieldsz);905free(req->path);906free(req->fullpath);907free(req->remote);908free(req->host);909free(req->cookiemap);910free(req->cookienmap);911free(req->fieldmap);912free(req->fieldnmap);913free(req->suffix);914free(req->pagename);915free(req->pname);916free(req->rawauth.digest);917
918if (req->rawauth.type == KAUTH_DIGEST) {919free(req->rawauth.d.digest.user);920free(req->rawauth.d.digest.uri);921free(req->rawauth.d.digest.realm);922free(req->rawauth.d.digest.nonce);923free(req->rawauth.d.digest.cnonce);924free(req->rawauth.d.digest.response);925free(req->rawauth.d.digest.opaque);926} else if (req->rawauth.type == KAUTH_BASIC) {927free(req->rawauth.d.basic.response);928} else if (req->rawauth.type == KAUTH_BEARER) {929free(req->rawauth.d.basic.response);930}931}
932
933enum kcgi_err934khttp_parse(struct kreq *req,935const struct kvalid *keys, size_t keysz,936const char *const *pages, size_t pagesz,937size_t defpage)938{
939
940return khttp_parsex(req, ksuffixmap, kmimetypes,941KMIME__MAX, keys, keysz, pages, pagesz,942KMIME_TEXT_HTML, defpage, NULL, NULL, 0, NULL);943}
944
945enum kcgi_err946khttp_parsex(struct kreq *req,947const struct kmimemap *suffixmap,948const char *const *mimes, size_t mimesz,949const struct kvalid *keys, size_t keysz,950const char *const *pages, size_t pagesz,951size_t defmime, size_t defpage, void *arg,952void (*argfree)(void *arg), unsigned debugging,953const struct kopts *opts)954{
955const struct kmimemap *mm;956enum kcgi_err kerr;957int er;958struct kopts kopts;959int work_dat[2];960pid_t work_pid;961
962memset(req, 0, sizeof(struct kreq));963
964/*965* We'll be using poll(2) for reading our HTTP document, so this
966* must be non-blocking in order to make the reads not spin the
967* CPU.
968*/
969
970if (kxsocketprep(STDIN_FILENO) != KCGI_OK)971return KCGI_SYSTEM;972if (kxsocketpair(work_dat) != KCGI_OK)973return KCGI_SYSTEM;974
975if ((work_pid = fork()) == -1) {976er = errno;977kutil_warn(NULL, NULL, "fork");978
979close(work_dat[KWORKER_PARENT]);980close(work_dat[KWORKER_CHILD]);981return (er == EAGAIN) ? KCGI_EAGAIN : KCGI_ENOMEM;982} else if (work_pid == 0) {983if (argfree != NULL)984(*argfree)(arg);985
986close(STDOUT_FILENO);987close(work_dat[KWORKER_PARENT]);988
989er = EXIT_SUCCESS;990if (!ksandbox_init_child(SAND_WORKER,991work_dat[KWORKER_CHILD], -1, -1, -1))992er = EXIT_FAILURE;993else if (kworker_child(work_dat[KWORKER_CHILD],994keys, keysz, mimes, mimesz, debugging) != KCGI_OK)995er = EXIT_FAILURE;996
997close(work_dat[KWORKER_CHILD]);998_exit(er);999/* NOTREACHED */1000}1001
1002close(work_dat[KWORKER_CHILD]);1003work_dat[KWORKER_CHILD] = -1;1004
1005if (opts == NULL) {1006memset(&kopts, 0, sizeof(struct kopts));1007kopts.sndbufsz = -1;1008} else1009kopts = *opts;1010
1011if (kopts.sndbufsz < 0)1012kopts.sndbufsz = 1024 * 8;1013
1014kerr = KCGI_ENOMEM;1015
1016/*1017* After this point, all errors should use "goto err", which
1018* will properly free up our memory and close any extant file
1019* descriptors.
1020* Also, we're running our child in the background, so make sure
1021* that it gets killed!
1022*/
1023
1024req->arg = arg;1025req->keys = keys;1026req->keysz = keysz;1027req->kdata = kdata_alloc(-1, -1, 0, debugging, &kopts);1028if (req->kdata == NULL)1029goto err;1030
1031if (keysz) {1032req->cookiemap = kxcalloc(keysz, sizeof(struct kpair *));1033if (req->cookiemap == NULL)1034goto err;1035req->cookienmap = kxcalloc(keysz, sizeof(struct kpair *));1036if (req->cookienmap == NULL)1037goto err;1038req->fieldmap = kxcalloc(keysz, sizeof(struct kpair *));1039if (req->fieldmap == NULL)1040goto err;1041req->fieldnmap = kxcalloc(keysz, sizeof(struct kpair *));1042if (req->fieldnmap == NULL)1043goto err;1044}1045
1046/*1047* Now read the input fields from the child and conditionally
1048* assign them to our lookup table.
1049*/
1050
1051kerr = kworker_parent1052(work_dat[KWORKER_PARENT], req, 1, mimesz);1053if (kerr != KCGI_OK)1054goto err;1055
1056/* Look up page type from component. */1057
1058req->page = defpage;1059if (*req->pagename != '\0')1060for (req->page = 0; req->page < pagesz; req->page++)1061if (strcasecmp1062(pages[req->page], req->pagename) == 0)1063break;1064
1065/*1066* Look up the MIME type, defaulting to defmime if none.
1067* If we can't find it, use the maximum (mimesz).
1068*/
1069
1070req->mime = defmime;1071if (*req->suffix != '\0') {1072for (mm = suffixmap; mm->name != NULL; mm++)1073if (strcasecmp(mm->name, req->suffix) == 0) {1074req->mime = mm->mime;1075break;1076}1077if (mm->name == NULL)1078req->mime = mimesz;1079}1080
1081close(work_dat[KWORKER_PARENT]);1082work_dat[KWORKER_PARENT] = -1;1083kerr = kxwaitpid(work_pid);1084work_pid = -1;1085if (kerr != KCGI_OK)1086goto err;1087return kerr;1088err:1089assert(kerr != KCGI_OK);1090if (work_dat[KWORKER_PARENT] != -1)1091close(work_dat[KWORKER_PARENT]);1092if (work_pid != -1)1093kxwaitpid(work_pid);1094kdata_free(req->kdata, 0);1095req->kdata = NULL;1096kreq_free(req);1097return kerr;1098}
1099
1100void
1101kutil_invalidate(struct kreq *r, struct kpair *kp)1102{
1103struct kpair *p, *lastp;1104size_t i;1105
1106if (kp == NULL)1107return;1108
1109kp->type = KPAIR__MAX;1110kp->state = KPAIR_INVALID;1111memset(&kp->parsed, 0, sizeof(union parsed));1112
1113/* We're not bucketed. */1114
1115if ((i = kp->keypos) == r->keysz)1116return;1117
1118/* Is it in our fieldmap? */1119
1120if (r->fieldmap[i] != NULL) {1121if (kp == r->fieldmap[i]) {1122r->fieldmap[i] = kp->next;1123kp->next = r->fieldnmap[i];1124r->fieldnmap[i] = kp;1125return;1126}1127lastp = r->fieldmap[i];1128p = lastp->next;1129for ( ; p != NULL; lastp = p, p = p->next)1130if (kp == p) {1131lastp->next = kp->next;1132kp->next = r->fieldnmap[i];1133r->fieldnmap[i] = kp;1134return;1135}1136}1137
1138/* ...cookies? */1139
1140if (r->cookiemap[i] != NULL) {1141if (kp == r->cookiemap[i]) {1142r->cookiemap[i] = kp->next;1143kp->next = r->cookienmap[i];1144r->cookienmap[i] = kp;1145return;1146}1147lastp = r->cookiemap[i];1148p = lastp->next;1149for ( ; p != NULL; lastp = p, p = p->next)1150if (kp == p) {1151lastp->next = kp->next;1152kp->next = r->cookienmap[i];1153r->cookienmap[i] = kp;1154return;1155}1156}1157}
1158
1159void
1160khttp_child_free(struct kreq *req)1161{
1162
1163kdata_free(req->kdata, 0);1164req->kdata = NULL;1165kreq_free(req);1166}
1167
1168void
1169khttp_free(struct kreq *req)1170{
1171
1172kdata_free(req->kdata, 1);1173req->kdata = NULL;1174kreq_free(req);1175}
1176
1177/*
1178* Trim leading and trailing whitespace from a word.
1179* Note that this returns a pointer within "val" and optionally sets the
1180* NUL-terminator, so don't free() the returned value.
1181*/
1182static char *1183trim(char *val)1184{
1185char *cp;1186
1187while (isspace((unsigned char)*val))1188val++;1189
1190cp = strchr(val, '\0') - 1;1191while (cp > val && isspace((unsigned char)*cp))1192*cp-- = '\0';1193
1194return val;1195}
1196
1197/*
1198* Simple email address validation: this is NOT according to the spec,
1199* but a simple heuristic look at the address.
1200* Note that this lowercases the mail address.
1201*/
1202static char *1203valid_email(char *p)1204{
1205char *cp, *start;1206size_t sz;1207
1208/*1209* Trim all white-space before and after.
1210* Length check (min: a@b, max: 254 bytes).
1211* Make sure we have an at-sign.
1212* Make sure at signs aren't at the start or end.
1213*/
1214
1215cp = start = trim(p);1216
1217if ((sz = strlen(cp)) < 3 || sz > 254)1218return NULL;1219if (cp[0] == '@' || cp[sz - 1] == '@')1220return NULL;1221if (strchr(cp, '@') == NULL)1222return NULL;1223
1224for (cp = start; *cp != '\0'; cp++)1225*cp = tolower((unsigned char)*cp);1226
1227return start;1228}
1229
1230int
1231kvalid_date(struct kpair *kp)1232{
1233int mday, mon, year;1234
1235if (kp->valsz != 10 || kp->val[10] != '\0' ||1236!isdigit((unsigned char)kp->val[0]) ||1237!isdigit((unsigned char)kp->val[1]) ||1238!isdigit((unsigned char)kp->val[2]) ||1239!isdigit((unsigned char)kp->val[3]) ||1240kp->val[4] != '-' ||1241!isdigit((unsigned char)kp->val[5]) ||1242!isdigit((unsigned char)kp->val[6]) ||1243kp->val[7] != '-' ||1244!isdigit((unsigned char)kp->val[8]) ||1245!isdigit((unsigned char)kp->val[9]))1246return 0;1247
1248/*1249* We know these are all positive integer values, so no need to
1250* use the more complicated interface for this.
1251*/
1252
1253year = atoi(&kp->val[0]);1254mon = atoi(&kp->val[5]);1255mday = atoi(&kp->val[8]);1256
1257if (!khttp_date2epoch(&kp->parsed.i, mday, mon, year))1258return 0;1259
1260kp->type = KPAIR_INTEGER;1261return 1;1262}
1263
1264int
1265kvalid_stringne(struct kpair *p)1266{
1267/*1268* To check if we're a valid string, simply make sure that the
1269* NUL-terminator is where we expect it to be.
1270*/
1271
1272if (strlen(p->val) != p->valsz || p->valsz == 0)1273return 0;1274p->type = KPAIR_STRING;1275p->parsed.s = p->val;1276return 1;1277}
1278
1279int
1280kvalid_string(struct kpair *p)1281{
1282/*1283* To check if we're a valid string, simply make sure that the
1284* NUL-terminator is where we expect it to be.
1285*/
1286
1287if (strlen(p->val) != p->valsz)1288return 0;1289p->type = KPAIR_STRING;1290p->parsed.s = p->val;1291return 1;1292}
1293
1294int
1295kvalid_email(struct kpair *p)1296{
1297
1298if (!kvalid_stringne(p))1299return 0;1300return (p->parsed.s = valid_email(p->val)) != NULL;1301}
1302
1303int
1304kvalid_udouble(struct kpair *p)1305{
1306
1307return kvalid_double(p) && p->parsed.d > 0.0;1308}
1309
1310int
1311kvalid_double(struct kpair *p)1312{
1313char *ep;1314const char *nval;1315double lval;1316int er;1317
1318if (!kvalid_stringne(p))1319return 0;1320
1321/*1322* We might get an empty string from trim, which constitutes a
1323* valid double (!?), so double check that the string is
1324* non-empty after trimming whitespace.
1325* We trim white-space because strtod(3) accepts white-space
1326* before but not after the string.
1327*/
1328
1329nval = trim(p->val);1330if (nval[0] == '\0')1331return 0;1332
1333/* Save errno so we can restore it later. */1334
1335er = errno;1336errno = 0;1337lval = strtod(nval, &ep);1338if (errno == ERANGE)1339return 0;1340
1341/* Restore errno. */1342
1343errno = er;1344
1345if (*ep != '\0')1346return 0;1347
1348p->parsed.d = lval;1349p->type = KPAIR_DOUBLE;1350return 1;1351}
1352
1353int
1354kvalid_int(struct kpair *p)1355{
1356const char *ep;1357
1358if (!kvalid_stringne(p))1359return 0;1360p->parsed.i = strtonum1361(trim(p->val), INT64_MIN, INT64_MAX, &ep);1362p->type = KPAIR_INTEGER;1363return ep == NULL;1364}
1365
1366int
1367kvalid_bit(struct kpair *p)1368{
1369
1370if (!kvalid_uint(p))1371return 0;1372return p->parsed.i <= 64;1373}
1374
1375int
1376kvalid_uint(struct kpair *p)1377{
1378const char *ep;1379
1380p->parsed.i = strtonum(trim(p->val), 0, INT64_MAX, &ep);1381p->type = KPAIR_INTEGER;1382return ep == NULL;1383}
1384
1385enum kcgi_err1386kcgi_buf_write(const char *s, size_t sz, void *arg)1387{
1388struct kcgi_buf *b = arg;1389void *pp;1390
1391/* Let empty/NULL pass through. */1392
1393if (s == NULL || sz == 0)1394return KCGI_OK;1395
1396/* Grow the buffer and leave some slop space. */1397
1398if (b->sz + sz + 1 > b->maxsz) {1399b->maxsz = b->sz + sz + 1 +1400(b->growsz == 0 ? 1024 : b->growsz);1401if ((pp = kxrealloc(b->buf, b->maxsz)) == NULL)1402return KCGI_ENOMEM;1403b->buf = pp;1404}1405
1406/* Always NUL-terminate even though we accept binary data. */1407
1408memcpy(&b->buf[b->sz], s, sz);1409b->sz += sz;1410b->buf[b->sz] = '\0';1411return KCGI_OK;1412}
1413
1414enum kcgi_err1415kcgi_buf_printf(struct kcgi_buf *buf, const char *fmt, ...)1416{
1417char *nbuf;1418int len;1419va_list ap;1420enum kcgi_err er;1421
1422/* Let this bogus case pass through. */1423
1424if (fmt == NULL)1425return KCGI_OK;1426
1427/* Allocate temporary buffer. */1428
1429va_start(ap, fmt);1430len = kxvasprintf(&nbuf, fmt, ap);1431va_end(ap);1432if (len == -1)1433return KCGI_ENOMEM;1434
1435/* Write and free. */1436
1437er = kcgi_buf_write(nbuf, (size_t)len, buf);1438free(nbuf);1439return er;1440}
1441
1442enum kcgi_err1443kcgi_buf_putc(struct kcgi_buf *buf, char c)1444{
1445
1446return kcgi_buf_write(&c, 1, buf);1447}
1448
1449enum kcgi_err1450kcgi_buf_puts(struct kcgi_buf *buf, const char *cp)1451{
1452
1453if (cp == NULL)1454return KCGI_OK;1455return kcgi_buf_write(cp, strlen(cp), buf);1456}
1457