#include <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <dirent.h>
#include <errno.h>
#include <resolv.h>
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ioctl.h>


#define MAXLSIETN 1024
#define MAXBUF 1024
#define MAXPATH 512

/* kill the zombie child process */
void childWaiter(int signum)
{
    pid_t pid;
    int stat;
    
    while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
        fprintf(stdout, "child %d terminated\n", pid);
    return;
}

/* get owner info of a certain file 
 *
 * returns pointer to username associated with uid, uses getpw()
 **/
char *uidToName(uid_t uid)
{
    struct passwd *getpwuid(), *pw_ptr;
    static  char numstr[10];
    
    if((pw_ptr = getpwuid(uid)) == NULL)
    {
        sprintf(numstr,"%d", uid);
        return numstr;
    }
    else return pw_ptr->pw_name ;
}

/* get group info of a certain file
 *
 * returns pointer to group number gid. used getgrgid(3)
 **/
char *gidToName(gid_t gid)
{
    struct group *getgrgid(), *grpPtr;
    static  char numstr[10];
    
    if((grpPtr = getgrgid(gid)) == NULL)
    {
    sprintf(numstr,"%d", gid);
    return numstr;
    }
    else return grpPtr->gr_name;
}

/* get permission info of a certain file_type
 *
 * This function takes a mode value and a char array and puts into the char array the file type and the 
 * nine letters that correspond to the bits in mode. NOTE: It does not code setuid, setgid, and sticky
 * codes
 **/
void modeToLetters(int mode, char str[])
{
    strcpy(str, "----------");           /* default=no perms */
    
    if(S_ISDIR(mode))  str[0] = 'd';    /* directory?       */
    if(S_ISCHR(mode))  str[0] = 'c';    /* char devices     */
    if(S_ISBLK(mode))  str[0] = 'b';    /* block device     */
    
    if(mode & S_IRUSR) str[1] = 'r';    /* 3 bits for user  */
    if(mode & S_IWUSR) str[2] = 'w';
    if(mode & S_IXUSR) str[3] = 'x';
    
    if(mode & S_IRGRP) str[4] = 'r';    /* 3 bits for group */
    if(mode & S_IWGRP) str[5] = 'w';
    if(mode & S_IXGRP) str[6] = 'x';
    
    if(mode & S_IROTH) str[7] = 'r';    /* 3 bits for other */
    if(mode & S_IWOTH) str[8] = 'w';
    if(mode & S_IXOTH) str[9] = 'x';
}

/* print out the info of a certain file/dir
 *
 * display the info about 'filename'.  The info is stored in struct at *info_p
 **/
void showFileInfo(FILE *lsFd, char *realFilename, char *filename, struct stat *infoP, char serverIp[], char port[])
{
    char *ctime(), *filemode();
    char modestr[11];
    
    modeToLetters(infoP->st_mode, modestr);
    
    fprintf(lsFd, "<tr>");
    fprintf(lsFd, "<td><a href=\"http://%s:%s%s\">%s</a></td>", serverIp, port, realFilename, filename);
    fprintf(lsFd, "<td>%8ld</td>" , (long)infoP->st_size);
    fprintf(lsFd, "<td>%s</td>" , modestr);
    fprintf(lsFd, "<td>%-8s</td>" , uidToName(infoP->st_uid));
    fprintf(lsFd, "<td>%-8s</td>" , gidToName(infoP->st_gid));
    fprintf(lsFd, "<td>%s</td>", ctime(&infoP->st_mtime));
    fprintf(lsFd, "</tr>\n");
}

/* stat a file/dir
 *
 * filename: relative path of a file/dir
 * dirname:  absolute path of a file/dir
 **/
void dostat(FILE *lsFd, char *filename, char *dirname, char serverIp[], char port[])
{
    struct stat info;
    char *realFilename;
    
    /* get the absolute path of a file */
    int strLen = strlen(dirname);
    int len = 0;
    if(dirname[strLen - 1] == '/')
    {
        len = strLen + strlen(filename);
        realFilename = malloc(len + 1);
        sprintf(realFilename, "%s%s", dirname, filename);
    }
    else
    {
        len = strLen + strlen(filename) + 1;
        realFilename = malloc(len + 1);
        sprintf(realFilename, "%s/%s", dirname, filename);
    }
    
    if(stat(realFilename, &info) == -1)   /* cannot stat */
        {
            fprintf(stderr, "cannot stat %s\r\n" , realFilename);
            exit(1);
        }
    else   /* else show info */
        showFileInfo(lsFd, realFilename, filename, &info, serverIp, port);
}


/* list files in directory called dirname
 *
 * serverIp: ip of server
 * port: port of server
 **/
void processReq(FILE *lsFd, char dirname[], char serverIp[], char port[])
{
    DIR *dirPtr;/* the directory */
    struct dirent *direntp;/* each entry */
    struct stat info;
    int readFd, fileLen, ret;
    char *readBuffer;
    
    if (stat(dirname, &info)) {
        fprintf(lsFd,
                "HTTP/1.1 200 OK\r\nServer: Web Resource Viewer by Arthur1989\r\nConnection: close\r\n\r\n<html><head><title>%d - %s</title></head>"
                "<body><font size=+4>Web Resource Viewer</font><br><hr width=\"100%%\"><br><center>"
                "<table border cols=3 width=\"100%%\">", errno,
                strerror(errno));
        fprintf(lsFd,
                "</table><font color=\"CC0000\" size=+2>please contact arthur19891106@gmail.com:\r <p></p> %s--%s</font></body></html>\r\n",
                dirname, strerror(errno));
        exit(1);
    }
    
    /* if dirname points to a file instead od a directory, then download it */
    if (S_ISREG(info.st_mode))
    {
        readFd = open(dirname, O_RDONLY);
        fileLen = lseek(readFd, 0, SEEK_END);
        readBuffer = (char *) malloc(fileLen + 1);
        bzero(readBuffer, fileLen + 1);
        lseek(readFd, 0, SEEK_SET);
        ret = read(readFd, readBuffer, fileLen);
        close(readFd);
        fprintf(lsFd, "HTTP/1.1 200 OK\r\nServer: Web Resource Viewer by Arthur1989\r\nConnection: keep-alive\r\nContent-type: application/*\r\nContent-Length:%d\r\n\r\n", fileLen);
        fwrite(readBuffer, fileLen, 1, lsFd);
        free(readBuffer);
    }
    /* if dirname points to a directory, then display all the content of it */
    else if (S_ISDIR(info.st_mode))
    {
        fprintf(lsFd,
                "HTTP/1.1 200 OK\r\nServer: Web Resource Viewer by Arthur1989\r\nConnection: close\r\n\r\n<html><head><title>%s</title></head>"
                "<body><font size=+4>Web Resource Viewer</font><br><hr width=\"100%%\"><br><center>"
                "<table border cols=3 width=\"100%%\">", dirname);
        fprintf(lsFd, "<caption><font size=+3>Directory: %s</font></caption>\n", dirname);
        fprintf(lsFd, "<tr><td>File</td><td>Size</td><td>Permission</td><td>Owner</td><td>Group</td><td>Last Modified Time</td></tr>\n");
        if((dirPtr = opendir(dirname)) == NULL)
        {
            fprintf(stderr,"cannot open %s\r\n", dirname);
            exit(1);
        }
        
        while((direntp = readdir(dirPtr)) != NULL)
            dostat(lsFd, direntp->d_name, dirname, serverIp, port);
        
        fprintf(lsFd, "</table></center>");
        fprintf(lsFd, "--from Arthur1989</body></html>");
        
        closedir(dirPtr);
    }
    /* something goes wrong */
    else
    {
        fprintf(lsFd,
                "HTTP/1.1 200 OK\r\nServer: Web Resource Viewer by Arthur1989\r\nConnection: close\r\n\r\n<html><head><title>permission denied</title></head>"
                "<body><font size=+4>Web Resource Viewer</font><br><hr width=\"100%%\"><br><center>"
                "<table border cols=3 width=\"100%%\">");
        fprintf(lsFd,
                "</table><font color=\"CC0000\" size=+2>Resource '%s' is prohibited from visiting, please contact arthur19891106@gmail.com</font></body></html& gt;",
                dirname);
    }
}

int main(int argc, char *argv[])
{
    int listenfd, connfd;
    pid_t childpid;
    socklen_t listenlen, chilen;
    struct sockaddr_in cliaddr, servaddr;
    struct ifreq ifr;
    char buffer[MAXBUF + 1];
    char serverIp[64 + 1];
    int len;
    
    if(argc != 2)
    {
        fprintf(stderr,"usage: ./mywebserv portnum\r\n");
        exit(1);
    }
    
    /* /* kill the zombie child process, signal: SIGCHLD */
    signal(SIGCHLD, childWaiter);
    
    /*get a socket*/
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
        fprintf(stderr,"failed to initialize a socket\r\n");
        exit(1);
    }
    
    /* make sure the main process can be run on the port,even if it is being used */
    listenlen = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &listenlen, sizeof(listenlen));
    
    /* initialize socket servaddr*/
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(atoi(argv[1]));
    
    /* bind address and port to the listenfd */
    if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
    {
        fprintf(stderr,"failed to bind server socket to specified port\r\n");
        exit(1);
    }
    
    /* get the server ip */
    strcpy(ifr.ifr_name, "eth0");
    if (ioctl(listenfd, SIOCGIFADDR, &ifr) < 0)
    {
        fprintf(stderr,"failed to ioctl\r\n");
        exit(1);
    }
    sprintf(serverIp, "%s", inet_ntoa(((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr));
    fprintf(stdout, "server ip is: %s\r\n", serverIp);
    
    /* make listenfd into a listen socket */
    if(listen(listenfd, MAXLSIETN) != 0)
    {
        fprintf(stderr,"failed to listen on listenfd\r\n");
        exit(1);
    }
    
    /* main process fall into the loop */
    for( ; ; )
    {
        /* server is blocked here, waiting to accept connection from client */
        chilen = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &chilen);
        /* a signal from child process might interrupt our accept() call,ignore it */
        if(connfd < 0 && errno == EINTR)
            continue;
        else if(connfd < 0)
        {
            /* something goes wrong -- kill the server */
            fprintf(stderr,"failed to accept connection\r\n");
            exit(1);
        }
        
        /* write the log */
        bzero(buffer, MAXBUF + 1);
        fprintf(stdout, "connection from %s, port %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, buffer, sizeof(buffer)),
            ntohs(cliaddr.sin_port));
        
        /* fork a child process to handle the request, while the main process waits for next connection*/
        childpid = fork();
        
        /* child process */
        if (childpid == 0 )
        {
            /* receive for request */
            bzero(buffer, MAXBUF + 1);
            if ((len = recv(connfd, buffer, MAXBUF, 0)) > 0)
            {
                /* make connfd into a FILE */
                FILE *clientFp = fdopen(connfd, "w");
                
                /* something goes wrong */
                if (clientFp == NULL)
                {
                    fprintf(stderr,"failed to make connfd into a FILE descriptor\r\n");
                    exit(1);
                }
                else
                {
                    /* filter out the request info */
                    char req[MAXPATH + 1] = "";
                    sscanf(buffer, "GET %s HTTP", req);
                    
                    /* write the log */
                    fprintf(stdout,"request for %s\r\n", req);
                    
                    /* handle the request */
                    processReq(clientFp, req, serverIp, argv[1]);
                    
                    fclose(clientFp);
                }
            }
            exit(0);
        }
        
        close(connfd);
    }
    
}

