Просмотр исходного кода

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!
thiagoftsm 5 лет назад
Родитель
Сommit
58b7d95a7e

+ 28 - 0
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;

+ 85 - 0
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;
+}

+ 22 - 0
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 */

+ 207 - 0
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&gtime=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

+ 57 - 40
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;
-
-        char *name = mystrsep(&value, "=");
-        if(!name || !*name) continue;
-        if(!value || !*value) continue;
-
-        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
-
-        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, "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;
+    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 = 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);
+
+            // 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);
+
+                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;
+
+        } 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;
 }

+ 41 - 38
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(!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;
+            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;
 
-                    break;
+                        break;
+                    }
                 }
             }
-        }
+        } while( ++i < end);
     }
 
     buffer_flush(w->response.data);

+ 77 - 68
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;
-
-                char *key = mystrsep(&value, "=");
-                if (!key || !*key) continue;
-                if (!value || !*value) continue;
-
-                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();
+            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 *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);
+
+                    // 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);
                         }
-                    }
-
-                    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);
+                        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 ;
+
+                } 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;

+ 174 - 138
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;
-
-        char *name = mystrsep(&value, "=");
-        if(!name || !*name) continue;
-        if(!value || !*value) continue;
-
-        if(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0);
+    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 = w->param_name[i].body;
+            size_t lname = w->param_name[i].length;
+
+            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;
-
-        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
-
-        if(!strcmp(name, "chart")) chart = value;
-        //else {
-        /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
-        //  goto cleanup;
-        //}
+    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;
+
+            // name and value are now the parameters
+            // they are not null and not empty
+
+            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;
-
-        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);
-
-        // 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);
+    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;
+
+            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
+
+            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;
                 }
-                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);
 
-        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;
+            return api_commands[i].callback(host, w, url);
+        }
     }
+
+    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;
 }

+ 353 - 160
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;
+static inline HTTP_VALIDATION web_client_is_complete(char *begin,char *end,size_t length){
+    if ( begin == end){
+        return HTTP_VALIDATION_INCOMPLETE;
+    }
 
-    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;
+    if ( length > 3  ){
+        begin = end - 4;
+    }
 
-    w->header_parse_tries++;
-    w->header_parse_last_size = buffer_strlen(w->response.data);
+    uint32_t counter = 0;
+    do{
+        if (*begin == '\r'){
+            begin++;
+            if ( begin == end )
+            {
+                break;
+            }
 
-    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;
+            if (*begin == '\n')
+            {
+                counter++;
             }
+        } else if (*begin == '\n') {
+            begin++;
+            counter++;
+        }
 
-            return HTTP_VALIDATION_INCOMPLETE;
+        if ( counter == 2){
+            break;
         }
     }
+    while(begin != end);
 
-    // is is a valid request?
+    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');
-
-        // did we reach the end?
-        if(unlikely(!*s)) break;
+    *ue = '\0';
+    url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1);
 
-        // is it \r\n ?
-        if(likely(*s++ == '\n')) {
+    web_client_split_path_query(w);
+    *ue = ' ';
+    web_client_parse_headers(w,s);
 
-            // is it again \r\n ? (header end)
-            if(unlikely(*s == '\r' && s[1] == '\n')) {
-                // a valid complete HTTP request found
+    // 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);
 
-                *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';
-
-        uint32_t hash = simple_hash(tok);
+        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);
 
-        host = rrdhost_find_by_hostname(tok, hash);
-        if(!host) host = rrdhost_find_by_guid(tok, hash);
+        host = rrdhost_find_by_hostname(url, hash);
+        if(!host) host = rrdhost_find_by_guid(url, hash);
+        *tok = '/';
 
-        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);
-
-                // 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;
-                }
+            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;
+                    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);
+                    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;
+                    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);

+ 12 - 0
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];