]> arthur.barton.de Git - netdata.git/blob - src/web_api_v1.c
Merge pull request #1998 from ktsaou/master
[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     st->last_accessed_time = now_realtime_sec();
189     callback(st, w->response.data);
190     return 200;
191
192     cleanup:
193     return ret;
194 }
195
196 inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
197     return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
198 }
199
200 inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
201     (void)url;
202
203     buffer_flush(w->response.data);
204     w->response.data->contenttype = CT_APPLICATION_JSON;
205     rrd_stats_api_v1_charts(host, w->response.data);
206     return 200;
207 }
208
209 inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
210     int format = ALLMETRICS_SHELL;
211
212     while(url) {
213         char *value = mystrsep(&url, "?&");
214         if (!value || !*value) continue;
215
216         char *name = mystrsep(&value, "=");
217         if(!name || !*name) continue;
218         if(!value || !*value) continue;
219
220         if(!strcmp(name, "format")) {
221             if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
222                 format = ALLMETRICS_SHELL;
223             else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
224                 format = ALLMETRICS_PROMETHEUS;
225             else if(!strcmp(value, ALLMETRICS_FORMAT_JSON))
226                 format = ALLMETRICS_JSON;
227             else
228                 format = 0;
229         }
230     }
231
232     buffer_flush(w->response.data);
233     buffer_no_cacheable(w->response.data);
234
235     switch(format) {
236         case ALLMETRICS_JSON:
237             w->response.data->contenttype = CT_APPLICATION_JSON;
238             rrd_stats_api_v1_charts_allmetrics_json(host, w->response.data);
239             return 200;
240
241         case ALLMETRICS_SHELL:
242             w->response.data->contenttype = CT_TEXT_PLAIN;
243             rrd_stats_api_v1_charts_allmetrics_shell(host, w->response.data);
244             return 200;
245
246         case ALLMETRICS_PROMETHEUS:
247             w->response.data->contenttype = CT_PROMETHEUS;
248             rrd_stats_api_v1_charts_allmetrics_prometheus(host, w->response.data);
249             return 200;
250
251         default:
252             w->response.data->contenttype = CT_TEXT_PLAIN;
253             buffer_strcat(w->response.data, "Which format? '" ALLMETRICS_FORMAT_SHELL "', '" ALLMETRICS_FORMAT_PROMETHEUS "' and '" ALLMETRICS_FORMAT_JSON "' are currently supported.");
254             return 400;
255     }
256 }
257
258 inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
259     return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
260 }
261
262 int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
263     int ret = 400;
264     buffer_flush(w->response.data);
265
266     BUFFER *dimensions = NULL;
267
268     const char *chart = NULL
269     , *before_str = NULL
270     , *after_str = NULL
271     , *points_str = NULL
272     , *multiply_str = NULL
273     , *divide_str = NULL
274     , *label = NULL
275     , *units = NULL
276     , *label_color = NULL
277     , *value_color = NULL
278     , *refresh_str = NULL
279     , *precision_str = NULL
280     , *alarm = NULL;
281
282     int group = GROUP_AVERAGE;
283     uint32_t options = 0x00000000;
284
285     while(url) {
286         char *value = mystrsep(&url, "/?&");
287         if(!value || !*value) continue;
288
289         char *name = mystrsep(&value, "=");
290         if(!name || !*name) continue;
291         if(!value || !*value) continue;
292
293         debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
294
295         // name and value are now the parameters
296         // they are not null and not empty
297
298         if(!strcmp(name, "chart")) chart = value;
299         else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
300             if(!dimensions)
301                 dimensions = buffer_create(100);
302
303             buffer_strcat(dimensions, "|");
304             buffer_strcat(dimensions, value);
305         }
306         else if(!strcmp(name, "after")) after_str = value;
307         else if(!strcmp(name, "before")) before_str = value;
308         else if(!strcmp(name, "points")) points_str = value;
309         else if(!strcmp(name, "group")) {
310             group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE);
311         }
312         else if(!strcmp(name, "options")) {
313             options |= web_client_api_request_v1_data_options(value);
314         }
315         else if(!strcmp(name, "label")) label = value;
316         else if(!strcmp(name, "units")) units = value;
317         else if(!strcmp(name, "label_color")) label_color = value;
318         else if(!strcmp(name, "value_color")) value_color = value;
319         else if(!strcmp(name, "multiply")) multiply_str = value;
320         else if(!strcmp(name, "divide")) divide_str = value;
321         else if(!strcmp(name, "refresh")) refresh_str = value;
322         else if(!strcmp(name, "precision")) precision_str = value;
323         else if(!strcmp(name, "alarm")) alarm = value;
324     }
325
326     if(!chart || !*chart) {
327         buffer_no_cacheable(w->response.data);
328         buffer_sprintf(w->response.data, "No chart id is given at the request.");
329         goto cleanup;
330     }
331
332     RRDSET *st = rrdset_find(host, chart);
333     if(!st) st = rrdset_find_byname(host, chart);
334     if(!st) {
335         buffer_no_cacheable(w->response.data);
336         buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1);
337         ret = 200;
338         goto cleanup;
339     }
340     st->last_accessed_time = now_realtime_sec();
341
342     RRDCALC *rc = NULL;
343     if(alarm) {
344         rc = rrdcalc_find(st, alarm);
345         if (!rc) {
346             buffer_no_cacheable(w->response.data);
347             buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1);
348             ret = 200;
349             goto cleanup;
350         }
351     }
352
353     long long multiply  = (multiply_str  && *multiply_str )?str2l(multiply_str):1;
354     long long divide    = (divide_str    && *divide_str   )?str2l(divide_str):1;
355     long long before    = (before_str    && *before_str   )?str2l(before_str):0;
356     long long after     = (after_str     && *after_str    )?str2l(after_str):-st->update_every;
357     int       points    = (points_str    && *points_str   )?str2i(points_str):1;
358     int       precision = (precision_str && *precision_str)?str2i(precision_str):-1;
359
360     if(!multiply) multiply = 1;
361     if(!divide) divide = 1;
362
363     int refresh = 0;
364     if(refresh_str && *refresh_str) {
365         if(!strcmp(refresh_str, "auto")) {
366             if(rc) refresh = rc->update_every;
367             else if(options & RRDR_OPTION_NOT_ALIGNED)
368                 refresh = st->update_every;
369             else {
370                 refresh = (int)(before - after);
371                 if(refresh < 0) refresh = -refresh;
372             }
373         }
374         else {
375             refresh = str2i(refresh_str);
376             if(refresh < 0) refresh = -refresh;
377         }
378     }
379
380     if(!label) {
381         if(alarm) {
382             char *s = (char *)alarm;
383             while(*s) {
384                 if(*s == '_') *s = ' ';
385                 s++;
386             }
387             label = alarm;
388         }
389         else if(dimensions) {
390             const char *dim = buffer_tostring(dimensions);
391             if(*dim == '|') dim++;
392             label = dim;
393         }
394         else
395             label = st->name;
396     }
397     if(!units) {
398         if(alarm) {
399             if(rc->units)
400                 units = rc->units;
401             else
402                 units = "";
403         }
404         else if(options & RRDR_OPTION_PERCENTAGE)
405             units = "%";
406         else
407             units = st->units;
408     }
409
410     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'"
411           , w->id
412           , chart
413           , alarm?alarm:""
414           , (dimensions)?buffer_tostring(dimensions):""
415           , after
416           , before
417           , points
418           , group
419           , options
420     );
421
422     if(rc) {
423         if (refresh > 0) {
424             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
425             w->response.data->expires = now_realtime_sec() + refresh;
426         }
427         else buffer_no_cacheable(w->response.data);
428
429         if(!value_color) {
430             switch(rc->status) {
431                 case RRDCALC_STATUS_CRITICAL:
432                     value_color = "red";
433                     break;
434
435                 case RRDCALC_STATUS_WARNING:
436                     value_color = "orange";
437                     break;
438
439                 case RRDCALC_STATUS_CLEAR:
440                     value_color = "brightgreen";
441                     break;
442
443                 case RRDCALC_STATUS_UNDEFINED:
444                     value_color = "lightgrey";
445                     break;
446
447                 case RRDCALC_STATUS_UNINITIALIZED:
448                     value_color = "#000";
449                     break;
450
451                 default:
452                     value_color = "grey";
453                     break;
454             }
455         }
456
457         buffer_svg(w->response.data,
458                 label,
459                 (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
460                 units,
461                 label_color,
462                 value_color,
463                 precision);
464         ret = 200;
465     }
466     else {
467         time_t latest_timestamp = 0;
468         int value_is_null = 1;
469         calculated_number n = NAN;
470         ret = 500;
471
472         // if the collected value is too old, don't calculate its value
473         if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above)))
474             ret = rrdset2value_api_v1(st, w->response.data, &n, (dimensions) ? buffer_tostring(dimensions) : NULL
475                                       , points, after, before, group, options, NULL, &latest_timestamp, &value_is_null);
476
477         // if the value cannot be calculated, show empty badge
478         if (ret != 200) {
479             buffer_no_cacheable(w->response.data);
480             value_is_null = 1;
481             n = 0;
482             ret = 200;
483         }
484         else if (refresh > 0) {
485             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
486             w->response.data->expires = now_realtime_sec() + refresh;
487         }
488         else buffer_no_cacheable(w->response.data);
489
490         // render the badge
491         buffer_svg(w->response.data,
492                 label,
493                 (value_is_null)?NAN:(n * multiply / divide),
494                 units,
495                 label_color,
496                 value_color,
497                 precision);
498     }
499
500     cleanup:
501     buffer_free(dimensions);
502     return ret;
503 }
504
505 // returns the HTTP code
506 inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
507     debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
508
509     int ret = 400;
510     BUFFER *dimensions = NULL;
511
512     buffer_flush(w->response.data);
513
514     char    *google_version = "0.6",
515             *google_reqId = "0",
516             *google_sig = "0",
517             *google_out = "json",
518             *responseHandler = NULL,
519             *outFileName = NULL;
520
521     time_t last_timestamp_in_data = 0, google_timestamp = 0;
522
523     char *chart = NULL
524     , *before_str = NULL
525     , *after_str = NULL
526     , *points_str = NULL;
527
528     int group = GROUP_AVERAGE;
529     uint32_t format = DATASOURCE_JSON;
530     uint32_t options = 0x00000000;
531
532     while(url) {
533         char *value = mystrsep(&url, "?&");
534         if(!value || !*value) continue;
535
536         char *name = mystrsep(&value, "=");
537         if(!name || !*name) continue;
538         if(!value || !*value) continue;
539
540         debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
541
542         // name and value are now the parameters
543         // they are not null and not empty
544
545         if(!strcmp(name, "chart")) chart = value;
546         else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
547             if(!dimensions) dimensions = buffer_create(100);
548             buffer_strcat(dimensions, "|");
549             buffer_strcat(dimensions, value);
550         }
551         else if(!strcmp(name, "after")) after_str = value;
552         else if(!strcmp(name, "before")) before_str = value;
553         else if(!strcmp(name, "points")) points_str = value;
554         else if(!strcmp(name, "group")) {
555             group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE);
556         }
557         else if(!strcmp(name, "format")) {
558             format = web_client_api_request_v1_data_format(value);
559         }
560         else if(!strcmp(name, "options")) {
561             options |= web_client_api_request_v1_data_options(value);
562         }
563         else if(!strcmp(name, "callback")) {
564             responseHandler = value;
565         }
566         else if(!strcmp(name, "filename")) {
567             outFileName = value;
568         }
569         else if(!strcmp(name, "tqx")) {
570             // parse Google Visualization API options
571             // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
572             char *tqx_name, *tqx_value;
573
574             while(value) {
575                 tqx_value = mystrsep(&value, ";");
576                 if(!tqx_value || !*tqx_value) continue;
577
578                 tqx_name = mystrsep(&tqx_value, ":");
579                 if(!tqx_name || !*tqx_name) continue;
580                 if(!tqx_value || !*tqx_value) continue;
581
582                 if(!strcmp(tqx_name, "version"))
583                     google_version = tqx_value;
584                 else if(!strcmp(tqx_name, "reqId"))
585                     google_reqId = tqx_value;
586                 else if(!strcmp(tqx_name, "sig")) {
587                     google_sig = tqx_value;
588                     google_timestamp = strtoul(google_sig, NULL, 0);
589                 }
590                 else if(!strcmp(tqx_name, "out")) {
591                     google_out = tqx_value;
592                     format = web_client_api_request_v1_data_google_format(google_out);
593                 }
594                 else if(!strcmp(tqx_name, "responseHandler"))
595                     responseHandler = tqx_value;
596                 else if(!strcmp(tqx_name, "outFileName"))
597                     outFileName = tqx_value;
598             }
599         }
600     }
601
602     if(!chart || !*chart) {
603         buffer_sprintf(w->response.data, "No chart id is given at the request.");
604         goto cleanup;
605     }
606
607     RRDSET *st = rrdset_find(host, chart);
608     if(!st) st = rrdset_find_byname(host, chart);
609     if(!st) {
610         buffer_strcat(w->response.data, "Chart is not found: ");
611         buffer_strcat_htmlescape(w->response.data, chart);
612         ret = 404;
613         goto cleanup;
614     }
615     st->last_accessed_time = now_realtime_sec();
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 = rrdset2anything_api_v1(st, w->response.data, dimensions, format, points, after, before, group, options
659                                  , &last_timestamp_in_data);
660
661     if(format == DATASOURCE_DATATABLE_JSONP) {
662         if(google_timestamp < last_timestamp_in_data)
663             buffer_strcat(w->response.data, "});");
664
665         else {
666             // the client already has the latest data
667             buffer_flush(w->response.data);
668             buffer_sprintf(w->response.data,
669                     "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
670                     responseHandler, google_version, google_reqId);
671         }
672     }
673     else if(format == DATASOURCE_JSONP)
674         buffer_strcat(w->response.data, ");");
675
676     cleanup:
677     buffer_free(dimensions);
678     return ret;
679 }
680
681 inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
682     static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
683             hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
684             hash_to = 0 /*, hash_redirects = 0 */;
685
686     if(unlikely(!hash_action)) {
687         hash_action = simple_hash("action");
688         hash_access = simple_hash("access");
689         hash_hello = simple_hash("hello");
690         hash_delete = simple_hash("delete");
691         hash_search = simple_hash("search");
692         hash_switch = simple_hash("switch");
693         hash_machine = simple_hash("machine");
694         hash_url = simple_hash("url");
695         hash_name = simple_hash("name");
696         hash_delete_url = simple_hash("delete_url");
697         hash_for = simple_hash("for");
698         hash_to = simple_hash("to");
699 /*
700         hash_redirects = simple_hash("redirects");
701 */
702     }
703
704     char person_guid[GUID_LEN + 1] = "";
705
706     debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
707
708     // FIXME
709     // The browser may send multiple cookies with our id
710
711     char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
712     if(cookie)
713         strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], 36);
714
715     char action = '\0';
716     char *machine_guid = NULL,
717             *machine_url = NULL,
718             *url_name = NULL,
719             *search_machine_guid = NULL,
720             *delete_url = NULL,
721             *to_person_guid = NULL;
722 /*
723     int redirects = 0;
724 */
725
726     while(url) {
727         char *value = mystrsep(&url, "?&");
728         if (!value || !*value) continue;
729
730         char *name = mystrsep(&value, "=");
731         if (!name || !*name) continue;
732         if (!value || !*value) continue;
733
734         debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
735
736         uint32_t hash = simple_hash(name);
737
738         if(hash == hash_action && !strcmp(name, "action")) {
739             uint32_t vhash = simple_hash(value);
740
741             if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
742             else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
743             else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
744             else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
745             else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
746 #ifdef NETDATA_INTERNAL_CHECKS
747             else error("unknown registry action '%s'", value);
748 #endif /* NETDATA_INTERNAL_CHECKS */
749         }
750 /*
751         else if(hash == hash_redirects && !strcmp(name, "redirects"))
752             redirects = atoi(value);
753 */
754         else if(hash == hash_machine && !strcmp(name, "machine"))
755             machine_guid = value;
756
757         else if(hash == hash_url && !strcmp(name, "url"))
758             machine_url = value;
759
760         else if(action == 'A') {
761             if(hash == hash_name && !strcmp(name, "name"))
762                 url_name = value;
763         }
764         else if(action == 'D') {
765             if(hash == hash_delete_url && !strcmp(name, "delete_url"))
766                 delete_url = value;
767         }
768         else if(action == 'S') {
769             if(hash == hash_for && !strcmp(name, "for"))
770                 search_machine_guid = value;
771         }
772         else if(action == 'W') {
773             if(hash == hash_to && !strcmp(name, "to"))
774                 to_person_guid = value;
775         }
776 #ifdef NETDATA_INTERNAL_CHECKS
777         else error("unused registry URL parameter '%s' with value '%s'", name, value);
778 #endif /* NETDATA_INTERNAL_CHECKS */
779     }
780
781     if(respect_web_browser_do_not_track_policy && w->donottrack) {
782         buffer_flush(w->response.data);
783         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.");
784         return 400;
785     }
786
787     if(action == 'A' && (!machine_guid || !machine_url || !url_name)) {
788         error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')",
789                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET");
790         buffer_flush(w->response.data);
791         buffer_strcat(w->response.data, "Invalid registry Access request.");
792         return 400;
793     }
794     else if(action == 'D' && (!machine_guid || !machine_url || !delete_url)) {
795         error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')",
796                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
797         buffer_flush(w->response.data);
798         buffer_strcat(w->response.data, "Invalid registry Delete request.");
799         return 400;
800     }
801     else if(action == 'S' && (!machine_guid || !machine_url || !search_machine_guid)) {
802         error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')",
803                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
804         buffer_flush(w->response.data);
805         buffer_strcat(w->response.data, "Invalid registry Search request.");
806         return 400;
807     }
808     else if(action == 'W' && (!machine_guid || !machine_url || !to_person_guid)) {
809         error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')",
810                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
811         buffer_flush(w->response.data);
812         buffer_strcat(w->response.data, "Invalid registry Switch request.");
813         return 400;
814     }
815
816     switch(action) {
817         case 'A':
818             w->tracking_required = 1;
819             return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
820
821         case 'D':
822             w->tracking_required = 1;
823             return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
824
825         case 'S':
826             w->tracking_required = 1;
827             return registry_request_search_json(host, w, person_guid, machine_guid, machine_url, search_machine_guid, now_realtime_sec());
828
829         case 'W':
830             w->tracking_required = 1;
831             return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
832
833         case 'H':
834             return registry_request_hello_json(host, w);
835
836         default:
837             buffer_flush(w->response.data);
838             buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
839             return 400;
840     }
841 }
842
843 inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) {
844     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;
845
846     if(unlikely(hash_data == 0)) {
847         hash_data = simple_hash("data");
848         hash_chart = simple_hash("chart");
849         hash_charts = simple_hash("charts");
850         hash_registry = simple_hash("registry");
851         hash_badge = simple_hash("badge.svg");
852         hash_alarms = simple_hash("alarms");
853         hash_alarm_log = simple_hash("alarm_log");
854         hash_alarm_variables = simple_hash("alarm_variables");
855         hash_raw = simple_hash("allmetrics");
856     }
857
858     // get the command
859     char *tok = mystrsep(&url, "/?&");
860     if(tok && *tok) {
861         debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
862         uint32_t hash = simple_hash(tok);
863
864         if(hash == hash_data && !strcmp(tok, "data"))
865             return web_client_api_request_v1_data(host, w, url);
866
867         else if(hash == hash_chart && !strcmp(tok, "chart"))
868             return web_client_api_request_v1_chart(host, w, url);
869
870         else if(hash == hash_charts && !strcmp(tok, "charts"))
871             return web_client_api_request_v1_charts(host, w, url);
872
873         else if(hash == hash_registry && !strcmp(tok, "registry"))
874             return web_client_api_request_v1_registry(host, w, url);
875
876         else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
877             return web_client_api_request_v1_badge(host, w, url);
878
879         else if(hash == hash_alarms && !strcmp(tok, "alarms"))
880             return web_client_api_request_v1_alarms(host, w, url);
881
882         else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log"))
883             return web_client_api_request_v1_alarm_log(host, w, url);
884
885         else if(hash == hash_alarm_variables && !strcmp(tok, "alarm_variables"))
886             return web_client_api_request_v1_alarm_variables(host, w, url);
887
888         else if(hash == hash_raw && !strcmp(tok, "allmetrics"))
889             return web_client_api_request_v1_allmetrics(host, w, url);
890
891         else {
892             buffer_flush(w->response.data);
893             buffer_strcat(w->response.data, "Unsupported v1 API command: ");
894             buffer_strcat_htmlescape(w->response.data, tok);
895             return 404;
896         }
897     }
898     else {
899         buffer_flush(w->response.data);
900         buffer_sprintf(w->response.data, "Which API v1 command?");
901         return 400;
902     }
903 }