1 /*****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2004
3 *****************************************************************************
4 * rrd_cgi.c RRD Web Page Generator
5 *****************************************************************************/
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
16 /* global variable for libcgi */
17 s_cgi *cgiArg;
19 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
20 and replace by result of arg[2] call */
21 int parse(char **, long, char *, char *(*)(long , const char **));
23 /**************************************************/
24 /* tag replacers ... they are called from parse */
25 /* through function pointers */
26 /**************************************************/
28 /* return cgi var named arg[0] */
29 char* cgiget(long , const char **);
31 /* return a quoted cgi var named arg[0] */
32 char* cgigetq(long , const char **);
34 /* return a quoted and sanitized cgi variable */
35 char* cgigetqp(long , const char **);
37 /* call rrd_graph and insert appropriate image tag */
38 char* drawgraph(long, char **);
40 /* return PRINT functions from last rrd_graph call */
41 char* drawprint(long, const char **);
43 /* pretty-print the <last></last> value for some.rrd via strftime() */
44 char* printtimelast(long, const char **);
46 /* pretty-print current time */
47 char* printtimenow(long, const char **);
49 /* set an environment variable */
50 char* rrdsetenv(long, const char **);
52 /* get an environment variable */
53 char* rrdgetenv(long, const char **);
55 /* include the named file at this point */
56 char* includefile(long, const char **);
58 /* for how long is the output of the cgi valid ? */
59 char* rrdgoodfor(long, const char **);
61 char* rrdstrip(char *buf);
62 char* scanargs(char *line, int *argc, char ***args);
64 /* format at-time specified times using strftime */
65 char* printstrftime(long, const char**);
67 /** HTTP protocol needs special format, and GMT time **/
68 char *http_time(time_t *);
70 /* return a pointer to newly allocated copy of this string */
71 char *stralloc(const char *);
74 /* rrd interface to the variable functions {put,get}var() */
75 char* rrdgetvar(long argc, const char **args);
76 char* rrdsetvar(long argc, const char **args);
77 char* rrdsetvarconst(long argc, const char **args);
80 /* variable store: put/get key-value pairs */
81 static int initvar();
82 static void donevar();
83 static const char* getvar(const char* varname);
84 static const char* putvar(const char* name, const char* value, int is_const);
86 /* key value pair that makes up an entry in the variable store */
87 typedef struct
88 {
89 int is_const; /* const variable or not */
90 const char* name; /* variable name */
91 const char* value; /* variable value */
92 } vardata;
94 /* the variable heap:
95 start with a heapsize of 10 variables */
96 #define INIT_VARSTORE_SIZE 10
97 static vardata* varheap = NULL;
98 static size_t varheap_size = 0;
100 /* allocate and initialize variable heap */
101 static int
102 initvar()
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 }
396 return stralloc("[ERROR: setenv failed because not enough "
397 "arguments were defined]");
398 }
400 /* rrd interface to the variable function putvar() */
401 char*
402 rrdsetvar(long argc, const char **args)
403 {
404 if (argc >= 2)
405 {
406 const char* result = putvar(args[0], args[1], 0 /* not const */);
407 if (result) {
408 /* setvar does not return the value set */
409 return stralloc("");
410 }
411 return stralloc("[ERROR: putvar failed]");
412 }
413 return stralloc("[ERROR: putvar failed because not enough arguments "
414 "were defined]");
415 }
417 /* rrd interface to the variable function putvar() */
418 char*
419 rrdsetvarconst(long argc, const char **args)
420 {
421 if (argc >= 2)
422 {
423 const char* result = putvar(args[0], args[1], 1 /* const */);
424 if (result) {
425 /* setvar does not return the value set */
426 return stralloc("");
427 }
428 return stralloc("[ERROR: putvar failed]");
429 }
430 return stralloc("[ERROR: putvar failed because not enough arguments "
431 "were defined]");
432 }
434 char* rrdgetenv(long argc, const char **args) {
435 char buf[128];
436 const char* envvar;
437 if (argc != 1) {
438 return stralloc("[ERROR: getenv failed because it did not "
439 "get 1 argument only]");
440 };
441 envvar = getenv(args[0]);
442 if (envvar) {
443 return stralloc(envvar);
444 } else {
445 #ifdef WIN32
446 _snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
447 #else
448 snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
449 #endif
450 return stralloc(buf);
451 }
452 }
454 char* rrdgetvar(long argc, const char **args) {
455 char buf[128];
456 const char* value;
457 if (argc != 1) {
458 return stralloc("[ERROR: getvar failed because it did not "
459 "get 1 argument only]");
460 };
461 value = getvar(args[0]);
462 if (value) {
463 return stralloc(value);
464 } else {
465 #ifdef WIN32
466 _snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
467 #else
468 snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
469 #endif
470 return stralloc(buf);
471 }
472 }
474 char* rrdgoodfor(long argc, const char **args){
475 if (argc == 1) {
476 goodfor = atol(args[0]);
477 } else {
478 return stralloc("[ERROR: goodfor expected 1 argument]");
479 }
481 if (goodfor == 0){
482 return stralloc("[ERROR: goodfor value must not be 0]");
483 }
485 return stralloc("");
486 }
488 /* Format start or end times using strftime. We always need both the
489 * start and end times, because, either might be relative to the other.
490 * */
491 #define MAX_STRFTIME_SIZE 256
492 char* printstrftime(long argc, const char **args){
493 struct rrd_time_value start_tv, end_tv;
494 char *parsetime_error = NULL;
495 char formatted[MAX_STRFTIME_SIZE];
496 struct tm *the_tm;
497 time_t start_tmp, end_tmp;
499 /* Make sure that we were given the right number of args */
500 if( argc != 4) {
501 rrd_set_error( "wrong number of args %d", argc);
502 return (char *) -1;
503 }
505 /* Init start and end time */
506 parsetime("end-24h", &start_tv);
507 parsetime("now", &end_tv);
509 /* Parse the start and end times we were given */
510 if( (parsetime_error = parsetime( args[1], &start_tv))) {
511 rrd_set_error( "start time: %s", parsetime_error);
512 return (char *) -1;
513 }
514 if( (parsetime_error = parsetime( args[2], &end_tv))) {
515 rrd_set_error( "end time: %s", parsetime_error);
516 return (char *) -1;
517 }
518 if( proc_start_end( &start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
519 return (char *) -1;
520 }
522 /* Do we do the start or end */
523 if( strcasecmp( args[0], "START") == 0) {
524 the_tm = localtime( &start_tmp);
525 }
526 else if( strcasecmp( args[0], "END") == 0) {
527 the_tm = localtime( &end_tmp);
528 }
529 else {
530 rrd_set_error( "start/end not found in '%s'", args[0]);
531 return (char *) -1;
532 }
534 /* now format it */
535 if( strftime( formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
536 return( stralloc( formatted));
537 }
538 else {
539 rrd_set_error( "strftime failed");
540 return (char *) -1;
541 }
542 }
544 char* includefile(long argc, const char **args){
545 char *buffer;
546 if (argc >= 1) {
547 char* filename = args[0];
548 readfile(filename, &buffer, 0);
549 if (rrd_test_error()) {
550 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
551 sprintf(err, "[ERROR: %s]",rrd_get_error());
552 rrd_clear_error();
553 return err;
554 } else {
555 return buffer;
556 }
557 }
558 else
559 {
560 return stralloc("[ERROR: No Inclue file defined]");
561 }
562 }
564 /* make a copy of buf and replace open/close brackets with '_' */
565 char* rrdstrip(char *buf) {
566 char* p;
567 if (buf == NULL) {
568 return NULL;
569 }
570 /* make a copy of the buffer */
571 buf = stralloc(buf);
572 if (buf == NULL) {
573 return NULL;
574 }
576 p = buf;
577 while (*p) {
578 if (*p == '<' || *p == '>') {
579 *p = '_';
580 }
581 p++;
582 }
583 return buf;
584 }
586 char* cgigetq(long argc, const char **args){
587 if (argc>= 1){
588 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
589 char *buf2;
590 char *c,*d;
591 int qc=0;
592 if (buf==NULL) return NULL;
594 for(c=buf;*c != '\0';c++)
595 if (*c == '"') qc++;
596 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
597 perror("Malloc Buffer");
598 exit(1);
599 };
600 c=buf;
601 d=buf2;
602 *(d++) = '"';
603 while(*c != '\0'){
604 if (*c == '"') {
605 *(d++) = '"';
606 *(d++) = '\'';
607 *(d++) = '"';
608 *(d++) = '\'';
609 }
610 *(d++) = *(c++);
611 }
612 *(d++) = '"';
613 *(d) = '\0';
614 free(buf);
615 return buf2;
616 }
618 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
619 }
621 /* remove occurrences of .. this is a general measure to make
622 paths which came in via cgi do not go UP ... */
624 char* cgigetqp(long argc, const char **args){
625 char* buf;
626 char* buf2;
627 char* p;
628 char* d;
630 if (argc < 1)
631 {
632 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
633 }
635 buf = rrdstrip(cgiGetValue(cgiArg, args[0]));
636 if (!buf)
637 {
638 return NULL;
639 }
641 buf2 = malloc(strlen(buf)+1);
642 if (!buf2)
643 {
644 perror("cgigetqp(): Malloc Path Buffer");
645 exit(1);
646 };
648 p = buf;
649 d = buf2;
651 while (*p)
652 {
653 /* prevent mallicious paths from entering the system */
654 if (p[0] == '.' && p[1] == '.')
655 {
656 p += 2;
657 *d++ = '_';
658 *d++ = '_';
659 }
660 else
661 {
662 *d++ = *p++;
663 }
664 }
666 *d = 0;
667 free(buf);
669 /* Make sure the path is relative, e.g. does not start with '/' */
670 p = buf2;
671 while ('/' == *p)
672 {
673 *p++ = '_';
674 }
676 return buf2;
677 }
680 char* cgiget(long argc, const char **args){
681 if (argc>= 1)
682 return rrdstrip(cgiGetValue(cgiArg,args[0]));
683 else
684 return stralloc("[ERROR: not enough arguments for RRD::CV]");
685 }
689 char* drawgraph(long argc, char **args){
690 int i,xsize, ysize;
691 for(i=0;i<argc;i++)
692 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
693 if(i==argc) {
694 args[argc++] = "--imginfo";
695 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
696 }
697 optind=0; /* reset gnu getopt */
698 opterr=0; /* reset gnu getopt */
699 calfree();
700 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL) != -1 ) {
701 return stralloc(calcpr[0]);
702 } else {
703 if (rrd_test_error()) {
704 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
705 sprintf(err, "[ERROR: %s]",rrd_get_error());
706 rrd_clear_error();
707 calfree();
708 return err;
709 }
710 }
711 return NULL;
712 }
714 char* drawprint(long argc, const char **args){
715 if (argc==1 && calcpr){
716 long i=0;
717 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
718 if (atol(args[0])<i-1)
719 return stralloc(calcpr[atol(args[0])+1]);
720 }
721 return stralloc("[ERROR: RRD::PRINT argument error]");
722 }
724 char* printtimelast(long argc, const char **args) {
725 time_t last;
726 struct tm tm_last;
727 char *buf;
728 if ( argc == 2 ) {
729 buf = malloc(255);
730 if (buf == NULL){
731 return stralloc("[ERROR: allocating strftime buffer]");
732 };
733 last = rrd_last(argc+1, args-1);
734 if (rrd_test_error()) {
735 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
736 sprintf(err, "[ERROR: %s]",rrd_get_error());
737 rrd_clear_error();
738 return err;
739 }
740 tm_last = *localtime(&last);
741 strftime(buf,254,args[1],&tm_last);
742 return buf;
743 }
744 if ( argc < 2 ) {
745 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
746 }
747 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
748 }
750 char* printtimenow(long argc, const char **args) {
751 time_t now = time(NULL);
752 struct tm tm_now;
753 char *buf;
754 if ( argc == 1 ) {
755 buf = malloc(255);
756 if (buf == NULL){
757 return stralloc("[ERROR: allocating strftime buffer]");
758 };
759 tm_now = *localtime(&now);
760 strftime(buf,254,args[0],&tm_now);
761 return buf;
762 }
763 if ( argc < 1 ) {
764 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
765 }
766 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
767 }
769 /* Scan buffer until an unescaped '>' arives.
770 * Update argument array with arguments found.
771 * Return end cursor where parsing stopped, or NULL in case of failure.
772 *
773 * FIXME:
774 * To allow nested constructs, we call rrd_expand_vars() for arguments
775 * that contain RRD::x directives. These introduce a small memory leak
776 * since we have to stralloc the arguments the way parse() works.
777 */
778 char*
779 scanargs(char *line, int *argument_count, char ***arguments)
780 {
781 char *getP; /* read cursor */
782 char *putP; /* write cursor */
783 char Quote; /* type of quote if in quoted string, 0 otherwise */
784 int tagcount; /* open tag count */
785 int in_arg; /* if we currently are parsing an argument or not */
786 int argsz; /* argument array size */
787 int curarg_contains_rrd_directives;
789 /* local array of arguments while parsing */
790 int argc = 0;
791 char** argv;
793 #ifdef DEBUG_PARSER
794 printf("<-- scanargs(%s) -->\n", line);
795 #endif
797 *arguments = NULL;
798 *argument_count = 0;
800 /* create initial argument array of char pointers */
801 argsz = 32;
802 argv = (char **)malloc(argsz * sizeof(char *));
803 if (!argv) {
804 return NULL;
805 }
807 /* skip leading blanks */
808 while (isspace((int)*line)) {
809 line++;
810 }
812 getP = line;
813 putP = line;
815 Quote = 0;
816 in_arg = 0;
817 tagcount = 0;
819 curarg_contains_rrd_directives = 0;
821 /* start parsing 'line' for arguments */
822 while (*getP)
823 {
824 unsigned char c = *getP++;
826 if (c == '>' && !Quote && !tagcount) {
827 /* this is our closing tag, quit scanning */
828 break;
829 }
831 /* remove all special chars */
832 if (c < ' ') {
833 c = ' ';
834 }
836 switch (c)
837 {
838 case ' ':
839 if (Quote || tagcount) {
840 /* copy quoted/tagged (=RRD expanded) string */
841 *putP++ = c;
842 }
843 else if (in_arg)
844 {
845 /* end argument string */
846 *putP++ = 0;
847 in_arg = 0;
848 if (curarg_contains_rrd_directives) {
849 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
850 curarg_contains_rrd_directives = 0;
851 }
852 }
853 break;
855 case '"': /* Fall through */
856 case '\'':
857 if (Quote != 0) {
858 if (Quote == c) {
859 Quote = 0;
860 } else {
861 /* copy quoted string */
862 *putP++ = c;
863 }
864 } else {
865 if (!in_arg) {
866 /* reference start of argument string in argument array */
867 argv[argc++] = putP;
868 in_arg=1;
869 }
870 Quote = c;
871 }
872 break;
874 default:
875 if (!in_arg) {
876 /* start new argument */
877 argv[argc++] = putP;
878 in_arg = 1;
879 }
880 if (c == '>') {
881 if (tagcount) {
882 tagcount--;
883 }
884 }
885 if (c == '<') {
886 tagcount++;
887 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
888 curarg_contains_rrd_directives = 1;
889 }
890 }
891 *putP++ = c;
892 break;
893 }
895 /* check if our argument array is still large enough */
896 if (argc == argsz) {
897 /* resize argument array */
898 argsz *= 2;
899 argv = rrd_realloc(argv, argsz * sizeof(char *));
900 if (*argv == NULL) {
901 return NULL;
902 }
903 }
904 }
906 /* terminate last argument found */
907 *putP = '\0';
908 if (curarg_contains_rrd_directives) {
909 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
910 }
912 #ifdef DEBUG_PARSER
913 if (argc > 0) {
914 int n;
915 printf("<-- arguments found [%d]\n", argc);
916 for (n=0; n<argc; n++) {
917 printf("arg %02d: '%s'\n", n, argv[n]);
918 }
919 printf("-->\n");
920 } else {
921 printf("<!-- No arguments found -->\n");
922 }
923 #endif
925 /* update caller's notion of the argument array and it's size */
926 *arguments = argv;
927 *argument_count = argc;
929 if (Quote) {
930 return NULL;
931 }
933 /* Return new scanning cursor:
934 pointer to char after closing bracket */
935 return getP;
936 }
939 /*
940 * Parse(): scan current portion of buffer for given tag.
941 * If found, parse tag arguments and call 'func' for it.
942 * The result of func is inserted at the current position
943 * in the buffer.
944 */
945 int
946 parse(
947 char **buf, /* buffer */
948 long i, /* offset in buffer */
949 char *tag, /* tag to handle */
950 char *(*func)(long , const char **) /* function to call for 'tag' */
951 )
952 {
953 /* the name of the vairable ... */
954 char *val;
955 long valln;
956 char **args;
957 char *end;
958 long end_offset;
959 int argc;
960 size_t taglen = strlen(tag);
962 /* Current position in buffer should start with 'tag' */
963 if (strncmp((*buf)+i, tag, taglen) != 0) {
964 return 0;
965 }
966 /* .. and match exactly (a whitespace following 'tag') */
967 if (! isspace(*((*buf) + i + taglen)) ) {
968 return 0;
969 }
971 #ifdef DEBUG_PARSER
972 printf("parse(): handling tag '%s'\n", tag);
973 #endif
975 /* Scan for arguments following the tag;
976 scanargs() puts \0 into *buf ... so after scanargs it is probably
977 not a good time to use strlen on buf */
978 end = scanargs((*buf) + i + taglen, &argc, &args);
979 if (end)
980 {
981 /* got arguments, call function for 'tag' with arguments */
982 val = func(argc, args);
983 free(args);
984 }
985 else
986 {
987 /* unable to parse arguments, undo 0-termination by scanargs */
988 for (; argc > 0; argc--) {
989 *((args[argc-1])-1) = ' ';
990 }
992 /* next call, try parsing at current offset +1 */
993 end = (*buf) + i + 1;
995 val = stralloc("[ERROR: Parsing Problem with the following text\n"
996 " Check original file. This may have been altered "
997 "by parsing.]\n\n");
998 }
1000 /* remember offset where we have to continue parsing */
1001 end_offset = end - (*buf);
1003 valln = 0;
1004 if (val) {
1005 valln = strlen(val);
1006 }
1008 /* Optionally resize buffer to hold the replacement value:
1009 Calculating the new length of the buffer is simple. add current
1010 buffer pos (i) to length of string after replaced tag to length
1011 of replacement string and add 1 for the final zero ... */
1012 if (end - (*buf) < (i + valln)) {
1013 /* make sure we do not shrink the mallocd block */
1014 size_t newbufsize = i + strlen(end) + valln + 1;
1015 *buf = rrd_realloc(*buf, newbufsize);
1017 if (*buf == NULL) {
1018 perror("Realoc buf:");
1019 exit(1);
1020 };
1021 }
1023 /* Update new end pointer:
1024 make sure the 'end' pointer gets moved along with the
1025 buf pointer when realloc moves memory ... */
1026 end = (*buf) + end_offset;
1028 /* splice the variable:
1029 step 1. Shift pending data to make room for 'val' */
1030 memmove((*buf) + i + valln, end, strlen(end) + 1);
1032 /* step 2. Insert val */
1033 if (val) {
1034 memmove((*buf)+i, val, valln);
1035 free(val);
1036 }
1037 return (valln > 0 ? valln-1: valln);
1038 }
1040 char *
1041 http_time(time_t *now) {
1042 struct tm *tmptime;
1043 static char buf[60];
1045 tmptime=gmtime(now);
1046 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1047 return(buf);
1048 }