/* * webkernel.c * * A small embeddable web server. * * Author: Dario Rodriguez dario@softhome.net * This library is licensed on the terms of the GNU LGPL v2+ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include "sbuf.h" #include "socklib.h" #include "webkernel.h" #define FDBLOCK 256 #define CLIENTBLOCK 1024 #define CLIENTBLOCKBLOCK 256 #define MAXOUTBUF 128 #define BUFSIZE 8192 #define BUFBLOCK 128 #define BUFBLOCKBLOCK 256 typedef struct wk_client { wk *web; int connid; /* this is numblock*CLIENTBLOCK+offset_in_block */ int fd; int inbufid; int sizeoutbufids; int usedoutbufids; int outbufids[MAXOUTBUF]; } wk_client; typedef struct wk_clientblock { int sizeclients; int usedclients; unsigned char acquired[CLIENTBLOCKBLOCK/8]; wk_client *clients; } wk_clientblock; typedef struct wk_buf { sbuf buf; char bufdata[BUFSIZE]; } wk_buf; typedef struct wk_bufblock { int sizebufs; int usedbufs; unsigned char acquired[BUFBLOCK/8]; wk_buf *bufs; } wk_bufblock; typedef struct _wk { int serverfd; fd_set fdset; sselect *ssel; int sizeclientblocks; int usedclientblocks; wk_clientblock *clientblocks; int sizebufblocks; int usedbufblocks; wk_bufblock *bufblocks; } _wk; static int wk_accept(_wk *web); static wk_client *wk_clientacquire(_wk *web); static int wk_clientrelease(_wk *web, int connid); static wk_client *wk_clientget(_wk *web, int connid); wk * wk_init(int port, sselect *ssel, void (*callback_event)(/*wk *paramweb,int connid, wk_event event, void *userptr*/), void (*callback_http)(/*wk *paramweb,int connid, wk_uri *uri, void *userptr*/), void (*callback_continuation)(/*wk *paramweb,int connid, wk_uri *uri, void *userptr*/), void *userptr) { _wk *web; if(ssel==NULL || callback_http==NULL) return(NULL); if((web=malloc(sizeof(_wk)))==NULL) return(NULL); memset(web,0,sizeof(_wk)); web->serverfd=-1; web->ssel=ssel; FD_ZERO(&(web->fdset)); if((web->serverfd=ipv4_server(port))==-1) { wk_free((wk *)web),web=NULL; return(NULL); } sock_setunsafe(web->serverfd); FD_SET(web->serverfd,&(web->fdset)); sselect_addread(ssel,web->serverfd,NULL); return((wk *)web); } void wk_free(wk *paramweb) { int i,j,k,n; _wk *web=(_wk *)paramweb; wk_clientblock *cb; wk_client *client; if(web==NULL) return; /* release server fds */ if(web->serverfd!=-1) { sselect_delread(web->ssel,web->serverfd); FD_CLR(web->serverfd,&(web->fdset)); close(web->serverfd),web->serverfd=-1; } /* release client fds and free clients */ for(i=0;i<web->usedclientblocks;i++) { cb=web->clientblocks+i; if(cb->clients==NULL) continue; for(j=0;j<cb->sizeclients;j+=8) { if(cb->acquired[j>>3]==0) continue; for(k=0x1,n=0;n<8;n++,k<<=1) { if(!(cb->acquired[j>>3]&k)) continue; client=cb->clients+j+k; if(client->fd!=-1) { sselect_delread(web->ssel,client->fd); sselect_delwrite(web->ssel,client->fd); FD_CLR(client->fd,&(web->fdset)); close(client->fd),client->fd=-1; } } } cb->usedclients=0; cb->sizeclients=0; free(cb->clients),cb->clients=NULL; } web->usedclientblocks=0; web->sizeclientblocks=0; if(web->clientblocks!=NULL) free(web->clientblocks),web->clientblocks=NULL; /* free the main struct */ free(web),web=NULL; } wk_uri * wk_geturi(wk *paramweb, int connid) { } int wk_service(wk *paramweb) { int fds[FDBLOCK]; int fd; int n; int i; _wk *web=(_wk *)paramweb; wk_client *client; if(web==NULL) return(-1); while((n=sselect_getreadfiltered(web,&(web->fdset),fds,sizeof(fds)/sizeof(fds[0])))>0) { for(i=0;i<n;i++) { fd=fds[i]; if(fd==web->serverfd) { /* accept new connection */ wk_accept(web); continue; } if((client=(wk_client *)sselect_getuserptr(web->ssel,fd))==NULL || client->web!=(wk *)web) continue; /* internal error */ } } } int wk_serve_buffer_as_file(wk *paramweb, int connid, void *data, int datalen, const char *mime) { } int wk_serve_file(wk *paramweb, int connid, char *filename, const char *mime) { } int wk_serve_error(wk *paramweb, int connid, wk_error wkerror) { } int wk_writestr(wk *paramweb, int connid, char *str) { } int wk_write(wk *paramweb, int connid, void *data, int datalen) { } int wk_close(wk *paramweb, int connid) { } char * wk_uri_getheader(wk_uri *wkuri, char *header, char *defaultvalue) { } char * wk_uri_getheaderbynum(wk_uri *wkuri, int num) { } const char * mime_getdefault(const char *filename, const char *defaultmime) { const char *dotptr,*dotdotptr; int i; struct { const char *ext; const char *mime; } dotdot[]={{"tar.gz","application/x-tgz"}}, dot[]={ {"html","text/html"}, {"htm","text/html"}, {"shtml","text/html"}, {"css","text/css"}, {"gif","image/gif"}, {"jpeg","image/jpeg"}, {"jpg","image/jpeg"}, {"png","image/png"}, {"tiff","image/tiff"}, {"tif","image/tiff"}, {"bmp","image/x-ms-bmp"}, {"svg","image/svg+xml"}, {"svgz","image/svg+xml"}, {"js","application/x-javascript"}, {"atom","application/atom+xml"}, {"txt","text/plain"}, {"json","application/json"}, {"pdf","application/pdf"}, {"zip","application/zip"}, {"mp3","audio/mpeg"}, {"wav","audio/x-wav"}, {"ogg","audio/ogg"}, {"mp4","video/mp4"}, {"webm","video/webm"}, {"avi","video/x-msvideo"}}; if(filename==NULL || (dotptr=strrchr(filename,'.'))==NULL) return(defaultmime); if(dotptr>filename) { for(dotdotptr=(dotptr-1);dotdotptr>filename && *dotdotptr!='.';dotdotptr--) ; if(*dotdotptr=='.') { for(i=0;i<(sizeof(dotdot)/sizeof(dotdot[0]));i++) { if(strcmp(dotdotptr+1,dotdot[i].ext)==0) return(dotdot[i].mime); } } } for(i=0;i<(sizeof(dot)/sizeof(dot[0]));i++) { if(strcmp(dotptr+1,dot[i].ext)==0) return(dot[i].mime); } return(defaultmime); } int wk_sbufacquire(wk *paramweb) { int i,j,k; wk_bufblock *bb; sbuf *buf; _wk *web=(_wk *)paramweb; if(web==NULL) return(-1); /* make sure there are free bufblocks */ if(web->usedbufblocks==web->sizebufblocks) { wk_bufblock *newbb; if((newbb=(wk_bufblock *)realloc(web->bufblocks,web->sizebufblocks+BUFBLOCKBLOCK))==NULL) return(-1); /* insufficient memory */ web->bufblocks=newbb; memset(web->bufblocks+web->sizebufblocks,0,BUFBLOCKBLOCK*sizeof(wk_bufblock)); web->sizebufblocks+=BUFBLOCKBLOCK; } /* search for a block with unused sbufs (or the first unalloc'd block) */ for(i=0;i<web->sizebufblocks;i++) { bb=web->bufblocks+i; if(bb->bufs==NULL || bb->usedbufs<bb->sizebufs) break; } if(i>=web->sizebufblocks) return(-1); /* internal error */ /* alloc block if not alloc'd */ if(bb->bufs==NULL) { if((bb->bufs=malloc(sizeof(wk_buf)*BUFBLOCK))==NULL) return(-1); /* insufficient memory */ memset(bb->bufs,0,sizeof(wk_buf)*BUFBLOCK); bb->sizebufs=BUFBLOCK; bb->usedbufs=0; } /* get first unused sbuf */ for(j=0;j<bb->sizebufs;j+=8) { if(bb->acquired[j>>3]!=0xff) break; } if(j>=bb->sizebufs) return(-1); /* internal error */ for(k=0x01;k<0x100;k<<=1,j++) { if((bb->acquired[j>>3]&k)==0) break; } if(k>=0x100) return(-1); /* internal error */ bb->acquired[j>>3]|=k; bb->usedbufs++; /* initialize sbuf and return it */ buf=&(bb->bufs[j].buf); sbuf_staticinit(buf,BUFSIZE,bb->bufs[j].bufdata); return(i*BUFBLOCK+j); } int wk_bufrelease(wk *paramweb, int sbufid) { sbuf *buf; int numblock,j,bit; _wk *web=(_wk *)paramweb; if(web==NULL) return(-1); numblock=sbufid/BUFBLOCK; j=sbufid%BUFBLOCK; bit=0x1<<(j&3); if((buf=wk_sbufget(web,sbufid))==NULL) return(-1); web->bufblocks[numblock].usedbufs--; web->bufblocks[numblock].acquired[j>>3]&=(~bit); sbuf_staticfree(buf); memset(web->bufblocks[numblock].bufs[j].bufdata,0,sizeof(BUFSIZE)); return(0); } sbuf * wk_bufget(wk *paramweb, int sbufid) { int numblock,j,bit; _wk *web=(_wk *)paramweb; if(web==NULL) return(NULL); numblock=sbufid/BUFBLOCK; j=sbufid%BUFBLOCK; bit=0x1<<(j&3); if(web==NULL || web->bufblocks==NULL || web->sizebufblocks<=numblock || web->bufblocks[numblock].bufs==NULL || (web->bufblocks[numblock].acquired[j>>3]&bit)!=bit) return(NULL); return(&(web->bufblocks[numblock].bufs[j].buf)); } /* local functions */ static int wk_accept(_wk *web) { int newfd; wk_client *client; if((newfd=sock_accept(web->serverfd))==-1) return(-1); if((client=wk_clientacquire(web))==NULL) { close(newfd),newfd=-1; return(-1); } client->fd=newfd; FD_SET(client->fd,&(web->fdset)); sselect_addread(web->ssel,client->fd,(void *)client); return(0); } static wk_client * wk_clientacquire(_wk *web) { int i,j,k; wk_clientblock *cb; wk_client *client; /* make sure there are free clientblocks */ if(web->usedclientblocks==web->sizeclientblocks) { wk_clientblock *newcb; if((newcb=(wk_clientblock *)realloc(web->clientblocks,web->sizeclientblocks+CLIENTBLOCKBLOCK))==NULL) return(NULL); /* insufficient memory */ web->clientblocks=newcb; memset(web->clientblocks+web->sizeclientblocks,0,CLIENTBLOCKBLOCK*sizeof(wk_clientblock)); web->sizeclientblocks+=CLIENTBLOCKBLOCK; } /* search for a block with unused clients (or the first unalloc'd block) */ for(i=0;i<web->sizeclientblocks;i++) { cb=web->clientblocks+i; if(cb->clients==NULL || cb->usedclients<cb->sizeclients) break; } if(i>=web->sizeclientblocks) return(NULL); /* internal error */ /* alloc block if not alloc'd */ if(cb->clients==NULL) { if((cb->clients=malloc(sizeof(wk_client)*CLIENTBLOCK))==NULL) return(NULL); /* insufficient memory */ memset(cb->clients,0,sizeof(wk_client)*CLIENTBLOCK); cb->sizeclients=CLIENTBLOCK; cb->usedclients=0; } /* get first unused client */ for(j=0;j<cb->sizeclients;j+=8) { if(cb->acquired[j>>3]!=0xff) break; } if(j>=cb->sizeclients) return(NULL); /* internal error */ for(k=0x01;k<0x100;k<<=1,j++) { if((cb->acquired[j>>3]&k)==0) break; } if(k>=0x100) return(NULL); /* internal error */ cb->acquired[j>>3]|=k; cb->usedclients++; /* initialize client and return it */ client=cb->clients+j; memset(client,0,sizeof(wk_client)); client->web=(wk *)web; client->connid=i*CLIENTBLOCK+j; client->fd=-1; return(client); } static int wk_clientrelease(_wk *web, int connid) { wk_client *client; int numblock,j,bit; numblock=connid/CLIENTBLOCK; j=connid%CLIENTBLOCK; bit=0x1<<(j&3); if((client=wk_clientget(web,connid))==NULL) return(-1); web->clientblocks[numblock].usedclients--; web->clientblocks[numblock].acquired[j>>3]&=(~bit); memset(client,0,sizeof(wk_client)); return(0); } static wk_client * wk_clientget(_wk *web, int connid) { int numblock,j,bit; numblock=connid/CLIENTBLOCK; j=connid%CLIENTBLOCK; bit=0x1<<(j&3); if(web==NULL || web->clientblocks==NULL || web->sizeclientblocks<=numblock || web->clientblocks[numblock].clients==NULL || (web->clientblocks[numblock].acquired[j>>3]&bit)!=bit) return(NULL); return(web->clientblocks[numblock].clients+j); }