#include "th1ish_cgi.h"
#include <string.h> /* strlen() */
#include <assert.h>
/**
Only for debuggering...
*/
#include <stdio.h>
#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 = NULL;
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