Update displayed copyright messages to be consistent. -- Mike Slifcak
[rrdtool.git] / 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  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
10
11
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
15
16 /* global variable for libcgi */
17 s_cgi **cgiArg;
18
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 **));
22
23 /**************************************************/
24 /* tag replacers ... they are called from parse   */
25 /* through function pointers                      */
26 /**************************************************/
27
28 /* return cgi var named arg[0] */ 
29 char* cgiget(long , char **);
30
31 /* return a quoted cgi var named arg[0] */ 
32 char* cgigetq(long , char **);
33
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , char **);
36
37 /* call rrd_graph and insert appropriate image tag */
38 char* drawgraph(long, char **);
39
40 /* return PRINT functions from last rrd_graph call */
41 char* drawprint(long, char **);
42
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, char **);
45
46 /* pretty-print current time */
47 char* printtimenow(long,char **);
48
49 /* set an environment variable */
50 char* rrdsetenv(long, char **);
51
52 /* get an environment variable */
53 char* rrdgetenv(long, char **);
54
55 /* include the named file at this point */
56 char* includefile(long, char **);
57
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, char **);
60
61 char* rrdstrip(char *buf);
62 char* scanargs(char *line, int *argc, char ***args);
63
64 /* format at-time specified times using strftime */
65 char* printstrftime(long, char**);
66
67 /** HTTP protocol needs special format, and GMT time **/
68 char *http_time(time_t *);
69
70 /* return a pointer to newly allocated copy of this string */
71 char *stralloc(const char *);
72
73
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);
78
79
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);
85
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;
93
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;
99
100 /* allocate and initialize variable heap */
101 static int
102 initvar()
103 {
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;
112 }
113
114 /* cleanup: free allocated memory */
115 static void
116 donevar()
117 {
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         }
130 }
131
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)
136 {
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;
150 }
151
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)
158 {
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         }
181
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);
198
199 #ifdef          DEBUG_VARS
200         printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
201 #endif
202         return varheap[i].value;
203 }
204
205 /* expand those RRD:* directives that can be used recursivly */
206 static char*
207 rrd_expand_vars(char* buffer)
208 {
209         int i;
210
211 #ifdef DEBUG_PARSER
212         printf("expanding variables in '%s'\n", buffer);
213 #endif
214
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;
225 }
226
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   }
241 }
242
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);
252 }
253
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                 }
280
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         }
290
291         if (!filter) {
292                 cgiDebug(0,0);
293                 cgiArg = cgiInit();
294                 server_url = getenv("SERVER_URL");
295         }
296
297         /* make sure we have one extra argument, 
298            if there are others, we do not care Apache gives several */
299
300         /* if ( (optind != argc-2 
301            && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
302            && optind != argc-1) { */
303
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         }
310
311         if(rrd_test_error()) {
312                 fprintf(stderr, "ERROR: %s\n",rrd_get_error());
313                 exit(1);
314         }
315
316         /* initialize variable heap */
317         initvar();
318
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         }
341
342         if (!filter) {
343                 printf ("Content-Type: text/html\n" 
344                                 "Content-Length: %d\n", 
345                                 strlen(buffer));
346
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         }
359
360         /* output result */
361         printf("%s", buffer);
362
363         /* cleanup */
364         calfree();
365         if (buffer){
366                 free(buffer);
367         }
368         donevar();
369         exit(0);
370 }
371
372 /* remove occurrences of .. this is a general measure to make
373    paths which came in via cgi do not go UP ... */
374
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]");
389 }
390
391 /* rrd interface to the variable function putvar() */
392 char*
393 rrdsetvar(long argc, char **args)
394 {
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]");
406 }
407
408 /* rrd interface to the variable function putvar() */
409 char*
410 rrdsetvarconst(long argc, char **args)
411 {
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]");
423 }
424
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         }
439 }
440
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         }
455 }
456
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("");
469 }
470
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  rrd_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;
481
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         }
487
488         /* Init start and end time */
489         parsetime("end-24h", &start_tv);
490         parsetime("now", &end_tv);
491
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         }
504
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         }
516
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         }
525 }
526
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   }
545 }
546
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   }
558
559   p = buf;
560   while (*p) {
561           if (*p == '<' || *p == '>') {
562                   *p = '_';
563           }
564           p++;
565   }
566   return buf;
567 }
568
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;
576
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   }
600
601   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
602 }
603
604 /* remove occurrences of .. this is a general measure to make
605    paths which came in via cgi do not go UP ... */
606
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;
613
614     if (buf==NULL) 
615                 return NULL;
616
617     for(c=buf;*c != '\0';c++) {
618         if (*c == '"') {
619                         qc++;
620                 }
621         }
622
623     if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
624                 perror("Malloc Buffer");
625                 exit(1);
626     };
627
628     c=buf;
629     d=buf2;
630
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]");
657 }
658
659
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]");
665 }
666
667
668
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,NULL) != -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;
692 }
693
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]");
702 }
703
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]");
728 }
729
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]");
747 }
748
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)
760 {
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;
768
769         /* local array of arguments while parsing */
770         int argc = 0;
771         char** argv;
772
773 #ifdef DEBUG_PARSER
774         printf("<-- scanargs(%s) -->\n", line);
775 #endif
776
777         *arguments = NULL;
778         *argument_count = 0;
779
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         }
786
787         /* skip leading blanks */
788         while (isspace((int)*line)) {
789                 line++;
790         }
791
792         getP = line;
793         putP = line;
794
795         Quote    = 0;
796         in_arg   = 0;
797         tagcount = 0;
798
799         curarg_contains_rrd_directives = 0;
800
801         /* start parsing 'line' for arguments */
802         while (*getP)
803         {
804                 unsigned char c = *getP++;
805
806                 if (c == '>' && !Quote && !tagcount) {
807                         /* this is our closing tag, quit scanning */
808                         break;
809                 }
810
811                 /* remove all special chars */
812                 if (c < ' ') {
813                         c = ' ';
814                 }
815
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;
834
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;
853
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                 }
876
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         }
887
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         }
893
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
906
907         /* update caller's notion of the argument array and it's size */
908         *arguments = argv;
909         *argument_count = argc;
910
911         if (Quote) {
912                 return NULL;
913         }
914
915         /* Return new scanning cursor:
916            pointer to char after closing bracket */
917         return getP;
918 }
919
920
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         )
934 {
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);
943
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         }
952
953 #ifdef DEBUG_PARSER
954         printf("parse(): handeling tag '%s'\n", tag);
955 #endif
956
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                 }
973
974                 /* next call, try parsing at current offset +1 */
975                 end = (*buf) + i + 1;
976
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         }
981
982         /* remember offset where we have to continue parsing */
983         end_offset = end - (*buf);
984
985         valln = 0;
986         if (val) {
987                 valln = strlen(val);
988         }
989
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);
998
999                 if (*buf == NULL) {
1000                         perror("Realoc buf:");
1001                         exit(1);
1002                 };
1003         }
1004
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; 
1009
1010         /* splice the variable:
1011            step 1. Shift pending data to make room for 'val' */
1012         memmove((*buf) + i + valln, end, strlen(end) + 1);
1013
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);
1020 }
1021
1022 char *
1023 http_time(time_t *now) {
1024         struct tm *tmptime;
1025         static char buf[60];
1026
1027         tmptime=gmtime(now);
1028         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1029         return(buf);
1030 }