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