diff options
Diffstat (limited to 'src/webserver')
-rw-r--r-- | src/webserver/Makefile.am | 4 | ||||
-rw-r--r-- | src/webserver/web_client.c | 1686 | ||||
-rw-r--r-- | src/webserver/web_client.h | 196 | ||||
-rw-r--r-- | src/webserver/web_server.c | 1298 | ||||
-rw-r--r-- | src/webserver/web_server.h | 47 |
5 files changed, 0 insertions, 3231 deletions
diff --git a/src/webserver/Makefile.am b/src/webserver/Makefile.am deleted file mode 100644 index 8773fd098c..0000000000 --- a/src/webserver/Makefile.am +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later - -AUTOMAKE_OPTIONS = subdir-objects -MAINTAINERCLEANFILES = Makefile.in diff --git a/src/webserver/web_client.c b/src/webserver/web_client.c deleted file mode 100644 index bc5a2a09b9..0000000000 --- a/src/webserver/web_client.c +++ /dev/null @@ -1,1686 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "web_client.h" - -// this is an async I/O implementation of the web server request parser -// it is used by all netdata web servers - -int respect_web_browser_do_not_track_policy = 0; -char *web_x_frame_options = NULL; - -#ifdef NETDATA_WITH_ZLIB -int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRATEGY; -#endif /* NETDATA_WITH_ZLIB */ - -inline int web_client_permission_denied(struct web_client *w) { - w->response.data->contenttype = CT_TEXT_PLAIN; - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "You are not allowed to access this resource."); - w->response.code = 403; - return 403; -} - -static inline int web_client_crock_socket(struct web_client *w) { -#ifdef TCP_CORK - if(likely(web_client_is_corkable(w) && !w->tcp_cork && w->ofd != -1)) { - w->tcp_cork = 1; - 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); - - w->tcp_cork = 0; - return -1; - } - } -#else - (void)w; -#endif /* TCP_CORK */ - - return 0; -} - -static inline int web_client_uncrock_socket(struct web_client *w) { -#ifdef TCP_CORK - if(likely(w->tcp_cork && w->ofd != -1)) { - w->tcp_cork = 0; - 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); - w->tcp_cork = 1; - return -1; - } - } -#else - (void)w; -#endif /* TCP_CORK */ - - return 0; -} - -static inline char *strip_control_characters(char *url) { - char *s = url; - if(!s) return ""; - - if(iscntrl(*s)) *s = ' '; - while(*++s) { - if(iscntrl(*s)) *s = ' '; - } - - return url; -} - -void web_client_request_done(struct web_client *w) { - web_client_uncrock_socket(w); - - debug(D_WEB_CLIENT, "%llu: Resetting client.", w->id); - - if(likely(w->last_url[0])) { - struct timeval tv; - now_realtime_timeval(&tv); - - size_t size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len; - size_t sent = size; -#ifdef NETDATA_WITH_ZLIB - if(likely(w->response.zoutput)) sent = (size_t)w->response.zstream.total_out; -#endif - - // -------------------------------------------------------------------- - // global statistics - - finished_web_request_statistics(dt_usec(&tv, &w->tv_in), - w->stats_received_bytes, - w->stats_sent_bytes, - size, - sent); - - w->stats_received_bytes = 0; - w->stats_sent_bytes = 0; - - - // -------------------------------------------------------------------- - - const char *mode; - switch(w->mode) { - case WEB_CLIENT_MODE_FILECOPY: - mode = "FILECOPY"; - break; - - case WEB_CLIENT_MODE_OPTIONS: - mode = "OPTIONS"; - break; - - case WEB_CLIENT_MODE_STREAM: - mode = "STREAM"; - break; - - case WEB_CLIENT_MODE_NORMAL: - mode = "DATA"; - break; - - default: - mode = "UNKNOWN"; - break; - } - - // 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'", - w->id - , gettid() - , w->client_ip - , w->client_port - , mode - , sent - , size - , -((size > 0) ? ((size - sent) / (double) size * 100.0) : 0.0) - , dt_usec(&w->tv_ready, &w->tv_in) / 1000.0 - , dt_usec(&tv, &w->tv_ready) / 1000.0 - , dt_usec(&tv, &w->tv_in) / 1000.0 - , w->response.code - , strip_control_characters(w->last_url) - ); - } - - 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); - - if(web_server_mode != WEB_SERVER_MODE_STATIC_THREADED) { - if (w->ifd != -1) close(w->ifd); - } - - w->ifd = w->ofd; - } - } - - w->last_url[0] = '\0'; - w->cookie1[0] = '\0'; - w->cookie2[0] = '\0'; - w->origin[0] = '*'; - w->origin[1] = '\0'; - - freez(w->user_agent); w->user_agent = NULL; - - w->mode = WEB_CLIENT_MODE_NORMAL; - - w->tcp_cork = 0; - web_client_disable_donottrack(w); - web_client_disable_tracking_required(w); - web_client_disable_keepalive(w); - w->decoded_url[0] = '\0'; - - buffer_reset(w->response.header_output); - buffer_reset(w->response.header); - buffer_reset(w->response.data); - w->response.rlen = 0; - w->response.sent = 0; - w->response.code = 0; - - w->header_parse_tries = 0; - w->header_parse_last_size = 0; - - web_client_enable_wait_receive(w); - web_client_disable_wait_send(w); - - w->response.zoutput = 0; - - // if we had enabled compression, release it -#ifdef NETDATA_WITH_ZLIB - if(w->response.zinitialized) { - debug(D_DEFLATE, "%llu: Freeing compression resources.", w->id); - deflateEnd(&w->response.zstream); - w->response.zsent = 0; - w->response.zhave = 0; - w->response.zstream.avail_in = 0; - w->response.zstream.avail_out = 0; - w->response.zstream.total_in = 0; - w->response.zstream.total_out = 0; - w->response.zinitialized = 0; - } -#endif // NETDATA_WITH_ZLIB -} - -uid_t web_files_uid(void) { - static char *web_owner = NULL; - static uid_t owner_uid = 0; - - if(unlikely(!web_owner)) { - // getpwuid() is not thread safe, - // but we have called this function once - // while single threaded - struct passwd *pw = getpwuid(geteuid()); - web_owner = config_get(CONFIG_SECTION_WEB, "web files owner", (pw)?(pw->pw_name?pw->pw_name:""):""); - if(!web_owner || !*web_owner) - owner_uid = geteuid(); - else { - // getpwnam() is not thread safe, - // but we have called this function once - // while single threaded - pw = getpwnam(web_owner); - if(!pw) { - error("User '%s' is not present. Ignoring option.", web_owner); - owner_uid = geteuid(); - } - else { - debug(D_WEB_CLIENT, "Web files owner set to %s.", web_owner); - owner_uid = pw->pw_uid; - } - } - } - - return(owner_uid); -} - -gid_t web_files_gid(void) { - static char *web_group = NULL; - static gid_t owner_gid = 0; - - if(unlikely(!web_group)) { - // getgrgid() is not thread safe, - // but we have called this function once - // while single threaded - struct group *gr = getgrgid(getegid()); - web_group = config_get(CONFIG_SECTION_WEB, "web files group", (gr)?(gr->gr_name?gr->gr_name:""):""); - if(!web_group || !*web_group) - owner_gid = getegid(); - else { - // getgrnam() is not thread safe, - // but we have called this function once - // while single threaded - gr = getgrnam(web_group); - if(!gr) { - error("Group '%s' is not present. Ignoring option.", web_group); - owner_gid = getegid(); - } - else { - debug(D_WEB_CLIENT, "Web files group set to %s.", web_group); - owner_gid = gr->gr_gid; - } - } - } - - return(owner_gid); -} - -static struct { - const char *extension; - uint32_t hash; - uint8_t contenttype; -} mime_types[] = { - { "html" , 0 , CT_TEXT_HTML} - , {"js" , 0 , CT_APPLICATION_X_JAVASCRIPT} - , {"css" , 0 , CT_TEXT_CSS} - , {"xml" , 0 , CT_TEXT_XML} - , {"xsl" , 0 , CT_TEXT_XSL} - , {"txt" , 0 , CT_TEXT_PLAIN} - , {"svg" , 0 , CT_IMAGE_SVG_XML} - , {"ttf" , 0 , CT_APPLICATION_X_FONT_TRUETYPE} - , {"otf" , 0 , CT_APPLICATION_X_FONT_OPENTYPE} - , {"woff2", 0 , CT_APPLICATION_FONT_WOFF2} - , {"woff" , 0 , CT_APPLICATION_FONT_WOFF} - , {"eot" , 0 , CT_APPLICATION_VND_MS_FONTOBJ} - , {"png" , 0 , CT_IMAGE_PNG} - , {"jpg" , 0 , CT_IMAGE_JPG} - , {"jpeg" , 0 , CT_IMAGE_JPG} - , {"gif" , 0 , CT_IMAGE_GIF} - , {"bmp" , 0 , CT_IMAGE_BMP} - , {"ico" , 0 , CT_IMAGE_XICON} - , {"icns" , 0 , CT_IMAGE_ICNS} - , { NULL, 0, 0} -}; - -static inline uint8_t contenttype_for_filename(const char *filename) { - // info("checking filename '%s'", filename); - - static int initialized = 0; - int i; - - if(unlikely(!initialized)) { - for (i = 0; mime_types[i].extension; i++) - mime_types[i].hash = simple_hash(mime_types[i].extension); - - initialized = 1; - } - - const char *s = filename, *last_dot = NULL; - - // find the last dot - while(*s) { - if(unlikely(*s == '.')) last_dot = s; - s++; - } - - if(unlikely(!last_dot || !*last_dot || !last_dot[1])) { - // info("no extension for filename '%s'", filename); - return CT_APPLICATION_OCTET_STREAM; - } - last_dot++; - - // 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); - return mime_types[i].contenttype; - } - } - - // 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) { - w->response.data->contenttype = CT_TEXT_HTML; - buffer_strcat(w->response.data, "Access to file is not permitted: "); - buffer_strcat_htmlescape(w->response.data, filename); - return 403; -} - -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); - - if(!web_client_can_access_dashboard(w)) - return web_client_permission_denied(w); - - // skip leading slashes - while (*filename == '/') filename++; - - // if the filename contains "strange" characters, refuse to serve it - 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); - w->response.data->contenttype = CT_TEXT_HTML; - buffer_sprintf(w->response.data, "Filename contains invalid characters: "); - buffer_strcat_htmlescape(w->response.data, filename); - return 400; - } - } - - // if the filename contains a .. refuse to serve it - if(strstr(filename, "..") != 0) { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); - w->response.data->contenttype = CT_TEXT_HTML; - buffer_strcat(w->response.data, "Relative filenames are not supported: "); - buffer_strcat_htmlescape(w->response.data, filename); - return 400; - } - - // find the physical file on disk - char webfilename[FILENAME_MAX + 1]; - snprintfz(webfilename, FILENAME_MAX, "%s/%s", netdata_configured_web_dir, filename); - - 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->contenttype = CT_TEXT_HTML; - buffer_strcat(w->response.data, "File does not exist, or is not accessible: "); - buffer_strcat_htmlescape(w->response.data, webfilename); - return 404; - } - - 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); - } - - // check if the file is owned by expected user - if (statbuf.st_uid != web_files_uid()) { - error("%llu: File '%s' is owned by user %u (expected user %u). Access Denied.", w->id, webfilename, statbuf.st_uid, web_files_uid()); - return access_to_file_is_not_permitted(w, webfilename); - } - - // check if the file is owned by expected group - if (statbuf.st_gid != web_files_gid()) { - error("%llu: File '%s' is owned by group %u (expected group %u). Access Denied.", w->id, webfilename, statbuf.st_gid, web_files_gid()); - return access_to_file_is_not_permitted(w, webfilename); - } - - done = 1; - } - - // open the file - w->ifd = open(webfilename, 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); - w->response.data->contenttype = 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); - return 307; - } - else { - error("%llu: Cannot open file '%s'.", w->id, webfilename); - w->response.data->contenttype = CT_TEXT_HTML; - buffer_strcat(w->response.data, "Cannot open file: "); - buffer_strcat_htmlescape(w->response.data, webfilename); - return 404; - } - } - - sock_setnonblock(w->ifd); - - w->response.data->contenttype = contenttype_for_filename(webfilename); - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, webfilename, statbuf.st_size, w->ifd, w->ofd); - - w->mode = WEB_CLIENT_MODE_FILECOPY; - web_client_enable_wait_receive(w); - web_client_disable_wait_send(w); - buffer_flush(w->response.data); - buffer_need_bytes(w->response.data, (size_t)statbuf.st_size); - w->response.rlen = (size_t)statbuf.st_size; -#ifdef __APPLE__ - w->response.data->date = statbuf.st_mtimespec.tv_sec; -#else - w->response.data->date = statbuf.st_mtim.tv_sec; -#endif /* __APPLE__ */ - buffer_cacheable(w->response.data); - - return 200; -} - - -#ifdef NETDATA_WITH_ZLIB -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); - return; - } - - if(unlikely(w->response.sent)) { - error("%llu: Cannot enable compression in the middle of a conversation.", w->id); - return; - } - - w->response.zstream.zalloc = Z_NULL; - w->response.zstream.zfree = Z_NULL; - w->response.zstream.opaque = Z_NULL; - - w->response.zstream.next_in = (Bytef *)w->response.data->buffer; - w->response.zstream.avail_in = 0; - w->response.zstream.total_in = 0; - - w->response.zstream.next_out = w->response.zbuffer; - w->response.zstream.avail_out = 0; - w->response.zstream.total_out = 0; - - w->response.zstream.zalloc = Z_NULL; - w->response.zstream.zfree = Z_NULL; - 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); -// 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); - return; - } - - w->response.zsent = 0; - w->response.zoutput = 1; - w->response.zinitialized = 1; - - debug(D_DEFLATE, "%llu: Initialized compression.", w->id); -} -#endif // NETDATA_WITH_ZLIB - -void buffer_data_options2string(BUFFER *wb, uint32_t options) { - int count = 0; - - if(options & RRDR_OPTION_NONZERO) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "nonzero"); - } - - if(options & RRDR_OPTION_REVERSED) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "flip"); - } - - if(options & RRDR_OPTION_JSON_WRAP) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "jsonwrap"); - } - - if(options & RRDR_OPTION_MIN2MAX) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "min2max"); - } - - if(options & RRDR_OPTION_MILLISECONDS) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "ms"); - } - - if(options & RRDR_OPTION_ABSOLUTE) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "absolute"); - } - - if(options & RRDR_OPTION_SECONDS) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "seconds"); - } - - if(options & RRDR_OPTION_NULL2ZERO) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "null2zero"); - } - - if(options & RRDR_OPTION_OBJECTSROWS) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "objectrows"); - } - - if(options & RRDR_OPTION_GOOGLE_JSON) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "google_json"); - } - - if(options & RRDR_OPTION_PERCENTAGE) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "percentage"); - } - - if(options & RRDR_OPTION_NOT_ALIGNED) { - if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "unaligned"); - } -} - -const char *group_method2string(int group) { - switch(group) { - case GROUP_UNDEFINED: - return ""; - - case GROUP_AVERAGE: - return "average"; - - case GROUP_MIN: - return "min"; - - case GROUP_MAX: - return "max"; - - case GROUP_SUM: - return "sum"; - - case GROUP_INCREMENTAL_SUM: - return "incremental-sum"; - - default: - return "unknown-group-method"; - } -} - -static inline int check_host_and_call(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) { - if(unlikely(host->rrd_memory_mode == RRD_MEMORY_MODE_NONE)) { - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "This host does not maintain a database"); - return 400; - } - - return func(host, w, url); -} - -static inline int check_host_and_dashboard_acl_and_call(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) { - if(!web_client_can_access_dashboard(w)) - return web_client_permission_denied(w); - - return check_host_and_call(host, w, url, func); -} - -int web_client_api_request(RRDHOST *host, struct web_client *w, char *url) -{ - // get the api version - char *tok = mystrsep(&url, "/?&"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); - if(strcmp(tok, "v1") == 0) - return web_client_api_request_v1(host, w, url); - else { - buffer_flush(w->response.data); - w->response.data->contenttype = CT_TEXT_HTML; - buffer_strcat(w->response.data, "Unsupported API version: "); - buffer_strcat_htmlescape(w->response.data, tok); - return 404; - } - } - else { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Which API version?"); - return 400; - } -} - -const char *web_content_type_to_string(uint8_t contenttype) { - switch(contenttype) { - case CT_TEXT_HTML: - return "text/html; charset=utf-8"; - - case CT_APPLICATION_XML: - return "application/xml; charset=utf-8"; - - case CT_APPLICATION_JSON: - return "application/json; charset=utf-8"; - - case CT_APPLICATION_X_JAVASCRIPT: - return "application/x-javascript; charset=utf-8"; - - case CT_TEXT_CSS: - return "text/css; charset=utf-8"; - - case CT_TEXT_XML: - return "text/xml; charset=utf-8"; - - case CT_TEXT_XSL: - return "text/xsl; charset=utf-8"; - - case CT_APPLICATION_OCTET_STREAM: - return "application/octet-stream"; - - case CT_IMAGE_SVG_XML: - return "image/svg+xml"; - - case CT_APPLICATION_X_FONT_TRUETYPE: - return "application/x-font-truetype"; - - case CT_APPLICATION_X_FONT_OPENTYPE: - return "application/x-font-opentype"; - - case CT_APPLICATION_FONT_WOFF: - return "application/font-woff"; - - case CT_APPLICATION_FONT_WOFF2: - return "application/font-woff2"; - - case CT_APPLICATION_VND_MS_FONTOBJ: - return "application/vnd.ms-fontobject"; - - case CT_IMAGE_PNG: - return "image/png"; - - case CT_IMAGE_JPG: - return "image/jpeg"; - - case CT_IMAGE_GIF: - return "image/gif"; - - case CT_IMAGE_XICON: - return "image/x-icon"; - - case CT_IMAGE_BMP: - return "image/bmp"; - - case CT_IMAGE_ICNS: - return "image/icns"; - - case CT_PROMETHEUS: - return "text/plain; version=0.0.4"; - - default: - case CT_TEXT_PLAIN: - return "text/plain; charset=utf-8"; - } -} - - -const char *web_response_code_to_string(int code) { - switch(code) { - case 200: - return "OK"; - - case 307: - return "Temporary Redirect"; - - case 400: - return "Bad Request"; - - case 403: - return "Forbidden"; - - case 404: - return "Not Found"; - - case 412: - return "Preconditions Failed"; - - default: - if(code >= 100 && code < 200) - return "Informational"; - - if(code >= 200 && code < 300) - return "Successful"; - - if(code >= 300 && code < 400) - return "Redirection"; - - if(code >= 400 && code < 500) - return "Bad Request"; - - if(code >= 500 && code < 600) - return "Server Error"; - - return "Undefined Error"; - } -} - -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_accept_encoding = 0, hash_donottrack = 0, hash_useragent = 0; - - if(unlikely(!hash_origin)) { - hash_origin = simple_uhash("Origin"); - hash_connection = simple_uhash("Connection"); - hash_accept_encoding = simple_uhash("Accept-Encoding"); - hash_donottrack = simple_uhash("DNT"); - hash_useragent = simple_uhash("User-Agent"); - } - - char *e = s; - - // find the : - while(*e && *e != ':') e++; - if(!*e) return e; - - // get the name - *e = '\0'; - - // find the value - char *v = e + 1, *ve; - - // skip leading spaces from value - while(*v == ' ') v++; - ve = v; - - // find the \r - while(*ve && *ve != '\r') ve++; - if(!*ve || ve[1] != '\n') { - *e = ':'; - return ve; - } - - // terminate the value - *ve = '\0'; - - // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v); - uint32_t hash = simple_uhash(s); - - if(hash == hash_origin && !strcasecmp(s, "Origin")) - strncpyz(w->origin, v, NETDATA_WEB_REQUEST_ORIGIN_HEADER_SIZE); - - else if(hash == hash_connection && !strcasecmp(s, "Connection")) { - if(strcasestr(v, "keep-alive")) - web_client_enable_keepalive(w); - } - else if(respect_web_browser_do_not_track_policy && hash == hash_donottrack && !strcasecmp(s, "DNT")) { - if(*v == '0') web_client_disable_donottrack(w); - else if(*v == '1') web_client_enable_donottrack(w); - } - else if(parse_useragent && hash == hash_useragent && !strcasecmp(s, "User-Agent")) { - w->user_agent = strdupz(v); - } -#ifdef NETDATA_WITH_ZLIB - else if(hash == hash_accept_encoding && !strcasecmp(s, "Accept-Encoding")) { - if(web_enable_gzip) { - if(strcasestr(v, "gzip")) - web_client_enable_deflate(w, 1); - // - // does not seem to work - // else if(strcasestr(v, "deflate")) - // web_client_enable_deflate(w, 0); - } - } -#endif /* NETDATA_WITH_ZLIB */ - - *e = ':'; - *ve = '\r'; - return ve; -} - -// http_request_validate() -// returns: -// = 0 : all good, process the request -// > 0 : request is not supported -// < 0 : request is incomplete - wait for more data - -typedef enum { - HTTP_VALIDATION_OK, - HTTP_VALIDATION_NOT_SUPPORTED, - HTTP_VALIDATION_INCOMPLETE -} HTTP_VALIDATION; - -static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { - char *s = (char *)buffer_tostring(w->response.data), *encoded_url = NULL; - - size_t last_pos = w->header_parse_last_size; - if(last_pos > 4) last_pos -= 4; // allow searching for \r\n\r\n - else last_pos = 0; - - w->header_parse_tries++; - w->header_parse_last_size = buffer_strlen(w->response.data); - - if(w->header_parse_tries > 1) { - if(w->header_parse_last_size < last_pos) - last_pos = 0; - - if(strstr(&s[last_pos], "\r\n\r\n") == NULL) { - if(w->header_parse_tries > 10) { - 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); - return HTTP_VALIDATION_NOT_SUPPORTED; - } - - return HTTP_VALIDATION_INCOMPLETE; - } - } - - // is is a valid request? - if(!strncmp(s, "GET ", 4)) { - encoded_url = s = &s[4]; - w->mode = WEB_CLIENT_MODE_NORMAL; - } - else if(!strncmp(s, "OPTIONS ", 8)) { - encoded_url = s = &s[8]; - w->mode = WEB_CLIENT_MODE_OPTIONS; - } - else if(!strncmp(s, "STREAM ", 7)) { - encoded_url = s = &s[7]; - w->mode = WEB_CLIENT_MODE_STREAM; - } - else { - w->header_parse_tries = 0; - w->header_parse_last_size = 0; - web_client_disable_wait_receive(w); - return HTTP_VALIDATION_NOT_SUPPORTED; - } - - // find the SPACE + "HTTP/" - while(*s) { - // find the next space - while (*s && *s != ' ') s++; - - // is it SPACE + "HTTP/" ? - if(*s && !strncmp(s, " HTTP/", 6)) break; - else s++; - } - - // incomplete requests - if(unlikely(!*s)) { - web_client_enable_wait_receive(w); - return HTTP_VALIDATION_INCOMPLETE; - } - - // we have the end of encoded_url - remember it - char *ue = s; - - // make sure we have complete request - // complete requests contain: \r\n\r\n - while(*s) { - // find a line feed - while(*s && *s++ != '\r'); - - // did we reach the end? - if(unlikely(!*s)) break; - - // is it \r\n ? - if(likely(*s++ == '\n')) { - - // is it again \r\n ? (header end) - if(unlikely(*s == '\r' && s[1] == '\n')) { - // a valid complete HTTP request found - - *ue = '\0'; - url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1); - *ue = ' '; - - // copy the URL - we are going to overwrite parts of it - // TODO -- ideally we we should avoid copying buffers around - strncpyz(w->last_url, w->decoded_url, NETDATA_WEB_REQUEST_URL_SIZE); - - w->header_parse_tries = 0; - w->header_parse_last_size = 0; - web_client_disable_wait_receive(w); - return HTTP_VALIDATION_OK; - } - - // another header line - s = http_header_parse(w, s, - (w->mode == WEB_CLIENT_MODE_STREAM) // parse user agent - ); - } - } - - // incomplete request - web_client_enable_wait_receive(w); - return HTTP_VALIDATION_INCOMPLETE; -} - -static inline void web_client_send_http_header(struct web_client *w) { - if(unlikely(w->response.code != 200)) - buffer_no_cacheable(w->response.data); - - // 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->tv_ready.tv_sec + localhost->rrd_update_every; - else - w->response.data->expires = w->tv_ready.tv_sec + 86400; - } - - // prepare the HTTP response header - 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->contenttype); - const char *code_msg = web_response_code_to_string(w->response.code); - - // prepare the last modified and expiration dates - char date[32], edate[32]; - { - struct tm tmbuf, *tm; - - tm = gmtime_r(&w->response.data->date, &tmbuf); - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", tm); - - tm = gmtime_r(&w->response.data->expires, &tmbuf); - strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", tm); - } - - buffer_sprintf(w->response.header_output, - "HTTP/1.1 %d %s\r\n" - "Connection: %s\r\n" - "Server: NetData Embedded HTTP Server v%s\r\n" - "Access-Control-Allow-Origin: %s\r\n" - "Access-Control-Allow-Credentials: true\r\n" - "Content-Type: %s\r\n" - "Date: %s\r\n" - , w->response.code, code_msg - , web_client_has_keepalive(w)?"keep-alive":"close" - , VERSION - , w->origin - , content_type_string - , date - ); - - if(unlikely(web_x_frame_options)) - buffer_sprintf(w->response.header_output, "X-Frame-Options: %s\r\n", web_x_frame_options); - - if(w->cookie1[0] || w-> |