/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
#include "s2_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 s2.
*/
struct cgi_native {
cwal_value * vSelf;
s2_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, s2_cgi_empty_m};
/**
cwal finalizer for cgi_native. v must be a (cgi_native*).
*/
static void cgi_native_finalize( cwal_engine * e, void * v ){
cgi_native * nat = (cgi_native *)v;
/*MARKER(("Finalizing cgi_native@%p (%s@%p)\n", (void *)nat,
cwal_value_type_name(nat->vSelf), (void*)nat->vSelf));*/
s2_cgi_cleanup( &nat->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( s2_engine * se,
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 = cwal_value_prototype_get(se->e, 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 \
s2_cgi * cgi; cgi_native * self; \
s2_engine * se = s2_engine_from_args(args); \
assert(se); \
self = cgi_native_from_value(se, args->self); \
cgi = self ? &self->cgi : NULL; \
if(!cgi) return cwal_exception_setf(args->engine, 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 cwal_exception_setf(args->engine, 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 );
s2_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 cwal_exception_setf(args->engine, 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 cwal_exception_setf(args->engine, 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 = s2_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 = s2_cgi_response_header_add( cgi, key, keyLen, args->argv[1] );
return rc;
misuse:
return cwal_exception_setf(args->engine, 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 = s2_cgi_cookie_set( cgi, key, keyLen, v );
return rc;
misuse:
return cwal_exception_setf(args->engine, 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 cwal_exception_setf(args->engine, 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 = s2_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 cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
"Expecting Value argument.");
}
rc = s2_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 s2_cgi_install_to_interp( s2_engine * se, cwal_value * ns ){
int rc;
cwal_value * v;
cwal_value * mod;
char const * modKey = "cgi";
cwal_size_t const keyLen = 3 /* == strlen(modKey) */;
cgi_native * cn;
s2_cgi * cgi;
assert(cwal_props_can(ns)) /* s2 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(se->e, sizeof(cgi_native));
if(!cn) return CWAL_RC_OOM;
*cn = cgi_native_empty;
mod = cwal_new_native_value(se->e, cn, cgi_native_finalize,
&cgi_native_empty);
if(!mod){
cwal_free(se->e, cn);
return CWAL_RC_OOM;
}
rc = cwal_prop_set( ns, modKey, keyLen, mod );
if(rc){
cwal_value_unref(mod);
goto end;
}
cwal_value_prototype_set( mod, s2_prototype_object(se) );
cn->vSelf = mod;
cgi = &cn->cgi;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(KEY) rc = cwal_prop_set( mod, KEY, strlen(KEY), v ); \
if(rc) goto end
#define FUNC(NAME,FP) \
v = cwal_new_function_value( se->e, FP, 0, 0, 0 ); \
CHECKV; \
SET(NAME); \
assert(!rc); \
if(rc) goto end
rc = s2_cgi_init(se, cgi);
if(rc){
rc = cwal_exception_setf(se->e, 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(se->e, "CGI", 3);
SET("__typename");
{
/* Add cgi.request... */
cwal_value * request = cwal_new_object_value(se->e);
if(!request){
rc = CWAL_RC_OOM;
goto end;
}
if( (rc = cwal_prop_set( mod, "request", 7, request )) ){
cwal_value_unref(request);
goto end;
}
#undef SET
#define SET(KEY) rc = cwal_prop_set( request, KEY, cwal_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.
*/
S2_MODULE_IMPL(cgimod,s2_cgi_install_to_interp);
/**
Module initializer for when the module is the only module in the
DLL.
*/
S2_MODULE_IMPL1(cgimod,s2_cgi_install_to_interp);
#undef THIS_CGI
#undef MARKER