/*
 * 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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include "sbuf.h"
#include "socklib.h"
#include "webkernel.h"

#define FDBLOCK 256
#define CLIENTBLOCK 1024
#define CLIENTBLOCKBLOCK 256
#define MAXOUTBUF 128
#ifndef BUFSIZE
#define BUFSIZE 8192
#endif
#define BUFBLOCK 128
#define BUFBLOCKBLOCK 256
#define POSTBLOCK 16
#define ALIVETIMEOUT 15
#define MAXETAGSIZE 127

#define HEXNIBBLE2BIN(a) (((a)>='0' && (a)<='9')?(a)-'0':((a)>='a' && (a)<='f')?(a)-'a'+10:((a)>='A' && (a)<='F')?(a)-'A'+10:0)
#define ISHEXNIBBLE(a) ((((a)>='0' && (a)<='9')||((a)>='a' && (a)<='f')||((a)>='A' && (a)<='F'))?1:0)


typedef struct wk_post {
        char *name;
        char *value;
        char *tofile;
        int bufid;
        int filewritten;
        int valueterminated;
} wk_post;

typedef enum wkc_status {
        wkc_header=0,
        wkc_post,
} wkc_status;

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];
        int uriready;
        wk_uri uri;
        int headerbufid;
        wkc_status status;
        int keepalive;
        int continuationactive;
        int fdtoserve;
        int sizepost;
        int usedpost;
        wk_post *post;
        int pendingpost;
        int inpostvar;
        int cookiebufid;
        char etag[MAXETAGSIZE+1];
        time_t lastio;
} wk_client;

typedef struct wk_clientblock {
        int sizeclients;
        int usedclients;
        unsigned char acquired[CLIENTBLOCK/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;
        void (*callback_event)(/*wk *web,int connid, wk_event event, void *userptr*/);
        wk_action (*callback_http)(/*wk *web,int connid, wk_uri *uri, void *userptr*/);
        wk_action (*callback_post)(/*wk *web,int connid, wk_uri *uri, void *userptr*/);
        wk_action (*callback_continuation)(/*wk *web,int connid, wk_uri *uri, void *userptr*/);
        void *userptr;
        time_t lasttimeoutcheck;
} _wk;

static wk_client *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);
static int wk_clientservicereadheader(_wk *web, wk_client *client);
static int wk_clientservicereadpost(_wk *web, wk_client *client);
static int wk_postadd(_wk *web, wk_client *client,char *varname, char *tofile);
static int wk_postset(_wk *web, wk_client *client,char *varname, char *data, int datalen);
static char *wk_postget(_wk *web, wk_client *client,char *varname, int *isfile);
static int wk_postfree(_wk *web, wk_client *client);
static int wk_etagnotmodified(_wk *web, wk_client *client);

#ifdef WK_DEBUG_IO
#define DEBUG_IN_PRE { int sbp=sbuf_count(in);
#define DEBUG_IN_POST wk_debug_io("READ",fd,sbuf_ptr(in)+sbp,sbuf_count(in)-sbp); }
#define DEBUG_OUT_PRE { int sbu=out->got;
#define DEBUG_OUT_POST wk_debug_io("WRITTEN",fd,out->buf+sbu,out->got-sbu); }

static void
wk_debug_io(char *iotype, int fd, char *ptr, long int size)
{
        int n,c;
        fprintf(stderr,"%s %li bytes on %i:",iotype,size,fd);
        for(n=0;n<size;n++) {
                c=((unsigned char *)ptr)[n];
                if(c=='\r' || c=='\n')
                        fprintf(stderr,"\\%c",(c=='\r')?'r':'n');
                else if(c<' ' || c>'~')
                        fprintf(stderr,"\\x%02X",c);
                else
                        fprintf(stderr,"%c",c);
        }
        fprintf(stderr,"\n");
}

#else
#define DEBUG_IN_PRE
#define DEBUG_IN_POST
#define DEBUG_OUT_PRE
#define DEBUG_OUT_POST
#endif

#ifdef WK_DEBUG_CONN
#include "loglib.h"
#define DEBUG_CONN(a) log_write a
#else
#define DEBUG_CONN(a)
#endif

wk *
wk_init(int serverfd, sselect *ssel, void (*callback_event)(/*wk *web,int connid, wk_event event, void *userptr*/), wk_action (*callback_http)(/*wk *web,int connid, wk_uri *uri, void *userptr*/), wk_action (*callback_post)(/*wk *web,int connid, wk_uri *uri, void *userptr*/), wk_action (*callback_continuation)(/*wk *web,int connid, wk_uri *uri, void *userptr*/), void *userptr)
{
        _wk *web;
        if(ssel==NULL || callback_http==NULL || serverfd==-1)
                return(NULL);
        if((web=malloc(sizeof(_wk)))==NULL)
                return(NULL);
        memset(web,0,sizeof(_wk));
        web->serverfd=serverfd;
        web->callback_event=callback_event;
        web->callback_http=callback_http;
        web->callback_post=callback_post;
        web->callback_continuation=callback_continuation;
        web->userptr=userptr;
        web->ssel=ssel;
        FD_ZERO(&(web->fdset));
        FD_SET(web->serverfd,&(web->fdset));
        sselect_addread(ssel,web->serverfd,NULL);
        if(web->callback_event!=NULL)
                web->callback_event((wk *)web,-1,wke_init,web->userptr);
        return((wk *)web);
}

void
wk_free(wk *paramweb)
{
        int i,j,k,n;
        _wk *web=(_wk *)paramweb;
        wk_clientblock *cb;
        wk_client *client;
        wk_bufblock *bb;
        if(web==NULL)
                return;
        if(web->callback_event!=NULL)
                web->callback_event((wk *)web,-1,wke_fini,web->userptr);
        /* unregister the server fd */
        if(web->serverfd!=-1) {
                sselect_delread(web->ssel,web->serverfd);
                FD_CLR(web->serverfd,&(web->fdset));
        }
        /* 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+n;
                                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;
                                }
                                if(client->fdtoserve!=-1)
                                        close(client->fdtoserve),client->fdtoserve=-1;
                                wk_postfree(web,client);
                        }
                }
                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;
        /* release the used sbuf */
        for(i=0;i<web->usedbufblocks;i++) {
                bb=web->bufblocks+i;
                if(bb->bufs==NULL)
                        continue;
                bb->usedbufs=0;
                bb->sizebufs=0;
                free(bb->bufs),bb->bufs=NULL;
        }
        web->usedbufblocks=0;
        web->sizebufblocks=0;
        if(web->bufblocks!=NULL)
                free(web->bufblocks),web->bufblocks=NULL;
        /* free the main struct */
        free(web),web=NULL;
}

wk_uri *
wk_geturi(wk *paramweb, int connid)
{
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL)
                return(NULL);
        if((client=wk_clientget(web,connid))==NULL)
                return(NULL);
        if(client->uriready==0)
                return(NULL);
        return(&(client->uri));
}

int
wk_service(wk *paramweb)
{
        int fds[FDBLOCK];
        int fd;
        int n,i,j,k;
        sbuf *in;
        sbuf *out;
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        wk_clientblock *cb;
        time_t now;
        if(web==NULL)
                return(-1);
        now=time(NULL);
        while((n=sselect_getreadfiltered(web->ssel,&(web->fdset),fds,sizeof(fds)/sizeof(fds[0])))>0) {
                for(i=0;i<n;i++) {
                        fd=fds[i];
                        if(fd==web->serverfd) {
                                do {
                                        /* accept new connection */
                                        if((client=wk_accept(web))!=NULL) {
                                                if(web->callback_event!=NULL)
                                                        web->callback_event((wk *)web,client->connid,wke_connected,web->userptr);
                                                client->lastio=now;
                                        }
                                } while(sock_readable(fd)==0);
                                continue; /* all done here */
                        }
                        if((client=(wk_client *)sselect_getuserptr(web->ssel,fd))==NULL || client->web!=(wk *)web) {
                                sselect_delread(web->ssel,fd);
                                close(fd),fd=-1;
                                continue; /* internal error */
                        }
                        if((in=wk_sbufget((wk *) web,client->inbufid))==NULL) {
                                wk_close((wk *)web,client->connid);
                                continue; /* internal error */
                        }
                        sbuf_discard(in);
                        if(sbuf_unused(in)<=0) {
                                /* no room for the new data */
                                sselect_delread(web->ssel,fd);
                                continue;
                        }
                        DEBUG_IN_PRE;
                        if(sbuf_fill(in,fd,sock_queued(fd))==0) {
                                /* client has closed connection */
                                wk_close((wk *) web, client->connid);
                                continue;
                        }
                        DEBUG_IN_POST;
                        client->lastio=now;
                        if(client->status==wkc_header) {
                                if(wk_clientservicereadheader(web,client)==-1) {
                                        /* internal error, protocol error or no enough memory */
                                        wk_close((wk *) web, client->connid);
                                        continue;
                                }
                        }
                        if(client->status==wkc_post) {
                                if(wk_clientservicereadpost(web,client)==-1) {
                                        /* internal error, protocol error or no enough memory */
                                        wk_close((wk *) web, client->connid);
                                        continue;
                                }
                        }
                        sbuf_discard(in);
                        if(sbuf_unused(in)>0)
                                sselect_addread(web->ssel,client->fd,(void *)client);
                }
        }
        while((n=sselect_getwritefiltered(web->ssel,&(web->fdset),fds,sizeof(fds)/sizeof(fds[0])))>0) {
                for(i=0;i<n;i++) {
                        fd=fds[i];
                        if((client=(wk_client *)sselect_getuserptr(web->ssel,fd))==NULL || client->web!=(wk *)web)
                                continue; /* internal error */
                        if((out=wk_sbufget((wk *) web,client->outbufids[0]))==NULL) {
                                wk_close((wk *)web,client->connid);
                                continue; /* internal error */
                        }
                        DEBUG_OUT_PRE;
                        if(sbuf_send(out,fd,sbuf_count(out))>0)
                                client->lastio=now;
                        DEBUG_OUT_POST;
                        /* if we are serving from file, load the next chunk */
                        if(client->fdtoserve!=-1 && client->usedoutbufids==1) {
                                sbuf_discard(out);
                                if(sbuf_unused(out)>0) {
                                        int n;
                                        n=sbuf_fill(out,client->fdtoserve,sbuf_unused(out));
                                        if(n<=0)
                                                close(client->fdtoserve),client->fdtoserve=-1;
                                }
                        }
                        /* free unused bufs, detect finished sending */
                        if(sbuf_count(out)==0) {
                                if(client->usedoutbufids>1) {
                                        int sbufid;
                                        sbufid=client->outbufids[0];
                                        client->usedoutbufids--;
                                        memmove(client->outbufids,client->outbufids+1,sizeof(int)*(MAXOUTBUF-1));
                                        client->outbufids[MAXOUTBUF-1]=-1;
                                        wk_sbufrelease((wk *)web,sbufid),sbufid=-1;
                                } else {
                                        sselect_delwrite(web->ssel,client->fd);
                                        if(client->continuationactive && web->callback_continuation!=NULL) {
                                                client->continuationactive=web->callback_continuation((wk *)web,client->connid,&(client->uri),web->userptr);
                                        } else {
                                                client->uriready=0; /* we have finished servicing this one */
                                                if(client->keepalive==0)
                                                        wk_close((wk *)web, client->connid); /* all sent */
                                        }
                                }
                        }
                }
        }
        /* timeout check */
        if(web->lasttimeoutcheck>now)
                web->lasttimeoutcheck=now; /* fix time warp */
        if((now-(ALIVETIMEOUT/2))>web->lasttimeoutcheck) {
                web->lasttimeoutcheck=now;
                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+n;
                                        if(client->fd==-1)
                                                continue;
                                        if(client->lastio>now)
                                                client->lastio=now;
                                        if((now-ALIVETIMEOUT)>client->lastio) {
                                                DEBUG_CONN(("WEBK","TIMEOUT: Closing client connection; now:%li, ALIVETIMEOUT:%li, lastio:%li, diff:%li\n",(long)now,(long)ALIVETIMEOUT,(long)client->lastio,(long)(now-client->lastio)));
                                                wk_close((wk *)web, client->connid); /* timeout */
                                        }
                                }
                        }
                }

        }
        return(0);
}

int
wk_serve_cookieadd(wk *paramweb, int connid, char *cookiename, char *value, char *domain, int maxage, char *attributes)
{
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        sbuf *buf;
        char str[1024];
        if(web==NULL || cookiename==NULL || (value==NULL && maxage>0))
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        if(client->cookiebufid==-1) {
                if((client->cookiebufid=wk_sbufacquire((wk *)web))==-1)
                        return(-1); /* insufficient memory */
        }
        if((buf=wk_sbufget((wk *)web,client->cookiebufid))==NULL)
                return(-1); /* internal error */
        if(sbuf_count(buf)==0) {
                sbuf_addstr(buf,"Cache-control: no-cache=\"set-cookie\"\r\n");
                sbuf_addstr(buf,"Expires: Tue, 01 Jan 1980 1:00:00 GMT\r\n");
        }
        snprintf(str,sizeof(str)-1,"Set-Cookie: %s=%s %s%s%s; Max-Age=%i ; Version=1%s%s\r\n",
                cookiename,(value!=NULL)?value:"",
                (domain!=NULL)?"; Domain = ":"",
                (domain!=NULL && *domain!='.')?".":"",
                (domain!=NULL)?domain:"",
                maxage,
                (attributes!=NULL)?"; ":"",(attributes!=NULL)?attributes:"");
        sbuf_addstr(buf,str);
        return(0);
}

int
wk_serve_etagset(wk *paramweb, int connid, char *etag)
{
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        if(etag!=NULL) {
                strncpy(client->etag,etag,sizeof(client->etag));
                client->etag[sizeof(client->etag)-1]='\0';
        } else
                client->etag[0]='\0';
        return(0);
}

int
wk_serve_generic_headers(wk *paramweb, int connid, int datalen, const char *mime, int *etagnotmodified)
{
        static const char *strcontenttype={"Content-Type: "};
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        char buf[256];
        sbuf *cookiebuf;
        int cachedetag;
        if(web==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        cachedetag=(wk_etagnotmodified(web,client)==0)?1:0;
        if(etagnotmodified!=NULL)
                *etagnotmodified=cachedetag;
        if(!cachedetag)
                wk_writestr((wk *)web,connid,"HTTP/1.0 200 OK\r\n");
        else
                wk_writestr((wk *)web,connid,"HTTP/1.1 304 Not modified\r\n");
        if(client->cookiebufid!=-1 && (cookiebuf=wk_sbufget((wk *)web,client->cookiebufid))!=NULL) {
                if(wk_write((wk *)web,connid,sbuf_getbytes(cookiebuf,sbuf_count(cookiebuf)),sbuf_count(cookiebuf))==-1 ||
                  sbuf_count(cookiebuf)!=0)
                        return(-1); /* insufficient memory */
                wk_sbufrelease((wk *)web,client->cookiebufid),client->cookiebufid=-1;
        }
        if(!cachedetag) {
                if(client->etag[0]!='\0') {
                        sprintf(buf,"ETag: \"%s\"\r\n",client->etag);
                        client->etag[0]='\0';
                        wk_writestr((wk *)web,connid,buf);
                }
                sprintf(buf,"Content-Length: %i\r\n",datalen);
                wk_writestr((wk *)web,connid,buf);
                if(mime!=NULL && strlen(mime)<(sizeof(buf)-sizeof(strcontenttype)-3))
                        sprintf(buf,"%s%s\r\n",strcontenttype,mime);
                else
                        sprintf(buf,"%s%s\r\n",strcontenttype,"application/octet-stream");
                wk_writestr((wk *)web,connid,buf);
        } else {
                wk_writestr((wk *)web,connid,"Content-Length: 0\r\n");
        }
        if(client->keepalive)
                wk_writestr((wk *)web,connid,"Connection: keep-alive\r\n");
        wk_writestr((wk *)web,connid,"\r\n");
        return(0);
}

int
wk_serve_buffer_as_file(wk *paramweb, int connid, void *data, int datalen, const char *mime)
{
        _wk *web=(_wk *)paramweb;
        int cachedetag;
        int res;
        if(web==NULL || datalen<0 || (data==NULL && datalen>0))
                return(-1);
        res=wk_serve_generic_headers((wk *)web,connid,datalen,mime,&cachedetag);
        if(res!=0)
                return(res);
        if(!cachedetag)
                return(wk_write((wk *)web,connid,data,datalen));
        return(0);
}

int
wk_serve_file(wk *paramweb, int connid, char *filename, const char *mime)
{
        static const char *strcontenttype={"Content-Type: "};
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        char buf[256];
        sbuf *cookiebuf;
        struct stat st;
        int cachedetag;
        if(web==NULL || filename==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        if(client->fdtoserve!=-1)
                close(client->fdtoserve),client->fdtoserve=-1;
        cachedetag=(wk_etagnotmodified(web,client)==0)?1:0;
        if(!cachedetag) {
                if((client->fdtoserve=open(filename,O_RDONLY))==-1) {
                        wk_serve_error((wk *)web,connid,wkerr_notfound);
                        return(-1);
                }
                wk_writestr((wk *)web,connid,"HTTP/1.0 200 OK\r\n");
        } else
                wk_writestr((wk *)web,connid,"HTTP/1.1 304 Not modified\r\n");
        if(client->cookiebufid!=-1 && (cookiebuf=wk_sbufget((wk *)web,client->cookiebufid))!=NULL) {
                if(wk_write((wk *)web,connid,sbuf_getbytes(cookiebuf,sbuf_count(cookiebuf)),sbuf_count(cookiebuf))==-1 ||
                  sbuf_count(cookiebuf)!=0)
                        return(-1); /* insufficient memory */
                wk_sbufrelease((wk *)web,client->cookiebufid),client->cookiebufid=-1;
        }
        if(!cachedetag) {
                if(client->etag[0]!='\0') {
                        sprintf(buf,"ETag: \"%s\"\r\n",client->etag);
                        client->etag[0]='\0';
                        wk_writestr((wk *)web,connid,buf);
                }
                if(fstat(client->fdtoserve,&st)==0) {
                        sprintf(buf,"Content-Length: %lld\r\n",(long long)st.st_size);
                        wk_writestr((wk *)web,connid,buf);
                } else
                        client->keepalive=0;
                if(mime!=NULL && strlen(mime)<(sizeof(buf)-sizeof(strcontenttype)-3))
                        sprintf(buf,"%s%s\r\n",strcontenttype,mime);
                else
                        sprintf(buf,"%s%s\r\n",strcontenttype,"application/octet-stream");
                wk_writestr((wk *)web,connid,buf);
        } else {
                wk_writestr((wk *)web,connid,"Content-Length: 0\r\n");
        }
        if(client->keepalive)
                wk_writestr((wk *)web,connid,"Connection: keep-alive\r\n");
        wk_writestr((wk *)web,connid,"\r\n");
        return(0);
}

int
wk_serve_error(wk *paramweb, int connid, wk_error wkerror)
{
        static const char strnotfound[]={"\
HTTP/1.1 404 Not found\r\n\
Content-Type: text/html\r\n\
Content-Length: 113\r\n\
\r\n\
<title>404 Not Found</title>\r\n\
<h1>404 Not Found</h1>\r\n\
The requested resource could not be found on this server.\r\n\
"};
        static const char strinternal[]={"\
HTTP/1.1 500 Internal Server Error\r\n\
Content-Type: text/html\r\n\
Content-Length: 118\r\n\
\r\n\
<title>500 Internal Server Error</title>\r\n\
<h1>500 Internal Server Error</h1>\r\n\
Internal error processing the request.\r\n\
"};
        static const char strnotimplemented[]={"\
HTTP/1.1 501 Not Implemented\r\n\
Content-Type: text/html\r\n\
Content-Length: 132\r\n\
\r\n\
<title>501 Not Implemented</title>\r\n\
<h1>501 Not Implemented</h1>\r\n\
The request was not understood or is not allowed by this server.\r\n\
"};
        int res;
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        if(wkerror==wkerr_notfound)
                res=wk_write((wk *)web,connid,strnotfound,sizeof(strnotfound)-1);
        else if(wkerror==wkerr_notimplemented)
                res=wk_write((wk *)web,connid,strnotimplemented,sizeof(strnotimplemented)-1);
        else
                res=wk_write((wk *)web,connid,strinternal,sizeof(strinternal)-1);
        return(res);
}

int
wk_serve_redirect(wk *paramweb, int connid, char *newlocation)
{
        static const char strredirect1[]={"\
HTTP/1.1 307 Temporary Redirect\r\n\
Content-Type: text/html\r\n\
Content-Length: 114\r\n\
Location: "
};
        static const char strredirect2[]={"\
\r\n\
\r\n\
<title>307 Temporary Redirect</title>\r\n\
<h1>307 Temporary Redirect</h1>\r\n\
The requsted page was moved temporarily.\r\n\
"};
        int res;
        int total;
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL || newlocation==NULL || newlocation[0]=='\0')
                return(-1);
        total=0;
        if((res=wk_writestr((wk *)web,connid,strredirect1))<0)
                return(-1);
        total+=res;
        if((res=wk_writestr((wk *)web,connid,newlocation))<0)
                return(-1);
        total+=res;
        if((res=wk_writestr((wk *)web,connid,strredirect2))<0)
                return(-1);
        total+=res;
        return(total);
}


int
wk_writestr(wk *paramweb, int connid, const char *str)
{
        return(wk_write(paramweb,connid,str,strlen(str)));
}

int
wk_write(wk *paramweb, int connid, const void *data, int datalen)
{
        int k;
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        sbuf *out;
        long added;
        long total;
        if(web==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        total=0;
        for(k=(client->usedoutbufids>0)?client->usedoutbufids-1:0;
          datalen>0 && k<MAXOUTBUF;k++) {
                if(client->outbufids[k]==-1) {
                        if((client->outbufids[k]=wk_sbufacquire((wk *)web))==-1)
                                return(-1); /* insufficient memory */
                        client->usedoutbufids++;
                }
                if((out=wk_sbufget((wk *)web,client->outbufids[k]))==NULL)
                        return(-1); /* internal error */
                if(sbuf_unused(out)==0)
                        continue;
                added=sbuf_add(out,data,datalen);
                data+=added;
                datalen-=added;
                total+=added;
        }
        if(total>0)
                sselect_addwrite(web->ssel,client->fd,(void *)client);
        return(total);
}

int
wk_close(wk *paramweb, int connid)
{
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        DEBUG_CONN(("WEBK","wk_close: connid: %i, fd: %i",connid,client->fd));
        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;
        }
        if(client->fdtoserve!=-1)
                close(client->fdtoserve),client->fdtoserve=-1;
        wk_clientrelease(web,connid);
        if(web->callback_event!=NULL)
                web->callback_event((wk *)web,connid,wke_closed,web->userptr);
        return(0);
}

char *
wk_uri_getheader(wk_uri *uri, char *header, char *defaultvalue)
{
        int n,len;
        char *ptr;
        if(uri==NULL || uri->headers==NULL || header==NULL)
                return(NULL);
        len=strlen(header);
        for(n=0,ptr=uri->headers;*ptr!='\0';ptr+=strlen(ptr)+1,n++) {
                if(memcmp(ptr,header,len)==0 && ptr[len]==':' && ptr[len+1]==' ')
                        return(ptr+len+2);
        }
        return(defaultvalue);
}

char *
wk_uri_getheaderbynum(wk_uri *uri, int num)
{
        int n;
        char *ptr;
        if(uri==NULL || uri->headers==NULL)
                return(NULL);
        for(n=0,ptr=uri->headers;*ptr!='\0';ptr+=strlen(ptr)+1,n++) {
                if(n==num)
                        return(ptr);
        }
        return(NULL);
}

char *
wk_uri_getvar(wk_uri *uri, char *varname, int *len)
{
        char *ptr,*end;
        int varlen;
        if(uri==NULL || uri->path==NULL || (ptr=strchr(uri->path,'?'))==NULL)
                return(NULL);
        varlen=strlen(varname);
        ptr++;
        while(*ptr!='\0' && ptr!=NULL) {
                if(memcmp(ptr,varname,varlen)==0 && ptr[varlen]=='=') {
                        ptr+=varlen+1;
                        if((end=strchr(ptr,'&'))==NULL)
                                end=ptr+strlen(ptr);
                        if(len!=NULL)
                                *len=end-ptr;
                        return(ptr);
                }
                if((end=strchr(ptr,'&'))==NULL)
                        break;
                ptr=end+1;
        }
        return(NULL);
}

char *
wk_uri_copyvar(wk_uri *uri, char *varname, char *dest, int destlen)
{
        char *value;
        int len;
        if(dest==NULL || destlen<1 || (value=wk_uri_getvar(uri,varname,&len))==NULL)
                return(NULL);
        memcpy(dest,value,(len<(destlen-1))?len:destlen-1);
        dest[(len<(destlen-1))?len:destlen-1]='\0';
        return(dest);
}

char *
wk_uri_getcookie(wk_uri *uri, char *cookiename, int *len)
{
        int n;
        char *header;
        int namelen;
        char *ptr;
        char *sep;
        char *end;
        char *next;
        if(uri==NULL || uri->headers==NULL || cookiename==NULL || len==NULL)
                return(NULL);
        namelen=strlen(cookiename);
        for(n=0,header=uri->headers;*header!='\0';header+=strlen(header)+1,n++) {
                if(memcmp(header,"Cookie: ",8)!=0 && memcmp(header,"cookie: ",8)!=0)
                        continue;
                for(ptr=header+8;*ptr!='\0';ptr=((*next!='\0')?next+1:next)) {
                        /* cookies are separated by ',' or ';' */
                        next=strchr(ptr,';');
                        if(next==NULL)
                                next=ptr+strlen(ptr);
                        if((sep=strchr(ptr,','))!=NULL && sep<next)
                                next=sep;
                        /* trim cookie name */
                        while(*ptr==' ')
                                ptr++;
                        /* check for our cookie and search for the value pos */
                        if(memcmp(ptr,cookiename,namelen)!=0)
                                continue; /* not our cookie */
                        for(sep=ptr+namelen;*sep==' ';sep++)
                                ;
                        if(*sep!='=')
                                continue; /* was partial match or unrecognized cookie format */
                        /* skip '=' and trim value */
                        sep++;
                        while(*sep==' ')
                                sep++;
                        for(end=next;end>sep && end[-1]==' ';)
                                end--;
                        /* cookie found, return it */
                        *len=end-sep;
                        return(sep);
                }
        }
        return(NULL);
}

char *
wk_uri_copycookie(wk_uri *uri, char *cookiename, char *dest, int destlen)
{
        char *value;
        int len;
        if(dest==NULL || destlen<1 || (value=wk_uri_getcookie(uri,cookiename,&len))==NULL)
                return(NULL);
        memcpy(dest,value,(len<(destlen-1))?len:destlen-1);
        dest[(len<(destlen-1))?len:destlen-1]='\0';
        return(dest);
}


int
wk_post_addvalid(wk *paramweb, int connid, char *varname, char *tofile)
{
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL || varname==NULL)
                return(-1);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        return(wk_postadd(web,client,varname,tofile));
}

char *
wk_post_get(wk *paramweb, int connid, char *varname, int *isfile)
{
        _wk *web=(_wk *)paramweb;
        wk_client *client;
        if(web==NULL || varname==NULL)
                return(NULL);
        if((client=wk_clientget(web,connid))==NULL)
                return(NULL);
        return(wk_postget(web,client,varname,isfile));
}


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
uri_urldecode(char *s)
{
        int i,o;
        int a,b,n;
        for(i=0,o=0;s[i]!='\0';i++,o++) {
                if(s[i]=='+')
                        s[o]=' ';
                else if(s[i]=='%' && (a=s[i+1])!='\0' && (b=s[i+2])!='\0' && ISHEXNIBBLE(a) && ISHEXNIBBLE(b)) {
                        n=(HEXNIBBLE2BIN(a)<<4)|HEXNIBBLE2BIN(b);
                        if(n>=128) {
                                s[o]=128|64|(n>>6);
                                s[o+1]=128|(n&0x3f);
                                i+=2;
                                o++;
                        } else {
                                s[o]=n;
                                i+=2;
                        }
                } else
                        s[o]=s[i];
        }
        s[o]='\0';
        return(0);
}


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)*sizeof(wk_bufblock)))==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;
                web->usedbufblocks++;
        }
        /* 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_sbufrelease(wk *paramweb, int sbufid)
{
        sbuf *buf;
        int numblock,j,bit;
        _wk *web=(_wk *)paramweb;
        if(web==NULL || sbufid<0)
                return(-1);
        numblock=sbufid/BUFBLOCK;
        j=sbufid%BUFBLOCK;
        bit=0x1<<(j&7);
        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_sbufget(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&7);
        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 wk_client *
wk_accept(_wk *web)
{
        int newfd;
        wk_client *client;
        if((newfd=sock_accept(web->serverfd))==-1)
                return(NULL);
        if((client=wk_clientacquire(web))==NULL) {
                close(newfd),newfd=-1;
                return(NULL);
        }
        client->fd=newfd;
        sock_setsafe(client->fd);
        DEBUG_CONN(("WEBK","wk_accept: connid: %i, fd: %i",client->connid,client->fd));
        FD_SET(client->fd,&(web->fdset));
        sselect_addread(web->ssel,client->fd,(void *)client);
        return(client);
}

static wk_client *
wk_clientacquire(_wk *web)
{
        int i,j,k;
        wk_clientblock *cb;
        wk_client *client;
        int w;
        /* make sure there are free clientblocks */
        if(web->usedclientblocks==web->sizeclientblocks) {
                wk_clientblock *newcb;
                if((newcb=(wk_clientblock *)realloc(web->clientblocks,(web->sizeclientblocks+CLIENTBLOCKBLOCK)*sizeof(wk_clientblock)))==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);
                memset(cb->acquired,0,sizeof(cb->acquired));
                cb->sizeclients=CLIENTBLOCK;
                cb->usedclients=0;
                web->usedclientblocks++;
        }
        /* 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;
        if((client->inbufid=wk_sbufacquire((wk *)web))==-1) {
                wk_clientrelease(web,client->connid);
                return(NULL); /* insufficient memory */
        }
        for(w=0;w<MAXOUTBUF;w++)
                client->outbufids[w]=-1;
        if((client->outbufids[0]=wk_sbufacquire((wk *)web))==-1) {
                wk_clientrelease(web,client->connid);
                return(NULL); /* insufficient memory */
        }
        client->usedoutbufids=1;
        client->fdtoserve=-1;
        client->headerbufid=-1;
        client->cookiebufid=-1;
        client->etag[0]='\0';
        return(client);
}

static int
wk_clientrelease(_wk *web, int connid)
{
        wk_client *client;
        int numblock,j,bit;
        int w;
        numblock=connid/CLIENTBLOCK;
        j=connid%CLIENTBLOCK;
        bit=0x1<<(j&7);
        if((client=wk_clientget(web,connid))==NULL)
                return(-1);
        web->clientblocks[numblock].usedclients--;
        web->clientblocks[numblock].acquired[j>>3]&=(~bit);
        if(client->inbufid!=-1)
                wk_sbufrelease((wk *)web,client->inbufid),client->inbufid=-1;
        for(w=0;w<client->usedoutbufids;w++)
                wk_sbufrelease((wk *)web,client->outbufids[w]),client->outbufids[w]=-1;
        if(client->headerbufid!=-1)
                wk_sbufrelease((wk *)web,client->headerbufid),client->headerbufid=-1;
        if(client->cookiebufid!=-1)
                wk_sbufrelease((wk *)web,client->cookiebufid),client->cookiebufid=-1;
        wk_postfree(web,client);
        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&7);
        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);
}

static char *
str_findfirstempty(char *ptr, int size)
{
        int i;
        for(i=0;i<(size-3);i++) {
                if(ptr[i+0]=='\r' && ptr[i+1]=='\n' && ptr[i+2]=='\r' && ptr[i+3]=='\n')
                        return(ptr+i);
        }
        return(NULL);
}

static int
wk_clientservicereadheader(_wk *web, wk_client *client)
{
        sbuf *in,*hbuf;
        char *end;
        char *ptr;
        char *lineend;
        char *sep;
        wk_uri *uri;
        wk_action action;
        int flagusepostcallback;
        if((in=wk_sbufget((wk *)web, client->inbufid))==NULL)
                return(-1); /* internal error */
        /* get memory for the uri data */
        if(client->headerbufid!=-1)
                wk_sbufrelease((wk *)web,client->headerbufid),client->headerbufid=-1;
        if((client->headerbufid=wk_sbufacquire((wk *)web))==-1)
                return(-1); /* insufficient memory */
        if((hbuf=wk_sbufget((wk *)web,client->headerbufid))==NULL)
                return(-1); /* internal error */
        /* check if we have all the headers */
        if((end=str_findfirstempty(sbuf_ptr(in),sbuf_count(in)))==NULL) {
                sbuf_discard(in);
                if(sbuf_unused(in)==0)
                        return(-1); /* header part too long */
                /* incomplete headers, have to wait for more data */
                return(0);
        }
        /* prepare to fill the uri struct */
        client->uriready=0;
        sbuf_wipe(hbuf);
        uri=&(client->uri);
        memset(uri,0,sizeof(wk_uri));
        /* check that the method is supported */
        ptr=sbuf_ptr(in);
        if(memcmp(ptr,"GET ",4)!=0 && memcmp(ptr,"PUT ",4)!=0 && memcmp(ptr,"POST ",5)!=0)
                return(-1); /* unknown method */
        if((lineend=strchr(ptr,'\r'))!=NULL)
                *lineend='\0';
        else
                lineend=end;
        /* method */
        sep=strchr(ptr,' ');
        *sep='\0';
        uri->method=sbuf_ptrunused(hbuf);
        sbuf_add(hbuf,ptr,strlen(ptr)+1);
        ptr+=strlen(ptr)+1;
        /* path */
        if((sep=strchr(ptr,' '))==NULL)
                return(-1); /* no separator between path and protocol */
        *sep='\0';
        uri->path=sbuf_ptrunused(hbuf);
        sbuf_add(hbuf,ptr,strlen(ptr)+1);
        ptr+=strlen(ptr)+1;
        /* protocol */
        uri->protocol=sbuf_ptrunused(hbuf);
        sbuf_add(hbuf,ptr,strlen(ptr)+1);
        ptr+=strlen(ptr)+1;
        /* headers */
        *end='\0';
        uri->headers=sbuf_ptrunused(hbuf);
        if(strcmp(uri->protocol,"HTTP/0.9")==0 || strcmp(uri->protocol,"HTTP/1.0")==0)
                client->keepalive=0;
        else
                client->keepalive=1; /* default for http/1.1 */
        while(ptr<end) {
                if(*ptr!='\n')
                        return(-1); /* line is not ended with \r\n */
                ptr++;
                if((lineend=strchr(ptr,'\r'))!=NULL)
                        *lineend='\0';
                else
                        lineend=end;
                if((sep=strchr(ptr,':'))==NULL || sep[1]!=' ')
                        return(-1); /* header not in format "name: value" */
                /* check for keepalive header */
                if(memcmp(ptr,"Connection: ",12)==0) {
                  if(strcmp(ptr+12,"keep-alive")==0 || strcmp(ptr+12,"Keep-Alive")==0)
                        client->keepalive=1;
                  else if(strcmp(ptr+12,"close")==0 || strcmp(ptr+12,"Close")==0)
                        client->keepalive=0;
                }
                sbuf_add(hbuf,ptr,strlen(ptr)+1);
                ptr+=strlen(ptr)+1;
        }
        /* add header terminator */
        sbuf_add(hbuf,"",1);
        /* mark data as used */
        sbuf_getbytes(in,end-sbuf_ptr(in)+4);
        /* call the http method if GET (or not post callback) , or the post method if POST */
        client->uriready=1;
        client->continuationactive=0;
        flagusepostcallback=(strcmp(client->uri.method,"POST")==0 && web->callback_post!=NULL)?1:0;
        if(flagusepostcallback) {
                action=web->callback_post((wk *)web, client->connid, uri, web->userptr);
        } else {
                action=web->callback_http((wk *)web, client->connid, uri, web->userptr);
        }
        if(action==wkact_continuation) {
                client->uriready=1;
                client->continuationactive=1;
                client->status=wkc_header;
        } else if(flagusepostcallback) {
                char *lenvar;
                char *contenttype;
                if(strcmp(client->uri.method,"POST")!=0 ||
                  (lenvar=wk_uri_getheader(&(client->uri),"Content-Length",NULL))==NULL) {
                        return(-1); /* malformed post */
                }
                if((contenttype=wk_uri_getheader(&(client->uri),"Content-Type",NULL))==NULL ||
                  strcmp(contenttype,"application/x-www-form-urlencoded")!=0) {
                        return(-1); /* unsupported encoding */
                }
                while(*lenvar==' ' || *lenvar=='\t')
                        lenvar++;
                client->uriready=1;
                client->continuationactive=0;
                client->status=wkc_post;
                client->pendingpost=atoi(lenvar);
                client->inpostvar=-1;
        } else {
                client->uriready=1;
                client->continuationactive=0;
                client->status=wkc_header;
                if(client->cookiebufid!=-1)
                        wk_sbufrelease((wk *)web,client->cookiebufid),client->cookiebufid=-1;
                client->etag[0]='\0';
        }
        return(0);
}

static int
wk_clientservicereadpost(_wk *web, wk_client *client)
{
        sbuf *in;
        char *buf;
        int buflen;
        char *sep;
        char *start;
        char *end;
        wk_action action;
        if((in=wk_sbufget((wk *)web, client->inbufid))==NULL)
                return(-1); /* internal error */
        while(sbuf_count(in)>0 && client->pendingpost>0) {
                if(client->inpostvar==-1) {
                        sbuf_discard(in);
                        buf=sbuf_ptr(in);
                        buflen=sbuf_count(in);
                        if(buflen>client->pendingpost)
                                buflen=client->pendingpost;
                        if((sep=memchr(buf,'=',buflen))==NULL)
                                return(0); /* varname not found */
                        *sep='\0';
                        for(client->inpostvar=0;client->inpostvar<client->usedpost;client->inpostvar++) {
                                if(strcmp(client->post[client->inpostvar].name,buf)==0)
                                        break;
                        }
                        sbuf_getbytes(in,(sep+1)-buf);
                        client->pendingpost-=((sep+1)-buf);
                }
                start=sbuf_ptr(in);
                end=start+sbuf_count(in);
                if((end-start)>client->pendingpost)
                        end=start+client->pendingpost;
                if((sep=memchr(start,'&',end-start))!=NULL)
                        end=sep;
                if(client->inpostvar>=0) {
                        if(client->inpostvar<client->usedpost)
                                wk_postset(web,client,client->post[client->inpostvar].name,start,end-start);
                        sbuf_getbytes(in,end-start);
                        client->pendingpost-=(end-start);
                }
                if(sep!=NULL && *sep=='&') {
                        sbuf_getbytes(in,1);
                        client->pendingpost--;
                        client->inpostvar=-1;
                }
        }
        if(client->pendingpost>0)
                return(0); /* nothing more to do for now*/
        /* call the http method */
        client->uriready=1;
        client->continuationactive=0;
        action=web->callback_http((wk *)web, client->connid, &(client->uri), web->userptr);
        if(action==wkact_continuation) {
                client->uriready=1;
                client->continuationactive=1;
                client->status=wkc_post;
        } else {
                client->uriready=1;
                client->continuationactive=0;
                client->status=wkc_header;
        }
        return(0);
}


static int
wk_postadd(_wk *web, wk_client *client,char *varname, char *tofile)
{
        wk_post *post;
        sbuf *buf;
        int i;
        if(client->post==NULL || client->sizepost==client->usedpost) {
                wk_post *newpost;
                if((newpost=(wk_post *)realloc(client->post,(client->sizepost+POSTBLOCK)*sizeof(wk_post)))==NULL)
                        return(-1); /* insufficient memory */
                client->post=newpost;
                memset(client->post+client->sizepost,0,POSTBLOCK*sizeof(wk_post));
                for(i=0;i<POSTBLOCK;i++)
                        client->post[client->sizepost+i].bufid=-1;
                client->sizepost+=POSTBLOCK;
        }
        post=client->post+client->usedpost;
        if((post->bufid=wk_sbufacquire((wk *)web))==-1)
                return(-1); /* insufficient memory */
        if((buf=wk_sbufget((wk *)web,post->bufid))==NULL) {
                post->bufid=-1;
                return(-1); /* internal error */
        }
        if((strlen(varname)+1)>(sbuf_unused(buf)-2)) {
                wk_sbufrelease((wk *)web,post->bufid),post->bufid=-1;
                return(-1); /* varname too long */
        }
        post->name=sbuf_ptrunused(buf);
        sbuf_add(buf,varname,strlen(varname)+1);
        if(tofile!=NULL) {
                if((strlen(tofile)+1)>(sbuf_unused(buf))) {
                        wk_sbufrelease((wk *)web,post->bufid),post->bufid=-1;
                        return(-1); /* varname+tofile too long */
                }
                post->tofile=sbuf_ptrunused(buf);
                sbuf_add(buf,tofile,strlen(tofile)+1);
        }
        client->usedpost++;
        post->value=NULL;
        post->filewritten=0;
        post->valueterminated=0;
        return(0);
}

static int
wk_postset(_wk *web, wk_client *client,char *varname, char *data, int datalen)
{
        int i;
        wk_post *post;
        int fd;
        sbuf *buf;
        if(varname==NULL || data==NULL || datalen==0)
                return(-1);
        for(i=0;i<client->usedpost;i++) {
                if(strcmp(client->post[i].name,varname)==0)
                        break;
        }
        if(i>=client->usedpost)
                return(-1); /* var not found */
        post=client->post+i;
        if(post->tofile!=NULL) {
                if((fd=open(post->tofile,O_CREAT|O_APPEND,0600))==-1)
                        return(-1); /* couldn't open file */
                if(write(fd,data,datalen)!=datalen) {
                        close(fd),fd=-1;
                        return(-1); /* couldn't write all data */
                }
                close(fd),fd=-1;
                post->filewritten=1;
        } else {
                if((buf=wk_sbufget((wk *)web,post->bufid))==NULL) {
                        post->bufid=-1;
                        return(-1); /* internal error */
                }
                if(post->value==NULL)
                        post->value=sbuf_ptrunused(buf);
                if(datalen>(sbuf_unused(buf)-1))
                        datalen=sbuf_unused(buf)-1;
                sbuf_add(buf,data,datalen);
                post->valueterminated=0;
        }
        return(0);
}

static char *
wk_postget(_wk *web, wk_client *client,char *varname, int *isfile)
{
        int i;
        wk_post *post;
        sbuf *buf;
        if(varname==NULL)
                return(NULL);
        for(i=0;i<client->usedpost;i++) {
                if(strcmp(client->post[i].name,varname)==0)
                        break;
        }
        if(i>=client->usedpost)
                return(NULL); /* var not found */
        post=client->post+i;
        /* file post */
        if(isfile!=NULL)
                *isfile=(post->tofile!=NULL)?1:0;
        if(post->tofile) {
                if(!post->filewritten)
                        return(NULL);
                return(post->tofile);
        }
        /* buffer post */
        if((buf=wk_sbufget((wk *)web,post->bufid))==NULL)
                return(NULL); /* internal error */
        if(!post->valueterminated) {
                if(sbuf_unused(buf)<1)
                        return(NULL); /* internal error */
                *(sbuf_ptrunused(buf))='\0';
                post->valueterminated=1;
        }
        return(post->value);
}

static int
wk_postfree(_wk *web, wk_client *client)
{
        int i;
        wk_post *post;
        for(i=0;i<client->usedpost;i++) {
                post=client->post+i;
                if(post->bufid!=-1)
                        wk_sbufrelease((wk *)web,post->bufid),post->bufid=-1;
        }
        if(client->post!=NULL) {
                client->usedpost=0;
                client->sizepost=0;
                free(client->post),client->post=NULL;
        }
        return(0);
}

static int
wk_etagnotmodified(_wk *web, wk_client *client)
{
        char *h;
        int len;
        int etaglen;
        if(client->etag[0]=='\0')
                return(-1);
        if((h=wk_uri_getheader(&(client->uri),"If-None-Match",NULL))==NULL)
                return(-1);
        etaglen=strlen(client->etag);
        if((len=strlen(h))<2 || h[0]!='\"' || h[len-1]!='\"')
                return(-1);
        if((len-2)!=etaglen || memcmp(h+1,client->etag,etaglen)!=0)
                return(-1);
        return(0);
}