/* * 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); }