| /*****************************************************************************\ |
| url_parser_internal.c - url parser handler |
| ***************************************************************************** |
| * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| * |
| * 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 "http_parser_url_port.h" |
| |
| #include "slurm/slurm.h" |
| |
| #include "src/common/http.h" |
| #include "src/common/log.h" |
| #include "src/common/read_config.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #include "src/interfaces/url_parser.h" |
| |
| /* Required Slurm plugin symbols: */ |
| const char plugin_name[] = "Slurm url_parser plugin"; |
| const char plugin_type[] = URL_PARSER_PREFIX URL_PARSER_INTERNAL_PLUGIN; |
| const uint32_t plugin_version = SLURM_VERSION_NUMBER; |
| |
| #define URL_PARSE_ERROR(error_number, name, buffer) \ |
| _on_url_parse_error(error_number, name, buffer, __func__) |
| #define LOG_URL_PARSE(name, buffer, fmt, ...) \ |
| _log_url_parse(name, buffer, __func__, fmt, ##__VA_ARGS__) |
| |
| static void _log_url_parse_buffer(const char *name, const char *caller, |
| const char *log_str, const buf_t *buffer) |
| { |
| const void *begin = get_buf_data(buffer); |
| const size_t bytes = get_buf_offset(buffer); |
| |
| xassert(buffer->magic == BUF_MAGIC); |
| xassert(begin); |
| xassert(begin <= (begin + bytes)); |
| |
| log_flag(DATA, "%s: [%s] URL PARSE [0,%zu)@0x%"PRIxPTR" %s", |
| caller, name, bytes, (uintptr_t) begin, log_str); |
| log_flag_hex_range(NET_RAW, begin, bytes, 0, bytes, "%s: [%s] %s", |
| caller, name, log_str); |
| } |
| |
| static void _log_url_parse(const char *name, const buf_t *buffer, |
| const char *caller, const char *fmt, ...) |
| { |
| char *log_str = NULL; |
| |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_DATA) || |
| (get_log_level() < LOG_LEVEL_VERBOSE)) |
| return; |
| |
| xassert(fmt); |
| if (fmt) { |
| va_list ap; |
| va_start(ap, fmt); |
| log_str = vxstrfmt(fmt, ap); |
| va_end(ap); |
| } |
| |
| if (buffer) |
| _log_url_parse_buffer(name, caller, log_str, buffer); |
| else |
| log_flag(DATA, "%s: [%s] URL PARSE %s", caller, name, log_str); |
| |
| xfree(log_str); |
| } |
| |
| /* |
| * Log that URL parsing failed |
| * NOTE: use URL_PARSE_ERROR() instead of calling directly |
| * IN error_number - Slurm error encountered |
| * IN buffer - buffer being parsed |
| * RET error_number |
| */ |
| static int _on_url_parse_error(slurm_err_t error_number, const char *name, |
| const buf_t *buffer, const char *caller) |
| { |
| LOG_URL_PARSE(name, buffer, "Parsing failed: %s", |
| slurm_strerror(error_number)); |
| |
| return error_number; |
| } |
| |
| /* |
| * Load and initialize URL parser plugin |
| * RET SLURM_SUCCESS or error |
| */ |
| extern int url_parser_p_init(void) |
| { |
| return SLURM_SUCCESS; |
| } |
| |
| /* Unload URL plugin */ |
| extern void url_parser_p_fini(void) {} |
| |
| /* |
| * Parse URL using a copy of libhttp_parser's URL parser implementation |
| * Examples: |
| * host:port |
| * https://user@[host]:port/path/?query#fragment |
| * RET |
| * SLURM_SUCCESS: parsed port successfully |
| * ESLURM_URL_UNSUPPORTED_FORMAT: library doesn't support format |
| * *: error |
| */ |
| static int _library_url_parse(const char *name, const buf_t *buffer, url_t *dst) |
| { |
| struct parsed_url url; |
| const void *data = get_buf_data(buffer); |
| const size_t bytes = get_buf_offset(buffer); |
| int rc = EINVAL; |
| |
| memset(&url, 0, sizeof(url)); |
| |
| xassert(data); |
| xassert(bytes > 0); |
| xassert(size_buf(buffer) >= bytes); |
| |
| /* Try parsing a full URL and then try parsing only a host:port pair */ |
| if (parse_url(data, bytes, false, &url) && |
| parse_url(data, bytes, true, &url)) |
| return ESLURM_URL_UNSUPPORTED_FORMAT; |
| |
| if ((url.field_set & (1 << UF_SCHEMA)) && |
| (rc = url_get_scheme((data + url.field_data[UF_SCHEMA].off), |
| url.field_data[UF_SCHEMA].len, &dst->scheme))) |
| return rc; |
| if (url.field_set & (1 << UF_HOST)) { |
| xassert(url.field_data[UF_HOST].len <= bytes); |
| dst->host = xstrndup((data + url.field_data[UF_HOST].off), |
| url.field_data[UF_HOST].len); |
| } |
| if (url.field_set & (1 << UF_PORT)) { |
| xassert(url.field_data[UF_PORT].len <= bytes); |
| dst->port = xstrndup((data + url.field_data[UF_PORT].off), |
| url.field_data[UF_PORT].len); |
| } |
| if (url.field_set & (1 << UF_PATH)) { |
| xassert(url.field_data[UF_PATH].len <= bytes); |
| dst->path = xstrndup((data + url.field_data[UF_PATH].off), |
| url.field_data[UF_PATH].len); |
| } |
| if (url.field_set & (1 << UF_QUERY)) { |
| xassert(url.field_data[UF_QUERY].len <= bytes); |
| dst->query = xstrndup((data + url.field_data[UF_QUERY].off), |
| url.field_data[UF_QUERY].len); |
| } |
| if (url.field_set & (1 << UF_FRAGMENT)) { |
| xassert(url.field_data[UF_FRAGMENT].len <= bytes); |
| dst->fragment = |
| xstrndup((data + url.field_data[UF_FRAGMENT].off), |
| url.field_data[UF_FRAGMENT].len); |
| } |
| if (url.field_set & (1 << UF_USERINFO)) { |
| xassert(url.field_data[UF_USERINFO].len <= bytes); |
| dst->user = xstrndup((data + url.field_data[UF_USERINFO].off), |
| url.field_data[UF_USERINFO].len); |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Parse URL where only the port is given. |
| * Examples: |
| * :8080 |
| * :ssh |
| * |
| * RET |
| * SLURM_SUCCESS: parsed port successfully |
| * ESLURM_URL_UNSUPPORTED_FORMAT: not a port only URL |
| * *: error |
| */ |
| static int _parse_only_port(const char *name, const buf_t *buffer, url_t *dst) |
| { |
| const char *data = get_buf_data(buffer); |
| const size_t bytes = get_buf_offset(buffer); |
| |
| if ((bytes < 1) || (data[0] != ':')) |
| return ESLURM_URL_UNSUPPORTED_FORMAT; |
| |
| if (bytes == 1) |
| return ESLURM_URL_EMPTY; |
| |
| dst->port = xstrndup((data + 1), (bytes - 1)); |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Parse URL where only the port is given. |
| * Examples: |
| * unix:/path/to/socket |
| * |
| * RET |
| * SLURM_SUCCESS: parsed port successfully |
| * ESLURM_URL_UNSUPPORTED_FORMAT: not a port only URL |
| * *: error |
| */ |
| static int _parse_unix_url(const char *name, const buf_t *buffer, url_t *dst) |
| { |
| const char *data = get_buf_data(buffer); |
| const size_t bytes = get_buf_offset(buffer); |
| |
| if ((bytes < UNIX_PREFIX_BYTES) || |
| xstrncmp(UNIX_PREFIX, data, UNIX_PREFIX_BYTES)) |
| return ESLURM_URL_UNSUPPORTED_FORMAT; |
| |
| if (bytes == UNIX_PREFIX_BYTES) |
| return ESLURM_URL_EMPTY; |
| |
| dst->scheme = URL_SCHEME_UNIX; |
| dst->path = xstrndup((data + UNIX_PREFIX_BYTES), |
| (bytes - UNIX_PREFIX_BYTES)); |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Parse URL |
| * IN name - name used for logging |
| * IN buffer - buffer containing string to parse |
| * IN/OUT url - URL to populate with parsed components of URL |
| * RET SLURM_SUCCESS or error |
| */ |
| extern int url_parser_p_parse(const char *name, const buf_t *buffer, url_t *dst) |
| { |
| int rc = EINVAL; |
| |
| xassert(!buffer || (buffer->magic == BUF_MAGIC)); |
| xassert(dst); |
| xassert(name && name[0]); |
| |
| url_free_members(dst); |
| |
| if (!buffer || !get_buf_offset(buffer)) |
| return URL_PARSE_ERROR(ESLURM_URL_EMPTY, name, buffer); |
| |
| /* Catch any errant NULL terminators */ |
| if (strnlen(get_buf_data(buffer), get_buf_offset(buffer)) != |
| get_buf_offset(buffer)) |
| return URL_PARSE_ERROR(ESLURM_URL_NON_NULL_TERMINATOR, name, |
| buffer); |
| |
| rc = _library_url_parse(name, buffer, dst); |
| if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) { |
| url_free_members(dst); |
| rc = _parse_only_port(name, buffer, dst); |
| } |
| if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) { |
| url_free_members(dst); |
| rc = _parse_unix_url(name, buffer, dst); |
| } |
| |
| if (rc) { |
| /* |
| * If none of the parsers apply, then consider the URL to be an |
| * invalid format |
| */ |
| if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) |
| rc = ESLURM_URL_INVALID_FORMATING; |
| |
| url_free_members(dst); |
| return URL_PARSE_ERROR(rc, name, buffer); |
| } |
| |
| LOG_URL_PARSE( |
| name, buffer, |
| "Parsed URL scheme:%s host:%s port:%s user:%s path:%s query:%s fragment:%s", |
| url_get_scheme_string(dst->scheme), dst->host, dst->port, |
| dst->user, dst->path, dst->query, dst->fragment); |
| return rc; |
| } |