#include #include #include #include #include "buffer.h" #include "http-parse.h" int whitespacep (const char *s) { for (; *s; ++s) { if (!isspace (*s)) return 0; } return 1; } int http_read_char (struct http_source *source) { if (((source->length >= 0) && (source->index >= source->length)) || (source->index >= MAX_CONTENT_LENGTH)) return EOF; source->index++; if (source->fp) return (getc (source->fp)); else if (source->string) return (source->string[source->index - 1]); } void http_unread_char (int c, struct http_source *source) { if ((c != EOF) && (source->index > 0)) { source->index--; if (source->fp) ungetc (c, source->fp); } } void http_write_char (int c, struct http_sink *sink) { if (sink->fp) putc (c, sink->fp); else buffer_addch (sink->b, c); } void http_write_string (const char *string, struct http_sink *sink) { if (sink->fp) fputs (string, sink->fp); else buffer_addstr (sink->b, string); } void http_write_substring (const char *string, int length, struct http_sink *sink) { if (sink->fp) fwrite ((void *) string, 1, length, sink->fp); else buffer_add (sink->b, string, length); } int maybe_read_crlf (struct http_source *source) { int c = http_read_char (source); if (c == '\r') { c = http_read_char (source); if (c == '\n') return 1; else { http_unread_char (c, source); return 0; } } else { http_unread_char (c, source); return 0; } } int http_read_line (struct http_source *source, struct buffer *b) { buffer_reset (b); while (1) { int c = http_read_char (source); if (c == EOF) return 0; else if ((c != ' ') && (c != '\t')) { http_unread_char (c, source); break; } } while (1) { int c = http_read_char (source); if (c == EOF) return 0; else if ((c == '\r') && ((c = http_read_char (source)) == '\n')) { c = http_read_char (source); if ((c == ' ') || (c == '\t')) buffer_addch (b, c); else { http_unread_char (c, source); while ((b->index > 0) && isspace (b->data[b->index - 1])) b->data[--b->index] = '\0'; return 1; } } else buffer_addch (b, c); } } int read_token (struct http_source *source, struct buffer *b) { while (1) { int c = http_read_char (source); buffer_reset (b); if (c == EOF) return EOF; else if (c == ';') { buffer_addch (b, c); return SEMICOLON; } else if (c == ':') { buffer_addch (b, c); return COLON; } else if (c == '=') { buffer_addch (b, c); return EQUALS; } else if (c == ',') { buffer_addch (b, c); return COMMA; } else if (c == '"') { while ((c = http_read_char (source)) != '"') { if (c == EOF) return EOF; else if (c == '\\') { c = http_read_char (source); if (c == EOF) return EOF; /* Internet Explorer doesn't escape the backslash character itself. Sigh. */ else if (c != '"') buffer_addch (b, '\\'); } else if (c == '\r') { c = http_read_char (source); if (c == '\n') { c = http_read_char (source); if ((c != ' ') && (c != '\t')) { /* Not supposed to happen. */ http_unread_char (c, source); buffer_reset (b); buffer_addstr (b, "\r\n"); return EOL; } } else { http_unread_char (c, source); continue; } } buffer_addch (b, c); } return QUOTE; } else if (isspace (c)) { if (c == '\r') { c = http_read_char (source); if (c == '\n') { c = http_read_char (source); if ((c == ' ') || (c == '\t')) continue; else { http_unread_char (c, source); buffer_addstr (b, "\r\n"); return EOL; } } else http_unread_char (c, source); } } else { while (isalnum (c) || (c == '-') || (c == '/') || (c == '.') || (c == '_') || (c == '*') || (c == '+')) { buffer_addch (b, c); c = http_read_char (source); } http_unread_char (c, source); return ((b->index > 0) ? WORD : EOF); } } } int match_boundary (const char *prefix, const char *boundary, struct http_source *source, struct http_sink *sink, int *final_boundary_p) { int c; int i; *final_boundary_p = 0; c = http_read_char (source); if (c != '-') { http_write_string (prefix, sink); http_unread_char (c, source); return 0; } c = http_read_char (source); if (c != '-') { http_write_string (prefix, sink); http_write_char ('-', sink); http_unread_char (c, source); return 0; } for (i = 0; boundary[i] != '\0'; i++) { c = http_read_char (source); if ((c == EOF) || (c != boundary[i])) { http_write_string (prefix, sink); http_write_string ("--", sink); if (i > 0) http_write_substring (boundary, i, sink); http_unread_char (c, source); return 0; } } /* Check for final boundary. */ c = http_read_char (source); if (c == '-') { c = http_read_char (source); if (c == '-') { *final_boundary_p = 1; c = http_read_char (source); } else { http_unread_char (c, source); c = '-'; } } /* Try to recognize a line terminator. If there's something after the boundary besides EOL or EOF then it's not a valid boundary. */ if (c == EOF) { *final_boundary_p = 1; return 1; } else if (c == '\r') { c = http_read_char (source); if (c != '\n') { http_unread_char (c, source); c = '\r'; } } if (c == '\n') return 1; else { http_write_string (prefix, sink); http_write_string ("--", sink); http_write_string (boundary, sink); if (*final_boundary_p) http_write_string ("--", sink); http_write_char (c, sink); return 0; } } #define EXPECT(test) \ do { \ token = read_token (source, &b); \ if (!(test)) \ goto error; \ } while (0) object parse_multipart (struct http_source *source, const char *boundary) { struct buffer b; struct http_sink sink; object alist = Cnil; int final_boundary_p = 0; int c; int token; buffer_init (&b, 128); source->index = 0; sink.b = &b; sink.fp = NULL; while (1) { c = http_read_char (source); if (c == EOF) break; else if (!isspace (c)) { http_unread_char (c, source); break; } } /* Match the initial boundary. */ if (!match_boundary ("", boundary, source, &sink, &final_boundary_p)) goto error; while (!final_boundary_p) { object name = Cnil; object filename = Cnil; object pathname = Cnil; object content_type = Cnil; if (sink.fp) { fclose (sink.fp); sink.fp = NULL; } while (!maybe_read_crlf (source)) { int hdr = -1; EXPECT (token == WORD); if (strcasecmp (b.data, "content-type") == 0) hdr = CONTENT_TYPE; else if (strcasecmp (b.data, "content-disposition") == 0) hdr = CONTENT_DISPOSITION; EXPECT (token == COLON); if (hdr != -1) { EXPECT (token == WORD); switch (hdr) { case CONTENT_TYPE: content_type = make_simple_string (b.data); break; case CONTENT_DISPOSITION: if (strcasecmp (b.data, "form-data") != 0) { fprintf (stderr, "can only handle form-data\n"); goto error; } break; } EXPECT ((token == EOL) || (token == SEMICOLON)); if (token == SEMICOLON) { while (token != EOL) { int parameter_type = -1; EXPECT (token == WORD); if (hdr == CONTENT_DISPOSITION) { if (strcasecmp (b.data, "name") == 0) parameter_type = 0; else if (strcasecmp (b.data, "filename") == 0) parameter_type = 1; } EXPECT (token == EQUALS); EXPECT ((token == WORD) || (token == QUOTE)); if (!whitespacep (b.data)) { switch (parameter_type) { case 0: name = make_simple_string (b.data); break; case 1: { char *end = b.data + b.index; char *p = end; while ((p > b.data) && (*(p - 1) != '/') && (*(p - 1) != '\\')) --p; if (p < end) filename = make_simple_string (p); break; } } } EXPECT ((token == EOL) || (token == SEMICOLON)); } } } else http_read_line (source, &b); } if (filename != Cnil) { int fd; buffer_reset (&b); buffer_addstr (&b, "/tmp/HTTP-POST-XXXXXX"); fd = mkstemp (b.data); if (fd == -1) { fprintf (stderr, "%s: cannot open\n", b.data); goto error; } pathname = make_simple_string (b.data); sink.fp = fdopen (fd, "wb"); if (!sink.fp) { close (fd); fprintf (stderr, "%s: cannot open\n", b.data); goto error; } } buffer_reset (&b); if (!match_boundary ("", boundary, source, &sink, &final_boundary_p)) { while ((c = http_read_char (source)) != EOF) { if (c == '\r') { c = http_read_char (source); if (c == '\n') { if (match_boundary ((filename != Cnil) ? "\r\n" : "\n", boundary, source, &sink, &final_boundary_p)) break; } else { http_write_char ('\r', &sink); http_unread_char (c, source); } } else http_write_char (c, &sink); } } if (filename != Cnil) alist = make_cons (list (4, name, filename, pathname, content_type), alist); else alist = make_cons (make_cons (name, whitespacep (b.data) ? Cnil : make_simple_string (b.data)), alist); } cleanup: if (sink.fp) fclose (sink.fp); free (b.data); return alist; error: alist = Cnil; goto cleanup; } int hex_digit_to_int (int c) { if ((c >= '0') && (c <= '9')) return (c - '0'); else if ((c >= 'A') && (c <= 'F')) return (10 + (c - 'A')); else if ((c >= 'a') && (c <= 'f')) return (10 + (c - 'a')); else return -1; } object parse_parameters (struct http_source *source) { struct buffer b; int c, x, y; object alist = Cnil; object name = Cnil; source->index = 0; buffer_init (&b, 128); while ((c = http_read_char (source)) != EOF) { if (c == '+') buffer_addch (&b, ' '); else if (isalnum (c) || (c == '.') || (c == '-') || (c == '_') || (c == '@') || (c == '*')) buffer_addch (&b, c); else if (c == '%') { c = http_read_char (source); if (c == EOF) break; x = hex_digit_to_int (c); c = http_read_char (source); if (c == EOF) break; y = hex_digit_to_int (c); if ((x >= 0) && (y >= 0)) { c = (x << 4) | y; if ((c == '\n') && (b.index > 0) && (b.data[b.index - 1] == '\r')) b.index--; buffer_addch (&b, c); } } else if (isspace (c)) ; /* Just skip it. */ else { if (name != Cnil) { alist = make_cons (make_cons (name, whitespacep (b.data) ? Cnil : make_simple_string (b.data)), alist); name = Cnil; } else if (!whitespacep (b.data)) name = make_simple_string (b.data); buffer_reset (&b); } } if (name != Cnil) alist = make_cons (make_cons (name, whitespacep (b.data) ? Cnil : make_simple_string (b.data)), alist); free (b.data); return alist; } object decode_alist (object string) { if (string != Cnil) { struct http_source source; source.fp = NULL; source.string = string->st.st_self; source.length = string->st.st_fillp; return (parse_parameters (&source)); } else return Cnil; } object parse_post_request (void) { object alist = Cnil; struct http_source source; const char *content_type; const char *content_length; struct buffer b; int token; int len; content_type = getenv ("CONTENT_TYPE"); if (!content_type) return; content_length = getenv ("CONTENT_LENGTH"); if (!content_length) return; len = atoi (content_length); if ((len <= 0) || (len > MAX_CONTENT_LENGTH)) { fprintf (stderr, "Unacceptable content length\n"); return; } buffer_init (&b, 32); source.fp = NULL; source.string = content_type; source.length = strlen (content_type); source.index = 0; token = read_token (&source, &b); if ((token == WORD) && (strcasecmp (b.data, "multipart/form-data") == 0)) { while (1) { token = read_token (&source, &b); if ((token == WORD) && (strcasecmp (b.data, "boundary") == 0)) break; if ((token == EOL) || (token == EOF)) goto cleanup; } if ((token = read_token (&source, &b)) != EQUALS) goto cleanup; if (((token = read_token (&source, &b)) != WORD) && (token != QUOTE)) goto cleanup; source.fp = stdin; source.string = NULL; source.length = len; alist = parse_multipart (&source, b.data); } else { source.fp = stdin; source.string = NULL; source.length = len; alist = parse_parameters (&source); } cleanup: free (b.data); return alist; }