rrd_first code contributed by Burton Strauss <Burton@ntopSupport.com>
[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                 return stralloc("");
396         }
397         return stralloc("[ERROR: setenv failed because not enough "
398                                         "arguments were defined]");
399 }
400
401 /* rrd interface to the variable function putvar() */
402 char*
403 rrdsetvar(long argc, const char **args)
404 {
405         if (argc >= 2)
406         {
407                 const char* result = putvar(args[0], args[1], 0 /* not const */);
408                 if (result) {
409                         /* setvar does not return the value set */
410                         return stralloc("");
411                 }
412                 return stralloc("[ERROR: putvar failed]");
413         }
414         return stralloc("[ERROR: putvar failed because not enough arguments "
415                                         "were defined]");
416 }
417
418 /* rrd interface to the variable function putvar() */
419 char*
420 rrdsetvarconst(long argc, const char **args)
421 {
422         if (argc >= 2)
423         {
424                 const char* result = putvar(args[0], args[1], 1 /* const */);
425                 if (result) {
426                         /* setvar does not return the value set */
427                         return stralloc("");
428                 }
429                 return stralloc("[ERROR: putvar failed]");
430         }
431         return stralloc("[ERROR: putvar failed because not enough arguments "
432                                         "were defined]");
433 }
434
435 char* rrdgetenv(long argc, const char **args) {
436         char buf[128];
437         const char* envvar;
438         if (argc != 1) {
439                 return stralloc("[ERROR: getenv failed because it did not "
440                                                 "get 1 argument only]");
441         };
442         envvar = getenv(args[0]);
443         if (envvar) {
444                 return stralloc(envvar);
445         } else {
446 #ifdef WIN32
447                _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
448 #else
449                 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
450 #endif         
451                 return stralloc(buf);
452         }
453 }
454
455 char* rrdgetvar(long argc, const char **args) {
456         char buf[128];
457         const char* value;
458         if (argc != 1) {
459                 return stralloc("[ERROR: getvar failed because it did not "
460                                                 "get 1 argument only]");
461         };
462         value = getvar(args[0]);
463         if (value) {
464                 return stralloc(value);
465         } else {
466 #ifdef WIN32
467                _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
468 #else
469                 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
470 #endif
471                 return stralloc(buf);
472         }
473 }
474
475 char* rrdgoodfor(long argc, const char **args){
476   if (argc == 1) {
477       goodfor = atol(args[0]);
478   } else {
479     return stralloc("[ERROR: goodfor expected 1 argument]");
480   }
481    
482   if (goodfor == 0){
483      return stralloc("[ERROR: goodfor value must not be 0]");
484   }
485    
486   return stralloc("");
487 }
488
489 /* Format start or end times using strftime.  We always need both the
490  * start and end times, because, either might be relative to the other.
491  * */
492 #define MAX_STRFTIME_SIZE 256
493 char* printstrftime(long argc, const char **args){
494         struct  rrd_time_value start_tv, end_tv;
495         char    *parsetime_error = NULL;
496         char    formatted[MAX_STRFTIME_SIZE];
497         struct tm *the_tm;
498         time_t  start_tmp, end_tmp;
499
500         /* Make sure that we were given the right number of args */
501         if( argc != 4) {
502                 rrd_set_error( "wrong number of args %d", argc);
503                 return (char *) -1;
504         }
505
506         /* Init start and end time */
507         parsetime("end-24h", &start_tv);
508         parsetime("now", &end_tv);
509
510         /* Parse the start and end times we were given */
511         if( (parsetime_error = parsetime( args[1], &start_tv))) {
512                 rrd_set_error( "start time: %s", parsetime_error);
513                 return (char *) -1;
514         }
515         if( (parsetime_error = parsetime( args[2], &end_tv))) {
516                 rrd_set_error( "end time: %s", parsetime_error);
517                 return (char *) -1;
518         }
519         if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
520                 return (char *) -1;
521         }
522
523         /* Do we do the start or end */
524         if( strcasecmp( args[0], "START") == 0) {
525                 the_tm = localtime( &start_tmp);
526         }
527         else if( strcasecmp( args[0], "END") == 0) {
528                 the_tm = localtime( &end_tmp);
529         }
530         else {
531                 rrd_set_error( "start/end not found in '%s'", args[0]);
532                 return (char *) -1;
533         }
534
535         /* now format it */
536         if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
537                 return( stralloc( formatted));
538         }
539         else {
540                 rrd_set_error( "strftime failed");
541                 return (char *) -1;
542         }
543 }
544
545 char* includefile(long argc, const char **args){
546   char *buffer;
547   if (argc >= 1) {
548       char* filename = args[0];
549       readfile(filename, &buffer, 0);
550       if (rrd_test_error()) {
551                 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
552           sprintf(err, "[ERROR: %s]",rrd_get_error());
553           rrd_clear_error();
554           return err;
555       } else {
556        return buffer;
557       }
558   }
559   else
560   {
561       return stralloc("[ERROR: No Inclue file defined]");
562   }
563 }
564
565 /* make a copy of buf and replace open/close brackets with '_' */
566 char* rrdstrip(char *buf) {
567   char* p;
568   if (buf == NULL) {
569           return NULL;
570   }
571   /* make a copy of the buffer */
572   buf = stralloc(buf);
573   if (buf == NULL) {
574           return NULL;
575   }
576
577   p = buf;
578   while (*p) {
579           if (*p == '<' || *p == '>') {
580                   *p = '_';
581           }
582           p++;
583   }
584   return buf;
585 }
586
587 char* cgigetq(long argc, const char **args){
588   if (argc>= 1){
589     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
590     char *buf2;
591     char *c,*d;
592     int  qc=0;
593     if (buf==NULL) return NULL;
594
595     for(c=buf;*c != '\0';c++)
596       if (*c == '"') qc++;
597     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
598         perror("Malloc Buffer");
599         exit(1);
600     };
601     c=buf;
602     d=buf2;
603     *(d++) = '"';
604     while(*c != '\0'){
605         if (*c == '"') {
606             *(d++) = '"';
607             *(d++) = '\'';
608             *(d++) = '"';
609             *(d++) = '\'';
610         } 
611         *(d++) = *(c++);
612     }
613     *(d++) = '"';
614     *(d) = '\0';
615     free(buf);
616     return buf2;
617   }
618
619   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
620 }
621
622 /* remove occurrences of .. this is a general measure to make
623    paths which came in via cgi do not go UP ... */
624
625 char* cgigetqp(long argc, const char **args){
626        char* buf;
627     char* buf2;
628     char* p;
629         char* d;
630
631         if (argc < 1)
632         {
633                 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
634         }
635
636         buf = rrdstrip(cgiGetValue(cgiArg, args[0]));
637     if (!buf)
638         {
639                 return NULL;
640         }
641
642         buf2 = malloc(strlen(buf)+1);
643     if (!buf2)
644         {
645                 perror("cgigetqp(): Malloc Path Buffer");
646                 exit(1);
647     };
648
649     p = buf;
650     d = buf2;
651
652     while (*p)
653         {
654                 /* prevent mallicious paths from entering the system */
655                 if (p[0] == '.' && p[1] == '.')
656                 {
657                         p += 2;
658                         *d++ = '_';
659                         *d++ = '_';     
660                 }
661                 else
662                 {
663                         *d++ = *p++;
664                 }
665     }
666
667     *d = 0;
668     free(buf);
669
670     /* Make sure the path is relative, e.g. does not start with '/' */
671     p = buf2;
672     while ('/' == *p)
673         {
674             *p++ = '_';
675     }
676
677     return buf2;
678 }
679
680
681 char* cgiget(long argc, const char **args){
682   if (argc>= 1)
683     return rrdstrip(cgiGetValue(cgiArg,args[0]));
684   else
685     return stralloc("[ERROR: not enough arguments for RRD::CV]");
686 }
687
688
689
690 char* drawgraph(long argc, char **args){
691   int i,xsize, ysize;
692   double ymin,ymax;
693   for(i=0;i<argc;i++)
694     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
695   if(i==argc) {
696     args[argc++] = "--imginfo";
697     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
698   }
699   optind=0; /* reset gnu getopt */
700   opterr=0; /* reset gnu getopt */
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 }