411b02bb23f75bafa0324a5379d26a5601c41dae
[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                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
224                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
225                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
226         }
227         return buffer;
228 }
229
230 static long goodfor=0;
231 static char **calcpr=NULL;
232 static void calfree (void){
233   if (calcpr) {
234     long i;
235     for(i=0;calcpr[i];i++){
236       if (calcpr[i]){
237               free(calcpr[i]);
238       }
239     } 
240     if (calcpr) {
241             free(calcpr);
242     }
243   }
244 }
245
246 /* create freeable version of the string */
247 char * stralloc(const char *str){
248   char* nstr;
249   if (!str) {
250           return NULL;
251   }
252   nstr = malloc((strlen(str)+1));
253   strcpy(nstr,str);
254   return(nstr);
255 }
256
257 int main(int argc, char *argv[]) {
258         long length;
259         char *buffer;
260         char *server_url = NULL;
261         long i;
262         long filter=0;
263 #ifdef MUST_DISABLE_SIGFPE
264         signal(SIGFPE,SIG_IGN);
265 #endif
266 #ifdef MUST_DISABLE_FPMASK
267         fpsetmask(0);
268 #endif
269         /* what do we get for cmdline arguments?
270         for (i=0;i<argc;i++)
271         printf("%d-'%s'\n",i,argv[i]); */
272         while (1) {
273                 static struct option long_options[] = {
274                         { "filter", no_argument, 0, 'f' },
275                         { 0, 0, 0, 0}
276                 };
277                 int option_index = 0;
278                 int opt;
279                 opt = getopt_long(argc, argv, "f", long_options, &option_index);
280                 if (opt == EOF) {
281                         break;
282                 }
283
284                 switch(opt) {
285                 case 'f':
286                                 filter=1;
287                         break;
288                 case '?':
289                         printf("unknown commandline option '%s'\n",argv[optind-1]);
290                         return -1;
291                 }
292         }
293
294         if (!filter) {
295                 cgiDebug(0,0);
296                 cgiArg = cgiInit();
297                 server_url = getenv("SERVER_URL");
298         }
299
300         /* make sure we have one extra argument, 
301            if there are others, we do not care Apache gives several */
302
303         /* if ( (optind != argc-2 
304            && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
305            && optind != argc-1) { */
306
307         if ( optind >= argc ) { 
308                 fprintf(stderr, "ERROR: expected a filename\n");
309                 exit(1);
310         } else {
311                 length = readfile(argv[optind], &buffer, 1);
312         }
313
314         if(rrd_test_error()) {
315                 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
316                 exit(1);
317         }
318
319         /* initialize variable heap */
320         initvar();
321
322 #ifdef DEBUG_PARSER
323        /* some fake header for testing */
324        printf ("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
325 #endif
326
327
328         /* expand rrd directives in buffer recursivly */
329         for (i=0; buffer[i]; i++) {
330                 if (buffer[i] != '<')
331                         continue;
332                 if (!filter) {
333                         parse(&buffer, i, "<RRD::CV", cgiget);
334                         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
335                         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
336                         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
337                 }
338                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
339                 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
340                 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
341                 parse(&buffer, i, "<RRD::INCLUDE", includefile);
342                 parse(&buffer, i, "<RRD::PRINT", drawprint);
343                 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
344                 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
345                 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
346                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
347                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
348                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
349         }
350
351         if (!filter) {
352                 printf ("Content-Type: text/html\n" 
353                                 "Content-Length: %d\n", 
354                                 strlen(buffer));
355
356                 if (labs(goodfor) > 0) {
357                         time_t now;
358                         now = time(NULL);
359                         printf("Last-Modified: %s\n", http_time(&now));
360                         now += labs(goodfor);
361                         printf("Expires: %s\n", http_time(&now));
362                         if (goodfor < 0) {
363                                 printf("Refresh: %ld\n", labs(goodfor));
364                         }
365                 }
366                 printf("\n");
367         }
368
369         /* output result */
370         printf("%s", buffer);
371
372         /* cleanup */
373         calfree();
374         if (buffer){
375                 free(buffer);
376         }
377         donevar();
378         exit(0);
379 }
380
381 /* remove occurrences of .. this is a general measure to make
382    paths which came in via cgi do not go UP ... */
383
384 char* rrdsetenv(long argc, const char **args) {
385         if (argc >= 2) {
386                 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
387                 if (xyz == NULL) {
388                         return stralloc("[ERROR: allocating setenv buffer]");
389                 };
390                 sprintf(xyz, "%s=%s", args[0], args[1]);
391                 if(putenv(xyz) == -1) {
392                         free(xyz);
393                         return stralloc("[ERROR: failed to do putenv]");
394                 };
395         }
396         return stralloc("[ERROR: setenv failed because not enough "
397                                         "arguments were defined]");
398 }
399
400 /* rrd interface to the variable function putvar() */
401 char*
402 rrdsetvar(long argc, const char **args)
403 {
404         if (argc >= 2)
405         {
406                 const char* result = putvar(args[0], args[1], 0 /* not const */);
407                 if (result) {
408                         /* setvar does not return the value set */
409                         return stralloc("");
410                 }
411                 return stralloc("[ERROR: putvar failed]");
412         }
413         return stralloc("[ERROR: putvar failed because not enough arguments "
414                                         "were defined]");
415 }
416
417 /* rrd interface to the variable function putvar() */
418 char*
419 rrdsetvarconst(long argc, const char **args)
420 {
421         if (argc >= 2)
422         {
423                 const char* result = putvar(args[0], args[1], 1 /* const */);
424                 if (result) {
425                         /* setvar does not return the value set */
426                         return stralloc("");
427                 }
428                 return stralloc("[ERROR: putvar failed]");
429         }
430         return stralloc("[ERROR: putvar failed because not enough arguments "
431                                         "were defined]");
432 }
433
434 char* rrdgetenv(long argc, const char **args) {
435         char buf[128];
436         const char* envvar;
437         if (argc != 1) {
438                 return stralloc("[ERROR: getenv failed because it did not "
439                                                 "get 1 argument only]");
440         };
441         envvar = getenv(args[0]);
442         if (envvar) {
443                 return stralloc(envvar);
444         } else {
445 #ifdef WIN32
446                _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
447 #else
448                 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
449 #endif         
450                 return stralloc(buf);
451         }
452 }
453
454 char* rrdgetvar(long argc, const char **args) {
455         char buf[128];
456         const char* value;
457         if (argc != 1) {
458                 return stralloc("[ERROR: getvar failed because it did not "
459                                                 "get 1 argument only]");
460         };
461         value = getvar(args[0]);
462         if (value) {
463                 return stralloc(value);
464         } else {
465 #ifdef WIN32
466                _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
467 #else
468                 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
469 #endif
470                 return stralloc(buf);
471         }
472 }
473
474 char* rrdgoodfor(long argc, const char **args){
475   if (argc == 1) {
476       goodfor = atol(args[0]);
477   } else {
478     return stralloc("[ERROR: goodfor expected 1 argument]");
479   }
480    
481   if (goodfor == 0){
482      return stralloc("[ERROR: goodfor value must not be 0]");
483   }
484    
485   return stralloc("");
486 }
487
488 /* Format start or end times using strftime.  We always need both the
489  * start and end times, because, either might be relative to the other.
490  * */
491 #define MAX_STRFTIME_SIZE 256
492 char* printstrftime(long argc, const char **args){
493         struct  rrd_time_value start_tv, end_tv;
494         char    *parsetime_error = NULL;
495         char    formatted[MAX_STRFTIME_SIZE];
496         struct tm *the_tm;
497         time_t  start_tmp, end_tmp;
498
499         /* Make sure that we were given the right number of args */
500         if( argc != 4) {
501                 rrd_set_error( "wrong number of args %d", argc);
502                 return (char *) -1;
503         }
504
505         /* Init start and end time */
506         parsetime("end-24h", &start_tv);
507         parsetime("now", &end_tv);
508
509         /* Parse the start and end times we were given */
510         if( (parsetime_error = parsetime( args[1], &start_tv))) {
511                 rrd_set_error( "start time: %s", parsetime_error);
512                 return (char *) -1;
513         }
514         if( (parsetime_error = parsetime( args[2], &end_tv))) {
515                 rrd_set_error( "end time: %s", parsetime_error);
516                 return (char *) -1;
517         }
518         if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
519                 return (char *) -1;
520         }
521
522         /* Do we do the start or end */
523         if( strcasecmp( args[0], "START") == 0) {
524                 the_tm = localtime( &start_tmp);
525         }
526         else if( strcasecmp( args[0], "END") == 0) {
527                 the_tm = localtime( &end_tmp);
528         }
529         else {
530                 rrd_set_error( "start/end not found in '%s'", args[0]);
531                 return (char *) -1;
532         }
533
534         /* now format it */
535         if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
536                 return( stralloc( formatted));
537         }
538         else {
539                 rrd_set_error( "strftime failed");
540                 return (char *) -1;
541         }
542 }
543
544 char* includefile(long argc, const char **args){
545   char *buffer;
546   if (argc >= 1) {
547       char* filename = args[0];
548       readfile(filename, &buffer, 0);
549       if (rrd_test_error()) {
550                 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
551           sprintf(err, "[ERROR: %s]",rrd_get_error());
552           rrd_clear_error();
553           return err;
554       } else {
555        return buffer;
556       }
557   }
558   else
559   {
560       return stralloc("[ERROR: No Inclue file defined]");
561   }
562 }
563
564 /* make a copy of buf and replace open/close brackets with '_' */
565 char* rrdstrip(char *buf) {
566   char* p;
567   if (buf == NULL) {
568           return NULL;
569   }
570   /* make a copy of the buffer */
571   buf = stralloc(buf);
572   if (buf == NULL) {
573           return NULL;
574   }
575
576   p = buf;
577   while (*p) {
578           if (*p == '<' || *p == '>') {
579                   *p = '_';
580           }
581           p++;
582   }
583   return buf;
584 }
585
586 char* cgigetq(long argc, const char **args){
587   if (argc>= 1){
588     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
589     char *buf2;
590     char *c,*d;
591     int  qc=0;
592     if (buf==NULL) return NULL;
593
594     for(c=buf;*c != '\0';c++)
595       if (*c == '"') qc++;
596     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
597         perror("Malloc Buffer");
598         exit(1);
599     };
600     c=buf;
601     d=buf2;
602     *(d++) = '"';
603     while(*c != '\0'){
604         if (*c == '"') {
605             *(d++) = '"';
606             *(d++) = '\'';
607             *(d++) = '"';
608             *(d++) = '\'';
609         } 
610         *(d++) = *(c++);
611     }
612     *(d++) = '"';
613     *(d) = '\0';
614     free(buf);
615     return buf2;
616   }
617
618   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
619 }
620
621 /* remove occurrences of .. this is a general measure to make
622    paths which came in via cgi do not go UP ... */
623
624 char* cgigetqp(long argc, const char **args){
625        char* buf;
626     char* buf2;
627     char* p;
628         char* d;
629
630         if (argc < 1)
631         {
632                 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
633         }
634
635         buf = rrdstrip(cgiGetValue(cgiArg, args[0]));
636     if (!buf)
637         {
638                 return NULL;
639         }
640
641         buf2 = malloc(strlen(buf)+1);
642     if (!buf2)
643         {
644                 perror("cgigetqp(): Malloc Path Buffer");
645                 exit(1);
646     };
647
648     p = buf;
649     d = buf2;
650
651     while (*p)
652         {
653                 /* prevent mallicious paths from entering the system */
654                 if (p[0] == '.' && p[1] == '.')
655                 {
656                         p += 2;
657                         *d++ = '_';
658                         *d++ = '_';     
659                 }
660                 else
661                 {
662                         *d++ = *p++;
663                 }
664     }
665
666     *d = 0;
667     free(buf);
668
669     /* Make sure the path is relative, e.g. does not start with '/' */
670     p = buf2;
671     while ('/' == *p)
672         {
673             *p++ = '_';
674     }
675
676     return buf2;
677 }
678
679
680 char* cgiget(long argc, const char **args){
681   if (argc>= 1)
682     return rrdstrip(cgiGetValue(cgiArg,args[0]));
683   else
684     return stralloc("[ERROR: not enough arguments for RRD::CV]");
685 }
686
687
688
689 char* drawgraph(long argc, char **args){
690   int i,xsize, ysize;
691   for(i=0;i<argc;i++)
692     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
693   if(i==argc) {
694     args[argc++] = "--imginfo";
695     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
696   }
697   optind=0; /* reset gnu getopt */
698   opterr=0; /* reset gnu getopt */
699   calfree();
700   if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL) != -1 ) {
701     return stralloc(calcpr[0]);
702   } else {
703     if (rrd_test_error()) {
704       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
705       sprintf(err, "[ERROR: %s]",rrd_get_error());
706       rrd_clear_error();
707       calfree();
708       return err;
709     }
710   }
711   return NULL;
712 }
713
714 char* drawprint(long argc, const char **args){
715   if (argc==1 && calcpr){
716     long i=0;
717     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
718     if (atol(args[0])<i-1)
719       return stralloc(calcpr[atol(args[0])+1]);    
720   }
721   return stralloc("[ERROR: RRD::PRINT argument error]");
722 }
723
724 char* printtimelast(long argc, const char **args) {
725   time_t last;
726   struct tm tm_last;
727   char *buf;
728   if ( argc == 2 ) {
729     buf = malloc(255);
730     if (buf == NULL){   
731         return stralloc("[ERROR: allocating strftime buffer]");
732     };
733     last = rrd_last(argc+1, args-1); 
734     if (rrd_test_error()) {
735       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
736       sprintf(err, "[ERROR: %s]",rrd_get_error());
737       rrd_clear_error();
738       return err;
739     }
740     tm_last = *localtime(&last);
741     strftime(buf,254,args[1],&tm_last);
742     return buf;
743   }
744   if ( argc < 2 ) {
745     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
746   }
747   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
748 }
749
750 char* printtimenow(long argc, const char **args) {
751   time_t now = time(NULL);
752   struct tm tm_now;
753   char *buf;
754   if ( argc == 1 ) {
755     buf = malloc(255);
756     if (buf == NULL){   
757         return stralloc("[ERROR: allocating strftime buffer]");
758     };
759     tm_now = *localtime(&now);
760     strftime(buf,254,args[0],&tm_now);
761     return buf;
762   }
763   if ( argc < 1 ) {
764     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
765   }
766   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
767 }
768
769 /* Scan buffer until an unescaped '>' arives.
770  * Update argument array with arguments found.
771  * Return end cursor where parsing stopped, or NULL in case of failure.
772  *
773  * FIXME:
774  * To allow nested constructs, we call rrd_expand_vars() for arguments
775  * that contain RRD::x directives. These introduce a small memory leak
776  * since we have to stralloc the arguments the way parse() works.
777  */
778 char*
779 scanargs(char *line, int *argument_count, char ***arguments)
780 {
781         char    *getP;          /* read cursor */
782         char    *putP;          /* write cursor */
783         char    Quote;          /* type of quote if in quoted string, 0 otherwise */
784         int     tagcount;       /* open tag count */
785         int     in_arg;         /* if we currently are parsing an argument or not */
786         int     argsz;          /* argument array size */
787         int             curarg_contains_rrd_directives;
788
789         /* local array of arguments while parsing */
790         int argc = 0;
791         char** argv;
792
793 #ifdef DEBUG_PARSER
794         printf("<-- scanargs(%s) -->\n", line);
795 #endif
796
797         *arguments = NULL;
798         *argument_count = 0;
799
800         /* create initial argument array of char pointers */
801         argsz = 32;
802         argv = (char **)malloc(argsz * sizeof(char *));
803         if (!argv) {
804                 return NULL;
805         }
806
807         /* skip leading blanks */
808         while (isspace((int)*line)) {
809                 line++;
810         }
811
812         getP = line;
813         putP = line;
814
815         Quote    = 0;
816         in_arg   = 0;
817         tagcount = 0;
818
819         curarg_contains_rrd_directives = 0;
820
821         /* start parsing 'line' for arguments */
822         while (*getP)
823         {
824                 unsigned char c = *getP++;
825
826                 if (c == '>' && !Quote && !tagcount) {
827                         /* this is our closing tag, quit scanning */
828                         break;
829                 }
830
831                 /* remove all special chars */
832                 if (c < ' ') {
833                         c = ' ';
834                 }
835
836                 switch (c)
837                 {
838                 case ' ': 
839                         if (Quote || tagcount) {
840                                 /* copy quoted/tagged (=RRD expanded) string */
841                                 *putP++ = c;
842                         }
843                         else if (in_arg)
844                         {
845                                 /* end argument string */
846                                 *putP++ = 0;
847                                 in_arg = 0;
848                                 if (curarg_contains_rrd_directives) {
849                                         argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
850                                         curarg_contains_rrd_directives = 0;
851                                 }
852                         }
853                         break;
854
855                 case '"': /* Fall through */
856                 case '\'':
857                         if (Quote != 0) {
858                                 if (Quote == c) {
859                                         Quote = 0;
860                                 } else {
861                                         /* copy quoted string */
862                                         *putP++ = c;
863                                 }
864                         } else {
865                                 if (!in_arg) {
866                                         /* reference start of argument string in argument array */
867                                         argv[argc++] = putP;
868                                         in_arg=1;
869                                 }
870                                 Quote = c;
871                         }
872                         break;
873
874                 default:
875                                 if (!in_arg) {
876                                         /* start new argument */
877                                         argv[argc++] = putP;
878                                         in_arg = 1;
879                                 }
880                                 if (c == '>') {
881                                         if (tagcount) {
882                                                 tagcount--;
883                                         }
884                                 }
885                                 if (c == '<') {
886                                         tagcount++;
887                                         if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
888                                                 curarg_contains_rrd_directives = 1;
889                                         }
890                                 }
891                         *putP++ = c;
892                         break;
893                 }
894
895                 /* check if our argument array is still large enough */
896                 if (argc == argsz) {
897                         /* resize argument array */
898                         argsz *= 2;
899                         argv = rrd_realloc(argv, argsz * sizeof(char *));
900                         if (*argv == NULL) {
901                                 return NULL;
902                         }
903                 }
904         }
905
906         /* terminate last argument found */
907         *putP = '\0';
908         if (curarg_contains_rrd_directives) {
909                 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
910         }
911
912 #ifdef DEBUG_PARSER
913         if (argc > 0) {
914                 int n;
915                 printf("<-- arguments found [%d]\n", argc);
916                 for (n=0; n<argc; n++) {
917                         printf("arg %02d: '%s'\n", n, argv[n]);
918                 }
919                 printf("-->\n");
920         } else {
921                 printf("<!-- No arguments found -->\n");
922         }
923 #endif
924
925         /* update caller's notion of the argument array and it's size */
926         *arguments = argv;
927         *argument_count = argc;
928
929         if (Quote) {
930                 return NULL;
931         }
932
933         /* Return new scanning cursor:
934            pointer to char after closing bracket */
935         return getP;
936 }
937
938
939 /*
940  * Parse(): scan current portion of buffer for given tag.
941  * If found, parse tag arguments and call 'func' for it.
942  * The result of func is inserted at the current position
943  * in the buffer.
944  */
945 int
946 parse(
947         char **buf,     /* buffer */
948         long i,                 /* offset in buffer */
949         char *tag,      /* tag to handle  */
950         char *(*func)(long , const char **) /* function to call for 'tag' */
951         )
952 {
953         /* the name of the vairable ... */
954         char *val;
955         long valln;  
956         char **args;
957         char *end;
958         long end_offset;
959         int  argc;
960         size_t taglen = strlen(tag);
961
962         /* Current position in buffer should start with 'tag' */
963         if (strncmp((*buf)+i, tag, taglen) != 0) {
964                 return 0;
965         }
966         /* .. and match exactly (a whitespace following 'tag') */
967         if (! isspace(*((*buf) + i + taglen)) ) {
968                 return 0;
969         }
970
971 #ifdef DEBUG_PARSER
972         printf("parse(): handling tag '%s'\n", tag);
973 #endif
974
975         /* Scan for arguments following the tag;
976            scanargs() puts \0 into *buf ... so after scanargs it is probably
977            not a good time to use strlen on buf */
978         end = scanargs((*buf) + i + taglen, &argc, &args);
979         if (end)
980         {
981                 /* got arguments, call function for 'tag' with arguments */
982                 val = func(argc, args);
983                 free(args);
984         }
985         else
986         {
987                 /* unable to parse arguments, undo 0-termination by scanargs */
988                 for (; argc > 0; argc--) {
989                         *((args[argc-1])-1) = ' ';
990                 }
991
992                 /* next call, try parsing at current offset +1 */
993                 end = (*buf) + i + 1;
994
995                 val = stralloc("[ERROR: Parsing Problem with the following text\n"
996                                                 " Check original file. This may have been altered "
997                                                 "by parsing.]\n\n");
998         }
999
1000         /* remember offset where we have to continue parsing */
1001         end_offset = end - (*buf);
1002
1003         valln = 0;
1004         if (val) {
1005                 valln = strlen(val);
1006         }
1007
1008         /* Optionally resize buffer to hold the replacement value:
1009            Calculating the new length of the buffer is simple. add current
1010            buffer pos (i) to length of string after replaced tag to length
1011            of replacement string and add 1 for the final zero ... */
1012         if (end - (*buf) < (i + valln)) {
1013                 /* make sure we do not shrink the mallocd block */
1014                 size_t newbufsize = i + strlen(end) + valln + 1;
1015                 *buf = rrd_realloc(*buf, newbufsize);
1016
1017                 if (*buf == NULL) {
1018                         perror("Realoc buf:");
1019                         exit(1);
1020                 };
1021         }
1022
1023         /* Update new end pointer:
1024            make sure the 'end' pointer gets moved along with the 
1025            buf pointer when realloc moves memory ... */
1026         end = (*buf) + end_offset; 
1027
1028         /* splice the variable:
1029            step 1. Shift pending data to make room for 'val' */
1030         memmove((*buf) + i + valln, end, strlen(end) + 1);
1031
1032         /* step 2. Insert val */
1033         if (val) {
1034                 memmove((*buf)+i, val, valln);
1035                 free(val);
1036         }
1037         return (valln > 0 ? valln-1: valln);
1038 }
1039
1040 char *
1041 http_time(time_t *now) {
1042         struct tm *tmptime;
1043         static char buf[60];
1044
1045         tmptime=gmtime(now);
1046         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1047         return(buf);
1048 }