mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-09 12:48:09 -05:00
[httpd] Abstract parsing of URIs
This commit is contained in:
parent
816f2c09b5
commit
96ce7c52c8
124
src/httpd.c
124
src/httpd.c
@ -40,9 +40,7 @@
|
||||
# include <sys/eventfd.h>
|
||||
#endif
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
|
||||
#include <regex.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "logger.h"
|
||||
@ -290,120 +288,18 @@ modules_search(const char *path)
|
||||
|
||||
/* --------------------------- REQUEST HELPERS ------------------------------ */
|
||||
|
||||
static void
|
||||
uri_parsed_free(struct httpd_uri_parsed *parsed)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!parsed)
|
||||
return;
|
||||
|
||||
free(parsed->uri_decoded);
|
||||
free(parsed->path);
|
||||
for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
|
||||
{
|
||||
free(parsed->path_parts[i]);
|
||||
}
|
||||
|
||||
httpd_query_clear(&(parsed->query));
|
||||
|
||||
if (parsed->ev_uri)
|
||||
evhttp_uri_free(parsed->ev_uri);
|
||||
|
||||
free(parsed);
|
||||
}
|
||||
|
||||
static struct httpd_uri_parsed *
|
||||
uri_parsed_create(const char *uri)
|
||||
{
|
||||
struct httpd_uri_parsed *parsed;
|
||||
char *path = NULL;
|
||||
const char *query;
|
||||
char *path_part;
|
||||
char *ptr;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
CHECK_NULL(L_HTTPD, parsed = calloc(1, sizeof(struct httpd_uri_parsed)));
|
||||
|
||||
parsed->uri = uri;
|
||||
|
||||
parsed->ev_uri = evhttp_uri_parse_with_flags(parsed->uri, EVHTTP_URI_NONCONFORMANT);
|
||||
if (!parsed->ev_uri)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not parse request: '%s'\n", parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
parsed->uri_decoded = evhttp_uridecode(parsed->uri, 0, NULL);
|
||||
if (!parsed->uri_decoded)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not URI decode request: '%s'\n", parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
query = evhttp_uri_get_query(parsed->ev_uri);
|
||||
if (query && strchr(query, '='))
|
||||
{
|
||||
ret = evhttp_parse_query_str(query, &(parsed->query));
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in request: '%s'\n", query, parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
path = strdup(evhttp_uri_get_path(parsed->ev_uri));
|
||||
if (!path)
|
||||
{
|
||||
DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
parsed->path = evhttp_uridecode(path, 0, NULL);
|
||||
if (!parsed->path)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not URI decode path: '%s'\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
path_part = strtok_r(path, "/", &ptr);
|
||||
for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
|
||||
{
|
||||
parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL);
|
||||
path_part = strtok_r(NULL, "/", &ptr);
|
||||
}
|
||||
|
||||
if (path_part)
|
||||
{
|
||||
// If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
|
||||
DPRINTF(E_LOG, L_HTTPD, "URI path has too many components (%d): '%s'\n", i, parsed->path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
free(path);
|
||||
|
||||
return parsed;
|
||||
|
||||
error:
|
||||
free(path);
|
||||
uri_parsed_free(parsed);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
request_unset(struct httpd_request *hreq)
|
||||
{
|
||||
if (hreq->out_body)
|
||||
evbuffer_free(hreq->out_body);
|
||||
|
||||
uri_parsed_free(hreq->uri_parsed);
|
||||
httpd_uri_parsed_free(hreq->uri_parsed);
|
||||
}
|
||||
|
||||
static void
|
||||
request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri, const char *user_agent)
|
||||
{
|
||||
struct httpd_uri_parsed *uri_parsed;
|
||||
struct httpd_uri_map *map;
|
||||
int ret;
|
||||
|
||||
@ -432,17 +328,19 @@ request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri,
|
||||
// buffer even if there is no backend.
|
||||
CHECK_NULL(L_HTTPD, hreq->out_body = evbuffer_new());
|
||||
|
||||
uri_parsed = uri_parsed_create(hreq->uri);
|
||||
if (!uri_parsed)
|
||||
hreq->uri_parsed = httpd_uri_parsed_create(hreq->uri);
|
||||
if (!hreq->uri_parsed)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Unable to parse URI '%s' in request from '%s'\n", hreq->uri, hreq->peer_address);
|
||||
return;
|
||||
}
|
||||
|
||||
hreq->uri_parsed = uri_parsed;
|
||||
hreq->query = &(hreq->uri_parsed->query);
|
||||
hreq->path = httpd_uri_path_get(hreq->uri_parsed);
|
||||
hreq->query = httpd_uri_query_get(hreq->uri_parsed);
|
||||
httpd_uri_path_parts_get(&hreq->path_parts, hreq->uri_parsed);
|
||||
|
||||
// Path with e.g. /api -> JSON module
|
||||
hreq->module = modules_search(uri_parsed->path);
|
||||
hreq->module = modules_search(hreq->path);
|
||||
if (!hreq->module)
|
||||
{
|
||||
return;
|
||||
@ -454,7 +352,7 @@ request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri,
|
||||
if (map->method && hreq->method && !(map->method & hreq->method))
|
||||
continue;
|
||||
|
||||
ret = regexec(map->preg, uri_parsed->path, 0, NULL, 0);
|
||||
ret = regexec(map->preg, hreq->path, 0, NULL, 0);
|
||||
if (ret != 0)
|
||||
continue;
|
||||
|
||||
@ -564,7 +462,7 @@ serve_file(struct httpd_request *hreq)
|
||||
if (!httpd_admin_check_auth(hreq))
|
||||
return;
|
||||
|
||||
ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, hreq->uri_parsed->path);
|
||||
ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, hreq->path);
|
||||
if ((ret < 0) || (ret >= sizeof(path)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", hreq->uri);
|
||||
@ -968,7 +866,7 @@ httpd_gen_cb(httpd_backend *backend, void *arg)
|
||||
httpd_redirect_to(&hreq, "/");
|
||||
goto out;
|
||||
}
|
||||
else if (!(&hreq)->uri_parsed->path)
|
||||
else if (!(&hreq)->path)
|
||||
{
|
||||
DPRINTF(E_WARN, L_HTTPD, "Invalid path in request: '%s'\n", (&hreq)->uri);
|
||||
httpd_redirect_to(&hreq, "/");
|
||||
|
@ -20,7 +20,6 @@
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <regex.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
@ -105,7 +104,7 @@ artworkapi_reply_item(struct httpd_request *hreq)
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id);
|
||||
ret = safe_atou32(hreq->path_parts[2], &id);
|
||||
if (ret != 0)
|
||||
return HTTP_BADREQUEST;
|
||||
|
||||
@ -126,7 +125,7 @@ artworkapi_reply_group(struct httpd_request *hreq)
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id);
|
||||
ret = safe_atou32(hreq->path_parts[2], &id);
|
||||
if (ret != 0)
|
||||
return HTTP_BADREQUEST;
|
||||
|
||||
|
@ -713,7 +713,7 @@ daap_request_authorize(struct httpd_request *hreq)
|
||||
// with httpd_basic_auth() if a library password is set. Remote clients will
|
||||
// also call /login, but they should not get a httpd_basic_auth(), instead
|
||||
// daap_reply_login() will take care of auth.
|
||||
if (session->is_remote && (strcmp(hreq->uri_parsed->path, "/login") == 0))
|
||||
if (session->is_remote && (strcmp(hreq->path, "/login") == 0))
|
||||
return 0;
|
||||
|
||||
param = httpd_query_value_find(hreq->query, "session-id");
|
||||
@ -721,7 +721,7 @@ daap_request_authorize(struct httpd_request *hreq)
|
||||
{
|
||||
if (session->id == 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Unauthorized request from '%s', DAAP session not found: '%s'\n", hreq->peer_address, hreq->uri_parsed->uri);
|
||||
DPRINTF(E_LOG, L_DAAP, "Unauthorized request from '%s', DAAP session not found: '%s'\n", hreq->peer_address, hreq->uri);
|
||||
|
||||
httpd_send_error(hreq, 401, "Unauthorized");
|
||||
return -1;
|
||||
@ -736,10 +736,10 @@ daap_request_authorize(struct httpd_request *hreq)
|
||||
return 0;
|
||||
|
||||
// If no valid session then we may need to authenticate
|
||||
if ((strcmp(hreq->uri_parsed->path, "/server-info") == 0)
|
||||
|| (strcmp(hreq->uri_parsed->path, "/logout") == 0)
|
||||
|| (strcmp(hreq->uri_parsed->path, "/content-codes") == 0)
|
||||
|| (strncmp(hreq->uri_parsed->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
|
||||
if ((strcmp(hreq->path, "/server-info") == 0)
|
||||
|| (strcmp(hreq->path, "/logout") == 0)
|
||||
|| (strcmp(hreq->path, "/content-codes") == 0)
|
||||
|| (strncmp(hreq->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
|
||||
return 0; // No authentication
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "Checking authentication for library\n");
|
||||
@ -1354,7 +1354,7 @@ daap_reply_plsonglist(struct httpd_request *hreq)
|
||||
int playlist;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &playlist);
|
||||
if (ret < 0)
|
||||
{
|
||||
dmap_error_make(hreq->out_body, "apso", "Invalid playlist ID");
|
||||
@ -1400,7 +1400,7 @@ daap_reply_playlists(struct httpd_request *hreq)
|
||||
|
||||
cfg_radiopl = cfg_getbool(cfg_getsec(cfg, "library"), "radio_playlists");
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[1], &database);
|
||||
ret = safe_atoi32(hreq->path_parts[1], &database);
|
||||
if (ret < 0)
|
||||
{
|
||||
dmap_error_make(hreq->out_body, "aply", "Invalid database ID");
|
||||
@ -1824,29 +1824,29 @@ daap_reply_browse(struct httpd_request *hreq)
|
||||
int nitems;
|
||||
int ret;
|
||||
|
||||
if (strcmp(hreq->uri_parsed->path_parts[3], "artists") == 0)
|
||||
if (strcmp(hreq->path_parts[3], "artists") == 0)
|
||||
{
|
||||
tag = "abar";
|
||||
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_ARTISTS);
|
||||
}
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[3], "albums") == 0)
|
||||
else if (strcmp(hreq->path_parts[3], "albums") == 0)
|
||||
{
|
||||
tag = "abal";
|
||||
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_ALBUMS);
|
||||
}
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[3], "genres") == 0)
|
||||
else if (strcmp(hreq->path_parts[3], "genres") == 0)
|
||||
{
|
||||
tag = "abgn";
|
||||
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_GENRES);
|
||||
}
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[3], "composers") == 0)
|
||||
else if (strcmp(hreq->path_parts[3], "composers") == 0)
|
||||
{
|
||||
tag = "abcp";
|
||||
query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_COMPOSERS);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", hreq->uri_parsed->path_parts[3]);
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", hreq->path_parts[3]);
|
||||
dmap_error_make(hreq->out_body, "abro", "Invalid browse type");
|
||||
return DAAP_REPLY_ERROR;
|
||||
}
|
||||
@ -1950,10 +1950,10 @@ daap_reply_extra_data(struct httpd_request *hreq)
|
||||
return DAAP_REPLY_NO_CONNECTION;
|
||||
}
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &id);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", hreq->uri_parsed->path_parts[3]);
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", hreq->path_parts[3]);
|
||||
return DAAP_REPLY_BAD_REQUEST;
|
||||
}
|
||||
|
||||
@ -1983,9 +1983,9 @@ daap_reply_extra_data(struct httpd_request *hreq)
|
||||
max_h = 0;
|
||||
}
|
||||
|
||||
if (strcmp(hreq->uri_parsed->path_parts[2], "groups") == 0)
|
||||
if (strcmp(hreq->path_parts[2], "groups") == 0)
|
||||
ret = artwork_get_group(hreq->out_body, id, max_w, max_h, 0);
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[2], "items") == 0)
|
||||
else if (strcmp(hreq->path_parts[2], "items") == 0)
|
||||
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
|
||||
|
||||
len = evbuffer_get_length(hreq->out_body);
|
||||
@ -2030,7 +2030,7 @@ daap_stream(struct httpd_request *hreq)
|
||||
return DAAP_REPLY_NO_CONNECTION;
|
||||
}
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &id);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &id);
|
||||
if (ret < 0)
|
||||
return DAAP_REPLY_BAD_REQUEST;
|
||||
|
||||
|
@ -653,7 +653,7 @@ dacp_request_authorize(struct httpd_request *hreq)
|
||||
return 0;
|
||||
|
||||
invalid:
|
||||
DPRINTF(E_LOG, L_DACP, "Unauthorized request '%s' from '%s' (is peer trusted in your config?)\n", hreq->uri_parsed->uri, hreq->peer_address);
|
||||
DPRINTF(E_LOG, L_DACP, "Unauthorized request '%s' from '%s' (is peer trusted in your config?)\n", hreq->uri, hreq->peer_address);
|
||||
|
||||
httpd_send_error(hreq, 403, "Forbidden");
|
||||
return -1;
|
||||
|
@ -5,17 +5,25 @@
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
struct httpd_request;
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include <regex.h> // evhtp conflicts with regex since it brings it own
|
||||
|
||||
typedef struct evhttp httpd_server;
|
||||
typedef struct evhttp_connection httpd_connection;
|
||||
typedef struct evhttp_request httpd_backend;
|
||||
typedef struct evkeyvalq httpd_headers;
|
||||
typedef struct evkeyvalq httpd_query;
|
||||
typedef struct httpd_uri_parsed httpd_uri_parsed;
|
||||
|
||||
typedef char *httpd_uri_path_parts[31];
|
||||
typedef void (*httpd_general_cb)(httpd_backend *backend, void *arg);
|
||||
typedef void (*httpd_connection_closecb)(httpd_connection *conn, void *arg);
|
||||
typedef void (*httpd_connection_chunkcb)(httpd_connection *conn, void *arg);
|
||||
@ -86,29 +94,6 @@ struct httpd_uri_map
|
||||
|
||||
/*------------------------------- HTTPD STRUCTS ------------------------------*/
|
||||
|
||||
/*
|
||||
* Contains a parsed version of the URI httpd got. The URI may have been
|
||||
* complete:
|
||||
* scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
|
||||
* or relative:
|
||||
* [/path][?query][#fragment]
|
||||
*
|
||||
* We are interested in the path and the query, so they are disassembled to
|
||||
* path_parts and ev_query. If the request is http://x:3689/foo/bar?key1=val1,
|
||||
* then part_parts[0] is "foo", [1] is "bar" and the rest is null.
|
||||
*
|
||||
* Each path_part is an allocated URI decoded string.
|
||||
*/
|
||||
struct httpd_uri_parsed
|
||||
{
|
||||
const char *uri;
|
||||
struct evhttp_uri *ev_uri;
|
||||
httpd_query query;
|
||||
char *uri_decoded;
|
||||
char *path;
|
||||
char *path_parts[31];
|
||||
};
|
||||
|
||||
/*
|
||||
* A collection of pointers to request data that the reply handlers may need.
|
||||
* Also has the function pointer to the reply handler and a pointer to a reply
|
||||
@ -117,22 +102,32 @@ struct httpd_uri_parsed
|
||||
struct httpd_request {
|
||||
// Request method
|
||||
enum httpd_methods method;
|
||||
// The request URI
|
||||
const char *uri;
|
||||
// User-agent (if available)
|
||||
const char *user_agent;
|
||||
// The parsed request URI given to us by httpd_uri_parse
|
||||
struct httpd_uri_parsed *uri_parsed;
|
||||
// Shortcut to &uri_parsed->query
|
||||
httpd_query *query;
|
||||
// Backend private request object
|
||||
httpd_backend *backend;
|
||||
// User-agent (if available)
|
||||
const char *user_agent;
|
||||
// Source IP address (ipv4 or ipv6) and port of the request (if available)
|
||||
char *peer_address;
|
||||
const char *peer_address;
|
||||
unsigned short peer_port;
|
||||
// A pointer to extra data that the module handling the request might need
|
||||
void *extra_data;
|
||||
|
||||
// The original, request URI. The URI may have been complete:
|
||||
// scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
|
||||
// or relative:
|
||||
// [/path][?query][#fragment]
|
||||
const char *uri;
|
||||
// URI decoded path from the request URI
|
||||
const char *path;
|
||||
// If the request is http://x:3689/foo/bar?key1=val1, then part_parts[0] is
|
||||
// "foo", [1] is "bar" and the rest is null. Each path_part is an allocated
|
||||
// URI decoded string.
|
||||
httpd_uri_path_parts path_parts;
|
||||
// Struct with the query, used with httpd_query_ functions
|
||||
httpd_query *query;
|
||||
// Backend private parser URI object
|
||||
httpd_uri_parsed *uri_parsed;
|
||||
|
||||
// Request headers
|
||||
httpd_headers *in_headers;
|
||||
// Request body
|
||||
@ -142,7 +137,7 @@ struct httpd_request {
|
||||
// Response body
|
||||
struct evbuffer *out_body;
|
||||
|
||||
// The module that will process this request
|
||||
// Our httpd module that will process this request
|
||||
struct httpd_module *module;
|
||||
// A pointer to the handler that will process the request
|
||||
int (*handler)(struct httpd_request *hreq);
|
||||
@ -248,7 +243,7 @@ int
|
||||
httpd_connection_closecb_set(httpd_connection *conn, httpd_connection_closecb cb, void *arg);
|
||||
|
||||
int
|
||||
httpd_connection_peer_get(char **addr, uint16_t *port, httpd_connection *conn);
|
||||
httpd_connection_peer_get(const char **addr, uint16_t *port, httpd_connection *conn);
|
||||
|
||||
void
|
||||
httpd_connection_free(httpd_connection *conn);
|
||||
@ -305,7 +300,7 @@ struct evbuffer *
|
||||
httpd_backend_input_buffer_get(httpd_backend *backend);
|
||||
|
||||
int
|
||||
httpd_backend_peer_get(char **addr, uint16_t *port, httpd_backend *backend);
|
||||
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend);
|
||||
|
||||
int
|
||||
httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend);
|
||||
@ -313,4 +308,19 @@ httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend);
|
||||
void
|
||||
httpd_backend_preprocess(httpd_backend *backend);
|
||||
|
||||
httpd_uri_parsed *
|
||||
httpd_uri_parsed_create(const char *uri);
|
||||
|
||||
void
|
||||
httpd_uri_parsed_free(httpd_uri_parsed *uri_parsed);
|
||||
|
||||
httpd_query *
|
||||
httpd_uri_query_get(httpd_uri_parsed *parsed);
|
||||
|
||||
const char *
|
||||
httpd_uri_path_get(httpd_uri_parsed *parsed);
|
||||
|
||||
void
|
||||
httpd_uri_path_parts_get(httpd_uri_path_parts *part_parts, httpd_uri_parsed *parsed);
|
||||
|
||||
#endif /* !__HTTPD_INTERNAL_H__ */
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <regex.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -989,7 +988,7 @@ jsonapi_reply_settings_category_get(struct httpd_request *hreq)
|
||||
json_object *jreply;
|
||||
|
||||
|
||||
categoryname = hreq->uri_parsed->path_parts[2];
|
||||
categoryname = hreq->path_parts[2];
|
||||
|
||||
category = settings_category_get(categoryname);
|
||||
if (!category)
|
||||
@ -1023,8 +1022,8 @@ jsonapi_reply_settings_option_get(struct httpd_request *hreq)
|
||||
json_object *jreply;
|
||||
|
||||
|
||||
categoryname = hreq->uri_parsed->path_parts[2];
|
||||
optionname = hreq->uri_parsed->path_parts[3];
|
||||
categoryname = hreq->path_parts[2];
|
||||
optionname = hreq->path_parts[3];
|
||||
|
||||
category = settings_category_get(categoryname);
|
||||
if (!category)
|
||||
@ -1069,8 +1068,8 @@ jsonapi_reply_settings_option_put(struct httpd_request *hreq)
|
||||
int ret;
|
||||
|
||||
|
||||
categoryname = hreq->uri_parsed->path_parts[2];
|
||||
optionname = hreq->uri_parsed->path_parts[3];
|
||||
categoryname = hreq->path_parts[2];
|
||||
optionname = hreq->path_parts[3];
|
||||
|
||||
category = settings_category_get(categoryname);
|
||||
if (!category)
|
||||
@ -1135,8 +1134,8 @@ jsonapi_reply_settings_option_delete(struct httpd_request *hreq)
|
||||
int ret;
|
||||
|
||||
|
||||
categoryname = hreq->uri_parsed->path_parts[2];
|
||||
optionname = hreq->uri_parsed->path_parts[3];
|
||||
categoryname = hreq->path_parts[2];
|
||||
optionname = hreq->path_parts[3];
|
||||
|
||||
category = settings_category_get(categoryname);
|
||||
if (!category)
|
||||
@ -1585,10 +1584,10 @@ jsonapi_reply_outputs_get_byid(struct httpd_request *hreq)
|
||||
json_object *jreply;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
|
||||
ret = safe_atou64(hreq->path_parts[2], &output_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -1597,7 +1596,7 @@ jsonapi_reply_outputs_get_byid(struct httpd_request *hreq)
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No output found for '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No output found for '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -1623,10 +1622,10 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
|
||||
const char *pin;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
|
||||
ret = safe_atou64(hreq->path_parts[2], &output_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -1681,10 +1680,10 @@ jsonapi_reply_outputs_toggle_byid(struct httpd_request *hreq)
|
||||
struct player_speaker_info spk;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
|
||||
ret = safe_atou64(hreq->path_parts[2], &output_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -1692,7 +1691,7 @@ jsonapi_reply_outputs_toggle_byid(struct httpd_request *hreq)
|
||||
ret = player_speaker_get_byid(&spk, output_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No output found for the given output id, toggle failed for '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No output found for the given output id, toggle failed for '%s'\n", hreq->path);
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
|
||||
@ -2585,12 +2584,12 @@ jsonapi_reply_queue_tracks_update(struct httpd_request *hreq)
|
||||
|
||||
player_get_status(&status);
|
||||
|
||||
if (strcmp(hreq->uri_parsed->path_parts[3], "now_playing") != 0)
|
||||
if (strcmp(hreq->path_parts[3], "now_playing") != 0)
|
||||
{
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[3], &item_id);
|
||||
ret = safe_atou32(hreq->path_parts[3], &item_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid item id given: '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid item id given: '%s'\n", hreq->path);
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
}
|
||||
@ -2600,7 +2599,7 @@ jsonapi_reply_queue_tracks_update(struct httpd_request *hreq)
|
||||
queue_item = db_queue_fetch_byitemid(item_id);
|
||||
if (!queue_item)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid item id given, or now_playing given but not playing: '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid item id given, or now_playing given but not playing: '%s'\n", hreq->path);
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
|
||||
@ -2638,10 +2637,10 @@ jsonapi_reply_queue_tracks_delete(struct httpd_request *hreq)
|
||||
uint32_t item_id;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[3], &item_id);
|
||||
ret = safe_atou32(hreq->path_parts[3], &item_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid item id given '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid item id given '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -3005,7 +3004,7 @@ jsonapi_reply_library_artist(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
artist_id = hreq->uri_parsed->path_parts[3];
|
||||
artist_id = hreq->path_parts[3];
|
||||
|
||||
reply = fetch_artist(¬found, artist_id);
|
||||
if (!reply)
|
||||
@ -3040,7 +3039,7 @@ jsonapi_reply_library_artist_albums(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
artist_id = hreq->uri_parsed->path_parts[3];
|
||||
artist_id = hreq->path_parts[3];
|
||||
|
||||
reply = json_object_new_object();
|
||||
items = json_object_new_array();
|
||||
@ -3154,7 +3153,7 @@ jsonapi_reply_library_album(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
album_id = hreq->uri_parsed->path_parts[3];
|
||||
album_id = hreq->path_parts[3];
|
||||
|
||||
reply = fetch_album(¬found, album_id);
|
||||
if (!reply)
|
||||
@ -3189,7 +3188,7 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
album_id = hreq->uri_parsed->path_parts[3];
|
||||
album_id = hreq->path_parts[3];
|
||||
|
||||
reply = json_object_new_object();
|
||||
items = json_object_new_array();
|
||||
@ -3235,7 +3234,7 @@ jsonapi_reply_library_album_tracks_put_byid(struct httpd_request *hreq)
|
||||
int64_t album_id;;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi64(hreq->uri_parsed->path_parts[3], &album_id);
|
||||
ret = safe_atoi64(hreq->path_parts[3], &album_id);
|
||||
if (ret < 0)
|
||||
return HTTP_INTERNAL;
|
||||
|
||||
@ -3273,7 +3272,7 @@ jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
track_id = hreq->uri_parsed->path_parts[3];
|
||||
track_id = hreq->path_parts[3];
|
||||
|
||||
memset(&query_params, 0, sizeof(struct query_params));
|
||||
|
||||
@ -3399,7 +3398,7 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq)
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &track_id);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &track_id);
|
||||
if (ret < 0)
|
||||
return HTTP_INTERNAL;
|
||||
|
||||
@ -3470,7 +3469,7 @@ jsonapi_reply_library_track_playlists(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_MODIFIED))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
track_id = hreq->uri_parsed->path_parts[3];
|
||||
track_id = hreq->path_parts[3];
|
||||
if (safe_atoi32(track_id, &id) < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Error converting track id '%s' to int.\n", track_id);
|
||||
@ -3580,7 +3579,7 @@ jsonapi_reply_library_playlist_get(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
||||
ret = safe_atou32(hreq->path_parts[3], &playlist_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Could not parse playlist id to integer\n");
|
||||
@ -3651,7 +3650,7 @@ jsonapi_reply_library_playlist_put(struct httpd_request *hreq)
|
||||
const char *param;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
||||
ret = safe_atou32(hreq->path_parts[3], &playlist_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Could not parse playlist id to integer\n");
|
||||
@ -3682,10 +3681,10 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
|
||||
// Due to smart playlists possibly changing their tracks between rescans, disable caching in clients
|
||||
httpd_response_not_cachable(hreq);
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &playlist_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -3731,10 +3730,10 @@ jsonapi_reply_library_playlist_delete(struct httpd_request *hreq)
|
||||
uint32_t pl_id;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou32(hreq->uri_parsed->path_parts[3], &pl_id);
|
||||
ret = safe_atou32(hreq->path_parts[3], &pl_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -3758,10 +3757,10 @@ jsonapi_reply_library_playlist_playlists(struct httpd_request *hreq)
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &playlist_id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path);
|
||||
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->path);
|
||||
|
||||
return HTTP_BADREQUEST;
|
||||
}
|
||||
@ -3810,7 +3809,7 @@ jsonapi_reply_library_playlist_tracks_put_byid(struct httpd_request *hreq)
|
||||
int playlist_id;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id);
|
||||
ret = safe_atoi32(hreq->path_parts[3], &playlist_id);
|
||||
if (ret < 0)
|
||||
return HTTP_INTERNAL;
|
||||
|
||||
@ -3896,7 +3895,7 @@ jsonapi_reply_library_browse(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
browse_type = hreq->uri_parsed->path_parts[2];
|
||||
browse_type = hreq->path_parts[2];
|
||||
DPRINTF(E_DBG, L_WEB, "Browse query with type '%s'\n", browse_type);
|
||||
|
||||
media_kind = 0;
|
||||
@ -3977,8 +3976,8 @@ jsonapi_reply_library_browseitem(struct httpd_request *hreq)
|
||||
if (!is_modified(hreq, DB_ADMIN_DB_UPDATE))
|
||||
return HTTP_NOTMODIFIED;
|
||||
|
||||
browse_type = hreq->uri_parsed->path_parts[2];
|
||||
item_name = hreq->uri_parsed->path_parts[3];
|
||||
browse_type = hreq->path_parts[2];
|
||||
item_name = hreq->path_parts[3];
|
||||
DPRINTF(E_DBG, L_WEB, "Browse item query with type '%s'\n", browse_type);
|
||||
|
||||
reply = json_object_new_object();
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
@ -7,6 +9,15 @@
|
||||
#include "misc.h" // For net_evhttp_bind
|
||||
#include "httpd_internal.h"
|
||||
|
||||
struct httpd_uri_parsed
|
||||
{
|
||||
struct evhttp_uri *ev_uri;
|
||||
struct evkeyvalq query;
|
||||
char *path;
|
||||
httpd_uri_path_parts path_parts;
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
httpd_query_value_find(httpd_query *query, const char *key)
|
||||
{
|
||||
@ -62,9 +73,9 @@ httpd_connection_closecb_set(httpd_connection *conn, httpd_connection_closecb cb
|
||||
}
|
||||
|
||||
int
|
||||
httpd_connection_peer_get(char **addr, uint16_t *port, httpd_connection *conn)
|
||||
httpd_connection_peer_get(const char **addr, uint16_t *port, httpd_connection *conn)
|
||||
{
|
||||
evhttp_connection_get_peer(conn, addr, port);
|
||||
evhttp_connection_get_peer(conn, (char **)addr, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -194,7 +205,7 @@ httpd_backend_output_buffer_get(httpd_backend *backend)
|
||||
}
|
||||
|
||||
int
|
||||
httpd_backend_peer_get(char **addr, uint16_t *port, httpd_backend *backend)
|
||||
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend)
|
||||
{
|
||||
httpd_connection *conn = httpd_backend_connection_get(backend);
|
||||
if (!conn)
|
||||
@ -232,3 +243,87 @@ httpd_backend_preprocess(httpd_backend *backend)
|
||||
// It has side-effects on Connection: keep-alive
|
||||
backend->flags &= ~EVHTTP_PROXY_REQUEST;
|
||||
}
|
||||
|
||||
httpd_uri_parsed *
|
||||
httpd_uri_parsed_create(const char *uri)
|
||||
{
|
||||
struct httpd_uri_parsed *parsed;
|
||||
const char *query;
|
||||
char *path = NULL;
|
||||
char *path_part;
|
||||
char *ptr;
|
||||
int i;
|
||||
|
||||
parsed = calloc(1, sizeof(struct httpd_uri_parsed));
|
||||
if (!parsed)
|
||||
goto error;
|
||||
|
||||
parsed->ev_uri = evhttp_uri_parse_with_flags(uri, EVHTTP_URI_NONCONFORMANT);
|
||||
if (!parsed->ev_uri)
|
||||
goto error;
|
||||
|
||||
query = evhttp_uri_get_query(parsed->ev_uri);
|
||||
if (query && strchr(query, '=') && evhttp_parse_query_str(query, &(parsed->query)) < 0)
|
||||
goto error;
|
||||
|
||||
path = strdup(evhttp_uri_get_path(parsed->ev_uri));
|
||||
if (!path || !(parsed->path = evhttp_uridecode(path, 0, NULL)))
|
||||
goto error;
|
||||
|
||||
path_part = strtok_r(path, "/", &ptr);
|
||||
for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
|
||||
{
|
||||
parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL);
|
||||
path_part = strtok_r(NULL, "/", &ptr);
|
||||
}
|
||||
|
||||
// If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
|
||||
if (path_part)
|
||||
goto error;
|
||||
|
||||
free(path);
|
||||
return parsed;
|
||||
|
||||
error:
|
||||
free(path);
|
||||
httpd_uri_parsed_free(parsed);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
httpd_uri_parsed_free(httpd_uri_parsed *parsed)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!parsed)
|
||||
return;
|
||||
|
||||
free(parsed->path);
|
||||
for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
|
||||
free(parsed->path_parts[i]);
|
||||
|
||||
httpd_query_clear(&(parsed->query));
|
||||
|
||||
if (parsed->ev_uri)
|
||||
evhttp_uri_free(parsed->ev_uri);
|
||||
|
||||
free(parsed);
|
||||
}
|
||||
|
||||
httpd_query *
|
||||
httpd_uri_query_get(httpd_uri_parsed *parsed)
|
||||
{
|
||||
return &parsed->query;
|
||||
}
|
||||
|
||||
const char *
|
||||
httpd_uri_path_get(httpd_uri_parsed *parsed)
|
||||
{
|
||||
return parsed->path;
|
||||
}
|
||||
|
||||
void
|
||||
httpd_uri_path_parts_get(httpd_uri_path_parts *path_parts, httpd_uri_parsed *parsed)
|
||||
{
|
||||
memcpy(path_parts, parsed->path_parts, sizeof(httpd_uri_path_parts));
|
||||
}
|
||||
|
@ -502,7 +502,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id);
|
||||
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(hreq, "Invalid playlist ID");
|
||||
@ -683,31 +683,31 @@ rsp_reply_browse(struct httpd_request *hreq)
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
if (strcmp(hreq->uri_parsed->path_parts[3], "artist") == 0)
|
||||
if (strcmp(hreq->path_parts[3], "artist") == 0)
|
||||
{
|
||||
qp.type = Q_BROWSE_ARTISTS;
|
||||
}
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[3], "genre") == 0)
|
||||
else if (strcmp(hreq->path_parts[3], "genre") == 0)
|
||||
{
|
||||
qp.type = Q_BROWSE_GENRES;
|
||||
}
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[3], "album") == 0)
|
||||
else if (strcmp(hreq->path_parts[3], "album") == 0)
|
||||
{
|
||||
qp.type = Q_BROWSE_ALBUMS;
|
||||
}
|
||||
else if (strcmp(hreq->uri_parsed->path_parts[3], "composer") == 0)
|
||||
else if (strcmp(hreq->path_parts[3], "composer") == 0)
|
||||
{
|
||||
qp.type = Q_BROWSE_COMPOSERS;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->uri_parsed->path_parts[3]);
|
||||
DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->path_parts[3]);
|
||||
|
||||
rsp_send_error(hreq, "Unsupported browse type");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id);
|
||||
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(hreq, "Invalid playlist ID");
|
||||
@ -801,7 +801,7 @@ rsp_stream(struct httpd_request *hreq)
|
||||
int id;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &id);
|
||||
ret = safe_atoi32(hreq->path_parts[2], &id);
|
||||
if (ret < 0)
|
||||
{
|
||||
httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
|
||||
|
Loading…
x
Reference in New Issue
Block a user