]> arthur.barton.de Git - netdata.git/blob - src/web_client.c
423555513883c7249204d1f02a90ac320e102cca
[netdata.git] / src / web_client.c
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <netinet/in.h>
9 #include <arpa/inet.h>
10 #include <errno.h>
11 #include <pthread.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <netinet/tcp.h>
15 #include <malloc.h>
16 #include <pwd.h>
17 #include <grp.h>
18 #include <ctype.h>
19 #include <poll.h>
20
21 #include "common.h"
22 #include "log.h"
23 #include "appconfig.h"
24 #include "url.h"
25 #include "web_buffer.h"
26 #include "web_server.h"
27 #include "global_statistics.h"
28 #include "rrd.h"
29 #include "rrd2json.h"
30 #include "registry.h"
31
32 #include "web_client.h"
33
34 #define INITIAL_WEB_DATA_LENGTH 16384
35 #define WEB_REQUEST_LENGTH 16384
36 #define TOO_BIG_REQUEST 16384
37
38 int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
39 int web_enable_gzip = 1;
40
41 extern int netdata_exit;
42
43 struct web_client *web_clients = NULL;
44 unsigned long long web_clients_count = 0;
45
46 inline int web_client_crock_socket(struct web_client *w) {
47 #ifdef TCP_CORK
48         if(likely(!w->tcp_cork && w->ofd != -1)) {
49                 w->tcp_cork = 1;
50                 if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) {
51                         error("%llu: failed to enable TCP_CORK on socket.", w->id);
52                         w->tcp_cork = 0;
53                         return -1;
54                 }
55         }
56 #endif /* TCP_CORK */
57
58         return 0;
59 }
60
61 inline int web_client_uncrock_socket(struct web_client *w) {
62 #ifdef TCP_CORK
63         if(likely(w->tcp_cork && w->ofd != -1)) {
64                 w->tcp_cork = 0;
65                 if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) {
66                         error("%llu: failed to disable TCP_CORK on socket.", w->id);
67                         w->tcp_cork = 1;
68                         return -1;
69                 }
70         }
71 #endif /* TCP_CORK */
72
73         return 0;
74 }
75
76 struct web_client *web_client_create(int listener)
77 {
78         struct web_client *w;
79
80         w = calloc(1, sizeof(struct web_client));
81         if(!w) {
82                 error("Cannot allocate new web_client memory.");
83                 return NULL;
84         }
85
86         w->id = ++web_clients_count;
87         w->mode = WEB_CLIENT_MODE_NORMAL;
88
89         {
90                 struct sockaddr *sadr;
91                 socklen_t addrlen;
92
93                 sadr = (struct sockaddr*) &w->clientaddr;
94                 addrlen = sizeof(w->clientaddr);
95
96                 w->ifd = accept(listener, sadr, &addrlen);
97                 if (w->ifd == -1) {
98                         error("%llu: Cannot accept new incoming connection.", w->id);
99                         free(w);
100                         return NULL;
101                 }
102                 w->ofd = w->ifd;
103
104                 if(getnameinfo(sadr, addrlen, w->client_ip, NI_MAXHOST, w->client_port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
105                         error("Cannot getnameinfo() on received client connection.");
106                         strncpyz(w->client_ip,   "UNKNOWN", NI_MAXHOST);
107                         strncpyz(w->client_port, "UNKNOWN", NI_MAXSERV);
108                 }
109                 w->client_ip[NI_MAXHOST]   = '\0';
110                 w->client_port[NI_MAXSERV] = '\0';
111
112                 switch(sadr->sa_family) {
113                 case AF_INET:
114                         debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv4 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd);
115                         break;
116
117                 case AF_INET6:
118                         if(strncmp(w->client_ip, "::ffff:", 7) == 0) {
119                                 strcpy(w->client_ip, &w->client_ip[7]);
120                                 debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv4 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd);
121                         }
122                         else
123                                 debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv6 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd);
124                         break;
125
126                 default:
127                         debug(D_WEB_CLIENT_ACCESS, "%llu: New UNKNOWN web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd);
128                         break;
129                 }
130
131                 int flag = 1;
132                 if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0)
133                         error("%llu: failed to enable TCP_NODELAY on socket.", w->id);
134
135                 flag = 1;
136                 if(setsockopt(w->ifd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int)) != 0)
137                         error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id);
138
139
140         }
141
142         w->response.data = buffer_create(INITIAL_WEB_DATA_LENGTH);
143         if(unlikely(!w->response.data)) {
144                 // no need for error log - web_buffer_create already logged the error
145                 close(w->ifd);
146                 free(w);
147                 return NULL;
148         }
149
150         w->response.header = buffer_create(HTTP_RESPONSE_HEADER_SIZE);
151         if(unlikely(!w->response.header)) {
152                 // no need for error log - web_buffer_create already logged the error
153                 buffer_free(w->response.data);
154                 close(w->ifd);
155                 free(w);
156                 return NULL;
157         }
158
159         w->response.header_output = buffer_create(HTTP_RESPONSE_HEADER_SIZE);
160         if(unlikely(!w->response.header_output)) {
161                 // no need for error log - web_buffer_create already logged the error
162                 buffer_free(w->response.header);
163                 buffer_free(w->response.data);
164                 close(w->ifd);
165                 free(w);
166                 return NULL;
167         }
168
169         w->origin[0] = '*';
170         w->wait_receive = 1;
171
172         if(web_clients) web_clients->prev = w;
173         w->next = web_clients;
174         web_clients = w;
175
176         global_statistics.connected_clients++;
177
178         return(w);
179 }
180
181 void web_client_reset(struct web_client *w)
182 {
183         web_client_uncrock_socket(w);
184
185         struct timeval tv;
186         gettimeofday(&tv, NULL);
187
188         debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id);
189
190         if(w->stats_received_bytes || w->stats_sent_bytes) {
191                 if(web_server_mode == WEB_SERVER_MODE_MULTI_THREADED)
192                         global_statistics_lock();
193
194                 global_statistics.web_requests++;
195                 global_statistics.web_usec += usecdiff(&tv, &w->tv_in);
196                 global_statistics.bytes_received += w->stats_received_bytes;
197                 global_statistics.bytes_sent += w->stats_sent_bytes;
198
199                 if(web_server_mode == WEB_SERVER_MODE_MULTI_THREADED)
200                         global_statistics_unlock();
201         }
202         w->stats_received_bytes = 0;
203         w->stats_sent_bytes = 0;
204
205         size_t sent = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len;
206
207 #ifdef NETDATA_WITH_ZLIB
208         if(likely(w->response.zoutput)) sent = (size_t)w->response.zstream.total_out;
209 #endif
210
211         size_t size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len;
212
213         if(likely(w->last_url[0]))
214                 log_access("%llu: (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %s: %d '%s'",
215                         w->id,
216                         sent, size, -((size>0)?((float)(size-sent)/(float)size * 100.0):0.0),
217                         (float)usecdiff(&w->tv_ready, &w->tv_in) / 1000.0,
218                         (float)usecdiff(&tv, &w->tv_ready) / 1000.0,
219                         (float)usecdiff(&tv, &w->tv_in) / 1000.0,
220                         (w->mode == WEB_CLIENT_MODE_FILECOPY)?"filecopy":((w->mode == WEB_CLIENT_MODE_OPTIONS)?"options":"data"),
221                         w->response.code,
222                         w->last_url
223                 );
224
225         if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) {
226                 if(w->ifd != w->ofd) {
227                         debug(D_WEB_CLIENT, "%llu: Closing filecopy input file descriptor %d.", w->id, w->ifd);
228                         if(w->ifd != -1) close(w->ifd);
229                         w->ifd = w->ofd;
230                 }
231         }
232
233         w->last_url[0] = '\0';
234         w->cookie1[0] = '\0';
235         w->cookie2[0] = '\0';
236         w->origin[0] = '*';
237         w->origin[1] = '\0';
238
239         w->mode = WEB_CLIENT_MODE_NORMAL;
240
241         w->keepalive = 0;
242         w->decoded_url[0] = '\0';
243
244         buffer_reset(w->response.header_output);
245         buffer_reset(w->response.header);
246         buffer_reset(w->response.data);
247         w->response.rlen = 0;
248         w->response.sent = 0;
249         w->response.code = 0;
250
251         w->wait_receive = 1;
252         w->wait_send = 0;
253
254         w->response.zoutput = 0;
255
256         // if we had enabled compression, release it
257 #ifdef NETDATA_WITH_ZLIB
258         if(w->response.zinitialized) {
259                 debug(D_DEFLATE, "%llu: Reseting compression.", w->id);
260                 deflateEnd(&w->response.zstream);
261                 w->response.zsent = 0;
262                 w->response.zhave = 0;
263                 w->response.zstream.avail_in = 0;
264                 w->response.zstream.avail_out = 0;
265                 w->response.zstream.total_in = 0;
266                 w->response.zstream.total_out = 0;
267                 w->response.zinitialized = 0;
268         }
269 #endif // NETDATA_WITH_ZLIB
270 }
271
272 struct web_client *web_client_free(struct web_client *w) {
273         web_client_uncrock_socket(w);
274
275         struct web_client *n = w->next;
276         if(w == web_clients) web_clients = n;
277
278         debug(D_WEB_CLIENT_ACCESS, "%llu: Closing web client from %s port %s.", w->id, w->client_ip, w->client_port);
279
280 #ifdef NETDATA_WITH_ZLIB
281         if(w->response.zinitialized)
282                 deflateEnd(&w->response.zstream);
283 #endif // NETDATA_WITH_ZLIB
284
285         if(w->prev)     w->prev->next = w->next;
286         if(w->next) w->next->prev = w->prev;
287         if(w->response.header_output) buffer_free(w->response.header_output);
288         if(w->response.header) buffer_free(w->response.header);
289         if(w->response.data) buffer_free(w->response.data);
290         if(w->ifd != -1) close(w->ifd);
291         if(w->ofd != -1 && w->ofd != w->ifd) close(w->ofd);
292         free(w);
293
294         global_statistics.connected_clients--;
295
296         return(n);
297 }
298
299 uid_t web_files_uid(void) {
300         static char *web_owner = NULL;
301         static uid_t owner_uid = 0;
302
303         if(unlikely(!web_owner)) {
304                 web_owner = config_get("global", "web files owner", config_get("global", "run as user", ""));
305                 if(!web_owner || !*web_owner)
306                         owner_uid = geteuid();
307                 else {
308                         // getpwnam() is not thread safe,
309                         // but we have called this function once
310                         // while single threaded
311                         struct passwd *pw = getpwnam(web_owner);
312                         if(!pw) {
313                                 error("User '%s' is not present. Ignoring option.", web_owner);
314                                 owner_uid = geteuid();
315                         }
316                         else {
317                                 debug(D_WEB_CLIENT, "Web files owner set to %s.", web_owner);
318                                 owner_uid = pw->pw_uid;
319                         }
320                 }
321         }
322
323         return(owner_uid);
324 }
325
326 gid_t web_files_gid(void) {
327         static char *web_group = NULL;
328         static gid_t owner_gid = 0;
329
330         if(unlikely(!web_group)) {
331                 web_group = config_get("global", "web files group", config_get("global", "web files owner", ""));
332                 if(!web_group || !*web_group)
333                         owner_gid = getegid();
334                 else {
335                         // getgrnam() is not thread safe,
336                         // but we have called this function once
337                         // while single threaded
338                         struct group *gr = getgrnam(web_group);
339                         if(!gr) {
340                                 error("Group '%s' is not present. Ignoring option.", web_group);
341                                 owner_gid = getegid();
342                         }
343                         else {
344                                 debug(D_WEB_CLIENT, "Web files group set to %s.", web_group);
345                                 owner_gid = gr->gr_gid;
346                         }
347                 }
348         }
349
350         return(owner_gid);
351 }
352
353 int mysendfile(struct web_client *w, char *filename)
354 {
355         static char *web_dir = NULL;
356
357         // initialize our static data
358         if(unlikely(!web_dir)) web_dir = config_get("global", "web files directory", WEB_DIR);
359
360         debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, web_dir, filename);
361
362         // skip leading slashes
363         while (*filename == '/') filename++;
364
365         // if the filename contain known paths, skip them
366         if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) filename = &filename[strlen(WEB_PATH_FILE) + 1];
367
368         char *s;
369         for(s = filename; *s ;s++) {
370                 if( !isalnum(*s) && *s != '/' && *s != '.' && *s != '-' && *s != '_') {
371                         debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
372                         buffer_sprintf(w->response.data, "File '%s' cannot be served. Filename contains invalid character '%c'", filename, *s);
373                         return 400;
374                 }
375         }
376
377         // if the filename contains a .. refuse to serve it
378         if(strstr(filename, "..") != 0) {
379                 debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
380                 buffer_sprintf(w->response.data, "File '%s' cannot be served. Relative filenames with '..' in them are not supported.", filename);
381                 return 400;
382         }
383
384         // access the file
385         char webfilename[FILENAME_MAX + 1];
386         snprintfz(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename);
387
388         // check if the file exists
389         struct stat stat;
390         if(lstat(webfilename, &stat) != 0) {
391                 debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, webfilename);
392                 buffer_sprintf(w->response.data, "File '%s' does not exist, or is not accessible.", webfilename);
393                 return 404;
394         }
395
396         // check if the file is owned by expected user
397         if(stat.st_uid != web_files_uid()) {
398                 error("%llu: File '%s' is owned by user %d (expected user %d). Access Denied.", w->id, webfilename, stat.st_uid, web_files_uid());
399                 buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename);
400                 return 403;
401         }
402
403         // check if the file is owned by expected group
404         if(stat.st_gid != web_files_gid()) {
405                 error("%llu: File '%s' is owned by group %d (expected group %d). Access Denied.", w->id, webfilename, stat.st_gid, web_files_gid());
406                 buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename);
407                 return 403;
408         }
409
410         if((stat.st_mode & S_IFMT) == S_IFDIR) {
411                 snprintfz(webfilename, FILENAME_MAX, "%s/index.html", filename);
412                 return mysendfile(w, webfilename);
413         }
414
415         if((stat.st_mode & S_IFMT) != S_IFREG) {
416                 error("%llu: File '%s' is not a regular file. Access Denied.", w->id, webfilename);
417                 buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename);
418                 return 403;
419         }
420
421         // open the file
422         w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY);
423         if(w->ifd == -1) {
424                 w->ifd = w->ofd;
425
426                 if(errno == EBUSY || errno == EAGAIN) {
427                         error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename);
428                         buffer_sprintf(w->response.header, "Location: /" WEB_PATH_FILE "/%s\r\n", filename);
429                         buffer_sprintf(w->response.data, "The file '%s' is currently busy. Please try again later.", webfilename);
430                         return 307;
431                 }
432                 else {
433                         error("%llu: Cannot open file '%s'.", w->id, webfilename);
434                         buffer_sprintf(w->response.data, "Cannot open file '%s'.", webfilename);
435                         return 404;
436                 }
437         }
438
439         // pick a Content-Type for the file
440                  if(strstr(filename, ".html") != NULL)  w->response.data->contenttype = CT_TEXT_HTML;
441         else if(strstr(filename, ".js")   != NULL)      w->response.data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
442         else if(strstr(filename, ".css")  != NULL)      w->response.data->contenttype = CT_TEXT_CSS;
443         else if(strstr(filename, ".xml")  != NULL)      w->response.data->contenttype = CT_TEXT_XML;
444         else if(strstr(filename, ".xsl")  != NULL)      w->response.data->contenttype = CT_TEXT_XSL;
445         else if(strstr(filename, ".txt")  != NULL)  w->response.data->contenttype = CT_TEXT_PLAIN;
446         else if(strstr(filename, ".svg")  != NULL)  w->response.data->contenttype = CT_IMAGE_SVG_XML;
447         else if(strstr(filename, ".ttf")  != NULL)  w->response.data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE;
448         else if(strstr(filename, ".otf")  != NULL)  w->response.data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE;
449         else if(strstr(filename, ".woff2")!= NULL)  w->response.data->contenttype = CT_APPLICATION_FONT_WOFF2;
450         else if(strstr(filename, ".woff") != NULL)  w->response.data->contenttype = CT_APPLICATION_FONT_WOFF;
451         else if(strstr(filename, ".eot")  != NULL)  w->response.data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ;
452         else if(strstr(filename, ".png")  != NULL)  w->response.data->contenttype = CT_IMAGE_PNG;
453         else if(strstr(filename, ".jpg")  != NULL)  w->response.data->contenttype = CT_IMAGE_JPG;
454         else if(strstr(filename, ".jpeg") != NULL)  w->response.data->contenttype = CT_IMAGE_JPG;
455         else if(strstr(filename, ".gif")  != NULL)  w->response.data->contenttype = CT_IMAGE_GIF;
456         else if(strstr(filename, ".bmp")  != NULL)  w->response.data->contenttype = CT_IMAGE_BMP;
457         else if(strstr(filename, ".ico")  != NULL)  w->response.data->contenttype = CT_IMAGE_XICON;
458         else if(strstr(filename, ".icns") != NULL)  w->response.data->contenttype = CT_IMAGE_ICNS;
459         else w->response.data->contenttype = CT_APPLICATION_OCTET_STREAM;
460
461         debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, webfilename, stat.st_size, w->ifd, w->ofd);
462
463         w->mode = WEB_CLIENT_MODE_FILECOPY;
464         w->wait_receive = 1;
465         w->wait_send = 0;
466         buffer_flush(w->response.data);
467         w->response.rlen = stat.st_size;
468         w->response.data->date = stat.st_mtim.tv_sec;
469
470         return 200;
471 }
472
473
474 #ifdef NETDATA_WITH_ZLIB
475 void web_client_enable_deflate(struct web_client *w, int gzip) {
476         if(unlikely(w->response.zinitialized)) {
477                 error("%llu: Compression has already be initialized for this client.", w->id);
478                 return;
479         }
480
481         if(unlikely(w->response.sent)) {
482                 error("%llu: Cannot enable compression in the middle of a conversation.", w->id);
483                 return;
484         }
485
486         w->response.zstream.zalloc = Z_NULL;
487         w->response.zstream.zfree = Z_NULL;
488         w->response.zstream.opaque = Z_NULL;
489
490         w->response.zstream.next_in = (Bytef *)w->response.data->buffer;
491         w->response.zstream.avail_in = 0;
492         w->response.zstream.total_in = 0;
493
494         w->response.zstream.next_out = w->response.zbuffer;
495         w->response.zstream.avail_out = 0;
496         w->response.zstream.total_out = 0;
497
498         w->response.zstream.zalloc = Z_NULL;
499         w->response.zstream.zfree = Z_NULL;
500         w->response.zstream.opaque = Z_NULL;
501
502 //      if(deflateInit(&w->response.zstream, Z_DEFAULT_COMPRESSION) != Z_OK) {
503 //              error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
504 //              return;
505 //      }
506
507         // Select GZIP compression: windowbits = 15 + 16 = 31
508         if(deflateInit2(&w->response.zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + ((gzip)?16:0), 8, Z_DEFAULT_STRATEGY) != Z_OK) {
509                 error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
510                 return;
511         }
512
513         w->response.zsent = 0;
514         w->response.zoutput = 1;
515         w->response.zinitialized = 1;
516
517         debug(D_DEFLATE, "%llu: Initialized compression.", w->id);
518 }
519 #endif // NETDATA_WITH_ZLIB
520
521 uint32_t web_client_api_request_v1_data_options(char *o)
522 {
523         uint32_t ret = 0x00000000;
524         char *tok;
525
526         while(o && *o && (tok = mystrsep(&o, ", |"))) {
527                 if(!*tok) continue;
528
529                 if(!strcmp(tok, "nonzero"))
530                         ret |= RRDR_OPTION_NONZERO;
531                 else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse"))
532                         ret |= RRDR_OPTION_REVERSED;
533                 else if(!strcmp(tok, "jsonwrap"))
534                         ret |= RRDR_OPTION_JSON_WRAP;
535                 else if(!strcmp(tok, "min2max"))
536                         ret |= RRDR_OPTION_MIN2MAX;
537                 else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds"))
538                         ret |= RRDR_OPTION_MILLISECONDS;
539                 else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum"))
540                         ret |= RRDR_OPTION_ABSOLUTE;
541                 else if(!strcmp(tok, "seconds"))
542                         ret |= RRDR_OPTION_SECONDS;
543                 else if(!strcmp(tok, "null2zero"))
544                         ret |= RRDR_OPTION_NULL2ZERO;
545                 else if(!strcmp(tok, "objectrows"))
546                         ret |= RRDR_OPTION_OBJECTSROWS;
547                 else if(!strcmp(tok, "google_json"))
548                         ret |= RRDR_OPTION_GOOGLE_JSON;
549                 else if(!strcmp(tok, "percentage"))
550                         ret |= RRDR_OPTION_PERCENTAGE;
551         }
552
553         return ret;
554 }
555
556 uint32_t web_client_api_request_v1_data_format(char *name)
557 {
558         if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable
559                 return DATASOURCE_DATATABLE_JSON;
560
561         else if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource
562                 return DATASOURCE_DATATABLE_JSONP;
563
564         else if(!strcmp(name, DATASOURCE_FORMAT_JSON)) // json
565                 return DATASOURCE_JSON;
566
567         else if(!strcmp(name, DATASOURCE_FORMAT_JSONP)) // jsonp
568                 return DATASOURCE_JSONP;
569
570         else if(!strcmp(name, DATASOURCE_FORMAT_SSV)) // ssv
571                 return DATASOURCE_SSV;
572
573         else if(!strcmp(name, DATASOURCE_FORMAT_CSV)) // csv
574                 return DATASOURCE_CSV;
575
576         else if(!strcmp(name, DATASOURCE_FORMAT_TSV) || !strcmp(name, "tsv-excel")) // tsv
577                 return DATASOURCE_TSV;
578
579         else if(!strcmp(name, DATASOURCE_FORMAT_HTML)) // html
580                 return DATASOURCE_HTML;
581
582         else if(!strcmp(name, DATASOURCE_FORMAT_JS_ARRAY)) // array
583                 return DATASOURCE_JS_ARRAY;
584
585         else if(!strcmp(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma
586                 return DATASOURCE_SSV_COMMA;
587
588         else if(!strcmp(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray
589                 return DATASOURCE_CSV_JSON_ARRAY;
590
591         return DATASOURCE_JSON;
592 }
593
594 uint32_t web_client_api_request_v1_data_google_format(char *name)
595 {
596         if(!strcmp(name, "json"))
597                 return DATASOURCE_DATATABLE_JSONP;
598
599         else if(!strcmp(name, "html"))
600                 return DATASOURCE_HTML;
601
602         else if(!strcmp(name, "csv"))
603                 return DATASOURCE_CSV;
604
605         else if(!strcmp(name, "tsv-excel"))
606                 return DATASOURCE_TSV;
607
608         return DATASOURCE_JSON;
609 }
610
611 int web_client_api_request_v1_data_group(char *name)
612 {
613         if(!strcmp(name, "max"))
614                 return GROUP_MAX;
615
616         else if(!strcmp(name, "average"))
617                 return GROUP_AVERAGE;
618
619         return GROUP_MAX;
620 }
621
622 int web_client_api_request_v1_charts(struct web_client *w, char *url)
623 {
624         if(url) { ; }
625
626         buffer_flush(w->response.data);
627         w->response.data->contenttype = CT_APPLICATION_JSON;
628         rrd_stats_api_v1_charts(w->response.data);
629         return 200;
630 }
631
632 int web_client_api_request_v1_chart(struct web_client *w, char *url)
633 {
634         int ret = 400;
635         char *chart = NULL;
636
637         buffer_flush(w->response.data);
638
639         while(url) {
640                 char *value = mystrsep(&url, "?&[]");
641                 if(!value || !*value) continue;
642
643                 char *name = mystrsep(&value, "=");
644                 if(!name || !*name) continue;
645                 if(!value || !*value) continue;
646
647                 // name and value are now the parameters
648                 // they are not null and not empty
649
650                 if(!strcmp(name, "chart")) chart = value;
651                 //else {
652                 ///     buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
653                 //      goto cleanup;
654                 //}
655         }
656
657         if(!chart || !*chart) {
658                 buffer_sprintf(w->response.data, "No chart id is given at the request.");
659                 goto cleanup;
660         }
661
662         RRDSET *st = rrdset_find(chart);
663         if(!st) st = rrdset_find_byname(chart);
664         if(!st) {
665                 buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart);
666                 ret = 404;
667                 goto cleanup;
668         }
669
670         w->response.data->contenttype = CT_APPLICATION_JSON;
671         rrd_stats_api_v1_chart(st, w->response.data);
672         return 200;
673
674 cleanup:
675         return ret;
676 }
677
678 // returns the HTTP code
679 int web_client_api_request_v1_data(struct web_client *w, char *url)
680 {
681         debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
682
683         int ret = 400;
684         BUFFER *dimensions = NULL;
685
686         buffer_flush(w->response.data);
687
688         char    *google_version = "0.6",
689                         *google_reqId = "0",
690                         *google_sig = "0",
691                         *google_out = "json",
692                         *responseHandler = NULL,
693                         *outFileName = NULL;
694
695         time_t last_timestamp_in_data = 0, google_timestamp = 0;
696
697         char *chart = NULL
698                         , *before_str = NULL
699                         , *after_str = NULL
700                         , *points_str = NULL;
701
702         int group = GROUP_MAX;
703         uint32_t format = DATASOURCE_JSON;
704         uint32_t options = 0x00000000;
705
706         while(url) {
707                 char *value = mystrsep(&url, "?&[]");
708                 if(!value || !*value) continue;
709
710                 char *name = mystrsep(&value, "=");
711                 if(!name || !*name) continue;
712                 if(!value || !*value) continue;
713
714                 debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
715
716                 // name and value are now the parameters
717                 // they are not null and not empty
718
719                 if(!strcmp(name, "chart")) chart = value;
720                 else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
721                         if(!dimensions) dimensions = buffer_create(strlen(value));
722                         if(dimensions) {
723                                 buffer_strcat(dimensions, "|");
724                                 buffer_strcat(dimensions, value);
725                         }
726                 }
727                 else if(!strcmp(name, "after")) after_str = value;
728                 else if(!strcmp(name, "before")) before_str = value;
729                 else if(!strcmp(name, "points")) points_str = value;
730                 else if(!strcmp(name, "group")) {
731                         group = web_client_api_request_v1_data_group(value);
732                 }
733                 else if(!strcmp(name, "format")) {
734                         format = web_client_api_request_v1_data_format(value);
735                 }
736                 else if(!strcmp(name, "options")) {
737                         options |= web_client_api_request_v1_data_options(value);
738                 }
739                 else if(!strcmp(name, "callback")) {
740                         responseHandler = value;
741                 }
742                 else if(!strcmp(name, "filename")) {
743                         outFileName = value;
744                 }
745                 else if(!strcmp(name, "tqx")) {
746                         // parse Google Visualization API options
747                         // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
748                         char *tqx_name, *tqx_value;
749
750                         while(value) {
751                                 tqx_value = mystrsep(&value, ";");
752                                 if(!tqx_value || !*tqx_value) continue;
753
754                                 tqx_name = mystrsep(&tqx_value, ":");
755                                 if(!tqx_name || !*tqx_name) continue;
756                                 if(!tqx_value || !*tqx_value) continue;
757
758                                 if(!strcmp(tqx_name, "version"))
759                                         google_version = tqx_value;
760                                 else if(!strcmp(tqx_name, "reqId"))
761                                         google_reqId = tqx_value;
762                                 else if(!strcmp(tqx_name, "sig")) {
763                                         google_sig = tqx_value;
764                                         google_timestamp = strtoul(google_sig, NULL, 0);
765                                 }
766                                 else if(!strcmp(tqx_name, "out")) {
767                                         google_out = tqx_value;
768                                         format = web_client_api_request_v1_data_google_format(google_out);
769                                 }
770                                 else if(!strcmp(tqx_name, "responseHandler"))
771                                         responseHandler = tqx_value;
772                                 else if(!strcmp(tqx_name, "outFileName"))
773                                         outFileName = tqx_value;
774                         }
775                 }
776         }
777
778         if(!chart || !*chart) {
779                 buffer_sprintf(w->response.data, "No chart id is given at the request.");
780                 goto cleanup;
781         }
782
783         RRDSET *st = rrdset_find(chart);
784         if(!st) st = rrdset_find_byname(chart);
785         if(!st) {
786                 buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart);
787                 ret = 404;
788                 goto cleanup;
789         }
790
791         long long before = (before_str && *before_str)?atol(before_str):0;
792         long long after  = (after_str  && *after_str) ?atol(after_str):0;
793         int       points = (points_str && *points_str)?atoi(points_str):0;
794
795         debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%u', format '%u', options '0x%08x'"
796                         , w->id
797                         , chart
798                         , (dimensions)?buffer_tostring(dimensions):""
799                         , after
800                         , before
801                         , points
802                         , group
803                         , format
804                         , options
805                         );
806
807         if(outFileName && *outFileName) {
808                 buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
809                 debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
810         }
811
812         if(format == DATASOURCE_DATATABLE_JSONP) {
813                 if(responseHandler == NULL)
814                         responseHandler = "google.visualization.Query.setResponse";
815
816                 debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
817                                 w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
818                         );
819
820                 buffer_sprintf(w->response.data,
821                         "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:",
822                         responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
823         }
824         else if(format == DATASOURCE_JSONP) {
825                 if(responseHandler == NULL)
826                         responseHandler = "callback";
827
828                 buffer_strcat(w->response.data, responseHandler);
829                 buffer_strcat(w->response.data, "(");
830         }
831
832         ret = rrd2format(st, w->response.data, dimensions, format, points, after, before, group, options, &last_timestamp_in_data);
833
834         if(format == DATASOURCE_DATATABLE_JSONP) {
835                 if(google_timestamp < last_timestamp_in_data)
836                         buffer_strcat(w->response.data, "});");
837
838                 else {
839                         // the client already has the latest data
840                         buffer_flush(w->response.data);
841                         buffer_sprintf(w->response.data,
842                                 "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
843                                 responseHandler, google_version, google_reqId);
844                 }
845         }
846         else if(format == DATASOURCE_JSONP)
847                 buffer_strcat(w->response.data, ");");
848
849 cleanup:
850         if(dimensions) buffer_free(dimensions);
851         return ret;
852 }
853
854 int web_client_api_request_v1_registry(struct web_client *w, char *url)
855 {
856         static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
857                         hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
858                         hash_to = 0;
859
860         if(unlikely(!hash_action)) {
861                 hash_action = simple_hash("action");
862                 hash_access = simple_hash("access");
863                 hash_hello = simple_hash("hello");
864                 hash_delete = simple_hash("delete");
865                 hash_search = simple_hash("search");
866                 hash_switch = simple_hash("switch");
867                 hash_machine = simple_hash("machine");
868                 hash_url = simple_hash("url");
869                 hash_name = simple_hash("name");
870                 hash_delete_url = simple_hash("delete_url");
871                 hash_for = simple_hash("for");
872                 hash_to = simple_hash("to");
873         }
874
875         char person_guid[36 + 1] = "";
876
877         debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
878
879         // FIXME
880         // The browser may send multiple cookies with our id
881         
882         char *cookie = strstr(w->response.data->buffer, " " NETDATA_REGISTRY_COOKIE_NAME "=");
883         if(cookie)
884                 strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME) + 1], 36);
885
886         char action = '\0';
887         char *machine_guid = NULL,
888                         *machine_url = NULL,
889                         *url_name = NULL,
890                         *search_machine_guid = NULL,
891                         *delete_url = NULL,
892                         *to_person_guid = NULL;
893
894         while(url) {
895                 char *value = mystrsep(&url, "?&[]");
896                 if (!value || !*value) continue;
897
898                 char *name = mystrsep(&value, "=");
899                 if (!name || !*name) continue;
900                 if (!value || !*value) continue;
901
902                 debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
903
904                 uint32_t hash = simple_hash(name);
905
906                 if(hash == hash_action && !strcmp(name, "action")) {
907                         uint32_t vhash = simple_hash(value);
908
909                         if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
910                         else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
911                         else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
912                         else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
913                         else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
914                 }
915                 else if(hash == hash_machine && !strcmp(name, "machine"))
916                         machine_guid = value;
917
918                 else if(hash == hash_url && !strcmp(name, "url"))
919                         machine_url = value;
920
921                 else if(action == 'A') {
922                         if(hash == hash_name && !strcmp(name, "name"))
923                                 url_name = value;
924                 }
925                 else if(action == 'D') {
926                         if(hash == hash_delete_url && !strcmp(name, "delete_url"))
927                                 delete_url = value;
928                 }
929                 else if(action == 'S') {
930                         if(hash == hash_for && !strcmp(name, "for"))
931                                 search_machine_guid = value;
932                 }
933                 else if(action == 'W') {
934                         if(hash == hash_to && !strcmp(name, "to"))
935                                 to_person_guid = value;
936                 }
937         }
938
939         if(action == 'A' && (!machine_guid || !machine_url || !url_name)) {
940                 buffer_flush(w->response.data);
941                 buffer_sprintf(w->response.data, "Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')",
942                                            machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET");
943                 return 400;
944         }
945         else if(action == 'D' && (!machine_guid || !machine_url || !delete_url)) {
946                 buffer_flush(w->response.data);
947                 buffer_sprintf(w->response.data, "Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')",
948                                            machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
949                 return 400;
950         }
951         else if(action == 'S' && (!machine_guid || !machine_url || !search_machine_guid)) {
952                 buffer_flush(w->response.data);
953                 buffer_sprintf(w->response.data, "Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')",
954                                            machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
955                 return 400;
956         }
957         else if(action == 'W' && (!machine_guid || !machine_url || !to_person_guid)) {
958                 buffer_flush(w->response.data);
959                 buffer_sprintf(w->response.data, "Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')",
960                                            machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
961                 return 400;
962         }
963
964         switch(action) {
965                 case 'A':
966                         return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL));
967
968                 case 'D':
969                         return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, time(NULL));
970
971                 case 'S':
972                         return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, time(NULL));
973
974                 case 'W':
975                         return registry_request_switch_json(w, person_guid, machine_guid, machine_url, to_person_guid, time(NULL));
976
977                 case 'H':
978                         return registry_request_hello_json(w);
979
980                 default:
981                         buffer_flush(w->response.data);
982                         buffer_sprintf(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
983                         return 400;
984         }
985
986         buffer_flush(w->response.data);
987         buffer_sprintf(w->response.data, "Invalid or no registry action.");
988         return 400;
989 }
990
991 int web_client_api_request_v1(struct web_client *w, char *url)
992 {
993         static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0;
994
995         if(unlikely(hash_data == 0)) {
996                 hash_data = simple_hash("data");
997                 hash_chart = simple_hash("chart");
998                 hash_charts = simple_hash("charts");
999                 hash_registry = simple_hash("registry");
1000         }
1001
1002         // get the command
1003         char *tok = mystrsep(&url, "/?&");
1004         if(tok && *tok) {
1005                 debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
1006                 uint32_t hash = simple_hash(tok);
1007
1008                 if(hash == hash_data && !strcmp(tok, "data"))
1009                         return web_client_api_request_v1_data(w, url);
1010
1011                 else if(hash == hash_chart && !strcmp(tok, "chart"))
1012                         return web_client_api_request_v1_chart(w, url);
1013
1014                 else if(hash == hash_charts && !strcmp(tok, "charts"))
1015                         return web_client_api_request_v1_charts(w, url);
1016
1017                 else if(hash == hash_registry && !strcmp(tok, "registry"))
1018                         return web_client_api_request_v1_registry(w, url);
1019
1020                 else {
1021                         buffer_flush(w->response.data);
1022                         buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok);
1023                         return 404;
1024                 }
1025         }
1026         else {
1027                 buffer_flush(w->response.data);
1028                 buffer_sprintf(w->response.data, "API v1 command?");
1029                 return 400;
1030         }
1031 }
1032
1033 int web_client_api_request(struct web_client *w, char *url)
1034 {
1035         // get the api version
1036         char *tok = mystrsep(&url, "/?&");
1037         if(tok && *tok) {
1038                 debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok);
1039                 if(strcmp(tok, "v1") == 0)
1040                         return web_client_api_request_v1(w, url);
1041                 else {
1042                         buffer_flush(w->response.data);
1043                         buffer_sprintf(w->response.data, "Unsupported API version: %s", tok);
1044                         return 404;
1045                 }
1046         }
1047         else {
1048                 buffer_flush(w->response.data);
1049                 buffer_sprintf(w->response.data, "Which API version?");
1050                 return 400;
1051         }
1052 }
1053
1054 int web_client_api_old_data_request(struct web_client *w, char *url, int datasource_type)
1055 {
1056         RRDSET *st = NULL;
1057
1058         char *args = strchr(url, '?');
1059         if(args) {
1060                 *args='\0';
1061                 args = &args[1];
1062         }
1063
1064         // get the name of the data to show
1065         char *tok = mystrsep(&url, "/");
1066
1067         // do we have such a data set?
1068         if(tok && *tok) {
1069                 debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1070                 st = rrdset_find_byname(tok);
1071                 if(!st) st = rrdset_find(tok);
1072         }
1073
1074         if(!st) {
1075                 // we don't have it
1076                 // try to send a file with that name
1077                 buffer_flush(w->response.data);
1078                 return(mysendfile(w, tok));
1079         }
1080
1081         // we have it
1082         debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok);
1083
1084         // how many entries does the client want?
1085         long lines = rrd_default_history_entries;
1086         long group_count = 1;
1087         time_t after = 0, before = 0;
1088         int group_method = GROUP_AVERAGE;
1089         int nonzero = 0;
1090
1091         if(url) {
1092                 // parse the lines required
1093                 tok = mystrsep(&url, "/");
1094                 if(tok) lines = atoi(tok);
1095                 if(lines < 1) lines = 1;
1096         }
1097         if(url) {
1098                 // parse the group count required
1099                 tok = mystrsep(&url, "/");
1100                 if(tok && *tok) group_count = atoi(tok);
1101                 if(group_count < 1) group_count = 1;
1102                 //if(group_count > save_history / 20) group_count = save_history / 20;
1103         }
1104         if(url) {
1105                 // parse the grouping method required
1106                 tok = mystrsep(&url, "/");
1107                 if(tok && *tok) {
1108                         if(strcmp(tok, "max") == 0) group_method = GROUP_MAX;
1109                         else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE;
1110                         else if(strcmp(tok, "sum") == 0) group_method = GROUP_SUM;
1111                         else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok);
1112                 }
1113         }
1114         if(url) {
1115                 // parse after time
1116                 tok = mystrsep(&url, "/");
1117                 if(tok && *tok) after = strtoul(tok, NULL, 10);
1118                 if(after < 0) after = 0;
1119         }
1120         if(url) {
1121                 // parse before time
1122                 tok = mystrsep(&url, "/");
1123                 if(tok && *tok) before = strtoul(tok, NULL, 10);
1124                 if(before < 0) before = 0;
1125         }
1126         if(url) {
1127                 // parse nonzero
1128                 tok = mystrsep(&url, "/");
1129                 if(tok && *tok && strcmp(tok, "nonzero") == 0) nonzero = 1;
1130         }
1131
1132         w->response.data->contenttype = CT_APPLICATION_JSON;
1133         buffer_flush(w->response.data);
1134
1135         char *google_version = "0.6";
1136         char *google_reqId = "0";
1137         char *google_sig = "0";
1138         char *google_out = "json";
1139         char *google_responseHandler = "google.visualization.Query.setResponse";
1140         char *google_outFileName = NULL;
1141         time_t last_timestamp_in_data = 0;
1142         if(datasource_type == DATASOURCE_DATATABLE_JSON || datasource_type == DATASOURCE_DATATABLE_JSONP) {
1143
1144                 w->response.data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
1145
1146                 while(args) {
1147                         tok = mystrsep(&args, "&");
1148                         if(tok && *tok) {
1149                                 char *name = mystrsep(&tok, "=");
1150                                 if(name && *name && strcmp(name, "tqx") == 0) {
1151                                         char *key = mystrsep(&tok, ":");
1152                                         char *value = mystrsep(&tok, ";");
1153                                         if(key && value && *key && *value) {
1154                                                 if(strcmp(key, "version") == 0)
1155                                                         google_version = value;
1156
1157                                                 else if(strcmp(key, "reqId") == 0)
1158                                                         google_reqId = value;
1159
1160                                                 else if(strcmp(key, "sig") == 0)
1161                                                         google_sig = value;
1162
1163                                                 else if(strcmp(key, "out") == 0)
1164                                                         google_out = value;
1165
1166                                                 else if(strcmp(key, "responseHandler") == 0)
1167                                                         google_responseHandler = value;
1168
1169                                                 else if(strcmp(key, "outFileName") == 0)
1170                                                         google_outFileName = value;
1171                                         }
1172                                 }
1173                         }
1174                 }
1175
1176                 debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
1177                         w->id, google_version, google_reqId, google_sig, google_out, google_responseHandler, google_outFileName
1178                         );
1179
1180                 if(datasource_type == DATASOURCE_DATATABLE_JSONP) {
1181                         last_timestamp_in_data = strtoul(google_sig, NULL, 0);
1182
1183                         // check the client wants json
1184                         if(strcmp(google_out, "json") != 0) {
1185                                 buffer_sprintf(w->response.data,
1186                                         "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'invalid_query',message:'output format is not supported',detailed_message:'the format %s requested is not supported by netdata.'}]});",
1187                                         google_responseHandler, google_version, google_reqId, google_out);
1188                                         return 200;
1189                         }
1190                 }
1191         }
1192
1193         if(datasource_type == DATASOURCE_DATATABLE_JSONP) {
1194                 buffer_sprintf(w->response.data,
1195                         "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:",
1196                         google_responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
1197         }
1198
1199         debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group, %d group_method, %lu after, %lu before).", w->id, st->name, st->id, lines, group_count, group_method, after, before);
1200         time_t timestamp_in_data = rrd_stats_json(datasource_type, st, w->response.data, lines, group_count, group_method, after, before, nonzero);
1201
1202         if(datasource_type == DATASOURCE_DATATABLE_JSONP) {
1203                 if(timestamp_in_data > last_timestamp_in_data)
1204                         buffer_strcat(w->response.data, "});");
1205
1206                 else {
1207                         // the client already has the latest data
1208                         buffer_flush(w->response.data);
1209                         buffer_sprintf(w->response.data,
1210                                 "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
1211                                 google_responseHandler, google_version, google_reqId);
1212                 }
1213         }
1214
1215         return 200;
1216 }
1217
1218 const char *web_content_type_to_string(uint8_t contenttype) {
1219         switch(contenttype) {
1220                 case CT_TEXT_HTML:
1221                         return "text/html; charset=utf-8";
1222
1223                 case CT_APPLICATION_XML:
1224                         return "application/xml; charset=utf-8";
1225
1226                 case CT_APPLICATION_JSON:
1227                         return "application/json; charset=utf-8";
1228
1229                 case CT_APPLICATION_X_JAVASCRIPT:
1230                         return "application/x-javascript; charset=utf-8";
1231
1232                 case CT_TEXT_CSS:
1233                         return "text/css; charset=utf-8";
1234
1235                 case CT_TEXT_XML:
1236                         return "text/xml; charset=utf-8";
1237
1238                 case CT_TEXT_XSL:
1239                         return "text/xsl; charset=utf-8";
1240
1241                 case CT_APPLICATION_OCTET_STREAM:
1242                         return "application/octet-stream";
1243
1244                 case CT_IMAGE_SVG_XML:
1245                         return "image/svg+xml";
1246
1247                 case CT_APPLICATION_X_FONT_TRUETYPE:
1248                         return "application/x-font-truetype";
1249
1250                 case CT_APPLICATION_X_FONT_OPENTYPE:
1251                         return "application/x-font-opentype";
1252
1253                 case CT_APPLICATION_FONT_WOFF:
1254                         return "application/font-woff";
1255
1256                 case CT_APPLICATION_FONT_WOFF2:
1257                         return "application/font-woff2";
1258
1259                 case CT_APPLICATION_VND_MS_FONTOBJ:
1260                         return "application/vnd.ms-fontobject";
1261
1262                 case CT_IMAGE_PNG:
1263                         return "image/png";
1264
1265                 case CT_IMAGE_JPG:
1266                         return "image/jpeg";
1267
1268                 case CT_IMAGE_GIF:
1269                         return "image/gif";
1270
1271                 case CT_IMAGE_XICON:
1272                         return "image/x-icon";
1273
1274                 case CT_IMAGE_BMP:
1275                         return "image/bmp";
1276
1277                 case CT_IMAGE_ICNS:
1278                         return "image/icns";
1279
1280                 default:
1281                 case CT_TEXT_PLAIN:
1282                         return "text/plain; charset=utf-8";
1283         }
1284 }
1285
1286
1287 const char *web_response_code_to_string(int code) {
1288         switch(code) {
1289                 case 200:
1290                         return "OK";
1291
1292                 case 307:
1293                         return "Temporary Redirect";
1294
1295                 case 400:
1296                         return "Bad Request";
1297
1298                 case 403:
1299                         return "Forbidden";
1300
1301                 case 404:
1302                         return "Not Found";
1303
1304                 case 412:
1305                         return "Preconditions Failed";
1306
1307                 default:
1308                         if(code >= 100 && code < 200)
1309                                 return "Informational";
1310
1311                         if(code >= 200 && code < 300)
1312                                 return "Successful";
1313
1314                         if(code >= 300 && code < 400)
1315                                 return "Redirection";
1316
1317                         if(code >= 400 && code < 500)
1318                                 return "Bad Request";
1319
1320                         if(code >= 500 && code < 600)
1321                                 return "Server Error";
1322
1323                         return "Undefined Error";
1324         }
1325 }
1326
1327 static inline char *http_header_parse(struct web_client *w, char *s) {
1328         static uint32_t hash_origin = 0, hash_connection = 0, hash_accept_encoding = 0;
1329
1330         if(unlikely(!hash_origin)) {
1331                 hash_origin = simple_uhash("Origin");
1332                 hash_connection = simple_uhash("Connection");
1333                 hash_accept_encoding = simple_uhash("Accept-Encoding");
1334         }
1335
1336         char *e = s;
1337
1338         // find the :
1339         while(*e && *e != ':') e++;
1340         if(!*e || e[1] != ' ') return e;
1341
1342         // get the name
1343         *e = '\0';
1344
1345         // find the value
1346         char *v, *ve;
1347         v = ve = e + 2;
1348
1349         // find the \r
1350         while(*ve && *ve != '\r') ve++;
1351         if(!*ve || ve[1] != '\n') {
1352                 *e = ':';
1353                 return ve;
1354         }
1355
1356         // terminate the value
1357         *ve = '\0';
1358
1359         // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v);
1360         uint32_t hash = simple_uhash(s);
1361
1362         if(hash == hash_origin && !strcasecmp(s, "Origin"))
1363                 strncpyz(w->origin, v, ORIGIN_MAX);
1364
1365         else if(hash == hash_connection && !strcasecmp(s, "Connection")) {
1366                 if(strcasestr(v, "keep-alive"))
1367                         w->keepalive = 1;
1368         }
1369 #ifdef NETDATA_WITH_ZLIB
1370         else if(hash == hash_accept_encoding && !strcasecmp(s, "Accept-Encoding")) {
1371                 if(web_enable_gzip) {
1372                         if(strcasestr(v, "gzip"))
1373                                 web_client_enable_deflate(w, 1);
1374                         //
1375                         // does not seem to work
1376                         // else if(strcasestr(v, "deflate"))
1377                         //      web_client_enable_deflate(w, 0);
1378                 }
1379         }
1380 #endif /* NETDATA_WITH_ZLIB */
1381
1382         *e = ':';
1383         *ve = '\r';
1384         return ve;
1385 }
1386
1387 // http_request_validate()
1388 // returns:
1389 // = 0 : all good, process the request
1390 // > 0 : request is not supported
1391 // < 0 : request is incomplete - wait for more data
1392
1393 static inline int http_request_validate(struct web_client *w) {
1394         char *s = w->response.data->buffer, *encoded_url = NULL;
1395
1396         // is is a valid request?
1397         if(!strncmp(s, "GET ", 4)) {
1398                 encoded_url = s = &s[4];
1399                 w->mode = WEB_CLIENT_MODE_NORMAL;
1400         }
1401         else if(!strncmp(s, "OPTIONS ", 8)) {
1402                 encoded_url = s = &s[8];
1403                 w->mode = WEB_CLIENT_MODE_OPTIONS;
1404         }
1405         else {
1406                 w->wait_receive = 0;
1407                 return 1;
1408         }
1409
1410         // find the SPACE + "HTTP/"
1411         while(*s) {
1412                 // find the next space
1413                 while (*s && *s != ' ') s++;
1414
1415                 // is it SPACE + "HTTP/" ?
1416                 if(*s && !strncmp(s, " HTTP/", 6)) break;
1417                 else s++;
1418         }
1419
1420         // incomplete requests
1421         if(unlikely(!*s)) {
1422                 w->wait_receive = 1;
1423                 return -2;
1424         }
1425
1426         // we have the end of encoded_url - remember it
1427         char *ue = s;
1428
1429         // make sure we have complete request
1430         // complete requests contain: \r\n\r\n
1431         while(*s) {
1432                 // find a line feed
1433                 while(*s && *s++ != '\r');
1434
1435                 // did we reach the end?
1436                 if(unlikely(!*s)) break;
1437
1438                 // is it \r\n ?
1439                 if(likely(*s++ == '\n')) {
1440
1441                         // is it again \r\n ? (header end)
1442                         if(unlikely(*s == '\r' && s[1] == '\n')) {
1443                                 // a valid complete HTTP request found
1444
1445                                 *ue = '\0';
1446                                 url_decode_r(w->decoded_url, encoded_url, URL_MAX + 1);
1447                                 *ue = ' ';
1448                                 
1449                                 // copy the URL - we are going to overwrite parts of it
1450                                 // FIXME -- we should avoid it
1451                                 strncpyz(w->last_url, w->decoded_url, URL_MAX);
1452
1453                                 w->wait_receive = 0;
1454                                 return 0;
1455                         }
1456
1457                         // another header line
1458                         s = http_header_parse(w, s);
1459                 }
1460         }
1461
1462         // incomplete request
1463         w->wait_receive = 1;
1464         return -3;
1465 }
1466
1467 void web_client_process(struct web_client *w) {
1468         static uint32_t hash_api = 0, hash_netdata_conf = 0, hash_data = 0, hash_datasource = 0, hash_graph = 0,
1469                         hash_list = 0, hash_all_json = 0, hash_exit = 0, hash_debug = 0, hash_mirror = 0;
1470
1471         if(unlikely(!hash_api)) {
1472                 hash_api = simple_hash("api");
1473                 hash_netdata_conf = simple_hash("netdata.conf");
1474                 hash_data = simple_hash(WEB_PATH_DATA);
1475                 hash_datasource = simple_hash(WEB_PATH_DATASOURCE);
1476                 hash_graph = simple_hash(WEB_PATH_GRAPH);
1477                 hash_list = simple_hash("list");
1478                 hash_all_json = simple_hash("all.json");
1479                 hash_exit = simple_hash("exit");
1480                 hash_debug = simple_hash("debug");
1481                 hash_mirror = simple_hash("mirror");
1482         }
1483
1484         int code = 500;
1485         ssize_t bytes;
1486
1487         int what_to_do = http_request_validate(w);
1488
1489         // wait for more data
1490         if(what_to_do < 0) {
1491                 if(w->response.data->len > TOO_BIG_REQUEST) {
1492                         strcpy(w->last_url, "too big request");
1493
1494                         debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zd bytes).", w->id, w->response.data->len);
1495
1496                         code = 400;
1497                         buffer_flush(w->response.data);
1498                         buffer_sprintf(w->response.data, "Received request is too big  (%zd bytes).\r\n", w->response.data->len);
1499                 }
1500                 else {
1501                         // wait for more data
1502                         return;
1503                 }
1504         }
1505         else if(what_to_do > 0) {
1506                 strcpy(w->last_url, "not a valid request");
1507
1508                 debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer);
1509
1510                 code = 500;
1511                 buffer_flush(w->response.data);
1512                 buffer_strcat(w->response.data, "I don't understand you...\r\n");
1513         }
1514         else { // what_to_do == 0
1515                 gettimeofday(&w->tv_in, NULL);
1516
1517                 if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
1518                         code = 200;
1519                         w->response.data->contenttype = CT_TEXT_PLAIN;
1520                         buffer_flush(w->response.data);
1521                         buffer_strcat(w->response.data, "OK");
1522                 }
1523                 else {
1524                         char *url = w->decoded_url;
1525                         char *tok = mystrsep(&url, "/?");
1526                         if(tok && *tok) {
1527                                 uint32_t hash = simple_hash(tok);
1528                                 debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
1529
1530                                 if(hash == hash_api && strcmp(tok, "api") == 0) {
1531                                         // the client is requesting api access
1532                                         code = web_client_api_request(w, url);
1533                                 }
1534                                 else if(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0) {
1535                                         code = 200;
1536                                         debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
1537
1538                                         w->response.data->contenttype = CT_TEXT_PLAIN;
1539                                         buffer_flush(w->response.data);
1540                                         generate_config(w->response.data, 0);
1541                                 }
1542                                 else if(hash == hash_data && strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
1543                                         // the client is requesting rrd data -- OLD API
1544                                         code = web_client_api_old_data_request(w, url, DATASOURCE_JSON);
1545                                 }
1546                                 else if(hash == hash_datasource && strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource"
1547                                         // the client is requesting google datasource -- OLD API
1548                                         code = web_client_api_old_data_request(w, url, DATASOURCE_DATATABLE_JSONP);
1549                                 }
1550                                 else if(hash == hash_graph && strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
1551                                         // the client is requesting an rrd graph -- OLD API
1552
1553                                         // get the name of the data to show
1554                                         tok = mystrsep(&url, "/?&");
1555                                         if(tok && *tok) {
1556                                                 debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1557
1558                                                 // do we have such a data set?
1559                                                 RRDSET *st = rrdset_find_byname(tok);
1560                                                 if(!st) st = rrdset_find(tok);
1561                                                 if(!st) {
1562                                                         // we don't have it
1563                                                         // try to send a file with that name
1564                                                         buffer_flush(w->response.data);
1565                                                         code = mysendfile(w, tok);
1566                                                 }
1567                                                 else {
1568                                                         code = 200;
1569                                                         debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
1570                                                         w->response.data->contenttype = CT_APPLICATION_JSON;
1571                                                         buffer_flush(w->response.data);
1572                                                         rrd_stats_graph_json(st, url, w->response.data);
1573                                                 }
1574                                         }
1575                                         else {
1576                                                 code = 400;
1577                                                 buffer_flush(w->response.data);
1578                                                 buffer_strcat(w->response.data, "Graph name?\r\n");
1579                                         }
1580                                 }
1581                                 else if(hash == hash_list && strcmp(tok, "list") == 0) {
1582                                         // OLD API
1583                                         code = 200;
1584
1585                                         debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
1586
1587                                         buffer_flush(w->response.data);
1588                                         RRDSET *st = rrdset_root;
1589
1590                                         for ( ; st ; st = st->next )
1591                                                 buffer_sprintf(w->response.data, "%s\n", st->name);
1592                                 }
1593                                 else if(hash == hash_all_json && strcmp(tok, "all.json") == 0) {
1594                                         // OLD API
1595                                         code = 200;
1596                                         debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
1597
1598                                         w->response.data->contenttype = CT_APPLICATION_JSON;
1599                                         buffer_flush(w->response.data);
1600                                         rrd_stats_all_json(w->response.data);
1601                                 }
1602 #ifdef NETDATA_INTERNAL_CHECKS
1603                                 else if(hash == hash_exit && strcmp(tok, "exit") == 0) {
1604                                         code = 200;
1605                                         w->response.data->contenttype = CT_TEXT_PLAIN;
1606                                         buffer_flush(w->response.data);
1607
1608                                         if(!netdata_exit)
1609                                                 buffer_strcat(w->response.data, "ok, will do...");
1610                                         else
1611                                                 buffer_strcat(w->response.data, "I am doing it already");
1612
1613                                         netdata_exit = 1;
1614                                 }
1615                                 else if(hash == hash_debug && strcmp(tok, "debug") == 0) {
1616                                         buffer_flush(w->response.data);
1617
1618                                         // get the name of the data to show
1619                                         tok = mystrsep(&url, "/?&");
1620                                         if(tok && *tok) {
1621                                                 debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1622
1623                                                 // do we have such a data set?
1624                                                 RRDSET *st = rrdset_find_byname(tok);
1625                                                 if(!st) st = rrdset_find(tok);
1626                                                 if(!st) {
1627                                                         code = 404;
1628                                                         buffer_sprintf(w->response.data, "Chart %s is not found.\r\n", tok);
1629                                                         debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok);
1630                                                 }
1631                                                 else {
1632                                                         code = 200;
1633                                                         debug_flags |= D_RRD_STATS;
1634                                                         st->debug = !st->debug;
1635                                                         buffer_sprintf(w->response.data, "Chart %s has now debug %s.\r\n", tok, st->debug?"enabled":"disabled");
1636                                                         debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, st->debug?"enabled":"disabled");
1637                                                 }
1638                                         }
1639                                         else {
1640                                                 code = 500;
1641                                                 buffer_flush(w->response.data);
1642                                                 buffer_strcat(w->response.data, "debug which chart?\r\n");
1643                                         }
1644                                 }
1645                                 else if(hash == hash_mirror && strcmp(tok, "mirror") == 0) {
1646                                         code = 200;
1647
1648                                         debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
1649
1650                                         // replace the zero bytes with spaces
1651                                         buffer_char_replace(w->response.data, '\0', ' ');
1652
1653                                         // just leave the buffer as is
1654                                         // it will be copied back to the client
1655                                 }
1656 #endif  /* NETDATA_INTERNAL_CHECKS */
1657                                 else {
1658                                         char filename[FILENAME_MAX+1];
1659                                         url = filename;
1660                                         strncpyz(filename, w->last_url, FILENAME_MAX);
1661                                         tok = mystrsep(&url, "?");
1662                                         buffer_flush(w->response.data);
1663                                         code = mysendfile(w, (tok && *tok)?tok:"/");
1664                                 }
1665                         }
1666                         else {
1667                                 char filename[FILENAME_MAX+1];
1668                                 url = filename;
1669                                 strncpyz(filename, w->last_url, FILENAME_MAX);
1670                                 tok = mystrsep(&url, "?");
1671                                 buffer_flush(w->response.data);
1672                                 code = mysendfile(w, (tok && *tok)?tok:"/");
1673                         }
1674                 }
1675         }
1676
1677         gettimeofday(&w->tv_ready, NULL);
1678         w->response.data->date = time(NULL);
1679         w->response.sent = 0;
1680         w->response.code = code;
1681
1682         // prepare the HTTP response header
1683         debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code);
1684
1685         const char *content_type_string = web_content_type_to_string(w->response.data->contenttype);
1686         const char *code_msg = web_response_code_to_string(code);
1687
1688         char date[100];
1689         struct tm tmbuf, *tm = gmtime_r(&w->response.data->date, &tmbuf);
1690         strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", tm);
1691
1692         buffer_sprintf(w->response.header_output,
1693                 "HTTP/1.1 %d %s\r\n"
1694                 "Connection: %s\r\n"
1695                 "Server: NetData Embedded HTTP Server\r\n"
1696                 "Access-Control-Allow-Origin: %s\r\n"
1697                 "Access-Control-Allow-Credentials: true\r\n"
1698                 "Content-Type: %s\r\n"
1699                 "Date: %s\r\n"
1700                 , code, code_msg
1701                 , w->keepalive?"keep-alive":"close"
1702                 , w->origin
1703                 , content_type_string
1704                 , date
1705                 );
1706
1707         if(w->cookie1[0]) {
1708                 buffer_sprintf(w->response.header_output,
1709                    "Set-Cookie: %s\r\n",
1710                    w->cookie1);
1711         }
1712
1713         if(w->cookie2[0]) {
1714                 buffer_sprintf(w->response.header_output,
1715                    "Set-Cookie: %s\r\n",
1716                    w->cookie2);
1717         }
1718
1719         if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
1720                 buffer_strcat(w->response.header_output,
1721                         "Access-Control-Allow-Methods: GET, OPTIONS\r\n"
1722                         "Access-Control-Allow-Headers: accept, x-requested-with, origin, content-type, cookie\r\n"
1723                         "Access-Control-Max-Age: 1209600\r\n" // 86400 * 14
1724                         );
1725         }
1726
1727         if(buffer_strlen(w->response.header))
1728                 buffer_strcat(w->response.header_output, buffer_tostring(w->response.header));
1729
1730         if(w->mode == WEB_CLIENT_MODE_NORMAL && (w->response.data->options & WB_CONTENT_NO_CACHEABLE)) {
1731                 buffer_sprintf(w->response.header_output,
1732                         "Expires: %s\r\n"
1733                         "Cache-Control: no-cache\r\n"
1734                         , date);
1735         }
1736         else if(w->mode != WEB_CLIENT_MODE_OPTIONS) {
1737                 char edate[100];
1738                 time_t et = w->response.data->date + (86400 * 14);
1739                 struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf);
1740                 strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm);
1741
1742                 buffer_sprintf(w->response.header_output,
1743                         "Expires: %s\r\n"
1744                         "Cache-Control: public\r\n"
1745                         , edate);
1746         }
1747
1748         // if we know the content length, put it
1749         if(!w->response.zoutput && (w->response.data->len || w->response.rlen))
1750                 buffer_sprintf(w->response.header_output,
1751                         "Content-Length: %ld\r\n"
1752                         , w->response.data->len? w->response.data->len: w->response.rlen
1753                         );
1754         else if(!w->response.zoutput)
1755                 w->keepalive = 0;       // content-length is required for keep-alive
1756
1757         if(w->response.zoutput) {
1758                 buffer_strcat(w->response.header_output,
1759                         "Content-Encoding: gzip\r\n"
1760                         "Transfer-Encoding: chunked\r\n"
1761                         );
1762         }
1763
1764         buffer_strcat(w->response.header_output, "\r\n");
1765
1766         // sent the HTTP header
1767         debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %d: '%s'"
1768                         , w->id
1769                         , buffer_strlen(w->response.header_output)
1770                         , buffer_tostring(w->response.header_output)
1771                         );
1772
1773         web_client_crock_socket(w);
1774
1775         bytes = send(w->ofd, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output), 0);
1776         if(bytes != (ssize_t) buffer_strlen(w->response.header_output)) {
1777                 if(bytes > 0)
1778                         w->stats_sent_bytes += bytes;
1779
1780                 debug(D_WEB_CLIENT, "%llu: HTTP Header failed to be sent (I sent %d bytes but the system sent %d bytes). Closing web client.", w->id,
1781                           buffer_strlen(w->response.header_output), bytes);
1782
1783                 WEB_CLIENT_IS_DEAD(w);
1784                 return;
1785         }
1786         else 
1787                 w->stats_sent_bytes += bytes;
1788
1789         // enable sending immediately if we have data
1790         if(w->response.data->len) w->wait_send = 1;
1791         else w->wait_send = 0;
1792
1793         // pretty logging
1794         switch(w->mode) {
1795                 case WEB_CLIENT_MODE_OPTIONS:
1796                         debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%d bytes) to client.", w->id, w->response.data->len);
1797                         break;
1798
1799                 case WEB_CLIENT_MODE_NORMAL:
1800                         debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%d bytes) to client.", w->id, w->response.data->len);
1801                         break;
1802
1803                 case WEB_CLIENT_MODE_FILECOPY:
1804                         if(w->response.rlen) {
1805                                 debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %d bytes to client.", w->id, w->response.rlen);
1806                                 w->wait_receive = 1;
1807
1808                                 /*
1809                                 // utilize the kernel sendfile() for copying the file to the socket.
1810                                 // this block of code can be commented, without anything missing.
1811                                 // when it is commented, the program will copy the data using async I/O.
1812                                 {
1813                                         long len = sendfile(w->ofd, w->ifd, NULL, w->response.data->rbytes);
1814                                         if(len != w->response.data->rbytes)
1815                                                 error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->response.data->rbytes, len);
1816                                         else
1817                                                 web_client_reset(w);
1818                                 }
1819                                 */
1820                         }
1821                         else
1822                                 debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id);
1823                         break;
1824
1825                 default:
1826                         fatal("%llu: Unknown client mode %d.", w->id, w->mode);
1827                         break;
1828         }
1829 }
1830
1831 ssize_t web_client_send_chunk_header(struct web_client *w, size_t len)
1832 {
1833         debug(D_DEFLATE, "%llu: OPEN CHUNK of %d bytes (hex: %x).", w->id, len, len);
1834         char buf[1024];
1835         sprintf(buf, "%lX\r\n", len);
1836         
1837         ssize_t bytes = send(w->ofd, buf, strlen(buf), 0);
1838         if(bytes > 0) {
1839                 debug(D_DEFLATE, "%llu: Sent chunk header %d bytes.", w->id, bytes);
1840                 w->stats_sent_bytes += bytes;
1841         }
1842
1843         else if(bytes == 0) {
1844                 debug(D_WEB_CLIENT, "%llu: Did not send chunk header to the client.", w->id);
1845                 WEB_CLIENT_IS_DEAD(w);
1846         }
1847         else {
1848                 debug(D_WEB_CLIENT, "%llu: Failed to send chunk header to client.", w->id);
1849                 WEB_CLIENT_IS_DEAD(w);
1850         }
1851
1852         return bytes;
1853 }
1854
1855 ssize_t web_client_send_chunk_close(struct web_client *w)
1856 {
1857         //debug(D_DEFLATE, "%llu: CLOSE CHUNK.", w->id);
1858
1859         ssize_t bytes = send(w->ofd, "\r\n", 2, 0);
1860         if(bytes > 0) {
1861                 debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
1862                 w->stats_sent_bytes += bytes;
1863         }
1864
1865         else if(bytes == 0) {
1866                 debug(D_WEB_CLIENT, "%llu: Did not send chunk suffix to the client.", w->id);
1867                 WEB_CLIENT_IS_DEAD(w);
1868         }
1869         else {
1870                 debug(D_WEB_CLIENT, "%llu: Failed to send chunk suffix to client.", w->id);
1871                 WEB_CLIENT_IS_DEAD(w);
1872         }
1873
1874         return bytes;
1875 }
1876
1877 ssize_t web_client_send_chunk_finalize(struct web_client *w)
1878 {
1879         //debug(D_DEFLATE, "%llu: FINALIZE CHUNK.", w->id);
1880
1881         ssize_t bytes = send(w->ofd, "\r\n0\r\n\r\n", 7, 0);
1882         if(bytes > 0) {
1883                 debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
1884                 w->stats_sent_bytes += bytes;
1885         }
1886
1887         else if(bytes == 0) {
1888                 debug(D_WEB_CLIENT, "%llu: Did not send chunk finalize suffix to the client.", w->id);
1889                 WEB_CLIENT_IS_DEAD(w);
1890         }
1891         else {
1892                 debug(D_WEB_CLIENT, "%llu: Failed to send chunk finalize suffix to client.", w->id);
1893                 WEB_CLIENT_IS_DEAD(w);
1894         }
1895
1896         return bytes;
1897 }
1898
1899 #ifdef NETDATA_WITH_ZLIB
1900 ssize_t web_client_send_deflate(struct web_client *w)
1901 {
1902         ssize_t len = 0, t = 0;
1903
1904         // when using compression,
1905         // w->response.sent is the amount of bytes passed through compression
1906
1907         debug(D_DEFLATE, "%llu: web_client_send_deflate(): w->response.data->len = %d, w->response.sent = %d, w->response.zhave = %d, w->response.zsent = %d, w->response.zstream.avail_in = %d, w->response.zstream.avail_out = %d, w->response.zstream.total_in = %d, w->response.zstream.total_out = %d.", w->id, w->response.data->len, w->response.sent, w->response.zhave, w->response.zsent, w->response.zstream.avail_in, w->response.zstream.avail_out, w->response.zstream.total_in, w->response.zstream.total_out);
1908
1909         if(w->response.data->len - w->response.sent == 0 && w->response.zstream.avail_in == 0 && w->response.zhave == w->response.zsent && w->response.zstream.avail_out != 0) {
1910                 // there is nothing to send
1911
1912                 debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
1913
1914                 // finalize the chunk
1915                 if(w->response.sent != 0) {
1916                         t = web_client_send_chunk_finalize(w);
1917                         if(t < 0) return t;
1918                 }
1919
1920                 if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->response.rlen && w->response.rlen > w->response.data->len) {
1921                         // we have to wait, more data will come
1922                         debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
1923                         w->wait_send = 0;
1924                         return t;
1925                 }
1926
1927                 if(unlikely(!w->keepalive)) {
1928                         debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->response.sent);
1929                         WEB_CLIENT_IS_DEAD(w);
1930                         return t;
1931                 }
1932
1933                 // reset the client
1934                 web_client_reset(w);
1935                 debug(D_WEB_CLIENT, "%llu: Done sending all data on socket.", w->id);
1936                 return t;
1937         }
1938
1939         if(w->response.zhave == w->response.zsent) {
1940                 // compress more input data
1941
1942                 // close the previous open chunk
1943                 if(w->response.sent != 0) {
1944                         t = web_client_send_chunk_close(w);
1945                         if(t < 0) return t;
1946                 }
1947
1948                 debug(D_DEFLATE, "%llu: Compressing %d new bytes starting from %d (and %d left behind).", w->id, (w->response.data->len - w->response.sent), w->response.sent, w->response.zstream.avail_in);
1949
1950                 // give the compressor all the data not passed through the compressor yet
1951                 if(w->response.data->len > w->response.sent) {
1952                         w->response.zstream.next_in = (Bytef *)&w->response.data->buffer[w->response.sent - w->response.zstream.avail_in];
1953                         w->response.zstream.avail_in += (uInt) (w->response.data->len - w->response.sent);
1954                 }
1955
1956                 // reset the compressor output buffer
1957                 w->response.zstream.next_out = w->response.zbuffer;
1958                 w->response.zstream.avail_out = ZLIB_CHUNK;
1959
1960                 // ask for FINISH if we have all the input
1961                 int flush = Z_SYNC_FLUSH;
1962                 if(w->mode == WEB_CLIENT_MODE_NORMAL
1963                         || (w->mode == WEB_CLIENT_MODE_FILECOPY && !w->wait_receive && w->response.data->len == w->response.rlen)) {
1964                         flush = Z_FINISH;
1965                         debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id);
1966                 }
1967                 else {
1968                         debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id);
1969                 }
1970
1971                 // compress
1972                 if(deflate(&w->response.zstream, flush) == Z_STREAM_ERROR) {
1973                         error("%llu: Compression failed. Closing down client.", w->id);
1974                         web_client_reset(w);
1975                         return(-1);
1976                 }
1977
1978                 w->response.zhave = ZLIB_CHUNK - w->response.zstream.avail_out;
1979                 w->response.zsent = 0;
1980
1981                 // keep track of the bytes passed through the compressor
1982                 w->response.sent = w->response.data->len;
1983
1984                 debug(D_DEFLATE, "%llu: Compression produced %d bytes.", w->id, w->response.zhave);
1985
1986                 // open a new chunk
1987                 ssize_t t2 = web_client_send_chunk_header(w, w->response.zhave);
1988                 if(t2 < 0) return t2;
1989                 t += t2;
1990         }
1991         
1992         debug(D_WEB_CLIENT, "%llu: Sending %d bytes of data (+%d of chunk header).", w->id, w->response.zhave - w->response.zsent, t);
1993
1994         len = send(w->ofd, &w->response.zbuffer[w->response.zsent], (size_t) (w->response.zhave - w->response.zsent), MSG_DONTWAIT);
1995         if(len > 0) {
1996                 w->stats_sent_bytes += len;
1997                 w->response.zsent += len;
1998                 len += t;
1999                 debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, len);
2000         }
2001         else if(len == 0) {
2002                 debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client (zhave = %ld, zsent = %ld, need to send = %ld).", w->id, w->response.zhave, w->response.zsent, w->response.zhave - w->response.zsent);
2003                 WEB_CLIENT_IS_DEAD(w);
2004         }
2005         else {
2006                 debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id);
2007                 WEB_CLIENT_IS_DEAD(w);
2008         }
2009
2010         return(len);
2011 }
2012 #endif // NETDATA_WITH_ZLIB
2013
2014 ssize_t web_client_send(struct web_client *w) {
2015 #ifdef NETDATA_WITH_ZLIB
2016         if(likely(w->response.zoutput)) return web_client_send_deflate(w);
2017 #endif // NETDATA_WITH_ZLIB
2018
2019         ssize_t bytes;
2020
2021         if(unlikely(w->response.data->len - w->response.sent == 0)) {
2022                 // there is nothing to send
2023
2024                 debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
2025
2026                 // there can be two cases for this
2027                 // A. we have done everything
2028                 // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
2029
2030                 if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->response.rlen && w->response.rlen > w->response.data->len) {
2031                         // we have to wait, more data will come
2032                         debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
2033                         w->wait_send = 0;
2034                         return 0;
2035                 }
2036
2037                 if(unlikely(!w->keepalive)) {
2038                         debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->response.sent);
2039                         WEB_CLIENT_IS_DEAD(w);
2040                         return 0;
2041                 }
2042
2043                 web_client_reset(w);
2044                 debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
2045                 return 0;
2046         }
2047
2048         bytes = send(w->ofd, &w->response.data->buffer[w->response.sent], w->response.data->len - w->response.sent, MSG_DONTWAIT);
2049         if(likely(bytes > 0)) {
2050                 w->stats_sent_bytes += bytes;
2051                 w->response.sent += bytes;
2052                 debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
2053         }
2054         else if(likely(bytes == 0)) {
2055                 debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
2056                 WEB_CLIENT_IS_DEAD(w);
2057         }
2058         else {
2059                 debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id);
2060                 WEB_CLIENT_IS_DEAD(w);
2061         }
2062
2063         return(bytes);
2064 }
2065
2066 ssize_t web_client_receive(struct web_client *w)
2067 {
2068         // do we have any space for more data?
2069         buffer_need_bytes(w->response.data, WEB_REQUEST_LENGTH);
2070
2071         ssize_t left = w->response.data->size - w->response.data->len;
2072         ssize_t bytes;
2073
2074         if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY))
2075                 bytes = read(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1));
2076         else
2077                 bytes = recv(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1), MSG_DONTWAIT);
2078
2079         if(likely(bytes > 0)) {
2080                 if(w->mode != WEB_CLIENT_MODE_FILECOPY)
2081                         w->stats_received_bytes += bytes;
2082
2083                 size_t old = w->response.data->len;
2084                 w->response.data->len += bytes;
2085                 w->response.data->buffer[w->response.data->len] = '\0';
2086
2087                 debug(D_WEB_CLIENT, "%llu: Received %d bytes.", w->id, bytes);
2088                 debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->response.data->buffer[old]);
2089
2090                 if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
2091                         w->wait_send = 1;
2092
2093                         if(w->response.rlen && w->response.data->len >= w->response.rlen)
2094                                 w->wait_receive = 0;
2095                 }
2096         }
2097         else if(likely(bytes == 0)) {
2098                 debug(D_WEB_CLIENT, "%llu: Out of input data.", w->id);
2099
2100                 // if we cannot read, it means we have an error on input.
2101                 // if however, we are copying a file from ifd to ofd, we should not return an error.
2102                 // in this case, the error should be generated when the file has been sent to the client.
2103
2104                 if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
2105                         // we are copying data from ifd to ofd
2106                         // let it finish copying...
2107                         w->wait_receive = 0;
2108
2109                         debug(D_WEB_CLIENT, "%llu: Read the whole file.", w->id);
2110                         if(w->ifd != w->ofd) close(w->ifd);
2111                         w->ifd = w->ofd;
2112                 }
2113                 else {
2114                         debug(D_WEB_CLIENT, "%llu: failed to receive data.", w->id);
2115                         WEB_CLIENT_IS_DEAD(w);
2116                 }
2117         }
2118         else {
2119                 debug(D_WEB_CLIENT, "%llu: receive data failed.", w->id);
2120                 WEB_CLIENT_IS_DEAD(w);
2121         }
2122
2123         return(bytes);
2124 }
2125
2126
2127 // --------------------------------------------------------------------------------------
2128 // the thread of a single client
2129
2130 // 1. waits for input and output, using async I/O
2131 // 2. it processes HTTP requests
2132 // 3. it generates HTTP responses
2133 // 4. it copies data from input to output if mode is FILECOPY
2134
2135 void *web_client_main(void *ptr)
2136 {
2137         if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
2138                 error("Cannot set pthread cancel type to DEFERRED.");
2139
2140         if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
2141                 error("Cannot set pthread cancel state to ENABLE.");
2142
2143         struct web_client *w = ptr;
2144         struct pollfd fds[2], *ifd, *ofd;
2145         int retval, fdmax = 0, timeout;
2146
2147         log_access("%llu: %s port %s connected on thread task id %d", w->id, w->client_ip, w->client_port, gettid());
2148
2149         for(;;) {
2150                 if(unlikely(w->dead)) {
2151                         debug(D_WEB_CLIENT, "%llu: client is dead.", w->id);
2152                         break;
2153                 }
2154                 else if(unlikely(!w->wait_receive && !w->wait_send)) {
2155                         debug(D_WEB_CLIENT, "%llu: client is not set for neither receiving nor sending data.");
2156                         break;
2157                 }
2158
2159                 if(unlikely(w->ifd < 0 || w->ofd < 0)) {
2160                         error("%llu: invalid file descriptor, ifd = %d, ofd = %d (required 0 <= fd", w->id, w->ifd, w->ofd);
2161                         break;
2162                 }
2163
2164                 if(w->ifd == w->ofd) {
2165                         fds[0].fd = w->ifd;
2166                         fds[0].events = 0;
2167                         fds[0].revents = 0;
2168
2169                         if(w->wait_receive) fds[0].events |= POLLIN;
2170                         if(w->wait_send)    fds[0].events |= POLLOUT;
2171
2172                         fds[1].fd = -1;
2173                         fds[1].events = 0;
2174                         fds[1].revents = 0;
2175
2176                         ifd = ofd = &fds[0];
2177
2178                         fdmax = 1;
2179                 }
2180                 else {
2181                         fds[0].fd = w->ifd;
2182                         fds[0].events = 0;
2183                         fds[0].revents = 0;
2184                         if(w->wait_receive) fds[0].events |= POLLIN;
2185                         ifd = &fds[0];
2186
2187                         fds[1].fd = w->ofd;
2188                         fds[1].events = 0;
2189                         fds[1].revents = 0;
2190                         if(w->wait_send)    fds[1].events |= POLLOUT;
2191                         ofd = &fds[1];
2192
2193                         fdmax = 2;
2194                 }
2195
2196                 debug(D_WEB_CLIENT, "%llu: Waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":"");
2197                 errno = 0;
2198                 timeout = web_client_timeout * 1000;
2199                 retval = poll(fds, fdmax, timeout);
2200
2201                 if(unlikely(retval == -1)) {
2202                         if(errno == EAGAIN || errno == EINTR) {
2203                                 debug(D_WEB_CLIENT, "%llu: EAGAIN received.", w->id);
2204                                 continue;
2205                         }
2206
2207                         debug(D_WEB_CLIENT, "%llu: LISTENER: poll() failed (input fd = %d, output fd = %d). Closing client.", w->id, w->ifd, w->ofd);
2208                         break;
2209                 }
2210                 else if(unlikely(!retval)) {
2211                         debug(D_WEB_CLIENT, "%llu: Timeout while waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":"");
2212                         break;
2213                 }
2214
2215                 int used = 0;
2216                 if(w->wait_send && ofd->revents & POLLOUT) {
2217                         used++;
2218                         if(web_client_send(w) < 0) {
2219                                 debug(D_WEB_CLIENT, "%llu: Cannot send data to client. Closing client.", w->id);
2220                                 break;
2221                         }
2222                 }
2223
2224                 if(w->wait_receive && (ifd->revents & POLLIN || ifd->revents & POLLPRI)) {
2225                         used++;
2226                         if(web_client_receive(w) < 0) {
2227                                 debug(D_WEB_CLIENT, "%llu: Cannot receive data from client. Closing client.", w->id);
2228                                 break;
2229                         }
2230
2231                         if(w->mode == WEB_CLIENT_MODE_NORMAL) {
2232                                 debug(D_WEB_CLIENT, "%llu: Attempting to process received data.", w->id);
2233                                 web_client_process(w);
2234                         }
2235                 }
2236
2237                 if(unlikely(!used)) {
2238                         debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on socket.", w->id);
2239                         break;
2240                 }
2241         }
2242
2243         web_client_reset(w);
2244
2245         log_access("%llu: %s port %s disconnected from thread task id %d", w->id, w->client_ip, w->client_port, gettid());
2246         debug(D_WEB_CLIENT, "%llu: done...", w->id);
2247
2248         // close the sockets/files now
2249         // to free file descriptors
2250         if(w->ifd == w->ofd) {
2251                 if(w->ifd != -1) close(w->ifd);
2252         }
2253         else {
2254                 if(w->ifd != -1) close(w->ifd);
2255                 if(w->ofd != -1) close(w->ofd);
2256         }
2257         w->ifd = -1;
2258         w->ofd = -1;
2259
2260         w->obsolete = 1;
2261
2262         pthread_exit(NULL);
2263         return NULL;
2264 }