/******************************************************************** Copyright (c) 2004, WebThing Ltd Author: Nick Kew This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *********************************************************************/ #include "xmlns.h" #include #include #include #include #include #include "apr_dbd.h" #include "mod_dbd.h" #include "mod_form.h" typedef enum { EVENT_START, EVENT_END } where_t ; typedef struct sql_ctx sql_ctx ; typedef void ( *sql_handler_t ) (xmlns_public*, sql_ctx*, where_t) ; struct sql_ctx { //apr_hash_t* handlers ; /* elements implemented */ apr_table_t* args ; /* args to this element */ apr_table_t* form ; /* form vars (GET or POST) */ /* sql stuff from driver */ apr_dbd_t* sql ; /* database handle */ apr_dbd_driver_t* driver ; /* driver functions */ apr_dbd_transaction* trans; /* current transaction */ /* sql errors */ int errnum ; /* last SQL error */ const char* errfmt ; /* format for reporting errors to client */ /* working buffer stuff */ size_t offset ; size_t avail ; char* buf ; } ; /* SQL namespace */ static const char* XMLNS_SQL = "http://apache.webthing.com/sql#" ; #define BUFSZ 2048 static void sql_alloc(apr_pool_t* pool, sql_ctx* ctx, size_t len) { char* newbuf ; if ( len <= ( ctx->avail - ctx->offset ) ) return ; else while ( len > ( ctx->avail - ctx->offset ) ) ctx->avail += BUFSZ ; newbuf = realloc(ctx->buf, ctx->avail) ; if ( newbuf != ctx->buf ) { if ( ctx->buf ) apr_pool_cleanup_kill(pool, ctx->buf, (void*)free) ; apr_pool_cleanup_register(pool, newbuf, (void*)free, apr_pool_cleanup_null); ctx->buf = newbuf ; } } static void sql_append(apr_pool_t* pool, sql_ctx* ctx, const char* buf, size_t len) { sql_alloc(pool, ctx, len) ; memcpy(ctx->buf+ctx->offset, buf, len) ; ctx->offset += len ; } #define FLUSH sql_append(r->pool, sctx, bufp, len) ; bufp += len #define TEST_ESCAPED if ( ( p > query ) && ( p[-1] == '\\' ) ) break #define TEST_END if ( e = strchr(p, ';') , e == NULL ) break static const char* sql_build_string(request_rec* r, sql_ctx* sctx, const char* qarg, int sqlescape) { const char* query = apr_table_get(sctx->args, qarg) ; /* count %s */ /* for each arg */ /* interpolate arg */ const char* bufp = query ; size_t len = 0 ; const char* p ; const char* e ; const char* var ; if ( ! query ) { return NULL ; } sctx->offset = 0 ; for ( p = query ; *p ; ++p ) { switch ( *p ) { case '$': /* interpolate env var */ TEST_ESCAPED ; TEST_END ; FLUSH ; var = apr_pstrndup(r->pool, p+1, e - p - 1) ; var = apr_table_get(r->subprocess_env, var) ; if ( var ) { if ( sqlescape ) { var = sctx->driver->escape(r->pool, var, sctx->sql) ; } sql_append(r->pool, sctx, var, strlen(var)) ; } p = e ; break ; case '%': /* interpolate form var */ TEST_ESCAPED ; TEST_END ; FLUSH ; var = apr_pstrndup(r->pool, p+1, e - p - 1) ; var = apr_table_get(sctx->form, var) ; if ( var ) { if ( sqlescape ) { var = sctx->driver->escape(r->pool, var, sctx->sql) ; } sql_append(r->pool, sctx, var, strlen(var)) ; } p = e ; break ; case '@': /* interpolate arg value */ TEST_ESCAPED ; TEST_END ; FLUSH ; var = apr_pstrndup(r->pool, p+1, e - p - 1) ; var = apr_table_get(sctx->args, var) ; if ( var ) { if ( sqlescape ) { var = sctx->driver->escape(r->pool, var, sctx->sql) ; } sql_append(r->pool, sctx, var, strlen(var)) ; } p = e ; break ; default: ++len ; break ; } } FLUSH ; return sctx->buf ; } #undef FLUSH static void sql_query(xmlns_public* ctxt, sql_ctx* sctx, where_t where) { int result ; int nrows; const char* query ; const char* errmsg ; const char* errfmt ; if ( where == EVENT_START ) { query = sql_build_string(ctxt->f->r, sctx, "query", 1) ; result = sctx->driver->query(sctx->sql, sctx->trans, &nrows, query) ; if ( result != 0 ) { sctx->errnum = result ; errmsg = sctx->driver->error(sctx->sql, result) ; if ( errmsg ) { errfmt = sql_build_string(ctxt->f->r, sctx, "errfmt", 0) ; if ( ! errfmt ) errfmt = sctx->errfmt ; if ( errfmt && *errfmt ) ap_fprintf(ctxt->f->next, ctxt->bb, errfmt, errmsg) ; } else { errmsg = "(null)" ; } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctxt->f->r, "Error %d executing \"%s\": %s", result, query, errmsg ) ; /* an error happened: log it and depending on error mode maybe echo */ } } } static void sql_error(xmlns_public* ctxt, sql_ctx* sctx, where_t where) { const char* format ; if ( where == EVENT_START ) { format = sql_build_string(ctxt->f->r, sctx, "format", 0) ; sctx->errfmt = apr_pstrdup(ctxt->f->r->pool, format) ; } } #define FLUSH ap_fwrite(ctxt->f->next, ctxt->bb, bufp, len) ; bufp += len static void sql_select(xmlns_public* ctxt, sql_ctx* sctx, where_t where) { size_t len ; int result ; int vnum ; const char* bufp ; const char* var ; const char* p ; const char* e ; const char* errfmt ; const char* errmsg ; const char* query ; const char* format ; apr_dbd_results* results = NULL ; apr_dbd_row* row = NULL ; if ( where == EVENT_START ) { query = sql_build_string(ctxt->f->r, sctx, "query", 1) ; result = sctx->driver->select(ctxt->f->r->pool, sctx->sql, sctx->trans, &results, query, 0); // format = apr_table_get(sctx->args, "format") ; format = sql_build_string(ctxt->f->r, sctx, "format", 0) ; if ( result != 0 ) { /* an error happened: log it and depending on error mode maybe echo */ sctx->errnum = result ; errmsg = sctx->driver->error(sctx->sql, result) ; if ( errmsg ) { // errfmt = apr_table_get(sctx->args, "errfmt") ; errfmt = sql_build_string(ctxt->f->r, sctx, "errfmt", 0) ; if ( ! errfmt ) errfmt = sctx->errfmt ; if ( errfmt && *errfmt ) ap_fprintf(ctxt->f->next, ctxt->bb, errfmt, errmsg) ; } else { errmsg = "(null)" ; } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctxt->f->r, "Error %d executing \"%s\": %s", result, query, errmsg ) ; /* an error happened: log it and depending on error mode maybe echo */ } else { while ( sctx->driver->get_row(ctxt->f->r->pool, results, &row, -1) != -1){ bufp = format ; len = 0 ; for ( p = format ; *p ; ++p ) { switch ( *p ) { case '%': TEST_ESCAPED ; TEST_END ; FLUSH ; var = apr_pstrndup(ctxt->f->r->pool, p+1, e - p - 1) ; vnum = atoi(var) ; var = sctx->driver->get_entry(row, vnum) ; if ( var ) ap_fputs(ctxt->f->next, ctxt->bb, var) ; p = e ; bufp = e+1 ; len = -1 ; break ; default: break ; } ++len ; } FLUSH ; } } } } #undef FLUSH static void sql_transaction(xmlns_public* ctxt, sql_ctx* sctx, where_t where) { int error ; const char* errmsg; const char* event; switch ( where ) { case EVENT_END: error = apr_dbd_transaction_end(sctx->driver, sctx->sql, sctx->trans) ; event = "EndTransaction"; sctx->trans = NULL; break ; case EVENT_START: error = apr_dbd_transaction_start(sctx->driver, ctxt->f->r->pool, sctx->sql, &sctx->trans) ; event = "StartTransaction"; break ; } if ( error ) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctxt->f->r, "Error in %s: %s", event, errmsg) ; } } static void sql_html_results(xmlns_public* ctxt, sql_ctx* sctx, where_t where) { const char* arg ; char* arg1 ; const char* arg2 ; char* arg3 = NULL ; char* arg4 ; apr_table_t* args = sctx->args ; switch ( where ) { case EVENT_END: ap_fputs(ctxt->f->next, ctxt->bb, "\n") ; break ; case EVENT_START: ap_fputs(ctxt->f->next, ctxt->bb, "f->next, ctxt->bb, " class=\"", arg, "\"", NULL) ; if ( arg = apr_table_get(args, "id"), arg ) ap_fputstrs(ctxt->f->next, ctxt->bb, " id=\"", arg, "\"", NULL) ; if ( arg = apr_table_get(args, "summary"), arg ) ap_fputstrs(ctxt->f->next, ctxt->bb, " summary=\"", arg, "\"", NULL) ; else if ( arg2 ) ap_fputstrs(ctxt->f->next, ctxt->bb, " summary=\"", arg2, "\"", NULL) ; ap_fputs(ctxt->f->next, ctxt->bb, ">\n") ; if ( arg2 ) ap_fputstrs(ctxt->f->next, ctxt->bb, "", arg2, "\n", NULL) ; if ( arg = apr_table_get(args, "headers"), arg ) { arg4 = apr_pstrdup(ctxt->f->r->pool, arg) ; ap_fputs(ctxt->f->next, ctxt->bb, "") ; for ( arg1 = apr_strtok(arg4, ",",&arg3) ; arg1 ; arg1 = apr_strtok(NULL, ",", &arg3) ) { ap_fputstrs(ctxt->f->next, ctxt->bb, "", arg1, "", NULL) ; } ap_fputs(ctxt->f->next, ctxt->bb, "\n") ; } ap_fputs(ctxt->f->next, ctxt->bb, "") ; break ; } } //typedef void (*handler_t)(xmlns_public*, sql_ctx*, where_t) ; static sql_handler_t sql_handler(const char* name, int len) { static struct { const char* name; sql_handler_t func; } handlers[] = { {"query", sql_query} , {"select", sql_select} , {"error", sql_error} , {"transaction", sql_transaction} , {"results", sql_html_results} , {NULL, NULL} }; int i; for (i=0; handlers[i].name != NULL; ++i) { if (!strncmp(handlers[i].name, name, len)) { return handlers[i].func; } } return NULL; } static int sql_end(xmlns_public* ctx, const parsedname* name3) { sql_ctx* sctx ; sql_handler_t handler ; if ( !strncmp("dbd", name3->elt, name3->eltlen) ) { xmlns_set_appdata(ctx, XMLNS_SQL, NULL) ; } else { if ( sctx = xmlns_get_appdata(ctx, XMLNS_SQL) , sctx ) { //handler = apr_hash_get(sctx->handlers, name3->elt, name3->eltlen) ; handler = sql_handler(name3->elt, name3->eltlen) ; (*handler)(ctx, sctx, EVENT_END) ; } else { ;/* error */ } } return OK ; } static apr_table_t* sql_args(apr_table_t* args, const xmlns_attr_t* atts) { const char* attname ; const char* attval ; int i ; apr_table_clear(args) ; for ( i = 0 ; ; ++i ) { attname = xmlns_get_attr_name(atts, i) ; if ( ! attname ) break ; attval = xmlns_get_attr_val(atts, i) ; apr_table_setn(args, attname, attval) ; } return args ; } static int sql_start(xmlns_public* ctx, const parsedname* name3, const xmlns_attr_t* atts) { sql_ctx* sctx ; sql_handler_t handler ; const char* arg ; const char* errmsg ; dbd_t* dbd; apr_table_t* args ; apr_table_t* (*form_vars)(request_rec*) ; if ( !strncmp("dbd", name3->elt, name3->eltlen) ) { sctx = apr_pcalloc(ctx->f->r->pool, sizeof(sql_ctx) ) ; //sctx->handlers = apr_hash_make(ctx->f->r->pool) ; args = apr_table_make(ctx->f->r->pool, 6) ; sctx->args = sql_args(args, atts) ; if ( form_vars = APR_RETRIEVE_OPTIONAL_FN(form_data), form_vars ) { sctx->form = form_vars(ctx->f->r) ; } /* set driver: set sctx handlers or bail out */ if ( arg = apr_table_get(args, "driver") , arg ) { dbd = dbd_acquire(ctx->f->r); sctx->driver = dbd->driver; sctx->sql = dbd->handle; } if ( ! sctx->sql ) { /* error message for failed to acquire */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->f->r, "Error acquiring connection to %s", arg) ; errmsg = sql_build_string(ctx->f->r, sctx, "errmsg", 0) ; if ( errmsg ) ap_fputs(ctx->f->next, ctx->bb, errmsg) ; return OK ; } //apr_hash_set(sctx->handlers, "query", APR_HASH_KEY_STRING, sql_query) ; //apr_hash_set(sctx->handlers, "select", APR_HASH_KEY_STRING, sql_select) ; #if 0 apr_hash_set(sctx->handlers, "transaction", APR_HASH_KEY_STRING, sql_transaction) ; #endif //apr_hash_set(sctx->handlers, "error", APR_HASH_KEY_STRING, sql_error) ; /* set dbname if present */ if ( arg = apr_table_get(args, "dbname") , arg ) { // sctx->errnum = sctx->driver->set_dbname(sctx->sql, arg) ; sctx->errnum = apr_dbd_set_dbname(sctx->driver, ctx->f->r->pool, sctx->sql, arg) ; if ( sctx->errnum ) { // errmsg = sctx->driver->error(sctx->sql, sctx->errnum) ; errmsg = apr_dbd_error(sctx->driver, sctx->sql, sctx->errnum) ; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->f->r, "Error selecting database %s: %s", arg, errmsg) ; if ( errmsg ) ap_fputs(ctx->f->next, ctx->bb, errmsg) ; } } /* set output method stuff */ arg = apr_table_get(args, "output") ; /* if ( ! strcmp(arg, "html") ) { apr_hash_set(sctx->handlers, "results", APR_HASH_KEY_STRING, sql_html_results) ; } */ xmlns_set_appdata(ctx, XMLNS_SQL, sctx) ; } else { /* things are initialised: lookup function to handle this */ if ( sctx = xmlns_get_appdata(ctx, XMLNS_SQL) , sctx ) { /* if (handler = apr_hash_get(sctx->handlers, name3->elt, name3->eltlen) , handler ) { */ if (handler = sql_handler(name3->elt, name3->eltlen) , handler ) { sql_args(sctx->args, atts) ; (*handler)(ctx, sctx, EVENT_START) ; } else { /* no handler: maybe debug if verbose */ ; } } else { /* no sctx: error */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->f->r, "Error processing SQL directive: no DBD active") ; } } return OK ; } static void sql_hooks(apr_pool_t* pool) { static const xmlns xmlns_sql = { sql_start , /* StartElement */ sql_end , /* EndElement */ NULL , NULL , NULL , NULL } ; ap_register_provider(pool, "xmlns", XMLNS_SQL , "1.0", &xmlns_sql) ; } module AP_MODULE_DECLARE_DATA sql_module = { STANDARD20_MODULE_STUFF, NULL, NULL, NULL, NULL, NULL, sql_hooks } ;