Code

411b02bb23f75bafa0324a5379d26a5601c41dae
[rrdtool-all.git] / program / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2004
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
16 /* global variable for libcgi */
17 s_cgi *cgiArg;
19 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
20    and replace by result of arg[2] call */
21 int parse(char **, long, char *, char *(*)(long , const char **));
23 /**************************************************/
24 /* tag replacers ... they are called from parse   */
25 /* through function pointers                      */
26 /**************************************************/
28 /* return cgi var named arg[0] */ 
29 char* cgiget(long , const char **);
31 /* return a quoted cgi var named arg[0] */ 
32 char* cgigetq(long , const char **);
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , const char **);
37 /* call rrd_graph and insert appropriate image tag */
38 char* drawgraph(long, char **);
40 /* return PRINT functions from last rrd_graph call */
41 char* drawprint(long, const char **);
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, const char **);
46 /* pretty-print current time */
47 char* printtimenow(long, const char **);
49 /* set an environment variable */
50 char* rrdsetenv(long, const char **);
52 /* get an environment variable */
53 char* rrdgetenv(long, const char **);
55 /* include the named file at this point */
56 char* includefile(long, const char **);
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, const char **);
61 char* rrdstrip(char *buf);
62 char* scanargs(char *line, int *argc, char ***args);
64 /* format at-time specified times using strftime */
65 char* printstrftime(long, const char**);
67 /** HTTP protocol needs special format, and GMT time **/
68 char *http_time(time_t *);
70 /* return a pointer to newly allocated copy of this string */
71 char *stralloc(const char *);
74 /* rrd interface to the variable functions {put,get}var() */
75 char* rrdgetvar(long argc, const char **args);
76 char* rrdsetvar(long argc, const char **args);
77 char* rrdsetvarconst(long argc, const char **args);
80 /* variable store: put/get key-value pairs */
81 static int   initvar();
82 static void  donevar();
83 static const char* getvar(const char* varname);
84 static const char* putvar(const char* name, const char* value, int is_const);
86 /* key value pair that makes up an entry in the variable store */
87 typedef struct
88 {
89         int is_const;           /* const variable or not */
90         const char* name;       /* variable name */
91         const char* value;      /* variable value */
92 } vardata;
94 /* the variable heap: 
95    start with a heapsize of 10 variables */
96 #define INIT_VARSTORE_SIZE      10
97 static vardata* varheap    = NULL;
98 static size_t varheap_size = 0;
100 /* allocate and initialize variable heap */
101 static int
102 initvar()
104         varheap = (vardata*)malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
105         if (varheap == NULL) {
106                 fprintf(stderr, "ERROR: unable to initialize variable store\n");
107                 return -1;
108         }
109         memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
110         varheap_size = INIT_VARSTORE_SIZE;
111         return 0;
114 /* cleanup: free allocated memory */
115 static void
116 donevar()
118         int i;
119         if (varheap) {
120                 for (i=0; i<(int)varheap_size; i++) {
121                         if (varheap[i].name) {
122                                 free((char*)varheap[i].name);
123                         }
124                         if (varheap[i].value) {
125                                 free((char*)varheap[i].value);
126                         }
127                 }
128                 free(varheap);
129         }
132 /* Get a variable from the variable store.
133    Return NULL in case the requested variable was not found. */
134 static const char*
135 getvar(const char* name)
137         int i;
138         for (i=0; i<(int)varheap_size && varheap[i].name; i++) {
139                 if (0 == strcmp(name, varheap[i].name)) {
140 #ifdef          DEBUG_VARS
141                         printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
142 #endif
143                         return varheap[i].value;
144                 }
145         }
146 #ifdef DEBUG_VARS
147         printf("<!-- getvar(%s) -> Not found-->\n", name);
148 #endif
149         return NULL;
152 /* Put a variable into the variable store. If a variable by that
153    name exists, it's value is overwritten with the new value unless it was
154    marked as 'const' (initialized by RRD::SETCONSTVAR).
155    Returns a copy the newly allocated value on success, NULL on error. */
156 static const char*
157 putvar(const char* name, const char* value, int is_const)
159         int i;
160         for (i=0; i < (int)varheap_size && varheap[i].name; i++) {
161                 if (0 == strcmp(name, varheap[i].name)) {
162                         /* overwrite existing entry */
163                         if (varheap[i].is_const) {
164 #ifdef                  DEBUG_VARS
165                                 printf("<!-- setver(%s, %s): not assigning: "
166                                                 "const variable -->\n", name, value);
167 #                               endif
168                                 return varheap[i].value;
169                         }
170 #ifdef          DEBUG_VARS
171                         printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
172                                         name, value, varheap[i].value);
173 #endif
174                         /* make it possible to promote a variable to readonly */
175                         varheap[i].is_const = is_const;
176                         free((char*)varheap[i].value);
177                         varheap[i].value = stralloc(value);
178                         return varheap[i].value;
179                 }
180         }
182         /* no existing variable found by that name, add it */
183         if (i == (int)varheap_size) {
184                 /* ran out of heap: resize heap to double size */
185                 size_t new_size = varheap_size * 2;
186                 varheap = (vardata*)(realloc(varheap, sizeof(vardata) * new_size));
187                 if (!varheap) {
188                         fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
189                         return NULL;
190                 }
191                 /* initialize newly allocated memory */;
192                 memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
193                 varheap_size = new_size;
194         }
195         varheap[i].is_const = is_const;
196         varheap[i].name  = stralloc(name);
197         varheap[i].value = stralloc(value);
199 #ifdef          DEBUG_VARS
200         printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
201 #endif
202         return varheap[i].value;
205 /* expand those RRD:* directives that can be used recursivly */
206 static char*
207 rrd_expand_vars(char* buffer)
209         int i;
211 #ifdef DEBUG_PARSER
212         printf("expanding variables in '%s'\n", buffer);
213 #endif
215         for (i=0; buffer[i]; i++) {
216                 if (buffer[i] != '<')
217                         continue;
218                 parse(&buffer, i, "<RRD::CV", cgiget);
219                 parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
220                 parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
221                 parse(&buffer, i, "<RRD::GETENV", rrdgetenv);    
222                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);    
223                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
224                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
225                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
226         }
227         return buffer;
230 static long goodfor=0;
231 static char **calcpr=NULL;
232 static void calfree (void){
233   if (calcpr) {
234     long i;
235     for(i=0;calcpr[i];i++){
236       if (calcpr[i]){
237               free(calcpr[i]);
238       }
239     } 
240     if (calcpr) {
241             free(calcpr);
242     }
243   }
246 /* create freeable version of the string */
247 char * stralloc(const char *str){
248   char* nstr;
249   if (!str) {
250           return NULL;
251   }
252   nstr = malloc((strlen(str)+1));
253   strcpy(nstr,str);
254   return(nstr);
257 int main(int argc, char *argv[]) {
258         long length;
259         char *buffer;
260         char *server_url = NULL;
261         long i;
262         long filter=0;
263 #ifdef MUST_DISABLE_SIGFPE
264         signal(SIGFPE,SIG_IGN);
265 #endif
266 #ifdef MUST_DISABLE_FPMASK
267         fpsetmask(0);
268 #endif
269         /* what do we get for cmdline arguments?
270         for (i=0;i<argc;i++)
271         printf("%d-'%s'\n",i,argv[i]); */
272         while (1) {
273                 static struct option long_options[] = {
274                         { "filter", no_argument, 0, 'f' },
275                         { 0, 0, 0, 0}
276                 };
277                 int option_index = 0;
278                 int opt;
279                 opt = getopt_long(argc, argv, "f", long_options, &option_index);
280                 if (opt == EOF) {
281                         break;
282                 }
284                 switch(opt) {
285                 case 'f':
286                                 filter=1;
287                         break;
288                 case '?':
289                         printf("unknown commandline option '%s'\n",argv[optind-1]);
290                         return -1;
291                 }
292         }
294         if (!filter) {
295                 cgiDebug(0,0);
296                 cgiArg = cgiInit();
297                 server_url = getenv("SERVER_URL");
298         }
300         /* make sure we have one extra argument, 
301            if there are others, we do not care Apache gives several */
303         /* if ( (optind != argc-2 
304            && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
305            && optind != argc-1) { */
307         if ( optind >= argc ) { 
308                 fprintf(stderr, "ERROR: expected a filename\n");
309                 exit(1);
310         } else {
311                 length = readfile(argv[optind], &buffer, 1);
312         }
314         if(rrd_test_error()) {
315                 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
316                 exit(1);
317         }
319         /* initialize variable heap */
320         initvar();
322 #ifdef DEBUG_PARSER
323        /* some fake header for testing */
324        printf ("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
325 #endif
328         /* expand rrd directives in buffer recursivly */
329         for (i=0; buffer[i]; i++) {
330                 if (buffer[i] != '<')
331                         continue;
332                 if (!filter) {
333                         parse(&buffer, i, "<RRD::CV", cgiget);
334                         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
335                         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
336                         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
337                 }
338                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
339                 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
340                 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
341                 parse(&buffer, i, "<RRD::INCLUDE", includefile);
342                 parse(&buffer, i, "<RRD::PRINT", drawprint);
343                 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
344                 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
345                 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
346                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
347                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
348                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
349         }
351         if (!filter) {
352                 printf ("Content-Type: text/html\n" 
353                                 "Content-Length: %d\n", 
354                                 strlen(buffer));
356                 if (labs(goodfor) > 0) {
357                         time_t now;
358                         now = time(NULL);
359                         printf("Last-Modified: %s\n", http_time(&now));
360                         now += labs(goodfor);
361                         printf("Expires: %s\n", http_time(&now));
362                         if (goodfor < 0) {
363                                 printf("Refresh: %ld\n", labs(goodfor));
364                         }
365                 }
366                 printf("\n");
367         }
369         /* output result */
370         printf("%s", buffer);
372         /* cleanup */
373         calfree();
374         if (buffer){
375                 free(buffer);
376         }
377         donevar();
378         exit(0);
381 /* remove occurrences of .. this is a general measure to make
382    paths which came in via cgi do not go UP ... */
384 char* rrdsetenv(long argc, const char **args) {
385         if (argc >= 2) {
386                 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
387                 if (xyz == NULL) {
388                         return stralloc("[ERROR: allocating setenv buffer]");
389                 };
390                 sprintf(xyz, "%s=%s", args[0], args[1]);
391                 if(putenv(xyz) == -1) {
392                         free(xyz);
393                         return stralloc("[ERROR: failed to do putenv]");
394                 };
395         }
396         return stralloc("[ERROR: setenv failed because not enough "
397                                         "arguments were defined]");
400 /* rrd interface to the variable function putvar() */
401 char*
402 rrdsetvar(long argc, const char **args)
404         if (argc >= 2)
405         {
406                 const char* result = putvar(args[0], args[1], 0 /* not const */);
407                 if (result) {
408                         /* setvar does not return the value set */
409                         return stralloc("");
410                 }
411                 return stralloc("[ERROR: putvar failed]");
412         }
413         return stralloc("[ERROR: putvar failed because not enough arguments "
414                                         "were defined]");
417 /* rrd interface to the variable function putvar() */
418 char*
419 rrdsetvarconst(long argc, const char **args)
421         if (argc >= 2)
422         {
423                 const char* result = putvar(args[0], args[1], 1 /* const */);
424                 if (result) {
425                         /* setvar does not return the value set */
426                         return stralloc("");
427                 }
428                 return stralloc("[ERROR: putvar failed]");
429         }
430         return stralloc("[ERROR: putvar failed because not enough arguments "
431                                         "were defined]");
434 char* rrdgetenv(long argc, const char **args) {
435         char buf[128];
436         const char* envvar;
437         if (argc != 1) {
438                 return stralloc("[ERROR: getenv failed because it did not "
439                                                 "get 1 argument only]");
440         };
441         envvar = getenv(args[0]);
442         if (envvar) {
443                 return stralloc(envvar);
444         } else {
445 #ifdef WIN32
446                _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
447 #else
448                 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
449 #endif         
450                 return stralloc(buf);
451         }
454 char* rrdgetvar(long argc, const char **args) {
455         char buf[128];
456         const char* value;
457         if (argc != 1) {
458                 return stralloc("[ERROR: getvar failed because it did not "
459                                                 "get 1 argument only]");
460         };
461         value = getvar(args[0]);
462         if (value) {
463                 return stralloc(value);
464         } else {
465 #ifdef WIN32
466                _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
467 #else
468                 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
469 #endif
470                 return stralloc(buf);
471         }
474 char* rrdgoodfor(long argc, const char **args){
475   if (argc == 1) {
476       goodfor = atol(args[0]);
477   } else {
478     return stralloc("[ERROR: goodfor expected 1 argument]");
479   }
480    
481   if (goodfor == 0){
482      return stralloc("[ERROR: goodfor value must not be 0]");
483   }
484    
485   return stralloc("");
488 /* Format start or end times using strftime.  We always need both the
489  * start and end times, because, either might be relative to the other.
490  * */
491 #define MAX_STRFTIME_SIZE 256
492 char* printstrftime(long argc, const char **args){
493         struct  rrd_time_value start_tv, end_tv;
494         char    *parsetime_error = NULL;
495         char    formatted[MAX_STRFTIME_SIZE];
496         struct tm *the_tm;
497         time_t  start_tmp, end_tmp;
499         /* Make sure that we were given the right number of args */
500         if( argc != 4) {
501                 rrd_set_error( "wrong number of args %d", argc);
502                 return (char *) -1;
503         }
505         /* Init start and end time */
506         parsetime("end-24h", &start_tv);
507         parsetime("now", &end_tv);
509         /* Parse the start and end times we were given */
510         if( (parsetime_error = parsetime( args[1], &start_tv))) {
511                 rrd_set_error( "start time: %s", parsetime_error);
512                 return (char *) -1;
513         }
514         if( (parsetime_error = parsetime( args[2], &end_tv))) {
515                 rrd_set_error( "end time: %s", parsetime_error);
516                 return (char *) -1;
517         }
518         if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
519                 return (char *) -1;
520         }
522         /* Do we do the start or end */
523         if( strcasecmp( args[0], "START") == 0) {
524                 the_tm = localtime( &start_tmp);
525         }
526         else if( strcasecmp( args[0], "END") == 0) {
527                 the_tm = localtime( &end_tmp);
528         }
529         else {
530                 rrd_set_error( "start/end not found in '%s'", args[0]);
531                 return (char *) -1;
532         }
534         /* now format it */
535         if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
536                 return( stralloc( formatted));
537         }
538         else {
539                 rrd_set_error( "strftime failed");
540                 return (char *) -1;
541         }
544 char* includefile(long argc, const char **args){
545   char *buffer;
546   if (argc >= 1) {
547       char* filename = args[0];
548       readfile(filename, &buffer, 0);
549       if (rrd_test_error()) {
550                 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
551           sprintf(err, "[ERROR: %s]",rrd_get_error());
552           rrd_clear_error();
553           return err;
554       } else {
555        return buffer;
556       }
557   }
558   else
559   {
560       return stralloc("[ERROR: No Inclue file defined]");
561   }
564 /* make a copy of buf and replace open/close brackets with '_' */
565 char* rrdstrip(char *buf) {
566   char* p;
567   if (buf == NULL) {
568           return NULL;
569   }
570   /* make a copy of the buffer */
571   buf = stralloc(buf);
572   if (buf == NULL) {
573           return NULL;
574   }
576   p = buf;
577   while (*p) {
578           if (*p == '<' || *p == '>') {
579                   *p = '_';
580           }
581           p++;
582   }
583   return buf;
586 char* cgigetq(long argc, const char **args){
587   if (argc>= 1){
588     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
589     char *buf2;
590     char *c,*d;
591     int  qc=0;
592     if (buf==NULL) return NULL;
594     for(c=buf;*c != '\0';c++)
595       if (*c == '"') qc++;
596     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
597         perror("Malloc Buffer");
598         exit(1);
599     };
600     c=buf;
601     d=buf2;
602     *(d++) = '"';
603     while(*c != '\0'){
604         if (*c == '"') {
605             *(d++) = '"';
606             *(d++) = '\'';
607             *(d++) = '"';
608             *(d++) = '\'';
609         } 
610         *(d++) = *(c++);
611     }
612     *(d++) = '"';
613     *(d) = '\0';
614     free(buf);
615     return buf2;
616   }
618   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
621 /* remove occurrences of .. this is a general measure to make
622    paths which came in via cgi do not go UP ... */
624 char* cgigetqp(long argc, const char **args){
625        char* buf;
626     char* buf2;
627     char* p;
628         char* d;
630         if (argc < 1)
631         {
632                 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
633         }
635         buf = rrdstrip(cgiGetValue(cgiArg, args[0]));
636     if (!buf)
637         {
638                 return NULL;
639         }
641         buf2 = malloc(strlen(buf)+1);
642     if (!buf2)
643         {
644                 perror("cgigetqp(): Malloc Path Buffer");
645                 exit(1);
646     };
648     p = buf;
649     d = buf2;
651     while (*p)
652         {
653                 /* prevent mallicious paths from entering the system */
654                 if (p[0] == '.' && p[1] == '.')
655                 {
656                         p += 2;
657                         *d++ = '_';
658                         *d++ = '_';     
659                 }
660                 else
661                 {
662                         *d++ = *p++;
663                 }
664     }
666     *d = 0;
667     free(buf);
669     /* Make sure the path is relative, e.g. does not start with '/' */
670     p = buf2;
671     while ('/' == *p)
672         {
673             *p++ = '_';
674     }
676     return buf2;
680 char* cgiget(long argc, const char **args){
681   if (argc>= 1)
682     return rrdstrip(cgiGetValue(cgiArg,args[0]));
683   else
684     return stralloc("[ERROR: not enough arguments for RRD::CV]");
689 char* drawgraph(long argc, char **args){
690   int i,xsize, ysize;
691   for(i=0;i<argc;i++)
692     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
693   if(i==argc) {
694     args[argc++] = "--imginfo";
695     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
696   }
697   optind=0; /* reset gnu getopt */
698   opterr=0; /* reset gnu getopt */
699   calfree();
700   if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL) != -1 ) {
701     return stralloc(calcpr[0]);
702   } else {
703     if (rrd_test_error()) {
704       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
705       sprintf(err, "[ERROR: %s]",rrd_get_error());
706       rrd_clear_error();
707       calfree();
708       return err;
709     }
710   }
711   return NULL;
714 char* drawprint(long argc, const char **args){
715   if (argc==1 && calcpr){
716     long i=0;
717     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
718     if (atol(args[0])<i-1)
719       return stralloc(calcpr[atol(args[0])+1]);    
720   }
721   return stralloc("[ERROR: RRD::PRINT argument error]");
724 char* printtimelast(long argc, const char **args) {
725   time_t last;
726   struct tm tm_last;
727   char *buf;
728   if ( argc == 2 ) {
729     buf = malloc(255);
730     if (buf == NULL){   
731         return stralloc("[ERROR: allocating strftime buffer]");
732     };
733     last = rrd_last(argc+1, args-1); 
734     if (rrd_test_error()) {
735       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
736       sprintf(err, "[ERROR: %s]",rrd_get_error());
737       rrd_clear_error();
738       return err;
739     }
740     tm_last = *localtime(&last);
741     strftime(buf,254,args[1],&tm_last);
742     return buf;
743   }
744   if ( argc < 2 ) {
745     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
746   }
747   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
750 char* printtimenow(long argc, const char **args) {
751   time_t now = time(NULL);
752   struct tm tm_now;
753   char *buf;
754   if ( argc == 1 ) {
755     buf = malloc(255);
756     if (buf == NULL){   
757         return stralloc("[ERROR: allocating strftime buffer]");
758     };
759     tm_now = *localtime(&now);
760     strftime(buf,254,args[0],&tm_now);
761     return buf;
762   }
763   if ( argc < 1 ) {
764     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
765   }
766   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
769 /* Scan buffer until an unescaped '>' arives.
770  * Update argument array with arguments found.
771  * Return end cursor where parsing stopped, or NULL in case of failure.
772  *
773  * FIXME:
774  * To allow nested constructs, we call rrd_expand_vars() for arguments
775  * that contain RRD::x directives. These introduce a small memory leak
776  * since we have to stralloc the arguments the way parse() works.
777  */
778 char*
779 scanargs(char *line, int *argument_count, char ***arguments)
781         char    *getP;          /* read cursor */
782         char    *putP;          /* write cursor */
783         char    Quote;          /* type of quote if in quoted string, 0 otherwise */
784         int     tagcount;       /* open tag count */
785         int     in_arg;         /* if we currently are parsing an argument or not */
786         int     argsz;          /* argument array size */
787         int             curarg_contains_rrd_directives;
789         /* local array of arguments while parsing */
790         int argc = 0;
791         char** argv;
793 #ifdef DEBUG_PARSER
794         printf("<-- scanargs(%s) -->\n", line);
795 #endif
797         *arguments = NULL;
798         *argument_count = 0;
800         /* create initial argument array of char pointers */
801         argsz = 32;
802         argv = (char **)malloc(argsz * sizeof(char *));
803         if (!argv) {
804                 return NULL;
805         }
807         /* skip leading blanks */
808         while (isspace((int)*line)) {
809                 line++;
810         }
812         getP = line;
813         putP = line;
815         Quote    = 0;
816         in_arg   = 0;
817         tagcount = 0;
819         curarg_contains_rrd_directives = 0;
821         /* start parsing 'line' for arguments */
822         while (*getP)
823         {
824                 unsigned char c = *getP++;
826                 if (c == '>' && !Quote && !tagcount) {
827                         /* this is our closing tag, quit scanning */
828                         break;
829                 }
831                 /* remove all special chars */
832                 if (c < ' ') {
833                         c = ' ';
834                 }
836                 switch (c)
837                 {
838                 case ' ': 
839                         if (Quote || tagcount) {
840                                 /* copy quoted/tagged (=RRD expanded) string */
841                                 *putP++ = c;
842                         }
843                         else if (in_arg)
844                         {
845                                 /* end argument string */
846                                 *putP++ = 0;
847                                 in_arg = 0;
848                                 if (curarg_contains_rrd_directives) {
849                                         argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
850                                         curarg_contains_rrd_directives = 0;
851                                 }
852                         }
853                         break;
855                 case '"': /* Fall through */
856                 case '\'':
857                         if (Quote != 0) {
858                                 if (Quote == c) {
859                                         Quote = 0;
860                                 } else {
861                                         /* copy quoted string */
862                                         *putP++ = c;
863                                 }
864                         } else {
865                                 if (!in_arg) {
866                                         /* reference start of argument string in argument array */
867                                         argv[argc++] = putP;
868                                         in_arg=1;
869                                 }
870                                 Quote = c;
871                         }
872                         break;
874                 default:
875                                 if (!in_arg) {
876                                         /* start new argument */
877                                         argv[argc++] = putP;
878                                         in_arg = 1;
879                                 }
880                                 if (c == '>') {
881                                         if (tagcount) {
882                                                 tagcount--;
883                                         }
884                                 }
885                                 if (c == '<') {
886                                         tagcount++;
887                                         if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
888                                                 curarg_contains_rrd_directives = 1;
889                                         }
890                                 }
891                         *putP++ = c;
892                         break;
893                 }
895                 /* check if our argument array is still large enough */
896                 if (argc == argsz) {
897                         /* resize argument array */
898                         argsz *= 2;
899                         argv = rrd_realloc(argv, argsz * sizeof(char *));
900                         if (*argv == NULL) {
901                                 return NULL;
902                         }
903                 }
904         }
906         /* terminate last argument found */
907         *putP = '\0';
908         if (curarg_contains_rrd_directives) {
909                 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
910         }
912 #ifdef DEBUG_PARSER
913         if (argc > 0) {
914                 int n;
915                 printf("<-- arguments found [%d]\n", argc);
916                 for (n=0; n<argc; n++) {
917                         printf("arg %02d: '%s'\n", n, argv[n]);
918                 }
919                 printf("-->\n");
920         } else {
921                 printf("<!-- No arguments found -->\n");
922         }
923 #endif
925         /* update caller's notion of the argument array and it's size */
926         *arguments = argv;
927         *argument_count = argc;
929         if (Quote) {
930                 return NULL;
931         }
933         /* Return new scanning cursor:
934            pointer to char after closing bracket */
935         return getP;
939 /*
940  * Parse(): scan current portion of buffer for given tag.
941  * If found, parse tag arguments and call 'func' for it.
942  * The result of func is inserted at the current position
943  * in the buffer.
944  */
945 int
946 parse(
947         char **buf,     /* buffer */
948         long i,                 /* offset in buffer */
949         char *tag,      /* tag to handle  */
950         char *(*func)(long , const char **) /* function to call for 'tag' */
951         )
953         /* the name of the vairable ... */
954         char *val;
955         long valln;  
956         char **args;
957         char *end;
958         long end_offset;
959         int  argc;
960         size_t taglen = strlen(tag);
962         /* Current position in buffer should start with 'tag' */
963         if (strncmp((*buf)+i, tag, taglen) != 0) {
964                 return 0;
965         }
966         /* .. and match exactly (a whitespace following 'tag') */
967         if (! isspace(*((*buf) + i + taglen)) ) {
968                 return 0;
969         }
971 #ifdef DEBUG_PARSER
972         printf("parse(): handling tag '%s'\n", tag);
973 #endif
975         /* Scan for arguments following the tag;
976            scanargs() puts \0 into *buf ... so after scanargs it is probably
977            not a good time to use strlen on buf */
978         end = scanargs((*buf) + i + taglen, &argc, &args);
979         if (end)
980         {
981                 /* got arguments, call function for 'tag' with arguments */
982                 val = func(argc, args);
983                 free(args);
984         }
985         else
986         {
987                 /* unable to parse arguments, undo 0-termination by scanargs */
988                 for (; argc > 0; argc--) {
989                         *((args[argc-1])-1) = ' ';
990                 }
992                 /* next call, try parsing at current offset +1 */
993                 end = (*buf) + i + 1;
995                 val = stralloc("[ERROR: Parsing Problem with the following text\n"
996                                                 " Check original file. This may have been altered "
997                                                 "by parsing.]\n\n");
998         }
1000         /* remember offset where we have to continue parsing */
1001         end_offset = end - (*buf);
1003         valln = 0;
1004         if (val) {
1005                 valln = strlen(val);
1006         }
1008         /* Optionally resize buffer to hold the replacement value:
1009            Calculating the new length of the buffer is simple. add current
1010            buffer pos (i) to length of string after replaced tag to length
1011            of replacement string and add 1 for the final zero ... */
1012         if (end - (*buf) < (i + valln)) {
1013                 /* make sure we do not shrink the mallocd block */
1014                 size_t newbufsize = i + strlen(end) + valln + 1;
1015                 *buf = rrd_realloc(*buf, newbufsize);
1017                 if (*buf == NULL) {
1018                         perror("Realoc buf:");
1019                         exit(1);
1020                 };
1021         }
1023         /* Update new end pointer:
1024            make sure the 'end' pointer gets moved along with the 
1025            buf pointer when realloc moves memory ... */
1026         end = (*buf) + end_offset; 
1028         /* splice the variable:
1029            step 1. Shift pending data to make room for 'val' */
1030         memmove((*buf) + i + valln, end, strlen(end) + 1);
1032         /* step 2. Insert val */
1033         if (val) {
1034                 memmove((*buf)+i, val, valln);
1035                 free(val);
1036         }
1037         return (valln > 0 ? valln-1: valln);
1040 char *
1041 http_time(time_t *now) {
1042         struct tm *tmptime;
1043         static char buf[60];
1045         tmptime=gmtime(now);
1046         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1047         return(buf);