Code

New recursive parser for rrdcgi by Arend-Jan Wijtzes <ajwytzes@wise-guys.nl>
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2003
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 , 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 , char **);
31 /* return a quoted cgi var named arg[0] */ 
32 char* cgigetq(long , char **);
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , 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, char **);
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, char **);
46 /* pretty-print current time */
47 char* printtimenow(long,char **);
49 /* set an environment variable */
50 char* rrdsetenv(long, char **);
52 /* get an environment variable */
53 char* rrdgetenv(long, char **);
55 /* include the named file at this point */
56 char* includefile(long, char **);
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, 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, 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, char **args);
76 char* rrdsetvar(long argc, char **args);
77 char* rrdsetvarconst(long argc, 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<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<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 < 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 == 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         }
224         return buffer;
227 static long goodfor=0;
228 static char **calcpr=NULL;
229 static void calfree (void){
230   if (calcpr) {
231     long i;
232     for(i=0;calcpr[i];i++){
233       if (calcpr[i]){
234               free(calcpr[i]);
235       }
236     } 
237     if (calcpr) {
238             free(calcpr);
239     }
240   }
243 /* create freeable version of the string */
244 char * stralloc(const char *str){
245   char* nstr;
246   if (!str) {
247           return NULL;
248   }
249   nstr = malloc((strlen(str)+1));
250   strcpy(nstr,str);
251   return(nstr);
254 int main(int argc, char *argv[]) {
255         long length;
256         char *buffer;
257         char *server_url = NULL;
258         long i;
259         long filter=0;
260 #ifdef MUST_DISABLE_SIGFPE
261         signal(SIGFPE,SIG_IGN);
262 #endif
263 #ifdef MUST_DISABLE_FPMASK
264         fpsetmask(0);
265 #endif
266         /* what do we get for cmdline arguments?
267         for (i=0;i<argc;i++)
268         printf("%d-'%s'\n",i,argv[i]); */
269         while (1) {
270                 static struct option long_options[] = {
271                         { "filter", no_argument, 0, 'f' },
272                         { 0, 0, 0, 0}
273                 };
274                 int option_index = 0;
275                 int opt;
276                 opt = getopt_long(argc, argv, "f", long_options, &option_index);
277                 if (opt == EOF) {
278                         break;
279                 }
281                 switch(opt) {
282                 case 'f':
283                                 filter=1;
284                         break;
285                 case '?':
286                         printf("unknown commandline option '%s'\n",argv[optind-1]);
287                         return -1;
288                 }
289         }
291         if (!filter) {
292                 cgiDebug(0,0);
293                 cgiArg = cgiInit();
294                 server_url = getenv("SERVER_URL");
295         }
297         /* make sure we have one extra argument, 
298            if there are others, we do not care Apache gives several */
300         /* if ( (optind != argc-2 
301            && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
302            && optind != argc-1) { */
304         if ( optind >= argc ) { 
305                 fprintf(stderr, "ERROR: expected a filename\n");
306                 exit(1);
307         } else {
308                 length = readfile(argv[optind], &buffer, 1);
309         }
311         if(rrd_test_error()) {
312                 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
313                 exit(1);
314         }
316         /* initialize variable heap */
317         initvar();
319         /* expand rrd directives in buffer recursivly */
320         for (i=0; buffer[i]; i++) {
321                 if (buffer[i] != '<')
322                         continue;
323                 if (!filter) {
324                         parse(&buffer, i, "<RRD::CV", cgiget);
325                         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
326                         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
327                         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
328                 }
329                 parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
330                 parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
331                 parse(&buffer, i, "<RRD::GRAPH", drawgraph);
332                 parse(&buffer, i, "<RRD::INCLUDE", includefile);
333                 parse(&buffer, i, "<RRD::PRINT", drawprint);
334                 parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
335                 parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
336                 parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
337                 parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
338                 parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
339                 parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
340         }
342         if (!filter) {
343                 printf ("Content-Type: text/html\n" 
344                                 "Content-Length: %d\n", 
345                                 strlen(buffer));
347                 if (labs(goodfor) > 0) {
348                         time_t now;
349                         now = time(NULL);
350                         printf("Last-Modified: %s\n", http_time(&now));
351                         now += labs(goodfor);
352                         printf("Expires: %s\n", http_time(&now));
353                         if (goodfor < 0) {
354                                 printf("Refresh: %ld\n", labs(goodfor));
355                         }
356                 }
357                 printf("\n");
358         }
360         /* output result */
361         printf("%s", buffer);
363         /* cleanup */
364         calfree();
365         if (buffer){
366                 free(buffer);
367         }
368         donevar();
369         exit(0);
372 /* remove occurrences of .. this is a general measure to make
373    paths which came in via cgi do not go UP ... */
375 char* rrdsetenv(long argc, char **args) {
376         if (argc >= 2) {
377                 char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
378                 if (xyz == NULL) {
379                         return stralloc("[ERROR: allocating setenv buffer]");
380                 };
381                 sprintf(xyz, "%s=%s", args[0], args[1]);
382                 if(putenv(xyz) == -1) {
383                         free(xyz);
384                         return stralloc("[ERROR: failed to do putenv]");
385                 };
386         }
387         return stralloc("[ERROR: setenv failed because not enough "
388                                         "arguments were defined]");
391 /* rrd interface to the variable function putvar() */
392 char*
393 rrdsetvar(long argc, char **args)
395         if (argc >= 2)
396         {
397                 const char* result = putvar(args[0], args[1], 0 /* not const */);
398                 if (result) {
399                         /* setvar does not return the value set */
400                         return stralloc("");
401                 }
402                 return stralloc("[ERROR: putvar failed]");
403         }
404         return stralloc("[ERROR: putvar failed because not enough arguments "
405                                         "were defined]");
408 /* rrd interface to the variable function putvar() */
409 char*
410 rrdsetvarconst(long argc, char **args)
412         if (argc >= 2)
413         {
414                 const char* result = putvar(args[0], args[1], 1 /* const */);
415                 if (result) {
416                         /* setvar does not return the value set */
417                         return stralloc("");
418                 }
419                 return stralloc("[ERROR: putvar failed]");
420         }
421         return stralloc("[ERROR: putvar failed because not enough arguments "
422                                         "were defined]");
425 char* rrdgetenv(long argc, char **args) {
426         char buf[128];
427         const char* envvar;
428         if (argc != 1) {
429                 return stralloc("[ERROR: getenv failed because it did not "
430                                                 "get 1 argument only]");
431         };
432         envvar = getenv(args[0]);
433         if (envvar) {
434                 return stralloc(envvar);
435         } else {
436                 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
437                 return stralloc(buf);
438         }
441 char* rrdgetvar(long argc, char **args) {
442         char buf[128];
443         const char* value;
444         if (argc != 1) {
445                 return stralloc("[ERROR: getvar failed because it did not "
446                                                 "get 1 argument only]");
447         };
448         value = getvar(args[0]);
449         if (value) {
450                 return stralloc(value);
451         } else {
452                 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
453                 return stralloc(buf);
454         }
457 char* rrdgoodfor(long argc, char **args){
458   if (argc == 1) {
459       goodfor = atol(args[0]);
460   } else {
461     return stralloc("[ERROR: goodfor expected 1 argument]");
462   }
463    
464   if (goodfor == 0){
465      return stralloc("[ERROR: goodfor value must not be 0]");
466   }
467    
468   return stralloc("");
471 /* Format start or end times using strftime.  We always need both the
472  * start and end times, because, either might be relative to the other.
473  * */
474 #define MAX_STRFTIME_SIZE 256
475 char* printstrftime(long argc, char **args){
476         struct  time_value start_tv, end_tv;
477         char    *parsetime_error = NULL;
478         char    formatted[MAX_STRFTIME_SIZE];
479         struct tm *the_tm;
480         time_t  start_tmp, end_tmp;
482         /* Make sure that we were given the right number of args */
483         if( argc != 4) {
484                 rrd_set_error( "wrong number of args %d", argc);
485                 return (char *) -1;
486         }
488         /* Init start and end time */
489         parsetime("end-24h", &start_tv);
490         parsetime("now", &end_tv);
492         /* Parse the start and end times we were given */
493         if( (parsetime_error = parsetime( args[1], &start_tv))) {
494                 rrd_set_error( "start time: %s", parsetime_error);
495                 return (char *) -1;
496         }
497         if( (parsetime_error = parsetime( args[2], &end_tv))) {
498                 rrd_set_error( "end time: %s", parsetime_error);
499                 return (char *) -1;
500         }
501         if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
502                 return (char *) -1;
503         }
505         /* Do we do the start or end */
506         if( strcasecmp( args[0], "START") == 0) {
507                 the_tm = localtime( &start_tmp);
508         }
509         else if( strcasecmp( args[0], "END") == 0) {
510                 the_tm = localtime( &end_tmp);
511         }
512         else {
513                 rrd_set_error( "start/end not found in '%s'", args[0]);
514                 return (char *) -1;
515         }
517         /* now format it */
518         if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
519                 return( stralloc( formatted));
520         }
521         else {
522                 rrd_set_error( "strftime failed");
523                 return (char *) -1;
524         }
527 char* includefile(long argc, char **args){
528   char *buffer;
529   if (argc >= 1) {
530           char* filename = args[0];
531       readfile(filename, &buffer, 0);
532       if (rrd_test_error()) {
533                 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
534           sprintf(err, "[ERROR: %s]",rrd_get_error());
535           rrd_clear_error();
536           return err;
537       } else {
538        return buffer;
539       }
540   }
541   else
542   {
543       return stralloc("[ERROR: No Inclue file defined]");
544   }
547 /* make a copy of buf and replace open/close brackets with '_' */
548 char* rrdstrip(char *buf) {
549   char* p;
550   if (buf == NULL) {
551           return NULL;
552   }
553   /* make a copy of the buffer */
554   buf = stralloc(buf);
555   if (buf == NULL) {
556           return NULL;
557   }
559   p = buf;
560   while (*p) {
561           if (*p == '<' || *p == '>') {
562                   *p = '_';
563           }
564           p++;
565   }
566   return buf;
569 char* cgigetq(long argc, char **args){
570   if (argc>= 1){
571     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
572     char *buf2;
573     char *c,*d;
574     int  qc=0;
575     if (buf==NULL) return NULL;
577     for(c=buf;*c != '\0';c++)
578       if (*c == '"') qc++;
579     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
580         perror("Malloc Buffer");
581         exit(1);
582     };
583     c=buf;
584     d=buf2;
585     *(d++) = '"';
586     while(*c != '\0'){
587         if (*c == '"') {
588             *(d++) = '"';
589             *(d++) = '\'';
590             *(d++) = '"';
591             *(d++) = '\'';
592         } 
593         *(d++) = *(c++);
594     }
595     *(d++) = '"';
596     *(d) = '\0';
597     free(buf);
598     return buf2;
599   }
601   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
604 /* remove occurrences of .. this is a general measure to make
605    paths which came in via cgi do not go UP ... */
607 char* cgigetqp(long argc, char **args){
608   if (argc>= 1) {
609     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
610     char *buf2;
611     char *c,*d;
612     int  qc=0;
614     if (buf==NULL) 
615                 return NULL;
617     for(c=buf;*c != '\0';c++) {
618         if (*c == '"') {
619                         qc++;
620                 }
621         }
623     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
624                 perror("Malloc Buffer");
625                 exit(1);
626     };
628     c=buf;
629     d=buf2;
631     *(d++) = '"';
632     while (*c != '\0') {
633                 if (*c == '"') {
634                         *(d++) = '"';
635                         *(d++) = '\'';
636                         *(d++) = '"';
637                         *(d++) = '\'';
638                 }
639                 if(*c == '/') {
640                         *(d++) = '_';
641                         c++;
642                 } else {
643                         if (*c=='.' && *(c+1) == '.') {
644                                 c += 2;
645                                 *(d++) = '_'; *(d++) ='_';      
646                         } else {
647                                 *(d++) = *(c++);
648                         }
649                 }
650     }
651     *(d++) = '"';
652     *(d) = '\0';
653     free(buf);
654     return buf2;
655   }
656   return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
660 char* cgiget(long argc, char **args){
661   if (argc>= 1)
662     return rrdstrip(cgiGetValue(cgiArg,args[0]));
663   else
664     return stralloc("[ERROR: not enough arguments for RRD::CV]");
669 char* drawgraph(long argc, char **args){
670   int i,xsize, ysize;
671   for(i=0;i<argc;i++)
672     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
673   if(i==argc) {
674     args[argc++] = "--imginfo";
675     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
676   }
677   optind=0; /* reset gnu getopt */
678   opterr=0; /* reset gnu getopt */
679   calfree();
680   if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize) != -1 ) {
681     return stralloc(calcpr[0]);
682   } else {
683     if (rrd_test_error()) {
684       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
685       sprintf(err, "[ERROR: %s]",rrd_get_error());
686       rrd_clear_error();
687       calfree();
688       return err;
689     }
690   }
691   return NULL;
694 char* drawprint(long argc, char **args){
695   if (argc==1 && calcpr){
696     long i=0;
697     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
698     if (atol(args[0])<i-1)
699       return stralloc(calcpr[atol(args[0])+1]);    
700   }
701   return stralloc("[ERROR: RRD::PRINT argument error]");
704 char* printtimelast(long argc, char **args) {
705   time_t last;
706   struct tm tm_last;
707   char *buf;
708   if ( argc == 2 ) {
709     buf = malloc(255);
710     if (buf == NULL){   
711         return stralloc("[ERROR: allocating strftime buffer]");
712     };
713     last = rrd_last(argc+1, args-1); 
714     if (rrd_test_error()) {
715       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
716       sprintf(err, "[ERROR: %s]",rrd_get_error());
717       rrd_clear_error();
718       return err;
719     }
720     tm_last = *localtime(&last);
721     strftime(buf,254,args[1],&tm_last);
722     return buf;
723   }
724   if ( argc < 2 ) {
725     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
726   }
727   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
730 char* printtimenow(long argc, char **args) {
731   time_t now = time(NULL);
732   struct tm tm_now;
733   char *buf;
734   if ( argc == 1 ) {
735     buf = malloc(255);
736     if (buf == NULL){   
737         return stralloc("[ERROR: allocating strftime buffer]");
738     };
739     tm_now = *localtime(&now);
740     strftime(buf,254,args[0],&tm_now);
741     return buf;
742   }
743   if ( argc < 1 ) {
744     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
745   }
746   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
749 /* Scan buffer until an unescaped '>' arives.
750  * Update argument array with arguments found.
751  * Return end cursor where parsing stopped, or NULL in case of failure.
752  *
753  * FIXME:
754  * To allow nested constructs, we call rrd_expand_vars() for arguments
755  * that contain RRD::x directives. These introduce a small memory leak
756  * since we have to stralloc the arguments the way parse() works.
757  */
758 char*
759 scanargs(char *line, int *argument_count, char ***arguments)
761         char    *getP;          /* read cursor */
762         char    *putP;          /* write cursor */
763         char    Quote;          /* type of quote if in quoted string, 0 otherwise */
764         int     tagcount;       /* open tag count */
765         int     in_arg;         /* if we currently are parsing an argument or not */
766         int     argsz;          /* argument array size */
767         int             curarg_contains_rrd_directives;
769         /* local array of arguments while parsing */
770         int argc = 0;
771         char** argv;
773 #ifdef DEBUG_PARSER
774         printf("<-- scanargs(%s) -->\n", line);
775 #endif
777         *arguments = NULL;
778         *argument_count = 0;
780         /* create initial argument array of char pointers */
781         argsz = 32;
782         argv = (char **)malloc(argsz * sizeof(char *));
783         if (!argv) {
784                 return NULL;
785         }
787         /* skip leading blanks */
788         while (isspace((int)*line)) {
789                 line++;
790         }
792         getP = line;
793         putP = line;
795         Quote    = 0;
796         in_arg   = 0;
797         tagcount = 0;
799         curarg_contains_rrd_directives = 0;
801         /* start parsing 'line' for arguments */
802         while (*getP)
803         {
804                 unsigned char c = *getP++;
806                 if (c == '>' && !Quote && !tagcount) {
807                         /* this is our closing tag, quit scanning */
808                         break;
809                 }
811                 /* remove all special chars */
812                 if (c < ' ') {
813                         c = ' ';
814                 }
816                 switch (c)
817                 {
818                 case ' ': 
819                         if (Quote || tagcount) {
820                                 /* copy quoted/tagged string */
821                                 *putP++ = c;
822                         }
823                         else if (in_arg)
824                         {
825                                 /* end argument string */
826                                 *putP++ = 0;
827                                 in_arg = 0;
828                                 if (curarg_contains_rrd_directives) {
829                                         argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
830                                         curarg_contains_rrd_directives = 0;
831                                 }
832                         }
833                         break;
835                 case '"': /* Fall through */
836                 case '\'':
837                         if (Quote != 0) {
838                                 if (Quote == c) {
839                                         Quote = 0;
840                                 } else {
841                                         /* copy quoted string */
842                                         *putP++ = c;
843                                 }
844                         } else {
845                                 if (!in_arg) {
846                                         /* reference argument string in argument array */
847                                         argv[argc++] = putP;
848                                         in_arg=1;
849                                 }
850                                 Quote = c;
851                         }
852                         break;
854                 default:
855                         if (!Quote) {
856                                 if (!in_arg) {
857                                         /* start new argument */
858                                         argv[argc++] = putP;
859                                         in_arg = 1;
860                                 }
861                                 if (c == '>') {
862                                         if (tagcount) {
863                                                 tagcount--;
864                                         }
865                                 }
866                                 if (c == '<') {
867                                         tagcount++;
868                                         if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
869                                                 curarg_contains_rrd_directives = 1;
870                                         }
871                                 }
872                         }
873                         *putP++ = c;
874                         break;
875                 }
877                 /* check if our argument array is still large enough */
878                 if (argc == argsz) {
879                         /* resize argument array */
880                         argsz *= 2;
881                         argv = rrd_realloc(argv, argsz * sizeof(char *));
882                         if (*argv == NULL) {
883                                 return NULL;
884                         }
885                 }
886         }
888         /* terminate last argument found */
889         *putP = '\0';
890         if (curarg_contains_rrd_directives) {
891                 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
892         }
894 #ifdef DEBUG_PARSER
895         if (argc > 0) {
896                 int n;
897                 printf("<-- arguments found [%d]\n", argc);
898                 for (n=0; n<argc; n++) {
899                         printf("arg %02d: '%s'\n", n, argv[n]);
900                 }
901                 printf("-->\n");
902         } else {
903                 printf("<!-- No arguments found -->\n");
904         }
905 #endif
907         /* update caller's notion of the argument array and it's size */
908         *arguments = argv;
909         *argument_count = argc;
911         if (Quote) {
912                 return NULL;
913         }
915         /* Return new scanning cursor:
916            pointer to char after closing bracket */
917         return getP;
921 /*
922  * Parse(): scan current portion of buffer for given tag.
923  * If found, parse tag arguments and call 'func' for it.
924  * The result of func is inserted at the current position
925  * in the buffer.
926  */
927 int
928 parse(
929         char **buf,     /* buffer */
930         long i,                 /* offset in buffer */
931         char *tag,      /* tag to handle  */
932         char *(*func)(long argc, char **args) /* function to call for 'tag' */
933         )
935         /* the name of the vairable ... */
936         char *val;
937         long valln;  
938         char **args;
939         char *end;
940         long end_offset;
941         int  argc;
942         size_t taglen = strlen(tag);
944         /* Current position in buffer should start with 'tag' */
945         if (strncmp((*buf)+i, tag, taglen) != 0) {
946                 return 0;
947         }
948         /* .. and match exactly (a whitespace following 'tag') */
949         if (! isspace(*((*buf) + i + taglen)) ) {
950                 return 0;
951         }
953 #ifdef DEBUG_PARSER
954         printf("parse(): handeling tag '%s'\n", tag);
955 #endif
957         /* Scan for arguments following the tag;
958            scanargs() puts \0 into *buf ... so after scanargs it is probably
959            not a good time to use strlen on buf */
960         end = scanargs((*buf) + i + taglen, &argc, &args);
961         if (end)
962         {
963                 /* got arguments, call function for 'tag' with arguments */
964                 val = func(argc, args);
965                 free(args);
966         }
967         else
968         {
969                 /* unable to parse arguments, undo 0-termination by scanargs */
970                 for (; argc > 0; argc--) {
971                         *((args[argc-1])-1) = ' ';
972                 }
974                 /* next call, try parsing at current offset +1 */
975                 end = (*buf) + i + 1;
977                 val = stralloc("[ERROR: Parsing Problem with the following text\n"
978                                                 " Check original file. This may have been altered "
979                                                 "by parsing.]\n\n");
980         }
982         /* remember offset where we have to continue parsing */
983         end_offset = end - (*buf);
985         valln = 0;
986         if (val) {
987                 valln = strlen(val);
988         }
990         /* Optionally resize buffer to hold the replacement value:
991            Calculating the new length of the buffer is simple. add current
992            buffer pos (i) to length of string after replaced tag to length
993            of replacement string and add 1 for the final zero ... */
994         if (end - (*buf) < (i + valln)) {
995                 /* make sure we do not shrink the mallocd block */
996                 size_t newbufsize = i + strlen(end) + valln + 1;
997                 *buf = rrd_realloc(*buf, newbufsize);
999                 if (*buf == NULL) {
1000                         perror("Realoc buf:");
1001                         exit(1);
1002                 };
1003         }
1005         /* Update new end pointer:
1006            make sure the 'end' pointer gets moved along with the 
1007            buf pointer when realloc moves memory ... */
1008         end = (*buf) + end_offset; 
1010         /* splice the variable:
1011            step 1. Shift pending data to make room for 'val' */
1012         memmove((*buf) + i + valln, end, strlen(end) + 1);
1014         /* step 2. Insert val */
1015         if (val) {
1016                 memmove((*buf)+i, val, valln);
1017                 free(val);
1018         }
1019         return (valln > 0 ? valln-1: valln);
1022 char *
1023 http_time(time_t *now) {
1024         struct tm *tmptime;
1025         static char buf[60];
1027         tmptime=gmtime(now);
1028         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1029         return(buf);