#include #include #include #include #include #include #include #include #include #include #include #include const char *BASE_DIR = "/tmp/clicko"; const char *BASE_URL = "http://woozle.org/l.cgi"; #define POST_MAX 1024 /* * CGI */ static int is_cgi = 0; static char **argv = NULL; static int read_char_argv() { static int arg = 0; static char *p; if (NULL == argv) { return EOF; } if (0 == arg) { arg = 1; p = argv[1]; } if (! p) { return EOF; } else if (! *p) { arg += 1; p = argv[arg]; return '&'; } return *(p++); } static int read_char_stdin() { static int inlen = -1; if (-1 == inlen) { char *p = getenv("CONTENT_LENGTH"); if (p) { inlen = atoi(p); if (inlen > POST_MAX) { inlen = POST_MAX; } if (inlen < 0) { inlen = 0; } } else { inlen = 0; } } if (inlen) { inlen -= 1; return getchar(); } return EOF; } static int read_char_query_string() { static char *p = (char *)-1; if ((char *)-1 == p) { p = getenv("QUERY_STRING"); } if (! p) { return EOF; } else if (! *p) { return EOF; } else { return *(p++); } } static int (* read_char)() = read_char_argv; int cgi_init(char *global_argv[]) { char *rm = getenv("REQUEST_METHOD"); if (! rm) { read_char = read_char_argv; argv = global_argv; } else if (0 == strcmp(rm, "POST")) { read_char = read_char_stdin; is_cgi = 1; } else if (0 == strcmp(rm, "GET")) { read_char = read_char_query_string; is_cgi = 1; } else { printf(("405 Method not allowed\r\n" "Allow: GET, POST\r\n" "Content-type: text/plain\r\n" "\r\n" "%s is not allowed.\n"), rm); return -1; } return 0; } static char tonum(int c) { if ((c >= '0') && (c <= '9')) { return c - '0'; } if ((c >= 'a') && (c <= 'f')) { return 10 + c - 'a'; } if ((c >= 'A') && (c <= 'F')) { return 10 + c - 'A'; } return 0; } static char read_hex() { int a = read_char(); int b = read_char(); return tonum(a)*16 + tonum(b); } /* Read a key or a value. Since & and = aren't supposed to appear outside of boundaries, we can use the same function for both. */ size_t cgi_item(char *str, size_t maxlen) { int c; size_t pos = 0; while (1) { c = read_char(); switch (c) { case EOF: case '=': case '&': str[pos] = '\0'; return pos; case '%': c = read_hex(); break; case '+': c = ' '; break; } if (pos < maxlen - 1) { str[pos] = c; pos += 1; } } } void cgi_head(char *title) { if (is_cgi) { printf("Content-type: text/html\r\n\r\n"); } printf(("\n" "\n" " %s\n" " \n" "

%s

\n"), title, title); } void cgi_foot() { printf("\n" " \n" "\n"); } void cgi_result(int code, char *desc, char *fmt, ...) { va_list ap; if (is_cgi) { printf("%d %s\r\n", code, desc); } cgi_head(desc); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); cgi_foot(); exit(0); } void cgi_page(char *title, char *fmt, ...) { va_list ap; cgi_head(title); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); cgi_foot(); exit(0); } void cgi_error(char *text) { cgi_result(500, "Internal error", "

%s

", text); } static FILE * open_file(char *shorty, char *mode) { char fn[256]; FILE *f; snprintf(fn, sizeof fn, "%s/%s.url", BASE_DIR, shorty); f = fopen(fn, mode); if (! f) { cgi_error("Unable to open database"); } return f; } void shorten_url() { char u[4096]; size_t ulen; char shorty[16]; ulen = cgi_item(u, sizeof u); if (ulen == 0) { cgi_error("No URL specified"); } snprintf(shorty, sizeof shorty, "%08x.%04x", (unsigned int)time(NULL), getpid()); /* Put the URL into a file */ { FILE *f = open_file(shorty, "w"); fprintf(f, "%s", u); fclose(f); } /* Report back */ printf("Content-type: text/plain\r\n\r\n"); printf("%s/%s\n", BASE_URL, shorty); } void redirect(char *pi) { int i; if (! pi) { cgi_error("No short URL provided"); } if (pi[0] != '/') { cgi_error("Invalid PATH_INFO"); } pi += 1; for (i = 0; pi[i]; i += 1) { if ((! isalnum(pi[i])) && (pi[i] != '.')) { cgi_error("Bad short URL"); } } /* Open file */ { char u[4096]; FILE *f = open_file(pi, "r"); fgets(u, sizeof u, f); fclose(f); printf("Location: %s\r\n", u); } } void list_links() { char g[256]; glob_t globbuf; int i; snprintf(g, sizeof g, "%s/*.url", BASE_DIR); glob(g, 0, NULL, &globbuf); cgi_head("Clicko history"); printf("
    \n"); for (i = globbuf.gl_pathc - 1; i >= 0; i -= 1) { FILE *f = fopen(globbuf.gl_pathv[i], "r"); char url[4096]; fgets(url, sizeof url, f); fclose(f); printf("
  • %s
  • \n", url, url); } printf("
\n"); cgi_foot(); } int main(int argc, char *argv[]) { if (-1 == cgi_init(argv)) { fprintf(stderr, "Unable to initialize CGI.\n"); return -1; } while (1) { char key[12]; size_t klen; klen = cgi_item(key, sizeof key); if (klen == 0) { break; } switch (key[0]) { case 'u': shorten_url(); return 0; } } { char *pi = getenv("PATH_INFO"); if (pi) { redirect(pi); } } list_links(); return 0; }