From 58b7d95a7ec9c576f8a06bbab07f755846b5349a Mon Sep 17 00:00:00 2001 From: thiagoftsm <49162938+thiagoftsm@users.noreply.github.com> Date: Thu, 6 Jun 2019 17:01:39 +0000 Subject: [PATCH] New URL parser (#6070) * URL_parser 3 * URL_parser rebase 2! * URL_parameter parsing 3 * URL_parameter parsing 4 * URL_parameter parsing 5 * URL_parser alarms * URL_parser finish the basic structure * URL_parser codacity fixes! * URL_parser scripts! * URL_parser codacy! * URL_parser rebase 3! * URL_parser host fixes! * URL_parser host fixes 2! * URL_parser fix spaces! * URL_parser error message! * URL_parser Christopher requests! * URL_parser alarms fixed! * URL_parser health fixed! * URL_parser rebase 4! * URL_parser C fix write format! * URL_parser fix bugs due cache! --- libnetdata/inlined.h | 28 ++ libnetdata/url/url.c | 85 ++++++ libnetdata/url/url.h | 22 ++ tests/urls/requests.sh | 207 +++++++++++++ web/api/badges/web_buffer_svg.c | 87 +++--- web/api/exporters/allmetrics.c | 79 ++--- web/api/health/health_cmdapi.c | 139 +++++---- web/api/web_api_v1.c | 300 ++++++++++-------- web/server/web_client.c | 523 ++++++++++++++++++++++---------- web/server/web_client.h | 12 + 10 files changed, 1047 insertions(+), 435 deletions(-) create mode 100644 tests/urls/requests.sh diff --git a/libnetdata/inlined.h b/libnetdata/inlined.h index 6a5994c12a..a9c3e472e4 100644 --- a/libnetdata/inlined.h +++ b/libnetdata/inlined.h @@ -31,6 +31,19 @@ static inline uint32_t simple_hash(const char *name) { return hval; } +static inline uint32_t simple_nhash(const char *name,size_t len) { + unsigned char *s = (unsigned char *) name; + size_t i; + uint32_t hval = 0x811c9dc5; + i = 0; + do { + hval *= 16777619; + hval ^= (uint32_t) *s++; + } while (++i < len); + + return hval; +} + static inline uint32_t simple_uhash(const char *name) { unsigned char *s = (unsigned char *) name; uint32_t hval = 0x811c9dc5, c; @@ -42,6 +55,21 @@ static inline uint32_t simple_uhash(const char *name) { return hval; } +static inline uint32_t simple_nuhash(const char *name,size_t len) { + unsigned char *s = (unsigned char *) name; + size_t i; + uint32_t hval = 0x811c9dc5, c; + + i = 0; + do { + c = *s++; + if (unlikely(c >= 'A' && c <= 'Z')) c += 'a' - 'A'; + hval *= 16777619; + hval ^= c; + } while ( ++i < len); + return hval; +} + static inline int simple_hash_strcmp(const char *name, const char *b, uint32_t *hash) { unsigned char *s = (unsigned char *) name; uint32_t hval = 0x811c9dc5; diff --git a/libnetdata/url/url.c b/libnetdata/url/url.c index 07a9f8069e..fb1fb7f07a 100644 --- a/libnetdata/url/url.c +++ b/libnetdata/url/url.c @@ -79,3 +79,88 @@ char *url_decode_r(char *to, char *url, size_t size) { return to; } + +inline HTTP_VALIDATION url_is_request_complete(char *begin,char *end,size_t length) { + if ( begin == end) { + return HTTP_VALIDATION_INCOMPLETE; + } + + if ( length > 3 ) { + begin = end - 4; + } + + uint32_t counter = 0; + do { + if (*begin == '\r') { + begin++; + if ( begin == end ) + { + break; + } + + if (*begin == '\n') + { + counter++; + } + } else if (*begin == '\n') { + begin++; + counter++; + } + + if ( counter == 2) { + break; + } + } + while (begin != end); + + return (counter == 2)?HTTP_VALIDATION_OK:HTTP_VALIDATION_INCOMPLETE; +} + +inline char *url_find_protocol(char *s) { + while(*s) { + // find the next space + while (*s && *s != ' ') s++; + + // is it SPACE + "HTTP/" ? + if(*s && !strncmp(s, " HTTP/", 6)) break; + else s++; + } + + return s; +} + +int url_parse_query_string(struct web_fields *names,struct web_fields *values,char *moveme,char *divisor) { + uint32_t i = 0; + uint32_t max = WEB_FIELDS_MAX; + + do { + if ( i == max) { + error("We are exceeding the maximum number of elements possible(%u) in this query string(%s)",max,moveme); + break; + } + if (divisor) { + names[i].body = moveme; + names[i].length = divisor - moveme;//= - begin + + moveme = ++divisor; //value + values[i].body = moveme; + + (void)divisor; + divisor = strchr(moveme,'&'); //end of value + if (divisor) { + values[i].length = (size_t )(divisor - moveme); + } else{ + values[i].length = strlen(moveme); + break; + } + + moveme = divisor; + divisor = strchr(++moveme,'='); //end of value + i++; + } else { + break; + } + } while (moveme); + + return ++i; +} diff --git a/libnetdata/url/url.h b/libnetdata/url/url.h index 6cef6d7a84..9e86c20cfd 100644 --- a/libnetdata/url/url.h +++ b/libnetdata/url/url.h @@ -25,4 +25,26 @@ extern char *url_decode(char *str); extern char *url_decode_r(char *to, char *url, size_t size); +#define WEB_FIELDS_MAX 200 +struct web_fields{ + char *body; + size_t length; +}; +// 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_REDIRECT +} HTTP_VALIDATION; + +extern HTTP_VALIDATION url_is_request_complete(char *begin,char *end,size_t length); +extern char *url_find_protocol(char *s); +extern int url_parse_query_string(struct web_fields *names,struct web_fields *values,char *moveme,char *divisor); + #endif /* NETDATA_URL_H */ diff --git a/tests/urls/requests.sh b/tests/urls/requests.sh new file mode 100644 index 0000000000..2e1a957a88 --- /dev/null +++ b/tests/urls/requests.sh @@ -0,0 +1,207 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0-or-later + +################################################################################################ +#### #### +#### GLOBAL VARIABLES #### +#### #### +################################################################################################ + +NETDATA_VARLIB_DIR="@varlibdir_POST@" + +#CT=`date +'%s%N' |cut -b1-13` +# The current time +CT=`date +'%s'` + +# The previous time +PT=$(( $CT - 100)) + +# The curl options used to do download +CURLOPTS="-v --create-dirs -o" + +# The output directory where we will store the results and error +OUTDIR="tests" + +################################################################################################ +#### #### +#### FUNCTIONS #### +#### #### +################################################################################################ + +# Print error message and close script +netdata_print_error(){ + echo "Closing due error \"$1\" code \"$2\"" + exit 1 +} + +# Print the header message of the function +netdata_print_header() { + echo "$1" +} + +# Create the main directory where the results will be stored +netdata_create_directory() { + netdata_print_header "Creating directory $1" + if [ ! -d $1 ]; then + mkdir $1 + if [ $? -ne 0 ]; then + netdata_print_error "Cannot create directory" $? + fi + else + echo "Working with directory $OUTDIR" + fi +} + +netdata_test_download(){ + grep "HTTP/1.1 200 OK" $1 2>/dev/null 1>/dev/null + if [ $? -ne 0 ]; then + netdata_print_error "Cannot do download of the page $2" $? + fi +} + +# Download information from Netdata +netdata_download_various() { + netdata_print_header "Getting $2" + curl $CURLOPTS $OUTDIR/$3.out "$1/$2" 2> $OUTDIR/$3.err + netdata_test_download $OUTDIR/$3.err "$1/$2" +} + +# Download charts from Netdata +netdata_download_charts() { + curl $CURLOPTS $OUTDIR/charts.out "$1/$2/charts" 2> $OUTDIR/charts.err + netdata_test_download $OUTDIR/charts.err "$1/$2/charts" + + #Rewrite the next + cat tests/charts.out | grep -w "id"| cut -d: -f2 | grep "\"," | sed s/,//g | sort +} + +#Test options for a specific chart +netdata_download_chart() { + NAME=`echo $3| sed s/\"//g` + netdata_print_header "Getting data for $NAME using $4" + + LDIR=$OUTDIR"/"$4 + + LURL=$1/$2=$NAME + + NAME=$NAME"_$4" + + curl $CURLOPTS $LDIR/$NAME.out "$LURL" 2> $LDIR/$NAME.err + netdata_test_download $LDIR/$NAME.err $LURL + + UFILES=( "points" "before" "after" ) + COUNTER=0 + for OPT in "&points=100" "&before=$PT" "&after=$CT" ; + do + LURL="$LURL$OPT" + LFILE=$NAME"_${UFILES[$COUNTER]}"; + + curl $CURLOPTS "$LDIR/$LFILE.out" "$LURL" 2> "$LDIR/$LFILE.err" + netdata_test_download $LDIR/$LFILE.err $LURL + + COUNTER=$(($COUNTER + 1)) + done + + LURL="$LURL&group=" + for OPT in "min" "max" "sum" "median" "stddev" "cv" "ses" "des" "incremental_sum" "average"; + do + TURL=$LURL$OPT + TFILE=$NAME"_$OPT"; + curl $CURLOPTS "$LDIR/$TFILE.out" "$TURL" 2> "$LDIR/$TFILE.err" + netdata_test_download $LDIR/$TFILE.err $TURL + for MORE in "jsonp" "json" "ssv" "csv" "datatable" "datasource" "tsv" "ssvcomma" "html" "array"; + do + TURL=$TURL"&format="$MORE + TFILE=$NAME"_$OPT""_$MORE"; + curl $CURLOPTS "$LDIR/$TFILE.out" "$TURL" 2> "$LDIR/$TFILE.err" + netdata_test_download $LDIR/$TFILE.err $TURL + done + + done + + LURL="$LURL$OPT>ime=60" + NFILE=$NAME"_gtime" + curl $CURLOPTS "$LDIR/$NFILE.out" "$TURL" 2> "$LDIR/$NFILE.err" + netdata_test_download $LDIR/$NFILE.err $LURL + + LURL="$LURL$OPT&options=percentage" + NFILE=$NAME"_percentage" + curl $CURLOPTS "$LDIR/$NFILE.out" "$TURL" 2> "$LDIR/$NFILE.err" + netdata_test_download $LDIR/$NFILE.err $LURL + + LURL="$LURL$OPT&options=percentage" + NFILE=$NAME"_percentage" + curl $CURLOPTS "$LDIR/$NFILE.out" "$TURL" 2> "$LDIR/$NFILE.err" + netdata_test_download $LDIR/$NFILE.err $LURL + + LURL="$LURL$OPT&dimensions=system%7Cnice" + NFILE=$NAME"_dimension" + curl $CURLOPTS "$LDIR/$NFILE.out" "$TURL" 2> "$LDIR/$NFILE.err" + netdata_test_download $LDIR/$NFILE.err $LURL + +} + +# Download information from Netdata +netdata_download_allmetrics() { + netdata_print_header "Getting All metrics" + curl $CURLOPTS $OUTDIR/allmetrics.out "$1/$2" 2> $OUTDIR/allmetrics.err + netdata_test_download $OUTDIR/allmetrics.err "$1/$2" +} + +# Download charts from Netdata + +################################################################################################ +#### #### +#### MAIN ROUTINE #### +#### #### +################################################################################################ +MURL="http://127.0.0.1:19999" + +wget --execute="robots = off" --mirror --convert-links --no-parent http://127.0.0.1:19999 + +netdata_create_directory $OUTDIR + +netdata_download_various $MURL "netdata.conf" "netdata.conf" + +netdata_download_various $MURL "api/v1/info" "info" + +netdata_download_various $MURL "api/v1/registry?action=hello" "action" + +netdata_print_header "Getting all the netdata charts" +CHARTS=$( netdata_download_charts "http://127.0.0.1:19999" "api/v1" ) + +netdata_download_various $MURL "api/v1/allmetrics?format=json" "allmetrics" + +netdata_download_various $MURL "api/v1/alarms?all" "alarms_all" + +netdata_download_various $MURL "api/v1/alarms?active" "alarms_active" + +netdata_download_various $MURL "api/v1/alarm_log?after&_=$PT" "alarm_log" + +for I in $CHARTS ; do + NAME=`echo $I| sed s/\"//g` + netdata_download_various $MURL "api/v1/alarm_variables?chart=$NAME" "alarm_variables_$NAME" +done + +netdata_create_directory "$OUTDIR/data" +for I in $CHARTS ; do + netdata_download_chart $MURL "api/v1/data?chart" $I "data" +done + +netdata_create_directory "$OUTDIR/badge.svg" +for I in $CHARTS ; do + netdata_download_chart $MURL "api/v1/badge.svg?chart" $I "badge.svg" +done + +if [ -f "${NETDATA_VARLIB_DIR}/netdata.api.key" ] ;then + read -r CORRECT_TOKEN < "${NETDATA_VARLIB_DIR}/netdata.api.key" +else + echo "${NETDATA_VARLIB_DIR}/netdata.api.key not found" + echo "Token not found." + exit 2 +fi +curl -H "X-Auth-Token: $TOKEN" "http://127.0.0.1:19999/api/v1/manage/health?cmd=RESET" + +echo "ALL the URLS got 200 as answer!" + +exit 0 diff --git a/web/api/badges/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c index b24fddedf5..d24e2820ef 100644 --- a/web/api/badges/web_buffer_svg.c +++ b/web/api/badges/web_buffer_svg.c @@ -889,6 +889,7 @@ void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const ch } int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) { + (void)url; int ret = 400; buffer_flush(w->response.data); @@ -912,46 +913,54 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u int group = RRDR_GROUPING_AVERAGE; uint32_t options = 0x00000000; - while(url) { - char *value = mystrsep(&url, "&"); - if(!value || !*value) continue; + uint32_t i = 0; + uint32_t end = w->total_params; + char save[WEB_FIELDS_MAX]; + char *value; + size_t lvalue; + if(end) { + do { + value = w->param_values[i].body; + lvalue = w->param_values[i].length; + save[i] = value[lvalue]; + value[lvalue] = 0x00; - char *name = mystrsep(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; + char *name = w->param_name[i].body; + size_t lname = w->param_name[i].length; - debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value); + debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value); - // name and value are now the parameters - // they are not null and not empty + // name and value are now the parameters + // they are not null and not empty + if(!strncmp(name, "chart",lname)) chart = value; + else if(!strncmp(name, "dimension",lname) || !strncmp(name, "dim",lname) || !strncmp(name, "dimensions",lname) || !strncmp(name, "dims",lname)) { + if(!dimensions) + dimensions = buffer_create(100); - if(!strcmp(name, "chart")) chart = value; - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { - if(!dimensions) - dimensions = buffer_create(100); + buffer_strcat(dimensions, "|"); + buffer_strcat(dimensions, value); + } + else if(!strncmp(name, "after",lname)) after_str = value; + else if(!strncmp(name, "before",lname)) before_str = value; + else if(!strncmp(name, "points",lname)) points_str = value; + else if(!strncmp(name, "group",lname)) { + group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); + } + else if(!strncmp(name, "options",lname)) { + options |= web_client_api_request_v1_data_options(value); + } + else if(!strncmp(name, "label",lname)) label = value; + else if(!strncmp(name, "units",lname)) units = value; + else if(!strncmp(name, "label_color",lname)) label_color = value; + else if(!strncmp(name, "value_color",lname)) value_color = value; + else if(!strncmp(name, "multiply",lname)) multiply_str = value; + else if(!strncmp(name, "divide",lname)) divide_str = value; + else if(!strncmp(name, "refresh",lname)) refresh_str = value; + else if(!strncmp(name, "precision",lname)) precision_str = value; + else if(!strncmp(name, "scale",lname)) scale_str = value; + else if(!strncmp(name, "alarm",lname)) alarm = value; - buffer_strcat(dimensions, "|"); - buffer_strcat(dimensions, value); - } - else if(!strcmp(name, "after")) after_str = value; - else if(!strcmp(name, "before")) before_str = value; - else if(!strcmp(name, "points")) points_str = value; - else if(!strcmp(name, "group")) { - group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); - } - else if(!strcmp(name, "options")) { - options |= web_client_api_request_v1_data_options(value); - } - else if(!strcmp(name, "label")) label = value; - else if(!strcmp(name, "units")) units = value; - else if(!strcmp(name, "label_color")) label_color = value; - else if(!strcmp(name, "value_color")) value_color = value; - else if(!strcmp(name, "multiply")) multiply_str = value; - else if(!strcmp(name, "divide")) divide_str = value; - else if(!strcmp(name, "refresh")) refresh_str = value; - else if(!strcmp(name, "precision")) precision_str = value; - else if(!strcmp(name, "scale")) scale_str = value; - else if(!strcmp(name, "alarm")) alarm = value; + } while (++i < end ); } if(!chart || !*chart) { @@ -1137,6 +1146,14 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u } cleanup: + if(end) { + i = 0; + do { + value = w->param_values[i].body; + lvalue = w->param_values[i].length; + value[lvalue] = save[i]; + } while(++i < end); + } buffer_free(dimensions); return ret; } diff --git a/web/api/exporters/allmetrics.c b/web/api/exporters/allmetrics.c index 88ff78e7e4..17e5e077eb 100644 --- a/web/api/exporters/allmetrics.c +++ b/web/api/exporters/allmetrics.c @@ -18,54 +18,57 @@ struct prometheus_output_options { }; inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) { + (void)url; int format = ALLMETRICS_SHELL; const char *prometheus_server = w->client_ip; uint32_t prometheus_backend_options = global_backend_options; PROMETHEUS_OUTPUT_OPTIONS prometheus_output_options = PROMETHEUS_OUTPUT_TIMESTAMPS | ((global_backend_options & BACKEND_OPTION_SEND_NAMES)?PROMETHEUS_OUTPUT_NAMES:0); const char *prometheus_prefix = global_backend_prefix; - while(url) { - char *value = mystrsep(&url, "&"); - if (!value || !*value) continue; + uint32_t end = w->total_params; + if (end) { + uint32_t i = 0; + do { + char *name = w->param_name[i].body; + size_t lname = w->param_name[i].length; + char *value = w->param_values[i].body; + size_t lvalue = w->param_values[i].length; - char *name = mystrsep(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; + if(!strncmp(name, "format",lname)) { + if(!strncmp(value, ALLMETRICS_FORMAT_SHELL,lvalue)) + format = ALLMETRICS_SHELL; + else if(!strncmp(value, ALLMETRICS_FORMAT_PROMETHEUS,lvalue)) + format = ALLMETRICS_PROMETHEUS; + else if(!strncmp(value, ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS,lvalue)) + format = ALLMETRICS_PROMETHEUS_ALL_HOSTS; + else if(!strncmp(value, ALLMETRICS_FORMAT_JSON,lvalue)) + format = ALLMETRICS_JSON; + else + format = 0; + } + else if(!strncmp(name, "server",lname)) { + prometheus_server = value; + } + else if(!strncmp(name, "prefix",lname)) { + prometheus_prefix = value; + } + else if(!strncmp(name, "data",lname) || !strncmp(name, "source",lname) || !strncmp(name, "data source",lname) || !strncmp(name, "data-source",lname) || !strncmp(name, "data_source",lname) || !strncmp(name, "datasource",lname)) { + prometheus_backend_options = backend_parse_data_source(value, prometheus_backend_options); + } + else { + int i; + for(i = 0; prometheus_output_flags_root[i].name ; i++) { + if(!strncmp(name, prometheus_output_flags_root[i].name,lname)) { + if(!strncmp(value, "yes",lvalue) || !strncmp(value, "1",lvalue) || !strncmp(value, "true",lvalue)) + prometheus_output_options |= prometheus_output_flags_root[i].flag; + else + prometheus_output_options &= ~prometheus_output_flags_root[i].flag; - if(!strcmp(name, "format")) { - if(!strcmp(value, ALLMETRICS_FORMAT_SHELL)) - format = ALLMETRICS_SHELL; - else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS)) - format = ALLMETRICS_PROMETHEUS; - else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS)) - format = ALLMETRICS_PROMETHEUS_ALL_HOSTS; - else if(!strcmp(value, ALLMETRICS_FORMAT_JSON)) - format = ALLMETRICS_JSON; - else - format = 0; - } - else if(!strcmp(name, "server")) { - prometheus_server = value; - } - else if(!strcmp(name, "prefix")) { - prometheus_prefix = value; - } - else if(!strcmp(name, "data") || !strcmp(name, "source") || !strcmp(name, "data source") || !strcmp(name, "data-source") || !strcmp(name, "data_source") || !strcmp(name, "datasource")) { - prometheus_backend_options = backend_parse_data_source(value, prometheus_backend_options); - } - else { - int i; - for(i = 0; prometheus_output_flags_root[i].name ; i++) { - if(!strcmp(name, prometheus_output_flags_root[i].name)) { - if(!strcmp(value, "yes") || !strcmp(value, "1") || !strcmp(value, "true")) - prometheus_output_options |= prometheus_output_flags_root[i].flag; - else - prometheus_output_options &= ~prometheus_output_flags_root[i].flag; - - break; + break; + } } } - } + } while( ++i < end); } buffer_flush(w->response.data); diff --git a/web/api/health/health_cmdapi.c b/web/api/health/health_cmdapi.c index ec177751b9..eac3daf06b 100644 --- a/web/api/health/health_cmdapi.c +++ b/web/api/health/health_cmdapi.c @@ -31,13 +31,10 @@ void free_silencers(SILENCER *t) { return; } - - int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, char *url) { int ret = 400; (void) host; - - + (void)url; BUFFER *wb = w->response.data; buffer_flush(wb); @@ -73,75 +70,86 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR); ret = 403; } else { - while (url) { - char *value = mystrsep(&url, "&"); - if (!value || !*value) continue; + uint32_t end = w->total_params; + if (end) { + uint32_t i = 0; + do { + char *key = w->param_name[i].body; + size_t lkey = w->param_name[i].length; + char ksave = key[lkey]; + key[lkey] = 0x00; - char *key = mystrsep(&value, "="); - if (!key || !*key) continue; - if (!value || !*value) continue; + char *value = w->param_values[i].body; + size_t lvalue = w->param_values[i].length; + char vsave = value[lvalue]; + value[lvalue] = 0x00; - debug(D_WEB_CLIENT, "%llu: API v1 health query param '%s' with value '%s'", w->id, key, value); + debug(D_WEB_CLIENT, "%llu: API v1 health query param '%s' with value '%s'", w->id, key, value); - // name and value are now the parameters - if (!strcmp(key, "cmd")) { - if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCEALL)) { - silencers->all_alarms = 1; - silencers->stype = STYPE_SILENCE_NOTIFICATIONS; - buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCEALL); - } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLEALL)) { - silencers->all_alarms = 1; - silencers->stype = STYPE_DISABLE_ALARMS; - buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLEALL); - } else if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCE)) { - silencers->stype = STYPE_SILENCE_NOTIFICATIONS; - buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCE); - } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLE)) { - silencers->stype = STYPE_DISABLE_ALARMS; - buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLE); - } else if (!strcmp(value, HEALTH_CMDAPI_CMD_RESET)) { - silencers->all_alarms = 0; - silencers->stype = STYPE_NONE; - free_silencers(silencers->silencers); - silencers->silencers = NULL; - buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET); - } - } else { - uint32_t hash = simple_uhash(key); - if (unlikely(silencer == NULL)) { - if ( - (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) || - (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) || - (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) || - (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) || - (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) || - (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) - ) { - silencer = create_silencer(); + // name and value are now the parameters + if (!strncmp(key, "cmd",lkey)) { + if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCEALL)) { + silencers->all_alarms = 1; + silencers->stype = STYPE_SILENCE_NOTIFICATIONS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCEALL); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLEALL)) { + silencers->all_alarms = 1; + silencers->stype = STYPE_DISABLE_ALARMS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLEALL); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCE)) { + silencers->stype = STYPE_SILENCE_NOTIFICATIONS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCE); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLE)) { + silencers->stype = STYPE_DISABLE_ALARMS; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLE); + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_RESET)) { + silencers->all_alarms = 0; + silencers->stype = STYPE_NONE; + free_silencers(silencers->silencers); + silencers->silencers = NULL; + buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET); + } + } else { + uint32_t hash = simple_uhash(key); + if (unlikely(silencer == NULL)) { + if ( + (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) || + (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) || + (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) || + (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) || + (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) || + (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) + ) { + silencer = create_silencer(); + } + } + + if (hash == hash_alarm && !strncasecmp(key, HEALTH_ALARM_KEY,lkey)) { + silencer->alarms = strdupz(value); + silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT); + } else if (hash == hash_chart && !strncasecmp(key, HEALTH_CHART_KEY,lkey)) { + silencer->charts = strdupz(value); + silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT); + } else if (hash == hash_context && !strncasecmp(key, HEALTH_CONTEXT_KEY,lkey)) { + silencer->contexts = strdupz(value); + silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT); + } else if (hash == hash_host && !strncasecmp(key, HEALTH_HOST_KEY,lkey)) { + silencer->hosts = strdupz(value); + silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT); + } else if (hash == hash_families && !strncasecmp(key, HEALTH_FAMILIES_KEY,lkey)) { + silencer->families = strdupz(value); + silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT); + } else { + buffer_strcat(wb, HEALTH_CMDAPI_MSG_INVALID_KEY); } } + key[lkey] = ksave ; + value[lvalue] = vsave ; - if (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) { - silencer->alarms = strdupz(value); - silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT); - } else if (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) { - silencer->charts = strdupz(value); - silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT); - } else if (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) { - silencer->contexts = strdupz(value); - silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT); - } else if (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) { - silencer->hosts = strdupz(value); - silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT); - } else if (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) { - silencer->families = strdupz(value); - silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT); - } else { - buffer_strcat(wb, HEALTH_CMDAPI_MSG_INVALID_KEY); - } - } + } while( ++i < end ); } + if (likely(silencer)) { // Add the created instance to the linked list in silencers silencer->next = silencers->silencers; @@ -160,6 +168,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c ret = 200; } } + w->response.data = wb; buffer_no_cacheable(w->response.data); return ret; diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c index 7c0d728bf8..590c025632 100644 --- a/web/api/web_api_v1.c +++ b/web/api/web_api_v1.c @@ -195,14 +195,18 @@ inline uint32_t web_client_api_request_v1_data_google_format(char *name) { inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url) { + (void)url; int all = 0; - while(url) { - char *value = mystrsep(&url, "&"); - if (!value || !*value) continue; + uint32_t end = w->total_params; + if(end) { + uint32_t i = 0; + do { + char *value = w->param_values[i].body; - if(!strcmp(value, "all")) all = 1; - else if(!strcmp(value, "active")) all = 0; + if(!strncmp(value, "all",3)) all = 1; + else if(!strncmp(value, "active",6)) all = 0; + } while(++i < end); } buffer_flush(w->response.data); @@ -213,17 +217,24 @@ inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, } inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) { + (void)url; uint32_t after = 0; - while(url) { - char *value = mystrsep(&url, "&"); - if (!value || !*value) continue; + uint32_t end = w->total_params; + if(end) { + uint32_t i = 0; + do { + char *value = w->param_values[i].body; + size_t lvalue = w->param_values[i].length; + char save = value[lvalue]; + value[lvalue] = 0x00; - char *name = mystrsep(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; + char *name = w->param_name[i].body; + size_t lname = w->param_name[i].length; - if(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0); + if(!strncmp(name, "after",lname)) after = (uint32_t)strtoul(value, NULL, 0); + value[lvalue] = save; + } while (++i < end); } buffer_flush(w->response.data); @@ -233,27 +244,29 @@ inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client } inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) { + (void)url; int ret = 400; char *chart = NULL; buffer_flush(w->response.data); - while(url) { - char *value = mystrsep(&url, "&"); - if(!value || !*value) continue; + uint32_t i = 0; + uint32_t end = w->total_params; + if(end) { + do { + char *name = w->param_name[i].body; + size_t nlength = w->param_name[i].length; + char *value = w->param_values[i].body; - char *name = mystrsep(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; + // name and value are now the parameters + // they are not null and not empty - // name and value are now the parameters - // they are not null and not empty - - if(!strcmp(name, "chart")) chart = value; - //else { - /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name); - // goto cleanup; - //} + if(!strncmp(name, "chart",nlength)) chart = value; + //else { + /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name); + // goto cleanup; + //} + } while (++i < end); } if(!chart || !*chart) { @@ -307,6 +320,7 @@ void fix_google_param(char *s) { // returns the HTTP code inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) { + (void)url; debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url); int ret = 400; @@ -333,75 +347,82 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c uint32_t format = DATASOURCE_JSON; uint32_t options = 0x00000000; - while(url) { - char *value = mystrsep(&url, "&"); - if(!value || !*value) continue; + uint32_t end = w->total_params; + char save[WEB_FIELDS_MAX]; + char *value ; + size_t lvalue; + if(end) { + uint32_t i = 0; + do { + char *name = w->param_name[i].body; + size_t lname = w->param_name[i].length; + value = w->param_values[i].body; + lvalue = w->param_values[i].length; + save[i] = value[lvalue]; + value[lvalue] = 0x00; - char *name = mystrsep(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; + debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value); - debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value); + // name and value are now the parameters + // they are not null and not empty - // name and value are now the parameters - // they are not null and not empty - - if(!strcmp(name, "chart")) chart = value; - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { - if(!dimensions) dimensions = buffer_create(100); - buffer_strcat(dimensions, "|"); - buffer_strcat(dimensions, value); - } - else if(!strcmp(name, "after")) after_str = value; - else if(!strcmp(name, "before")) before_str = value; - else if(!strcmp(name, "points")) points_str = value; - else if(!strcmp(name, "gtime")) group_time_str = value; - else if(!strcmp(name, "group")) { - group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); - } - else if(!strcmp(name, "format")) { - format = web_client_api_request_v1_data_format(value); - } - else if(!strcmp(name, "options")) { - options |= web_client_api_request_v1_data_options(value); - } - else if(!strcmp(name, "callback")) { - responseHandler = value; - } - else if(!strcmp(name, "filename")) { - outFileName = value; - } - else if(!strcmp(name, "tqx")) { - // parse Google Visualization API options - // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source - char *tqx_name, *tqx_value; - - while(value) { - tqx_value = mystrsep(&value, ";"); - if(!tqx_value || !*tqx_value) continue; - - tqx_name = mystrsep(&tqx_value, ":"); - if(!tqx_name || !*tqx_name) continue; - if(!tqx_value || !*tqx_value) continue; - - if(!strcmp(tqx_name, "version")) - google_version = tqx_value; - else if(!strcmp(tqx_name, "reqId")) - google_reqId = tqx_value; - else if(!strcmp(tqx_name, "sig")) { - google_sig = tqx_value; - google_timestamp = strtoul(google_sig, NULL, 0); - } - else if(!strcmp(tqx_name, "out")) { - google_out = tqx_value; - format = web_client_api_request_v1_data_google_format(google_out); - } - else if(!strcmp(tqx_name, "responseHandler")) - responseHandler = tqx_value; - else if(!strcmp(tqx_name, "outFileName")) - outFileName = tqx_value; + if(!strncmp(name, "chart",lname)) chart = value; + else if(!strncmp(name, "dimension",lname) || !strncmp(name, "dim",lname) || !strncmp(name, "dimensions",lname) || !strncmp(name, "dims",lname)) { + if(!dimensions) dimensions = buffer_create(100); + buffer_strcat(dimensions, "|"); + buffer_strcat(dimensions, value); } - } + else if(!strncmp(name, "after",lname)) after_str = value; + else if(!strncmp(name, "before",lname)) before_str = value; + else if(!strncmp(name, "points",lname)) points_str = value; + else if(!strncmp(name, "gtime",lname)) group_time_str = value; + else if(!strncmp(name, "group",lname)) { + group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); + } + else if(!strncmp(name, "format",lname)) { + format = web_client_api_request_v1_data_format(value); + } + else if(!strncmp(name, "options",lname)) { + options |= web_client_api_request_v1_data_options(value); + } + else if(!strncmp(name, "callback",lname)) { + responseHandler = value; + } + else if(!strncmp(name, "filename",lname)) { + outFileName = value; + } + else if(!strncmp(name, "tqx",lname)) { + // parse Google Visualization API options + // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source + char *tqx_name, *tqx_value; + + while(value) { + tqx_value = mystrsep(&value, ";"); + if(!tqx_value || !*tqx_value) continue; + + tqx_name = mystrsep(&tqx_value, ":"); + if(!tqx_name || !*tqx_name) continue; + if(!tqx_value || !*tqx_value) continue; + + if(!strcmp(tqx_name, "version")) + google_version = tqx_value; + else if(!strcmp(tqx_name, "reqId")) + google_reqId = tqx_value; + else if(!strcmp(tqx_name, "sig")) { + google_sig = tqx_value; + google_timestamp = strtoul(google_sig, NULL, 0); + } + else if(!strcmp(tqx_name, "out")) { + google_out = tqx_value; + format = web_client_api_request_v1_data_google_format(google_out); + } + else if(!strcmp(tqx_name, "responseHandler")) + responseHandler = tqx_value; + else if(!strcmp(tqx_name, "outFileName")) + outFileName = tqx_value; + } + } + } while (++i < end); } // validate the google parameters given @@ -488,6 +509,14 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c buffer_strcat(w->response.data, ");"); cleanup: + if(end) { + uint32_t i = 0; + do { + value = w->param_values[i].body; + lvalue = w->param_values[i].length; + value[lvalue] = save[i]; + } while ( ++i < end ); + } buffer_free(dimensions); return ret; } @@ -551,26 +580,33 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client * int redirects = 0; */ - while(url) { - char *value = mystrsep(&url, "&"); - if (!value || !*value) continue; + uint32_t i = 0; + uint32_t end = w->total_params; + if (!end) { + goto nothing; + } - char *name = mystrsep(&value, "="); - if (!name || !*name) continue; - if (!value || !*value) continue; + do { + char *name = w->param_name[i].body; + size_t nlength = w->param_name[i].length; + char *value = w->param_values[i].body; + size_t vlength = w->param_values[i].length; debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value); - uint32_t hash = simple_hash(name); + //uint32_t hash = simple_hash(name); + uint32_t hash = simple_nhash(name,nlength); - if(hash == hash_action && !strcmp(name, "action")) { - uint32_t vhash = simple_hash(value); + //if(hash == hash_action && !strcmp(name, "action")) { + if(hash == hash_action && !strncmp(name, "action",nlength)) { + //uint32_t vhash = simple_hash(value); + uint32_t vhash = simple_nhash(value,vlength); - if(vhash == hash_access && !strcmp(value, "access")) action = 'A'; - else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H'; - else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D'; - else if(vhash == hash_search && !strcmp(value, "search")) action = 'S'; - else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W'; + if(vhash == hash_access && !strncmp(value, "access",vlength)) action = 'A'; + else if(vhash == hash_hello && !strncmp(value, "hello",vlength)) action = 'H'; + else if(vhash == hash_delete && !strncmp(value, "delete",vlength)) action = 'D'; + else if(vhash == hash_search && !strncmp(value, "search",vlength)) action = 'S'; + else if(vhash == hash_switch && !strncmp(value, "switch",vlength)) action = 'W'; #ifdef NETDATA_INTERNAL_CHECKS else error("unknown registry action '%s'", value); #endif /* NETDATA_INTERNAL_CHECKS */ @@ -579,33 +615,34 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client * else if(hash == hash_redirects && !strcmp(name, "redirects")) redirects = atoi(value); */ - else if(hash == hash_machine && !strcmp(name, "machine")) + else if(hash == hash_machine && !strncmp(name, "machine",nlength)) machine_guid = value; - else if(hash == hash_url && !strcmp(name, "url")) + else if(hash == hash_url && !strncmp(name, "url",nlength)) machine_url = value; else if(action == 'A') { - if(hash == hash_name && !strcmp(name, "name")) + if(hash == hash_name && !strncmp(name, "name",nlength)) url_name = value; } else if(action == 'D') { - if(hash == hash_delete_url && !strcmp(name, "delete_url")) + if(hash == hash_delete_url && !strncmp(name, "delete_url",nlength)) delete_url = value; } else if(action == 'S') { - if(hash == hash_for && !strcmp(name, "for")) + if(hash == hash_for && !strncmp(name, "for",nlength)) search_machine_guid = value; } else if(action == 'W') { - if(hash == hash_to && !strcmp(name, "to")) + if(hash == hash_to && !strncmp(name, "to",nlength)) to_person_guid = value; } #ifdef NETDATA_INTERNAL_CHECKS else error("unused registry URL parameter '%s' with value '%s'", name, value); #endif /* NETDATA_INTERNAL_CHECKS */ - } + } while (++i < end ); +nothing: if(unlikely(respect_web_browser_do_not_track_policy && web_client_has_donottrack(w))) { buffer_flush(w->response.data); buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work."); @@ -797,28 +834,27 @@ inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char * } // get the command - char *tok = mystrsep(&url, "?"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok); - uint32_t hash = simple_hash(tok); - for(i = 0; api_commands[i].command ;i++) { - if(unlikely(hash == api_commands[i].hash && !strcmp(tok, api_commands[i].command))) { - if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl)) - return web_client_permission_denied(w); + char *cmd = w->command.body; + size_t length = w->command.length; + uint32_t hash = simple_nhash(cmd,length); - return api_commands[i].callback(host, w, url); - } + for(i = 0; api_commands[i].command ;i++) { + if(unlikely(hash == api_commands[i].hash && !strncmp(cmd, api_commands[i].command,length))) { + if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl)) + return web_client_permission_denied(w); + + return api_commands[i].callback(host, w, url); } + } - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "Unsupported v1 API command: "); - buffer_strcat_htmlescape(w->response.data, tok); - return 404; - } - else { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Which API v1 command?"); - return 400; - } + char copyme[256]; + length = w->path.length; + memcpy(copyme,w->path.body,length); + copyme[length] = 0x00; + + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "Unsupported v1 API command: "); + buffer_strcat_htmlescape(w->response.data, copyme); + return 404; } diff --git a/web/server/web_client.c b/web/server/web_client.c index bd275f5e50..1b0878f393 100644 --- a/web/server/web_client.c +++ b/web/server/web_client.c @@ -143,7 +143,7 @@ void web_client_request_done(struct web_client *w) { 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){ + if (w->ifd != -1) { close(w->ifd); } } @@ -352,7 +352,7 @@ int mysendfile(struct web_client *w, char *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 != '_') { + 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: "); @@ -594,15 +594,26 @@ 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) + (void)tok; + char *body = w->version.body; + size_t length = w->version.length; + if(body) { + //if(tok && *tok) { + //debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); + debug(D_WEB_CLIENT, "%llu: Searching for API version'.", w->id); + //if(strcmp(tok, "v1") == 0) + if(strncmp(body, "v1",length) == 0) { return web_client_api_request_v1(host, w, url); - else { + } else { + char response[NETDATA_WEB_REQUEST_URL_SIZE]; + strncpy(response,w->version.body,length); + response[length] = 0x00; 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); + buffer_strcat_htmlescape(w->response.data, response); + //buffer_strcat_htmlescape(w->response.data, tok); return 404; } } @@ -791,7 +802,7 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u } else if(hash == hash_authorization&& !strcasecmp(s, "X-Auth-Token")) { w->auth_bearer_token = strdupz(v); } - else if(hash == hash_host && !strcasecmp(s, "Host")){ + else if(hash == hash_host && !strcasecmp(s, "Host")) { strncpyz(w->host, v, (ve - v)); } #ifdef NETDATA_WITH_ZLIB @@ -812,76 +823,67 @@ 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; - } +static inline HTTP_VALIDATION web_client_is_complete(char *begin,char *end,size_t length){ + if ( begin == end){ + return HTTP_VALIDATION_INCOMPLETE; } - // is is a valid request? + if ( length > 3 ){ + begin = end - 4; + } + + uint32_t counter = 0; + do{ + if (*begin == '\r'){ + begin++; + if ( begin == end ) + { + break; + } + + if (*begin == '\n') + { + counter++; + } + } else if (*begin == '\n') { + begin++; + counter++; + } + + if ( counter == 2){ + break; + } + } + while(begin != end); + + return (counter == 2)?HTTP_VALIDATION_OK:HTTP_VALIDATION_INCOMPLETE; +} + +static inline char *web_client_parse_method(struct web_client *w,char *s) { 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)) { #ifdef ENABLE_HTTPS - if ( (w->ssl.flags) && (netdata_use_ssl_on_stream & NETDATA_SSL_FORCE)){ + if ((w->ssl.flags) && (netdata_use_ssl_on_stream & NETDATA_SSL_FORCE)) { 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 ){ + if (copyme) { copyme += 9; char *end = strchr(copyme,'&'); - if(end){ + if(end) { size_t length = end - copyme; memcpy(hostname,copyme,length); hostname[length] = 0X00; - } - else{ + } else { memcpy(hostname,"not available",13); hostname[13] = 0x00; } @@ -891,30 +893,205 @@ 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; + return NULL; } #endif - - encoded_url = s = &s[7]; + s = &s[7]; w->mode = WEB_CLIENT_MODE_STREAM; } else { + s = NULL; + } + + return s; +} + +static inline char *web_client_find_protocol(struct web_client *w,char *s) { + s = url_find_protocol(s); + + w->protocol.body = s+1; + char *end = strchr(s+6,'\n'); + if (end) { + w->protocol.length = end - w->protocol.body ; + } + + return s; +} + +static inline void web_client_parse_headers(struct web_client *w,char *s) { + while(*s) { + // find a line feed + while(*s && *s++ != '\r'); + + // did we reach the end? + if(unlikely(!*s)) break; + + if (*s == '\n') { + s++; + } + + s = http_header_parse(w, s, + (w->mode == WEB_CLIENT_MODE_STREAM) // parse user agent + ); + + } +} + +int web_client_parse_request(struct web_client *w,char *divisor) { + if(!divisor) { + w->total_params = 0; + return 0; + } + + uint32_t i = url_parse_query_string(w->param_name,w->param_values,w->query_string.body+1,divisor); + w->total_params = i; + + return i; +} + +static inline void web_client_set_directory(struct web_client *w,char *begin,char *enddir,char *endcmd) { + if (enddir) { + w->directory.body = begin; + w->directory.length = enddir - begin; + + if (!strncmp(w->directory.body,"api",w->directory.length)) { + begin = enddir + 1; + enddir = strchr(begin,'/'); + if(enddir) { + w->version.body = begin; + w->version.length = enddir - begin; + + enddir++; + w->command.body = enddir; + w->command.length = (size_t) (endcmd - enddir); + } + } + } + else{ + w->directory.body = begin; + w->directory.length = w->path.length - 1; + w->version.body = NULL; + w->version.length = 0; + w->command.body = NULL; + w->command.length = 0; + } +} + +static inline void web_client_set_without_query_string(struct web_client *w) { + w->query_string.body = NULL; + w->query_string.length = 0; + + char *test = w->path.body+1; + if (!strncmp(test,"api/v1/",7) ) { + test += 7; + if (!strncmp(test,"info",4)) { + w->command.length = 4; + } + else if (!strncmp(test,"charts",6)) { + w->command.length = 6; + } + else { + test = NULL; + w->command.length = 0; + } + }else{ + w->command.length = w->path.length; + } + w->command.body = test; + w->total_params = 0; +} + +static inline void web_client_split_path_query(struct web_client *w) { + w->path.body = w->decoded_url; + w->decoded_length = strlen(w->decoded_url); + char *moveme = strchr(w->path.body,'?'); + char *enddir; + if (moveme) { + w->path.length = moveme - w->path.body; + w->query_string.body = moveme; + w->query_string.length = w->decoded_length - w->path.length; + + enddir = strchr(w->path.body+1,'/'); + char *begin = w->path.body+1; + web_client_set_directory(w,begin,enddir,moveme); + if (w->query_string.body) { + enddir = strchr(moveme,'='); + if (!web_client_parse_request(w,enddir) ) { + moveme++; + size_t length = strlen(moveme); + w->param_name[0].body = moveme; + w->param_name[0].length = length; + w->param_values[0].body = moveme; + w->param_values[0].length = length; + + w->total_params = 1; + } + } + } else { + w->path.length = w->decoded_length; + + enddir = strchr(w->path.body+1,'/'); + w->directory.body = w->path.body + 1; + if(enddir) { + w->directory.length = (size_t)(enddir - w->directory.body); + enddir++; + + w->version.body = enddir; + enddir = strchr(++enddir,'/'); + if(enddir) { + w->version.length = (size_t)(enddir - w->version.body); + + enddir++; + w->command.body = enddir; + w->command.length = (size_t)(moveme - enddir); + } else{ + w->version.length = strlen(w->version.body); + } + + }else { + w->directory.length = w->decoded_length - 1; + } + web_client_set_without_query_string(w); + } +} + +static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { + char *s = (char *)buffer_tostring(w->response.data), *encoded_url = NULL; + size_t status; + + w->header_parse_tries++; + w->header_parse_last_size = buffer_strlen(w->response.data); + status = w->header_parse_last_size; + + // make sure we have complete request + // complete requests contain: \r\n\r\n + status = url_is_request_complete(s,&s[status],status); + if (w->header_parse_tries > 10) { + if (status == HTTP_VALIDATION_INCOMPLETE) { + 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; + } + } else{ + if (status == HTTP_VALIDATION_INCOMPLETE) { + web_client_enable_wait_receive(w); + return HTTP_VALIDATION_INCOMPLETE; + } + } + //Parse the method used to communicate + s = web_client_parse_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; } - // 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++; - } + encoded_url = s; + s = web_client_find_protocol(w,s); // incomplete requests if(unlikely(!*s)) { web_client_enable_wait_receive(w); @@ -924,64 +1101,40 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { // 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'); + *ue = '\0'; + url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1); - // did we reach the end? - if(unlikely(!*s)) break; + web_client_split_path_query(w); + *ue = ' '; + web_client_parse_headers(w,s); - // is it \r\n ? - if(likely(*s++ == '\n')) { + // 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); - // 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); #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)) ) { - w->header_parse_tries = 0; - w->header_parse_last_size = 0; - web_client_disable_wait_receive(w); - return HTTP_VALIDATION_REDIRECT; - } - } -#endif - - 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 - ); + 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)) ) { + w->header_parse_tries = 0; + w->header_parse_last_size = 0; + web_client_disable_wait_receive(w); + return HTTP_VALIDATION_REDIRECT; } } +#endif - // incomplete request - web_client_enable_wait_receive(w); - return HTTP_VALIDATION_INCOMPLETE; + w->header_parse_tries = 0; + w->header_parse_last_size = 0; + web_client_disable_wait_receive(w); + return HTTP_VALIDATION_OK; } static inline ssize_t web_client_send_data(struct web_client *w,const void *buf,size_t len, int flags) { ssize_t bytes; #ifdef ENABLE_HTTPS - if ( (!web_client_check_unix(w)) && (netdata_srv_ctx) ) { - if ( ( w->ssl.conn ) && ( !w->ssl.flags ) ){ + if ((!web_client_check_unix(w)) && (netdata_srv_ctx)) { + if ((w->ssl.conn) && (!w->ssl.flags)) { bytes = SSL_write(w->ssl.conn,buf, len) ; } else { bytes = send(w->ofd,buf, len , flags); @@ -1102,7 +1255,7 @@ static inline void web_client_send_http_header(struct web_client *w) { buffer_sprintf(w->response.header_output, "Cache-Control: %s\r\n" "Expires: %s\r\n", - (w->response.data->options & WB_CONTENT_NO_CACHEABLE)?"no-cache, no-store, must-revalidate\r\nPragma: no-cache":"public", + (w->response.data->options & WB_CONTENT_NO_CACHEABLE)?"no-cache":"public", edate); } @@ -1143,8 +1296,8 @@ static inline void web_client_send_http_header(struct web_client *w) { size_t count = 0; ssize_t bytes; #ifdef ENABLE_HTTPS - if ( (!web_client_check_unix(w)) && (netdata_srv_ctx) ) { - if ( ( w->ssl.conn ) && ( !w->ssl.flags ) ){ + if ((!web_client_check_unix(w)) && (netdata_srv_ctx)) { + if ((w->ssl.conn) && (!w->ssl.flags)) { while((bytes = SSL_write(w->ssl.conn, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output))) < 0) { count++; if(count > 100 || (errno != EAGAIN && errno != EWOULDBLOCK)) { @@ -1213,21 +1366,52 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch return 400; } - char *tok = mystrsep(&url, "/"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for host with name '%s'.", w->id, tok); + char *tok = strchr(url,'/'); + if (tok) { + w->switch_host = 1; + debug(D_WEB_CLIENT, "%llu: Searching for host with name '%s'.", w->id, url); // copy the URL, we need it to serve files w->last_url[0] = '/'; - if(url && *url) strncpyz(&w->last_url[1], url, NETDATA_WEB_REQUEST_URL_SIZE - 1); - else w->last_url[1] = '\0'; + if(*(tok+1) != ' ') { + strncpyz(&w->last_url[1], tok+1, NETDATA_WEB_REQUEST_URL_SIZE - 1); + char *enddir; + if (w->total_params) { + enddir = strchr(tok+1,'/'); + if (enddir) { + char *moveme = strchr(enddir,'?'); + if (moveme) { + web_client_set_directory(w,tok+1,enddir,moveme); + } + } + else { + w->directory.body = tok; + w->directory.length = strlen(tok); + } + } else { + enddir = strchr(tok+1,'/'); + if(enddir) { + w->directory.body = tok + 1; + w->directory.length = enddir - tok - 1; + } else { + if (!strlen(tok + 1)) { + w->directory.body = tok; + w->directory.length = 1; + } + } + } + } + else { + w->last_url[1] = '\0'; + } + *tok = 0x00; + uint32_t hash = simple_hash(url); - uint32_t hash = simple_hash(tok); + host = rrdhost_find_by_hostname(url, hash); + if(!host) host = rrdhost_find_by_guid(url, hash); + *tok = '/'; - host = rrdhost_find_by_hostname(tok, hash); - if(!host) host = rrdhost_find_by_guid(tok, hash); - - if(host) return web_client_process_url(host, w, url); + if(host) return web_client_process_url(host, w, tok); } buffer_flush(w->response.data); @@ -1258,20 +1442,21 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch #endif } - char *tok = mystrsep(&url, "/?"); - if(likely(tok && *tok)) { - uint32_t hash = simple_hash(tok); - debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok); + if (w->path.length > 1) { + char *cmp = w->directory.body; + size_t len = w->directory.length; + uint32_t hash = simple_nhash(cmp,len); + debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, w->command.body); - if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) { // current API + if(unlikely(hash == hash_api && strncmp(cmp, "api",len) == 0)) { // current API debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id); return check_host_and_call(host, w, url, web_client_api_request); } - else if(unlikely(hash == hash_host && strcmp(tok, "host") == 0)) { // host switching + else if(unlikely(hash == hash_host && strncmp(cmp, "host",len) == 0)) { // host switching debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id); - return web_client_switch_host(host, w, url); + return web_client_switch_host(host, w, cmp+5); } - else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) { // netdata.conf + else if(unlikely(hash == hash_netdata_conf && strncmp(cmp, "netdata.conf",len) == 0)) { // current API if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); @@ -1282,7 +1467,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch return 200; } #ifdef NETDATA_INTERNAL_CHECKS - else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) { + else if(unlikely(hash == hash_exit && strncmp(cmp, "exit",len) == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); @@ -1298,47 +1483,50 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch netdata_cleanup_and_exit(0); return 200; } - else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) { + else if(unlikely(hash == hash_debug && strncmp(cmp, "debug",len) == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); buffer_flush(w->response.data); // get the name of the data to show - tok = mystrsep(&url, "&"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); + char *tok = mystrsep(&url, "/?"); + if(likely(tok && *tok)) { + tok = mystrsep(&url, "&"); + if(tok && *tok) { + 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); + if(!st) st = rrdset_find(host, tok); + if(!st) { + w->response.data->contenttype = 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); + return 404; + } + + debug_flags |= D_RRD_STATS; + + if(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)) + rrdset_flag_clear(st, RRDSET_FLAG_DEBUG); + else + rrdset_flag_set(st, RRDSET_FLAG_DEBUG); - // do we have such a data set? - RRDSET *st = rrdset_find_byname(host, tok); - if(!st) st = rrdset_find(host, tok); - if(!st) { w->response.data->contenttype = CT_TEXT_HTML; - buffer_strcat(w->response.data, "Chart is not found: "); + 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: %s is not found.", w->id, tok); - return 404; + 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; } - - debug_flags |= D_RRD_STATS; - - if(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)) - rrdset_flag_clear(st, RRDSET_FLAG_DEBUG); - else - rrdset_flag_set(st, RRDSET_FLAG_DEBUG); - - w->response.data->contenttype = 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"); - return 200; } buffer_flush(w->response.data); buffer_strcat(w->response.data, "debug which chart?\r\n"); return 400; } - else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) { + else if(unlikely(hash == hash_mirror && strncmp(cmp, "mirror",len) == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); @@ -1355,12 +1543,17 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch #endif /* NETDATA_INTERNAL_CHECKS */ } + w->switch_host = 0; + char *tok = mystrsep(&url, "/?"); + (void)tok; + char filename[FILENAME_MAX+1]; url = filename; strncpyz(filename, w->last_url, FILENAME_MAX); tok = mystrsep(&url, "?"); buffer_flush(w->response.data); - return mysendfile(w, (tok && *tok)?tok:"/"); + + return mysendfile(w, (w->path.length > 1)?tok:"/"); } void web_client_process_request(struct web_client *w) { @@ -1812,8 +2005,8 @@ ssize_t web_client_receive(struct web_client *w) buffer_need_bytes(w->response.data, NETDATA_WEB_REQUEST_RECEIVE_SIZE); #ifdef ENABLE_HTTPS - if ( (!web_client_check_unix(w)) && (netdata_srv_ctx) ) { - if ( ( w->ssl.conn ) && (!w->ssl.flags)) { + if ((!web_client_check_unix(w)) && (netdata_srv_ctx)) { + if ((w->ssl.conn) && (!w->ssl.flags)) { bytes = SSL_read(w->ssl.conn, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1)); }else { bytes = recv(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1), MSG_DONTWAIT); diff --git a/web/server/web_client.h b/web/server/web_client.h index 0a57e8d8e7..c5c5a2abca 100644 --- a/web/server/web_client.h +++ b/web/server/web_client.h @@ -128,6 +128,18 @@ struct web_client { char client_port[NI_MAXSERV+1]; char decoded_url[NETDATA_WEB_REQUEST_URL_SIZE + 1]; // we decode the URL in this buffer + size_t decoded_length; + struct web_fields path; + struct web_fields directory; + struct web_fields query_string; + struct web_fields version; + struct web_fields command; + struct web_fields protocol; + struct web_fields param_name[WEB_FIELDS_MAX]; + struct web_fields param_values[WEB_FIELDS_MAX]; + uint32_t total_params; + int switch_host; + char last_url[NETDATA_WEB_REQUEST_URL_SIZE+1]; // we keep a copy of the decoded URL here char host[256];