blob: 98020b1b55acb75bb3e2912e909a772e7c38fc2f [file] [log] [blame] [edit]
/*****************************************************************************\
* 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;
}