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