Code

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