/* __ _ * ____ / /_ ____ ___ __ ___________ _(_) * / __ \/ __ \/ __ `__ \/ / / / ___/ __ `/ / * / /_/ / / / / / / / / / /_/ / /__/ /_/ / / * \____/_/ /_/_/ /_/ /_/\__, /\___/\__, /_/ * /____/ /____/ * * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2021, Max Christian Pohle * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * {{{ DISCLAIMER * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * }}} */ #include "main.h" #include static inline char * handle_colon(char ** start, char ** end, char ** search) { // HTTP headers and content are separated by an empty line (which is two // newlines), but before that there are pairs of names and values, separated // by a colon, e.g. Content-Type: text/html and this function is called, when // *end reaches a colon. *end[0] = '\0'; // remember header 'names' and search for the value (*end)++; // jump over the colon if (0 == strcasecmp("Content-Type", *start)) { *search = "\r\n;"; // (more unlikely) also search for a semicolon in Content-Type: [...]; boundary=[...] } else { *search = "\r\n"; // (likely) search for some kind of newline } return *start; // remember, where name starts, will be important in the newline case } static inline char * handle_semicolon(char ** start, char ** end, char ** search) { // find the form-data boundary in the main header *start += strspn(*start, "; "); // remove spaces and semicolons (boundary check implicit; also stops at '\0') const char s_multipart_form_data[] = "boundary="; if(0 < strcasecmp(*start, s_multipart_form_data)) { *end = *end + sizeof(s_multipart_form_data) + 1; *end += strspn(*end, "-"); *start = *end; *end += strcspn(*end, "\r\n "); *end[0] = '\0'; DEBUG("> Boundary found: %s\n", *start); } *search = "\r\n"; // do not search further semicolons return *start; } void parse_http(size_t new_socket, char * request, size_t request_length) { // this http parser modifies the input buffer and replaces single characters // with \0-chars to terminate them, but it does not copy strings, because // that would require tedious length checks and would also allow the user // to submit forms with unreasonable long strings in strange places or to // make it shorter: it would make boundary checks necessary for every single // heaer name, value, sub-value and content. This implementation is more // generic and only requires content length bounary checks. char * start = request; char * end = "\0"; char * search = "\r\n"; Http_Header http_header = {0}; request[request_length] = '\0'; // {{{ Parse the request line (e.g. "POST / HTTP/1.1") http_header.method = start; start += strcspn(start, " "); start[0] = '\0'; http_header.url = ++start; start += strcspn(start, " "); start[0] = '\0'; http_header.protocol = ++start; start += strcspn(start, "\r\n"); http_header.newline_length = strspn(start, "\r\n"); start[0] = '\0'; start += http_header.newline_length; DEBUG("%% Request line: method='%s' url='%s' protocol='%s' newline_length=%ld\n", http_header.method, http_header.url, http_header.protocol, http_header.newline_length); // }}} char * name = NULL; while(NULL != (end = strpbrk(start, search))) { // TODO: try harder to break things (are SEGFAULTs possible?) size_t matchlen = strspn(end, search); switch(end[0]) { case ':': name = handle_colon(&start, &end, &search); break; case ';': http_header.boundary = handle_semicolon(&start, &end, &search); http_header.boundary_size = end - start; break; case '\r': // fallthrough case '\n': end[0] = '\0'; search = ":"; // we will continue to search for headers // {{{ NAME AND VALUE OF A HEADER FIELD KNOWN if(NULL != name) { DEBUG("\033[32m[%ld]> '% 20s' = '%s'\033[0m\n", matchlen, name, start); if (0 == strcasecmp("Content-Disposition", name)) { http_header.content_disposition = start; } } // }}} // {{{ END OF A HTTP HEADER (two newlines) if(matchlen > http_header.newline_length) { end += matchlen; // go behind the double line break... DEBUG("%% END HEADERS, because there were %d newlines; boundary='%s'[%ld]\n", matchlen / http_header.newline_length, http_header.boundary, http_header.boundary_size ); start = end; for(; ; ) { size_t size_remaining = (size_t) request_length - (end - request) - 1; DEBUG("%ld remaining.\n", size_remaining); matchlen = strspn(end, "-"); if(0 == strncmp(end + matchlen, http_header.boundary, http_header.boundary_size)) { size_t file_size = end - http_header.newline_length - start; DEBUG("> Content ends here, size of the last file is %ld\n", file_size); start[file_size + 1] = '\0'; next_part(&http_header, start, file_size); end += http_header.boundary_size + matchlen; matchlen = http_header.newline_length; DEBUG("> end is at %p, matchlen is %ld\n", end, matchlen); search = ":"; break; } else { if(!http_header.boundary || size_remaining <= 0 || NULL == (end = memchr((void*) end, '-', size_remaining))) { DEBUG("EOF reached or no further '-' found\n"); break; } // end++; } } } // }}} if condition after a header break; default: DEBUG("Unknown char found: '%c'\n ", end[0]); break; } // switch if(NULL == end) break; else start = end + matchlen; } // failed to find URL? Use a default to avoid NULL pointer exceptions later if(!http_header.url || http_header.url[0] == '\0' || http_header.url[1] == '\0') { http_header.url = "/index.html"; DEBUG("Warning: Request had no URL and is probably invalid: %d", http_header.url[0]); } DEBUG("> sending answer...\n"); FILE * f = fdopen((size_t) new_socket, "w"); send_answer(&http_header, f); fflush(f); fclose(f); DEBUG("> answer sent.\n"); } // modeline for vim: shiftwidth=2 tabstop=2 number foldmethod=marker