/*
 * newslettersan.c
 *
 * Small newsletter web server
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This program is licensed under the terms of the Affero GPL v1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gen_res.h"
#include "socklib.h"
#include "loglib.h"
#include "webkernel.h"
#include "newslettersan.h"
#include "newsl_config.h"

#define CONFIGFILE "newslettersan.cfg"
#define CFLOGFILE "newslettersan.log"
#define CFROOTDIR "subscribe"

static int signal_init(int signum, void (*fn)(int));
static void sigint(int signum);
volatile int sigint_flag=0;

wk_action callback_post(wk *web, int connid, wk_uri *uri, void *userptr);
wk_action callback_http(wk *web, int connid, wk_uri *uri, void *userptr);
int newsl_serve_buffer_with_rootdir(newslettersan *newsl, wk *web, int connid, char *data, int datalen, const char *mime);

int
main(int argc, char *argv[])
{
        int timeout=500;
        int serverfd;
        newslettersan *newsl,newslstore;
        char *hostnameport;
        char hostname[128];
        char *host;
        long hostsize;
        char *ptr,*sep;
        memset(&newslstore,0,sizeof(newslstore));
        newsl=&newslstore;
        if(argc!=2) {
                printf("Syntax: %s [ip:]port\n",argv[0]);
                return(1);
        }
        hostnameport=argv[1];
        if(newslconfig_exists(CONFIGFILE)!=0) {
                log_setlogfile(CFLOGFILE);
                log_write("INIT","Config file not found, writing default file %s",CONFIGFILE);
                newslconfig_write(CONFIGFILE,CFLOGFILE,CFROOTDIR);
        }
        if((newsl->config=newslconfig_init(CONFIGFILE,CFLOGFILE,CFROOTDIR))==NULL) {
                log_setlogfile(CFLOGFILE);
                log_write("INIT","ERROR: insufficient memory or config file error");
                return(1);
        }
        log_setlogfile((newsl->config->logfile!=NULL)?newsl->config->logfile:CFLOGFILE);
        if((newsl->ssel=sselect_init())==NULL) {
                log_write("INIT","ERROR: insufficient memory");
                return(1);
        }
        if((sep=strchr(hostnameport,':'))!=NULL) {
                serverfd=-1;
                strncpy(hostname,hostnameport,sizeof(hostname));
                hostname[sizeof(hostname)-1]='\0';
                if((ptr=strchr(hostname,':'))!=NULL)
                        *ptr='\0';
                if((host=ipv4_genip(hostname,&hostsize))!=NULL) {
                        serverfd=ipv4_serverbinded(host,hostsize,atoi(sep+1));
                        free(host),host=NULL;
                }
        } else {
                serverfd=ipv4_server(atoi(hostnameport));
        }
        if(serverfd==-1) {
                sselect_free(newsl->ssel),newsl->ssel=NULL;
                log_write("INIT","ERROR: couldn't listen on port");
                return(2);
        }
        sock_setunsafe(serverfd);
        if((newsl->web=wk_init(serverfd,newsl->ssel,NULL,callback_http,callback_post,NULL,newsl))==NULL) {
                sselect_free(newsl->ssel),newsl->ssel=NULL;
                log_write("INIT","ERROR: couldn't init web server");
                return(2);
        }
        sigint_flag=0;
        signal_init(SIGINT,sigint);
        log_write("INIT","Server initialized, waiting connections...");
        while(!sigint_flag) {
                sselect_wait(newsl->ssel,timeout);
                if(sigint_flag)
                        break;
                wk_service(newsl->web);
        }
        wk_free(newsl->web),newsl->web=NULL;
        close(serverfd),serverfd=-1;
        sselect_free(newsl->ssel),newsl->ssel=NULL;
        newslconfig_free(newsl->config),newsl->config=NULL;
        log_write("FINI","SIGINT detected, exiting...");
        return(0);
}

static int
signal_init(int signum, void (*fn)(int))
{
        struct sigaction sa;
        sa.sa_handler=fn;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        return(sigaction(signum,&sa,0));
}

static void
sigint(int signum)
{
        sigint_flag=1;
}

wk_action
callback_post(wk *web, int connid, wk_uri *uri, void *userptr)
{
        newslettersan *newsl=(newslettersan *)userptr;
        char partialpath[1024];
        int l;
        if(newsl==NULL)
                return(wkact_finished);
        /* check that URI starts with /$rootdir/ */
        l=strlen(newsl->config->rootdir);
        if(uri->path[0]!='/' || memcmp(uri->path+1,newsl->config->rootdir,l)!=0 ||
                uri->path[l+1]!='/') {
                return(wkact_finished);
        }
        /* copy the path below $rootdir into partialpath */
        strncpy(partialpath,uri->path+1+l,sizeof(partialpath)-1);
        partialpath[sizeof(partialpath)-1]='\0';
        /* set push variables for the specified URI */
        /* to be able to retrieve them in callback_http */
        if(strcmp(partialpath,"/addemail")==0) {
                wk_post_addvalid(web,connid,"email",NULL);
                wk_post_addvalid(web,connid,"language",NULL);
        }
        return(wkact_finished);
}

wk_action
callback_http(wk *web, int connid, wk_uri *uri, void *userptr)
{
        newslettersan *newsl=(newslettersan *)userptr;
        resindex *res;
        int l;
        char partialpath[1024];
        if(newsl==NULL)
                return(wkact_finished);
        /* redirect to the real root URI if request is "/" */
        if(strcmp(uri->path,"/")==0) {
                partialpath[0]='/';
                strncpy(partialpath+1,newsl->config->rootdir,sizeof(partialpath)-1);
                partialpath[sizeof(partialpath)-1]='\0';
                if((l=strlen(partialpath))<(sizeof(partialpath)-2))
                        strcpy(partialpath+l,"/");
                wk_serve_redirect(web,connid,partialpath);
                return(wkact_finished);
        }
        /* check that URI starts with /$rootdir/ */
        l=strlen(newsl->config->rootdir);
        if(uri->path[0]!='/' || memcmp(uri->path+1,newsl->config->rootdir,l)!=0 ||
                uri->path[l+1]!='/') {
                wk_serve_error(web,connid,wkerr_notfound);
                return(wkact_finished);
        }
        /* copy the path below $rootdir into partialpath */
        strncpy(partialpath,uri->path+1+l,sizeof(partialpath)-1);
        partialpath[sizeof(partialpath)-1]='\0';
        /* change the root url to index.html if neccessary */
        if(strcmp(partialpath,"/")==0) {
                strncpy(partialpath,"/index.html",sizeof(partialpath));
                partialpath[sizeof(partialpath)-1]='\0';
        }
        /* serve the requested page */
        if(partialpath[0]=='/' && (res=res_find(resindexdata,partialpath+1))!=NULL) {
                log_write("HTTP","Serving in-memory file %s",partialpath+1);
                wk_serve_etagset(web,connid,res->etag);
                newsl_serve_buffer_with_rootdir(newsl,web,connid,(char *) res->data,res->len,mime_getdefault(res->name,"application/octet-stream"));
                return(wkact_finished);
        } else if(strcmp(partialpath,"/addemail")==0) {
                char *email,*language;
                if((email=wk_post_get(web,connid,"email",NULL))==NULL || (language=wk_post_get(web,connid,"language",NULL))==NULL) {
                        wk_serve_error(web,connid,wkerr_internal);
                        return(wkact_finished);
                }
                #warning TODO: do something with the email
                {
                        FILE *f;
                        if((f=fopen("emails.txt","a"))==NULL) {
                                wk_serve_error(web,connid,wkerr_internal);
                                return(wkact_finished);
                        }
                        fprintf(f,"%s %s\n",language,email);
                        fclose(f);
                }
                if((res=res_find(resindexdata,"success.html"))!=NULL) {
                        wk_serve_buffer_as_file(web,connid,(char *) res->data,res->len,mime_getdefault(res->name,"application/octet-stream"));
                } else {
                        char success[]={"The email has been saved.\n"};
                        wk_serve_buffer_as_file(web,connid,success,strlen(success),"text/plain");
                }
                return(wkact_finished);
        }
        wk_serve_error(web,connid,wkerr_notfound);
        return(wkact_finished);
}


/* newsl_serve_buffer_with_rootdir() substitutes /~/ with /$rootdir/ */
/* NOTE: it is limited to webkernel's MAXOUTBUF * BUFSIZE; normally MAXOUTBUF is 128 and BUFSIZE is 8192 (that is, 1MB) */
int
newsl_serve_buffer_with_rootdir(newslettersan *newsl, wk *web, int connid, char *data, int datalen, const char *mime)
{
        int res;
        int newdatalen;
        int newstrlen;
        char *last,*next,*end;
        int etagnotmodified;
        if(newsl==NULL || web==NULL || data==NULL || datalen<0)
                return(-1); /* sanity check failed */
        /* we calculate the new datalen */
        end=data+datalen;
        newstrlen=1+strlen(newsl->config->rootdir)+1;
        for(last=data,next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL;next!=NULL;next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL) {
                if(memcmp(next,"/~/",3)==0) {
                        newdatalen+=next-last+newstrlen;
                        last=next+3;
                } else {
                        newdatalen+=next-last+1;
                        last=next+1;
                }
        }
        newdatalen+=end-last;
        /* serve the headers */
        res=wk_serve_generic_headers(web,connid,newdatalen,mime,&etagnotmodified);
        if(res!=0)
                return(res); /* error in headers */
        /* serve the contents */
        if(!etagnotmodified) {
                /* we copy the buffer substituting /~/ for /$rootdir/ */
                end=data+datalen;
                for(last=data,next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL;next!=NULL;next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL) {
                        if(memcmp(next,"/~/",3)==0) {
                                wk_write(web,connid,last,next-last);
                                wk_write(web,connid,"/",1);
                                wk_writestr(web,connid,newsl->config->rootdir);
                                wk_write(web,connid,"/",1);
                                last=next+3;
                        } else {
                                wk_write(web,connid,last,next-last+1);
                                last=next+1;
                        }
                }
                wk_write(web,connid,last,end-last);
        }
        return(0);
}