#include "th1ish_cgi.h" #include /* strlen() */ #include /** Only for debuggering... */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) /** Custom client-side native type to bind to th1ish. */ struct cgi_native { /* Put your data here. For now we just use a placeholder. */ cwal_value * vSelf; th1ish_cgi cgi; }; typedef struct cgi_native cgi_native; /** Serves as a copy-constructable default instance and as a type ID pointer for cwal_new_native() and friends. */ static const cgi_native cgi_native_empty = {NULL, th1ish_cgi_empty_m}; static void cgi_native_finalize( cwal_engine * e, void * v ){ cgi_native * ln = (cgi_native *)v; /* MARKER(("Finalizing cgi_native@%p\n", (void *)ln)); */ th1ish_cgi_cleanup( &ln->cgi ); cwal_free( e, v ); } /** A helper function which comes in handy when script code subclasses our native or overrides its methods. */ static cgi_native * cgi_native_from_value( th1ish_interp * ie, cwal_value * v ){ cgi_native * rc = NULL; do{ cwal_native * n = cwal_value_get_native(v); if(n && (rc = cwal_native_get(n, &cgi_native_empty))) break; else v = th1ish_prototype_get(ie, v); }while(v); return rc; } /* Helper macro for cwal_callback_f() implementations bound to cgi_native instances which ensures that the arguments->self value is a cgi_native instance... */ #define THIS_CGI \ th1ish_cgi * cgi; cgi_native * self; \ th1ish_interp * ie = th1ish_args_interp(args); \ assert(ie); \ self = cgi_native_from_value(ie, args->self); \ cgi = self ? &self->cgi : NULL; \ if(!cgi) return th1ish_toss(ie, CWAL_RC_TYPE, \ "'this' is not (or is no longer) " \ "a CGI instance.") static int cgi_cb_urldecode( cwal_callback_args const * args, cwal_value **rv ){ cwal_size_t slen = 0; char const * cstr; int rc; THIS_CGI; cstr = (args->argc) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL; if(!cstr) return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a String argument."); rc = cwal_buffer_reserve(args->engine, &cgi->tmpBuf, slen+1); if(rc) return rc; cgi->tmpBuf.used = 0; cwal_buffer_append( args->engine, &cgi->tmpBuf, cstr, slen ); th1ish_cgi_urldecode_inline( (char *)cgi->tmpBuf.mem, &cgi->tmpBuf.used); *rv = cwal_new_string_value(args->engine, cgi->tmpBuf.used ? (char const *)cgi->tmpBuf.mem : NULL, cgi->tmpBuf.used); cgi->tmpBuf.used = 0; return *rv ? 0 : CWAL_RC_OOM; } static int cgi_cb_urlencode( cwal_callback_args const * args, cwal_value **rv ){ cwal_size_t slen = 0; char const * cstr; int rc; cwal_size_t oldPos; THIS_CGI; cstr = (args->argc) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL; if(!cstr) return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a String argument."); else if(!slen){ /* Optimization: don't bother with an empty string... */ if(cwal_value_is_string(args->argv[0])) *rv = args->argv[0]; else *rv = cwal_new_string_value(args->engine, "", 0) /* cannot fail */; return 0; } oldPos = cgi->tmpBuf.used; rc = cwal_buffer_reserve(args->engine, &cgi->tmpBuf, oldPos + slen + 1); if(rc) return rc; rc = cwal_buffer_printf(args->engine, &cgi->tmpBuf, "%t", cstr); if(!rc){ cwal_size_t const len = cgi->tmpBuf.used - oldPos; *rv = cwal_new_string_value(args->engine, len ? ((char const *)cgi->tmpBuf.mem + oldPos) : NULL, len); if(!*rv) rc = CWAL_RC_OOM; } cgi->tmpBuf.used = oldPos; if(cgi->tmpBuf.mem){ cgi->tmpBuf.mem[oldPos] = 0; } return rc; } static int cgi_cb_html_escape( cwal_callback_args const * args, cwal_value **rv ){ cwal_size_t slen = 0; char const * cstr; int rc; cwal_size_t oldPos; THIS_CGI; cstr = (args->argc) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL; if(!cstr) return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a String argument."); else if(!slen){ /* Optimization: don't bother with an empty string... */ if(cwal_value_is_string(args->argv[0])) *rv = args->argv[0]; else *rv = cwal_new_string_value(args->engine, "", 0) /* cannot fail */; return 0; } oldPos = cgi->tmpBuf.used; rc = cwal_buffer_reserve(args->engine, &cgi->tmpBuf, oldPos + slen + 1); if(rc) return rc; rc = cwal_buffer_printf(args->engine, &cgi->tmpBuf, "%h", cstr); if(!rc){ cwal_size_t const len = cgi->tmpBuf.used - oldPos; *rv = cwal_new_string_value(args->engine, len ? ((char const *)cgi->tmpBuf.mem + oldPos) : NULL, len); if(!*rv) rc = CWAL_RC_OOM; } cgi->tmpBuf.used = oldPos; if(cgi->tmpBuf.mem){ cgi->tmpBuf.mem[oldPos] = 0; } return rc; } static int cgi_cb_send( cwal_callback_args const * args, cwal_value **rv ){ int rc; THIS_CGI; rc = th1ish_cgi_response_output_all(cgi); cwal_native_clear( cwal_value_get_native(self->vSelf), 1 ); return rc; } static int cgi_cb_header_set( cwal_callback_args const * args, cwal_value **rv ){ char const * key; cwal_size_t keyLen = 0; int rc; THIS_CGI; if(args->argc != 2) goto misuse; key = args->argc ? cwal_value_get_cstr(args->argv[0], &keyLen) : NULL; if(!key) goto misuse; rc = th1ish_cgi_response_header_add( cgi, key, keyLen, args->argv[1] ); return rc; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting (key,value) arguments."); } static int cgi_cb_cookie_set( cwal_callback_args const * args, cwal_value **rv ){ char const * key; cwal_size_t keyLen = 0; int rc; cwal_value * v = 0; THIS_CGI; if(!args->argc) goto misuse; if(2==args->argc){ key = cwal_value_get_cstr(args->argv[0], &keyLen); if(!key) goto misuse; v = args->argv[1]; }else if(cwal_value_is_object(args->argv[0])){ v = args->argv[0]; } else goto misuse; /* MARKER(("Setting cookie [%s]\n", key)); */ rc = th1ish_cgi_cookie_set( cgi, key, keyLen, v ); return rc; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting (key,value) or Object arguments."); } /** Script usage: cgi.httpStatus(404, "Not found") cgi.httpStatus(404) // uses default message var code = cgi.httpStatus() assert 'integer' === typename code */ static int cgi_cb_response_status( cwal_callback_args const * args, cwal_value **rv ){ char const * msg; cwal_size_t nMsg = 0; cwal_int_t code; int rc; THIS_CGI; if(!args->argc){ *rv = cwal_new_integer(args->engine, (cwal_int_t)cgi->response.httpCode); return *rv ? 0 : CWAL_RC_OOM; } if((args->argc != 1) && (args->argc != 2)){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting no arguments (getter) " "or (integer,string) arguments (setter)."); } code = cwal_value_get_integer(args->argv[0]); msg = (args->argc > 1) ? cwal_value_get_cstr(args->argv[1], &nMsg) : NULL; rc = th1ish_cgi_response_status_set( cgi, (int)code, msg ); return rc; } static int cgi_cb_content_type_set( cwal_callback_args const * args, cwal_value **rv ){ int rc; THIS_CGI; if(args->argc != 1) { return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting Value argument."); } rc = th1ish_cgi_response_header_add( cgi, "Content-type", 12, args->argv[0] ); return rc; } /** Example of installing a custom native instance as a module... Modules "should" install their new feature(s) in the given container/namespace value. */ int th1ish_cgi_install_to_interp( th1ish_interp * ie, cwal_value * ns ){ int rc; cwal_value * v; cwal_value * mod; static char const * modKey = "cgi"; static cwal_size_t const keyLen = 3 /* == strlen(modKey) */; cgi_native * cn; th1ish_cgi * cgi; assert(cwal_props_can(ns)) /* th1ish won't call this function if ns provided by the caller is not a container. */; /* See if we're already installed... */ v = cwal_prop_get( ns, modKey, keyLen); if(v) return 0; cn = (cgi_native*)cwal_malloc(ie->e, sizeof(cgi_native)); if(!cn) return CWAL_RC_OOM; *cn = cgi_native_empty; mod = cwal_new_native_value(ie->e, cn, cgi_native_finalize, &cgi_native_empty); if(!mod){ cwal_free(ie->e, cn); return CWAL_RC_OOM; } rc = cwal_prop_set( ns, modKey, keyLen, mod ); if(rc){ cwal_value_unref(mod); goto end; } cn->vSelf = mod; cgi = &cn->cgi; /*Done automatically: cwal_value_prototype_set( mod, th1ish_prototype_object(ie) );*/ #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define SET(KEY) rc = cwal_prop_set( mod, KEY, strlen(KEY), v ); \ assert(!rc); if(rc) goto end #define FUNC(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ SET(NAME); \ assert(!rc); \ if(rc) goto end rc = th1ish_cgi_init(ie, cgi); if(rc){ rc = th1ish_toss(ie, rc, "CGI initialization failed%s%.*s", cgi->err.msg.used ? ": " : "", (int)cgi->err.msg.used, cgi->err.msg.used ? (char const *)cgi->err.msg.mem : ""); goto end; } FUNC("httpStatus", cgi_cb_response_status); FUNC("htmlEscape", cgi_cb_html_escape); FUNC("send", cgi_cb_send); FUNC("setContentType", cgi_cb_content_type_set); FUNC("setCookie", cgi_cb_cookie_set); FUNC("setHeader", cgi_cb_header_set); FUNC("urldecode", cgi_cb_urldecode); FUNC("urlencode", cgi_cb_urlencode); v = cwal_new_string_value(ie->e, "CGI", 3); SET("__typename"); { /* Add cgi.request... */ cwal_value * request = cwal_new_object_value(cgi->e); if(!request){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prop_set( mod, "request", 7, request ); if(rc){ cwal_value_unref(request); goto end; } #undef SET #define SET(KEY) rc = cwal_prop_set( request, KEY, strlen(KEY), v ); \ assert(!rc); if(rc) goto end v = cgi->request.env; if(v){ SET("ENV"); } v = cgi->request.get; if(v){ SET("GET"); } v = cgi->request.post; if(v){ SET("POST"); } v = cgi->request.cookies; if(v){ SET("COOKIES"); } } #undef SET #undef FUNC #undef CHECKV end: assert(!rc); return rc; } /** Module initializer for when the module is compiled in a DLL with other modules. */ TH1ISH_MODULE_IMPL(cgiish,th1ish_cgi_install_to_interp); /** Module initializer for when the module is the only module in the DLL. */ TH1ISH_MODULE_IMPL1(cgiish,th1ish_cgi_install_to_interp); #undef THIS_CGI #undef MARKER