| /*****************************************************************************\ |
| * http_con.c - handling HTTP connections |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm 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 General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include "slurm/slurm_errno.h" |
| |
| #include "src/common/http.h" |
| #include "src/common/http_con.h" |
| #include "src/common/http_mime.h" |
| #include "src/common/pack.h" |
| #include "src/common/read_config.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| #include "src/conmgr/conmgr.h" |
| #include "src/interfaces/http_parser.h" |
| |
| #define CRLF "\r\n" |
| #define MAX_BODY_BYTES 52428800 /* 50MB */ |
| #define MAX_STATUS_BYTES 1024 |
| #define MAX_HEADER_BYTES 80 |
| /* RFC7231-6.2 Informational 1xx */ |
| #define HTTP_STATUS_INFO_BEGIN 100 |
| #define HTTP_STATUS_INFO_END 199 |
| /* RFC7231-6.3 Successful 2xx */ |
| #define HTTP_STATUS_SUCCESS_BEGIN 200 |
| #define HTTP_STATUS_SUCCESS_END 299 |
| |
| #define MAGIC 0xab0a8aff |
| |
| typedef struct http_con_s { |
| int magic; /* MAGIC */ |
| /* reference to assigned connection */ |
| conmgr_fd_ref_t *con; |
| /* True to xfree() this pointer */ |
| bool free_on_close; |
| const http_con_server_events_t *events; |
| void *arg; /* arbitrary pointer from caller */ |
| http_parser_state_t *parser; /* http parser plugin state */ |
| http_con_request_t request; |
| } http_con_t; |
| |
| #define WRITE_EACH_HEADER_MAGIC 0xba3a8aff |
| |
| typedef struct { |
| int magic; /* WRITE_EACH_HEADER_MAGIC */ |
| int rc; |
| conmgr_fd_ref_t *con; |
| } write_each_header_args_t; |
| |
| static int _send_reject(http_con_t *hcon, slurm_err_t error_number); |
| |
| extern const size_t http_con_bytes(void) |
| { |
| return sizeof(http_con_t); |
| } |
| |
| static int _valid_http_version(uint16_t major, uint16_t minor) |
| { |
| if ((major == 0) && (minor == 0)) |
| return SLURM_SUCCESS; |
| |
| if ((major == 1) && ((minor == 0) || (minor == 1))) |
| return SLURM_SUCCESS; |
| |
| return ESLURM_HTTP_UNSUPPORTED_VERSION; |
| } |
| |
| static void _request_init(http_con_t *hcon) |
| { |
| http_con_request_t *request = &hcon->request; |
| |
| xassert(hcon->magic == MAGIC); |
| /* catch memory leak */ |
| xassert(!request->headers); |
| |
| *request = (http_con_request_t) { |
| .url = URL_INITIALIZER, |
| .method = HTTP_REQUEST_INVALID, |
| }; |
| |
| request->headers = list_create((ListDelF) free_http_header); |
| } |
| |
| static void _request_free_members(http_con_t *hcon) |
| { |
| http_con_request_t *request = &hcon->request; |
| |
| xassert(hcon->magic == MAGIC); |
| |
| url_free_members(&request->url); |
| FREE_NULL_LIST(request->headers); |
| xfree(request->accept); |
| xfree(request->content_type); |
| FREE_NULL_BUFFER(request->content); |
| } |
| |
| /* reset state of request */ |
| static void _request_reset(http_con_t *hcon) |
| { |
| xassert(hcon->magic == MAGIC); |
| |
| _request_free_members(hcon); |
| _request_init(hcon); |
| } |
| |
| static int _on_request(const http_parser_request_t *req, void *arg) |
| { |
| http_con_t *hcon = arg; |
| http_con_request_t *request = &hcon->request; |
| int rc = EINVAL; |
| |
| xassert(hcon->magic == MAGIC); |
| |
| request->http_version.major = req->http_version.major; |
| request->http_version.minor = req->http_version.minor; |
| request->method = req->method; |
| url_copy_members(&request->url, req->url); |
| |
| /* Default to http if none given */ |
| if (request->url.scheme == URL_SCHEME_INVALID) |
| request->url.scheme = URL_SCHEME_HTTP; |
| |
| if (!request->url.path) { |
| error("%s: [%s] Rejecting request with empty URL path", |
| __func__, conmgr_con_get_name(hcon->con)); |
| |
| return _send_reject(hcon, ESLURM_URL_INVALID_PATH); |
| } |
| |
| if (req->method == HTTP_REQUEST_INVALID) |
| return _send_reject(hcon, ESLURM_HTTP_INVALID_METHOD); |
| |
| if ((rc = _valid_http_version(req->http_version.major, |
| req->http_version.minor))) { |
| error("%s: [%s] rejecting unsupported HTTP %hu.%hu version: %s", |
| __func__, conmgr_con_get_name(hcon->con), |
| req->http_version.major, req->http_version.minor, |
| slurm_strerror(rc)); |
| return rc; |
| } |
| |
| if ((request->url.scheme != URL_SCHEME_HTTP) && |
| (request->url.scheme != URL_SCHEME_HTTPS)) { |
| error("%s: [%s] URL scheme not supported: %s", |
| __func__, conmgr_con_get_name(hcon->con), |
| url_get_scheme_string(request->url.scheme)); |
| return ESLURM_URL_UNSUPPORTED_SCHEME; |
| } |
| |
| if ((request->url.scheme == URL_SCHEME_HTTPS) && |
| !conmgr_fd_is_tls(hcon->con)) { |
| error("%s: [%s] URL requested HTTPS but connection is not TLS wrapped", |
| __func__, conmgr_con_get_name(hcon->con)); |
| return ESLURM_TLS_REQUIRED; |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| static int _on_header(const http_parser_header_t *header, void *arg) |
| { |
| http_con_t *hcon = arg; |
| http_con_request_t *request = &hcon->request; |
| |
| xassert(hcon->magic == MAGIC); |
| |
| log_flag(NET, "%s: [%s] Header: %s Value: %s", |
| __func__, conmgr_con_get_name(hcon->con), header->name, |
| header->value); |
| |
| /* Add header copy to list of headers */ |
| list_append(request->headers, |
| http_header_new(header->name, header->value)); |
| |
| /* Watch for connection headers */ |
| if (!xstrcasecmp(header->name, "Connection")) { |
| if (!xstrcasecmp(header->value, "Keep-Alive")) { |
| request->keep_alive = true; |
| } else if (!xstrcasecmp(header->value, "Close")) { |
| request->connection_close = true; |
| } else { |
| warning("%s: [%s] ignoring unsupported header request: Connection: %s", |
| __func__, |
| conmgr_con_get_name(hcon->con), |
| header->value); |
| } |
| } else if (!xstrcasecmp(header->name, "Keep-Alive")) { |
| /* |
| * RFC2068-19.7.1.1: HTTP/1.1 does not define any parameters. |
| * RFC2068-19.7.1.1: If the Keep-Alive header is sent, the |
| * corresponding connection token MUST be transmitted. The |
| * Keep-Alive header MUST be ignored if received without the |
| * connection token. |
| */ |
| log_flag(NET, "%s: [%s] Ignoring Keep-Alive header parameter: %s", |
| __func__, conmgr_con_get_name(hcon->con), header->value); |
| } else if (!xstrcasecmp(header->name, "Content-Type")) { |
| xfree(request->content_type); |
| request->content_type = xstrdup(header->value); |
| } else if (!xstrcasecmp(header->name, "Content-Length")) { |
| /* Use signed buffer to catch if negative length is provided */ |
| ssize_t cl; |
| |
| if ((sscanf(header->value, "%zd", &cl) != 1) || (cl < 0)) |
| return _send_reject(hcon, |
| ESLURM_HTTP_INVALID_CONTENT_LENGTH); |
| request->content_length = cl; |
| } else if (!xstrcasecmp(header->name, "Accept")) { |
| xfree(request->accept); |
| request->accept = xstrdup(header->value); |
| } else if (!xstrcasecmp(header->name, "Expect")) { |
| if (!xstrcasecmp(header->value, "100-continue")) |
| request->expect = 100; |
| else |
| return _send_reject(hcon, |
| ESLURM_HTTP_UNSUPPORTED_EXPECT); |
| } else if (!xstrcasecmp(header->name, "Transfer-Encoding")) { |
| /* Transfer encoding is not allowed */ |
| return _send_reject(hcon, |
| ESLURM_HTTP_INVALID_TRANSFER_ENCODING); |
| } else if (!xstrcasecmp(header->name, "Content-Encoding")) { |
| /* Content encoding is not allowed */ |
| return _send_reject(hcon, ESLURM_HTTP_INVALID_CONTENT_ENCODING); |
| } else if (!xstrcasecmp(header->name, "Upgrade")) { |
| /* Upgrades are not allowed */ |
| return _send_reject(hcon, ESLURM_HTTP_UNSUPPORTED_UPGRADE); |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| static int _on_headers_complete(void *arg) |
| { |
| http_con_t *hcon = arg; |
| http_con_request_t *request = &hcon->request; |
| |
| xassert(hcon->magic == MAGIC); |
| |
| if (!request->http_version.major && !request->http_version.minor) { |
| log_flag(NET, "%s: [%s] HTTP/0.9 connection", |
| __func__, conmgr_con_get_name(hcon->con)); |
| |
| /* |
| * Force connection without version to HTTP/0.9 as only |
| * recognized versions in RFC2068-19.7 |
| */ |
| request->http_version.major = 0; |
| request->http_version.minor = 9; |
| |
| /* |
| * Disable persistent connections for HTTP/0.9 connections to |
| * avoid breaking non-compliant clients |
| * |
| * RFC9112-C.2.2: Clients are also encouraged to consider the |
| * use of "Connection: keep-alive" in requests carefully |
| */ |
| request->connection_close = true; |
| request->keep_alive = false; |
| } else if ((request->http_version.major == 1) && |
| (request->http_version.minor == 0)) { |
| log_flag(NET, "%s: [%s] HTTP/1.0 connection", |
| __func__, conmgr_con_get_name(hcon->con)); |
| /* |
| * RFC9112-C.2.2: In HTTP/1.0, each connection is established by |
| * the client prior to the request and closed by the server |
| * after sending the response. Servers might wish to be |
| * compatible with these previous approaches to persistent |
| * connections, by explicitly negotiating for them with a |
| * "Connection: keep-alive" request header field. |
| * RFC2068-19.7.1: Persistent connections in HTTP/1.0 must be |
| * explicitly negotiated as they are not the default behavior. |
| * |
| * Default HTTP/1.0 to close w/o keep_alive being requested. |
| */ |
| if (!request->keep_alive) |
| request->connection_close = true; |
| } else if ((request->http_version.major == 1) && |
| (request->http_version.minor == 1)) { |
| log_flag(NET, "%s: [%s] HTTP/1.1 connection", |
| __func__, conmgr_con_get_name(hcon->con)); |
| |
| /* |
| * RFC2068-8.1.2.1: An HTTP/1.1 server MAY assume that a |
| * HTTP/1.1 client intends to maintain a persistent connection |
| */ |
| request->keep_alive = true; |
| } else { |
| log_flag(NET, "%s: [%s] HTTP/%d.%d connection", |
| __func__, conmgr_con_get_name(hcon->con), |
| request->http_version.major, |
| request->http_version.minor); |
| |
| /* |
| * RFC9112-9.3: If the received protocol is HTTP/1.1 (or later), |
| * the connection will persist after the current response |
| */ |
| request->keep_alive = true; |
| } |
| |
| if (!request->http_version.major && request->http_version.minor) |
| return SLURM_SUCCESS; |
| |
| if ((request->method == HTTP_REQUEST_POST) && |
| (request->content_length <= 0)) |
| return _send_reject(hcon, |
| ESLURM_HTTP_POST_MISSING_CONTENT_LENGTH); |
| |
| if (request->expect) |
| return http_con_send_response(hcon, request->expect, NULL, |
| false, NULL, NULL); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| static int _on_content(const http_parser_content_t *content, void *arg) |
| { |
| http_con_t *hcon = arg; |
| http_con_request_t *request = &hcon->request; |
| const void *at = get_buf_data(content->buffer); |
| const size_t length = get_buf_offset(content->buffer); |
| |
| xassert(hcon->magic == MAGIC); |
| |
| log_flag_hex(NET_RAW, at, length, "%s: [%s] received HTTP content", |
| __func__, conmgr_con_get_name(hcon->con)); |
| |
| if (!request->url.path) { |
| error("%s: [%s] rejecting missing path", |
| __func__, conmgr_con_get_name(hcon->con)); |
| return ESLURM_HTTP_UNEXPECTED_REQUEST; |
| } |
| |
| if (get_buf_offset(content->buffer) > 0) { |
| size_t nlength = (length + request->content_bytes); |
| int rc = EINVAL; |
| |
| if (nlength > MAX_BODY_BYTES) |
| return _send_reject( |
| hcon, ESLURM_HTTP_CONTENT_LENGTH_TOO_LARGE); |
| |
| if ((request->content_length > 0) && |
| (nlength > request->content_length)) |
| return _send_reject(hcon, ESLURM_HTTP_UNEXPECTED_BODY); |
| |
| if (!request->content && |
| !(request->content = try_init_buf(BUF_SIZE))) |
| return _send_reject(hcon, ENOMEM); |
| |
| /* Always include 1 extra byte for NULL terminator */ |
| if ((rc = try_grow_buf_remaining(request->content, |
| (length + 1)))) |
| return _send_reject(hcon, rc); |
| |
| xassert(remaining_buf(request->content) >= nlength); |
| memmove((get_buf_data(request->content) + |
| get_buf_offset(request->content)), |
| at, length); |
| set_buf_offset(request->content, |
| (get_buf_offset(request->content) + length)); |
| request->content_bytes += length; |
| |
| /* final byte must in body must always be NULL terminated */ |
| { |
| char *term = (get_buf_data(request->content) + |
| get_buf_offset(request->content) + 1); |
| *term = '\0'; |
| } |
| } |
| |
| log_flag(NET, "%s: [%s] received %zu bytes for HTTP body length %zu/%zu bytes", |
| __func__, conmgr_con_get_name(hcon->con), length, |
| request->content_bytes, request->content_length); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Create and write rfc2616 formatted header |
| * IN con - connection pointer |
| * IN name header name |
| * IN value header value |
| * RET SLURM_SUCCESS or error |
| */ |
| static int _write_fmt_header(conmgr_fd_ref_t *con, const char *name, |
| const char *value) |
| { |
| char buffer[MAX_HEADER_BYTES] = { 0 }; |
| int wrote = -1; |
| |
| if ((wrote = snprintf(buffer, sizeof(buffer), "%s: %s%s", name, value, |
| CRLF)) >= sizeof(buffer)) { |
| log_flag_hex(NET, value, strlen(value), "%s: [%s] header \"%s\" too large: %d/%d bytes", |
| __func__, conmgr_con_get_name(con), name, wrote, |
| sizeof(buffer)); |
| return ENOMEM; |
| } |
| |
| return conmgr_con_queue_write_data(con, buffer, wrote); |
| } |
| |
| /* |
| * Send HTTP close notification header |
| * Warns the client that we are about to close the connection. |
| * Requests the connection close as this reply is done. |
| * IN hcon - http connection |
| * RET SLURM_SUCCESS or error |
| */ |
| static int _send_http_connection_close(http_con_t *hcon) |
| { |
| /* |
| * Ensure connection gets closed as we just told the client the |
| * connection is closing |
| */ |
| conmgr_con_queue_close(hcon->con); |
| |
| return _write_fmt_header(hcon->con, "Connection", "Close"); |
| } |
| |
| /* |
| * Create and write rfc2616 formatted numerical header |
| * IN con - connection pointer |
| * IN name header name |
| * IN value header value |
| * RET SLURM_SUCCESS or error |
| */ |
| static int _write_fmt_num_header(conmgr_fd_ref_t *con, const char *name, |
| size_t value) |
| { |
| char buffer[MAX_HEADER_BYTES] = { 0 }; |
| int wrote = -1; |
| |
| if ((wrote = snprintf(buffer, sizeof(buffer), "%s: %zu%s", name, value, |
| CRLF)) >= sizeof(buffer)) { |
| log_flag(NET, "%s: [%s] header \"%s\":%zu too large: %d/%zu bytes", |
| __func__, conmgr_con_get_name(con), name, value, wrote, |
| sizeof(buffer)); |
| return ENOMEM; |
| } |
| |
| return conmgr_con_queue_write_data(con, buffer, wrote); |
| } |
| |
| static int _write_each_header(void *x, void *arg) |
| { |
| http_header_t *header = x; |
| write_each_header_args_t *args = arg; |
| |
| xassert(args->magic == WRITE_EACH_HEADER_MAGIC); |
| xassert(header->magic == HTTP_HEADER_MAGIC); |
| |
| if ((args->rc = |
| _write_fmt_header(args->con, header->name, header->value))) |
| return SLURM_ERROR; |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* Send RFC2616 response */ |
| static int _send_http_status_response(http_con_request_t *request, |
| http_status_code_t status_code, |
| conmgr_fd_ref_t *con) |
| { |
| char buffer[MAX_STATUS_BYTES] = { 0 }; |
| int wrote = -1; |
| |
| if ((wrote = snprintf(buffer, sizeof(buffer), "HTTP/%d.%d %d %s%s", |
| request->http_version.major, |
| request->http_version.minor, status_code, |
| get_http_status_code_string(status_code), |
| CRLF)) >= sizeof(buffer)) { |
| log_flag(NET, "%s: [%s] HTTP response %s too large: %d/%zu bytes", |
| __func__, conmgr_con_get_name(con), |
| get_http_status_code_string(status_code), wrote, |
| sizeof(buffer)); |
| return ENOMEM; |
| } |
| |
| log_flag_hex(NET, buffer, wrote, "%s: [%s] HTTP response", |
| __func__, conmgr_con_get_name(con), status_code, |
| get_http_status_code_string(status_code)); |
| |
| return conmgr_con_queue_write_data(con, buffer, wrote); |
| } |
| |
| static int _send_content_length(http_con_t *hcon, conmgr_fd_ref_t *con, |
| http_status_code_t status_code, |
| const size_t body_length) |
| { |
| /* |
| * RFC7230-3.3.2: |
| * A server MUST NOT send a Content-Length header field in any |
| * response with a status code of 1xx (Informational) or 204 (No |
| * Content). A server MUST NOT send a Content-Length header |
| * field in any 2xx (Successful) response to a CONNECT request |
| */ |
| |
| if ((status_code > HTTP_STATUS_INFO_BEGIN) && |
| (status_code <= HTTP_STATUS_INFO_END)) |
| return SLURM_SUCCESS; |
| |
| if (status_code == HTTP_STATUS_CODE_SUCCESS_NO_CONTENT) |
| return SLURM_SUCCESS; |
| |
| /* |
| * TODO: Add reference to request method to only limit CONNECT requests |
| */ |
| if ((status_code > HTTP_STATUS_SUCCESS_BEGIN) && |
| (status_code <= HTTP_STATUS_SUCCESS_END)) |
| return SLURM_SUCCESS; |
| |
| return _write_fmt_num_header(con, "Content-Length", body_length); |
| } |
| |
| extern int http_con_send_response(http_con_t *hcon, |
| http_status_code_t status_code, |
| list_t *headers, bool close_header, |
| const buf_t *body, const char *body_encoding) |
| { |
| int rc = SLURM_SUCCESS; |
| http_con_request_t *request = &hcon->request; |
| conmgr_fd_ref_t *con = hcon->con; |
| |
| xassert(hcon->magic == MAGIC); |
| xassert(conmgr_con_get_name(con)); |
| xassert(status_code > HTTP_STATUS_CODE_INVALID); |
| xassert(status_code < HTTP_STATUS_CODE_INVALID_MAX); |
| xassert(request->http_version.major > 0); |
| |
| log_flag(NET, "%s: [%s] sending response %u: %s", |
| __func__, conmgr_con_get_name(con), status_code, |
| get_http_status_code_string(status_code)); |
| |
| if ((rc = _send_http_status_response(request, status_code, con))) |
| return rc; |
| |
| /* send along any requested headers */ |
| if (headers) { |
| write_each_header_args_t args = { |
| .magic = WRITE_EACH_HEADER_MAGIC, |
| .rc = SLURM_SUCCESS, |
| .con = hcon->con, |
| }; |
| |
| (void) list_for_each(headers, _write_each_header, &args); |
| |
| if (args.rc) |
| return args.rc; |
| } |
| |
| if (close_header && (rc = _send_http_connection_close(hcon))) |
| return rc; |
| |
| if (body && (get_buf_offset(body) > 0)) { |
| const size_t body_length = get_buf_offset(body); |
| |
| if ((rc = _send_content_length(hcon, con, status_code, |
| body_length))) |
| return rc; |
| |
| if (body_encoding && |
| (rc = _write_fmt_header(con, "Content-Type", |
| body_encoding))) |
| return rc; |
| |
| /* Send end of headers */ |
| if ((rc = conmgr_con_queue_write_data(con, CRLF, strlen(CRLF)))) |
| return rc; |
| |
| log_flag(NET, "%s: [%s] rc=%s(%u) sending %zu bytes of body", |
| __func__, conmgr_con_get_name(con), |
| get_http_status_code_string(status_code), status_code, |
| body_length); |
| |
| log_flag_hex(NET_RAW, get_buf_data(body), body_length, |
| "%s: [%s] sending body", __func__, |
| conmgr_con_get_name(con)); |
| |
| if ((rc = conmgr_con_queue_write_data(con, get_buf_data(body), |
| body_length))) |
| return rc; |
| } else { |
| /* |
| * RFC2616 Section 6 Response always requires empty line after |
| * headers in the HTTP Response message |
| */ |
| if ((rc = conmgr_con_queue_write_data(con, CRLF, strlen(CRLF)))) |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| static int _send_reject(http_con_t *hcon, slurm_err_t error_number) |
| { |
| http_con_request_t *request = &hcon->request; |
| bool close_header = false; |
| const char *error = slurm_strerror(error_number); |
| const size_t error_len = strlen(error); |
| const buf_t body = SHADOW_BUF_INITIALIZER(error, error_len); |
| |
| xassert(hcon->magic == MAGIC); |
| |
| if (!_valid_http_version(request->http_version.major, |
| request->http_version.minor)) { |
| /* |
| * Default to HTTP/1.1 when version is invalid or failed to |
| * parse for rejecting a request: |
| */ |
| request->http_version.major = 1; |
| request->http_version.minor = 1; |
| } |
| |
| close_header = (request->connection_close || |
| _valid_http_version(request->http_version.major, |
| request->http_version.minor)); |
| |
| (void) http_con_send_response(hcon, |
| http_status_from_error(error_number), |
| NULL, close_header, &body, |
| MIME_TYPE_TEXT); |
| |
| /* ensure connection gets closed */ |
| conmgr_con_queue_close(hcon->con); |
| |
| /* reset connection to avoid inheriting request state */ |
| _request_reset(hcon); |
| |
| return error_number; |
| } |
| |
| static int _on_content_complete(void *arg) |
| { |
| http_con_t *hcon = arg; |
| http_con_request_t *request = &hcon->request; |
| int rc = EINVAL; |
| |
| xassert(hcon->magic == MAGIC); |
| |
| if ((request->content_length > 0) && |
| (request->content_length != request->content_bytes)) { |
| error("%s: [%s] Content-Length %zd and received body length %zd mismatch", |
| __func__, conmgr_con_get_name(hcon->con), |
| request->content_length, request->content_bytes); |
| return _send_reject(hcon, ESLURM_HTTP_INVALID_CONTENT_LENGTH); |
| } |
| |
| rc = hcon->events->on_request(hcon, conmgr_con_get_name(hcon->con), |
| &hcon->request, hcon->arg); |
| |
| if (request->connection_close) { |
| /* Notify client that this connection will be closed now */ |
| _send_http_connection_close(hcon); |
| conmgr_con_queue_close(hcon->con); |
| } |
| |
| _request_reset(hcon); |
| |
| return rc; |
| } |
| |
| extern int _on_data(conmgr_callback_args_t conmgr_args, void *arg) |
| { |
| http_con_t *hcon = arg; |
| static const http_parser_callbacks_t callbacks = { |
| .on_request = _on_request, |
| .on_header = _on_header, |
| .on_headers_complete = _on_headers_complete, |
| .on_content = _on_content, |
| .on_content_complete = _on_content_complete, |
| }; |
| int rc = SLURM_SUCCESS; |
| ssize_t bytes_parsed = -1; |
| buf_t *buffer = NULL; |
| |
| xassert(hcon->magic == MAGIC); |
| xassert(conmgr_fd_get_ref(hcon->con) == conmgr_args.con); |
| |
| if (!hcon->parser && (rc = http_parser_g_new_parse_request( |
| conmgr_con_get_name(hcon->con), |
| &callbacks, hcon, &hcon->parser))) { |
| log_flag(NET, "%s: [%s] Creating new HTTP parser failed: %s", |
| __func__, conmgr_con_get_name(hcon->con), |
| slurm_strerror(rc)); |
| goto cleanup; |
| } |
| |
| if ((rc = conmgr_con_shadow_in_buffer(hcon->con, &buffer))) { |
| log_flag(NET, "%s: [%s] Unable to get HTTP input buffer: %s", |
| __func__, conmgr_con_get_name(hcon->con), |
| slurm_strerror(rc)); |
| goto cleanup; |
| } |
| |
| /* Set buffer as fully populated */ |
| set_buf_offset(buffer, size_buf(buffer)); |
| |
| log_flag(NET, "%s: [%s] Accepted HTTP connection", |
| __func__, conmgr_con_get_name(hcon->con)); |
| |
| rc = http_parser_g_parse_request(hcon->parser, buffer, &bytes_parsed); |
| |
| if (hcon->con) |
| log_flag(NET, "%s: [%s] parsed %zu/%u bytes: %s", |
| __func__, conmgr_con_get_name(hcon->con), |
| bytes_parsed, get_buf_offset(buffer), |
| slurm_strerror(rc)); |
| |
| if (rc) { |
| rc = _send_reject(hcon, rc); |
| } else if (hcon->con && (bytes_parsed > 0) && |
| (rc = conmgr_con_mark_consumed_input_buffer(hcon->con, |
| bytes_parsed))) { |
| log_flag(NET, "%s: [%s] Input buffer became invalid after parsing: %s", |
| __func__, conmgr_con_get_name(hcon->con), |
| slurm_strerror(rc)); |
| goto cleanup; |
| } |
| |
| cleanup: |
| FREE_NULL_BUFFER(buffer); |
| return rc; |
| } |
| |
| static void _on_finish(conmgr_callback_args_t conmgr_args, void *arg) |
| { |
| http_con_t *hcon = arg; |
| void *hcon_arg = hcon->arg; |
| conmgr_fd_ref_t *hcon_con = NULL; |
| const http_con_server_events_t *hcon_events = hcon->events; |
| |
| xassert(hcon->magic == MAGIC); |
| xassert(conmgr_fd_get_ref(hcon->con) == conmgr_args.con); |
| |
| /* |
| * Preserve conmgr connection reference to ensure that the connection is |
| * always valid which is redundant since this callback will keep the |
| * conmgr alive until the conversion to only conmgr references. |
| * TODO: remove hcon->con once after conmgr reference conversion |
| */ |
| SWAP(hcon_con, hcon->con); |
| |
| http_parser_g_free_parse_request(&hcon->parser); |
| _request_free_members(hcon); |
| hcon->magic = ~MAGIC; |
| |
| if (hcon->free_on_close) |
| xfree(hcon); |
| |
| /* |
| * hcon must not be used in any way to call on_close() as it is expected |
| * that this function call will release that memory which would risk a |
| * use after free() |
| */ |
| |
| if (hcon_events->on_close) |
| hcon_events->on_close(conmgr_con_get_name(hcon_con), hcon_arg); |
| |
| CONMGR_CON_UNLINK(hcon_con); |
| } |
| |
| extern int http_con_assign_server(conmgr_fd_ref_t *con, http_con_t *hcon, |
| const http_con_server_events_t *events, |
| void *arg) |
| { |
| static const conmgr_events_t http_events = { |
| .on_data = _on_data, |
| .on_finish = _on_finish, |
| }; |
| const conmgr_events_t *prior_events = NULL; |
| void *prior_arg = NULL; |
| bool free_on_close = false; |
| int rc = EINVAL; |
| |
| xassert(con); |
| xassert(events); |
| xassert(events->on_request); |
| |
| if (!con || !events) |
| return rc; |
| |
| if (!hcon) { |
| hcon = xmalloc(sizeof(*hcon)); |
| free_on_close = true; |
| } |
| |
| /* catch over-subscription */ |
| xassert(hcon->magic != MAGIC); |
| |
| *hcon = (http_con_t) { |
| .magic = MAGIC, |
| .free_on_close = free_on_close, |
| .events = events, |
| .arg = arg, |
| }; |
| |
| if ((rc = conmgr_con_get_events(con, &prior_events, &prior_arg))) |
| goto failed; |
| |
| CONMGR_CON_LINK(con, hcon->con); |
| |
| if ((rc = conmgr_con_set_events(con, &http_events, hcon, __func__))) |
| goto failed; |
| |
| _request_init(hcon); |
| |
| return rc; |
| failed: |
| CONMGR_CON_UNLINK(hcon->con); |
| hcon->magic = ~MAGIC; |
| |
| /* Attempt to revert changes */ |
| if (prior_events) |
| (void) conmgr_con_set_events(con, prior_events, prior_arg, |
| __func__); |
| |
| return rc; |
| } |