#include "th1ish_cgi.h" #include /* isxdigit() */ #include /* strlen() */ #include #include /*getenv() and friends */ /** Only for debuggering... */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) const th1ish_cgi th1ish_cgi_empty = th1ish_cgi_empty_m; /** Some cson_cgi-internal value keys. */ static const struct { char const * ENV_GET; char const * ENV_POST; char const * ENV_COOKIE; char const * ENV_SYS; char const * ENV_APP; char const * ENV_ARGV; char const * ENV_CONFIG; char const * ENV_SESSION; char const * RESPONSE_HEADERS; } th1ish_cgi_keys = { "cgi.GET", "cgi.POST", "cgi.COOKIE", "cgi.ENV", "cgi.APP", "cgi.ARGV", "cgi.CONFIG", "cgi.SESSION", "cgi.response.headers" }; /** If PATH_INFO is set, this function splits it on '/' characters and creates an array out of the elements. The array is stored as $ENV["PATH_INFO_SPLIT"]. Returns non-0 on error. If PATH_INFO is not set, 0 is returned. If it is set but has no entries, an empty array is created. A return value of CWAL_RC_RANGE probably means that a path element was longer than our internal buffer size, in which case processing ends and PATH_INFO_SPLIT is not set. That error can probably be ignored by the caller, but all others are probably serious (e.g. CWAL_RC_OOM). */ static int th1ish_cgi_import_path_info(th1ish_cgi *cx) { char const * head = th1ish_cgi_getenv_cstr(cx, "e", "PATH_INFO", 9, NULL); if( NULL == head ) return 0; else{ cwal_value * env = th1ish_cgi_env_get(cx, 'e', 1); cwal_array * ar = env ? cwal_new_array(cx->e) : NULL; char const * tail = NULL; if( !ar ) return CWAL_RC_OOM; else{ enum { BufSize = 256 }; char buf[BufSize]; cwal_value * partV; cwal_size_t slen; int rc = 0; while( cwal_strtok( &head, '/', &tail ) ){ slen = (tail-head); if( slen >= BufSize ){ rc = CWAL_RC_RANGE; goto end_clean; } memcpy( buf, head, slen ); buf[slen] = 0; th1ish_cgi_urldecode_inline( buf, &slen ); partV = cwal_new_string_value( cx->e, buf, slen ); if( ! partV ){ rc = CWAL_RC_OOM; goto end_clean; } rc = cwal_array_append( ar, partV ); if( rc ){ cwal_value_unref( partV ); goto end_clean; } partV = NULL; head = tail; tail = NULL; } assert( 0 == rc ); rc = cwal_prop_set( env, "PATH_INFO_SPLIT", 15, cwal_array_value(ar) ); end_clean: if( rc ){ cwal_array_unref( ar ); } return rc; } } } /** Imports (extern char ** environ) into cx->request.env, initializing cx->request.env if needed. If called multiple times the environment is re-read each time, but old entries which are no longer in the new environment are not removed from cx->request.env. Returns 0 on success. */ static int th1ish_cgi_import_environ(th1ish_cgi * cx) { extern char ** environ; int i = 0; char const * e = environ[0]; char const * v = NULL; enum { KeyBufSize = 512 }; char keybuf[KeyBufSize]; char * kpos = NULL; cwal_size_t vLen; int rc = 0; cwal_value * jv = NULL; cwal_value * env = th1ish_cgi_env_get( cx, 'e', 1 ); if(!env) return CWAL_RC_OOM; for( ; e && *e; e = environ[++i] ){ v = NULL; kpos = keybuf; for( ; *e && ('=' != *e); ++e ){ *(kpos++) = *e; assert( kpos < (keybuf+KeyBufSize) ); if( kpos == (keybuf+KeyBufSize) ){ return CWAL_RC_RANGE; } } *kpos = 0; if( '=' == *e ){ v = e+1; vLen = strlen(v); } else{ v = ""; vLen = 0; } /* TODO? URLdecode? We could do that using cwal_printf()'s %T. */ jv = cwal_new_string_value( cx->e, vLen ? v : NULL, vLen ); if(!jv){ rc = CWAL_RC_OOM; break; } rc = cwal_prop_set( env, keybuf, strlen(keybuf), jv ); if( 0 != rc ) { cwal_value_unref(jv); break; } } if( !rc ){ rc = th1ish_cgi_import_path_info(cx); } return rc; } /** Parses inp as a delimited list, separated by the given separator character. Each item in the list is treated as a key/value pair in the form KEY=VALUE, and inserted into the target cson_object (which must not be NULL). This is intended for parsing HTTP GET-style parameter lists. If doUrlDecode is true (non-zero) then the VALUE part of the key/value pair gets url-decoded before insertion. (FIXME? Also decode the keys?) If firstOneWins is non-0 then if a given key in the parameters is duplicated, entries after the first are ignored. If it is 0 then the "last one wins." This is basically a workaround for when we have multiple session ID cookies hanging around :/. On success it returns 0. If a given key contains the string "[]", that part is stripped and the entry is treated like an array element. e.g. a query string of "a[]=3&a[]=7" would result in an array property named "a" with the (string) entries ("3", "7"). */ static int th1ish_cgi_parse_param_list( th1ish_cgi * cx, cwal_value * tgt, char const * inp, char separator, char doUrlDecode, char firstOneWins) { if( ! tgt || !separator ) return CWAL_RC_MISUSE; else if( !inp || !*inp ) return 0; else{ char const * head = inp; char const * tail = NULL; char * out = NULL; unsigned int inLen = strlen( inp ); cwal_size_t valLen; cwal_value * jval = NULL; cwal_value * listV = NULL; cwal_array * list = NULL; cwal_size_t keyLen; cwal_size_t const oldUsed = cx->tmpBuf.used; int rc = cwal_buffer_reserve( cx->e, &cx->tmpBuf, inLen+1 ); if( 0 != rc ) return rc; while( cwal_strtok( &head, separator, &tail ) ) { char const * key = head; char * value = NULL; rc = 0; if( head == tail ) break; out = (char *)cx->tmpBuf.mem; memset( cx->tmpBuf.mem, 0, cx->tmpBuf.capacity ); for( ; (keypos >= st->len ){ *n = 0; return 0; } else if( !*n || ((st->pos + *n) > st->len) ){ return CWAL_RC_RANGE; } else{ unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh ); if( ! rsz ){ *n = (cwal_size_t)rsz; return feof(st->fh) ? 0 : CWAL_RC_IO; } else{ *n = (cwal_size_t)rsz; st->pos += *n; return 0; } } } } static int th1ish_cgi_err_set( th1ish_cgi * cx, int code, char const * msg, ... ){ assert(0 != code); cx->err.msg.used = 0; if(msg && *msg){ va_list args; va_start(args,msg); cwal_buffer_printfv( cx->e, &cx->err.msg, msg, args ); va_end(args); } return cx->err.rc = code; } static int th1ish_cgi_parse_POST_JSON(th1ish_cgi * cx, FILE * src, unsigned int contentLen){ cwal_value * jv = NULL; int rc = 0; CgiPostReadState state; cwal_json_parse_info pinfo = cwal_json_parse_info_empty; assert( 0 != contentLen ); assert( NULL == cx->request.post ); state.fh = src; state.len = contentLen; state.pos = 0; rc = cwal_json_parse( cx->e, cwal_input_FILE_n, &state, &jv, &pinfo ); if( rc ){ assert(!jv); return th1ish_cgi_err_set(cx, rc, "Parsing POST as JSON failed: " "code=%d (%s) " "line=%u, col=%u\n", rc, cwal_rc_cstr(rc), (unsigned)pinfo.line, (unsigned)pinfo.col); return rc; } cwal_value_rescope( cx->topScope, jv ); cx->request.post = jv; return rc; } #define TH1ISH_CGI_ENABLE_POST_FORM_URLENCODED 1 #if TH1ISH_CGI_ENABLE_POST_FORM_URLENCODED static int th1ish_cgi_parse_post_urlencoded( th1ish_cgi * cx, char const * qstr ) { cwal_value * env = NULL; if( !qstr || !*qstr ) return 0; assert(cx); env = th1ish_cgi_env_get( cx, 'p', 1 ); return env ? th1ish_cgi_parse_param_list( cx, env, qstr, '&', 1, 0 ) : CWAL_RC_OOM; } #endif static int th1ish_cgi_init_POST(th1ish_cgi * cx){ if( ! cx || !cx->request.input ) return CWAL_RC_MISUSE; else{ FILE * src = cx->request.input; char const * ctype = th1ish_cgi_getenv_cstr( cx, "e", "CONTENT_TYPE", 12, NULL ); if( NULL == ctype ) return 0; else{ char const * clen = th1ish_cgi_getenv_cstr( cx, "e", "CONTENT_LENGTH", 14, NULL ); if( NULL == clen ){ return th1ish_cgi_err_set(cx, CWAL_RC_RANGE, "CONTENT_LENGTH not specified."); } #if TH1ISH_CGI_ENABLE_POST_FORM_URLENCODED else if( 0 == strncmp(ctype,"application/x-www-form-urlencoded",33) ){ cwal_buffer buf = cwal_buffer_empty; int rc = cwal_buffer_fill_from_FILE( cx->e, &buf, src ); if( rc ) goto end_clean; if( buf.mem && buf.used ){ #if 1 if( strlen((char const *)buf.mem) != buf.used ){ /* assume bad/malicious input. */ rc = CWAL_RC_RANGE; rc = th1ish_cgi_err_set(cx, CWAL_RC_RANGE, "Not allowing binary POST input."); goto end_clean; } #endif rc = th1ish_cgi_parse_post_urlencoded( cx, (char const *)buf.mem ); } end_clean: cwal_buffer_reserve( cx->e, &buf, 0 ); return rc; } #endif else{ char * endpt = NULL; long len = strtol( clen, &endpt, 10 ); if( (endpt && *endpt) || (len<=0) ) return CWAL_RC_RANGE; else if( (0 == strncmp(ctype,"application/json",16)) || (0 == strncmp(ctype,"text/plain",10)) || (0 == strncmp(ctype,"application/javascript",22)) ) { return th1ish_cgi_parse_POST_JSON(cx, src, len); } else{ return th1ish_cgi_err_set(cx, CWAL_RC_TYPE, "Don't know how to handle " "Content-type [%s]\n", ctype); } } } } } /** If orig is one of the types (string,double,bool,undef,null) then a pointer to its string representation is returned, else NULL is returned. For non-string types, dest must be at least destLen bytes of memory, and if destLen is not long enough to hold the string form then NULL is returned. On success a pointer to a string is returned. It will be one of: - if orig is-a string then it's underlying string. - for (double,integer,bool,undef,null), dest will be returned. The encoded form is decimal for (double,integer), the number 0 or 1 for bool, and the number 0 for (undef,null). Ownership of dest is not modified by this call. The returned value is valid until either orig or dest are modified. On error dest is not modified. Dest is also not modified if orig is-a string, as its own string bytes are returned instead. */ static char const * th1ish_cgi_pod_to_string( th1ish_cgi * cx, cwal_value const * orig, cwal_buffer * dest ) { if( !cx || !orig || !dest ) return NULL; else {/* FIXME? use cson's output support for the numeric types. i _think_ those bits might not be in the public API, though. We could use it for serializing objects/arrays, in any case. */ enum { NumBufSize = 80 }; int rc = 0; cwal_size_t slen = 0; char const * cstr = cwal_value_get_cstr(orig, &slen); dest->used = 0; if(cstr){ if( (rc=cwal_buffer_append(cx->e, dest, cstr, slen)) ){ th1ish_cgi_err_set(cx, rc, NULL); return NULL; } return (char *)dest->mem; } else if( cwal_value_is_integer(orig) ){ if( (rc=cwal_buffer_printf( cx->e, dest, "%"CWAL_INT_T_PFMT, cwal_value_get_integer(orig)))){ th1ish_cgi_err_set(cx, rc, NULL); return NULL; } return (char *)dest->mem; } else if( cwal_value_is_double(orig) ){ if((rc=cwal_buffer_printf( cx->e, dest, "%"CWAL_DOUBLE_T_PFMT, cwal_value_get_double(orig)))){ th1ish_cgi_err_set(cx, rc, NULL); return NULL; } return (char *)dest->mem; } else if( cwal_value_is_bool( orig ) ){ char const bv = cwal_value_get_bool(orig); if( (rc=cwal_buffer_append( cx->e, dest, bv ? "1": "0", 1)) ){ th1ish_cgi_err_set(cx, rc, NULL); return NULL; } return (char *)dest->mem; } else if( cwal_value_is_null( orig ) || cwal_value_is_undef( orig ) ){ if( (rc=cwal_buffer_append( cx->e, dest, "0", 1)) ){ th1ish_cgi_err_set(cx, rc, NULL); return NULL; } return (char *)dest->mem; } else{ return NULL; } } } static int th1ish_cgi_kvp_visitor_headers( cwal_kvp const * kvp, void * state ){ th1ish_cgi * cx = (th1ish_cgi*)state; char const * key; cwal_value const * val; char const * valcstr; cwal_size_t keyLen = 0; cx->tmpBuf.used = 0; key = cwal_value_get_cstr(cwal_kvp_key(kvp), &keyLen); val = cwal_kvp_value(kvp); valcstr = th1ish_cgi_pod_to_string( cx, val, &cx->tmpBuf ); if( ! valcstr ) return 0; else{ int const rc = cwal_outputf(cx->e, "%.*s: %.*s\r\n", (int)keyLen, key, (int)cx->tmpBuf.used, valcstr); cx->tmpBuf.used = 0; return rc; } } /** Writes an RFC822 timestamp string to dest, which must be at least destLen bytes long. On success returns dest, else NULL. destLen must be at least 31. */ static char * th1ish_cgi_rfc822_timedate( time_t now, char * dest, unsigned int destLen ) { static const char * dayNames[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0 }; static const char * monthNames[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; struct tm * t = (dest && (destLen>30)) ? gmtime(&now) : NULL; if( ! t || (destLen<31) ) return NULL; else { int const rc = sprintf( dest, "%s, %d %s %02d %02d:%02d:%02d GMT", dayNames[t->tm_wday], t->tm_mday, monthNames[t->tm_mon], t->tm_year+1900, t->tm_hour, t->tm_min, t->tm_sec ); assert( (rc>0) && ((unsigned int)rc) < destLen ); return dest; } } static int th1ish_cgi_kvp_visitor_cookies( cwal_kvp const * kvp, void * state ){ enum { TSBufSize = 32 }; char tsBuf[TSBufSize] = {0} /* buffer for expiry timestamp */; th1ish_cgi * cx = (th1ish_cgi*)state; int rc; char const * key; cwal_size_t keyLen = 0; cwal_value const * val; key = cwal_value_get_cstr(cwal_kvp_key(kvp), &keyLen); if(!key || !*key) return 0 /* ignore non-string keys */; val = cwal_kvp_value(kvp); if( cwal_value_is_null(val) ){ cwal_outputf(cx->e,"Set-Cookie: %.*s=; Expires=Thu, " "01-Jan-1970 00:00:01 GMT\r\n", (int)keyLen, key); return 0; } else if( cwal_value_is_object(val) ){ /* Accept in Object in the form: { value: VALUE, domain: string, path: string, secure: bool, httponly: bool, expires: integer } */ cwal_object * obj = cwal_value_get_object( val ); cwal_value const * cv = cwal_object_get( obj, "value", 5 ); char const * valstr = NULL; char const isNull = !cv || cwal_value_is_null( cv ); if( isNull ){ cwal_outputf(cx->e, "Set-Cookie: %.*s=", (int)keyLen, key); } else{ /* FIXME: streamify urlencode so we can get around fixed buffer size. */ valstr = th1ish_cgi_pod_to_string( cx, cv, &cx->tmpBuf ); if( ! valstr ) return 0; else { rc = th1ish_cgi_urlencode( cx, valstr, cx->tmpBuf.used, &cx->tmpBuf2 ); if(rc) return rc; cwal_outputf(cx->e, "Set-Cookie: %.*s=%.*s", (int)keyLen, key, (int)cx->tmpBuf2.used, (char const *)cx->tmpBuf2.mem); cx->tmpBuf2.used = 0; } } #define DOPART(KEY,KEY2) cv = cwal_object_get( obj, KEY, strlen(key) ); \ if( cv ) { \ valstr = th1ish_cgi_pod_to_string( cx, cv, &cx->tmpBuf ); \ if( valstr ) { \ cwal_outputf( cx->e, "; "KEY2"=%.*s", (int)cx->tmpBuf.used, valstr ); \ } \ cx->tmpBuf.used = 0; \ } (void)0 DOPART("domain","Domain"); DOPART("path","Path"); #undef DOPART cv = cwal_object_get( obj, "expires", 7 ); if( cv || isNull ){ cwal_int_t const intVal = isNull ? 1 : cwal_value_get_integer(cv); if( intVal ){ valstr = th1ish_cgi_rfc822_timedate( (time_t)intVal, tsBuf, TSBufSize ); if( valstr ){ cwal_outputf( cx->e, "; Expires=%s", valstr ); } } } cv = cwal_object_get( obj, "secure", 6 ); if( cwal_value_get_bool(cv) ){ cwal_output( cx->e, "; Secure", 8 ); } cv = cwal_object_get( obj, "httpOnly", 8 ); if( cwal_value_get_bool(cv) ){ cwal_output( cx->e, "; HttpOnly", 10 ); } cwal_output( cx->e, "\r\n", 2); } else{ char const * valstr; valstr = th1ish_cgi_pod_to_string( cx, val, &cx->tmpBuf); if( ! valstr ) return 0; else{ rc = th1ish_cgi_urlencode( cx, valstr, cx->tmpBuf.used, &cx->tmpBuf2 ); if( rc ) return rc; cwal_outputf(cx->e,"Set-Cookie: %.*s=%.*s\r\n", (int)keyLen, key, (int)cx->tmpBuf2.used, (char const *)cx->tmpBuf2.mem); cx->tmpBuf2.used = 0; } } return 0; } static char const * th1ish_cgi_http_status_cstr(int httpCode){ if(httpCode>=500){ switch(httpCode){ case 501: return "Not Implemented"; case 502: return "Temporarily Unavailable"; case 503: return "Gateway Timeout"; case 500: default: return "Internal Server Error"; } }else if (httpCode>=400){ switch(httpCode){ case 401: return "Unauthorized"; case 402: return "PaymentRequired"; case 403: return "Forbidden"; case 404: return "Not Found"; case 400: default: return "Bad Request"; } }else if (httpCode>=300){ switch(httpCode){ case 301: return "Moved"; case 302: return "Found"; case 303: return "Method"; case 304: return "Not Modified"; case 300: default: return "Redirect"; } }else if (httpCode>=200){ switch(httpCode){ case 201: return "Created"; case 202: return "Accepted"; case 203: return "Partial Information"; case 204: return "No Response"; case 200: default: return "OK"; } }else{ return "???"; } } int th1ish_cgi_response_output_headers(th1ish_cgi * cx) { enum { BufSize = 64 }; cwal_value * jo = NULL; int rc; if(cx->response.contentType){ rc = cwal_outputf(cx->e, "Content-type: %s\r\n", cx->response.contentType); if(rc) return rc; }/* else assume it's been set as a header */ rc = cwal_outputf(cx->e, "Status: %d %s\r\n", cx->response.httpCode, cx->response.httpStatus ? cx->response.httpStatus : th1ish_cgi_http_status_cstr(cx->response.httpCode)); if(rc) return rc; jo = cx->response.headers; if( jo ){ rc = cwal_props_visit_kvp( jo, th1ish_cgi_kvp_visitor_headers, cx ); if(rc) return rc; } if(cx->response.cookies){ rc = cwal_props_visit_kvp( cx->response.cookies, th1ish_cgi_kvp_visitor_cookies, cx ); } if( 0 == rc ){ cwal_output(cx->e, "\r\n", 2); } return rc; } int th1ish_cgi_response_status_set(th1ish_cgi * cx, int httpCode, char const * msg){ size_t msgLen; if(!cx) return CWAL_RC_MISUSE; if(!msg || !*msg){ msg = th1ish_cgi_http_status_cstr(httpCode); } cx->response.httpCode = httpCode; cwal_free(cx->e, cx->response.httpStatus); msgLen = msg ? strlen(msg) : 0; if(!msgLen) cx->response.httpStatus = NULL; else{ cx->response.httpStatus = (char *)cwal_malloc(cx->e, msgLen+1); if(!cx->response.httpStatus) return CWAL_RC_OOM; memcpy(cx->response.httpStatus, msg, msgLen); cx->response.httpStatus[msgLen] = 0; } return 0; } /** TH1ISH_CGI_BUFFER determines whether this module implicitly pushes an output buffering level (in which case it also relies on buffering being set) or not. */ #define TH1ISH_CGI_BUFFER 0 int th1ish_cgi_response_output_all(th1ish_cgi * cx){ int rc = 0; char doHeaders; cwal_buffer body = cwal_buffer_empty; if(!cx) return CWAL_RC_MISUSE; doHeaders = cx->response.headerMode; if( doHeaders < 0 ){ doHeaders = getenv("GATEWAY_INTERFACE") ? 1 : 0; } if(th1ish_ob_level(cx->ie)){ while(1 < th1ish_ob_level(cx->ie)){ rc = th1ish_ob_flush(cx->ie); assert(!rc); if(rc) goto end; th1ish_ob_pop(cx->ie); } th1ish_ob_take( cx->ie, &body ); th1ish_ob_pop(cx->ie); assert(0 == th1ish_ob_level(cx->ie)); } if( doHeaders > 0 ){ rc = th1ish_cgi_response_output_headers(cx); if(rc) goto end; } /** Output the body... */ if(body.used){ rc = cwal_output(cx->e, body.mem, body.used); if(rc) goto end; } rc = cwal_output_flush( cx->e ); end: cwal_buffer_reserve(cx->e, &body, 0); return rc; } cwal_value * th1ish_cgi_path_part( th1ish_cgi * cx, unsigned short ndx ){ cwal_value * piV = th1ish_cgi_getenv( cx, "e", "PATH_INFO_SPLIT", 15 ); return piV ? cwal_array_get(cwal_value_get_array(piV), ndx) : NULL; } char const * th1ish_cgi_path_part_cstr( th1ish_cgi * cx, unsigned short ndx, cwal_size_t * strLen ) { cwal_value * v = th1ish_cgi_path_part( cx, ndx ); return v ? cwal_value_get_cstr( v, strLen ) : NULL; } int th1ish_cgi_init( th1ish_interp * ie, th1ish_cgi * cx ){ int rc; if(!ie || !ie->e || !cx) return CWAL_RC_MISUSE; *cx = th1ish_cgi_empty; cx->ie = ie; cx->e = ie->e; cx->topScope = ie->topScope; rc = th1ish_cgi_import_environ(cx); cx->request.input = stdin /*TODO: make configurable*/; if(!rc) rc = th1ish_cgi_parse_cookies(cx, getenv("HTTP_COOKIE") ); if(!rc) rc = th1ish_cgi_parse_query_string( cx, getenv("QUERY_STRING") ); if(!rc) rc = th1ish_cgi_init_POST(cx); #if TH1ISH_CGI_BUFFER if(!rc) rc = th1ish_ob_push(ie); #endif if(rc){ th1ish_cgi_err_set( cx, rc, "CGI initialization failed " "with code %d (%s).", rc, cwal_rc_cstr(rc) ); } return rc; } void th1ish_cgi_cleanup( th1ish_cgi * cgi ){ if(cgi && cgi->ie){ cwal_buffer_reserve( cgi->e, &cgi->tmpBuf, 0 ); cwal_buffer_reserve( cgi->e, &cgi->tmpBuf2, 0 ); cwal_buffer_reserve( cgi->e, &cgi->err.msg, 0 ); #if TH1ISH_CGI_BUFFER while(th1ish_ob_level(cgi->ie)){ th1ish_ob_pop(cgi->ie); } #endif cwal_free(cgi->e, cgi->response.httpStatus); *cgi = th1ish_cgi_empty; } /* We leave the rest of the pointers for cgi->e to clean up because we cannot know here if it's already done so or not. */ } cwal_value * th1ish_cgi_env_get( th1ish_cgi * cx, char which, char createIfNeeded ){ cwal_value ** v = NULL; char const * gckey = NULL; switch( which ) { case 'c': case 'C': gckey = th1ish_cgi_keys.ENV_COOKIE; v = &cx->request.cookies; break; case 'e': case 'E': gckey = th1ish_cgi_keys.ENV_SYS; v = &cx->request.env; break; case 'h': case 'H': gckey = th1ish_cgi_keys.RESPONSE_HEADERS; v = &cx->response.headers; break; case 'g': case 'G': gckey = th1ish_cgi_keys.ENV_GET; v = &cx->request.get; break; case 'f': case 'F': gckey = th1ish_cgi_keys.ENV_CONFIG; v = &cx->config; break; case 'p': case 'P': gckey = th1ish_cgi_keys.ENV_POST; v = &cx->request.post; break; #if 0 case 'a': case 'A': gckey = th1ish_cgi_keys.ENV_APP; v = &cx->clientEnv; break; case 's': case 'S': gckey = th1ish_cgi_keys.ENV_SESSION; v = &cx->session.env; break; #endif default: break; } if( v ){ if( !*v && createIfNeeded ){ *v = cwal_new_object_value(cx->e); if(*v){ if(th1ish_stash_set(cx->ie, gckey, *v)){ cwal_value_unref(*v); *v = NULL; }else{ cwal_value_rescope(cx->topScope, *v); } } } } return v ? *v : NULL; } cwal_value * th1ish_cgi_getenv( th1ish_cgi * cx, char const * fromWhere, char const * key, cwal_size_t keyLen ) { cwal_value * jv = NULL; if( !key || !*key ) return NULL; else if( !fromWhere || !*fromWhere ) fromWhere = TH1ISH_CGI_GETENV_DEFAULT; for( ; *fromWhere ; ++fromWhere ){ if( ('r'==*fromWhere)||('R'==*fromWhere) ){ jv = th1ish_cgi_getenv( cx, "gpc", key, keyLen ); }else{ jv = th1ish_cgi_env_get( cx, *fromWhere, 0 ); } if( jv ){ jv = cwal_prop_get( jv, key, keyLen ); if(jv) return jv; } } return NULL; } char const * th1ish_cgi_getenv_cstr( th1ish_cgi * cx, char const * where, char const * key, cwal_size_t keyLen, cwal_size_t * strLen ) { cwal_value * v = th1ish_cgi_getenv(cx, where, key, keyLen); return v ? cwal_value_get_cstr(v, strLen) : NULL; } int th1ish_cgi_setenv_v( th1ish_cgi * cx, char env, char const * key, cwal_size_t keyLen, cwal_value * v ){ if( !cx || !env || !key || !*key ) return CWAL_RC_MISUSE; else { cwal_value * e = th1ish_cgi_env_get( cx, env, 1 ); return !e ? CWAL_RC_OOM /* FIXME: expand the above code so we can distinguish between invalid env and allocation error. (Except that there is no allocation on get_obj().*/ : cwal_prop_set( e, key, keyLen, v ) ; } } int th1ish_cgi_setenv( th1ish_cgi * cx, char const * key, cwal_size_t keyLen, cwal_value * v ){ return th1ish_cgi_setenv_v( cx, 'e', key, keyLen, v ); } int th1ish_cgi_response_header_add( th1ish_cgi * cx, char const * key, cwal_size_t keyLen, cwal_value * v ) { cwal_value * env; if( !cx || ! key || !*key ) return CWAL_RC_MISUSE; env = th1ish_cgi_env_get( cx, 'h', 1 ); if(!env) return CWAL_RC_OOM; return cwal_prop_set( env, key, keyLen, v ); } static int th1ish_cgi_response_init_cookies(th1ish_cgi * cx){ if(!cx->response.cookies){ cx->response.cookies = cwal_new_object_value(cx->e); if(cx->response.cookies){ th1ish_stash_set(cx->ie, th1ish_cgi_keys.ENV_COOKIE, cx->response.cookies); } } return cx->response.cookies ? 0 : CWAL_RC_OOM; } int th1ish_cgi_cookie_set( th1ish_cgi * cx, char const * key, cwal_size_t keyLen, cwal_value * v ){ if( !key || !*key ) return CWAL_RC_MISUSE; else{ int rc = th1ish_cgi_response_init_cookies(cx); /* MARKER(("Setting cookie [%s]\n", key)); */ if(!rc) rc = cwal_prop_set( cx->response.cookies, key, keyLen, v ); return rc; } } int th1ish_cgi_cookie_set2( th1ish_cgi * cx, char const * key, cwal_size_t keyLen, cwal_value * v, char const * domain, char const * path, unsigned int expires, char secure, char httponly ) { if( ! key || !*key ) return CWAL_RC_MISUSE; else { int rc; cwal_value * x = NULL; cwal_value * jo = cwal_new_object_value(cx->e); if( ! jo ) return CWAL_RC_OOM; if( !v ) v = cwal_value_null(); #define SET(KEY) if(!x) rc=CWAL_RC_OOM; \ if( rc || (rc = cwal_prop_set( jo, KEY, strlen(KEY), x) ) ) { \ if(x) cwal_value_unref(x); \ cwal_value_unref( jo ); \ return rc; \ } rc = cwal_prop_set( jo, "value", 5, v); if(rc) return rc; if(domain){ x = cwal_new_string_value( cx->e, domain, strlen(domain) ); SET("domain"); } if( path ){ x = cwal_new_string_value( cx->e, path, strlen(path) ); SET("path"); } if( cwal_value_is_null(v) ){ x = cwal_new_integer( cx->e, 1 ); SET("expires"); } else if( expires ){ x = cwal_new_integer( cx->e, (cwal_int_t) expires ); SET("expires"); } if( secure ){ x = cwal_new_bool(secure); SET("secure"); } if( httponly ){ x = cwal_new_bool(httponly); SET("httpOnly"); } #undef SET rc = th1ish_cgi_cookie_set( cx, key, keyLen, jo ); if(rc) cwal_value_unref( jo ); return rc; } } /** th1ish_cgi_hexchar_to_int(): For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal number. For any other character it returns -1. */ static int th1ish_cgi_hexchar_to_int( int ch ){ if( (ch>='a' && ch<='f') ) return ch-'a'+10; else if( (ch>='A' && ch<='F') ) return ch-'A'+10; else if( (ch>='0' && ch<='9') ) return ch-'0'; return -1; } void th1ish_cgi_urldecode_inline( char * str, cwal_size_t * sLen ){ unsigned char ch = 0; unsigned char cx1 = 0; unsigned char cx2 = 0; int decoded; unsigned char * pos = (unsigned char *)str; unsigned char * out = pos; unsigned char const * end; size_t slen = (str && *str) ? (sLen ? *sLen : strlen(str)) : 0; if( !slen ) return; end = pos + slen; for( ; pos < end; ++pos ){ ch = *pos; if( ch == '%' ) { cx1 = *(pos+1); /* FIXME: with only minor refactoring we can remove the isxdigit() calls and use th1ish_cgi_hexchar_to_int() instead, checking for a negative return value. That would potentially save us 2 extra function calls here. */ if( isxdigit(cx1) ){ cx2 = *(pos+2); if( isxdigit(cx2) ) { decoded = (th1ish_cgi_hexchar_to_int( cx1 ) * 16) + th1ish_cgi_hexchar_to_int( cx2 ); *(out++) = (char)decoded; pos += 2; continue; } /* else fall through... */ } /* else fall through... */ } else if( ch == '+' ) { *(out++) = ' '; continue; } *(out++) = ch; } *out = 0; if(sLen) *sLen = (out - (unsigned char *)str); } int th1ish_cgi_urlencode( th1ish_cgi * cx, char const * src, cwal_size_t srcLen, cwal_buffer * _dest ){ #define needs_escape \ ( (ch >= 32 && ch <=47) \ || ( ch>=58 && ch<=64) \ || ( ch>=91 && ch<=96) \ || ( ch>=123 && ch<=126) \ || ( ch<32 || ch>=127) \ ) char const * pos = src; char const * end = src + srcLen; char * dest; char ch; int rc; static char const * hex = "0123456789ABCDEF"; if( !cx || !src ) return CWAL_RC_MISUSE; rc = cwal_buffer_reserve( cx->e, _dest, _dest->used + (srcLen*3) + 1); if(rc) return rc; dest = (char *)_dest->mem + _dest->used; for( ; (pos>4)&0xf)]; *(dest++) = hex[(ch&0xf)]; } } _dest->used = dest - (char *)_dest->mem; return 0; #undef needs_escape } #if 0 char const * th1ish_cgi_guess_content_type(){ char const * cset; char doUtf8; char const * cstr; cset = getenv("HTTP_ACCEPT_CHARSET"); doUtf8 = (cset && strstr("utf-8",cset)) ? 1 : 0; cstr = getenv("HTTP_ACCEPT"); if( strstr( cstr, "application/json" ) || strstr( cstr, "*/*" ) ){ return doUtf8 ? "application/json; charset=utf-8" : "application/json"; }else{ return NULL /*"text/plain"*/; } } #endif #undef TH1ISH_CGI_BUFFER #undef MARKER