/* Wbox - HTTP fun tool, wirtten by Salvatore 'antirez' Sanfilippo
 *                       antirez at gmail dot org
 *
 * This software is released under the GPL license version 2
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <ctype.h>
#include <locale.h>
#include <dirent.h>
#include <fcntl.h>

#include "wbsignal.h"
#include "anet.h"
#include "sds.h"

/* Flags */
#define WBOX_NONE 0
#define WBOX_ACCEPT_COMPR 1
#define WBOX_USE_HEAD 2
#define WBOX_USE_HTTP10 4

/* Exit codes */
#define WBOX_EXIT_SUCCESS 0
#define WBOX_EXIT_BADARGS 1
#define WBOX_EXIT_RESOLV 2
#define WBOX_EXIT_CONN 3
#define WBOX_EXIT_IO 4
#define WBOX_EXIT_EOF 5
#define WBOX_EXIT_PROTO 6

/* Useful defines */
#define WBOX_NOTUSED(V) ((void) V)

/* Hardcoded stuff */
#define WBOX_VERSION 4
#define WBOX_DEFAULT_SERVER_PORT 8081
#define WBOX_DEFAULT_MAX_CLIENTS 20
#define WBOX_RECV_BUF (1024*4)
#define WBOX_TIMESPLIT_SAMPLES 40
#define WBOX_COOKIES_MAX 20
/* the ANSI sequence to clear the current line
 * and move the curosr on the left */
#define WBOX_ANSI_CLEARLINE "\033[1K\033[G"

static char *htmlheader = "<html><head><title>WBox</title></head><body>"
"<style type=\"text/css\">"
"A.dir {font-weight: bold; color: #1777b1;}"
"h1 {color: #000097;}"
"h2 {color: #c90404;}"
"A {text-decoration:none; color: #000097;}"
"A:visited {text-decoration:none; color: #777777;}"
"#footer {margin-top:15px;}"
"</style>";
static char *htmlfooter= "<div id=\"footer\">Generated by <a href=\"http://hping.org/wbox/\">The <b>WBox</b> HTTP testing tool</a></div></body></html>";

/* Data structures */

typedef struct cookie {
    char *name;
    char *value;
} cookie;

typedef struct wconfig {
    /* Configuration */
    char *url;
    char *host;
    char *webroot;
    char *referer;
    int dump;
    int compr;
    int head;
    int showhdr;
    int wait;
    int clients;
    int silent;
    int timesplit;
    int maxreq;
    int http10;
    int close;
    int cookies; /* number of set cookies */
    cookie cookie[WBOX_COOKIES_MAX];
    /* Server mode configuration */
    int servermode;
    int serverport;
    int maxclients;
    /* Runtime state (client mode) */
    int mintime, maxtime;
    double timesum; /* average = timesum/timesum_samples */
    int timesum_samples;
    /* Runtime state (server mode) */
    volatile sig_atomic_t activeclients;
} wconfig;

typedef struct timesplit {
    int time;
    int firstbyte;
    int lastbyte;
} timesplit;

/* Reply info describes the HTTP reply we get from server */
typedef struct replyinfo {
    int code;
    char *reason;
    int replylen;
    int time;
    int compr;
    int tsamples; /* timesplit samples used */
    timesplit tsample[WBOX_TIMESPLIT_SAMPLES];
} replyinfo;

/* Url info describes an URL */
typedef struct urlinfo {
    char *proto;
    char *domain;
    int port;
    char *req;
} urlinfo;

/* Request info describes an HTTP request (for server mode) */
#define WBOX_REQ_METHOD_GET 0
#define WBOX_REQ_METHOD_POST 1
#define WBOX_REQ_METHOD_HEAD 2
#define WBOX_REQ_METHOD_OTHER 3
typedef struct reqinfo {
    int method;
    int protover;
    char *file;
} reqinfo;


/* Global vars */
wconfig conf;

/* ---------------------------- support functions --------------------------- */
long long milliseconds(void)
{
    struct timeval tmptv;

    gettimeofday(&tmptv, NULL);
    return ((long long)tmptv.tv_sec*1000)+(tmptv.tv_usec/1000);
}

int strisnumber(char *s) {
    while(*s == ' ' || (*s >= '0' && *s <= '9')) s++;
    return *s == '\0';
}

static void setlowercase(char *p) {
    while(*p) {
        *p = tolower(*p);
        p++;
    }
}

/* string compare, case insensitive */
int strcmpNC(char *s1, char *s2) {
    int l1 = strlen(s1);
    int l2 = strlen(s2);

    while(l1 && l2) {
        if (tolower((int)*s1) != tolower((int)*s2))
            return tolower((int)*s1)-tolower((int)*s2);
        s1++; s2++; l1--; l2--;
    }
    if (!l1 && !l2) return 0;
    return l1-l2;
}

/* sdscat() with plain old url encoding */
static char *sdscaturlencode(char *d, char *s) {
    int len = strlen(s);
    int i;

    for(i = 0; i < len; i++) {
        int c = s[i];
        if ((c >= 'A' && c <= 'Z') ||
            (c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9'))
        {
            d = sdscatlen(d,s+i,1);
        } else if (c == ' ') {
            d = sdscatlen(d,"+",1);
        } else if (c == '\n') {
            d = sdscatlen(d,"%0d%0a",6);
        } else {
            char *hexset = "0123456789abcdef";
            unsigned int t;
            t = (unsigned) c;
            d = sdscatlen(d,"%",1);
            d = sdscatlen(d,hexset+((t & 0xF0) >> 4),1);
            d = sdscatlen(d,hexset+((t & 0x0F) >> 4),1);
        }
    }
    return d;
}

void parseUrl(char *url, urlinfo *ui)
{
    char *copy = sdsnew(url), *p, *d;

    /* protocol */
    p = strstr(copy,"://");
    if (!p) {
        ui->proto = sdsnew("http");
        p = copy;
    } else {
        *p='\0';
        ui->proto = sdsnew(copy);
        *p=':';
        p += 3; /* skip "://" */
    }
    /* domain */
    d = strchr(p,'/');
    if (!d) d = strchr(p,'?');
    if (!d) {
        ui->domain = sdsnew(p);
        d = "\0";
    } else {
        char saved = *d;
        *d = '\0';
        ui->domain = sdsnew(p);
        *d = saved;
        if (*d == '/') d++; /* skip "/" */
    }
    /* port */
    p = strchr(ui->domain,':');
    if (!p) {
        ui->port = 80;
    } else {
        *p = '\0';
        ui->port = atoi(p+1);
        if (ui->port == 0) ui->port = 80;
    }
    /* request */
    ui->req = sdsnew("/");
    ui->req = sdscat(ui->req,d);
    sdsfree(copy);
}

void freeUrl(urlinfo *ui) {
    if (ui->proto) sdsfree(ui->proto);
    if (ui->domain) sdsfree(ui->domain);
    if (ui->req) sdsfree(ui->req);
}

/* --------------------------------- HTTP client ---------------------------- */
static char *createHttpReq(urlinfo *ui, int flags, cookie *cookie, int numcookies, char *referer)
{
    char *r = sdsnew((flags&WBOX_USE_HEAD) ? "HEAD ":"GET ");
    int j;

    r = sdscat(r, ui->req);
    r = sdscat(r, " HTTP/1.");
    r = sdscat(r, (flags & WBOX_USE_HTTP10) ? "0" : "1");
    r = sdscat(r, "\r\nHost: ");
    r = sdscat(r, ui->domain);
    if (ui->port != 80) {
        r = sdscatprintf(r,":%d",ui->port);
    }
    r = sdscat(r,"\r\n"
"User-Agent: Mozilla/5.0 Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4\r\n"
"Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n"
"Accept-Language: en-us,en;q=0.5\r\n");
    if (flags & WBOX_ACCEPT_COMPR)
        r = sdscat(r,"Accept-Encoding: gzip,deflate\r\n");
    r = sdscat(r,
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
"Connection: close\r\n");
    for (j = 0; j < numcookies; j++) {
        if (j == 0) r = sdscat(r,"Cookie: ");
        r = sdscaturlencode(r,cookie[j].name);
        r = sdscatlen(r,"=",1);
        r = sdscaturlencode(r,cookie[j].value);
        if (j+1 != numcookies)
            r = sdscatlen(r,"; ",2);
        else
            r = sdscatlen(r,"\r\n",2);
    }
    if (referer != NULL) {
        r = sdscat(r,"Referer: ");
        r = sdscat(r,referer);
        r = sdscatlen(r,"\r\n",2);
    }
    r = sdscat(r,"\r\n");
    return r;
}

static int extractReplyInfo(replyinfo *ri, char *buf, int buflen, wconfig *wc)
{
    char *hdr = sdsnewlen(buf,buflen); /* make a copy to play with it */
    char *code,*reason,*p;

    /* Make sure we don't go over the header */
    p = strstr(hdr,"\r\n\r\n");
    if (p) *p = '\0';
    if (wc->showhdr) {
        if (!wc->silent) printf("\n");
        printf("%s\n",hdr);
        if (!wc->silent) printf("\n");
    }
    ri->compr = strstr(hdr,"Content-Encoding: gzip") != NULL;

    /* Some ugly parsing required... */
    code = strchr(hdr,' ');
    if (!code) goto fmterr;
    code++;
    reason = strchr(code,' ');
    if (!reason) goto fmterr;
    *reason = '\0';
    reason++;
    p = strchr(reason,'\n');
    if (!p) goto fmterr;
    *p = '\0';
    p = strchr(reason,'\r');
    if (p) *p = '\0';
    
    /* We can now fill the struct */
    ri->code = atoi(code);
    ri->reason = sdsnew(reason);
    sdsfree(hdr);
    return 0;
fmterr:
    sdsfree(hdr);
    return 1;
}

void initReplyInfo(replyinfo *ri)
{
    ri->code = 0;
    ri->replylen = 0;
    ri->time = 0;
    ri->compr = 0;
    ri->tsamples = 0;
    ri->reason = NULL;
}

void freeReplyInfo(replyinfo *ri)
{
    if (ri->reason) sdsfree(ri->reason);
}

void copyReplyInfo(replyinfo *d, replyinfo *s)
{
    *d = *s;
    if (d->reason) d->reason = sdsdup(s->reason);
}

static int httpRequest(replyinfo *ri, char *ip, urlinfo *ui, wconfig *conf) {
    char err[ANET_ERR_LEN];
    char *req;
    int s, nwritten, totlen, reqflags = WBOX_NONE;
    long long stime = milliseconds();
    long long tsample_stime = milliseconds();
    long long stime_bps = 0; /* used for accurate bandwidth measuring */

    initReplyInfo(ri);
    /* Connect */
    s = anetTcpConnect(err, ip, ui->port);
    if (s == ANET_ERR) {
        fprintf(stderr, "Opening the connection: %s\n", err);
        exit(WBOX_EXIT_CONN);
    }
    /* Write the HTTP request */
    if (conf->compr) reqflags |= WBOX_ACCEPT_COMPR;
    if (conf->head) reqflags |= WBOX_USE_HEAD;
    if (conf->http10) reqflags |= WBOX_USE_HTTP10;
    req = createHttpReq(ui,reqflags,conf->cookie,conf->cookies,conf->referer);
    nwritten = write(s, req, sdslen(req));
    if (nwritten == -1) {
        perror("Sending the HTTP request");
        sdsfree(req);
        exit(WBOX_EXIT_IO);
    }
    /* Read the request */
    totlen = 0;
    while(1) {
        char buf[WBOX_RECV_BUF];
        int nread;

        nread = anetRead(s, buf, WBOX_RECV_BUF);
        if (nread == 0) break;
        if (nread == -1) {
            perror("Reading from socket");
            sdsfree(req);
            exit(WBOX_EXIT_IO);
        }
        /* Populare tsamples */
        if (conf->timesplit) {
            int lastsample = ri->tsamples == WBOX_TIMESPLIT_SAMPLES;
            timesplit *ts;
            if (lastsample)
                ts = &ri->tsample[WBOX_TIMESPLIT_SAMPLES-1];
            else
                ts = &ri->tsample[ri->tsamples];
            ts->time = (lastsample ? ts->time : 0) +
                        (int) (milliseconds()-tsample_stime);
            if (!lastsample) ts->firstbyte = totlen;
            ts->lastbyte = totlen+nread-1;
            if (!lastsample) ri->tsamples++;
            tsample_stime = milliseconds();
        }
        /* Get HTTP reply header information from the first chunk of data */
        if (totlen == 0) extractReplyInfo(ri,buf,nread,conf);

        if (conf->dump) {
            int lastsample = ri->tsamples == WBOX_TIMESPLIT_SAMPLES;
            fwrite(buf,nread,1,stdout);
            if (!lastsample && conf->timesplit) {
                int idx = ri->tsamples-1;
                printf("\n\n-----------------------------------------------\n");
                printf("CHUNK TIME INFORMATION: %d-%d -> %d ms\n",
                    ri->tsample[idx].firstbyte,
                    ri->tsample[idx].lastbyte,
                    ri->tsample[idx].time);
                printf("-----------------------------------------------\n\n");
            }
            fflush(stdout);
        }
        totlen += nread;
        if (!conf->dump && !conf->silent && conf->clients <= 1) {
            printf(WBOX_ANSI_CLEARLINE);
            printf("%d bytes readed",totlen);
            if (totlen == WBOX_RECV_BUF*4)
                stime_bps = milliseconds();
            if (totlen > WBOX_RECV_BUF*4) {
                int recvbytes = totlen-WBOX_RECV_BUF*4;
                int elapsed = (int) (milliseconds()-stime_bps);
                float kbs = ((float)recvbytes*1000/elapsed)/1024;
                printf(" (%.2f kbytes/s)",kbs);
            }
            fflush(stdout);
        }
        if (conf->close && totlen == nread) break;
    }
    /* Done, close the socket and calculate timings */
    close(s);
    sdsfree(req);
    ri->time = (int) (milliseconds()-stime);
    ri->replylen = totlen;
    return 0;
}

/* return 0 for the parent, 1 for the child */
static int spawnChilds(int count)
{
    int j;
    for(j = 0; j < count; j++) {
        pid_t p = fork();
        if (p == -1) {
            perror("fork");
            return 0;
        }
        if (p == 0) return 1;
    }
    return 0;
}

static void printTimesplit(replyinfo *ri) {
    int j;

    for (j = 0; j < ri->tsamples; j++) {
        printf("       [%d] %d-%d -> %d ms\n", j,
            ri->tsample[j].firstbyte,
            ri->tsample[j].lastbyte,
            ri->tsample[j].time);
    }
}

/* --------------------------------- HTTP server ---------------------------- */
static int parseRequest(char *req, reqinfo *ri) {
    char *copy, *r;
    char *p;
    int noproto = 0;

    ri->file = NULL; /* Make it safe to free later */
    copy = r = sdsdup(req); /* work with a copy */

    p = strchr(r,' ');
    if (!p) goto fmterr;
    *p = '\0';
    setlowercase(r);
    if (!strcmp(r,"get")) ri->method = WBOX_REQ_METHOD_GET;
    else if (!strcmp(r,"post")) ri->method = WBOX_REQ_METHOD_POST;
    else if (!strcmp(r,"head")) ri->method = WBOX_REQ_METHOD_HEAD;
    else ri->method = WBOX_REQ_METHOD_OTHER;

    r = p+1;
    while(*r == ' ') r++;
    p = NULL;
    (p = strchr(r,' ')) || (p = strchr(r,'\r')) || (p = strchr(r,'\n'));
    if (p)
        *p = '\0';
    else
        noproto = 1;
    if (r[0] != '/') goto fmterr;
    ri->file = sdsnew(r);

    ri->protover = 10;
    if (!noproto) {
        r = p+1;
        if ((p = strchr(r,'\n')) != NULL) {
            *p = '\0';
            if (strstr(r, "HTTP/1.1")) ri->protover = 11;
        }
    }
    sdsfree(copy);
    return 0;

fmterr:
    sdsfree(copy);
    return 1;
}

static void freeReqInfo(reqinfo *ri) {
    if (ri->file) sdsfree(ri->file);
}

static void urldecode(char *d, char *s, int n)
{
    char *start = d;
    if (n < 1) return;
    while(*s && n > 1) {
        int c = *s;
        switch(c) {
        case '+': c = ' '; break;
        case '%':
            if (*(s+1) && *(s+2)) {
                int high = toupper(*(s+1));
                int low = toupper(*(s+2));

                if (high <= '9') high -= '0';
                else high = (high - 'A') + 10;
                if (low <= '9') low -= '0';
                else low = (low - 'A') + 10;
                c = (high << 4)+low;
                s += 2;
            }
            break;
        }
        if (c != ' ' || d != start) {
                *d++ = c;
                n--;
        }
        s++;
    }
    /* Right trim */
    *d = '\0';
    d--;
    while (d >= start && *d == ' ') {
            *d = '\0';
            d--;
    }
}

static char *createFullPath(char *root, char *file) {
    char *uefile, *fp, *p;
    int len;
    
    uefile = sdsnewlen(NULL,sdslen(file)+1);
    urldecode(uefile,file,sdslen(uefile)); /* url decode the original name */
    sdsupdatelen(uefile);
    fp = sdsnew(root);
    fp = sdscat(fp,uefile);
    sdsfree(uefile);
    while((p=strstr(fp,".."))) {
        *p = '_';
        *(p+1) = '_';
    }
    /* Make sure there aren't trailing slashes */
    len = sdslen(fp)-1;
    while(len >= 1 && fp[len] == '/') fp[len] = '\0';
    sdsupdatelen(fp);
    return fp;
}

static char *createHttpReply(reqinfo *ri, char *code, char *reason, char *ctype, int len)
{
    char date[128];
    struct tm *tm;
    time_t t;
    char *r;

    r = sdsnew("HTTP/1.");
    r = sdscat(r,ri->protover == 11 ? "1 " :"0 ");
    r = sdscat(r,code);
    r = sdscat(r," ");
    r = sdscat(r,reason);
    r = sdscat(r,"\r\n");
    t = time(NULL);
    tm = gmtime(&t);
    strftime(date, 128, "%a, %d %b %Y %H:%M:%S GMT", tm);
    r = sdscat(r,"Date: ");
    r = sdscat(r,date);
    r = sdscatprintf(r, "\r\nServer: WBox %d (http://hping.org/wbox)\r\n",
        WBOX_VERSION);
    r = sdscat(r,"Content-type: ");
    r = sdscat(r,ctype);
    if (len != -1)
        r = sdscatprintf(r,"\r\nContent-Length: %d",len);
    r = sdscat(r,"\r\n\r\n");
    return r;
}

static char *sdscatentities(char *d, char *s) {
    int len = strlen(s);
    int i;

    for(i = 0; i < len; i++) {
        switch(s[i]) {
        case '<': d=sdscatlen(d,"&lt;",4); break;
        case '>': d=sdscatlen(d,"&gt;",4); break;
        case '&': d=sdscatlen(d,"&amp;",5); break;
        case '"': d=sdscatlen(d,"&quot;",6); break;
        default: d=sdscatlen(d,s+i,1); break;
        }
    }
    return d;
}

/* Special version of sdscat() that takes care of special
 * chars in filenames having a special meaning for the
 * browser (like #). This function also performs HTML entities
 * conversion. */
static char *sdscaturl(char *d, char *s) {
    char *hexset = "0123456789abcdef";
    int len;
    int i;
    unsigned int t;

    /* Entities conversion step */
    {
        char *new = sdsnew("");
        new = sdscatentities(new,s);
        s = new;
    }
    len = strlen(s);
    for(i = 0; i < len; i++) {
        switch(s[i]) {
        case '#':
            t = (unsigned) s[i];
            d = sdscatlen(d,"%",1);
            d = sdscatlen(d,hexset+((t & 0xF0) >> 4),1);
            d = sdscatlen(d,hexset+((t & 0x0F) >> 4),1);
            break;
        default:
            d = sdscatlen(d,s+i,1);
            break;
        }
    }
    sdsfree(s);
    return d;
}

static char *createDirListing(char *path, char *webpath) {
    DIR *d;
    struct dirent *de;
    struct stat sbuf;
    char *b;
    int onlydirs;

    d = opendir(path);
    if (d == NULL)
        return sdsnew("Can't open the directory...");

    b = sdsnew(htmlheader);
    b = sdscat(b,"<h1>Index of ");
    b = sdscatentities(b,webpath);
    b = sdscat(b,"</h1>\n<table class=\"dirindex\">");
    for (onlydirs = 1; onlydirs >= 0; onlydirs--) {
        while((de = readdir(d))) {
            char *filepath, *trname;
            int retval, isdir;
            char unitchar;
            float size;

            filepath = sdsdup(path);
            filepath = sdscatlen(filepath,"/",1);
            filepath = sdscat(filepath,de->d_name);
            retval = stat(filepath,&sbuf);
            if (retval == -1) {
                sdsfree(filepath);
                continue;
            }
            if (access(filepath,R_OK) == -1) {
                sdsfree(filepath);
                continue;
            }
            sdsfree(filepath);
            isdir = S_ISDIR(sbuf.st_mode);
            if (onlydirs != isdir) continue;
            if (isdir && de->d_name[0] == '.' && de->d_name[1] == '\0')
                continue;
            /* File name */
            b = sdscat(b,"<tr><td><a href=\"");
            trname = sdsnew(de->d_name);
            if (sdslen(trname) > 30) {
                trname = sdsrange(trname,0,30);
                trname = sdscat(trname,"...>");
            }
            b = sdscaturl(b,de->d_name);
            if (isdir) {
                b = sdscatlen(b,"/",1);
                b = sdscat(b,"\" class=\"dir\">[DIR] ");
            } else
                b = sdscat(b,"\">");
            b = sdscatentities(b,trname);
            b = sdscat(b,"</a></td>\n");
            sdsfree(trname);
            /* File size */
            size = sbuf.st_size;
            if (size > 1024*1024*1024) {
                size /= 1024*1024*1024;
                unitchar='G';
            } else if (size > 1024*1024) {
                size /= 1024*1024;
                unitchar='M';
            } else if (size > 1024) {
                size /= 1024;
                unitchar='k';
            } else {
                unitchar='b';
            }
            b = sdscat(b,"<td>");
            if (size-(int)size)
                b = sdscatprintf(b,"%.1f%c",size,unitchar);
            else
                b = sdscatprintf(b,"%d%c",(int)size,unitchar);
            b = sdscat(b,"</td></tr>\n");
        }
        rewinddir(d);
    }
    closedir(d);
    b = sdscat(b,"</table>\n");
    b = sdscat(b,htmlfooter);
    return b;
}

static char *guessContentType(char *filename) {
    char *p;

    p = strrchr(filename,'.');
    if (!p) return "text/plain";
    p++;
    if (!strcmpNC(p,"jpg") || !strcmpNC(p,"jpeg")) return "image/jpeg";
    if (!strcmpNC(p,"css")) return "text/css";
    if (!strcmpNC(p,"js")) return "text/javascript";
    if (!strcmpNC(p,"ico")) return "image/x-icon";
    if (!strcmpNC(p,"png")) return "image/png";
    if (!strcmpNC(p,"gif")) return "image/gif";
    if (!strcmpNC(p,"html")) return "text/html";
    if (!strcmpNC(p,"htm")) return "text/html";
    if (!strcmpNC(p,"xml")) return "text/plain";
    if (!strcmpNC(p,"txt")) return "text/plain";
    if (!strcmpNC(p,"c")) return "text/plain";
    if (!strcmpNC(p,"rb")) return "text/plain";
    if (!strcmpNC(p,"py")) return "text/plain";
    if (!strcmpNC(p,"cpp")) return "text/plain";
    if (!strcmpNC(p,"c++")) return "text/plain";
    if (!strcmpNC(p,"tcl")) return "text/plain";
    if (!strcmpNC(p,"pl")) return "text/plain";
    if (!strcmpNC(p,"lua")) return "text/plain";
    if (!strcmpNC(p,"csv")) return "text/plain";
    if (!strcmpNC(p,"pdf")) return "application/pdf";
    if (!strcmpNC(p,"mp3")) return "audio/mpeg";
    if (!strcmpNC(p,"mpg")) return "video/mpeg";
    /* All the rest is binary data... */
    return "text/plain";
}

static void serverModeChild(int s, char *ip, int port, wconfig *conf) {
    char *req = sdsnew("");
    char buf[WBOX_RECV_BUF];
    char *fullpath, *reply = NULL;
    char *document = NULL;
    struct stat sbuf;
    reqinfo ri;

    /* Read the request */
    while(1) {
        int nread, len;
        nread = read(s,buf,WBOX_RECV_BUF);
        if (nread == 0) {
            printf("%s:%d EOF from client\n",ip,port);
            exit(WBOX_EXIT_IO);
        } else if (nread == -1) {
            printf("%s:%d reading: %s\n", ip, port, strerror(errno));
            exit(WBOX_EXIT_IO);
        }
        req = sdscatlen(req,buf,nread);
        len = sdslen(req);
        if (len > 4) {
            if (!memcmp(req+(len-2),"\n\n",2) ||
                !memcmp(req+(len-4),"\r\n\r\n",4)) break;
        }
    }
    /* Parse it */
    if (parseRequest(req,&ri)) {
        printf("%s:%d bad request:\n%s\n", ip, port, req);
        sdsfree(req);
        exit(WBOX_EXIT_PROTO);
    }
    sdsfree(req);
    /* Create the full path of the resource */
    fullpath = createFullPath(conf->webroot, ri.file);
    /*
    printf("Method: %d, file: %s, protover: %d\n",
        ri.method, fullpath, ri.protover);
    */
    /* Generate the reply */
    if (access(fullpath,R_OK) == -1) {
        /* 404 */
        if (!conf->silent)
            printf("%s:%d 404: %s\n", ip, port, fullpath);
        reply = createHttpReply(&ri,"404","Not Found","text/html",-1);
        reply = sdscat(reply, htmlheader);
        reply = sdscat(reply, "<h1>404 Not Found</h1>");
        reply = sdscatprintf(reply, "<h2>");
        reply = sdscatentities(reply,ri.file);
        reply = sdscat(reply," was not found on this server</h2>");
        reply = sdscat(reply, htmlfooter);
        anetWrite(s,reply,sdslen(reply));
        goto cleanup;
    }
    if (stat(fullpath,&sbuf) == -1) goto cleanup;
    if (S_ISDIR(sbuf.st_mode)) {
        /* Directory listing */
        document = createDirListing(fullpath,ri.file);
        reply = createHttpReply(&ri,"200","OK","text/html",sdslen(document));
        anetWrite(s,reply,sdslen(reply));
        anetWrite(s,document,sdslen(document));
        goto cleanup;
    } else {
        /* Regular file */
        char *ctype = guessContentType(ri.file);
        char buf[WBOX_RECV_BUF];
        int fd;
        reply = createHttpReply(&ri,"200","OK",ctype,sbuf.st_size);
        anetWrite(s,reply,sdslen(reply));
        if ((fd = open(fullpath,O_RDONLY)) == -1) goto cleanup;
        while(1) {
            int nread;
            nread = read(fd,buf,WBOX_RECV_BUF);
            if (nread == 0 || nread == -1) break;
            if (anetWrite(s,buf,nread) == -1) {
                if (!conf->silent)
                    printf("%s:%d write error\n", ip, port);
                break;
            }
        }
    }
cleanup:
    sdsfree(document);
    sdsfree(reply);
    sdsfree(fullpath);
    freeReqInfo(&ri);
    close(s);
    if (!conf->silent)
        printf("%s:%d served with success\n", ip, port);
}

static void serverMode(wconfig *conf) {
    int server, clientport;
    char err[ANET_ERR_LEN], clientip[32];
    if (!conf->webroot) {
        fprintf(stderr,
            "Sorry, you must specify 'webroot <path>' in server mode.\n");
        exit(WBOX_EXIT_BADARGS);
    }
    /* Make sure the webroot does not end with a slash */
    {
        int wblen = strlen(conf->webroot);
        while (wblen && conf->webroot[wblen-1] == '/') {
            conf->webroot[wblen-1] = '\0';
            wblen--;
        }
    }
    printf("WBOX starting in server mode, port %d, webroot %s\n",
        conf->serverport, conf->webroot);
    /* Configure the TCP server */
    server = anetTcpServer(err,conf->serverport,NULL);
    if (server == ANET_ERR) {
        fprintf(stderr, "Starting in server mode (port %d): %s\n",
            conf->serverport, err);
        exit(WBOX_EXIT_IO);
    }
    while(1) {
        pid_t pid;
        int fd = anetAccept(err, server, clientip, &clientport);
        if (fd == ANET_ERR) {
            fprintf(stderr, "Warning, accepting client: %s\n", err);
            continue;
        }
        if (conf->activeclients == conf->maxclients) {
            printf("%s:%d closing connection! max number of clients reached (tune this using the maxclients <number> option)\n",clientip,clientport);
            close(fd);
            continue;
        }
        if (!conf->silent)
            printf("%s:%d connected\n",clientip,clientport);
        conf->activeclients++;
        pid = fork();
        if (pid == -1) {
            conf->activeclients--;
            perror("fork");
            close(fd);
            continue;
        }
        if (pid == 0) {
            close(server);
            serverModeChild(fd,clientip,clientport,conf);
            exit(WBOX_EXIT_SUCCESS);
        } else {
            if (!conf->silent)
                printf("%s:%d handled by process %d\n",clientip,clientport,pid);
            close(fd);
        }
    }
}

/* --------------------------------- Main() & co ---------------------------- */
static void wboxHelp(void) {
    printf(
"Usage: wbox <url> [options ...]\n\n"
"options:\n\n"
"<number>             - stop after <number> requests\n"
"compr                - send Accept-Encoding: gzip,deflate in request\n"
"showhdr              - show the HTTP reply header\n"
"dump                 - show the HTTP reply header + body\n"
"silent               - don't show status lines\n"
"head                 - use the HEAD method instead of GET\n"
"http10               - use HTTP/1.0 instead of HTTP/1.1\n"
"close                - close the connection after reading few bytes\n"
"host    <hostname>   - use <hostname> as Host: field in HTTP request\n"
"timesplit            - show transfer times for different data chunks\n"
"wait    <number>     - wait <number> seconds between requests. Default 1.\n"
"clients <number>     - spawn <number> concurrent clients (via fork()).\n"
"referer <url>        - Send the specified referer header.\n"
"cookie  <name> <val> - Set cookie name=val, can be used multiple times.\n"
"-h or --help         - show this help.\n"
"-v                   - show version.\n"
"\nSERVER MODE\n\n"
"Usage: wbox servermode webroot <path> [serverport <portnumber> (def 8081)]\n\n"
"options:\n\n"
"maxclients <number>  - Max concurrent clients in server mode (default 20).\n"
"\nEXAMPLES\n\n"
"wbox wikipedia.org                  (simplest, basic usage)\n"
"wbox wikipedia.org 3 compr wait 0   (three requests, compression, no delay)\n"
"wbox wikipedia.org 1 showhdr silent (just show the HTTP reply header)\n"
"wbox wikipedia.org timesplit        (show splitted time information)\n"
"wbox 1.2.3.4 host example.domain    (test a virtual domain at 1.2.3.4)\n"
"wbox servermode webroot /tmp/mydocuments  (Try it with http://127.0.0.1:8081)\n"
"\n"
"More docs? there is a tutorial at http://hping.org/wbox\n"
    );
}

static void printStats(void) {
    printf("--- %d replies received",conf.timesum_samples);
    if (conf.timesum_samples) {
        printf(", time min/avg/max = %d/%.2f/%d",
            conf.mintime,
            (float)(conf.timesum/conf.timesum_samples),
            conf.maxtime);
    }
    printf(" ---\n");
}

static void sigHandler(int signum)
{
    if (signum == SIGINT) {
        WBOX_NOTUSED(signum);
        if (!conf.silent) {
            printf("\n");
            printStats();
            printf("\n");
        }
        exit(WBOX_EXIT_SUCCESS);
    } else if (signum == SIGCHLD) {
        int status;
        while (waitpid(-1,&status,WNOHANG) > 0)
            conf.activeclients--;
        if (!conf.silent && conf.servermode)
            printf("%d active clients\n", conf.activeclients);
    }
}

static void parseArgs(char **argv, int argc, wconfig *conf) {
    int j;

    memset(conf,0,sizeof(*conf));
    conf->wait = 1;
    conf->maxreq = -1;
    conf->serverport = WBOX_DEFAULT_SERVER_PORT;
    conf->maxclients = WBOX_DEFAULT_MAX_CLIENTS;

    if (argc < 2) {
        wboxHelp();
        exit(WBOX_EXIT_BADARGS);
    }
    if (!strcmp(argv[1],"-h") || !strcmp(argv[1],"--help")) {
        wboxHelp();
        exit(WBOX_EXIT_SUCCESS);
    }
    if (!strcmp(argv[1],"-v")) {
        printf("WBox version %d\n", WBOX_VERSION);
        exit(WBOX_EXIT_SUCCESS);
    }
    /* Server mode option must be at argv[1] ... */
    if (!strcmp(argv[1],"servermode")) conf->servermode = 1;

    /* Make sure it's possible to call wbox with every kind
     * of argument as url including "-h", using:
     * 
     *   wbox -- -h
     *
     * Hardly useful but there must be a way to do this for
     * the user.
    */
    conf->url = argv[1];
    if (argc > 2 && !strcmp(argv[1],"--")) {
        argv++;
        argc--;
        conf->url = argv[1];
    }

    /* Option parsing, in a more discorsive way compared to
     * the usual unix switches */
    for(j = 2; j < argc; j++) {
        int next = (j+1 != argc);
        int leftargs = argc-(j+1);
        if (!strcmp(argv[j],"dump")) {
            conf->dump=1;
        } else if (!strcmp(argv[j],"compr")) {
            conf->compr=1;
        } else if (!strcmp(argv[j],"head")) {
            conf->head=1;
        } else if (!strcmp(argv[j],"timesplit")) {
            conf->timesplit=1;
        } else if (!strcmp(argv[j],"showhdr")) {
            conf->showhdr=1;
        } else if (!strcmp(argv[j],"silent")) {
            conf->silent=1;
        } else if (!strcmp(argv[j],"http10")) {
            conf->http10=1;
        } else if (!strcmp(argv[j],"close")) {
            conf->close=1;
        } else if (next && !strcmp(argv[j],"host")) {
            j++;
            conf->host = argv[j];
        } else if (next && !strcmp(argv[j],"wait")) {
            j++;
            conf->wait = atoi(argv[j]);
        } else if (next && !strcmp(argv[j],"clients")) {
            j++;
            conf->clients = atoi(argv[j]);
        } else if (next && !strcmp(argv[j],"referer")) {
            j++;
            conf->referer = argv[j];
        } else if (leftargs >= 2 && !strcmp(argv[j],"cookie")) {
            if (conf->cookies < WBOX_COOKIES_MAX) {
                conf->cookie[conf->cookies].name = argv[j+1];
                conf->cookie[conf->cookies].value = argv[j+2];
                conf->cookies++;
            }
            j += 2;
        } else if (next && !strcmp(argv[j],"webroot")) {
            j++;
            conf->webroot = argv[j];
        } else if (next && !strcmp(argv[j],"serverport")) {
            j++;
            conf->serverport = atoi(argv[j]);
        } else if (next && !strcmp(argv[j],"maxclients")) {
            j++;
            conf->maxclients = atoi(argv[j]);
        } else if (!strcmp(argv[j],"-h") ||
                   !strcmp(argv[j],"--help")) {
            wboxHelp();
            exit(WBOX_EXIT_SUCCESS);
        } else if (strisnumber(argv[j]) && atoi(argv[j]) > 0) {
            conf->maxreq = atoi(argv[j]);
        } else {
            fprintf(stderr, "\n * Wrong option or params: %s\n\n", argv[j]);
            wboxHelp();
            exit(WBOX_EXIT_BADARGS);
        }
    }
}

static void printReplyStatus(int reqid, replyinfo *oldri, replyinfo *ri) {
    /* Print status line */
    printf(WBOX_ANSI_CLEARLINE);
    /* hostname id */
    printf("%d. %d %s",reqid,ri->code,ri->reason ? ri->reason : "()");
    /* reply length */
    if (reqid != 0 && oldri->replylen != ri->replylen)
        printf("    (%d)",ri->replylen);
    else
        printf("    %d",ri->replylen);
    printf(" bytes");
    /* request time */
    printf("    %d ms",ri->time);
    if (ri->compr) printf("    compr");
    printf("\n");
}

int main(int argc, char **argv)
{
    char err[ANET_ERR_LEN];
    char ip[32];
    int reqid = 0;
    replyinfo ri, oldri;
    urlinfo ui;

    setlocale(LC_ALL,"C");
    Signal(SIGCHLD,sigHandler);
    Signal(SIGPIPE,sigHandler);
    parseArgs(argv,argc,&conf);

    /* Start in server mode if needed */
    if (conf.servermode) serverMode(&conf);

    parseUrl(conf.url,&ui);
    if (anetResolve(err,ui.domain,ip) != ANET_OK) {
        fprintf(stderr,"%s\n",err);
        exit(WBOX_EXIT_RESOLV);
    }

    if (conf.host != NULL) {
        sdsfree(ui.domain);
        ui.domain=sdsnew(conf.host);
    }

    if (!conf.silent) {
        printf("WBOX %s (%s) port %d",ui.domain,ip,ui.port);
        if (conf.compr) printf(" [compr]");
        if (conf.head) printf(" [head]");
        if (conf.wait != 1) printf(" [wait %d]",conf.wait);
        printf("\n");
    }

    if (conf.clients > 1) {
        if (spawnChilds(conf.clients-1)) {
            /* Childs specific code */
        } else {
            /* Parent specific code */
            Signal(SIGINT,sigHandler);
        }
    } else {
        Signal(SIGINT,sigHandler);
    }

    initReplyInfo(&oldri);
    while(1) {
        int flags = WBOX_NONE;
        if (conf.compr) flags |= WBOX_ACCEPT_COMPR;
        if (conf.head) flags |= WBOX_USE_HEAD;

        /* Request */
        httpRequest(&ri,ip,&ui,&conf);
        conf.timesum += ri.time;
        conf.timesum_samples++;
        if (reqid == 0) {
            conf.mintime = conf.maxtime = ri.time;
        } else {
            if (conf.mintime > ri.time) conf.mintime = ri.time;
            if (conf.maxtime < ri.time) conf.maxtime = ri.time;
        }
        if (!conf.silent) {
            printReplyStatus(reqid,&oldri,&ri);
            if (conf.timesplit) printTimesplit(&ri);
        }
        reqid++;
        freeReplyInfo(&oldri);
        copyReplyInfo(&oldri,&ri);
        freeReplyInfo(&ri);
        if (reqid == conf.maxreq) break;
        sleep(conf.wait);
    }
    freeUrl(&ui);
    freeReplyInfo(&oldri);
    if (!conf.silent) printStats();
    return 0;
}
