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