d72879f023a2c4a55b03b3a42fc2067a5d50e0e2
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2004
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
10
11
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
15
16 /* global variable for libcgi */
17 s_cgi *cgiArg;
18
19 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
20    and replace by result of arg[2] call */
21 int parse(char **, long, char *, char *(*)(long , const char **));
22
23 /**************************************************/
24 /* tag replacers ... they are called from parse   */
25 /* through function pointers                      */
26 /**************************************************/
27
28 /* return cgi var named arg[0] */ 
29 char* cgiget(long , const char **);
30
31 /* return a quoted cgi var named arg[0] */ 
32 char* cgigetq(long , const char **);
33
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , const char **);
36
37 /* call rrd_graph and insert appropriate image tag */
38 char* drawgraph(long, char **);
39
40 /* return PRINT functions from last rrd_graph call */
41 char* drawprint(long, const char **);
42
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, const char **);
45
46 /* pretty-print current time */
47 char* printtimenow(long, const char **);
48
49 /* set an environment variable */
50 char* rrdsetenv(long, const char **);
51
52 /* get an environment variable */
53 char* rrdgetenv(long, const char **);
54
55 /* include the named file at this point */
56 char* includefile(long, const char **);
57
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, const char **);
60
61 char* rrdstrip(char *buf);
62 char* scanargs(char *line, int *argc, char ***args);
63
64 /* format at-time specified times using strftime */
65 char* printstrftime(long, const char**);
66
67 /** HTTP protocol needs special format, and GMT time **/
68 char *http_time(time_t *);
69
70 /* return a pointer to newly allocated copy of this string */
71 char *stralloc(const char *);
72
73
74 /* rrd interface to the variable functions {put,get}var() */
75 char* rrdgetvar(long argc, const char **args);
76 char* rrdsetvar(long argc, const char **args);
77 char* rrdsetvarconst(long argc, const char **args);
78
79
80 /* variable store: put/get key-value pairs */
81 static int   initvar();
82 static void  donevar();
83 static const char* getvar(const char* varname);
84 static const char* putvar(const char* name, const char* value, int is_const);
85
86 /* key value pair that makes up an entry in the variable store */
87 typedef struct
88 {
89         int is_const;           /* const variable or not */
90         const char* name;       /* variable name */
91         const char* value;      /* variable value */
92 } vardata;
93
94 /* the variable heap: 
95    start with a heapsize of 10 variables */
96 #define INIT_VARSTORE_SIZE      10
97 static vardata* varheap    = NULL;
98 static size_t varheap_size = 0;
99
100 /* allocate and initialize variable heap */
101 static int
102 initvar()
103 {
104         varheap = (vardata*)malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
105         if (varheap == NULL) {
106                 fprintf(stderr, "ERROR: unable to initialize variable store\n");
107                 return -1;
108         }
109         memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
110         varheap_size = INIT_VARSTORE_SIZE;
111         return 0;
112 }
113
114 /* cleanup: free allocated memory */
115 static void
116 donevar()
117 {
118         int i;
119         if (varheap) {
120                 for (i=0; i<(int)varheap_size; i++) {
121                         if (varheap[i].name) {
122                                 free((char*)varheap[i].name);
123                         }
124                         if (varheap[i].value) {
125                                 free((char*)varheap[i].value);
126                         }
127                 }
128                 free(varheap);
129         }
130 }
131
132 /* Get a variable from the variable store.
133    Return NULL in case the requested variable was not found. */
134 static const char*
135 getvar(const char* name)
136 {
137         int i;
138         for (i=0; i<(int)varheap_size && varheap[i].name; i++) {
139                 if (0 == strcmp(name, varheap[i].name)) {
140 #ifdef          DEBUG_VARS
141                         printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
142 #endif
143                         return varheap[i].value;
144                 }
145         }
146 #ifdef DEBUG_VARS
147         printf("<!-- getvar(%s) -> Not found-->\n", name);
148 #endif
149         return NULL;
150 }
151
152 /* Put a variable into the variable store. If a variable by that
153    name exists, it's value is overwritten with the new value unless it was
154    marked as 'const' (initialized by RRD::SETCONSTVAR).
155    Returns a copy the newly allocated value on success, NULL on error. */
156 static const char*
157 putvar(const char* name, const char* value, int is_const)
158 {
159         int i;
160         for (i=0; i < (int)varheap_size && varheap[i].name; i++) {
161                 if (0 == strcmp(name, varheap[i].name)) {
162                         /* overwrite existing entry */
163                         if (varheap[i].is_const) {
164 #ifdef                  DEBUG_VARS
165                                 printf("<!-- setver(%s, %s): not assigning: "
166                                                 "const variable -->\n", name, value);
167 #                               endif
168                                 return varheap[i].value;
169                         }
170 #ifdef          DEBUG_VARS
171                         printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
172                                         name, value, varheap[i].value);
173 #endif
174                         /* make it possible to promote a variable to readonly */
175                         varheap[i].is_const = is_const;
176                         free((char*)varheap[i].value);
177                         varheap[i].value = stralloc(value);
178                         return varheap[i].value;
179                 }
180         }
181
182         /* no existing variable found by that name, add it */
183         if (i == (int)varheap_size) {
184                 /* ran out of heap: resize heap to double size */
185                 size_t new_size = varheap_size * 2;
186                 varheap = (vardata*)(realloc(varheap, sizeof(vardata) * new_size));
187                 if (!varheap) {
188                         fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
189                         return NULL;
190                 }
191                 /* initialize newly allocated memory */;
192                 memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
193                 varheap_size = new_size;
194         }
195         varheap[i].is_const = is_const;
196         varheap[i].name  = stralloc(name);
197         varheap[i].value = stralloc(value);
198
199 #ifdef          DEBUG_VARS
200         printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
201 #endif
202         return varheap[i].value;
203 }
204
205 /* expand those RRD:* directives that can be used recursivly */
206 static char*
207 rrd_expand_vars(char* buffer)
208 {
209         int i;
210
211 #ifdef DEBUG_PARSER
212         printf("expanding variables in '%s'\n", buffer);
213 #endif
214
215         for (i=0; buffer[i]; i++) {
216                 if (buffer[i] != '<')
217                         continue;
218                 parse(&buffer, i, "<RRD::CV", cgiget);
219                 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
220                 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
221                 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);    
222                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);    
223         }
224         return buffer;
225 }
226
227 static long goodfor=0;
228 static char **calcpr=NULL;
229 static void calfree (void){
230   if (calcpr) {
231     long i;
232     for(i=0;calcpr[i];i++){
233       if (calcpr[i]){
234               free(calcpr[i]);
235       }
236     } 
237     if (calcpr) {
238             free(calcpr);
239     }
240   }
241 }
242
243 /* create freeable version of the string */
244 char * stralloc(const char *str){
245   char* nstr;
246   if (!str) {
247           return NULL;
248   }
249   nstr = malloc((strlen(str)+1));
250   strcpy(nstr,str);
251   return(nstr);
252 }
253
254 int main(int argc, char *argv[]) {
255         long length;
256         char *buffer;
257         char *server_url = NULL;
258         long i;
259         long filter=0;
260 #ifdef MUST_DISABLE_SIGFPE
261         signal(SIGFPE,SIG_IGN);
262 #endif
263 #ifdef MUST_DISABLE_FPMASK
264         fpsetmask(0);
265 #endif
266         /* what do we get for cmdline arguments?
267         for (i=0;i<argc;i++)
268         printf("%d-'%s'\n",i,argv[i]); */
269         while (1) {
270                 static struct option long_options[] = {
271                         { "filter", no_argument, 0, 'f' },
272                         { 0, 0, 0, 0}
273                 };
274                 int option_index = 0;
275                 int opt;
276                 opt = getopt_long(argc, argv, "f", long_options, &option_index);
277                 if (opt == EOF) {
278                         break;
279                 }
280
281                 switch(opt) {
282                 case 'f':
283                                 filter=1;
284                         break;
285                 case '?':
286                         printf("unknown commandline option '%s'\n",argv[optind-1]);
287                         return -1;
288                 }
289         }
290
291         if (!filter) {
292                 cgiDebug(0,0);
293                 cgiArg = cgiInit();
294                 server_url = getenv("SERVER_URL");
295         }
296
297         /* make sure we have one extra argument, 
298            if there are others, we do not care Apache gives several */
299
300         /* if ( (optind != argc-2 
301            && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
302            && optind != argc-1) { */
303
304         if ( optind >= argc ) { 
305                 fprintf(stderr, "ERROR: expected a filename\n");
306                 exit(1);
307         } else {
308                 length = readfile(argv[optind], &buffer, 1);
309         }
310
311         if(rrd_test_error()) {
312                 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
313                 exit(1);
314         }
315
316         /* initialize variable heap */
317         initvar();
318
319         /* expand rrd directives in buffer recursivly */
320         for (i=0; buffer[i]; i++) {
321                 if (buffer[i] != '<')
322                         continue;
323                 if (!filter) {
324                         parse(&buffer, i, "<RRD::CV", cgiget);
325                         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
326                         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
327                         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
328                 }
329                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
330                 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
331                 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
332                 parse(&buffer, i, "<RRD::INCLUDE", includefile);
333                 parse(&buffer, i, "<RRD::PRINT", drawprint);
334                 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
335                 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
336                 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
337                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
338                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
339                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
340         }
341
342         if (!filter) {
343                 printf ("Content-Type: text/html\n" 
344                                 "Content-Length: %d\n", 
345                                 strlen(buffer));
346
347                 if (labs(goodfor) > 0) {
348                         time_t now;
349                         now = time(NULL);
350                         printf("Last-Modified: %s\n", http_time(&now));
351                         now += labs(goodfor);
352                         printf("Expires: %s\n", http_time(&now));
353                         if (goodfor < 0) {
354                                 printf("Refresh: %ld\n", labs(goodfor));
355                         }
356                 }
357                 printf("\n");
358         }
359
360         /* output result */
361         printf("%s", buffer);
362
363         /* cleanup */
364         calfree();
365         if (buffer){
366                 free(buffer);
367         }
368         donevar();
369         exit(0);
370 }
371
372 /* remove occurrences of .. this is a general measure to make
373    paths which came in via cgi do not go UP ... */
374
375 char* rrdsetenv(long argc, const char **args) {
376         if (argc >= 2) {
377                 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
378                 if (xyz == NULL) {
379                         return stralloc("[ERROR: allocating setenv buffer]");
380                 };
381                 sprintf(xyz, "%s=%s", args[0], args[1]);
382                 if(putenv(xyz) == -1) {
383                         free(xyz);
384                         return stralloc("[ERROR: failed to do putenv]");
385                 };
386         }
387         return stralloc("[ERROR: setenv failed because not enough "
388                                         "arguments were defined]");
389 }
390
391 /* rrd interface to the variable function putvar() */
392 char*
393 rrdsetvar(long argc, const char **args)
394 {
395         if (argc >= 2)
396         {
397                 const char* result = putvar(args[0], args[1], 0 /* not const */);
398                 if (result) {
399                         /* setvar does not return the value set */
400                         return stralloc("");
401                 }
402                 return stralloc("[ERROR: putvar failed]");
403         }
404         return stralloc("[ERROR: putvar failed because not enough arguments "
405                                         "were defined]");
406 }
407
408 /* rrd interface to the variable function putvar() */
409 char*
410 rrdsetvarconst(long argc, const char **args)
411 {
412         if (argc >= 2)
413         {
414                 const char* result = putvar(args[0], args[1], 1 /* const */);
415                 if (result) {
416                         /* setvar does not return the value set */
417                         return stralloc("");
418                 }
419                 return stralloc("[ERROR: putvar failed]");
420         }
421         return stralloc("[ERROR: putvar failed because not enough arguments "
422                                         "were defined]");
423 }
424
425 char* rrdgetenv(long argc, const char **args) {
426         char buf[128];
427         const char* envvar;
428         if (argc != 1) {
429                 return stralloc("[ERROR: getenv failed because it did not "
430                                                 "get 1 argument only]");
431         };
432         envvar = getenv(args[0]);
433         if (envvar) {
434                 return stralloc(envvar);
435         } else {
436                 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
437                 return stralloc(buf);
438         }
439 }
440
441 char* rrdgetvar(long argc, const char **args) {
442         char buf[128];
443         const char* value;
444         if (argc != 1) {
445                 return stralloc("[ERROR: getvar failed because it did not "
446                                                 "get 1 argument only]");
447         };
448         value = getvar(args[0]);
449         if (value) {
450                 return stralloc(value);
451         } else {
452                 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
453                 return stralloc(buf);
454         }
455 }
456
457 char* rrdgoodfor(long argc, const char **args){
458   if (argc == 1) {
459       goodfor = atol(args[0]);
460   } else {
461     return stralloc("[ERROR: goodfor expected 1 argument]");
462   }
463    
464   if (goodfor == 0){
465      return stralloc("[ERROR: goodfor value must not be 0]");
466   }
467    
468   return stralloc("");
469 }
470
471 /* Format start or end times using strftime.  We always need both the
472  * start and end times, because, either might be relative to the other.
473  * */
474 #define MAX_STRFTIME_SIZE 256
475 char* printstrftime(long argc, const char **args){
476         struct  rrd_time_value start_tv, end_tv;
477         char    *parsetime_error = NULL;
478         char    formatted[MAX_STRFTIME_SIZE];
479         struct tm *the_tm;
480         time_t  start_tmp, end_tmp;
481
482         /* Make sure that we were given the right number of args */
483         if( argc != 4) {
484                 rrd_set_error( "wrong number of args %d", argc);
485                 return (char *) -1;
486         }
487
488         /* Init start and end time */
489         parsetime("end-24h", &start_tv);
490         parsetime("now", &end_tv);
491
492         /* Parse the start and end times we were given */
493         if( (parsetime_error = parsetime( args[1], &start_tv))) {
494                 rrd_set_error( "start time: %s", parsetime_error);
495                 return (char *) -1;
496         }
497         if( (parsetime_error = parsetime( args[2], &end_tv))) {
498                 rrd_set_error( "end time: %s", parsetime_error);
499                 return (char *) -1;
500         }
501         if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
502                 return (char *) -1;
503         }
504
505         /* Do we do the start or end */
506         if( strcasecmp( args[0], "START") == 0) {
507                 the_tm = localtime( &start_tmp);
508         }
509         else if( strcasecmp( args[0], "END") == 0) {
510                 the_tm = localtime( &end_tmp);
511         }
512         else {
513                 rrd_set_error( "start/end not found in '%s'", args[0]);
514                 return (char *) -1;
515         }
516
517         /* now format it */
518         if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
519                 return( stralloc( formatted));
520         }
521         else {
522                 rrd_set_error( "strftime failed");
523                 return (char *) -1;
524         }
525 }
526
527 char* includefile(long argc, const char **args){
528   char *buffer;
529   if (argc >= 1) {
530       readfile(args[0], &buffer, 0);
531       if (rrd_test_error()) {
532                 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
533           sprintf(err, "[ERROR: %s]",rrd_get_error());
534           rrd_clear_error();
535           return err;
536       } else {
537        return buffer;
538       }
539   }
540   else
541   {
542       return stralloc("[ERROR: No Inclue file defined]");
543   }
544 }
545
546 /* make a copy of buf and replace open/close brackets with '_' */
547 char* rrdstrip(char *buf) {
548   char* p;
549   if (buf == NULL) {
550           return NULL;
551   }
552   /* make a copy of the buffer */
553   buf = stralloc(buf);
554   if (buf == NULL) {
555           return NULL;
556   }
557
558   p = buf;
559   while (*p) {
560           if (*p == '<' || *p == '>') {
561                   *p = '_';
562           }
563           p++;
564   }
565   return buf;
566 }
567
568 char* cgigetq(long argc, const char **args){
569   if (argc>= 1){
570     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
571     char *buf2;
572     char *c,*d;
573     int  qc=0;
574     if (buf==NULL) return NULL;
575
576     for(c=buf;*c != '\0';c++)
577       if (*c == '"') qc++;
578     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
579         perror("Malloc Buffer");
580         exit(1);
581     };
582     c=buf;
583     d=buf2;
584     *(d++) = '"';
585     while(*c != '\0'){
586         if (*c == '"') {
587             *(d++) = '"';
588             *(d++) = '\'';
589             *(d++) = '"';
590             *(d++) = '\'';
591         } 
592         *(d++) = *(c++);
593     }
594     *(d++) = '"';
595     *(d) = '\0';
596     free(buf);
597     return buf2;
598   }
599
600   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
601 }
602
603 /* remove occurrences of .. this is a general measure to make
604    paths which came in via cgi do not go UP ... */
605
606 char* cgigetqp(long argc, const char **args){
607   if (argc>= 1) {
608     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
609     char *buf2;
610     char *c,*d;
611     int  qc=0;
612
613     if (buf==NULL) 
614                 return NULL;
615
616     for(c=buf;*c != '\0';c++) {
617         if (*c == '"') {
618                         qc++;
619                 }
620         }
621
622     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
623                 perror("Malloc Buffer");
624                 exit(1);
625     };
626
627     c=buf;
628     d=buf2;
629
630     *(d++) = '"';
631     while (*c != '\0') {
632                 if (*c == '"') {
633                         *(d++) = '"';
634                         *(d++) = '\'';
635                         *(d++) = '"';
636                         *(d++) = '\'';
637                 }
638                 if(*c == '/') {
639                         *(d++) = '_';
640                         c++;
641                 } else {
642                         if (*c=='.' && *(c+1) == '.') {
643                                 c += 2;
644                                 *(d++) = '_'; *(d++) ='_';      
645                         } else {
646                                 *(d++) = *(c++);
647                         }
648                 }
649     }
650     *(d++) = '"';
651     *(d) = '\0';
652     free(buf);
653     return buf2;
654   }
655   return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
656 }
657
658
659 char* cgiget(long argc, const char **args){
660   if (argc>= 1)
661     return rrdstrip(cgiGetValue(cgiArg,args[0]));
662   else
663     return stralloc("[ERROR: not enough arguments for RRD::CV]");
664 }
665
666
667
668 char* drawgraph(long argc, char **args){
669   int i,xsize, ysize;
670   for(i=0;i<argc;i++)
671     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
672   if(i==argc) {
673     args[argc++] = "--imginfo";
674     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
675   }
676   optind=0; /* reset gnu getopt */
677   opterr=0; /* reset gnu getopt */
678   calfree();
679   if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL) != -1 ) {
680     return stralloc(calcpr[0]);
681   } else {
682     if (rrd_test_error()) {
683       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
684       sprintf(err, "[ERROR: %s]",rrd_get_error());
685       rrd_clear_error();
686       calfree();
687       return err;
688     }
689   }
690   return NULL;
691 }
692
693 char* drawprint(long argc, const char **args){
694   if (argc==1 && calcpr){
695     long i=0;
696     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
697     if (atol(args[0])<i-1)
698       return stralloc(calcpr[atol(args[0])+1]);    
699   }
700   return stralloc("[ERROR: RRD::PRINT argument error]");
701 }
702
703 char* printtimelast(long argc, const char **args) {
704   time_t last;
705   struct tm tm_last;
706   char *buf;
707   if ( argc == 2 ) {
708     buf = malloc(255);
709     if (buf == NULL){   
710         return stralloc("[ERROR: allocating strftime buffer]");
711     };
712     last = rrd_last(argc+1, args-1); 
713     if (rrd_test_error()) {
714       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
715       sprintf(err, "[ERROR: %s]",rrd_get_error());
716       rrd_clear_error();
717       return err;
718     }
719     tm_last = *localtime(&last);
720     strftime(buf,254,args[1],&tm_last);
721     return buf;
722   }
723   if ( argc < 2 ) {
724     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
725   }
726   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
727 }
728
729 char* printtimenow(long argc, const char **args) {
730   time_t now = time(NULL);
731   struct tm tm_now;
732   char *buf;
733   if ( argc == 1 ) {
734     buf = malloc(255);
735     if (buf == NULL){   
736         return stralloc("[ERROR: allocating strftime buffer]");
737     };
738     tm_now = *localtime(&now);
739     strftime(buf,254,args[0],&tm_now);
740     return buf;
741   }
742   if ( argc < 1 ) {
743     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
744   }
745   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
746 }
747
748 /* Scan buffer until an unescaped '>' arives.
749  * Update argument array with arguments found.
750  * Return end cursor where parsing stopped, or NULL in case of failure.
751  *
752  * FIXME:
753  * To allow nested constructs, we call rrd_expand_vars() for arguments
754  * that contain RRD::x directives. These introduce a small memory leak
755  * since we have to stralloc the arguments the way parse() works.
756  */
757 char*
758 scanargs(char *line, int *argument_count, char ***arguments)
759 {
760         char    *getP;          /* read cursor */
761         char    *putP;          /* write cursor */
762         char    Quote;          /* type of quote if in quoted string, 0 otherwise */
763         int     tagcount;       /* open tag count */
764         int     in_arg;         /* if we currently are parsing an argument or not */
765         int     argsz;          /* argument array size */
766         int             curarg_contains_rrd_directives;
767
768         /* local array of arguments while parsing */
769         int argc = 0;
770         char** argv;
771
772 #ifdef DEBUG_PARSER
773         printf("<-- scanargs(%s) -->\n", line);
774 #endif
775
776         *arguments = NULL;
777         *argument_count = 0;
778
779         /* create initial argument array of char pointers */
780         argsz = 32;
781         argv = (char **)malloc(argsz * sizeof(char *));
782         if (!argv) {
783                 return NULL;
784         }
785
786         /* skip leading blanks */
787         while (isspace((int)*line)) {
788                 line++;
789         }
790
791         getP = line;
792         putP = line;
793
794         Quote    = 0;
795         in_arg   = 0;
796         tagcount = 0;
797
798         curarg_contains_rrd_directives = 0;
799
800         /* start parsing 'line' for arguments */
801         while (*getP)
802         {
803                 unsigned char c = *getP++;
804
805                 if (c == '>' && !Quote && !tagcount) {
806                         /* this is our closing tag, quit scanning */
807                         break;
808                 }
809
810                 /* remove all special chars */
811                 if (c < ' ') {
812                         c = ' ';
813                 }
814
815                 switch (c)
816                 {
817                 case ' ': 
818                         if (Quote || tagcount) {
819                                 /* copy quoted/tagged string */
820                                 *putP++ = c;
821                         }
822                         else if (in_arg)
823                         {
824                                 /* end argument string */
825                                 *putP++ = 0;
826                                 in_arg = 0;
827                                 if (curarg_contains_rrd_directives) {
828                                         argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
829                                         curarg_contains_rrd_directives = 0;
830                                 }
831                         }
832                         break;
833
834                 case '"': /* Fall through */
835                 case '\'':
836                         if (Quote != 0) {
837                                 if (Quote == c) {
838                                         Quote = 0;
839                                 } else {
840                                         /* copy quoted string */
841                                         *putP++ = c;
842                                 }
843                         } else {
844                                 if (!in_arg) {
845                                         /* reference argument string in argument array */
846                                         argv[argc++] = putP;
847                                         in_arg=1;
848                                 }
849                                 Quote = c;
850                         }
851                         break;
852
853                 default:
854                         if (!Quote) {
855                                 if (!in_arg) {
856                                         /* start new argument */
857                                         argv[argc++] = putP;
858                                         in_arg = 1;
859                                 }
860                                 if (c == '>') {
861                                         if (tagcount) {
862                                                 tagcount--;
863                                         }
864                                 }
865                                 if (c == '<') {
866                                         tagcount++;
867                                         if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
868                                                 curarg_contains_rrd_directives = 1;
869                                         }
870                                 }
871                         }
872                         *putP++ = c;
873                         break;
874                 }
875
876                 /* check if our argument array is still large enough */
877                 if (argc == argsz) {
878                         /* resize argument array */
879                         argsz *= 2;
880                         argv = rrd_realloc(argv, argsz * sizeof(char *));
881                         if (*argv == NULL) {
882                                 return NULL;
883                         }
884                 }
885         }
886
887         /* terminate last argument found */
888         *putP = '\0';
889         if (curarg_contains_rrd_directives) {
890                 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
891         }
892
893 #ifdef DEBUG_PARSER
894         if (argc > 0) {
895                 int n;
896                 printf("<-- arguments found [%d]\n", argc);
897                 for (n=0; n<argc; n++) {
898                         printf("arg %02d: '%s'\n", n, argv[n]);
899                 }
900                 printf("-->\n");
901         } else {
902                 printf("<!-- No arguments found -->\n");
903         }
904 #endif
905
906         /* update caller's notion of the argument array and it's size */
907         *arguments = argv;
908         *argument_count = argc;
909
910         if (Quote) {
911                 return NULL;
912         }
913
914         /* Return new scanning cursor:
915            pointer to char after closing bracket */
916         return getP;
917 }
918
919
920 /*
921  * Parse(): scan current portion of buffer for given tag.
922  * If found, parse tag arguments and call 'func' for it.
923  * The result of func is inserted at the current position
924  * in the buffer.
925  */
926 int
927 parse(
928         char **buf,     /* buffer */
929         long i,                 /* offset in buffer */
930         char *tag,      /* tag to handle  */
931         char *(*func)(long , const char **) /* function to call for 'tag' */
932         )
933 {
934         /* the name of the vairable ... */
935         char *val;
936         long valln;  
937         char **args;
938         char *end;
939         long end_offset;
940         int  argc;
941         size_t taglen = strlen(tag);
942
943         /* Current position in buffer should start with 'tag' */
944         if (strncmp((*buf)+i, tag, taglen) != 0) {
945                 return 0;
946         }
947         /* .. and match exactly (a whitespace following 'tag') */
948         if (! isspace(*((*buf) + i + taglen)) ) {
949                 return 0;
950         }
951
952 #ifdef DEBUG_PARSER
953         printf("parse(): handling tag '%s'\n", tag);
954 #endif
955
956         /* Scan for arguments following the tag;
957            scanargs() puts \0 into *buf ... so after scanargs it is probably
958            not a good time to use strlen on buf */
959         end = scanargs((*buf) + i + taglen, &argc, &args);
960         if (end)
961         {
962                 /* got arguments, call function for 'tag' with arguments */
963                 val = func(argc, args);
964                 free(args);
965         }
966         else
967         {
968                 /* unable to parse arguments, undo 0-termination by scanargs */
969                 for (; argc > 0; argc--) {
970                         *((args[argc-1])-1) = ' ';
971                 }
972
973                 /* next call, try parsing at current offset +1 */
974                 end = (*buf) + i + 1;
975
976                 val = stralloc("[ERROR: Parsing Problem with the following text\n"
977                                                 " Check original file. This may have been altered "
978                                                 "by parsing.]\n\n");
979         }
980
981         /* remember offset where we have to continue parsing */
982         end_offset = end - (*buf);
983
984         valln = 0;
985         if (val) {
986                 valln = strlen(val);
987         }
988
989         /* Optionally resize buffer to hold the replacement value:
990            Calculating the new length of the buffer is simple. add current
991            buffer pos (i) to length of string after replaced tag to length
992            of replacement string and add 1 for the final zero ... */
993         if (end - (*buf) < (i + valln)) {
994                 /* make sure we do not shrink the mallocd block */
995                 size_t newbufsize = i + strlen(end) + valln + 1;
996                 *buf = rrd_realloc(*buf, newbufsize);
997
998                 if (*buf == NULL) {
999                         perror("Realoc buf:");
1000                         exit(1);
1001                 };
1002         }
1003
1004         /* Update new end pointer:
1005            make sure the 'end' pointer gets moved along with the 
1006            buf pointer when realloc moves memory ... */
1007         end = (*buf) + end_offset; 
1008
1009         /* splice the variable:
1010            step 1. Shift pending data to make room for 'val' */
1011         memmove((*buf) + i + valln, end, strlen(end) + 1);
1012
1013         /* step 2. Insert val */
1014         if (val) {
1015                 memmove((*buf)+i, val, valln);
1016                 free(val);
1017         }
1018         return (valln > 0 ? valln-1: valln);
1019 }
1020
1021 char *
1022 http_time(time_t *now) {
1023         struct tm *tmptime;
1024         static char buf[60];
1025
1026         tmptime=gmtime(now);
1027         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1028         return(buf);
1029 }