/** * processor.c * * Copyright (C) 2003 - 2006 Bojan Smojver, Rexursive * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licence as published by the Free * Software Foundation; either version 2 of the Licence, 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 Licence for * more details. * * You should have received a copy of the GNU General Public Licence along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as two special exceptions, Bojan Smojver, Rexursive, gives * permission to: * * 1. Link, both statically and dynamically, the code of this program with * Apache HTTP Server, Apache Portable Runtime and Apache Portable Runtime * Utility Library from Apache Software Foundation (or with modified versions * of the above, that use the same licence - Apache Software Licence 1.1, 2.0 * or any later version), and distribute linked combinations including the * program and the above software from Apache Software Foundation. You must * obey the GNU General Public Licence in all respects for all of the code * used other than Apache HTTP Server, Apache Portable Runtime and Apache * Portable Runtime Utility Library. * * 2. Dynamically link any shared library with this program at run-time, * through the interface of SpinApplication or LoadModule run-time * configuration directives of Apache HTTP Server, as provided by this program * or Apache HTTP Server itself, regardless of licensing terms of those shared * libraries. You must obey the GNU General Public Licence in all respects for * all of the code used other than that of those shared libraries. * * If you modify this file, you may extend these exceptions to your version of * the file, but you are not obligated to do so. If you do not wish to do so, * delete one or both of these exception statements from your version. * */ #include "private.h" #include "scanner.h" static apr_status_t process_nodes(rxv_spin_node_t *node, rxv_spin_extra_t *extra){ char *buf; unsigned long val; apr_status_t status; apr_bucket_brigade *brigade=extra->brigade; apr_bucket *bucket; rxv_spin_txt_t *txt; rxv_spin_ref_t *ref,*rfr; rxv_spin_for_t *loop; rxv_spin_if_t *cond; for(;node;node=node->next){ switch(node->type){ case RXV_SPIN_TXT: txt=node->txt; #ifdef DEBUG fprintf(stderr,"txt=%" APR_OFF_T_FMT ",%" APR_SIZE_T_FMT "\n", txt->offset,txt->length); fflush(stderr); #endif if(txt->cache) bucket=apr_bucket_immortal_create(txt->cache,txt->length, extra->brigade->bucket_alloc); else bucket=apr_bucket_file_create(extra->fp,txt->offset,txt->length, extra->pool, extra->brigade->bucket_alloc); if(!bucket) return APR_ENOMEM; APR_BRIGADE_INSERT_TAIL(brigade,bucket); break; case RXV_SPIN_REF: ref=node->ref; #ifdef DEBUG fprintf(stderr,"ref=%s\n",ref->name); fflush(stderr); #endif if(!ref->looked){ /* only look if we didn't already */ if(ref->loop){ /* if reference is enclosed in a loop, we have to look in the data that belongs to that loop; only if loop data is rows we do the search, as singles cannot have columns */ if(ref->loop->ref->data->type==RXV_SPIN_DATA_RWS){ ref->data=apr_hash_get(ref->loop->ref->data->cols,ref->chunk, APR_HASH_KEY_STRING); } } else{ /* not loop */ ref->data=apr_hash_get(extra->ctx->data->cols,ref->chunk, APR_HASH_KEY_STRING); } ref->looked=RXV_SPIN_TRUE; } if(ref->data){ char *buf=NULL; apr_size_t len=0,index; rxv_spin_data_t *data; /* fully resolved data element */ if(ref->loop){ data=ref->data+ref->loop->index; index=ref->loop->index+1; } else{ data=ref->data; index=0; } /* skip metadata */ if(data->type>RXV_SPIN_DATA_RWS) continue; /* prepare data and size for bucket */ switch(ref->type){ case RXV_SPIN_REF_REG: if(data->type!=RXV_SPIN_DATA_SGL) continue; buf=data->data; len=data->size; break; case RXV_SPIN_REF_SIZ: buf=apr_ltoa(extra->pool,data->size); len=strlen(buf); break; case RXV_SPIN_REF_IND: buf=apr_ltoa(extra->pool,index); len=strlen(buf); break; default: /* must be an error, we don't know this */ return APR_EGENERAL; } /* only replace valid stuff (i.e. size, index and singles) */ if(buf && len>0){ /* We can do this because the bucket cleanup function always runs * before the data cleanup function, making sure that the data is * set aside (i.e. preserved). This, however, only works if the * data has lifetime of at least that of the bucket (i.e. the * lifetime of the request). */ bucket=apr_bucket_pool_create(buf,len,extra->pool, extra->brigade->bucket_alloc); APR_BRIGADE_INSERT_TAIL(brigade,bucket); } } break; case RXV_SPIN_FOR: loop=node->loop; ref=loop->ref; #ifdef DEBUG fprintf(stderr,"for=%s\n",ref->name); fflush(stderr); #endif if(!ref->looked){ /* only look if we didn't already */ if(ref->loop){ /* if reference is enclosed in a loop, we have to look in the data that belongs to that loop; only if loop data is rows we do the search, as singles cannot have columns */ if(ref->loop->ref->data->type==RXV_SPIN_DATA_RWS){ ref->data=apr_hash_get(ref->loop->ref->data->cols,ref->chunk, APR_HASH_KEY_STRING); } } else{ /* not loop */ ref->data=apr_hash_get(extra->ctx->data->cols,ref->chunk, APR_HASH_KEY_STRING); } ref->looked=RXV_SPIN_TRUE; } if(ref->data){ rxv_spin_data_t *data; /* fully resolved data element */ if(ref->loop) data=ref->data+ref->loop->index; else data=ref->data; switch(data->type){ case RXV_SPIN_DATA_SGL: if(data->data){ loop->index=0; if((status=process_nodes(loop->stmt,extra))!=APR_SUCCESS) return status; } break; case RXV_SPIN_DATA_RWS: if(data->cols) for(loop->index=0;loop->indexsize;loop->index++) if((status=process_nodes(loop->stmt,extra))!=APR_SUCCESS) return status; break; default: /* can't loop around this, skip */ continue; } } break; case RXV_SPIN_IF: cond=node->cond; ref=cond->ref; rfr=cond->rfr; #ifdef DEBUG fprintf(stderr,"if=%s\n",ref?ref->name:"NULL"); fflush(stderr); #endif if(!ref->looked){ /* only look if we didn't already */ if(ref->loop){ /* if reference is enclosed in a loop, we have to look in the data that belongs to that loop; only if loop data is rows we do the search, as singles cannot have columns */ if(ref->loop->ref->data->type==RXV_SPIN_DATA_RWS){ ref->data=apr_hash_get(ref->loop->ref->data->cols,ref->chunk, APR_HASH_KEY_STRING); } } else{ /* not loop */ ref->data=apr_hash_get(extra->ctx->data->cols,ref->chunk, APR_HASH_KEY_STRING); } ref->looked=RXV_SPIN_TRUE; } if(ref->data){ unsigned int truth; rxv_spin_data_t *data,*rdta; /* fully resolved data elements */ if(ref->loop) data=ref->data+ref->loop->index; else data=ref->data; /* metadata always yields false */ if(data->type>RXV_SPIN_DATA_RWS){ if(cond->neg && (status=process_nodes(cond->neg,extra))!=APR_SUCCESS) return status; continue; } switch(cond->oper){ case RXV_SPIN_OP_NONE: switch(ref->type){ case RXV_SPIN_REF_REG: truth=((data->type==RXV_SPIN_DATA_SGL && data->data) || (data->type==RXV_SPIN_DATA_RWS && data->cols) || RXV_SPIN_FALSE); break; case RXV_SPIN_REF_SIZ: truth=(data->size>0); break; case RXV_SPIN_REF_IND: truth=(ref->loop!=NULL || RXV_SPIN_FALSE); break; default: /* don't know this, false */ truth=RXV_SPIN_FALSE; break; } break; case RXV_SPIN_OP_MATCH: switch(ref->type){ case RXV_SPIN_REF_REG: truth=(data->type==RXV_SPIN_DATA_SGL && data->data && !ap_regexec(cond->reg,data->data,0,NULL,0)); break; case RXV_SPIN_REF_SIZ: if((buf=apr_ltoa(extra->pool,data->size))){ truth=!ap_regexec(cond->reg,buf,0,NULL,0); break; } return APR_ENOMEM; case RXV_SPIN_REF_IND: if(ref->loop){ if((buf=apr_ltoa(extra->pool,ref->loop->index+1))){ truth=!ap_regexec(cond->reg,buf,0,NULL,0); break; } } else{ truth=!ap_regexec(cond->reg,"0",0,NULL,0); break; } return APR_ENOMEM; default: /* must be an error, we don't know this */ return APR_EGENERAL; } break; case RXV_SPIN_OP_EQ_LIT: switch(ref->type){ case RXV_SPIN_REF_REG: truth=(data->type==RXV_SPIN_DATA_SGL && data->data && !strcmp(data->data,cond->lit)); break; case RXV_SPIN_REF_SIZ: if((buf=apr_ltoa(extra->pool,data->size))){ truth=!strcmp(buf,cond->lit); break; } return APR_ENOMEM; case RXV_SPIN_REF_IND: if(ref->loop){ if((buf=apr_ltoa(extra->pool,ref->loop->index+1))){ truth=!strcmp(buf,cond->lit); break; } } else{ truth=!strcmp("0",cond->lit); break; } return APR_ENOMEM; default: /* must be an error, we don't know this */ return APR_EGENERAL; } break; case RXV_SPIN_OP_EQ_NUM: switch(ref->type){ case RXV_SPIN_REF_REG: truth=(data->type==RXV_SPIN_DATA_SGL && data->data && atol(data->data)==cond->num); break; case RXV_SPIN_REF_SIZ: truth=(data->size==cond->num); break; case RXV_SPIN_REF_IND: if(ref->loop) truth=(ref->loop->index+1==cond->num); else truth=(0==cond->num); break; default: /* must be an error, we don't know this */ return APR_EGENERAL; } break; case RXV_SPIN_OP_EQ_REF: if(!rfr->looked){ /* only look if we didn't already */ if(rfr->loop){ /* if reference is enclosed in a loop, we have to look in the data that belongs to that loop; only if loop data is rows we do the search, as singles cannot have columns */ if(rfr->loop->ref->data->type==RXV_SPIN_DATA_RWS){ rfr->data=apr_hash_get(rfr->loop->ref->data->cols, rfr->chunk,APR_HASH_KEY_STRING); } } else{ /* not loop */ rfr->data=apr_hash_get(extra->ctx->data->cols, rfr->chunk,APR_HASH_KEY_STRING); } rfr->looked=RXV_SPIN_TRUE; } if(rfr->data){ if(rfr->loop) rdta=rfr->data+rfr->loop->index; else rdta=rfr->data; /* metadata always yields false */ if(rdta->type>RXV_SPIN_DATA_RWS){ if(cond->neg && (status=process_nodes(cond->neg,extra))!=APR_SUCCESS) return status; continue; } } else{ /* no data, always false */ if(cond->neg && (status=process_nodes(cond->neg,extra))!=APR_SUCCESS) return status; continue; } /* we are wasting CPU cycles and memory here as a tradeoff for slightly simpler code - life isn't perfect ;-) */ switch(rfr->type){ case RXV_SPIN_REF_REG: if(rdta->type==RXV_SPIN_DATA_SGL){ buf=rdta->data; val=(buf?atol(buf):0); } else{ buf=NULL; val=0; } break; case RXV_SPIN_REF_SIZ: if(!(buf=apr_ltoa(extra->pool,rdta->size))) return APR_ENOMEM; val=rdta->size; break; case RXV_SPIN_REF_IND: if(rfr->loop){ val=rfr->loop->index+1; if(!(buf=apr_ltoa(extra->pool,val))) return APR_ENOMEM; } else{ buf="0"; val=0; } break; default: /* must be an error, we don't know this */ return APR_EGENERAL; } switch(ref->type){ case RXV_SPIN_REF_REG: truth=(data->type==RXV_SPIN_DATA_SGL && data->data && buf && !strcmp(data->data,buf)); break; case RXV_SPIN_REF_SIZ: truth=(data->size==val); break; case RXV_SPIN_REF_IND: if(ref->loop) truth=(ref->loop->index+1==val); else truth=(0==val); break; default: /* must be an error, we don't know this */ return APR_EGENERAL; } break; case RXV_SPIN_OP_MODULO: switch(ref->type){ case RXV_SPIN_REF_REG: truth=(data->type==RXV_SPIN_DATA_SGL && data->data && atol(data->data)%cond->num==cond->res); break; case RXV_SPIN_REF_SIZ: truth=(data->size%cond->num==cond->res); break; case RXV_SPIN_REF_IND: if(ref->loop) truth=(ref->loop->index+1%cond->num==cond->res); else truth=(0%cond->num==cond->res); break; default: /* must be an error, we don't know this */ return APR_EGENERAL; } break; default: /* must be an error, we don't know this */ return APR_EGENERAL; } if(truth){ if(cond->pos && (status=process_nodes(cond->pos,extra))!=APR_SUCCESS) return status; } else if(cond->neg){ if((status=process_nodes(cond->neg,extra))!=APR_SUCCESS) return status; } } else if(cond->neg){ if((status=process_nodes(cond->neg,extra))!=APR_SUCCESS) return status; } break; } } return APR_SUCCESS; } static apr_status_t clear_nodes(rxv_spin_node_t *node){ rxv_spin_ref_t *ref,*rfr; rxv_spin_for_t *loop; rxv_spin_if_t *cond; for(;node;node=node->next){ switch(node->type){ case RXV_SPIN_REF: ref=node->ref; ref->data=NULL; ref->looked=RXV_SPIN_FALSE; break; case RXV_SPIN_FOR: loop=node->loop; ref=loop->ref; ref->data=NULL; ref->looked=RXV_SPIN_FALSE; clear_nodes(loop->stmt); break; case RXV_SPIN_IF: cond=node->cond; ref=cond->ref; ref->data=NULL; ref->looked=RXV_SPIN_FALSE; if(cond->oper==RXV_SPIN_OP_EQ_REF && (rfr=cond->rfr)){ rfr->data=NULL; rfr->looked=RXV_SPIN_FALSE; } clear_nodes(cond->neg); clear_nodes(cond->pos); break; } } return APR_SUCCESS; } static rxv_spin_cache_t *cache_create(apr_pool_t *pool,apr_hash_t *used){ rxv_spin_cache_t *cache; apr_pool_t *cpool; apr_pool_create(&cpool,pool); cache=apr_pcalloc(cpool,sizeof(*cache)); cache->pool=cpool; /* by default, all cache items are garbage until explicitly good */ cache->kill=1; /* register that we used this template */ apr_hash_set(used,cache,sizeof(cache->pool),cache); return cache; } apr_status_t rxv_spin_file(const char *file,rxv_spin_extra_t *extra, apr_pool_t *pool,apr_finfo_t *finfo){ int status; apr_status_t astatus; yyscan_t yyscanner; rxv_spin_cache_t *ready; FILE *fp; /* do we have it in cache */ if((ready=apr_hash_get(extra->cache,file,APR_HASH_KEY_STRING))){ /* register that we used this template */ apr_hash_set(extra->used,&ready->pool,sizeof(ready->pool),ready); /* if the template inode, modification time and size are same, we're OK */ if(finfo->inode==ready->inode && finfo->mtime==ready->mtime && finfo->size==ready->size){ extra->root=ready->root; extra->tpool=ready->pool; extra->ready=ready; APR_RING_REMOVE(ready,link); APR_RING_INSERT_HEAD(&extra->ctx->priv->list,ready,rxv_spin_cache,link); } else{ /* file modified, schedule for destruction on connection closing */ ready->kill=1; apr_hash_set(extra->cache,file,APR_HASH_KEY_STRING,NULL); APR_RING_REMOVE(ready,link); } } /* we have to create cache entry from scratch and parse */ if(!extra->ready){ ready=cache_create(pool,extra->used); ready->inode=finfo->inode; ready->mtime=finfo->mtime; ready->size=finfo->size; ready->sendfile=extra->sendfile; ready->cacheall=extra->cacheall; extra->tpool=ready->pool; extra->ready=ready; /* open an ANSI file descriptor for scanning */ if(!(fp=fopen(file,"r"))) return APR_EACCES; /* need a buffer, we'll use realloc to resize this, therefore malloc */ if(!(extra->buf=malloc(RXV_SPIN_MIN_SENDFILE))){ fclose(fp); return APR_ENOMEM; } extra->bsize=RXV_SPIN_MIN_SENDFILE; if(yylex_init(&yyscanner)){ fclose(fp); return apr_get_os_error(); } yyset_in(fp,yyscanner); yyset_extra(extra,yyscanner); status=rxv_spin_parse(yyscanner); yylex_destroy(yyscanner); /* clean up first, this is not pool space */ if(status){ fclose(fp); return APR_EGENERAL; } extra->ready->root=extra->root; fclose(fp); /* we survived up until now, put the newly parsed template into cache */ ready->file=apr_pstrdup(ready->pool,file); ready->kill=!extra->tcache; apr_hash_set(extra->cache,ready->file,APR_HASH_KEY_STRING,ready); APR_RING_INSERT_HEAD(&extra->ctx->priv->list,ready,rxv_spin_cache,link); } else{ /* clean up data */ clear_nodes(extra->root); } /* if we're not fully cached, we'll need the file opened */ if(!ready->cacheall && (astatus=apr_file_open(&extra->fp,file, APR_READ|(ready->sendfile?APR_SENDFILE_ENABLED:0), APR_OS_DEFAULT,extra->pool))!=APR_SUCCESS) return astatus; if((astatus=process_nodes(extra->root,extra))==APR_SUCCESS){ apr_bucket *eos=apr_bucket_eos_create(extra->brigade->bucket_alloc); if(eos) APR_BRIGADE_INSERT_TAIL(extra->brigade,eos); else return APR_ENOMEM; } return astatus; }