diff options
Diffstat (limited to 'web/server/web_client.c')
-rw-r--r-- | web/server/web_client.c | 315 |
1 files changed, 221 insertions, 94 deletions
diff --git a/web/server/web_client.c b/web/server/web_client.c index bd275f5e5..2da6c1dec 100644 --- a/web/server/web_client.c +++ b/web/server/web_client.c @@ -16,8 +16,8 @@ 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; + w->response.code = HTTP_RESP_FORBIDDEN; + return HTTP_RESP_FORBIDDEN; } static inline int web_client_crock_socket(struct web_client *w) { @@ -337,7 +337,7 @@ static inline int access_to_file_is_not_permitted(struct web_client *w, const ch 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; + return HTTP_RESP_FORBIDDEN; } int mysendfile(struct web_client *w, char *filename) { @@ -357,7 +357,7 @@ int mysendfile(struct web_client *w, char *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; + return HTTP_RESP_BAD_REQUEST; } } @@ -367,7 +367,7 @@ int mysendfile(struct web_client *w, char *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; + return HTTP_RESP_BAD_REQUEST; } // find the physical file on disk @@ -383,7 +383,7 @@ int mysendfile(struct web_client *w, char *filename) { 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; + return HTTP_RESP_NOT_FOUND; } if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { @@ -422,14 +422,14 @@ int mysendfile(struct web_client *w, char *filename) { 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; + return HTTP_RESP_REDIR_TEMP; } 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; + return HTTP_RESP_NOT_FOUND; } } @@ -451,7 +451,7 @@ int mysendfile(struct web_client *w, char *filename) { #endif /* __APPLE__ */ buffer_cacheable(w->response.data); - return 200; + return HTTP_RESP_OK; } @@ -570,7 +570,7 @@ static inline int check_host_and_call(RRDHOST *host, struct web_client *w, 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 HTTP_RESP_BAD_REQUEST; //} return func(host, w, url); @@ -603,13 +603,13 @@ int web_client_api_request(RRDHOST *host, struct web_client *w, char *url) w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "Unsupported API version: "); buffer_strcat_htmlescape(w->response.data, tok); - return 404; + return HTTP_RESP_NOT_FOUND; } } else { buffer_flush(w->response.data); buffer_sprintf(w->response.data, "Which API version?"); - return 400; + return HTTP_RESP_BAD_REQUEST; } } @@ -687,25 +687,25 @@ const char *web_content_type_to_string(uint8_t contenttype) { const char *web_response_code_to_string(int code) { switch(code) { - case 200: + case HTTP_RESP_OK: return "OK"; - case 301: + case HTTP_RESP_MOVED_PERM: return "Moved Permanently"; - case 307: + case HTTP_RESP_REDIR_TEMP: return "Temporary Redirect"; - case 400: + case HTTP_RESP_BAD_REQUEST: return "Bad Request"; - case 403: + case HTTP_RESP_FORBIDDEN: return "Forbidden"; - case 404: + case HTTP_RESP_NOT_FOUND: return "Not Found"; - case 412: + case HTTP_RESP_PRECOND_FAIL: return "Preconditions Failed"; default: @@ -772,7 +772,6 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u // 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")) @@ -812,65 +811,35 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u 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, -#ifdef ENABLE_HTTPS - HTTP_VALIDATION_INCOMPLETE, - HTTP_VALIDATION_REDIRECT -#else - HTTP_VALIDATION_INCOMPLETE -#endif -} 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; - } - } - +/** + * Valid Method + * + * Netdata accepts only three methods, including one of these three(STREAM) is an internal method. + * + * @param w is the structure with the client request + * @param s is the start string to parse + * + * @return it returns the next address to parse case the method is valid and NULL otherwise. + */ +static inline char *web_client_valid_method(struct web_client *w, char *s) { // is is a valid request? if(!strncmp(s, "GET ", 4)) { - encoded_url = s = &s[4]; + s = &s[4]; w->mode = WEB_CLIENT_MODE_NORMAL; } else if(!strncmp(s, "OPTIONS ", 8)) { - encoded_url = s = &s[8]; + s = &s[8]; w->mode = WEB_CLIENT_MODE_OPTIONS; } else if(!strncmp(s, "STREAM ", 7)) { + s = &s[7]; + #ifdef ENABLE_HTTPS - if ( (w->ssl.flags) && (netdata_use_ssl_on_stream & NETDATA_SSL_FORCE)){ + if (w->ssl.flags && web_client_is_using_ssl_force(w)){ w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); + char hostname[256]; char *copyme = strstr(s,"hostname="); if ( copyme ){ @@ -891,29 +860,150 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { hostname[13] = 0x00; } error("The server is configured to always use encrypt connection, please enable the SSL on slave with hostname '%s'.",hostname); - return HTTP_VALIDATION_NOT_SUPPORTED; + s = NULL; } #endif - encoded_url = s = &s[7]; w->mode = WEB_CLIENT_MODE_STREAM; } else { + s = NULL; + } + + return s; +} + +/** + * Set Path Query + * + * Set the pointers to the path and query string according to the input. + * + * @param w is the structure with the client request + * @param s is the first address of the string. + * @param ptr is the address of the separator. + */ +static void web_client_set_path_query(struct web_client *w, char *s, char *ptr) { + w->url_path_length = (size_t)(ptr -s); + + w->url_search_path = ptr; +} + +/** + * Split path query + * + * Do the separation between path and query string + * + * @param w is the structure with the client request + * @param s is the string to parse + */ +void web_client_split_path_query(struct web_client *w, char *s) { + //I am assuming here that the separator character(?) is not encoded + char *ptr = strchr(s, '?'); + if(ptr) { + w->separator = '?'; + web_client_set_path_query(w, s, ptr); + return; + } + + //Here I test the second possibility, the URL is completely encoded by the user. + //I am not using the strcasestr, because it is fastest to check %3f and compare + //the next character. + //We executed some tests with "encodeURI(uri);" described in https://www.w3schools.com/jsref/jsref_encodeuri.asp + //on July 1st, 2019, that show us that URLs won't have '?','=' and '&' encoded, but we decided to move in front + //with the next part, because users can develop their own encoded that won't follow this rule. + char *moveme = s; + while (moveme) { + ptr = strchr(moveme, '%'); + if(ptr) { + char *test = (ptr+1); + if (!strncmp(test, "3f", 2) || !strncmp(test, "3F", 2)) { + w->separator = *ptr; + web_client_set_path_query(w, s, ptr); + return; + } + ptr++; + } + + moveme = ptr; + } + + w->separator = 0x00; + w->url_path_length = strlen(s); + w->url_search_path = NULL; +} + +/** + * Request validate + * + * @param w is the structure with the client request + * + * @return It returns HTTP_VALIDATION_OK on success and another code present + * in the enum HTTP_VALIDATION otherwise. + */ +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; + + w->header_parse_tries++; + w->header_parse_last_size = buffer_strlen(w->response.data); + + int is_it_valid; + if(w->header_parse_tries > 1) { + if(last_pos > 4) last_pos -= 4; // allow searching for \r\n\r\n + else last_pos = 0; + + if(w->header_parse_last_size < last_pos) + last_pos = 0; + + is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size); + if(!is_it_valid) { + 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_it_valid = 1; + } else { + last_pos = w->header_parse_last_size; + is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size); + } + + s = web_client_valid_method(w, s); + if (!s) { w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); + return HTTP_VALIDATION_NOT_SUPPORTED; + } else if (!is_it_valid) { + //Invalid request, we have more data after the end of message + char *check = strstr((char *)buffer_tostring(w->response.data), "\r\n\r\n"); + if(check) { + check += 4; + if (*check) { + w->header_parse_tries = 0; + w->header_parse_last_size = 0; + web_client_disable_wait_receive(w); + return HTTP_VALIDATION_NOT_SUPPORTED; + } + } + + web_client_enable_wait_receive(w); + return HTTP_VALIDATION_INCOMPLETE; } - // find the SPACE + "HTTP/" - while(*s) { - // find the next space - while (*s && *s != ' ') s++; + //After the method we have the path and query string together + encoded_url = s; - // is it SPACE + "HTTP/" ? - if(*s && !strncmp(s, " HTTP/", 6)) break; - else s++; - } + //we search for the position where we have " HTTP/", because it finishes the user request + s = url_find_protocol(s); // incomplete requests if(unlikely(!*s)) { @@ -924,6 +1014,10 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { // we have the end of encoded_url - remember it char *ue = s; + //Variables used to map the variables in the query string case it is present + int total_variables; + char *ptr_variables[WEB_FIELDS_MAX]; + // make sure we have complete request // complete requests contain: \r\n\r\n while(*s) { @@ -941,15 +1035,41 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { // a valid complete HTTP request found *ue = '\0'; - url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1); + if(w->mode != WEB_CLIENT_MODE_NORMAL) { + if(!url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1)) + return HTTP_VALIDATION_MALFORMED_URL; + } else { + web_client_split_path_query(w, encoded_url); + + if (w->separator) { + *w->url_search_path = 0x00; + } + + if(!url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1)) + return HTTP_VALIDATION_MALFORMED_URL; + + if (w->separator) { + *w->url_search_path = w->separator; + + char *from = (encoded_url + w->url_path_length); + total_variables = url_map_query_string(ptr_variables, from); + + if (url_parse_query_string(w->decoded_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1, ptr_variables, total_variables)) { + return HTTP_VALIDATION_MALFORMED_URL; + } + } + } *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); + if (w->separator) { + *w->url_search_path = 0x00; + } #ifdef ENABLE_HTTPS if ( (!web_client_check_unix(w)) && (netdata_srv_ctx) ) { - if ((w->ssl.conn) && ((w->ssl.flags & NETDATA_SSL_NO_HANDSHAKE) && (netdata_use_ssl_on_http & NETDATA_SSL_FORCE) && (w->mode != WEB_CLIENT_MODE_STREAM)) ) { + if ((w->ssl.conn) && ((w->ssl.flags & NETDATA_SSL_NO_HANDSHAKE) && (web_client_is_using_ssl_force(w) || web_client_is_using_ssl_default(w)) && (w->mode != WEB_CLIENT_MODE_STREAM)) ) { w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); @@ -997,7 +1117,7 @@ static inline ssize_t web_client_send_data(struct web_client *w,const void *buf, } static inline void web_client_send_http_header(struct web_client *w) { - if(unlikely(w->response.code != 200)) + if(unlikely(w->response.code != HTTP_RESP_OK)) buffer_no_cacheable(w->response.data); // set a proper expiration date, if not already set @@ -1027,7 +1147,7 @@ static inline void web_client_send_http_header(struct web_client *w) { } char headerbegin[8328]; - if (w->response.code == 301) { + if (w->response.code == HTTP_RESP_MOVED_PERM) { memcpy(headerbegin,"\r\nLocation: https://",20); size_t headerlength = strlen(w->host); memcpy(&headerbegin[20],w->host,headerlength); @@ -1210,7 +1330,7 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch if(host != localhost) { buffer_flush(w->response.data); buffer_strcat(w->response.data, "Nesting of hosts is not allowed."); - return 400; + return HTTP_RESP_BAD_REQUEST; } char *tok = mystrsep(&url, "/"); @@ -1234,7 +1354,7 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "This netdata does not maintain a database for host: "); buffer_strcat_htmlescape(w->response.data, tok?tok:""); - return 404; + return HTTP_RESP_NOT_FOUND; } static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url) { @@ -1279,7 +1399,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); config_generate(w->response.data, 0); - return 200; + return HTTP_RESP_OK; } #ifdef NETDATA_INTERNAL_CHECKS else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) { @@ -1296,7 +1416,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch error("web request to exit received."); netdata_cleanup_and_exit(0); - return 200; + return HTTP_RESP_OK; } else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) @@ -1317,7 +1437,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch 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); - return 404; + return HTTP_RESP_NOT_FOUND; } debug_flags |= D_RRD_STATS; @@ -1331,12 +1451,12 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch 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"); - return 200; + return HTTP_RESP_OK; } buffer_flush(w->response.data); buffer_strcat(w->response.data, "debug which chart?\r\n"); - return 400; + return HTTP_RESP_BAD_REQUEST; } else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) @@ -1350,7 +1470,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch // just leave the buffer as is // it will be copied back to the client - return 200; + return HTTP_RESP_OK; } #endif /* NETDATA_INTERNAL_CHECKS */ } @@ -1395,7 +1515,7 @@ void web_client_process_request(struct web_client *w) { w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); buffer_strcat(w->response.data, "OK"); - w->response.code = 200; + w->response.code = HTTP_RESP_OK; break; case WEB_CLIENT_MODE_FILECOPY: @@ -1424,7 +1544,7 @@ void web_client_process_request(struct web_client *w) { buffer_flush(w->response.data); buffer_sprintf(w->response.data, "Received request is too big (%zu bytes).\r\n", w->response.data->len); - w->response.code = 400; + w->response.code = HTTP_RESP_BAD_REQUEST; } else { // wait for more data @@ -1437,16 +1557,23 @@ void web_client_process_request(struct web_client *w) { buffer_flush(w->response.data); w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "<!DOCTYPE html><!-- SPDX-License-Identifier: GPL-3.0-or-later --><html><body onload=\"window.location.href ='https://'+ window.location.hostname + ':' + window.location.port + window.location.pathname\">Redirecting to safety connection, case your browser does not support redirection, please click <a onclick=\"window.location.href ='https://'+ window.location.hostname + ':' + window.location.port + window.location.pathname\">here</a>.</body></html>"); - w->response.code = 301; + w->response.code = HTTP_RESP_MOVED_PERM; break; } #endif + case HTTP_VALIDATION_MALFORMED_URL: + debug(D_WEB_CLIENT_ACCESS, "%llu: URL parsing failed (malformed URL). Cannot understand '%s'.", w->id, w->response.data->buffer); + + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "URL not valid. I don't understand you...\r\n"); + w->response.code = HTTP_RESP_BAD_REQUEST; + break; case HTTP_VALIDATION_NOT_SUPPORTED: debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer); buffer_flush(w->response.data); buffer_strcat(w->response.data, "I don't understand you...\r\n"); - w->response.code = 400; + w->response.code = HTTP_RESP_BAD_REQUEST; break; } |