prepare for the release of rrdtool-1.2.12
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.2.12  Copyright by Tobi Oetiker, 1997-2005
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8
9
10 #define MEMBLK 1024
11 /*#define DEBUG_PARSER
12 #define DEBUG_VARS*/
13
14 typedef struct var_s {
15         char    *name, *value;
16 } s_var;
17
18 typedef struct cgi_s {
19         s_var **vars;
20 } s_cgi;
21
22 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
23    and replace by result of arg[2] call */
24 int parse(char **, long, char *, char *(*)(long , const char **));
25
26 /**************************************************/
27 /* tag replacers ... they are called from parse   */
28 /* through function pointers                      */
29 /**************************************************/
30
31 /* return cgi var named arg[0] */ 
32 char* cgiget(long , const char **);
33
34 /* return a quoted cgi var named arg[0] */ 
35 char* cgigetq(long , const char **);
36
37 /* return a quoted and sanitized cgi variable */
38 char* cgigetqp(long , const char **);
39
40 /* call rrd_graph and insert appropriate image tag */
41 char* drawgraph(long, const char **);
42
43 /* return PRINT functions from last rrd_graph call */
44 char* drawprint(long, const char **);
45
46 /* pretty-print the <last></last> value for some.rrd via strftime() */
47 char* printtimelast(long, const char **);
48
49 /* pretty-print current time */
50 char* printtimenow(long, const char **);
51
52 /* set an environment variable */
53 char* rrdsetenv(long, const char **);
54
55 /* get an environment variable */
56 char* rrdgetenv(long, const char **);
57
58 /* include the named file at this point */
59 char* includefile(long, const char **);
60
61 /* for how long is the output of the cgi valid ? */
62 char* rrdgoodfor(long, const char **);
63
64 char* rrdstrip(char *buf);
65 char* scanargs(char *line, int *argc, char ***args);
66
67 /* format at-time specified times using strftime */
68 char* printstrftime(long, const char**);
69
70 /** HTTP protocol needs special format, and GMT time **/
71 char *http_time(time_t *);
72
73 /* return a pointer to newly allocated copy of this string */
74 char *stralloc(const char *);
75
76 /* global variable for rrdcgi */
77 s_cgi *rrdcgiArg;
78
79 /* rrdcgiHeader
80  * 
81  *  Prints a valid CGI Header (Content-type...) etc.
82  */
83 void rrdcgiHeader(void);
84
85 /* rrdcgiDecodeString
86  * decode html escapes
87  */
88  
89 char *rrdcgiDecodeString(char *text);
90
91 /* rrdcgiDebug
92  * 
93  *  Set/unsets debugging
94  */
95 void rrdcgiDebug(int level, int where);
96
97 /* rrdcgiInit
98  *
99  *  Reads in variables set via POST or stdin.
100  */
101 s_cgi *rrdcgiInit (void);
102
103 /* rrdcgiGetValue
104  *
105  *  Returns the value of the specified variable or NULL if it's empty
106  *  or doesn't exist.
107  */
108 char *rrdcgiGetValue (s_cgi *parms, const char *name);
109
110 /* rrdcgiFreeList
111  *
112  * Frees a list as returned by rrdcgiGetVariables()
113  */
114 void rrdcgiFreeList (char **list);
115
116 /* rrdcgiFree
117  *
118  * Frees the internal data structures
119  */
120 void rrdcgiFree (s_cgi *parms);
121
122 /*  rrdcgiReadVariables()
123  *
124  *  Read from stdin if no string is provided via CGI.  Variables that
125  *  doesn't have a value associated with it doesn't get stored.
126  */
127 s_var **rrdcgiReadVariables(void);
128
129
130 int rrdcgiDebugLevel = 0;
131 int rrdcgiDebugStderr = 1;
132 char *rrdcgiHeaderString = NULL;
133 char *rrdcgiType = NULL;
134
135 /* rrd interface to the variable functions {put,get}var() */
136 char* rrdgetvar(long argc, const char **args);
137 char* rrdsetvar(long argc, const char **args);
138 char* rrdsetvarconst(long argc, const char **args);
139
140
141 /* variable store: put/get key-value pairs */
142 static int   initvar();
143 static void  donevar();
144 static const char* getvar(const char* varname);
145 static const char* putvar(const char* name, const char* value, int is_const);
146
147 /* key value pair that makes up an entry in the variable store */
148 typedef struct
149 {
150         int is_const;           /* const variable or not */
151         const char* name;       /* variable name */
152         const char* value;      /* variable value */
153 } vardata;
154
155 /* the variable heap: 
156    start with a heapsize of 10 variables */
157 #define INIT_VARSTORE_SIZE      10
158 static vardata* varheap    = NULL;
159 static size_t varheap_size = 0;
160
161 /* allocate and initialize variable heap */
162 static int
163 initvar()
164 {
165         varheap = (vardata*)malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
166         if (varheap == NULL) {
167                 fprintf(stderr, "ERROR: unable to initialize variable store\n");
168                 return -1;
169         }
170         memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
171         varheap_size = INIT_VARSTORE_SIZE;
172         return 0;
173 }
174
175 /* cleanup: free allocated memory */
176 static void
177 donevar()
178 {
179         int i;
180         if (varheap) {
181                 for (i=0; i<(int)varheap_size; i++) {
182                         if (varheap[i].name) {
183                                 free((char*)varheap[i].name);
184                         }
185                         if (varheap[i].value) {
186                                 free((char*)varheap[i].value);
187                         }
188                 }
189                 free(varheap);
190         }
191 }
192
193 /* Get a variable from the variable store.
194    Return NULL in case the requested variable was not found. */
195 static const char*
196 getvar(const char* name)
197 {
198         int i;
199         for (i=0; i<(int)varheap_size && varheap[i].name; i++) {
200                 if (0 == strcmp(name, varheap[i].name)) {
201 #ifdef          DEBUG_VARS
202                         printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
203 #endif
204                         return varheap[i].value;
205                 }
206         }
207 #ifdef DEBUG_VARS
208         printf("<!-- getvar(%s) -> Not found-->\n", name);
209 #endif
210         return NULL;
211 }
212
213 /* Put a variable into the variable store. If a variable by that
214    name exists, it's value is overwritten with the new value unless it was
215    marked as 'const' (initialized by RRD::SETCONSTVAR).
216    Returns a copy the newly allocated value on success, NULL on error. */
217 static const char*
218 putvar(const char* name, const char* value, int is_const)
219 {
220         int i;
221         for (i=0; i < (int)varheap_size && varheap[i].name; i++) {
222                 if (0 == strcmp(name, varheap[i].name)) {
223                         /* overwrite existing entry */
224                         if (varheap[i].is_const) {
225 #ifdef                  DEBUG_VARS
226                                 printf("<!-- setver(%s, %s): not assigning: "
227                                                 "const variable -->\n", name, value);
228 #                               endif
229                                 return varheap[i].value;
230                         }
231 #ifdef          DEBUG_VARS
232                         printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
233                                         name, value, varheap[i].value);
234 #endif
235                         /* make it possible to promote a variable to readonly */
236                         varheap[i].is_const = is_const;
237                         free((char*)varheap[i].value);
238                         varheap[i].value = stralloc(value);
239                         return varheap[i].value;
240                 }
241         }
242
243         /* no existing variable found by that name, add it */
244         if (i == (int)varheap_size) {
245                 /* ran out of heap: resize heap to double size */
246                 size_t new_size = varheap_size * 2;
247                 varheap = (vardata*)(realloc(varheap, sizeof(vardata) * new_size));
248                 if (!varheap) {
249                         fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
250                         return NULL;
251                 }
252                 /* initialize newly allocated memory */;
253                 memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
254                 varheap_size = new_size;
255         }
256         varheap[i].is_const = is_const;
257         varheap[i].name  = stralloc(name);
258         varheap[i].value = stralloc(value);
259
260 #ifdef          DEBUG_VARS
261         printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
262 #endif
263         return varheap[i].value;
264 }
265
266 /* expand those RRD:* directives that can be used recursivly */
267 static char*
268 rrd_expand_vars(char* buffer)
269 {
270         int i;
271
272 #ifdef DEBUG_PARSER
273         printf("expanding variables in '%s'\n", buffer);
274 #endif
275
276         for (i=0; buffer[i]; i++) {
277                 if (buffer[i] != '<')
278                         continue;
279                 parse(&buffer, i, "<RRD::CV", cgiget);
280                 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
281                 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
282                 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);    
283                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);    
284                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
285                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
286                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
287         }
288         return buffer;
289 }
290
291 static long goodfor=0;
292 static char **calcpr=NULL;
293 static void calfree (void){
294   if (calcpr) {
295     long i;
296     for(i=0;calcpr[i];i++){
297       if (calcpr[i]){
298               free(calcpr[i]);
299       }
300     } 
301     if (calcpr) {
302             free(calcpr);
303     }
304   }
305 }
306
307 /* create freeable version of the string */
308 char * stralloc(const char *str){
309   char* nstr;
310   if (!str) {
311           return NULL;
312   }
313   nstr = malloc((strlen(str)+1));
314   strcpy(nstr,str);
315   return(nstr);
316 }
317
318 int main(int argc, char *argv[]) {
319         long length;
320         char *buffer;
321         char *server_url = NULL;
322         long i;
323         long filter=0;
324 #ifdef MUST_DISABLE_SIGFPE
325         signal(SIGFPE,SIG_IGN);
326 #endif
327 #ifdef MUST_DISABLE_FPMASK
328         fpsetmask(0);
329 #endif
330         optind = 0; opterr = 0;  /* initialize getopt */
331
332         /* what do we get for cmdline arguments?
333         for (i=0;i<argc;i++)
334         printf("%d-'%s'\n",i,argv[i]); */
335         while (1) {
336                 static struct option long_options[] = {
337                         { "filter", no_argument, 0, 'f' },
338                         { 0, 0, 0, 0}
339                 };
340                 int option_index = 0;
341                 int opt;
342                 opt = getopt_long(argc, argv, "f", long_options, &option_index);
343                 if (opt == EOF) {
344                         break;
345                 }
346
347                 switch(opt) {
348                 case 'f':
349                                 filter=1;
350                         break;
351                 case '?':
352                         printf("unknown commandline option '%s'\n",argv[optind-1]);
353                         return -1;
354                 }
355         }
356
357         if (!filter) {
358                 rrdcgiDebug(0,0);
359                 rrdcgiArg = rrdcgiInit();
360                 server_url = getenv("SERVER_URL");
361         }
362
363         /* make sure we have one extra argument, 
364            if there are others, we do not care Apache gives several */
365
366         /* if ( (optind != argc-2 
367            && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
368            && optind != argc-1) { */
369
370         if ( optind >= argc ) { 
371                 fprintf(stderr, "ERROR: expected a filename\n");
372                 exit(1);
373         } else {
374                 length = readfile(argv[optind], &buffer, 1);
375         }
376
377         if(rrd_test_error()) {
378                 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
379                 exit(1);
380         }
381
382         /* initialize variable heap */
383         initvar();
384
385 #ifdef DEBUG_PARSER
386        /* some fake header for testing */
387        printf ("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
388 #endif
389
390
391         /* expand rrd directives in buffer recursivly */
392         for (i=0; buffer[i]; i++) {
393                 if (buffer[i] != '<')
394                         continue;
395                 if (!filter) {
396                         parse(&buffer, i, "<RRD::CV", cgiget);
397                         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
398                         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
399                         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
400                 }
401                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
402                 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
403                 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
404                 parse(&buffer, i, "<RRD::INCLUDE", includefile);
405                 parse(&buffer, i, "<RRD::PRINT", drawprint);
406                 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
407                 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
408                 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
409                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
410                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
411                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
412         }
413
414         if (!filter) {
415                 printf ("Content-Type: text/html\n" 
416                                 "Content-Length: %d\n", 
417                                 strlen(buffer));
418
419                 if (labs(goodfor) > 0) {
420                         time_t now;
421                         now = time(NULL);
422                         printf("Last-Modified: %s\n", http_time(&now));
423                         now += labs(goodfor);
424                         printf("Expires: %s\n", http_time(&now));
425                         if (goodfor < 0) {
426                                 printf("Refresh: %ld\n", labs(goodfor));
427                         }
428                 }
429                 printf("\n");
430         }
431
432         /* output result */
433         printf("%s", buffer);
434
435         /* cleanup */
436         calfree();
437         if (buffer){
438                 free(buffer);
439         }
440         donevar();
441         exit(0);
442 }
443
444 /* remove occurrences of .. this is a general measure to make
445    paths which came in via cgi do not go UP ... */
446
447 char* rrdsetenv(long argc, const char **args) {
448         if (argc >= 2) {
449                 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
450                 if (xyz == NULL) {
451                         return stralloc("[ERROR: allocating setenv buffer]");
452                 };
453                 sprintf(xyz, "%s=%s", args[0], args[1]);
454                 if(putenv(xyz) == -1) {
455                         free(xyz);
456                         return stralloc("[ERROR: failed to do putenv]");
457                 };
458                 return stralloc("");
459         }
460         return stralloc("[ERROR: setenv failed because not enough "
461                                         "arguments were defined]");
462 }
463
464 /* rrd interface to the variable function putvar() */
465 char*
466 rrdsetvar(long argc, const char **args)
467 {
468         if (argc >= 2)
469         {
470                 const char* result = putvar(args[0], args[1], 0 /* not const */);
471                 if (result) {
472                         /* setvar does not return the value set */
473                         return stralloc("");
474                 }
475                 return stralloc("[ERROR: putvar failed]");
476         }
477         return stralloc("[ERROR: putvar failed because not enough arguments "
478                                         "were defined]");
479 }
480
481 /* rrd interface to the variable function putvar() */
482 char*
483 rrdsetvarconst(long argc, const char **args)
484 {
485         if (argc >= 2)
486         {
487                 const char* result = putvar(args[0], args[1], 1 /* const */);
488                 if (result) {
489                         /* setvar does not return the value set */
490                         return stralloc("");
491                 }
492                 return stralloc("[ERROR: putvar failed]");
493         }
494         return stralloc("[ERROR: putvar failed because not enough arguments "
495                                         "were defined]");
496 }
497
498 char* rrdgetenv(long argc, const char **args) {
499         char buf[128];
500         const char* envvar;
501         if (argc != 1) {
502                 return stralloc("[ERROR: getenv failed because it did not "
503                                                 "get 1 argument only]");
504         };
505         envvar = getenv(args[0]);
506         if (envvar) {
507                 return stralloc(envvar);
508         } else {
509 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
510                _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
511 #else
512                 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
513 #endif         
514                 return stralloc(buf);
515         }
516 }
517
518 char* rrdgetvar(long argc, const char **args) {
519         char buf[128];
520         const char* value;
521         if (argc != 1) {
522                 return stralloc("[ERROR: getvar failed because it did not "
523                                                 "get 1 argument only]");
524         };
525         value = getvar(args[0]);
526         if (value) {
527                 return stralloc(value);
528         } else {
529 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
530                _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
531 #else
532                 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
533 #endif
534                 return stralloc(buf);
535         }
536 }
537
538 char* rrdgoodfor(long argc, const char **args){
539   if (argc == 1) {
540       goodfor = atol(args[0]);
541   } else {
542     return stralloc("[ERROR: goodfor expected 1 argument]");
543   }
544    
545   if (goodfor == 0){
546      return stralloc("[ERROR: goodfor value must not be 0]");
547   }
548    
549   return stralloc("");
550 }
551
552 /* Format start or end times using strftime.  We always need both the
553  * start and end times, because, either might be relative to the other.
554  * */
555 #define MAX_STRFTIME_SIZE 256
556 char* printstrftime(long argc, const char **args){
557         struct  rrd_time_value start_tv, end_tv;
558         char    *parsetime_error = NULL;
559         char    formatted[MAX_STRFTIME_SIZE];
560         struct tm *the_tm;
561         time_t  start_tmp, end_tmp;
562
563         /* Make sure that we were given the right number of args */
564         if( argc != 4) {
565                 rrd_set_error( "wrong number of args %d", argc);
566                 return (char *) -1;
567         }
568
569         /* Init start and end time */
570         parsetime("end-24h", &start_tv);
571         parsetime("now", &end_tv);
572
573         /* Parse the start and end times we were given */
574         if( (parsetime_error = parsetime( args[1], &start_tv))) {
575                 rrd_set_error( "start time: %s", parsetime_error);
576                 return (char *) -1;
577         }
578         if( (parsetime_error = parsetime( args[2], &end_tv))) {
579                 rrd_set_error( "end time: %s", parsetime_error);
580                 return (char *) -1;
581         }
582         if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
583                 return (char *) -1;
584         }
585
586         /* Do we do the start or end */
587         if( strcasecmp( args[0], "START") == 0) {
588                 the_tm = localtime( &start_tmp);
589         }
590         else if( strcasecmp( args[0], "END") == 0) {
591                 the_tm = localtime( &end_tmp);
592         }
593         else {
594                 rrd_set_error( "start/end not found in '%s'", args[0]);
595                 return (char *) -1;
596         }
597
598         /* now format it */
599         if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
600                 return( stralloc( formatted));
601         }
602         else {
603                 rrd_set_error( "strftime failed");
604                 return (char *) -1;
605         }
606 }
607
608 char* includefile(long argc, const char **args){
609   char *buffer;
610   if (argc >= 1) {
611       const char* filename = args[0];
612       readfile(filename, &buffer, 0);
613       if (rrd_test_error()) {
614                 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
615           sprintf(err, "[ERROR: %s]",rrd_get_error());
616           rrd_clear_error();
617           return err;
618       } else {
619        return buffer;
620       }
621   }
622   else
623   {
624       return stralloc("[ERROR: No Inclue file defined]");
625   }
626 }
627
628 /* make a copy of buf and replace open/close brackets with '_' */
629 char* rrdstrip(char *buf) {
630   char* p;
631   if (buf == NULL) {
632           return NULL;
633   }
634   /* make a copy of the buffer */
635   buf = stralloc(buf);
636   if (buf == NULL) {
637           return NULL;
638   }
639
640   p = buf;
641   while (*p) {
642           if (*p == '<' || *p == '>') {
643                   *p = '_';
644           }
645           p++;
646   }
647   return buf;
648 }
649
650 char* cgigetq(long argc, const char **args){
651   if (argc>= 1){
652     char *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg,args[0]));
653     char *buf2;
654     char *c,*d;
655     int  qc=0;
656     if (buf==NULL) return NULL;
657
658     for(c=buf;*c != '\0';c++)
659       if (*c == '"') qc++;
660     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
661         perror("Malloc Buffer");
662         exit(1);
663     };
664     c=buf;
665     d=buf2;
666     *(d++) = '"';
667     while(*c != '\0'){
668         if (*c == '"') {
669             *(d++) = '"';
670             *(d++) = '\'';
671             *(d++) = '"';
672             *(d++) = '\'';
673         } 
674         *(d++) = *(c++);
675     }
676     *(d++) = '"';
677     *(d) = '\0';
678     free(buf);
679     return buf2;
680   }
681
682   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
683 }
684
685 /* remove occurrences of .. this is a general measure to make
686    paths which came in via cgi do not go UP ... */
687
688 char* cgigetqp(long argc, const char **args){
689        char* buf;
690     char* buf2;
691     char* p;
692         char* d;
693
694         if (argc < 1)
695         {
696                 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
697         }
698
699         buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
700     if (!buf)
701         {
702                 return NULL;
703         }
704
705         buf2 = malloc(strlen(buf)+1);
706     if (!buf2)
707         {
708                 perror("cgigetqp(): Malloc Path Buffer");
709                 exit(1);
710     };
711
712     p = buf;
713     d = buf2;
714
715     while (*p)
716         {
717                 /* prevent mallicious paths from entering the system */
718                 if (p[0] == '.' && p[1] == '.')
719                 {
720                         p += 2;
721                         *d++ = '_';
722                         *d++ = '_';     
723                 }
724                 else
725                 {
726                         *d++ = *p++;
727                 }
728     }
729
730     *d = 0;
731     free(buf);
732
733     /* Make sure the path is relative, e.g. does not start with '/' */
734     p = buf2;
735     while ('/' == *p)
736         {
737             *p++ = '_';
738     }
739
740     return buf2;
741 }
742
743
744 char* cgiget(long argc, const char **args){
745   if (argc>= 1)
746     return rrdstrip(rrdcgiGetValue(rrdcgiArg,args[0]));
747   else
748     return stralloc("[ERROR: not enough arguments for RRD::CV]");
749 }
750
751
752
753 char* drawgraph(long argc, const char **args){
754   int i,xsize, ysize;
755   double ymin,ymax;
756   for(i=0;i<argc;i++)
757     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
758   if(i==argc) {
759     args[argc++] = "--imginfo";
760     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
761   }
762   calfree();
763   if( rrd_graph(argc+1, (char **) args-1, &calcpr, &xsize, &ysize,NULL,&ymin,&ymax) != -1 ) {
764     return stralloc(calcpr[0]);
765   } else {
766     if (rrd_test_error()) {
767       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
768       sprintf(err, "[ERROR: %s]",rrd_get_error());
769       rrd_clear_error();
770       calfree();
771       return err;
772     }
773   }
774   return NULL;
775 }
776
777 char* drawprint(long argc, const char **args){
778   if (argc==1 && calcpr){
779     long i=0;
780     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
781     if (atol(args[0])<i-1)
782       return stralloc(calcpr[atol(args[0])+1]);    
783   }
784   return stralloc("[ERROR: RRD::PRINT argument error]");
785 }
786
787 char* printtimelast(long argc, const char **args) {
788   time_t last;
789   struct tm tm_last;
790   char *buf;
791   if ( argc == 2 ) {
792     buf = malloc(255);
793     if (buf == NULL){   
794         return stralloc("[ERROR: allocating strftime buffer]");
795     };
796     last = rrd_last(argc+1, (char **) args-1); 
797     if (rrd_test_error()) {
798       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
799       sprintf(err, "[ERROR: %s]",rrd_get_error());
800       rrd_clear_error();
801       return err;
802     }
803     tm_last = *localtime(&last);
804     strftime(buf,254,args[1],&tm_last);
805     return buf;
806   }
807   if ( argc < 2 ) {
808     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
809   }
810   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
811 }
812
813 char* printtimenow(long argc, const char **args) {
814   time_t now = time(NULL);
815   struct tm tm_now;
816   char *buf;
817   if ( argc == 1 ) {
818     buf = malloc(255);
819     if (buf == NULL){   
820         return stralloc("[ERROR: allocating strftime buffer]");
821     };
822     tm_now = *localtime(&now);
823     strftime(buf,254,args[0],&tm_now);
824     return buf;
825   }
826   if ( argc < 1 ) {
827     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
828   }
829   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
830 }
831
832 /* Scan buffer until an unescaped '>' arives.
833  * Update argument array with arguments found.
834  * Return end cursor where parsing stopped, or NULL in case of failure.
835  *
836  * FIXME:
837  * To allow nested constructs, we call rrd_expand_vars() for arguments
838  * that contain RRD::x directives. These introduce a small memory leak
839  * since we have to stralloc the arguments the way parse() works.
840  */
841 char*
842 scanargs(char *line, int *argument_count, char ***arguments)
843 {
844         char    *getP;          /* read cursor */
845         char    *putP;          /* write cursor */
846         char    Quote;          /* type of quote if in quoted string, 0 otherwise */
847         int     tagcount;       /* open tag count */
848         int     in_arg;         /* if we currently are parsing an argument or not */
849         int     argsz;          /* argument array size */
850         int             curarg_contains_rrd_directives;
851
852         /* local array of arguments while parsing */
853         int argc = 0;
854         char** argv;
855
856 #ifdef DEBUG_PARSER
857         printf("<-- scanargs(%s) -->\n", line);
858 #endif
859
860         *arguments = NULL;
861         *argument_count = 0;
862
863         /* create initial argument array of char pointers */
864         argsz = 32;
865         argv = (char **)malloc(argsz * sizeof(char *));
866         if (!argv) {
867                 return NULL;
868         }
869
870         /* skip leading blanks */
871         while (isspace((int)*line)) {
872                 line++;
873         }
874
875         getP = line;
876         putP = line;
877
878         Quote    = 0;
879         in_arg   = 0;
880         tagcount = 0;
881
882         curarg_contains_rrd_directives = 0;
883
884         /* start parsing 'line' for arguments */
885         while (*getP)
886         {
887                 unsigned char c = *getP++;
888
889                 if (c == '>' && !Quote && !tagcount) {
890                         /* this is our closing tag, quit scanning */
891                         break;
892                 }
893
894                 /* remove all special chars */
895                 if (c < ' ') {
896                         c = ' ';
897                 }
898
899                 switch (c)
900                 {
901                 case ' ': 
902                         if (Quote || tagcount) {
903                                 /* copy quoted/tagged (=RRD expanded) string */
904                                 *putP++ = c;
905                         }
906                         else if (in_arg)
907                         {
908                                 /* end argument string */
909                                 *putP++ = 0;
910                                 in_arg = 0;
911                                 if (curarg_contains_rrd_directives) {
912                                         argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
913                                         curarg_contains_rrd_directives = 0;
914                                 }
915                         }
916                         break;
917
918                 case '"': /* Fall through */
919                 case '\'':
920                         if (Quote != 0) {
921                                 if (Quote == c) {
922                                         Quote = 0;
923                                 } else {
924                                         /* copy quoted string */
925                                         *putP++ = c;
926                                 }
927                         } else {
928                                 if (!in_arg) {
929                                         /* reference start of argument string in argument array */
930                                         argv[argc++] = putP;
931                                         in_arg=1;
932                                 }
933                                 Quote = c;
934                         }
935                         break;
936
937                 default:
938                                 if (!in_arg) {
939                                         /* start new argument */
940                                         argv[argc++] = putP;
941                                         in_arg = 1;
942                                 }
943                                 if (c == '>') {
944                                         if (tagcount) {
945                                                 tagcount--;
946                                         }
947                                 }
948                                 if (c == '<') {
949                                         tagcount++;
950                                         if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
951                                                 curarg_contains_rrd_directives = 1;
952                                         }
953                                 }
954                         *putP++ = c;
955                         break;
956                 }
957
958                 /* check if our argument array is still large enough */
959                 if (argc == argsz) {
960                         /* resize argument array */
961                         argsz *= 2;
962                         argv = rrd_realloc(argv, argsz * sizeof(char *));
963                         if (*argv == NULL) {
964                                 return NULL;
965                         }
966                 }
967         }
968
969         /* terminate last argument found */
970         *putP = '\0';
971         if (curarg_contains_rrd_directives) {
972                 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
973         }
974
975 #ifdef DEBUG_PARSER
976         if (argc > 0) {
977                 int n;
978                 printf("<-- arguments found [%d]\n", argc);
979                 for (n=0; n<argc; n++) {
980                         printf("arg %02d: '%s'\n", n, argv[n]);
981                 }
982                 printf("-->\n");
983         } else {
984                 printf("<!-- No arguments found -->\n");
985         }
986 #endif
987
988         /* update caller's notion of the argument array and it's size */
989         *arguments = argv;
990         *argument_count = argc;
991
992         if (Quote) {
993                 return NULL;
994         }
995
996         /* Return new scanning cursor:
997            pointer to char after closing bracket */
998         return getP;
999 }
1000
1001
1002 /*
1003  * Parse(): scan current portion of buffer for given tag.
1004  * If found, parse tag arguments and call 'func' for it.
1005  * The result of func is inserted at the current position
1006  * in the buffer.
1007  */
1008 int
1009 parse(
1010         char **buf,     /* buffer */
1011         long i,                 /* offset in buffer */
1012         char *tag,      /* tag to handle  */
1013         char *(*func)(long , const char **) /* function to call for 'tag' */
1014         )
1015 {
1016         /* the name of the vairable ... */
1017         char *val;
1018         long valln;  
1019         char **args;
1020         char *end;
1021         long end_offset;
1022         int  argc;
1023         size_t taglen = strlen(tag);
1024
1025         /* Current position in buffer should start with 'tag' */
1026         if (strncmp((*buf)+i, tag, taglen) != 0) {
1027                 return 0;
1028         }
1029         /* .. and match exactly (a whitespace following 'tag') */
1030         if (! isspace(*((*buf) + i + taglen)) ) {
1031                 return 0;
1032         }
1033
1034 #ifdef DEBUG_PARSER
1035         printf("parse(): handling tag '%s'\n", tag);
1036 #endif
1037
1038         /* Scan for arguments following the tag;
1039            scanargs() puts \0 into *buf ... so after scanargs it is probably
1040            not a good time to use strlen on buf */
1041         end = scanargs((*buf) + i + taglen, &argc, &args);
1042         if (end)
1043         {
1044                 /* got arguments, call function for 'tag' with arguments */
1045                 val = func(argc, (const char **) args);
1046                 free(args);
1047         }
1048         else
1049         {
1050                 /* unable to parse arguments, undo 0-termination by scanargs */
1051                 for (; argc > 0; argc--) {
1052                         *((args[argc-1])-1) = ' ';
1053                 }
1054
1055                 /* next call, try parsing at current offset +1 */
1056                 end = (*buf) + i + 1;
1057
1058                 val = stralloc("[ERROR: Parsing Problem with the following text\n"
1059                                                 " Check original file. This may have been altered "
1060                                                 "by parsing.]\n\n");
1061         }
1062
1063         /* remember offset where we have to continue parsing */
1064         end_offset = end - (*buf);
1065
1066         valln = 0;
1067         if (val) {
1068                 valln = strlen(val);
1069         }
1070
1071         /* Optionally resize buffer to hold the replacement value:
1072            Calculating the new length of the buffer is simple. add current
1073            buffer pos (i) to length of string after replaced tag to length
1074            of replacement string and add 1 for the final zero ... */
1075         if (end - (*buf) < (i + valln)) {
1076                 /* make sure we do not shrink the mallocd block */
1077                 size_t newbufsize = i + strlen(end) + valln + 1;
1078                 *buf = rrd_realloc(*buf, newbufsize);
1079
1080                 if (*buf == NULL) {
1081                         perror("Realoc buf:");
1082                         exit(1);
1083                 };
1084         }
1085
1086         /* Update new end pointer:
1087            make sure the 'end' pointer gets moved along with the 
1088            buf pointer when realloc moves memory ... */
1089         end = (*buf) + end_offset; 
1090
1091         /* splice the variable:
1092            step 1. Shift pending data to make room for 'val' */
1093         memmove((*buf) + i + valln, end, strlen(end) + 1);
1094
1095         /* step 2. Insert val */
1096         if (val) {
1097                 memmove((*buf)+i, val, valln);
1098                 free(val);
1099         }
1100         return (valln > 0 ? valln-1: valln);
1101 }
1102
1103 char *
1104 http_time(time_t *now) {
1105         struct tm *tmptime;
1106         static char buf[60];
1107
1108         tmptime=gmtime(now);
1109         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1110         return(buf);
1111 }
1112
1113 void rrdcgiHeader(void)
1114 {
1115     if (rrdcgiType)
1116         printf ("Content-type: %s\n", rrdcgiType);
1117     else
1118         printf ("Content-type: text/html\n");
1119     if (rrdcgiHeaderString)
1120         printf ("%s", rrdcgiHeaderString);
1121     printf ("\n");
1122 }
1123
1124 void rrdcgiDebug(int level, int where)
1125 {
1126     if (level > 0)
1127         rrdcgiDebugLevel = level;
1128     else
1129         rrdcgiDebugLevel = 0;
1130     if (where)
1131         rrdcgiDebugStderr = 0;
1132     else
1133         rrdcgiDebugStderr = 1;
1134 }
1135
1136 char *rrdcgiDecodeString(char *text)
1137 {
1138     char *cp, *xp;
1139
1140     for (cp=text,xp=text; *cp; cp++) {
1141         if (*cp == '%') {
1142             if (strchr("0123456789ABCDEFabcdef", *(cp+1))
1143                 && strchr("0123456789ABCDEFabcdef", *(cp+2))) {
1144                 if (islower(*(cp+1)))
1145                     *(cp+1) = toupper(*(cp+1));
1146                 if (islower(*(cp+2)))
1147                     *(cp+2) = toupper(*(cp+2));
1148                 *(xp) = (*(cp+1) >= 'A' ? *(cp+1) - 'A' + 10 : *(cp+1) - '0' ) * 16
1149                     + (*(cp+2) >= 'A' ? *(cp+2) - 'A' + 10 : *(cp+2) - '0');
1150                 xp++;cp+=2;
1151             }
1152         } else {
1153             *(xp++) = *cp;
1154         }
1155     }
1156     memset(xp, 0, cp-xp);
1157     return text;
1158 }
1159
1160 /*  rrdcgiReadVariables()
1161  *
1162  *  Read from stdin if no string is provided via CGI.  Variables that
1163  *  doesn't have a value associated with it doesn't get stored.
1164  */
1165 s_var **rrdcgiReadVariables(void)
1166 {
1167     int length;
1168     char *line = NULL;
1169     int numargs;
1170     char *cp, *ip, *esp, *sptr;
1171     s_var **result;
1172     int i, k, len;
1173     char tmp[101];
1174
1175     cp = getenv("REQUEST_METHOD");
1176     ip = getenv("CONTENT_LENGTH");
1177
1178     if (cp && !strcmp(cp, "POST")) {
1179         if (ip) {
1180             length = atoi(ip);
1181             if ((line = (char *)malloc (length+2)) == NULL)
1182                 return NULL;
1183             fgets(line, length+1, stdin);
1184         } else
1185             return NULL;
1186     } else if (cp && !strcmp(cp, "GET")) {
1187         esp = getenv("QUERY_STRING");
1188         if (esp && strlen(esp)) {
1189             if ((line = (char *)malloc (strlen(esp)+2)) == NULL)
1190                 return NULL;
1191             sprintf (line, "%s", esp);
1192         } else
1193             return NULL;
1194     } else {
1195         length = 0;
1196         printf ("(offline mode: enter name=value pairs on standard input)\n");
1197         memset (tmp, 0, sizeof(tmp));
1198         while((cp = fgets (tmp, 100, stdin)) != NULL) {
1199             if (strlen(tmp)) {
1200                 if (tmp[strlen(tmp)-1] == '\n')
1201                     tmp[strlen(tmp)-1] = '&';
1202                 if (length) {
1203                     length += strlen(tmp);
1204                     len = (length+1) * sizeof(char);
1205                     if ((line = (char *)realloc (line, len)) == NULL)
1206                         return NULL;
1207                     strcat (line, tmp);
1208                 } else {
1209                     length = strlen(tmp);
1210                     len = (length+1) * sizeof(char);
1211                     if ((line = (char *)malloc (len)) == NULL)
1212                         return NULL;
1213                     memset (line, 0, len);
1214                     strcpy (line, tmp);
1215                 }
1216             }
1217             memset (tmp, 0, sizeof(tmp));
1218         }
1219         if (!line)
1220             return NULL;
1221         if (line[strlen(line)-1] == '&')
1222             line[strlen(line)-1] = '\0';
1223     }
1224
1225     /*
1226      *  From now on all cgi variables are stored in the variable line
1227      *  and look like  foo=bar&foobar=barfoo&foofoo=
1228      */
1229
1230     if (rrdcgiDebugLevel > 0) {
1231         if (rrdcgiDebugStderr)
1232             fprintf (stderr, "Received cgi input: %s\n", line);
1233         else
1234             printf ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n", line);
1235     }
1236
1237     for (cp=line; *cp; cp++)
1238         if (*cp == '+')
1239             *cp = ' ';
1240
1241     if (strlen(line)) {
1242         for (numargs=1,cp=line; *cp; cp++)
1243             if (*cp == '&') numargs++;
1244     } else
1245         numargs = 0;
1246     if (rrdcgiDebugLevel > 0) {
1247         if (rrdcgiDebugStderr)
1248             fprintf (stderr, "%d cgi variables found.\n", numargs);
1249         else
1250             printf ("%d cgi variables found.<br>\n", numargs);
1251     }
1252
1253     len = (numargs+1) * sizeof(s_var *);
1254     if ((result = (s_var **)malloc (len)) == NULL)
1255         return NULL;
1256     memset (result, 0, len);
1257
1258     cp = line;
1259     i=0;
1260     while (*cp) {
1261         if ((ip = (char *)strchr(cp, '&')) != NULL) {
1262             *ip = '\0';
1263         }else
1264             ip = cp + strlen(cp);
1265
1266         if ((esp=(char *)strchr(cp, '=')) == NULL) {
1267             cp = ++ip;
1268             continue;
1269         }
1270
1271         if (!strlen(esp)) {
1272             cp = ++ip;
1273             continue;
1274         }
1275
1276         if (i<numargs) {
1277
1278             /* try to find out if there's already such a variable */
1279             for (k=0; k<i && (strncmp (result[k]->name,cp, esp-cp) || !(strlen (result[k]->name) == esp-cp)); k++);
1280
1281             if (k == i) {       /* No such variable yet */
1282                 if ((result[i] = (s_var *)malloc(sizeof(s_var))) == NULL)
1283                     return NULL;
1284                 if ((result[i]->name = (char *)malloc((esp-cp+1) * sizeof(char))) == NULL)
1285                     return NULL;
1286                 memset (result[i]->name, 0, esp-cp+1);
1287                 strncpy(result[i]->name, cp, esp-cp);
1288                 cp = ++esp;
1289                 if ((result[i]->value = (char *)malloc((ip-esp+1) * sizeof(char))) == NULL)
1290                     return NULL;
1291                 memset (result[i]->value, 0, ip-esp+1);
1292                 strncpy(result[i]->value, cp, ip-esp);
1293                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1294                 if (rrdcgiDebugLevel) {
1295                     if (rrdcgiDebugStderr)
1296                         fprintf (stderr, "%s: %s\n", result[i]->name, result[i]->value);
1297                     else
1298                         printf ("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n", result[i]->name, result[i]->value);
1299                 }
1300                 i++;
1301             } else {    /* There is already such a name, suppose a mutiple field */
1302                 cp = ++esp;
1303                 len = (strlen(result[k]->value)+(ip-esp)+2) * sizeof (char);
1304                 if ((sptr = (char *)malloc(len)) == NULL)
1305                     return NULL;
1306                 memset (sptr, 0, len);
1307                 sprintf (sptr, "%s\n", result[k]->value);
1308                 strncat(sptr, cp, ip-esp);
1309                 free(result[k]->value);
1310                 result[k]->value = rrdcgiDecodeString (sptr);
1311             }
1312         }
1313         cp = ++ip;
1314     }
1315     return result;
1316 }
1317
1318 /*  rrdcgiInit()
1319  *
1320  *  Read from stdin if no string is provided via CGI.  Variables that
1321  *  doesn't have a value associated with it doesn't get stored.
1322  */
1323 s_cgi *rrdcgiInit(void)
1324 {
1325     s_cgi *res;
1326     s_var **vars;
1327
1328     vars = rrdcgiReadVariables();
1329
1330     if (!vars)
1331         return NULL;
1332
1333     if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
1334         return NULL;
1335     res->vars = vars;
1336
1337     return res;
1338 }
1339
1340 char *rrdcgiGetValue(s_cgi *parms, const char *name)
1341 {
1342     int i;
1343
1344     if (!parms || !parms->vars)
1345         return NULL;
1346     for (i=0;parms->vars[i]; i++)
1347         if (!strcmp(name,parms->vars[i]->name)) {
1348             if (rrdcgiDebugLevel > 0) {
1349                 if (rrdcgiDebugStderr)
1350                     fprintf (stderr, "%s found as %s\n", name, parms->vars[i]->value);
1351                 else
1352                     printf ("%s found as %s<br>\n", name, parms->vars[i]->value);
1353             }
1354             return parms->vars[i]->value;
1355         }
1356     if (rrdcgiDebugLevel) {
1357         if (rrdcgiDebugStderr)
1358             fprintf (stderr, "%s not found\n", name);
1359         else
1360             printf ("%s not found<br>\n", name);
1361     }
1362     return NULL;
1363 }
1364
1365 void rrdcgiFreeList (char **list)
1366 {
1367     int i;
1368
1369     for (i=0; list[i] != NULL; i++)
1370         free (list[i]);
1371         free (list);
1372 }
1373
1374 void rrdcgiFree (s_cgi *parms)
1375 {
1376     int i;
1377
1378     if (!parms)
1379         return;
1380     if (parms->vars) {
1381                 for (i=0;parms->vars[i]; i++) {
1382                         if (parms->vars[i]->name)
1383                                 free (parms->vars[i]->name);
1384                         if (parms->vars[i]->value)
1385                                 free (parms->vars[i]->value);
1386             free (parms->vars[i]);
1387                 }
1388                 free (parms->vars);
1389     }
1390     free (parms);
1391
1392     if (rrdcgiHeaderString) {
1393         free (rrdcgiHeaderString);
1394         rrdcgiHeaderString = NULL;
1395     }
1396     if (rrdcgiType) {
1397         free (rrdcgiType);
1398         rrdcgiType = NULL;
1399     }
1400 }
1401