]> arthur.barton.de Git - netdata.git/blob - src/web_api_v1.c
c180cf39fd1cf2e45e3057c6a1ead7d149720696
[netdata.git] / src / web_api_v1.c
1 #include "common.h"
2
3 inline int web_client_api_request_v1_data_group(char *name, int def) {
4     if(!strcmp(name, "average"))
5         return GROUP_AVERAGE;
6
7     else if(!strcmp(name, "min"))
8         return GROUP_MIN;
9
10     else if(!strcmp(name, "max"))
11         return GROUP_MAX;
12
13     else if(!strcmp(name, "sum"))
14         return GROUP_SUM;
15
16     else if(!strcmp(name, "incremental-sum"))
17         return GROUP_INCREMENTAL_SUM;
18
19     return def;
20 }
21
22 inline uint32_t web_client_api_request_v1_data_options(char *o) {
23     uint32_t ret = 0x00000000;
24     char *tok;
25
26     while(o && *o && (tok = mystrsep(&o, ", |"))) {
27         if(!*tok) continue;
28
29         if(!strcmp(tok, "nonzero"))
30             ret |= RRDR_OPTION_NONZERO;
31         else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse"))
32             ret |= RRDR_OPTION_REVERSED;
33         else if(!strcmp(tok, "jsonwrap"))
34             ret |= RRDR_OPTION_JSON_WRAP;
35         else if(!strcmp(tok, "min2max"))
36             ret |= RRDR_OPTION_MIN2MAX;
37         else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds"))
38             ret |= RRDR_OPTION_MILLISECONDS;
39         else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum"))
40             ret |= RRDR_OPTION_ABSOLUTE;
41         else if(!strcmp(tok, "seconds"))
42             ret |= RRDR_OPTION_SECONDS;
43         else if(!strcmp(tok, "null2zero"))
44             ret |= RRDR_OPTION_NULL2ZERO;
45         else if(!strcmp(tok, "objectrows"))
46             ret |= RRDR_OPTION_OBJECTSROWS;
47         else if(!strcmp(tok, "google_json"))
48             ret |= RRDR_OPTION_GOOGLE_JSON;
49         else if(!strcmp(tok, "percentage"))
50             ret |= RRDR_OPTION_PERCENTAGE;
51         else if(!strcmp(tok, "unaligned"))
52             ret |= RRDR_OPTION_NOT_ALIGNED;
53     }
54
55     return ret;
56 }
57
58 inline uint32_t web_client_api_request_v1_data_format(char *name) {
59     if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable
60         return DATASOURCE_DATATABLE_JSON;
61
62     else if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource
63         return DATASOURCE_DATATABLE_JSONP;
64
65     else if(!strcmp(name, DATASOURCE_FORMAT_JSON)) // json
66         return DATASOURCE_JSON;
67
68     else if(!strcmp(name, DATASOURCE_FORMAT_JSONP)) // jsonp
69         return DATASOURCE_JSONP;
70
71     else if(!strcmp(name, DATASOURCE_FORMAT_SSV)) // ssv
72         return DATASOURCE_SSV;
73
74     else if(!strcmp(name, DATASOURCE_FORMAT_CSV)) // csv
75         return DATASOURCE_CSV;
76
77     else if(!strcmp(name, DATASOURCE_FORMAT_TSV) || !strcmp(name, "tsv-excel")) // tsv
78         return DATASOURCE_TSV;
79
80     else if(!strcmp(name, DATASOURCE_FORMAT_HTML)) // html
81         return DATASOURCE_HTML;
82
83     else if(!strcmp(name, DATASOURCE_FORMAT_JS_ARRAY)) // array
84         return DATASOURCE_JS_ARRAY;
85
86     else if(!strcmp(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma
87         return DATASOURCE_SSV_COMMA;
88
89     else if(!strcmp(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray
90         return DATASOURCE_CSV_JSON_ARRAY;
91
92     return DATASOURCE_JSON;
93 }
94
95 inline uint32_t web_client_api_request_v1_data_google_format(char *name) {
96     if(!strcmp(name, "json"))
97         return DATASOURCE_DATATABLE_JSONP;
98
99     else if(!strcmp(name, "html"))
100         return DATASOURCE_HTML;
101
102     else if(!strcmp(name, "csv"))
103         return DATASOURCE_CSV;
104
105     else if(!strcmp(name, "tsv-excel"))
106         return DATASOURCE_TSV;
107
108     return DATASOURCE_JSON;
109 }
110
111
112 inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
113     int all = 0;
114
115     while(url) {
116         char *value = mystrsep(&url, "?&");
117         if (!value || !*value) continue;
118
119         if(!strcmp(value, "all")) all = 1;
120         else if(!strcmp(value, "active")) all = 0;
121     }
122
123     buffer_flush(w->response.data);
124     w->response.data->contenttype = CT_APPLICATION_JSON;
125     health_alarms2json(host, w->response.data, all);
126     return 200;
127 }
128
129 inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
130     uint32_t after = 0;
131
132     while(url) {
133         char *value = mystrsep(&url, "?&");
134         if (!value || !*value) continue;
135
136         char *name = mystrsep(&value, "=");
137         if(!name || !*name) continue;
138         if(!value || !*value) continue;
139
140         if(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0);
141     }
142
143     buffer_flush(w->response.data);
144     w->response.data->contenttype = CT_APPLICATION_JSON;
145     health_alarm_log2json(host, w->response.data, after);
146     return 200;
147 }
148
149 inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
150     int ret = 400;
151     char *chart = NULL;
152
153     buffer_flush(w->response.data);
154
155     while(url) {
156         char *value = mystrsep(&url, "?&");
157         if(!value || !*value) continue;
158
159         char *name = mystrsep(&value, "=");
160         if(!name || !*name) continue;
161         if(!value || !*value) continue;
162
163         // name and value are now the parameters
164         // they are not null and not empty
165
166         if(!strcmp(name, "chart")) chart = value;
167         //else {
168         /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
169         //  goto cleanup;
170         //}
171     }
172
173     if(!chart || !*chart) {
174         buffer_sprintf(w->response.data, "No chart id is given at the request.");
175         goto cleanup;
176     }
177
178     RRDSET *st = rrdset_find(host, chart);
179     if(!st) st = rrdset_find_byname(host, chart);
180     if(!st) {
181         buffer_strcat(w->response.data, "Chart is not found: ");
182         buffer_strcat_htmlescape(w->response.data, chart);
183         ret = 404;
184         goto cleanup;
185     }
186
187     w->response.data->contenttype = CT_APPLICATION_JSON;
188     callback(st, w->response.data);
189     return 200;
190
191     cleanup:
192     return ret;
193 }
194
195 inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
196     return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
197 }
198
199 inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
200     (void)url;
201
202     buffer_flush(w->response.data);
203     w->response.data->contenttype = CT_APPLICATION_JSON;
204     rrd_stats_api_v1_charts(host, w->response.data);
205     return 200;
206 }
207
208 inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
209     int format = ALLMETRICS_SHELL;
210
211     while(url) {
212         char *value = mystrsep(&url, "?&");
213         if (!value || !*value) continue;
214
215         char *name = mystrsep(&value, "=");
216         if(!name || !*name) continue;
217         if(!value || !*value) continue;
218
219         if(!strcmp(name, "format")) {
220             if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
221                 format = ALLMETRICS_SHELL;
222             else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
223                 format = ALLMETRICS_PROMETHEUS;
224             else
225                 format = 0;
226         }
227     }
228
229     buffer_flush(w->response.data);
230     buffer_no_cacheable(w->response.data);
231
232     switch(format) {
233         case ALLMETRICS_SHELL:
234             w->response.data->contenttype = CT_TEXT_PLAIN;
235             rrd_stats_api_v1_charts_allmetrics_shell(host, w->response.data);
236             return 200;
237
238         case ALLMETRICS_PROMETHEUS:
239             w->response.data->contenttype = CT_PROMETHEUS;
240             rrd_stats_api_v1_charts_allmetrics_prometheus(host, w->response.data);
241             return 200;
242
243         default:
244             w->response.data->contenttype = CT_TEXT_PLAIN;
245             buffer_strcat(w->response.data, "Which format? Only '" ALLMETRICS_FORMAT_SHELL "' and '" ALLMETRICS_FORMAT_PROMETHEUS "' is currently supported.");
246             return 400;
247     }
248 }
249
250 inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
251     return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
252 }
253
254 int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
255     int ret = 400;
256     buffer_flush(w->response.data);
257
258     BUFFER *dimensions = NULL;
259
260     const char *chart = NULL
261     , *before_str = NULL
262     , *after_str = NULL
263     , *points_str = NULL
264     , *multiply_str = NULL
265     , *divide_str = NULL
266     , *label = NULL
267     , *units = NULL
268     , *label_color = NULL
269     , *value_color = NULL
270     , *refresh_str = NULL
271     , *precision_str = NULL
272     , *alarm = NULL;
273
274     int group = GROUP_AVERAGE;
275     uint32_t options = 0x00000000;
276
277     while(url) {
278         char *value = mystrsep(&url, "/?&");
279         if(!value || !*value) continue;
280
281         char *name = mystrsep(&value, "=");
282         if(!name || !*name) continue;
283         if(!value || !*value) continue;
284
285         debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
286
287         // name and value are now the parameters
288         // they are not null and not empty
289
290         if(!strcmp(name, "chart")) chart = value;
291         else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
292             if(!dimensions)
293                 dimensions = buffer_create(100);
294
295             buffer_strcat(dimensions, "|");
296             buffer_strcat(dimensions, value);
297         }
298         else if(!strcmp(name, "after")) after_str = value;
299         else if(!strcmp(name, "before")) before_str = value;
300         else if(!strcmp(name, "points")) points_str = value;
301         else if(!strcmp(name, "group")) {
302             group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE);
303         }
304         else if(!strcmp(name, "options")) {
305             options |= web_client_api_request_v1_data_options(value);
306         }
307         else if(!strcmp(name, "label")) label = value;
308         else if(!strcmp(name, "units")) units = value;
309         else if(!strcmp(name, "label_color")) label_color = value;
310         else if(!strcmp(name, "value_color")) value_color = value;
311         else if(!strcmp(name, "multiply")) multiply_str = value;
312         else if(!strcmp(name, "divide")) divide_str = value;
313         else if(!strcmp(name, "refresh")) refresh_str = value;
314         else if(!strcmp(name, "precision")) precision_str = value;
315         else if(!strcmp(name, "alarm")) alarm = value;
316     }
317
318     if(!chart || !*chart) {
319         buffer_no_cacheable(w->response.data);
320         buffer_sprintf(w->response.data, "No chart id is given at the request.");
321         goto cleanup;
322     }
323
324     RRDSET *st = rrdset_find(host, chart);
325     if(!st) st = rrdset_find_byname(host, chart);
326     if(!st) {
327         buffer_no_cacheable(w->response.data);
328         buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1);
329         ret = 200;
330         goto cleanup;
331     }
332
333     RRDCALC *rc = NULL;
334     if(alarm) {
335         rc = rrdcalc_find(st, alarm);
336         if (!rc) {
337             buffer_no_cacheable(w->response.data);
338             buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1);
339             ret = 200;
340             goto cleanup;
341         }
342     }
343
344     long long multiply  = (multiply_str  && *multiply_str )?str2l(multiply_str):1;
345     long long divide    = (divide_str    && *divide_str   )?str2l(divide_str):1;
346     long long before    = (before_str    && *before_str   )?str2l(before_str):0;
347     long long after     = (after_str     && *after_str    )?str2l(after_str):-st->update_every;
348     int       points    = (points_str    && *points_str   )?str2i(points_str):1;
349     int       precision = (precision_str && *precision_str)?str2i(precision_str):-1;
350
351     if(!multiply) multiply = 1;
352     if(!divide) divide = 1;
353
354     int refresh = 0;
355     if(refresh_str && *refresh_str) {
356         if(!strcmp(refresh_str, "auto")) {
357             if(rc) refresh = rc->update_every;
358             else if(options & RRDR_OPTION_NOT_ALIGNED)
359                 refresh = st->update_every;
360             else {
361                 refresh = (int)(before - after);
362                 if(refresh < 0) refresh = -refresh;
363             }
364         }
365         else {
366             refresh = str2i(refresh_str);
367             if(refresh < 0) refresh = -refresh;
368         }
369     }
370
371     if(!label) {
372         if(alarm) {
373             char *s = (char *)alarm;
374             while(*s) {
375                 if(*s == '_') *s = ' ';
376                 s++;
377             }
378             label = alarm;
379         }
380         else if(dimensions) {
381             const char *dim = buffer_tostring(dimensions);
382             if(*dim == '|') dim++;
383             label = dim;
384         }
385         else
386             label = st->name;
387     }
388     if(!units) {
389         if(alarm) {
390             if(rc->units)
391                 units = rc->units;
392             else
393                 units = "";
394         }
395         else if(options & RRDR_OPTION_PERCENTAGE)
396             units = "%";
397         else
398             units = st->units;
399     }
400
401     debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'"
402           , w->id
403           , chart
404           , alarm?alarm:""
405           , (dimensions)?buffer_tostring(dimensions):""
406           , after
407           , before
408           , points
409           , group
410           , options
411     );
412
413     if(rc) {
414         if (refresh > 0) {
415             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
416             w->response.data->expires = now_realtime_sec() + refresh;
417         }
418         else buffer_no_cacheable(w->response.data);
419
420         if(!value_color) {
421             switch(rc->status) {
422                 case RRDCALC_STATUS_CRITICAL:
423                     value_color = "red";
424                     break;
425
426                 case RRDCALC_STATUS_WARNING:
427                     value_color = "orange";
428                     break;
429
430                 case RRDCALC_STATUS_CLEAR:
431                     value_color = "brightgreen";
432                     break;
433
434                 case RRDCALC_STATUS_UNDEFINED:
435                     value_color = "lightgrey";
436                     break;
437
438                 case RRDCALC_STATUS_UNINITIALIZED:
439                     value_color = "#000";
440                     break;
441
442                 default:
443                     value_color = "grey";
444                     break;
445             }
446         }
447
448         buffer_svg(w->response.data,
449                 label,
450                 (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
451                 units,
452                 label_color,
453                 value_color,
454                 precision);
455         ret = 200;
456     }
457     else {
458         time_t latest_timestamp = 0;
459         int value_is_null = 1;
460         calculated_number n = NAN;
461         ret = 500;
462
463         // if the collected value is too old, don't calculate its value
464         if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above)))
465             ret = rrd2value(st,
466                     w->response.data,
467                     &n,
468                     (dimensions) ? buffer_tostring(dimensions) : NULL,
469                     points,
470                     after,
471                     before,
472                     group,
473                     options,
474                     NULL,
475                     &latest_timestamp,
476                     &value_is_null);
477
478         // if the value cannot be calculated, show empty badge
479         if (ret != 200) {
480             buffer_no_cacheable(w->response.data);
481             value_is_null = 1;
482             n = 0;
483             ret = 200;
484         }
485         else if (refresh > 0) {
486             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
487             w->response.data->expires = now_realtime_sec() + refresh;
488         }
489         else buffer_no_cacheable(w->response.data);
490
491         // render the badge
492         buffer_svg(w->response.data,
493                 label,
494                 (value_is_null)?NAN:(n * multiply / divide),
495                 units,
496                 label_color,
497                 value_color,
498                 precision);
499     }
500
501     cleanup:
502     buffer_free(dimensions);
503     return ret;
504 }
505
506 // returns the HTTP code
507 inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
508     debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
509
510     int ret = 400;
511     BUFFER *dimensions = NULL;
512
513     buffer_flush(w->response.data);
514
515     char    *google_version = "0.6",
516             *google_reqId = "0",
517             *google_sig = "0",
518             *google_out = "json",
519             *responseHandler = NULL,
520             *outFileName = NULL;
521
522     time_t last_timestamp_in_data = 0, google_timestamp = 0;
523
524     char *chart = NULL
525     , *before_str = NULL
526     , *after_str = NULL
527     , *points_str = NULL;
528
529     int group = GROUP_AVERAGE;
530     uint32_t format = DATASOURCE_JSON;
531     uint32_t options = 0x00000000;
532
533     while(url) {
534         char *value = mystrsep(&url, "?&");
535         if(!value || !*value) continue;
536
537         char *name = mystrsep(&value, "=");
538         if(!name || !*name) continue;
539         if(!value || !*value) continue;
540
541         debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
542
543         // name and value are now the parameters
544         // they are not null and not empty
545
546         if(!strcmp(name, "chart")) chart = value;
547         else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
548             if(!dimensions) dimensions = buffer_create(100);
549             buffer_strcat(dimensions, "|");
550             buffer_strcat(dimensions, value);
551         }
552         else if(!strcmp(name, "after")) after_str = value;
553         else if(!strcmp(name, "before")) before_str = value;
554         else if(!strcmp(name, "points")) points_str = value;
555         else if(!strcmp(name, "group")) {
556             group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE);
557         }
558         else if(!strcmp(name, "format")) {
559             format = web_client_api_request_v1_data_format(value);
560         }
561         else if(!strcmp(name, "options")) {
562             options |= web_client_api_request_v1_data_options(value);
563         }
564         else if(!strcmp(name, "callback")) {
565             responseHandler = value;
566         }
567         else if(!strcmp(name, "filename")) {
568             outFileName = value;
569         }
570         else if(!strcmp(name, "tqx")) {
571             // parse Google Visualization API options
572             // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
573             char *tqx_name, *tqx_value;
574
575             while(value) {
576                 tqx_value = mystrsep(&value, ";");
577                 if(!tqx_value || !*tqx_value) continue;
578
579                 tqx_name = mystrsep(&tqx_value, ":");
580                 if(!tqx_name || !*tqx_name) continue;
581                 if(!tqx_value || !*tqx_value) continue;
582
583                 if(!strcmp(tqx_name, "version"))
584                     google_version = tqx_value;
585                 else if(!strcmp(tqx_name, "reqId"))
586                     google_reqId = tqx_value;
587                 else if(!strcmp(tqx_name, "sig")) {
588                     google_sig = tqx_value;
589                     google_timestamp = strtoul(google_sig, NULL, 0);
590                 }
591                 else if(!strcmp(tqx_name, "out")) {
592                     google_out = tqx_value;
593                     format = web_client_api_request_v1_data_google_format(google_out);
594                 }
595                 else if(!strcmp(tqx_name, "responseHandler"))
596                     responseHandler = tqx_value;
597                 else if(!strcmp(tqx_name, "outFileName"))
598                     outFileName = tqx_value;
599             }
600         }
601     }
602
603     if(!chart || !*chart) {
604         buffer_sprintf(w->response.data, "No chart id is given at the request.");
605         goto cleanup;
606     }
607
608     RRDSET *st = rrdset_find(host, chart);
609     if(!st) st = rrdset_find_byname(host, chart);
610     if(!st) {
611         buffer_strcat(w->response.data, "Chart is not found: ");
612         buffer_strcat_htmlescape(w->response.data, chart);
613         ret = 404;
614         goto cleanup;
615     }
616
617     long long before = (before_str && *before_str)?str2l(before_str):0;
618     long long after  = (after_str  && *after_str) ?str2l(after_str):0;
619     int       points = (points_str && *points_str)?str2i(points_str):0;
620
621     debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', format '%u', options '0x%08x'"
622           , w->id
623           , chart
624           , (dimensions)?buffer_tostring(dimensions):""
625           , after
626           , before
627           , points
628           , group
629           , format
630           , options
631     );
632
633     if(outFileName && *outFileName) {
634         buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
635         debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
636     }
637
638     if(format == DATASOURCE_DATATABLE_JSONP) {
639         if(responseHandler == NULL)
640             responseHandler = "google.visualization.Query.setResponse";
641
642         debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
643                 w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
644         );
645
646         buffer_sprintf(w->response.data,
647                 "%s({version:'%s',reqId:'%s',status:'ok',sig:'%ld',table:",
648                 responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
649     }
650     else if(format == DATASOURCE_JSONP) {
651         if(responseHandler == NULL)
652             responseHandler = "callback";
653
654         buffer_strcat(w->response.data, responseHandler);
655         buffer_strcat(w->response.data, "(");
656     }
657
658     ret = rrd2format(st, w->response.data, dimensions, format, points, after, before, group, options, &last_timestamp_in_data);
659
660     if(format == DATASOURCE_DATATABLE_JSONP) {
661         if(google_timestamp < last_timestamp_in_data)
662             buffer_strcat(w->response.data, "});");
663
664         else {
665             // the client already has the latest data
666             buffer_flush(w->response.data);
667             buffer_sprintf(w->response.data,
668                     "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
669                     responseHandler, google_version, google_reqId);
670         }
671     }
672     else if(format == DATASOURCE_JSONP)
673         buffer_strcat(w->response.data, ");");
674
675     cleanup:
676     buffer_free(dimensions);
677     return ret;
678 }
679
680 inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
681     static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
682             hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
683             hash_to = 0 /*, hash_redirects = 0 */;
684
685     if(unlikely(!hash_action)) {
686         hash_action = simple_hash("action");
687         hash_access = simple_hash("access");
688         hash_hello = simple_hash("hello");
689         hash_delete = simple_hash("delete");
690         hash_search = simple_hash("search");
691         hash_switch = simple_hash("switch");
692         hash_machine = simple_hash("machine");
693         hash_url = simple_hash("url");
694         hash_name = simple_hash("name");
695         hash_delete_url = simple_hash("delete_url");
696         hash_for = simple_hash("for");
697         hash_to = simple_hash("to");
698 /*
699         hash_redirects = simple_hash("redirects");
700 */
701     }
702
703     char person_guid[GUID_LEN + 1] = "";
704
705     debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
706
707     // FIXME
708     // The browser may send multiple cookies with our id
709
710     char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
711     if(cookie)
712         strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], 36);
713
714     char action = '\0';
715     char *machine_guid = NULL,
716             *machine_url = NULL,
717             *url_name = NULL,
718             *search_machine_guid = NULL,
719             *delete_url = NULL,
720             *to_person_guid = NULL;
721 /*
722     int redirects = 0;
723 */
724
725     while(url) {
726         char *value = mystrsep(&url, "?&");
727         if (!value || !*value) continue;
728
729         char *name = mystrsep(&value, "=");
730         if (!name || !*name) continue;
731         if (!value || !*value) continue;
732
733         debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
734
735         uint32_t hash = simple_hash(name);
736
737         if(hash == hash_action && !strcmp(name, "action")) {
738             uint32_t vhash = simple_hash(value);
739
740             if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
741             else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
742             else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
743             else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
744             else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
745 #ifdef NETDATA_INTERNAL_CHECKS
746             else error("unknown registry action '%s'", value);
747 #endif /* NETDATA_INTERNAL_CHECKS */
748         }
749 /*
750         else if(hash == hash_redirects && !strcmp(name, "redirects"))
751             redirects = atoi(value);
752 */
753         else if(hash == hash_machine && !strcmp(name, "machine"))
754             machine_guid = value;
755
756         else if(hash == hash_url && !strcmp(name, "url"))
757             machine_url = value;
758
759         else if(action == 'A') {
760             if(hash == hash_name && !strcmp(name, "name"))
761                 url_name = value;
762         }
763         else if(action == 'D') {
764             if(hash == hash_delete_url && !strcmp(name, "delete_url"))
765                 delete_url = value;
766         }
767         else if(action == 'S') {
768             if(hash == hash_for && !strcmp(name, "for"))
769                 search_machine_guid = value;
770         }
771         else if(action == 'W') {
772             if(hash == hash_to && !strcmp(name, "to"))
773                 to_person_guid = value;
774         }
775 #ifdef NETDATA_INTERNAL_CHECKS
776         else error("unused registry URL parameter '%s' with value '%s'", name, value);
777 #endif /* NETDATA_INTERNAL_CHECKS */
778     }
779
780     if(respect_web_browser_do_not_track_policy && w->donottrack) {
781         buffer_flush(w->response.data);
782         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.");
783         return 400;
784     }
785
786     if(action == 'A' && (!machine_guid || !machine_url || !url_name)) {
787         error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')",
788                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET");
789         buffer_flush(w->response.data);
790         buffer_strcat(w->response.data, "Invalid registry Access request.");
791         return 400;
792     }
793     else if(action == 'D' && (!machine_guid || !machine_url || !delete_url)) {
794         error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')",
795                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
796         buffer_flush(w->response.data);
797         buffer_strcat(w->response.data, "Invalid registry Delete request.");
798         return 400;
799     }
800     else if(action == 'S' && (!machine_guid || !machine_url || !search_machine_guid)) {
801         error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')",
802                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
803         buffer_flush(w->response.data);
804         buffer_strcat(w->response.data, "Invalid registry Search request.");
805         return 400;
806     }
807     else if(action == 'W' && (!machine_guid || !machine_url || !to_person_guid)) {
808         error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')",
809                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
810         buffer_flush(w->response.data);
811         buffer_strcat(w->response.data, "Invalid registry Switch request.");
812         return 400;
813     }
814
815     switch(action) {
816         case 'A':
817             w->tracking_required = 1;
818             return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
819
820         case 'D':
821             w->tracking_required = 1;
822             return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
823
824         case 'S':
825             w->tracking_required = 1;
826             return registry_request_search_json(host, w, person_guid, machine_guid, machine_url, search_machine_guid, now_realtime_sec());
827
828         case 'W':
829             w->tracking_required = 1;
830             return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
831
832         case 'H':
833             return registry_request_hello_json(host, w);
834
835         default:
836             buffer_flush(w->response.data);
837             buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
838             return 400;
839     }
840 }
841
842 inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) {
843     static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0, hash_alarms = 0, hash_alarm_log = 0, hash_alarm_variables = 0, hash_raw = 0;
844
845     if(unlikely(hash_data == 0)) {
846         hash_data = simple_hash("data");
847         hash_chart = simple_hash("chart");
848         hash_charts = simple_hash("charts");
849         hash_registry = simple_hash("registry");
850         hash_badge = simple_hash("badge.svg");
851         hash_alarms = simple_hash("alarms");
852         hash_alarm_log = simple_hash("alarm_log");
853         hash_alarm_variables = simple_hash("alarm_variables");
854         hash_raw = simple_hash("allmetrics");
855     }
856
857     // get the command
858     char *tok = mystrsep(&url, "/?&");
859     if(tok && *tok) {
860         debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
861         uint32_t hash = simple_hash(tok);
862
863         if(hash == hash_data && !strcmp(tok, "data"))
864             return web_client_api_request_v1_data(host, w, url);
865
866         else if(hash == hash_chart && !strcmp(tok, "chart"))
867             return web_client_api_request_v1_chart(host, w, url);
868
869         else if(hash == hash_charts && !strcmp(tok, "charts"))
870             return web_client_api_request_v1_charts(host, w, url);
871
872         else if(hash == hash_registry && !strcmp(tok, "registry"))
873             return web_client_api_request_v1_registry(host, w, url);
874
875         else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
876             return web_client_api_request_v1_badge(host, w, url);
877
878         else if(hash == hash_alarms && !strcmp(tok, "alarms"))
879             return web_client_api_request_v1_alarms(host, w, url);
880
881         else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log"))
882             return web_client_api_request_v1_alarm_log(host, w, url);
883
884         else if(hash == hash_alarm_variables && !strcmp(tok, "alarm_variables"))
885             return web_client_api_request_v1_alarm_variables(host, w, url);
886
887         else if(hash == hash_raw && !strcmp(tok, "allmetrics"))
888             return web_client_api_request_v1_allmetrics(host, w, url);
889
890         else {
891             buffer_flush(w->response.data);
892             buffer_strcat(w->response.data, "Unsupported v1 API command: ");
893             buffer_strcat_htmlescape(w->response.data, tok);
894             return 404;
895         }
896     }
897     else {
898         buffer_flush(w->response.data);
899         buffer_sprintf(w->response.data, "Which API v1 command?");
900         return 400;
901     }
902 }