let rrd_graph return the actual value range it picked ...
[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   double ymin,ymax;
692   for(i=0;i<argc;i++)
693     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
694   if(i==argc) {
695     args[argc++] = "--imginfo";
696     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
697   }
698   optind=0; /* reset gnu getopt */
699   opterr=0; /* reset gnu getopt */
700   calfree();
701   if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL,&ymin,&ymax) != -1 ) {
702     return stralloc(calcpr[0]);
703   } else {
704     if (rrd_test_error()) {
705       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
706       sprintf(err, "[ERROR: %s]",rrd_get_error());
707       rrd_clear_error();
708       calfree();
709       return err;
710     }
711   }
712   return NULL;
713 }
714
715 char* drawprint(long argc, const char **args){
716   if (argc==1 && calcpr){
717     long i=0;
718     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
719     if (atol(args[0])<i-1)
720       return stralloc(calcpr[atol(args[0])+1]);    
721   }
722   return stralloc("[ERROR: RRD::PRINT argument error]");
723 }
724
725 char* printtimelast(long argc, const char **args) {
726   time_t last;
727   struct tm tm_last;
728   char *buf;
729   if ( argc == 2 ) {
730     buf = malloc(255);
731     if (buf == NULL){   
732         return stralloc("[ERROR: allocating strftime buffer]");
733     };
734     last = rrd_last(argc+1, args-1); 
735     if (rrd_test_error()) {
736       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
737       sprintf(err, "[ERROR: %s]",rrd_get_error());
738       rrd_clear_error();
739       return err;
740     }
741     tm_last = *localtime(&last);
742     strftime(buf,254,args[1],&tm_last);
743     return buf;
744   }
745   if ( argc < 2 ) {
746     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
747   }
748   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
749 }
750
751 char* printtimenow(long argc, const char **args) {
752   time_t now = time(NULL);
753   struct tm tm_now;
754   char *buf;
755   if ( argc == 1 ) {
756     buf = malloc(255);
757     if (buf == NULL){   
758         return stralloc("[ERROR: allocating strftime buffer]");
759     };
760     tm_now = *localtime(&now);
761     strftime(buf,254,args[0],&tm_now);
762     return buf;
763   }
764   if ( argc < 1 ) {
765     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
766   }
767   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
768 }
769
770 /* Scan buffer until an unescaped '>' arives.
771  * Update argument array with arguments found.
772  * Return end cursor where parsing stopped, or NULL in case of failure.
773  *
774  * FIXME:
775  * To allow nested constructs, we call rrd_expand_vars() for arguments
776  * that contain RRD::x directives. These introduce a small memory leak
777  * since we have to stralloc the arguments the way parse() works.
778  */
779 char*
780 scanargs(char *line, int *argument_count, char ***arguments)
781 {
782         char    *getP;          /* read cursor */
783         char    *putP;          /* write cursor */
784         char    Quote;          /* type of quote if in quoted string, 0 otherwise */
785         int     tagcount;       /* open tag count */
786         int     in_arg;         /* if we currently are parsing an argument or not */
787         int     argsz;          /* argument array size */
788         int             curarg_contains_rrd_directives;
789
790         /* local array of arguments while parsing */
791         int argc = 0;
792         char** argv;
793
794 #ifdef DEBUG_PARSER
795         printf("<-- scanargs(%s) -->\n", line);
796 #endif
797
798         *arguments = NULL;
799         *argument_count = 0;
800
801         /* create initial argument array of char pointers */
802         argsz = 32;
803         argv = (char **)malloc(argsz * sizeof(char *));
804         if (!argv) {
805                 return NULL;
806         }
807
808         /* skip leading blanks */
809         while (isspace((int)*line)) {
810                 line++;
811         }
812
813         getP = line;
814         putP = line;
815
816         Quote    = 0;
817         in_arg   = 0;
818         tagcount = 0;
819
820         curarg_contains_rrd_directives = 0;
821
822         /* start parsing 'line' for arguments */
823         while (*getP)
824         {
825                 unsigned char c = *getP++;
826
827                 if (c == '>' && !Quote && !tagcount) {
828                         /* this is our closing tag, quit scanning */
829                         break;
830                 }
831
832                 /* remove all special chars */
833                 if (c < ' ') {
834                         c = ' ';
835                 }
836
837                 switch (c)
838                 {
839                 case ' ': 
840                         if (Quote || tagcount) {
841                                 /* copy quoted/tagged (=RRD expanded) string */
842                                 *putP++ = c;
843                         }
844                         else if (in_arg)
845                         {
846                                 /* end argument string */
847                                 *putP++ = 0;
848                                 in_arg = 0;
849                                 if (curarg_contains_rrd_directives) {
850                                         argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
851                                         curarg_contains_rrd_directives = 0;
852                                 }
853                         }
854                         break;
855
856                 case '"': /* Fall through */
857                 case '\'':
858                         if (Quote != 0) {
859                                 if (Quote == c) {
860                                         Quote = 0;
861                                 } else {
862                                         /* copy quoted string */
863                                         *putP++ = c;
864                                 }
865                         } else {
866                                 if (!in_arg) {
867                                         /* reference start of argument string in argument array */
868                                         argv[argc++] = putP;
869                                         in_arg=1;
870                                 }
871                                 Quote = c;
872                         }
873                         break;
874
875                 default:
876                                 if (!in_arg) {
877                                         /* start new argument */
878                                         argv[argc++] = putP;
879                                         in_arg = 1;
880                                 }
881                                 if (c == '>') {
882                                         if (tagcount) {
883                                                 tagcount--;
884                                         }
885                                 }
886                                 if (c == '<') {
887                                         tagcount++;
888                                         if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
889                                                 curarg_contains_rrd_directives = 1;
890                                         }
891                                 }
892                         *putP++ = c;
893                         break;
894                 }
895
896                 /* check if our argument array is still large enough */
897                 if (argc == argsz) {
898                         /* resize argument array */
899                         argsz *= 2;
900                         argv = rrd_realloc(argv, argsz * sizeof(char *));
901                         if (*argv == NULL) {
902                                 return NULL;
903                         }
904                 }
905         }
906
907         /* terminate last argument found */
908         *putP = '\0';
909         if (curarg_contains_rrd_directives) {
910                 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
911         }
912
913 #ifdef DEBUG_PARSER
914         if (argc > 0) {
915                 int n;
916                 printf("<-- arguments found [%d]\n", argc);
917                 for (n=0; n<argc; n++) {
918                         printf("arg %02d: '%s'\n", n, argv[n]);
919                 }
920                 printf("-->\n");
921         } else {
922                 printf("<!-- No arguments found -->\n");
923         }
924 #endif
925
926         /* update caller's notion of the argument array and it's size */
927         *arguments = argv;
928         *argument_count = argc;
929
930         if (Quote) {
931                 return NULL;
932         }
933
934         /* Return new scanning cursor:
935            pointer to char after closing bracket */
936         return getP;
937 }
938
939
940 /*
941  * Parse(): scan current portion of buffer for given tag.
942  * If found, parse tag arguments and call 'func' for it.
943  * The result of func is inserted at the current position
944  * in the buffer.
945  */
946 int
947 parse(
948         char **buf,     /* buffer */
949         long i,                 /* offset in buffer */
950         char *tag,      /* tag to handle  */
951         char *(*func)(long , const char **) /* function to call for 'tag' */
952         )
953 {
954         /* the name of the vairable ... */
955         char *val;
956         long valln;  
957         char **args;
958         char *end;
959         long end_offset;
960         int  argc;
961         size_t taglen = strlen(tag);
962
963         /* Current position in buffer should start with 'tag' */
964         if (strncmp((*buf)+i, tag, taglen) != 0) {
965                 return 0;
966         }
967         /* .. and match exactly (a whitespace following 'tag') */
968         if (! isspace(*((*buf) + i + taglen)) ) {
969                 return 0;
970         }
971
972 #ifdef DEBUG_PARSER
973         printf("parse(): handling tag '%s'\n", tag);
974 #endif
975
976         /* Scan for arguments following the tag;
977            scanargs() puts \0 into *buf ... so after scanargs it is probably
978            not a good time to use strlen on buf */
979         end = scanargs((*buf) + i + taglen, &argc, &args);
980         if (end)
981         {
982                 /* got arguments, call function for 'tag' with arguments */
983                 val = func(argc, args);
984                 free(args);
985         }
986         else
987         {
988                 /* unable to parse arguments, undo 0-termination by scanargs */
989                 for (; argc > 0; argc--) {
990                         *((args[argc-1])-1) = ' ';
991                 }
992
993                 /* next call, try parsing at current offset +1 */
994                 end = (*buf) + i + 1;
995
996                 val = stralloc("[ERROR: Parsing Problem with the following text\n"
997                                                 " Check original file. This may have been altered "
998                                                 "by parsing.]\n\n");
999         }
1000
1001         /* remember offset where we have to continue parsing */
1002         end_offset = end - (*buf);
1003
1004         valln = 0;
1005         if (val) {
1006                 valln = strlen(val);
1007         }
1008
1009         /* Optionally resize buffer to hold the replacement value:
1010            Calculating the new length of the buffer is simple. add current
1011            buffer pos (i) to length of string after replaced tag to length
1012            of replacement string and add 1 for the final zero ... */
1013         if (end - (*buf) < (i + valln)) {
1014                 /* make sure we do not shrink the mallocd block */
1015                 size_t newbufsize = i + strlen(end) + valln + 1;
1016                 *buf = rrd_realloc(*buf, newbufsize);
1017
1018                 if (*buf == NULL) {
1019                         perror("Realoc buf:");
1020                         exit(1);
1021                 };
1022         }
1023
1024         /* Update new end pointer:
1025            make sure the 'end' pointer gets moved along with the 
1026            buf pointer when realloc moves memory ... */
1027         end = (*buf) + end_offset; 
1028
1029         /* splice the variable:
1030            step 1. Shift pending data to make room for 'val' */
1031         memmove((*buf) + i + valln, end, strlen(end) + 1);
1032
1033         /* step 2. Insert val */
1034         if (val) {
1035                 memmove((*buf)+i, val, valln);
1036                 free(val);
1037         }
1038         return (valln > 0 ? valln-1: valln);
1039 }
1040
1041 char *
1042 http_time(time_t *now) {
1043         struct tm *tmptime;
1044         static char buf[60];
1045
1046         tmptime=gmtime(now);
1047         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1048         return(buf);
1049 }