9e6aaa233e4342283eb661c90c2983dde964c661
1 /*****************************************************************************
2 * RRDtool 1.2rc8 Copyright by Tobi Oetiker, 1997-2005
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()
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 }
114 /* cleanup: free allocated memory */
115 static void
116 donevar()
117 {
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 }
130 }
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<(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;
150 }
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 < (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;
203 }
205 /* expand those RRD:* directives that can be used recursivly */
206 static char*
207 rrd_expand_vars(char* buffer)
208 {
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;
228 }
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 }
244 }
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);
255 }
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);
379 }
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 return stralloc("");
396 }
397 return stralloc("[ERROR: setenv failed because not enough "
398 "arguments were defined]");
399 }
401 /* rrd interface to the variable function putvar() */
402 char*
403 rrdsetvar(long argc, const char **args)
404 {
405 if (argc >= 2)
406 {
407 const char* result = putvar(args[0], args[1], 0 /* not const */);
408 if (result) {
409 /* setvar does not return the value set */
410 return stralloc("");
411 }
412 return stralloc("[ERROR: putvar failed]");
413 }
414 return stralloc("[ERROR: putvar failed because not enough arguments "
415 "were defined]");
416 }
418 /* rrd interface to the variable function putvar() */
419 char*
420 rrdsetvarconst(long argc, const char **args)
421 {
422 if (argc >= 2)
423 {
424 const char* result = putvar(args[0], args[1], 1 /* const */);
425 if (result) {
426 /* setvar does not return the value set */
427 return stralloc("");
428 }
429 return stralloc("[ERROR: putvar failed]");
430 }
431 return stralloc("[ERROR: putvar failed because not enough arguments "
432 "were defined]");
433 }
435 char* rrdgetenv(long argc, const char **args) {
436 char buf[128];
437 const char* envvar;
438 if (argc != 1) {
439 return stralloc("[ERROR: getenv failed because it did not "
440 "get 1 argument only]");
441 };
442 envvar = getenv(args[0]);
443 if (envvar) {
444 return stralloc(envvar);
445 } else {
446 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
447 _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
448 #else
449 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
450 #endif
451 return stralloc(buf);
452 }
453 }
455 char* rrdgetvar(long argc, const char **args) {
456 char buf[128];
457 const char* value;
458 if (argc != 1) {
459 return stralloc("[ERROR: getvar failed because it did not "
460 "get 1 argument only]");
461 };
462 value = getvar(args[0]);
463 if (value) {
464 return stralloc(value);
465 } else {
466 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
467 _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
468 #else
469 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
470 #endif
471 return stralloc(buf);
472 }
473 }
475 char* rrdgoodfor(long argc, const char **args){
476 if (argc == 1) {
477 goodfor = atol(args[0]);
478 } else {
479 return stralloc("[ERROR: goodfor expected 1 argument]");
480 }
482 if (goodfor == 0){
483 return stralloc("[ERROR: goodfor value must not be 0]");
484 }
486 return stralloc("");
487 }
489 /* Format start or end times using strftime. We always need both the
490 * start and end times, because, either might be relative to the other.
491 * */
492 #define MAX_STRFTIME_SIZE 256
493 char* printstrftime(long argc, const char **args){
494 struct rrd_time_value start_tv, end_tv;
495 char *parsetime_error = NULL;
496 char formatted[MAX_STRFTIME_SIZE];
497 struct tm *the_tm;
498 time_t start_tmp, end_tmp;
500 /* Make sure that we were given the right number of args */
501 if( argc != 4) {
502 rrd_set_error( "wrong number of args %d", argc);
503 return (char *) -1;
504 }
506 /* Init start and end time */
507 parsetime("end-24h", &start_tv);
508 parsetime("now", &end_tv);
510 /* Parse the start and end times we were given */
511 if( (parsetime_error = parsetime( args[1], &start_tv))) {
512 rrd_set_error( "start time: %s", parsetime_error);
513 return (char *) -1;
514 }
515 if( (parsetime_error = parsetime( args[2], &end_tv))) {
516 rrd_set_error( "end time: %s", parsetime_error);
517 return (char *) -1;
518 }
519 if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
520 return (char *) -1;
521 }
523 /* Do we do the start or end */
524 if( strcasecmp( args[0], "START") == 0) {
525 the_tm = localtime( &start_tmp);
526 }
527 else if( strcasecmp( args[0], "END") == 0) {
528 the_tm = localtime( &end_tmp);
529 }
530 else {
531 rrd_set_error( "start/end not found in '%s'", args[0]);
532 return (char *) -1;
533 }
535 /* now format it */
536 if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
537 return( stralloc( formatted));
538 }
539 else {
540 rrd_set_error( "strftime failed");
541 return (char *) -1;
542 }
543 }
545 char* includefile(long argc, const char **args){
546 char *buffer;
547 if (argc >= 1) {
548 char* filename = args[0];
549 readfile(filename, &buffer, 0);
550 if (rrd_test_error()) {
551 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
552 sprintf(err, "[ERROR: %s]",rrd_get_error());
553 rrd_clear_error();
554 return err;
555 } else {
556 return buffer;
557 }
558 }
559 else
560 {
561 return stralloc("[ERROR: No Inclue file defined]");
562 }
563 }
565 /* make a copy of buf and replace open/close brackets with '_' */
566 char* rrdstrip(char *buf) {
567 char* p;
568 if (buf == NULL) {
569 return NULL;
570 }
571 /* make a copy of the buffer */
572 buf = stralloc(buf);
573 if (buf == NULL) {
574 return NULL;
575 }
577 p = buf;
578 while (*p) {
579 if (*p == '<' || *p == '>') {
580 *p = '_';
581 }
582 p++;
583 }
584 return buf;
585 }
587 char* cgigetq(long argc, const char **args){
588 if (argc>= 1){
589 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
590 char *buf2;
591 char *c,*d;
592 int qc=0;
593 if (buf==NULL) return NULL;
595 for(c=buf;*c != '\0';c++)
596 if (*c == '"') qc++;
597 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
598 perror("Malloc Buffer");
599 exit(1);
600 };
601 c=buf;
602 d=buf2;
603 *(d++) = '"';
604 while(*c != '\0'){
605 if (*c == '"') {
606 *(d++) = '"';
607 *(d++) = '\'';
608 *(d++) = '"';
609 *(d++) = '\'';
610 }
611 *(d++) = *(c++);
612 }
613 *(d++) = '"';
614 *(d) = '\0';
615 free(buf);
616 return buf2;
617 }
619 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
620 }
622 /* remove occurrences of .. this is a general measure to make
623 paths which came in via cgi do not go UP ... */
625 char* cgigetqp(long argc, const char **args){
626 char* buf;
627 char* buf2;
628 char* p;
629 char* d;
631 if (argc < 1)
632 {
633 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
634 }
636 buf = rrdstrip(cgiGetValue(cgiArg, args[0]));
637 if (!buf)
638 {
639 return NULL;
640 }
642 buf2 = malloc(strlen(buf)+1);
643 if (!buf2)
644 {
645 perror("cgigetqp(): Malloc Path Buffer");
646 exit(1);
647 };
649 p = buf;
650 d = buf2;
652 while (*p)
653 {
654 /* prevent mallicious paths from entering the system */
655 if (p[0] == '.' && p[1] == '.')
656 {
657 p += 2;
658 *d++ = '_';
659 *d++ = '_';
660 }
661 else
662 {
663 *d++ = *p++;
664 }
665 }
667 *d = 0;
668 free(buf);
670 /* Make sure the path is relative, e.g. does not start with '/' */
671 p = buf2;
672 while ('/' == *p)
673 {
674 *p++ = '_';
675 }
677 return buf2;
678 }
681 char* cgiget(long argc, const char **args){
682 if (argc>= 1)
683 return rrdstrip(cgiGetValue(cgiArg,args[0]));
684 else
685 return stralloc("[ERROR: not enough arguments for RRD::CV]");
686 }
690 char* drawgraph(long argc, char **args){
691 int i,xsize, ysize;
692 double ymin,ymax;
693 for(i=0;i<argc;i++)
694 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
695 if(i==argc) {
696 args[argc++] = "--imginfo";
697 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
698 }
699 optind=0; /* reset gnu getopt */
700 opterr=0; /* reset gnu getopt */
701 calfree();
702 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL,&ymin,&ymax) != -1 ) {
703 return stralloc(calcpr[0]);
704 } else {
705 if (rrd_test_error()) {
706 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
707 sprintf(err, "[ERROR: %s]",rrd_get_error());
708 rrd_clear_error();
709 calfree();
710 return err;
711 }
712 }
713 return NULL;
714 }
716 char* drawprint(long argc, const char **args){
717 if (argc==1 && calcpr){
718 long i=0;
719 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
720 if (atol(args[0])<i-1)
721 return stralloc(calcpr[atol(args[0])+1]);
722 }
723 return stralloc("[ERROR: RRD::PRINT argument error]");
724 }
726 char* printtimelast(long argc, const char **args) {
727 time_t last;
728 struct tm tm_last;
729 char *buf;
730 if ( argc == 2 ) {
731 buf = malloc(255);
732 if (buf == NULL){
733 return stralloc("[ERROR: allocating strftime buffer]");
734 };
735 last = rrd_last(argc+1, args-1);
736 if (rrd_test_error()) {
737 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
738 sprintf(err, "[ERROR: %s]",rrd_get_error());
739 rrd_clear_error();
740 return err;
741 }
742 tm_last = *localtime(&last);
743 strftime(buf,254,args[1],&tm_last);
744 return buf;
745 }
746 if ( argc < 2 ) {
747 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
748 }
749 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
750 }
752 char* printtimenow(long argc, const char **args) {
753 time_t now = time(NULL);
754 struct tm tm_now;
755 char *buf;
756 if ( argc == 1 ) {
757 buf = malloc(255);
758 if (buf == NULL){
759 return stralloc("[ERROR: allocating strftime buffer]");
760 };
761 tm_now = *localtime(&now);
762 strftime(buf,254,args[0],&tm_now);
763 return buf;
764 }
765 if ( argc < 1 ) {
766 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
767 }
768 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
769 }
771 /* Scan buffer until an unescaped '>' arives.
772 * Update argument array with arguments found.
773 * Return end cursor where parsing stopped, or NULL in case of failure.
774 *
775 * FIXME:
776 * To allow nested constructs, we call rrd_expand_vars() for arguments
777 * that contain RRD::x directives. These introduce a small memory leak
778 * since we have to stralloc the arguments the way parse() works.
779 */
780 char*
781 scanargs(char *line, int *argument_count, char ***arguments)
782 {
783 char *getP; /* read cursor */
784 char *putP; /* write cursor */
785 char Quote; /* type of quote if in quoted string, 0 otherwise */
786 int tagcount; /* open tag count */
787 int in_arg; /* if we currently are parsing an argument or not */
788 int argsz; /* argument array size */
789 int curarg_contains_rrd_directives;
791 /* local array of arguments while parsing */
792 int argc = 0;
793 char** argv;
795 #ifdef DEBUG_PARSER
796 printf("<-- scanargs(%s) -->\n", line);
797 #endif
799 *arguments = NULL;
800 *argument_count = 0;
802 /* create initial argument array of char pointers */
803 argsz = 32;
804 argv = (char **)malloc(argsz * sizeof(char *));
805 if (!argv) {
806 return NULL;
807 }
809 /* skip leading blanks */
810 while (isspace((int)*line)) {
811 line++;
812 }
814 getP = line;
815 putP = line;
817 Quote = 0;
818 in_arg = 0;
819 tagcount = 0;
821 curarg_contains_rrd_directives = 0;
823 /* start parsing 'line' for arguments */
824 while (*getP)
825 {
826 unsigned char c = *getP++;
828 if (c == '>' && !Quote && !tagcount) {
829 /* this is our closing tag, quit scanning */
830 break;
831 }
833 /* remove all special chars */
834 if (c < ' ') {
835 c = ' ';
836 }
838 switch (c)
839 {
840 case ' ':
841 if (Quote || tagcount) {
842 /* copy quoted/tagged (=RRD expanded) string */
843 *putP++ = c;
844 }
845 else if (in_arg)
846 {
847 /* end argument string */
848 *putP++ = 0;
849 in_arg = 0;
850 if (curarg_contains_rrd_directives) {
851 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
852 curarg_contains_rrd_directives = 0;
853 }
854 }
855 break;
857 case '"': /* Fall through */
858 case '\'':
859 if (Quote != 0) {
860 if (Quote == c) {
861 Quote = 0;
862 } else {
863 /* copy quoted string */
864 *putP++ = c;
865 }
866 } else {
867 if (!in_arg) {
868 /* reference start of argument string in argument array */
869 argv[argc++] = putP;
870 in_arg=1;
871 }
872 Quote = c;
873 }
874 break;
876 default:
877 if (!in_arg) {
878 /* start new argument */
879 argv[argc++] = putP;
880 in_arg = 1;
881 }
882 if (c == '>') {
883 if (tagcount) {
884 tagcount--;
885 }
886 }
887 if (c == '<') {
888 tagcount++;
889 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
890 curarg_contains_rrd_directives = 1;
891 }
892 }
893 *putP++ = c;
894 break;
895 }
897 /* check if our argument array is still large enough */
898 if (argc == argsz) {
899 /* resize argument array */
900 argsz *= 2;
901 argv = rrd_realloc(argv, argsz * sizeof(char *));
902 if (*argv == NULL) {
903 return NULL;
904 }
905 }
906 }
908 /* terminate last argument found */
909 *putP = '\0';
910 if (curarg_contains_rrd_directives) {
911 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
912 }
914 #ifdef DEBUG_PARSER
915 if (argc > 0) {
916 int n;
917 printf("<-- arguments found [%d]\n", argc);
918 for (n=0; n<argc; n++) {
919 printf("arg %02d: '%s'\n", n, argv[n]);
920 }
921 printf("-->\n");
922 } else {
923 printf("<!-- No arguments found -->\n");
924 }
925 #endif
927 /* update caller's notion of the argument array and it's size */
928 *arguments = argv;
929 *argument_count = argc;
931 if (Quote) {
932 return NULL;
933 }
935 /* Return new scanning cursor:
936 pointer to char after closing bracket */
937 return getP;
938 }
941 /*
942 * Parse(): scan current portion of buffer for given tag.
943 * If found, parse tag arguments and call 'func' for it.
944 * The result of func is inserted at the current position
945 * in the buffer.
946 */
947 int
948 parse(
949 char **buf, /* buffer */
950 long i, /* offset in buffer */
951 char *tag, /* tag to handle */
952 char *(*func)(long , const char **) /* function to call for 'tag' */
953 )
954 {
955 /* the name of the vairable ... */
956 char *val;
957 long valln;
958 char **args;
959 char *end;
960 long end_offset;
961 int argc;
962 size_t taglen = strlen(tag);
964 /* Current position in buffer should start with 'tag' */
965 if (strncmp((*buf)+i, tag, taglen) != 0) {
966 return 0;
967 }
968 /* .. and match exactly (a whitespace following 'tag') */
969 if (! isspace(*((*buf) + i + taglen)) ) {
970 return 0;
971 }
973 #ifdef DEBUG_PARSER
974 printf("parse(): handling tag '%s'\n", tag);
975 #endif
977 /* Scan for arguments following the tag;
978 scanargs() puts \0 into *buf ... so after scanargs it is probably
979 not a good time to use strlen on buf */
980 end = scanargs((*buf) + i + taglen, &argc, &args);
981 if (end)
982 {
983 /* got arguments, call function for 'tag' with arguments */
984 val = func(argc, args);
985 free(args);
986 }
987 else
988 {
989 /* unable to parse arguments, undo 0-termination by scanargs */
990 for (; argc > 0; argc--) {
991 *((args[argc-1])-1) = ' ';
992 }
994 /* next call, try parsing at current offset +1 */
995 end = (*buf) + i + 1;
997 val = stralloc("[ERROR: Parsing Problem with the following text\n"
998 " Check original file. This may have been altered "
999 "by parsing.]\n\n");
1000 }
1002 /* remember offset where we have to continue parsing */
1003 end_offset = end - (*buf);
1005 valln = 0;
1006 if (val) {
1007 valln = strlen(val);
1008 }
1010 /* Optionally resize buffer to hold the replacement value:
1011 Calculating the new length of the buffer is simple. add current
1012 buffer pos (i) to length of string after replaced tag to length
1013 of replacement string and add 1 for the final zero ... */
1014 if (end - (*buf) < (i + valln)) {
1015 /* make sure we do not shrink the mallocd block */
1016 size_t newbufsize = i + strlen(end) + valln + 1;
1017 *buf = rrd_realloc(*buf, newbufsize);
1019 if (*buf == NULL) {
1020 perror("Realoc buf:");
1021 exit(1);
1022 };
1023 }
1025 /* Update new end pointer:
1026 make sure the 'end' pointer gets moved along with the
1027 buf pointer when realloc moves memory ... */
1028 end = (*buf) + end_offset;
1030 /* splice the variable:
1031 step 1. Shift pending data to make room for 'val' */
1032 memmove((*buf) + i + valln, end, strlen(end) + 1);
1034 /* step 2. Insert val */
1035 if (val) {
1036 memmove((*buf)+i, val, valln);
1037 free(val);
1038 }
1039 return (valln > 0 ? valln-1: valln);
1040 }
1042 char *
1043 http_time(time_t *now) {
1044 struct tm *tmptime;
1045 static char buf[60];
1047 tmptime=gmtime(now);
1048 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1049 return(buf);
1050 }