Login
Artifact [a9a498f38e]
Login

Artifact a9a498f38ecbe227d24b7afc069b4d64f4e46ba7:


/* -*- 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