From ab1bb5b7f1c3c3a7b240ab7fc8661459ecd7decb Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 20 Jul 2023 06:49:55 +0200 Subject: Adding upstream version 1.41.0. Signed-off-by: Daniel Baumann --- web/server/h2o/h2o_utils.c | 60 +++ web/server/h2o/h2o_utils.h | 38 ++ web/server/h2o/http_server.c | 339 +++++++++++++++++ web/server/h2o/http_server.h | 10 + web/server/static/static-threaded.c | 62 ++-- web/server/web_client.c | 702 +++++++++++++++++++++++++----------- web/server/web_client.h | 46 ++- web/server/web_client_cache.c | 32 +- web/server/web_server.c | 4 +- 9 files changed, 1007 insertions(+), 286 deletions(-) create mode 100644 web/server/h2o/h2o_utils.c create mode 100644 web/server/h2o/h2o_utils.h create mode 100644 web/server/h2o/http_server.c create mode 100644 web/server/h2o/http_server.h (limited to 'web/server') diff --git a/web/server/h2o/h2o_utils.c b/web/server/h2o/h2o_utils.c new file mode 100644 index 000000000..943216f59 --- /dev/null +++ b/web/server/h2o/h2o_utils.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "h2o_utils.h" + +#include "h2o/string_.h" + +#include "libnetdata/libnetdata.h" + +char *iovec_to_cstr(h2o_iovec_t *str) +{ + char *c_str = mallocz(str->len + 1); + memcpy(c_str, str->base, str->len); + c_str[str->len] = 0; + return c_str; +} + +#define KEY_VAL_BUFFER_GROWTH_STEP 5 +h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string) +{ + h2o_iovec_pair_vector_t *params_vec = h2o_mem_alloc_shared(pool, sizeof(h2o_iovec_pair_vector_t), NULL); + memset(params_vec, 0, sizeof(h2o_iovec_pair_vector_t)); + + h2o_iovec_pair_t param; + while ((param.name.base = (char*)h2o_next_token(¶ms_string, '&', ¶m.name.len, ¶m.value)) != NULL) { + if (params_vec->capacity == params_vec->size) + h2o_vector_reserve(pool, params_vec, params_vec->capacity + KEY_VAL_BUFFER_GROWTH_STEP); + + params_vec->entries[params_vec->size++] = param; + } + + return params_vec; +} + +h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len) +{ + for (size_t i = 0; i < params_vec->size; i++) { + h2o_iovec_pair_t *ret = ¶ms_vec->entries[i]; + if (h2o_memis(ret->name.base, ret->name.len, needle, needle_len)) + return ret; + } + return NULL; +} + +char *url_unescape(const char *url) +{ + char *result = mallocz(strlen(url) + 1); + + int i, j; + for (i = 0, j = 0; url[i] != 0; i++, j++) { + if (url[i] == '%' && isxdigit(url[i+1]) && isxdigit(url[i+2])) { + char hex[3] = { url[i+1], url[i+2], 0 }; + result[j] = strtol(hex, NULL, 16); + i += 2; + } else + result[j] = url[i]; + } + result[j] = 0; + + return result; +} diff --git a/web/server/h2o/h2o_utils.h b/web/server/h2o/h2o_utils.h new file mode 100644 index 000000000..6760ed9a9 --- /dev/null +++ b/web/server/h2o/h2o_utils.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_H2O_UTILS_H +#define NETDATA_H2O_UTILS_H + +#include "h2o/memory.h" + +#define __HAS_URL_PARAMS(reqptr) ((reqptr)->query_at != SIZE_MAX && ((reqptr)->path.len - (reqptr)->query_at > 1)) +#define IF_HAS_URL_PARAMS(reqptr) if __HAS_URL_PARAMS(reqptr) +#define UNLESS_HAS_URL_PARAMS(reqptr) if (!__HAS_URL_PARAMS(reqptr)) +#define URL_PARAMS_IOVEC_INIT(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at + 1], \ + .len = (reqptr)->path.len - (reqptr)->query_at - 1 } +#define URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at], \ + .len = (reqptr)->path.len - (reqptr)->query_at } + +#define PRINTF_H2O_IOVEC_FMT "%.*s" +#define PRINTF_H2O_IOVEC(iovec) ((int)(iovec)->len), ((iovec)->base) + +char *iovec_to_cstr(h2o_iovec_t *str); + +typedef struct h2o_iovec_pair { + h2o_iovec_t name; + h2o_iovec_t value; +} h2o_iovec_pair_t; + +typedef H2O_VECTOR(h2o_iovec_pair_t) h2o_iovec_pair_vector_t; + +// Takes the part of url behind ? (the url encoded parameters) +// and parse it to vector of name/value pairs without copying the actual strings +h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string); + +// Searches for parameter by name (provided in needle) +// returns pointer to it or NULL +h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len); + +char *url_unescape(const char *url); + +#endif /* NETDATA_H2O_UTILS_H */ diff --git a/web/server/h2o/http_server.c b/web/server/h2o/http_server.c new file mode 100644 index 000000000..3a46889c2 --- /dev/null +++ b/web/server/h2o/http_server.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "daemon/common.h" +#include "http_server.h" +#include "h2o.h" + +#include "h2o_utils.h" + +static h2o_globalconf_t config; +static h2o_context_t ctx; +static h2o_accept_ctx_t accept_ctx; + +#define CONTENT_JSON_UTF8 H2O_STRLIT("application/json; charset=utf-8") +#define CONTENT_TEXT_UTF8 H2O_STRLIT("text/plain; charset=utf-8") +#define NBUF_INITIAL_SIZE_RESP (4096) +#define API_V1_PREFIX "/api/v1/" +#define HOST_SELECT_PREFIX "/host/" + +#define HTTPD_CONFIG_SECTION "httpd" +#define HTTPD_ENABLED_DEFAULT false + +static void on_accept(h2o_socket_t *listener, const char *err) +{ + h2o_socket_t *sock; + + if (err != NULL) { + return; + } + + if ((sock = h2o_evloop_socket_accept(listener)) == NULL) + return; + h2o_accept(&accept_ctx, sock); +} + +static int create_listener(const char *ip, int port) +{ + struct sockaddr_in addr; + int fd, reuseaddr_flag = 1; + h2o_socket_t *sock; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip); + addr.sin_port = htons(port); + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 || + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_flag, sizeof(reuseaddr_flag)) != 0 || + bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, SOMAXCONN) != 0) { + return -1; + } + + sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ); + h2o_socket_read_start(sock, on_accept); + + return 0; +} + +static int ssl_init() +{ + if (!config_get_boolean(HTTPD_CONFIG_SECTION, "ssl", false)) + return 0; + + char default_fn[FILENAME_MAX + 1]; + + snprintfz(default_fn, FILENAME_MAX, "%s/ssl/key.pem", netdata_configured_user_config_dir); + const char *key_fn = config_get(HTTPD_CONFIG_SECTION, "ssl key", default_fn); + + snprintfz(default_fn, FILENAME_MAX, "%s/ssl/cert.pem", netdata_configured_user_config_dir); + const char *cert_fn = config_get(HTTPD_CONFIG_SECTION, "ssl certificate", default_fn); + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110 + accept_ctx.ssl_ctx = SSL_CTX_new(SSLv23_server_method()); +#else + accept_ctx.ssl_ctx = SSL_CTX_new(TLS_server_method()); +#endif + + SSL_CTX_set_options(accept_ctx.ssl_ctx, SSL_OP_NO_SSLv2); + + /* load certificate and private key */ + if (SSL_CTX_use_PrivateKey_file(accept_ctx.ssl_ctx, key_fn, SSL_FILETYPE_PEM) != 1) { + netdata_log_error("Could not load server key from \"%s\"", key_fn); + return -1; + } + if (SSL_CTX_use_certificate_file(accept_ctx.ssl_ctx, cert_fn, SSL_FILETYPE_PEM) != 1) { + netdata_log_error("Could not load certificate from \"%s\"", cert_fn); + return -1; + } + + h2o_ssl_register_alpn_protocols(accept_ctx.ssl_ctx, h2o_http2_alpn_protocols); + + netdata_log_info("SSL support enabled"); + + return 0; +} + +// I did not find a way to do wildcard paths to make common handler for urls like: +// /api/v1/info +// /host/child/api/v1/info +// /host/uuid/api/v1/info +// ideally we could do something like "/*/api/v1/info" subscription +// so we do it "manually" here with uberhandler +static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host) +{ + if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) + return -1; + + static h2o_generator_t generator = { NULL, NULL }; + + h2o_iovec_t norm_path = req->path_normalized; + + if (norm_path.len > strlen(HOST_SELECT_PREFIX) && !memcmp(norm_path.base, HOST_SELECT_PREFIX, strlen(HOST_SELECT_PREFIX))) { + h2o_iovec_t host_id; // host_id can be either and UUID or a hostname of the child + + norm_path.base += strlen(HOST_SELECT_PREFIX); + norm_path.len -= strlen(HOST_SELECT_PREFIX); + + host_id = norm_path; + + size_t end_loc = h2o_strstr(host_id.base, host_id.len, "/", 1); + if (end_loc != SIZE_MAX) { + host_id.len = end_loc; + norm_path.base += end_loc; + norm_path.len -= end_loc; + } + + char *c_host_id = iovec_to_cstr(&host_id); + *host = rrdhost_find_by_hostname(c_host_id); + if (!*host) + *host = rrdhost_find_by_guid(c_host_id); + if (!*host) { + req->res.status = HTTP_RESP_BAD_REQUEST; + req->res.reason = "Wrong host id"; + h2o_send_inline(req, H2O_STRLIT("Host id provided was not found!\n")); + freez(c_host_id); + return 0; + } + freez(c_host_id); + + // we have to rewrite URL here in case this is not an api call + // so that the subsequent file upload handler can send the correct + // files to the client + // if this is not an API call we will abort this handler later + // and let the internal serve file handler of h2o care for things + + if (end_loc == SIZE_MAX) { + req->path.len = 1; + req->path_normalized.len = 1; + } else { + size_t offset = norm_path.base - req->path_normalized.base; + req->path.len -= offset; + req->path.base += offset; + req->query_at -= offset; + req->path_normalized.len -= offset; + req->path_normalized.base += offset; + } + } + + // workaround for a dashboard bug which causes sometimes urls like + // "//api/v1/info" to be caled instead of "/api/v1/info" + if (norm_path.len > 2 && + norm_path.base[0] == '/' && + norm_path.base[1] == '/' ) { + norm_path.base++; + norm_path.len--; + } + + size_t api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V1_PREFIX)); + if (api_loc == SIZE_MAX) + return 1; + + h2o_iovec_t api_command = norm_path; + api_command.base += api_loc + strlen(API_V1_PREFIX); + api_command.len -= api_loc + strlen(API_V1_PREFIX); + + if (!api_command.len) + return 1; + + // this (emulating struct web_client) is a hack and will be removed + // in future PRs but needs bigger changes in old http_api_v1 + // we need to make the web_client_api_request_v1 to be web server + // agnostic and remove the old webservers dependency creep into the + // individual response generators and thus remove the need to "emulate" + // the old webserver calling this function here and in ACLK + struct web_client w; + w.response.data = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + w.response.header = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + w.url_query_string_decoded = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + w.acl = WEB_CLIENT_ACL_DASHBOARD; + + char *path_c_str = iovec_to_cstr(&api_command); + char *path_unescaped = url_unescape(path_c_str); + freez(path_c_str); + + IF_HAS_URL_PARAMS(req) { + h2o_iovec_t query_params = URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(req); + char *query_c_str = iovec_to_cstr(&query_params); + char *query_unescaped = url_unescape(query_c_str); + freez(query_c_str); + buffer_strcat(w.url_query_string_decoded, query_unescaped); + freez(query_unescaped); + } + + web_client_api_request_v1(*host, &w, path_unescaped); + freez(path_unescaped); + + h2o_iovec_t body = buffer_to_h2o_iovec(w.response.data); + + // we move msg body to req->pool managed memory as it has to + // live until whole response has been encrypted and sent + // when req is finished memory will be freed with the pool + void *managed = h2o_mem_alloc_shared(&req->pool, body.len, NULL); + memcpy(managed, body.base, body.len); + body.base = managed; + + req->res.status = HTTP_RESP_OK; + req->res.reason = "OK"; + if (w.response.data->content_type == CT_APPLICATION_JSON) + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_JSON_UTF8); + else + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8); + h2o_start_response(req, &generator); + h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL); + + buffer_free(w.response.data); + buffer_free(w.response.header); + buffer_free(w.url_query_string_decoded); + + return 0; +} + +static int netdata_uberhandler(h2o_handler_t *self, h2o_req_t *req) +{ + UNUSED(self); + RRDHOST *host = localhost; + + int ret = _netdata_uberhandler(req, &host); + + char host_uuid_str[UUID_STR_LEN]; + uuid_unparse_lower(host->host_uuid, host_uuid_str); + + if (!ret) { + netdata_log_access("HTTPD OK method: " PRINTF_H2O_IOVEC_FMT + ", path: " PRINTF_H2O_IOVEC_FMT + ", as host: %s" + ", response: %d", + PRINTF_H2O_IOVEC(&req->method), + PRINTF_H2O_IOVEC(&req->input.path), + host == localhost ? "localhost" : host_uuid_str, + req->res.status); + } else { + netdata_log_access("HTTPD %d" + " method: " PRINTF_H2O_IOVEC_FMT + ", path: " PRINTF_H2O_IOVEC_FMT + ", forwarding to file handler as path: " PRINTF_H2O_IOVEC_FMT, + ret, + PRINTF_H2O_IOVEC(&req->method), + PRINTF_H2O_IOVEC(&req->input.path), + PRINTF_H2O_IOVEC(&req->path)); + } + + return ret; +} + +static int hdl_netdata_conf(h2o_handler_t *self, h2o_req_t *req) +{ + UNUSED(self); + if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) + return -1; + + BUFFER *buf = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + config_generate(buf, 0); + + void *managed = h2o_mem_alloc_shared(&req->pool, buf->len, NULL); + memcpy(managed, buf->buffer, buf->len); + + req->res.status = HTTP_RESP_OK; + req->res.reason = "OK"; + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8); + h2o_send_inline(req, managed, buf->len); + buffer_free(buf); + + return 0; +} + +#define POLL_INTERVAL 100 + +void *h2o_main(void *ptr) { + struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; + + h2o_pathconf_t *pathconf; + h2o_hostconf_t *hostconf; + + netdata_thread_disable_cancelability(); + + const char *bind_addr = config_get(HTTPD_CONFIG_SECTION, "bind to", "127.0.0.1"); + int bind_port = config_get_number(HTTPD_CONFIG_SECTION, "port", 19998); + + h2o_config_init(&config); + hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), bind_port); + + pathconf = h2o_config_register_path(hostconf, "/netdata.conf", 0); + h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler)); + handler->on_req = hdl_netdata_conf; + + pathconf = h2o_config_register_path(hostconf, "/", 0); + handler = h2o_create_handler(pathconf, sizeof(*handler)); + handler->on_req = netdata_uberhandler; + h2o_file_register(pathconf, netdata_configured_web_dir, NULL, NULL, H2O_FILE_FLAG_SEND_COMPRESSED); + + h2o_context_init(&ctx, h2o_evloop_create(), &config); + + if(ssl_init()) { + error_report("SSL was requested but could not be properly initialized. Aborting."); + return NULL; + } + + accept_ctx.ctx = &ctx; + accept_ctx.hosts = config.hosts; + + if (create_listener(bind_addr, bind_port) != 0) { + netdata_log_error("failed to create listener %s:%d", bind_addr, bind_port); + return NULL; + } + + while (service_running(SERVICE_HTTPD)) { + int rc = h2o_evloop_run(ctx.loop, POLL_INTERVAL); + if (rc < 0 && errno != EINTR) { + netdata_log_error("h2o_evloop_run returned (%d) with errno other than EINTR. Aborting", rc); + break; + } + } + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; + return NULL; +} + +int httpd_is_enabled() { + return config_get_boolean(HTTPD_CONFIG_SECTION, "enabled", HTTPD_ENABLED_DEFAULT); +} diff --git a/web/server/h2o/http_server.h b/web/server/h2o/http_server.h new file mode 100644 index 000000000..c9e6f0c53 --- /dev/null +++ b/web/server/h2o/http_server.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef HTTP_SERVER_H +#define HTTP_SERVER_H + +void *h2o_main(void * ptr); + +int httpd_is_enabled(); + +#endif /* HTTP_SERVER_H */ diff --git a/web/server/static/static-threaded.c b/web/server/static/static-threaded.c index 4cb3dcd92..b0e691163 100644 --- a/web/server/static/static-threaded.c +++ b/web/server/static/static-threaded.c @@ -41,11 +41,11 @@ static struct web_client *web_client_create_on_fd(POLLINFO *pi) { int flag = 1; if(unlikely(web_client_check_tcp(w) && setsockopt(w->ifd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0)) - debug(D_WEB_CLIENT, "%llu: failed to enable TCP_NODELAY on socket fd %d.", w->id, w->ifd); + netdata_log_debug(D_WEB_CLIENT, "%llu: failed to enable TCP_NODELAY on socket fd %d.", w->id, w->ifd); flag = 1; if(unlikely(setsockopt(w->ifd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int)) != 0)) - debug(D_WEB_CLIENT, "%llu: failed to enable SO_KEEPALIVE on socket fd %d.", w->id, w->ifd); + netdata_log_debug(D_WEB_CLIENT, "%llu: failed to enable SO_KEEPALIVE on socket fd %d.", w->id, w->ifd); web_client_update_acl_matches(w); web_client_enable_wait_receive(w); @@ -101,7 +101,7 @@ static void *web_server_file_add_callback(POLLINFO *pi, short int *events, void worker_private->files_read++; - debug(D_WEB_CLIENT, "%llu: ADDED FILE READ ON FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: ADDED FILE READ ON FD %d", w->id, pi->fd); *events = POLLIN; pi->data = w; @@ -111,14 +111,14 @@ static void *web_server_file_add_callback(POLLINFO *pi, short int *events, void static void web_server_file_del_callback(POLLINFO *pi) { struct web_client *w = (struct web_client *)pi->data; - debug(D_WEB_CLIENT, "%llu: RELEASE FILE READ ON FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: RELEASE FILE READ ON FD %d", w->id, pi->fd); worker_is_busy(WORKER_JOB_DEL_FILE); w->pollinfo_filecopy_slot = 0; if(unlikely(!w->pollinfo_slot)) { - debug(D_WEB_CLIENT, "%llu: CROSS WEB CLIENT CLEANUP (iFD %d, oFD %d)", w->id, pi->fd, w->ofd); + netdata_log_debug(D_WEB_CLIENT, "%llu: CROSS WEB CLIENT CLEANUP (iFD %d, oFD %d)", w->id, pi->fd, w->ofd); web_server_log_connection(w, "DISCONNECTED"); web_client_request_done(w); web_client_release_to_cache(w); @@ -137,18 +137,18 @@ static int web_server_file_read_callback(POLLINFO *pi, short int *events) { // if there is no POLLINFO linked to this, it means the client disconnected // stop the file reading too if(unlikely(!w->pollinfo_slot)) { - debug(D_WEB_CLIENT, "%llu: PREVENTED ATTEMPT TO READ FILE ON FD %d, ON CLOSED WEB CLIENT", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: PREVENTED ATTEMPT TO READ FILE ON FD %d, ON CLOSED WEB CLIENT", w->id, pi->fd); retval = -1; goto cleanup; } if(unlikely(w->mode != WEB_CLIENT_MODE_FILECOPY || w->ifd == w->ofd)) { - debug(D_WEB_CLIENT, "%llu: PREVENTED ATTEMPT TO READ FILE ON FD %d, ON NON-FILECOPY WEB CLIENT", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: PREVENTED ATTEMPT TO READ FILE ON FD %d, ON NON-FILECOPY WEB CLIENT", w->id, pi->fd); retval = -1; goto cleanup; } - debug(D_WEB_CLIENT, "%llu: READING FILE ON FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: READING FILE ON FD %d", w->id, pi->fd); worker_private->file_reads++; ssize_t ret = unlikely(web_client_read_file(w)); @@ -157,12 +157,12 @@ static int web_server_file_read_callback(POLLINFO *pi, short int *events) { POLLJOB *p = pi->p; // our POLLJOB POLLINFO *wpi = pollinfo_from_slot(p, w->pollinfo_slot); // POLLINFO of the client socket - debug(D_WEB_CLIENT, "%llu: SIGNALING W TO SEND (iFD %d, oFD %d)", w->id, pi->fd, wpi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: SIGNALING W TO SEND (iFD %d, oFD %d)", w->id, pi->fd, wpi->fd); p->fds[wpi->slot].events |= POLLOUT; } if(unlikely(ret <= 0 || w->ifd == w->ofd)) { - debug(D_WEB_CLIENT, "%llu: DONE READING FILE ON FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: DONE READING FILE ON FD %d", w->id, pi->fd); retval = -1; goto cleanup; } @@ -180,7 +180,7 @@ static int web_server_file_write_callback(POLLINFO *pi, short int *events) { (void)events; worker_is_busy(WORKER_JOB_WRITE_FILE); - error("Writing to web files is not supported!"); + netdata_log_error("Writing to web files is not supported!"); worker_is_idle(); return -1; @@ -201,7 +201,7 @@ static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data *events = POLLIN; - debug(D_WEB_CLIENT_ACCESS, "LISTENER on %d: new connection.", pi->fd); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "LISTENER on %d: new connection.", pi->fd); struct web_client *w = web_client_create_on_fd(pi); if (!strncmp(pi->client_port, "UNIX", 4)) { @@ -240,7 +240,7 @@ static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data } #endif - debug(D_WEB_CLIENT, "%llu: ADDED CLIENT FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: ADDED CLIENT FD %d", w->id, pi->fd); cleanup: worker_is_idle(); @@ -260,13 +260,13 @@ static void web_server_del_callback(POLLINFO *pi) { POLLINFO *fpi = pollinfo_from_slot(pi->p, w->pollinfo_filecopy_slot); // POLLINFO of the client socket (void)fpi; - debug(D_WEB_CLIENT, "%llu: THE CLIENT WILL BE FRED BY READING FILE JOB ON FD %d", w->id, fpi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: THE CLIENT WILL BE FRED BY READING FILE JOB ON FD %d", w->id, fpi->fd); } else { if(web_client_flag_check(w, WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET)) pi->flags |= POLLINFO_FLAG_DONT_CLOSE; - debug(D_WEB_CLIENT, "%llu: CLOSING CLIENT FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: CLOSING CLIENT FD %d", w->id, pi->fd); web_server_log_connection(w, "DISCONNECTED"); web_client_request_done(w); web_client_release_to_cache(w); @@ -289,7 +289,7 @@ static int web_server_rcv_callback(POLLINFO *pi, short int *events) { bytes = web_client_receive(w); if (likely(bytes > 0)) { - debug(D_WEB_CLIENT, "%llu: processing received data on fd %d.", w->id, fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: processing received data on fd %d.", w->id, fd); worker_is_idle(); worker_is_busy(WORKER_JOB_PROCESS); web_client_process_request(w); @@ -300,11 +300,11 @@ static int web_server_rcv_callback(POLLINFO *pi, short int *events) { else if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) { if(w->pollinfo_filecopy_slot == 0) { - debug(D_WEB_CLIENT, "%llu: FILECOPY DETECTED ON FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: FILECOPY DETECTED ON FD %d", w->id, pi->fd); if (unlikely(w->ifd != -1 && w->ifd != w->ofd && w->ifd != fd)) { // add a new socket to poll_events, with the same - debug(D_WEB_CLIENT, "%llu: CREATING FILECOPY SLOT ON FD %d", w->id, pi->fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: CREATING FILECOPY SLOT ON FD %d", w->id, pi->fd); POLLINFO *fpi = poll_add_fd( pi->p @@ -325,7 +325,7 @@ static int web_server_rcv_callback(POLLINFO *pi, short int *events) { if(fpi) w->pollinfo_filecopy_slot = fpi->slot; else { - error("Failed to add filecopy fd. Closing client."); + netdata_log_error("Failed to add filecopy fd. Closing client."); ret = -1; goto cleanup; } @@ -366,7 +366,7 @@ static int web_server_snd_callback(POLLINFO *pi, short int *events) { struct web_client *w = (struct web_client *)pi->data; int fd = pi->fd; - debug(D_WEB_CLIENT, "%llu: sending data on fd %d.", w->id, fd); + netdata_log_debug(D_WEB_CLIENT, "%llu: sending data on fd %d.", w->id, fd); int ret = web_client_send(w); @@ -394,7 +394,7 @@ cleanup: static void socket_listen_main_static_threaded_worker_cleanup(void *ptr) { worker_private = (struct web_server_static_threaded_worker *)ptr; - info("stopped after %zu connects, %zu disconnects (max concurrent %zu), %zu receptions and %zu sends", + netdata_log_info("stopped after %zu connects, %zu disconnects (max concurrent %zu), %zu receptions and %zu sends", worker_private->connected, worker_private->disconnected, worker_private->max_concurrent, @@ -462,16 +462,16 @@ static void socket_listen_main_static_threaded_cleanup(void *ptr) { // for(i = 1; i < static_threaded_workers_count; i++) { // if(static_workers_private_data[i].running) { // found++; -// info("stopping worker %d", i + 1); +// netdata_log_info("stopping worker %d", i + 1); // netdata_thread_cancel(static_workers_private_data[i].thread); // } // else -// info("found stopped worker %d", i + 1); +// netdata_log_info("found stopped worker %d", i + 1); // } // // while(found && max > 0) { // max -= step; -// info("Waiting %d static web threads to finish...", found); +// netdata_log_info("Waiting %d static web threads to finish...", found); // sleep_usec(step); // found = 0; // @@ -483,12 +483,12 @@ static void socket_listen_main_static_threaded_cleanup(void *ptr) { // } // // if(found) -// error("%d static web threads are taking too long to finish. Giving up.", found); +// netdata_log_error("%d static web threads are taking too long to finish. Giving up.", found); - info("closing all web server sockets..."); + netdata_log_info("closing all web server sockets..."); listen_sockets_close(&api_sockets); - info("all static web threads stopped."); + netdata_log_info("all static web threads stopped."); static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; } @@ -502,7 +502,7 @@ void *socket_listen_main_static_threaded(void *ptr) { netdata_ssl_validate_certificate = !config_get_boolean(CONFIG_SECTION_WEB, "ssl skip certificate verification", !netdata_ssl_validate_certificate); if(!netdata_ssl_validate_certificate_sender) - info("SSL: web server will skip SSL certificates verification."); + netdata_log_info("SSL: web server will skip SSL certificates verification."); #ifdef ENABLE_HTTPS netdata_ssl_initialize_ctx(NETDATA_SSL_WEB_SERVER_CTX); @@ -514,7 +514,7 @@ void *socket_listen_main_static_threaded(void *ptr) { int def_thread_count = MIN(get_netdata_cpus(), 6); if (!strcmp(config_get(CONFIG_SECTION_WEB, "mode", ""),"single-threaded")) { - info("Running web server with one thread, because mode is single-threaded"); + netdata_log_info("Running web server with one thread, because mode is single-threaded"); config_set(CONFIG_SECTION_WEB, "mode", "static-threaded"); def_thread_count = 1; } @@ -526,7 +526,7 @@ void *socket_listen_main_static_threaded(void *ptr) { // See https://github.com/netdata/netdata/issues/11081#issuecomment-831998240 for more details if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110) { static_threaded_workers_count = 1; - info("You are running an OpenSSL older than 1.1.0, web server will not enable multithreading."); + netdata_log_info("You are running an OpenSSL older than 1.1.0, web server will not enable multithreading."); } #endif @@ -546,7 +546,7 @@ void *socket_listen_main_static_threaded(void *ptr) { char tag[50 + 1]; snprintfz(tag, 50, "WEB[%d]", i+1); - info("starting worker %d", i+1); + netdata_log_info("starting worker %d", i+1); netdata_thread_create(&static_workers_private_data[i].thread, tag, NETDATA_THREAD_OPTION_DEFAULT, socket_listen_main_static_threaded_worker, (void *)&static_workers_private_data[i]); } diff --git a/web/server/web_client.c b/web/server/web_client.c index 5dcff0b0f..1a1d63155 100644 --- a/web/server/web_client.c +++ b/web/server/web_client.c @@ -18,12 +18,28 @@ inline int web_client_permission_denied(struct web_client *w) { return HTTP_RESP_FORBIDDEN; } -static inline int web_client_crock_socket(struct web_client *w __maybe_unused) { +inline int web_client_bearer_required(struct web_client *w) { + w->response.data->content_type = CT_TEXT_PLAIN; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "An authorization bearer is required to access the resource."); + w->response.code = HTTP_RESP_UNAUTHORIZED; + return HTTP_RESP_UNAUTHORIZED; +} + +static inline int bad_request_multiple_dashboard_versions(struct web_client *w) { + w->response.data->content_type = CT_TEXT_PLAIN; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "Multiple dashboard versions given at the URL."); + w->response.code = HTTP_RESP_BAD_REQUEST; + return HTTP_RESP_BAD_REQUEST; +} + +static inline int web_client_cork_socket(struct web_client *w __maybe_unused) { #ifdef TCP_CORK if(likely(web_client_is_corkable(w) && !w->tcp_cork && w->ofd != -1)) { w->tcp_cork = true; if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) { - error("%llu: failed to enable TCP_CORK on socket.", w->id); + netdata_log_error("%llu: failed to enable TCP_CORK on socket.", w->id); w->tcp_cork = false; return -1; @@ -45,11 +61,12 @@ static inline void web_client_enable_wait_from_ssl(struct web_client *w) { } } -static inline int web_client_uncrock_socket(struct web_client *w __maybe_unused) { +static inline int web_client_uncork_socket(struct web_client *w __maybe_unused) { #ifdef TCP_CORK if(likely(w->tcp_cork && w->ofd != -1)) { + w->tcp_cork = false; if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) { - error("%llu: failed to disable TCP_CORK on socket.", w->id); + netdata_log_error("%llu: failed to disable TCP_CORK on socket.", w->id); w->tcp_cork = true; return -1; } @@ -140,12 +157,14 @@ static void web_client_reset_allocations(struct web_client *w, bool free_all) { w->response.zinitialized = false; w->flags &= ~WEB_CLIENT_CHUNKED_TRANSFER; } + + web_client_reset_path_flags(w); } void web_client_request_done(struct web_client *w) { - web_client_uncrock_socket(w); + web_client_uncork_socket(w); - debug(D_WEB_CLIENT, "%llu: Resetting client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Resetting client.", w->id); if(likely(buffer_strlen(w->url_as_received))) { struct timeval tv; @@ -195,7 +214,7 @@ void web_client_request_done(struct web_client *w) { } // access log - log_access("%llu: %d '[%s]:%s' '%s' (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %d '%s'", + netdata_log_access("%llu: %d '[%s]:%s' '%s' (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %d '%s'", w->id , gettid() , w->client_ip @@ -214,7 +233,7 @@ void web_client_request_done(struct web_client *w) { if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) { if(w->ifd != w->ofd) { - debug(D_WEB_CLIENT, "%llu: Closing filecopy input file descriptor %d.", w->id, w->ifd); + netdata_log_debug(D_WEB_CLIENT, "%llu: Closing filecopy input file descriptor %d.", w->id, w->ifd); if(web_server_mode != WEB_SERVER_MODE_STATIC_THREADED) { if (w->ifd != -1){ @@ -275,7 +294,7 @@ static struct { }; static inline uint8_t contenttype_for_filename(const char *filename) { - // info("checking filename '%s'", filename); + // netdata_log_info("checking filename '%s'", filename); static int initialized = 0; int i; @@ -296,36 +315,172 @@ static inline uint8_t contenttype_for_filename(const char *filename) { } if(unlikely(!last_dot || !*last_dot || !last_dot[1])) { - // info("no extension for filename '%s'", filename); + // netdata_log_info("no extension for filename '%s'", filename); return CT_APPLICATION_OCTET_STREAM; } last_dot++; - // info("extension for filename '%s' is '%s'", filename, last_dot); + // netdata_log_info("extension for filename '%s' is '%s'", filename, last_dot); uint32_t hash = simple_hash(last_dot); for(i = 0; mime_types[i].extension ; i++) { if(unlikely(hash == mime_types[i].hash && !strcmp(last_dot, mime_types[i].extension))) { - // info("matched extension for filename '%s': '%s'", filename, last_dot); + // netdata_log_info("matched extension for filename '%s': '%s'", filename, last_dot); return mime_types[i].contenttype; } } - // info("not matched extension for filename '%s': '%s'", filename, last_dot); + // netdata_log_info("not matched extension for filename '%s': '%s'", filename, last_dot); return CT_APPLICATION_OCTET_STREAM; } -static inline int access_to_file_is_not_permitted(struct web_client *w, const char *filename) { +static int append_slash_to_url_and_redirect(struct web_client *w) { + // this function returns a relative redirect + // it finds the last path component on the URL and just appends / to it + // + // So, if the URL is: + // + // /path/to/file?query_string + // + // It adds a Location header like this: + // + // Location: file/?query_string\r\n + // + // The web browser already knows that it is inside /path/to/ + // so it converts the path to /path/to/file/ and executes the + // request again. + + buffer_strcat(w->response.header, "Location: "); + const char *b = buffer_tostring(w->url_as_received); + const char *q = strchr(b, '?'); + if(q && q > b) { + const char *e = q - 1; + while(e > b && *e != '/') e--; + if(*e == '/') e++; + + size_t len = q - e; + buffer_strncat(w->response.header, e, len); + buffer_strncat(w->response.header, "/", 1); + buffer_strcat(w->response.header, q); + } + else { + const char *e = &b[buffer_strlen(w->url_as_received) - 1]; + while(e > b && *e != '/') e--; + if(*e == '/') e++; + + buffer_strcat(w->response.header, e); + buffer_strncat(w->response.header, "/", 1); + } + + buffer_strncat(w->response.header, "\r\n", 2); + w->response.data->content_type = CT_TEXT_HTML; - buffer_strcat(w->response.data, "Access to file is not permitted: "); - buffer_strcat_htmlescape(w->response.data, filename); - return HTTP_RESP_FORBIDDEN; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, + "" + "" + "Redirecting. In case your browser does not support redirection, please click " + "here." + ""); + return HTTP_RESP_MOVED_PERM; } // Work around a bug in the CMocka library by removing this function during testing. #ifndef REMOVE_MYSENDFILE -int mysendfile(struct web_client *w, char *filename) { - debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename); + +static inline int dashboard_version(struct web_client *w) { + if(!web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION)) + return -1; + + if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_V0)) + return 0; + if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_V1)) + return 1; + if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_V2)) + return 2; + + return -1; +} + +static bool find_filename_to_serve(const char *filename, char *dst, size_t dst_len, struct stat *statbuf, struct web_client *w, bool *is_dir) { + int d_version = dashboard_version(w); + bool has_extension = web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION); + + int fallback = 0; + + if(has_extension) { + if(d_version == -1) + snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename); + else { + // check if the filename or directory exists + // fallback to the same path without the dashboard version otherwise + snprintfz(dst, dst_len, "%s/v%d/%s", netdata_configured_web_dir, d_version, filename); + fallback = 1; + } + } + else if(d_version != -1) { + if(filename && *filename) { + // check if the filename exists + // fallback to /vN/index.html otherwise + snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename); + fallback = 2; + } + else { + if(filename && *filename) + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH); + snprintfz(dst, dst_len, "%s/v%d", netdata_configured_web_dir, d_version); + } + } + else { + // check if filename exists + // this is needed to serve {filename}/index.html, in case a user puts a html file into a directory + // fallback to /index.html otherwise + snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename); + fallback = 3; + } + + if (stat(dst, statbuf) != 0) { + if(fallback == 1) { + snprintfz(dst, dst_len, "%s/%s", netdata_configured_web_dir, filename); + if (stat(dst, statbuf) != 0) + return false; + } + else if(fallback == 2) { + if(filename && *filename) + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH); + snprintfz(dst, dst_len, "%s/v%d", netdata_configured_web_dir, d_version); + if (stat(dst, statbuf) != 0) + return false; + } + else if(fallback == 3) { + if(filename && *filename) + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH); + snprintfz(dst, dst_len, "%s", netdata_configured_web_dir); + if (stat(dst, statbuf) != 0) + return false; + } + else + return false; + } + + if((statbuf->st_mode & S_IFMT) == S_IFDIR) { + size_t len = strlen(dst); + if(len > dst_len - 11) + return false; + + strncpyz(&dst[len], "/index.html", dst_len - len); + + if (stat(dst, statbuf) != 0) + return false; + + *is_dir = true; + } + + return true; +} + +static int mysendfile(struct web_client *w, char *filename) { + netdata_log_debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename); if(!web_client_can_access_dashboard(w)) return web_client_permission_denied(w); @@ -337,7 +492,7 @@ int mysendfile(struct web_client *w, char *filename) { char *s; for(s = filename; *s ;s++) { if( !isalnum(*s) && *s != '/' && *s != '.' && *s != '-' && *s != '_') { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); w->response.data->content_type = CT_TEXT_HTML; buffer_sprintf(w->response.data, "Filename contains invalid characters: "); buffer_strcat_htmlescape(w->response.data, filename); @@ -347,7 +502,7 @@ int mysendfile(struct web_client *w, char *filename) { // if the filename contains a double dot refuse to serve it if(strstr(filename, "..") != 0) { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); w->response.data->content_type = CT_TEXT_HTML; buffer_strcat(w->response.data, "Relative filenames are not supported: "); buffer_strcat_htmlescape(w->response.data, filename); @@ -355,60 +510,45 @@ int mysendfile(struct web_client *w, char *filename) { } // find the physical file on disk - char webfilename[FILENAME_MAX + 1]; - snprintfz(webfilename, FILENAME_MAX, "%s/%s", netdata_configured_web_dir, filename); - + bool is_dir = false; + char web_filename[FILENAME_MAX + 1]; struct stat statbuf; - int done = 0; - while(!done) { - // check if the file exists - if (lstat(webfilename, &statbuf) != 0) { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, webfilename); - w->response.data->content_type = CT_TEXT_HTML; - buffer_strcat(w->response.data, "File does not exist, or is not accessible: "); - buffer_strcat_htmlescape(w->response.data, webfilename); - return HTTP_RESP_NOT_FOUND; - } - - if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { - snprintfz(webfilename, FILENAME_MAX, "%s/%s/index.html", netdata_configured_web_dir, filename); - continue; - } - - if ((statbuf.st_mode & S_IFMT) != S_IFREG) { - error("%llu: File '%s' is not a regular file. Access Denied.", w->id, webfilename); - return access_to_file_is_not_permitted(w, webfilename); - } - - done = 1; + if(!find_filename_to_serve(filename, web_filename, FILENAME_MAX, &statbuf, w, &is_dir)) { + w->response.data->content_type = CT_TEXT_HTML; + buffer_strcat(w->response.data, "File does not exist, or is not accessible: "); + buffer_strcat_htmlescape(w->response.data, web_filename); + return HTTP_RESP_NOT_FOUND; } + if(is_dir && !web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH)) + return append_slash_to_url_and_redirect(w); + // open the file - w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY); + w->ifd = open(web_filename, O_NONBLOCK, O_RDONLY); if(w->ifd == -1) { w->ifd = w->ofd; if(errno == EBUSY || errno == EAGAIN) { - error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename); + netdata_log_error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, web_filename); w->response.data->content_type = CT_TEXT_HTML; buffer_sprintf(w->response.header, "Location: /%s\r\n", filename); buffer_strcat(w->response.data, "File is currently busy, please try again later: "); - buffer_strcat_htmlescape(w->response.data, webfilename); + buffer_strcat_htmlescape(w->response.data, web_filename); return HTTP_RESP_REDIR_TEMP; } else { - error("%llu: Cannot open file '%s'.", w->id, webfilename); + netdata_log_error("%llu: Cannot open file '%s'.", w->id, web_filename); w->response.data->content_type = CT_TEXT_HTML; buffer_strcat(w->response.data, "Cannot open file: "); - buffer_strcat_htmlescape(w->response.data, webfilename); + buffer_strcat_htmlescape(w->response.data, web_filename); return HTTP_RESP_NOT_FOUND; } } sock_setnonblock(w->ifd); - w->response.data->content_type = contenttype_for_filename(webfilename); - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%"PRId64" bytes, ifd %d, ofd %d).", w->id, webfilename, (int64_t)statbuf.st_size, w->ifd, w->ofd); + w->response.data->content_type = contenttype_for_filename(web_filename); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%"PRId64" bytes, ifd %d, ofd %d).", w->id, web_filename, (int64_t)statbuf.st_size, w->ifd, w->ofd); w->mode = WEB_CLIENT_MODE_FILECOPY; web_client_enable_wait_receive(w); @@ -429,12 +569,12 @@ int mysendfile(struct web_client *w, char *filename) { void web_client_enable_deflate(struct web_client *w, int gzip) { if(unlikely(w->response.zinitialized)) { - debug(D_DEFLATE, "%llu: Compression has already be initialized for this client.", w->id); + netdata_log_debug(D_DEFLATE, "%llu: Compression has already be initialized for this client.", w->id); return; } if(unlikely(w->response.sent)) { - error("%llu: Cannot enable compression in the middle of a conversation.", w->id); + netdata_log_error("%llu: Cannot enable compression in the middle of a conversation.", w->id); return; } @@ -455,13 +595,13 @@ void web_client_enable_deflate(struct web_client *w, int gzip) { w->response.zstream.opaque = Z_NULL; // if(deflateInit(&w->response.zstream, Z_DEFAULT_COMPRESSION) != Z_OK) { -// error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); +// netdata_log_error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); // return; // } // Select GZIP compression: windowbits = 15 + 16 = 31 if(deflateInit2(&w->response.zstream, web_gzip_level, Z_DEFLATED, 15 + ((gzip)?16:0), 8, web_gzip_strategy) != Z_OK) { - error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); + netdata_log_error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); return; } @@ -470,7 +610,7 @@ void web_client_enable_deflate(struct web_client *w, int gzip) { w->response.zinitialized = true; w->flags |= WEB_CLIENT_CHUNKED_TRANSFER; - debug(D_DEFLATE, "%llu: Initialized compression.", w->id); + netdata_log_debug(D_DEFLATE, "%llu: Initialized compression.", w->id); } void buffer_data_options2string(BUFFER *wb, uint32_t options) { @@ -571,7 +711,7 @@ int web_client_api_request(RRDHOST *host, struct web_client *w, char *url_path_f // get the api version char *tok = strsep_skip_consecutive_separators(&url_path_fragment, "/"); if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); + netdata_log_debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); if(strcmp(tok, "v2") == 0) return web_client_api_request_v2(host, w, url_path_fragment); else if(strcmp(tok, "v1") == 0) @@ -603,7 +743,7 @@ const char *web_content_type_to_string(HTTP_CONTENT_TYPE content_type) { return "application/json; charset=utf-8"; case CT_APPLICATION_X_JAVASCRIPT: - return "application/x-javascript; charset=utf-8"; + return "application/javascript; charset=utf-8"; case CT_TEXT_CSS: return "text/css; charset=utf-8"; @@ -656,35 +796,159 @@ const char *web_content_type_to_string(HTTP_CONTENT_TYPE content_type) { case CT_PROMETHEUS: return "text/plain; version=0.0.4"; + case CT_AUDIO_MPEG: + return "audio/mpeg"; + + case CT_AUDIO_OGG: + return "audio/ogg"; + + case CT_VIDEO_MP4: + return "video/mp4"; + + case CT_APPLICATION_PDF: + return "application/pdf"; + + case CT_APPLICATION_ZIP: + return "application/zip"; + default: case CT_TEXT_PLAIN: return "text/plain; charset=utf-8"; } } - const char *web_response_code_to_string(int code) { switch(code) { - case HTTP_RESP_OK: + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + + case 200: return "OK"; - - case HTTP_RESP_MOVED_PERM: + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + + case 300: + return "Multiple Choices"; + case 301: return "Moved Permanently"; - - case HTTP_RESP_REDIR_TEMP: + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 306: + return "Switch Proxy"; + case 307: return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; - case HTTP_RESP_BAD_REQUEST: + case 400: return "Bad Request"; - - case HTTP_RESP_FORBIDDEN: + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: return "Forbidden"; - - case HTTP_RESP_NOT_FOUND: + case 404: return "Not Found"; - - case HTTP_RESP_PRECOND_FAIL: - return "Preconditions Failed"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Entity"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; default: if(code >= 100 && code < 200) @@ -697,7 +961,7 @@ const char *web_response_code_to_string(int code) { return "Redirection"; if(code >= 400 && code < 500) - return "Bad Request"; + return "Client Error"; if(code >= 500 && code < 600) return "Server Error"; @@ -708,7 +972,7 @@ const char *web_response_code_to_string(int code) { static inline char *http_header_parse(struct web_client *w, char *s, int parse_useragent) { static uint32_t hash_origin = 0, hash_connection = 0, hash_donottrack = 0, hash_useragent = 0, - hash_authorization = 0, hash_host = 0, hash_forwarded_proto = 0, hash_forwarded_host = 0; + hash_authorization = 0, hash_host = 0, hash_forwarded_host = 0; static uint32_t hash_accept_encoding = 0; if(unlikely(!hash_origin)) { @@ -719,7 +983,6 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u hash_useragent = simple_uhash("User-Agent"); hash_authorization = simple_uhash("X-Auth-Token"); hash_host = simple_uhash("Host"); - hash_forwarded_proto = simple_uhash("X-Forwarded-Proto"); hash_forwarded_host = simple_uhash("X-Forwarded-Host"); } @@ -783,10 +1046,6 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u // web_client_enable_deflate(w, 0); } } - else if(hash == hash_forwarded_proto && !strcasecmp(s, "X-Forwarded-Proto")) { - if(strcasestr(v, "https")) - w->flags |= WEB_CLIENT_FLAG_PROXY_HTTPS; - } else if(hash == hash_forwarded_host && !strcasecmp(s, "X-Forwarded-Host")) { char buffer[NI_MAXHOST]; strncpyz(buffer, v, ((size_t)(ve - v) < sizeof(buffer) - 1 ? (size_t)(ve - v) : sizeof(buffer) - 1)); @@ -850,7 +1109,7 @@ static inline char *web_client_valid_method(struct web_client *w, char *s) { memcpy(hostname,"not available",13); hostname[13] = 0x00; } - error("The server is configured to always use encrypted connections, please enable the SSL on child with hostname '%s'.",hostname); + netdata_log_error("The server is configured to always use encrypted connections, please enable the SSL on child with hostname '%s'.",hostname); s = NULL; } #endif @@ -891,7 +1150,7 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size, &w->post_payload, &w->post_payload_size); if(!is_it_valid) { if(w->header_parse_tries > HTTP_REQ_MAX_HEADER_FETCH_TRIES) { - info("Disabling slow client after %zu attempts to read the request (%zu bytes received)", w->header_parse_tries, buffer_strlen(w->response.data)); + netdata_log_info("Disabling slow client after %zu attempts to read the request (%zu bytes received)", w->header_parse_tries, buffer_strlen(w->response.data)); w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); @@ -1017,16 +1276,19 @@ void web_client_build_http_header(struct web_client *w) { if(unlikely(w->response.code != HTTP_RESP_OK)) buffer_no_cacheable(w->response.data); + if(unlikely(!w->response.data->date)) + w->response.data->date = now_realtime_sec(); + // set a proper expiration date, if not already set if(unlikely(!w->response.data->expires)) { if(w->response.data->options & WB_CONTENT_NO_CACHEABLE) - w->response.data->expires = w->timings.tv_ready.tv_sec + localhost->rrd_update_every; + w->response.data->expires = w->response.data->date + localhost->rrd_update_every; else - w->response.data->expires = w->timings.tv_ready.tv_sec + 86400; + w->response.data->expires = w->response.data->date + 86400; } // prepare the HTTP response header - debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, w->response.code); + netdata_log_debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, w->response.code); const char *content_type_string = web_content_type_to_string(w->response.data->content_type); const char *code_msg = web_response_code_to_string(w->response.code); @@ -1043,14 +1305,16 @@ void web_client_build_http_header(struct web_client *w) { strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", tm); } - if (w->response.code == HTTP_RESP_MOVED_PERM) { + if (w->response.code == HTTP_RESP_HTTPS_UPGRADE) { buffer_sprintf(w->response.header_output, "HTTP/1.1 %d %s\r\n" "Location: https://%s%s\r\n", w->response.code, code_msg, w->server_host ? w->server_host : "", buffer_tostring(w->url_as_received)); - }else { + w->response.code = HTTP_RESP_MOVED_PERM; + } + else { buffer_sprintf(w->response.header_output, "HTTP/1.1 %d %s\r\n" "Connection: %s\r\n" @@ -1131,13 +1395,13 @@ static inline void web_client_send_http_header(struct web_client *w) { web_client_build_http_header(w); // sent the HTTP header - debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %zu: '%s'" + netdata_log_debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %zu: '%s'" , w->id , buffer_strlen(w->response.header_output) , buffer_tostring(w->response.header_output) ); - web_client_crock_socket(w); + web_client_cork_socket(w); size_t count = 0; ssize_t bytes; @@ -1152,7 +1416,7 @@ static inline void web_client_send_http_header(struct web_client *w) { count++; if(count > 100 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - error("Cannot send HTTP headers to web client."); + netdata_log_error("Cannot send HTTP headers to web client."); break; } } @@ -1163,7 +1427,7 @@ static inline void web_client_send_http_header(struct web_client *w) { count++; if(count > 100 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - error("Cannot send HTTP headers to web client."); + netdata_log_error("Cannot send HTTP headers to web client."); break; } } @@ -1173,7 +1437,7 @@ static inline void web_client_send_http_header(struct web_client *w) { count++; if(count > 100 || (errno != EAGAIN && errno != EWOULDBLOCK)) { - error("Cannot send HTTP headers to web client."); + netdata_log_error("Cannot send HTTP headers to web client."); break; } } @@ -1184,8 +1448,7 @@ static inline void web_client_send_http_header(struct web_client *w) { w->statistics.sent_bytes += bytes; if (bytes < 0) { - - error("HTTP headers failed to be sent (I sent %zu bytes but the system sent %zd bytes). Closing web client." + netdata_log_error("HTTP headers failed to be sent (I sent %zu bytes but the system sent %zd bytes). Closing web client." , buffer_strlen(w->response.header_output) , bytes); @@ -1212,7 +1475,7 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch char *tok = strsep_skip_consecutive_separators(&url, "/"); if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for host with name '%s'.", w->id, tok); + netdata_log_debug(D_WEB_CLIENT, "%llu: Searching for host with name '%s'.", w->id, tok); if(nodeid) { host = find_host_by_node_id(tok); @@ -1243,36 +1506,9 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch } if (host) { - if(!url) { //no delim found - debug(D_WEB_CLIENT, "%llu: URL doesn't end with / generating redirect.", w->id); - char *protocol, *url_host; - protocol = ( -#ifdef ENABLE_HTTPS - SSL_connection(&w->ssl) || -#endif - (w->flags & WEB_CLIENT_FLAG_PROXY_HTTPS)) ? "https" : "http"; - - url_host = w->forwarded_host; - if(!url_host) { - url_host = w->server_host; - if(!url_host) url_host = ""; - } - - buffer_sprintf(w->response.header, "Location: %s://%s/%s/%s/%s", - protocol, url_host, nodeid?"node":"host", tok, buffer_tostring(w->url_path_decoded)); - - if(buffer_strlen(w->url_query_string_decoded)) { - const char *query_string = buffer_tostring(w->url_query_string_decoded); - if(*query_string) { - if(*query_string != '?') - buffer_fast_strcat(w->response.header, "?", 1); - buffer_strcat(w->response.header, query_string); - } - } - buffer_fast_strcat(w->response.header, "\r\n", 2); - buffer_strcat(w->response.data, "Permanent redirect"); - return HTTP_RESP_REDIR_PERM; - } + if(!url) + //no delim found + return append_slash_to_url_and_redirect(w); size_t len = strlen(url) + 2; char buf[len]; @@ -1311,12 +1547,12 @@ int web_client_api_request_with_node_selection(RRDHOST *host, struct web_client if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) { // current API - debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id); return check_host_and_call(host, w, decoded_url_path, web_client_api_request); } else if(unlikely((hash == hash_host && strcmp(tok, "host") == 0) || (hash == hash_node && strcmp(tok, "node") == 0))) { // host switching - debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id); return web_client_switch_host(host, w, decoded_url_path, hash == hash_node, web_client_api_request_with_node_selection); } } @@ -1335,7 +1571,10 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch hash_api = 0, hash_netdata_conf = 0, hash_host = 0, - hash_node = 0; + hash_node = 0, + hash_v0 = 0, + hash_v1 = 0, + hash_v2 = 0; #ifdef NETDATA_INTERNAL_CHECKS static uint32_t hash_exit = 0, hash_debug = 0, hash_mirror = 0; @@ -1346,6 +1585,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch hash_netdata_conf = simple_hash("netdata.conf"); hash_host = simple_hash("host"); hash_node = simple_hash("node"); + hash_v0 = simple_hash("v0"); + hash_v1 = simple_hash("v1"); + hash_v2 = simple_hash("v2"); #ifdef NETDATA_INTERNAL_CHECKS hash_exit = simple_hash("exit"); hash_debug = simple_hash("debug"); @@ -1355,26 +1597,44 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch // keep a copy of the decoded path, in case we need to serve it as a filename char filename[FILENAME_MAX + 1]; - strncpyz(filename, buffer_tostring(w->url_path_decoded), FILENAME_MAX); + strncpyz(filename, decoded_url_path ? decoded_url_path : "", FILENAME_MAX); char *tok = strsep_skip_consecutive_separators(&decoded_url_path, "/?"); if(likely(tok && *tok)) { uint32_t hash = simple_hash(tok); - debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok); + netdata_log_debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok); - if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) { // current API - debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id); + if(likely(hash == hash_api && strcmp(tok, "api") == 0)) { // current API + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id); return check_host_and_call(host, w, decoded_url_path, web_client_api_request); } else if(unlikely((hash == hash_host && strcmp(tok, "host") == 0) || (hash == hash_node && strcmp(tok, "node") == 0))) { // host switching - debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id); return web_client_switch_host(host, w, decoded_url_path, hash == hash_node, web_client_process_url); } + else if(unlikely(hash == hash_v2 && strcmp(tok, "v2") == 0)) { + if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION)) + return bad_request_multiple_dashboard_versions(w); + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_IS_V2); + return web_client_process_url(host, w, decoded_url_path); + } + else if(unlikely(hash == hash_v1 && strcmp(tok, "v1") == 0)) { + if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION)) + return bad_request_multiple_dashboard_versions(w); + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_IS_V1); + return web_client_process_url(host, w, decoded_url_path); + } + else if(unlikely(hash == hash_v0 && strcmp(tok, "v0") == 0)) { + if(web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_WITH_VERSION)) + return bad_request_multiple_dashboard_versions(w); + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_IS_V0); + return web_client_process_url(host, w, decoded_url_path); + } else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) { // netdata.conf if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); - debug(D_WEB_CLIENT_ACCESS, "%llu: generating netdata.conf ...", w->id); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: generating netdata.conf ...", w->id); w->response.data->content_type = CT_TEXT_PLAIN; buffer_flush(w->response.data); config_generate(w->response.data, 0); @@ -1393,7 +1653,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch else buffer_strcat(w->response.data, "I am doing it already"); - error("web request to exit received."); + netdata_log_error("web request to exit received."); netdata_cleanup_and_exit(0); return HTTP_RESP_OK; } @@ -1406,7 +1666,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch // get the name of the data to show tok = strsep_skip_consecutive_separators(&decoded_url_path, "&"); if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); + netdata_log_debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); // do we have such a data set? RRDSET *st = rrdset_find_byname(host, tok); @@ -1415,7 +1675,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch w->response.data->content_type = CT_TEXT_HTML; buffer_strcat(w->response.data, "Chart is not found: "); buffer_strcat_htmlescape(w->response.data, tok); - debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok); return HTTP_RESP_NOT_FOUND; } @@ -1429,7 +1689,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch w->response.data->content_type = CT_TEXT_HTML; buffer_sprintf(w->response.data, "Chart has now debug %s: ", rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled"); buffer_strcat_htmlescape(w->response.data, tok); - debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled"); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled"); return HTTP_RESP_OK; } @@ -1441,7 +1701,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); - debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id); // replace the zero bytes with spaces buffer_char_replace(w->response.data, '\0', ' '); @@ -1507,7 +1767,33 @@ void web_client_process_request(struct web_client *w) { break; } - w->response.code = web_client_process_url(localhost, w, (char *)buffer_tostring(w->url_path_decoded)); + web_client_reset_path_flags(w); + + // find if the URL path has a filename extension + char path[FILENAME_MAX + 1]; + strncpyz(path, buffer_tostring(w->url_path_decoded), FILENAME_MAX); + char *s = path, *e = path; + + // remove the query string and find the last char + for (; *e ; e++) { + if (*e == '?') + break; + } + + if(e == s || (*(e - 1) == '/')) + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH); + + // check if there is a filename extension + while (--e > s) { + if (*e == '/') + break; + if(*e == '.') { + web_client_flag_set(w, WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION); + break; + } + } + + w->response.code = (short)web_client_process_url(localhost, w, path); break; } break; @@ -1517,7 +1803,7 @@ void web_client_process_request(struct web_client *w) { buffer_flush(w->url_as_received); buffer_strcat(w->url_as_received, "too big request"); - debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zu bytes).", w->id, w->response.data->len); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zu bytes).", w->id, w->response.data->len); size_t len = w->response.data->len; buffer_flush(w->response.data); @@ -1546,33 +1832,33 @@ void web_client_process_request(struct web_client *w) { " click here." ""); - w->response.code = HTTP_RESP_MOVED_PERM; + w->response.code = HTTP_RESP_HTTPS_UPGRADE; break; } #endif case HTTP_VALIDATION_MALFORMED_URL: - debug(D_WEB_CLIENT_ACCESS, "%llu: Malformed URL '%s'.", w->id, w->response.data->buffer); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Malformed URL '%s'.", w->id, w->response.data->buffer); buffer_flush(w->response.data); buffer_strcat(w->response.data, "Malformed URL...\r\n"); w->response.code = HTTP_RESP_BAD_REQUEST; break; case HTTP_VALIDATION_EXCESS_REQUEST_DATA: - debug(D_WEB_CLIENT_ACCESS, "%llu: Excess data in request '%s'.", w->id, w->response.data->buffer); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Excess data in request '%s'.", w->id, w->response.data->buffer); buffer_flush(w->response.data); buffer_strcat(w->response.data, "Excess data in request.\r\n"); w->response.code = HTTP_RESP_BAD_REQUEST; break; case HTTP_VALIDATION_TOO_MANY_READ_RETRIES: - debug(D_WEB_CLIENT_ACCESS, "%llu: Too many retries to read request '%s'.", w->id, w->response.data->buffer); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Too many retries to read request '%s'.", w->id, w->response.data->buffer); buffer_flush(w->response.data); buffer_strcat(w->response.data, "Too many retries to read request.\r\n"); w->response.code = HTTP_RESP_BAD_REQUEST; break; case HTTP_VALIDATION_NOT_SUPPORTED: - debug(D_WEB_CLIENT_ACCESS, "%llu: HTTP method requested is not supported '%s'.", w->id, w->response.data->buffer); + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: HTTP method requested is not supported '%s'.", w->id, w->response.data->buffer); buffer_flush(w->response.data); buffer_strcat(w->response.data, "HTTP method requested is not supported...\r\n"); @@ -1585,10 +1871,6 @@ void web_client_process_request(struct web_client *w) { w->response.sent = 0; - // set a proper last modified date - if(unlikely(!w->response.data->date)) - w->response.data->date = w->timings.tv_ready.tv_sec; - web_client_send_http_header(w); // enable sending immediately if we have data @@ -1597,21 +1879,21 @@ void web_client_process_request(struct web_client *w) { switch(w->mode) { case WEB_CLIENT_MODE_STREAM: - debug(D_WEB_CLIENT, "%llu: STREAM done.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: STREAM done.", w->id); break; case WEB_CLIENT_MODE_OPTIONS: - debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%zu bytes) to client.", w->id, w->response.data->len); + netdata_log_debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%zu bytes) to client.", w->id, w->response.data->len); break; case WEB_CLIENT_MODE_POST: case WEB_CLIENT_MODE_GET: - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%zu bytes) to client.", w->id, w->response.data->len); + netdata_log_debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%zu bytes) to client.", w->id, w->response.data->len); break; case WEB_CLIENT_MODE_FILECOPY: if(w->response.rlen) { - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %zu bytes to client.", w->id, w->response.rlen); + netdata_log_debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %zu bytes to client.", w->id, w->response.rlen); web_client_enable_wait_receive(w); /* @@ -1621,14 +1903,14 @@ void web_client_process_request(struct web_client *w) { { long len = sendfile(w->ofd, w->ifd, NULL, w->response.data->rbytes); if(len != w->response.data->rbytes) - error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->response.data->rbytes, len); + netdata_log_error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->response.data->rbytes, len); else web_client_request_done(w); } */ } else - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id); break; default: @@ -1639,7 +1921,7 @@ void web_client_process_request(struct web_client *w) { ssize_t web_client_send_chunk_header(struct web_client *w, size_t len) { - debug(D_DEFLATE, "%llu: OPEN CHUNK of %zu bytes (hex: %zx).", w->id, len, len); + netdata_log_debug(D_DEFLATE, "%llu: OPEN CHUNK of %zu bytes (hex: %zx).", w->id, len, len); char buf[24]; ssize_t bytes; bytes = (ssize_t)sprintf(buf, "%zX\r\n", len); @@ -1647,15 +1929,15 @@ ssize_t web_client_send_chunk_header(struct web_client *w, size_t len) bytes = web_client_send_data(w,buf,strlen(buf),0); if(bytes > 0) { - debug(D_DEFLATE, "%llu: Sent chunk header %zd bytes.", w->id, bytes); + netdata_log_debug(D_DEFLATE, "%llu: Sent chunk header %zd bytes.", w->id, bytes); w->statistics.sent_bytes += bytes; } else if(bytes == 0) { - debug(D_WEB_CLIENT, "%llu: Did not send chunk header to the client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Did not send chunk header to the client.", w->id); } else { - debug(D_WEB_CLIENT, "%llu: Failed to send chunk header to client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Failed to send chunk header to client.", w->id); WEB_CLIENT_IS_DEAD(w); } @@ -1669,15 +1951,15 @@ ssize_t web_client_send_chunk_close(struct web_client *w) ssize_t bytes; bytes = web_client_send_data(w,"\r\n",2,0); if(bytes > 0) { - debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes); + netdata_log_debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes); w->statistics.sent_bytes += bytes; } else if(bytes == 0) { - debug(D_WEB_CLIENT, "%llu: Did not send chunk suffix to the client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Did not send chunk suffix to the client.", w->id); } else { - debug(D_WEB_CLIENT, "%llu: Failed to send chunk suffix to client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Failed to send chunk suffix to client.", w->id); WEB_CLIENT_IS_DEAD(w); } @@ -1691,15 +1973,15 @@ ssize_t web_client_send_chunk_finalize(struct web_client *w) ssize_t bytes; bytes = web_client_send_data(w,"\r\n0\r\n\r\n",7,0); if(bytes > 0) { - debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes); + netdata_log_debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes); w->statistics.sent_bytes += bytes; } else if(bytes == 0) { - debug(D_WEB_CLIENT, "%llu: Did not send chunk finalize suffix to the client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Did not send chunk finalize suffix to the client.", w->id); } else { - debug(D_WEB_CLIENT, "%llu: Failed to send chunk finalize suffix to client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Failed to send chunk finalize suffix to client.", w->id); WEB_CLIENT_IS_DEAD(w); } @@ -1713,13 +1995,13 @@ ssize_t web_client_send_deflate(struct web_client *w) // when using compression, // w->response.sent is the amount of bytes passed through compression - debug(D_DEFLATE, "%llu: web_client_send_deflate(): w->response.data->len = %zu, w->response.sent = %zu, w->response.zhave = %zu, w->response.zsent = %zu, w->response.zstream.avail_in = %u, w->response.zstream.avail_out = %u, w->response.zstream.total_in = %lu, w->response.zstream.total_out = %lu.", + netdata_log_debug(D_DEFLATE, "%llu: web_client_send_deflate(): w->response.data->len = %zu, w->response.sent = %zu, w->response.zhave = %zu, w->response.zsent = %zu, w->response.zstream.avail_in = %u, w->response.zstream.avail_out = %u, w->response.zstream.total_in = %lu, w->response.zstream.total_out = %lu.", w->id, w->response.data->len, w->response.sent, w->response.zhave, w->response.zsent, w->response.zstream.avail_in, w->response.zstream.avail_out, w->response.zstream.total_in, w->response.zstream.total_out); if(w->response.data->len - w->response.sent == 0 && w->response.zstream.avail_in == 0 && w->response.zhave == w->response.zsent && w->response.zstream.avail_out != 0) { // there is nothing to send - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); // finalize the chunk if(w->response.sent != 0) { @@ -1729,20 +2011,20 @@ ssize_t web_client_send_deflate(struct web_client *w) if(w->mode == WEB_CLIENT_MODE_FILECOPY && web_client_has_wait_receive(w) && w->response.rlen && w->response.rlen > w->response.data->len) { // we have to wait, more data will come - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); web_client_disable_wait_send(w); return t; } if(unlikely(!web_client_has_keepalive(w))) { - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %zu bytes sent.", w->id, w->response.sent); + netdata_log_debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %zu bytes sent.", w->id, w->response.sent); WEB_CLIENT_IS_DEAD(w); return t; } // reset the client web_client_request_done(w); - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Done sending all data on socket.", w->id); return t; } @@ -1755,7 +2037,7 @@ ssize_t web_client_send_deflate(struct web_client *w) if(t < 0) return t; } - debug(D_DEFLATE, "%llu: Compressing %zu new bytes starting from %zu (and %u left behind).", w->id, (w->response.data->len - w->response.sent), w->response.sent, w->response.zstream.avail_in); + netdata_log_debug(D_DEFLATE, "%llu: Compressing %zu new bytes starting from %zu (and %u left behind).", w->id, (w->response.data->len - w->response.sent), w->response.sent, w->response.zstream.avail_in); // give the compressor all the data not passed through the compressor yet if(w->response.data->len > w->response.sent) { @@ -1772,15 +2054,15 @@ ssize_t web_client_send_deflate(struct web_client *w) if((w->mode == WEB_CLIENT_MODE_GET || w->mode == WEB_CLIENT_MODE_POST) || (w->mode == WEB_CLIENT_MODE_FILECOPY && !web_client_has_wait_receive(w) && w->response.data->len == w->response.rlen)) { flush = Z_FINISH; - debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id); + netdata_log_debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id); } else { - debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id); + netdata_log_debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id); } // compress if(deflate(&w->response.zstream, flush) == Z_STREAM_ERROR) { - error("%llu: Compression failed. Closing down client.", w->id); + netdata_log_error("%llu: Compression failed. Closing down client.", w->id); web_client_request_done(w); return(-1); } @@ -1791,30 +2073,30 @@ ssize_t web_client_send_deflate(struct web_client *w) // keep track of the bytes passed through the compressor w->response.sent = w->response.data->len; - debug(D_DEFLATE, "%llu: Compression produced %zu bytes.", w->id, w->response.zhave); + netdata_log_debug(D_DEFLATE, "%llu: Compression produced %zu bytes.", w->id, w->response.zhave); // open a new chunk ssize_t t2 = web_client_send_chunk_header(w, w->response.zhave); if(t2 < 0) return t2; t += t2; } - - debug(D_WEB_CLIENT, "%llu: Sending %zu bytes of data (+%zd of chunk header).", w->id, w->response.zhave - w->response.zsent, t); + + netdata_log_debug(D_WEB_CLIENT, "%llu: Sending %zu bytes of data (+%zd of chunk header).", w->id, w->response.zhave - w->response.zsent, t); len = web_client_send_data(w,&w->response.zbuffer[w->response.zsent], (size_t) (w->response.zhave - w->response.zsent), MSG_DONTWAIT); if(len > 0) { w->statistics.sent_bytes += len; w->response.zsent += len; len += t; - debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, len); + netdata_log_debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, len); } else if(len == 0) { - debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client (zhave = %zu, zsent = %zu, need to send = %zu).", + netdata_log_debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client (zhave = %zu, zsent = %zu, need to send = %zu).", w->id, w->response.zhave, w->response.zsent, w->response.zhave - w->response.zsent); } else { - debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); WEB_CLIENT_IS_DEAD(w); } @@ -1829,7 +2111,7 @@ ssize_t web_client_send(struct web_client *w) { if(unlikely(w->response.data->len - w->response.sent == 0)) { // there is nothing to send - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); // there can be two cases for this // A. we have done everything @@ -1837,19 +2119,19 @@ ssize_t web_client_send(struct web_client *w) { if(w->mode == WEB_CLIENT_MODE_FILECOPY && web_client_has_wait_receive(w) && w->response.rlen && w->response.rlen > w->response.data->len) { // we have to wait, more data will come - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); web_client_disable_wait_send(w); return 0; } if(unlikely(!web_client_has_keepalive(w))) { - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %zu bytes sent.", w->id, w->response.sent); + netdata_log_debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %zu bytes sent.", w->id, w->response.sent); WEB_CLIENT_IS_DEAD(w); return 0; } web_client_request_done(w); - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id); return 0; } @@ -1857,13 +2139,13 @@ ssize_t web_client_send(struct web_client *w) { if(likely(bytes > 0)) { w->statistics.sent_bytes += bytes; w->response.sent += bytes; - debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, bytes); + netdata_log_debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, bytes); } else if(likely(bytes == 0)) { - debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id); } else { - debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); WEB_CLIENT_IS_DEAD(w); } @@ -1887,8 +2169,8 @@ ssize_t web_client_read_file(struct web_client *w) w->response.data->len += bytes; w->response.data->buffer[w->response.data->len] = '\0'; - debug(D_WEB_CLIENT, "%llu: Read %zd bytes.", w->id, bytes); - debug(D_WEB_DATA, "%llu: Read data: '%s'.", w->id, &w->response.data->buffer[old]); + netdata_log_debug(D_WEB_CLIENT, "%llu: Read %zd bytes.", w->id, bytes); + netdata_log_debug(D_WEB_DATA, "%llu: Read data: '%s'.", w->id, &w->response.data->buffer[old]); web_client_enable_wait_send(w); @@ -1896,7 +2178,7 @@ ssize_t web_client_read_file(struct web_client *w) web_client_disable_wait_receive(w); } else if(likely(bytes == 0)) { - debug(D_WEB_CLIENT, "%llu: Out of input file data.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Out of input file data.", w->id); // if we cannot read, it means we have an error on input. // if however, we are copying a file from ifd to ofd, we should not return an error. @@ -1906,7 +2188,7 @@ ssize_t web_client_read_file(struct web_client *w) // let it finish copying... web_client_disable_wait_receive(w); - debug(D_WEB_CLIENT, "%llu: Read the whole file.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: Read the whole file.", w->id); if(web_server_mode != WEB_SERVER_MODE_STATIC_THREADED) { if (w->ifd != w->ofd) close(w->ifd); @@ -1915,7 +2197,7 @@ ssize_t web_client_read_file(struct web_client *w) w->ifd = w->ofd; } else { - debug(D_WEB_CLIENT, "%llu: read data failed.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: read data failed.", w->id); WEB_CLIENT_IS_DEAD(w); } @@ -1961,23 +2243,22 @@ ssize_t web_client_receive(struct web_client *w) w->response.data->len += bytes; w->response.data->buffer[w->response.data->len] = '\0'; - debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes); - debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->response.data->buffer[old]); + netdata_log_debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes); + netdata_log_debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->response.data->buffer[old]); } else if(unlikely(bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))) { web_client_enable_wait_receive(w); return 0; } else if (bytes < 0) { - debug(D_WEB_CLIENT, "%llu: receive data failed.", w->id); + netdata_log_debug(D_WEB_CLIENT, "%llu: receive data failed.", w->id); WEB_CLIENT_IS_DEAD(w); } else - debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes); + netdata_log_debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes); return(bytes); } - void web_client_decode_path_and_query_string(struct web_client *w, const char *path_and_query_string) { char buffer[NETDATA_WEB_REQUEST_URL_SIZE + 2]; buffer[0] = '\0'; @@ -1999,33 +2280,28 @@ void web_client_decode_path_and_query_string(struct web_client *w, const char *p } else { // in non-stream mode, there is a path - // FIXME - the way this is implemented, query string params never accept the symbol &, not even encoded as %26 // To support the symbol & in query string params, we need to turn the url_query_string_decoded into a // dictionary and decode each of the parameters individually. // OR: in url_query_string_decoded use as separator a control character that cannot appear in the URL. - char *question_mark_start = strchr(path_and_query_string, '?'); - if (question_mark_start) - url_decode_r(buffer, question_mark_start, NETDATA_WEB_REQUEST_URL_SIZE + 1); - - buffer[NETDATA_WEB_REQUEST_URL_SIZE + 1] = '\0'; - buffer_strcat(w->url_query_string_decoded, buffer); + url_decode_r(buffer, path_and_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1); + char *question_mark_start = strchr(buffer, '?'); if (question_mark_start) { + buffer_strcat(w->url_query_string_decoded, question_mark_start); char c = *question_mark_start; *question_mark_start = '\0'; - url_decode_r(buffer, path_and_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1); + buffer_strcat(w->url_path_decoded, buffer); *question_mark_start = c; - } else - url_decode_r(buffer, path_and_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1); - - buffer[NETDATA_WEB_REQUEST_URL_SIZE + 1] = '\0'; - buffer_strcat(w->url_path_decoded, buffer); + } else { + buffer_strcat(w->url_query_string_decoded, ""); + buffer_strcat(w->url_path_decoded, buffer); + } } } -void web_client_zero(struct web_client *w) { +void web_client_reuse_from_cache(struct web_client *w) { // zero everything about it - but keep the buffers web_client_reset_allocations(w, false); diff --git a/web/server/web_client.h b/web/server/web_client.h index 4c2b06a70..68fcbfa31 100644 --- a/web/server/web_client.h +++ b/web/server/web_client.h @@ -33,29 +33,28 @@ typedef enum { } HTTP_VALIDATION; typedef enum web_client_flags { - WEB_CLIENT_FLAG_DEAD = 1 << 1, // if set, this client is dead - - WEB_CLIENT_FLAG_KEEPALIVE = 1 << 2, // if set, the web client will be re-used - - WEB_CLIENT_FLAG_WAIT_RECEIVE = 1 << 3, // if set, we are waiting more input data - WEB_CLIENT_FLAG_WAIT_SEND = 1 << 4, // if set, we have data to send to the client - - WEB_CLIENT_FLAG_DO_NOT_TRACK = 1 << 5, // if set, we should not set cookies on this client - WEB_CLIENT_FLAG_TRACKING_REQUIRED = 1 << 6, // if set, we need to send cookies - - WEB_CLIENT_FLAG_TCP_CLIENT = 1 << 7, // if set, the client is using a TCP socket - WEB_CLIENT_FLAG_UNIX_CLIENT = 1 << 8, // if set, the client is using a UNIX socket - - WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET = 1 << 9, // don't close the socket when cleaning up (static-threaded web server) - - WEB_CLIENT_CHUNKED_TRANSFER = 1 << 10, // chunked transfer (used with zlib compression) - - WEB_CLIENT_FLAG_SSL_WAIT_RECEIVE = 1 << 11, // if set, we are waiting more input data from an ssl conn - WEB_CLIENT_FLAG_SSL_WAIT_SEND = 1 << 12, // if set, we have data to send to the client from an ssl conn - - WEB_CLIENT_FLAG_PROXY_HTTPS = 1 << 13, // if set, the client reaches us via an https proxy + WEB_CLIENT_FLAG_DEAD = (1 << 1), // if set, this client is dead + WEB_CLIENT_FLAG_KEEPALIVE = (1 << 2), // if set, the web client will be re-used + WEB_CLIENT_FLAG_WAIT_RECEIVE = (1 << 3), // if set, we are waiting more input data + WEB_CLIENT_FLAG_WAIT_SEND = (1 << 4), // if set, we have data to send to the client + WEB_CLIENT_FLAG_DO_NOT_TRACK = (1 << 5), // if set, we should not set cookies on this client + WEB_CLIENT_FLAG_TRACKING_REQUIRED = (1 << 6), // if set, we need to send cookies + WEB_CLIENT_FLAG_TCP_CLIENT = (1 << 7), // if set, the client is using a TCP socket + WEB_CLIENT_FLAG_UNIX_CLIENT = (1 << 8), // if set, the client is using a UNIX socket + WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET = (1 << 9), // don't close the socket when cleaning up (static-threaded web server) + WEB_CLIENT_CHUNKED_TRANSFER = (1 << 10), // chunked transfer (used with zlib compression) + WEB_CLIENT_FLAG_SSL_WAIT_RECEIVE = (1 << 11), // if set, we are waiting more input data from an ssl conn + WEB_CLIENT_FLAG_SSL_WAIT_SEND = (1 << 12), // if set, we have data to send to the client from an ssl conn + WEB_CLIENT_FLAG_PATH_IS_V0 = (1 << 13), // v0 dashboard found on the path + WEB_CLIENT_FLAG_PATH_IS_V1 = (1 << 14), // v1 dashboard found on the path + WEB_CLIENT_FLAG_PATH_IS_V2 = (1 << 15), // v2 dashboard found on the path + WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH = (1 << 16), // the path has a trailing hash + WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION = (1 << 17), // the path ends with a filename extension } WEB_CLIENT_FLAGS; +#define WEB_CLIENT_FLAG_PATH_WITH_VERSION (WEB_CLIENT_FLAG_PATH_IS_V0|WEB_CLIENT_FLAG_PATH_IS_V1|WEB_CLIENT_FLAG_PATH_IS_V2) +#define web_client_reset_path_flags(w) (w)->flags &= ~(WEB_CLIENT_FLAG_PATH_WITH_VERSION|WEB_CLIENT_FLAG_PATH_HAS_TRAILING_SLASH|WEB_CLIENT_FLAG_PATH_HAS_FILE_EXTENSION) + #define web_client_flag_check(w, flag) ((w)->flags & (flag)) #define web_client_flag_set(w, flag) (w)->flags |= flag #define web_client_flag_clear(w, flag) (w)->flags &= ~flag @@ -200,6 +199,7 @@ struct web_client { }; int web_client_permission_denied(struct web_client *w); +int web_client_bearer_required(struct web_client *w); ssize_t web_client_send(struct web_client *w); ssize_t web_client_receive(struct web_client *w); @@ -210,12 +210,10 @@ void web_client_request_done(struct web_client *w); void buffer_data_options2string(BUFFER *wb, uint32_t options); -int mysendfile(struct web_client *w, char *filename); - void web_client_build_http_header(struct web_client *w); char *strip_control_characters(char *url); -void web_client_zero(struct web_client *w); +void web_client_reuse_from_cache(struct web_client *w); struct web_client *web_client_create(size_t *statistics_memory_accounting); void web_client_free(struct web_client *w); diff --git a/web/server/web_client_cache.c b/web/server/web_client_cache.c index 394bea32b..5aa3af22e 100644 --- a/web/server/web_client_cache.c +++ b/web/server/web_client_cache.c @@ -58,7 +58,7 @@ void web_client_cache_destroy(void) { struct web_client *w, *t; - netdata_spinlock_lock(&web_clients_cache.avail.spinlock); + spinlock_lock(&web_clients_cache.avail.spinlock); w = web_clients_cache.avail.head; while(w) { t = w; @@ -67,10 +67,10 @@ void web_client_cache_destroy(void) { } web_clients_cache.avail.head = NULL; web_clients_cache.avail.count = 0; - netdata_spinlock_unlock(&web_clients_cache.avail.spinlock); + spinlock_unlock(&web_clients_cache.avail.spinlock); // DO NOT FREE THEM IF THEY ARE USED -// netdata_spinlock_lock(&web_clients_cache.used.spinlock); +// spinlock_lock(&web_clients_cache.used.spinlock); // w = web_clients_cache.used.head; // while(w) { // t = w; @@ -81,37 +81,37 @@ void web_client_cache_destroy(void) { // web_clients_cache.used.count = 0; // web_clients_cache.used.reused = 0; // web_clients_cache.used.allocated = 0; -// netdata_spinlock_unlock(&web_clients_cache.used.spinlock); +// spinlock_unlock(&web_clients_cache.used.spinlock); } struct web_client *web_client_get_from_cache(void) { - netdata_spinlock_lock(&web_clients_cache.avail.spinlock); + spinlock_lock(&web_clients_cache.avail.spinlock); struct web_client *w = web_clients_cache.avail.head; if(w) { // get it from avail DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(web_clients_cache.avail.head, w, cache.prev, cache.next); web_clients_cache.avail.count--; - netdata_spinlock_unlock(&web_clients_cache.avail.spinlock); + spinlock_unlock(&web_clients_cache.avail.spinlock); - web_client_zero(w); + web_client_reuse_from_cache(w); - netdata_spinlock_lock(&web_clients_cache.used.spinlock); + spinlock_lock(&web_clients_cache.used.spinlock); web_clients_cache.used.reused++; } else { - netdata_spinlock_unlock(&web_clients_cache.avail.spinlock); + spinlock_unlock(&web_clients_cache.avail.spinlock); // allocate it w = web_client_create(&netdata_buffers_statistics.buffers_web); - netdata_spinlock_lock(&web_clients_cache.used.spinlock); + spinlock_lock(&web_clients_cache.used.spinlock); web_clients_cache.used.allocated++; } // link it to used web clients DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(web_clients_cache.used.head, w, cache.prev, cache.next); web_clients_cache.used.count++; - netdata_spinlock_unlock(&web_clients_cache.used.spinlock); + spinlock_unlock(&web_clients_cache.used.spinlock); // initialize it w->use_count++; @@ -128,14 +128,14 @@ void web_client_release_to_cache(struct web_client *w) { #endif // unlink it from the used - netdata_spinlock_lock(&web_clients_cache.used.spinlock); + spinlock_lock(&web_clients_cache.used.spinlock); DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(web_clients_cache.used.head, w, cache.prev, cache.next); ssize_t used_count = (ssize_t)--web_clients_cache.used.count; - netdata_spinlock_unlock(&web_clients_cache.used.spinlock); + spinlock_unlock(&web_clients_cache.used.spinlock); - netdata_spinlock_lock(&web_clients_cache.avail.spinlock); + spinlock_lock(&web_clients_cache.avail.spinlock); if(w->use_count > 100 || (used_count > 0 && web_clients_cache.avail.count >= 2 * (size_t)used_count) || (used_count <= 10 && web_clients_cache.avail.count >= 20)) { - netdata_spinlock_unlock(&web_clients_cache.avail.spinlock); + spinlock_unlock(&web_clients_cache.avail.spinlock); // we have too many of them - free it web_client_free(w); @@ -144,6 +144,6 @@ void web_client_release_to_cache(struct web_client *w) { // link it to the avail DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(web_clients_cache.avail.head, w, cache.prev, cache.next); web_clients_cache.avail.count++; - netdata_spinlock_unlock(&web_clients_cache.avail.spinlock); + spinlock_unlock(&web_clients_cache.avail.spinlock); } } diff --git a/web/server/web_server.c b/web/server/web_server.c index e136f728c..b35fc252b 100644 --- a/web/server/web_server.c +++ b/web/server/web_server.c @@ -48,7 +48,7 @@ void debug_sockets() { buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_MGMT)?"management ":""); buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_STREAMING)?"streaming ":""); buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_NETDATACONF)?"netdata.conf ":""); - debug(D_WEB_CLIENT, "Socket fd %d name '%s' acl_flags: %s", + netdata_log_debug(D_WEB_CLIENT, "Socket fd %d name '%s' acl_flags: %s", i, api_sockets.fds_names[i], buffer_tostring(wb)); @@ -130,5 +130,5 @@ void web_client_update_acl_matches(struct web_client *w) { // -------------------------------------------------------------------------------------- void web_server_log_connection(struct web_client *w, const char *msg) { - log_access("%llu: %d '[%s]:%s' '%s'", w->id, gettid(), w->client_ip, w->client_port, msg); + netdata_log_access("%llu: %d '[%s]:%s' '%s'", w->id, gettid(), w->client_ip, w->client_port, msg); } -- cgit v1.2.3