/*
   multimedia pastebin cgi script
   Copyright (C) 2010 Johannes Schauer <josch@pyneo.org>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License 3
   as published by the Free Software Foundation.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   For a copy of the GNU Affero General Public License see
   <http://www.gnu.org/licenses/>.
   */

/*
   this script is written as a cgi for a chrooted thttpd setup
   to statically build it with required libs compiled in do:
   gcc -static -Wall -o paste paste.c /usr/lib/libmagic.a /usr/lib/libz.a
   */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <magic.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>

#define MAX_UPLOAD 8388608
#define MAGIC_FILE "magic.mgc"

const char base64[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
    'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
    'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
    'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
    'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
    'W', 'X', 'Y', 'Z', '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', '-', '_' };

const char default_msg[] =
"<style> a { text-decoration: none } </style>\n"
"<pre>\n"
"mister-muffin(1)                 MISTER-MUFFIN                 mister-muffin(1)\n"
"\n"
"<b>NAME</b>\n"
"       mister-muffin - multimedia command line pastebin\n"
"\n"
"<b>SYNOPSIS</b>\n"
"       &lt;command&gt; | curl -F 'arg=&lt;-' http://mister-muffin.de/paste\n"
"\n"
"<b>DESCRIPTION</b>\n"
"       Inspired by <a href=\"http://sprunge.us\"><b>sprunge</b>(1)</a> but being more powerful and only lacking\n"
"       syntax highlighting and the cool domain, this pastebin allows to upload\n"
"       files of any kind and adds a proper filename extension to filetypes\n"
"       known to it.\n"
"\n"
"       The maximum allowed Content-Length is set to 8388608 bytes.\n"
"\n"
"<b>FILES</b>\n"
"       <a href=\"http://mister-muffin.de/magic.mgc\">/magic.mgc</a>\n"
"              magic file used by this script to identify filetypes\n"
"\n"
"<b>EXAMPLES</b>\n"
"       ~$ cat bin/ching | curl -F 'arg=&lt;-' http://mister-muffin.de/paste\n"
"          <a href=\"http://mister-muffin.de/p/b33f\">http://mister-muffin.de/p/b33f</a>\n"
"       ~$ firefox <a href=\"http://mister-muffin.de/p/b33f\">http://mister-muffin.de/p/b33f</a>\n"
"\n"
"<b>WWW</b>\n"
"       <a href=\"http://mister-muffin.de/p/b33f\">http://mister-muffin.de/p/b33f</a>\n"
"              the AGPL3 c program source\n"
"       <a href=\"http://mister-muffin.de/post.htm\">http://mister-muffin.de/post.htm</a>\n"
"              a webinterface for non curl users\n"
"\n"
"<b>AUTHORS</b>\n"
"       Johannes 'josch' Schauer &lt;josch at pyneo.org&gt\n"
"\n"
"<b>COPYRIGHT</b>\n"
"       Distributed under the terms of the GNU Affero General Public License 3\n"
"\n"
"<b>SEE ALSO</b>\n"
"       <a href=\"http://sprunge.us\"><b>sprunge</b>(1)</a>\n"
"\n"
"                                  12 Feb 2010                  mister-muffin(1)\n"
"</pre>\n";

static void print_default() {
    printf("Content-Type: text/html; charset=UTF-8\n\n");
    printf("%s", default_msg);
    exit(0);
}

static void error(const char* format, ...) {
    va_list argptr;
    va_start(argptr, format);
    printf("Content-Type: text/plain; charset=UTF-8\n\n");
    vfprintf(stdout, format, argptr);
    va_end(argptr);
    exit(0);
}

static char* get_ext(char *buffer, int len) {
    const char *mime;
    int ret;
    char *ext;
    magic_t cookie = magic_open(MAGIC_MIME_TYPE);
    if (cookie == NULL) {
        error("cannot magic_open: %s", strerror(errno));
    }
    ret = magic_load(cookie, MAGIC_FILE);
    if (ret != 0) {
        error("cannot magic_load: %s", magic_error(cookie));
    }
    mime = magic_buffer(cookie, buffer, len);

    if (mime == NULL) {
        fprintf(stderr, "magic == NULL :(\n");
        magic_close(cookie);
        return "";
    }

    if(!strcmp(mime, "image/png"))
        ext = ".png";
    else if(!strcmp(mime, "image/jpeg"))
        ext = ".jpg";
    else if(!strcmp(mime, "image/bmp"))
        ext = ".bmp";
    else if(!strcmp(mime, "image/gif"))
        ext = ".gif";
    else if(!strcmp(mime, "image/tiff"))
        ext = ".tiff";
    else if(!strcmp(mime, "audio/midi"))
        ext = ".mid";
    else if(!strcmp(mime, "audio/mpeg"))
        ext = ".mp3";
    else if(!strcmp(mime, "application/pdf"))
        ext = ".pdf";
    else if(!strcmp(mime, "application/postscript"))
        ext = ".ps";
    else if(!strcmp(mime, "application/octet-stream"))
        ext = ".bin";
    else if(!strcmp(mime, "application/zip"))
        ext = ".zip";
    else if(!strcmp(mime, "application/x-dvi"))
        ext = ".dvi";
    else if(!strcmp(mime, "application/x-javascript"))
        ext = ".js";
    else if(!strcmp(mime, "application/x-latex"))
        ext = ".tex";
    else if(!strcmp(mime, "application/x-ogg"))
        ext = ".ogg";
    else if(!strcmp(mime, "application/x-bittorrent"))
        ext = ".torrent";
    else if(!strcmp(mime, "video/mp4"))
        ext = ".mp4";
    else if(!strcmp(mime, "video/quicktime"))
        ext = ".mov";
    else if(!strcmp(mime, "video/mpeg"))
        ext = ".mpeg";
    else if(!strcmp(mime, "video/x-msvideo"))
        ext = ".avi";
    else if(!strcmp(mime, "text/plain"))
        ext = ".txt";
    else if(!strcmp(mime, "text/css"))
        ext = ".css";
    else if(!strcmp(mime, "text/xml"))
        ext = ".xml";
    else if(!strcmp(mime, "text/html"))
        ext = ".html";
    else
        ext = "";
    magic_close(cookie);
    return ext;
}

static char* get_random_str(int n) {
    int i = 4;
    char *r;
    r = (char *)malloc((i+1)*sizeof(char));
    r[i] = '\0';
    for(;i>0;i--) {
        r[i-1] = base64[(n>>(6*(i-1)))&0x3f];
    }
    return r;
}

static void write_postdata_to_file(int len, char **filename) {
    FILE *to;
    char buf[8192];
    char *end_of_delim;
    char *end_of_header;
    int len_header, len_delim;
    int r = 0;

    // read one first block	
    if(len > sizeof(buf)) {
        fread(buf, sizeof(char), sizeof(buf), stdin);
    } else {
        fread(buf, sizeof(char), len, stdin);
    }

    // read delimiter
    if((end_of_delim = strstr(buf, "\r\n")) == NULL)
        error("Bad Request - no delimiter\n");
    else
        end_of_delim += 2;
    len_delim = end_of_delim - buf + 4;

    // read content-disposition
    if((end_of_header = strstr(end_of_delim, "\r\n\r\n")) == NULL)
        error("Bad Request - no content disposition\n");
    else
        end_of_header += 4;
    len_header = end_of_header - buf;

    *filename = (char *)malloc(39*sizeof(char*));
    srand(time(NULL));
    do {
        r = rand();
        if(len > sizeof(buf))
            sprintf(*filename, "p/%s%s", get_random_str(r),
                    get_ext(end_of_header, sizeof(buf)-len_header));
        else
            sprintf(*filename, "p/%s%s", get_random_str(r),
                    get_ext(end_of_header, len-len_header-len_delim));
    } while(!access(*filename, F_OK));
    to = fopen(*filename, "w+");

    if (!to)
        error("could not open output file\n");

    // write real content to file
    if(len > sizeof(buf)) {
        // write the chunk of data we just read but without the header
        fwrite(end_of_header, sizeof(char), sizeof(buf)-len_header, to);
        len -= sizeof(buf)+len_delim;

        // read all the other blocks
        while(len > sizeof(buf)) {
            fread(buf, sizeof(char), sizeof(buf), stdin);
            fwrite(buf, sizeof(char), sizeof(buf), to);
            len -= sizeof(buf);
        }

        // read last block
        fread(buf, sizeof(char), len, stdin);
        fwrite(buf, sizeof(char), len, to);
        fclose(to);

        // read the delimeter at the end but dont write it
        fread(buf, sizeof(char), len_delim, stdin);
    } else {
        // read all content
        fwrite(end_of_header, sizeof(char), len-len_header-len_delim, to);
        fclose(to);
    }
}

int main(int argc, char** argv) {
    char *clen;
    int input_len;
    char *filename;

    if (access(MAGIC_FILE, F_OK))
        error("magic file "MAGIC_FILE" not accessible\n");

    if((clen = getenv("CONTENT_LENGTH")) == NULL) {
        print_default();
    }
    input_len = atoi(clen);
    if(input_len == 0) {
        print_default();
    }
    if(input_len > MAX_UPLOAD)
        error("Request Entity Too Large\n");

    write_postdata_to_file(input_len, &filename);

    printf("Content-Type: text/plain; charset=UTF-8\n\n");
    printf("http://mister-muffin.de/%s\n", filename);
    return 0;
}
