From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on gnuweeb.org X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,NO_DNS_FOR_FROM,URIBL_BLOCKED autolearn=no autolearn_force=no version=3.4.6 Received: from integral2.. (unknown [36.81.65.188]) by gnuweeb.org (Postfix) with ESMTPSA id 860E97E312; Fri, 8 Jul 2022 12:10:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gnuweeb.org; s=default; t=1657282250; bh=aWp9DT8elxqdj1kvXBPWFEFc3x5DP7GT589LxlGHaTw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OOgXr9i/BZxHMMjyxA7NmsVfXGsYMiSs2pui8Yaf1bpITnGbOWG9dhtmAM/ybolAj HXJMhozUek+Y8528iGsSCKOL3yMVU4iPXfcxoHHXQal5uAHEWVR5OJsj+AKT0N9pw4 4SIQccUvM6r2EZw55ip1Jyvz9cO68RDTGF+b2tJ5updX5LTTAq7n9CRu6xXqomafyu vuabn+PZd5wXJ+FAWsuCtVrusE+o82pRP8FxtNw/RVI8jh1t9Ht4u0FzLSJ/jn7wV5 mdvTBPkQmN+T97++GgUBL+1volxZVjKw6/HLzOSPi9QkDon8B+XoSZ0B4UVY4ctOXe 1bsJ6BRGGGw3w== From: Ammar Faizi To: GNU/Weeb Mailing List Cc: Ammar Faizi , Alviro Iskandar Setiawan , Arthur Lapz , Fernanda Ma'rouf , Sprite , Yonle Subject: [PATCH gwhttpd 05/14] gwhttpd: Refactor HTTP header parser Date: Fri, 8 Jul 2022 19:10:16 +0700 Message-Id: <20220708121025.926162-6-ammarfaizi2@gnuweeb.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220708121025.926162-1-ammarfaizi2@gnuweeb.org> References: <20220708121025.926162-1-ammarfaizi2@gnuweeb.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: The HTTP header parser was prone and didn't handle many edge cases. This is a full refactor of HTTP header parser. While in there, add more HTTP method supports. Signed-off-by: Ammar Faizi --- gwhttpd.cpp | 183 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 136 insertions(+), 47 deletions(-) diff --git a/gwhttpd.cpp b/gwhttpd.cpp index ce280de..fbe219d 100644 --- a/gwhttpd.cpp +++ b/gwhttpd.cpp @@ -30,7 +30,10 @@ enum http_method { HTTP_GET, - HTTP_POST + HTTP_POST, + HTTP_PATCH, + HTTP_PUT, + HTTP_DELETE, }; struct client_sess { @@ -39,6 +42,7 @@ struct client_sess { char buf[4096]; enum http_method method; char *uri; + char *qs; char *http_ver; char *body; bool got_http_header; @@ -305,48 +309,6 @@ static int handle_new_client(struct server_state *state) return 0; } -static int parse_http_header(struct client_sess *sess) -{ - char *tmp; - - /* - * CRLF = Carriage Return, Line Feed. - */ - tmp = strstr(sess->buf, "\r\n\r\n"); - if (!tmp) - return 0; - - sess->body = tmp + 4; - if (!strncmp(sess->buf, "GET", sizeof("GET") - 1)) { - sess->method = HTTP_GET; - sess->uri = &sess->buf[sizeof("GET")]; - } else if (!strncmp(sess->buf, "POST", sizeof("POST") - 1)) { - sess->method = HTTP_POST; - sess->uri = &sess->buf[sizeof("POST")]; - } else { - return -EBADMSG; - } - - sess->http_ver = strstr(sess->uri, " "); - if (!sess->http_ver) - return -EBADMSG; - - sess->http_ver[0] = '\0'; - tmp = strstr(++sess->http_ver, "\r\n"); - tmp[0] = '\0'; - sess->got_http_header = true; - return 0; -} - -static void close_sess(struct client_sess *sess, struct server_state *state) -{ - printf("Closing session " FCIP " (idx = %u)\n", FCIP_ARG(sess), - sess->idx); - delete_client(state->epl_fd, sess); - close(sess->fd); - put_sess_idx(sess->idx, state); -} - static ssize_t send_to_client(struct client_sess *sess, const char *buf, size_t len) { @@ -384,6 +346,128 @@ static void send_http_error(int code, struct client_sess *sess) send_to_client(sess, buf, (size_t)tmp); } +static char *parse_http_method(char *buf, enum http_method *method_p) +{ + if (!strncmp("GET ", buf, sizeof("GET"))) { + *method_p = HTTP_GET; + return buf + sizeof("GET"); + } + + if (!strncmp("POST ", buf, sizeof("POST"))) { + *method_p = HTTP_POST; + return buf + sizeof("POST"); + } + + if (!strncmp("PUT ", buf, sizeof("PUT"))) { + *method_p = HTTP_PUT; + return buf + sizeof("PUT"); + } + + if (!strncmp("PATCH ", buf, sizeof("PATCH"))) { + *method_p = HTTP_PATCH; + return buf + sizeof("PATCH"); + } + + if (!strncmp("DELETE ", buf, sizeof("DELETE"))) { + *method_p = HTTP_DELETE; + return buf + sizeof("DELETE"); + } + + return NULL; +} + +static char *parse_query_string(char *uri, char *end_uri) +{ + while (uri < end_uri) { + if (*uri++ != '?') + continue; + if (uri == end_uri) + /* + * We got an empty query string: + * "http://somehwere.com/path?" + */ + return NULL; + return uri; + } + return NULL; +} + +static int parse_http_header(struct client_sess *sess) +{ + char *buf = sess->buf; + char *end; + char *ret; + + /* + * Split the HTTP header and HTTP body. + */ + ret = strstr(buf, "\r\n\r\n"); + if (!ret) { + /* + * If we fail here, we may got a partial packet. + * Don't fail here if still have enough buffer, + * we will wait for the next recv() iteration. + */ + if (sess->buf_size >= sizeof(sess->buf) - 1) + goto bad_req; + + return 0; + } + end = ret; + + /* + * The HTTP body is located right after "\r\n\r\n". + */ + sess->body = &ret[4]; + + ret = parse_http_method(buf, &sess->method); + if (unlikely(!ret)) + goto bad_req; + + /* + * Now @ret is pointing to URI. For example: + * + * "GET / HTTP/1.1" + * ^ + * @ret + */ + sess->uri = ret; + ret = strstr(sess->uri, " "); + if (unlikely(!ret)) + goto bad_req; + + ret[0] = '\0'; + if (unlikely(&ret[1] >= end)) + goto bad_req; + + sess->qs = parse_query_string(sess->uri, end); + ret = strstr(&ret[1], "HTTP/"); + if (unlikely(!ret)) + goto bad_req; + + sess->http_ver = ret; + ret = strstr(sess->http_ver, "\r\n"); + if (unlikely(!ret)) + goto bad_req; + + ret[0] = '\0'; + sess->got_http_header = true; + return 0; + +bad_req: + send_http_error(400, sess); + return -EBADMSG; +} + +static void close_sess(struct client_sess *sess, struct server_state *state) +{ + printf("Closing session " FCIP " (idx = %u)\n", FCIP_ARG(sess), + sess->idx); + delete_client(state->epl_fd, sess); + close(sess->fd); + put_sess_idx(sess->idx, state); +} + #define HTTP_200_HTML "HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n" #define HTTP_200_TEXT "HTTP/1.1 200\r\nContent-Type: text/plain\r\n\r\n" @@ -495,15 +579,20 @@ static int _handle_client(struct client_sess *sess, struct server_state *state) sess->buf[sess->buf_size] = '\0'; if (!sess->got_http_header) { ret = parse_http_header(sess); - if (ret) { - send_http_error(400, sess); - return 0; - } + if (ret) + goto out; if (!sess->got_http_header) return 0; } +#if 0 + printf("URI: %s\n", sess->uri); + printf("Query String: %s\n", sess->qs); + printf("HTTP version: %s\n", sess->http_ver); +#endif + ret = handle_route(sess, state); +out: if (ret) { close_sess(sess, state); if (likely(ret == -EBADMSG || ret == -ENETDOWN)) -- Ammar Faizi