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 }
224 return buffer;
225 }
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 }
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 }
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);
370 }
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, const 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 }
391 /* rrd interface to the variable function putvar() */
392 char*
393 rrdsetvar(long argc, const 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 }
408 /* rrd interface to the variable function putvar() */
409 char*
410 rrdsetvarconst(long argc, const 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 }
425 char* rrdgetenv(long argc, const 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 }
441 char* rrdgetvar(long argc, const 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 }
457 char* rrdgoodfor(long argc, const char **args){
458 if (argc == 1) {
459 goodfor = atol(args[0]);
460 } else {
461 return stralloc("[ERROR: goodfor expected 1 argument]");
462 }
464 if (goodfor == 0){
465 return stralloc("[ERROR: goodfor value must not be 0]");
466 }
468 return stralloc("");
469 }
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, const 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;
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 }
525 }
527 char* includefile(long argc, const char **args){
528 char *buffer;
529 if (argc >= 1) {
530 readfile(args[0], &buffer, 0);
531 if (rrd_test_error()) {
532 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
533 sprintf(err, "[ERROR: %s]",rrd_get_error());
534 rrd_clear_error();
535 return err;
536 } else {
537 return buffer;
538 }
539 }
540 else
541 {
542 return stralloc("[ERROR: No Inclue file defined]");
543 }
544 }
546 /* make a copy of buf and replace open/close brackets with '_' */
547 char* rrdstrip(char *buf) {
548 char* p;
549 if (buf == NULL) {
550 return NULL;
551 }
552 /* make a copy of the buffer */
553 buf = stralloc(buf);
554 if (buf == NULL) {
555 return NULL;
556 }
558 p = buf;
559 while (*p) {
560 if (*p == '<' || *p == '>') {
561 *p = '_';
562 }
563 p++;
564 }
565 return buf;
566 }
568 char* cgigetq(long argc, const char **args){
569 if (argc>= 1){
570 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
571 char *buf2;
572 char *c,*d;
573 int qc=0;
574 if (buf==NULL) return NULL;
576 for(c=buf;*c != '\0';c++)
577 if (*c == '"') qc++;
578 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
579 perror("Malloc Buffer");
580 exit(1);
581 };
582 c=buf;
583 d=buf2;
584 *(d++) = '"';
585 while(*c != '\0'){
586 if (*c == '"') {
587 *(d++) = '"';
588 *(d++) = '\'';
589 *(d++) = '"';
590 *(d++) = '\'';
591 }
592 *(d++) = *(c++);
593 }
594 *(d++) = '"';
595 *(d) = '\0';
596 free(buf);
597 return buf2;
598 }
600 return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
601 }
603 /* remove occurrences of .. this is a general measure to make
604 paths which came in via cgi do not go UP ... */
606 char* cgigetqp(long argc, const char **args){
607 if (argc>= 1) {
608 char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
609 char *buf2;
610 char *c,*d;
611 int qc=0;
613 if (buf==NULL)
614 return NULL;
616 for(c=buf;*c != '\0';c++) {
617 if (*c == '"') {
618 qc++;
619 }
620 }
622 if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
623 perror("Malloc Buffer");
624 exit(1);
625 };
627 c=buf;
628 d=buf2;
630 *(d++) = '"';
631 while (*c != '\0') {
632 if (*c == '"') {
633 *(d++) = '"';
634 *(d++) = '\'';
635 *(d++) = '"';
636 *(d++) = '\'';
637 }
638 if(*c == '/') {
639 *(d++) = '_';
640 c++;
641 } else {
642 if (*c=='.' && *(c+1) == '.') {
643 c += 2;
644 *(d++) = '_'; *(d++) ='_';
645 } else {
646 *(d++) = *(c++);
647 }
648 }
649 }
650 *(d++) = '"';
651 *(d) = '\0';
652 free(buf);
653 return buf2;
654 }
655 return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
656 }
659 char* cgiget(long argc, const char **args){
660 if (argc>= 1)
661 return rrdstrip(cgiGetValue(cgiArg,args[0]));
662 else
663 return stralloc("[ERROR: not enough arguments for RRD::CV]");
664 }
668 char* drawgraph(long argc, char **args){
669 int i,xsize, ysize;
670 for(i=0;i<argc;i++)
671 if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
672 if(i==argc) {
673 args[argc++] = "--imginfo";
674 args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
675 }
676 optind=0; /* reset gnu getopt */
677 opterr=0; /* reset gnu getopt */
678 calfree();
679 if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize,NULL) != -1 ) {
680 return stralloc(calcpr[0]);
681 } else {
682 if (rrd_test_error()) {
683 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
684 sprintf(err, "[ERROR: %s]",rrd_get_error());
685 rrd_clear_error();
686 calfree();
687 return err;
688 }
689 }
690 return NULL;
691 }
693 char* drawprint(long argc, const char **args){
694 if (argc==1 && calcpr){
695 long i=0;
696 while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
697 if (atol(args[0])<i-1)
698 return stralloc(calcpr[atol(args[0])+1]);
699 }
700 return stralloc("[ERROR: RRD::PRINT argument error]");
701 }
703 char* printtimelast(long argc, const char **args) {
704 time_t last;
705 struct tm tm_last;
706 char *buf;
707 if ( argc == 2 ) {
708 buf = malloc(255);
709 if (buf == NULL){
710 return stralloc("[ERROR: allocating strftime buffer]");
711 };
712 last = rrd_last(argc+1, args-1);
713 if (rrd_test_error()) {
714 char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
715 sprintf(err, "[ERROR: %s]",rrd_get_error());
716 rrd_clear_error();
717 return err;
718 }
719 tm_last = *localtime(&last);
720 strftime(buf,254,args[1],&tm_last);
721 return buf;
722 }
723 if ( argc < 2 ) {
724 return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
725 }
726 return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
727 }
729 char* printtimenow(long argc, const char **args) {
730 time_t now = time(NULL);
731 struct tm tm_now;
732 char *buf;
733 if ( argc == 1 ) {
734 buf = malloc(255);
735 if (buf == NULL){
736 return stralloc("[ERROR: allocating strftime buffer]");
737 };
738 tm_now = *localtime(&now);
739 strftime(buf,254,args[0],&tm_now);
740 return buf;
741 }
742 if ( argc < 1 ) {
743 return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
744 }
745 return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
746 }
748 /* Scan buffer until an unescaped '>' arives.
749 * Update argument array with arguments found.
750 * Return end cursor where parsing stopped, or NULL in case of failure.
751 *
752 * FIXME:
753 * To allow nested constructs, we call rrd_expand_vars() for arguments
754 * that contain RRD::x directives. These introduce a small memory leak
755 * since we have to stralloc the arguments the way parse() works.
756 */
757 char*
758 scanargs(char *line, int *argument_count, char ***arguments)
759 {
760 char *getP; /* read cursor */
761 char *putP; /* write cursor */
762 char Quote; /* type of quote if in quoted string, 0 otherwise */
763 int tagcount; /* open tag count */
764 int in_arg; /* if we currently are parsing an argument or not */
765 int argsz; /* argument array size */
766 int curarg_contains_rrd_directives;
768 /* local array of arguments while parsing */
769 int argc = 0;
770 char** argv;
772 #ifdef DEBUG_PARSER
773 printf("<-- scanargs(%s) -->\n", line);
774 #endif
776 *arguments = NULL;
777 *argument_count = 0;
779 /* create initial argument array of char pointers */
780 argsz = 32;
781 argv = (char **)malloc(argsz * sizeof(char *));
782 if (!argv) {
783 return NULL;
784 }
786 /* skip leading blanks */
787 while (isspace((int)*line)) {
788 line++;
789 }
791 getP = line;
792 putP = line;
794 Quote = 0;
795 in_arg = 0;
796 tagcount = 0;
798 curarg_contains_rrd_directives = 0;
800 /* start parsing 'line' for arguments */
801 while (*getP)
802 {
803 unsigned char c = *getP++;
805 if (c == '>' && !Quote && !tagcount) {
806 /* this is our closing tag, quit scanning */
807 break;
808 }
810 /* remove all special chars */
811 if (c < ' ') {
812 c = ' ';
813 }
815 switch (c)
816 {
817 case ' ':
818 if (Quote || tagcount) {
819 /* copy quoted/tagged string */
820 *putP++ = c;
821 }
822 else if (in_arg)
823 {
824 /* end argument string */
825 *putP++ = 0;
826 in_arg = 0;
827 if (curarg_contains_rrd_directives) {
828 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
829 curarg_contains_rrd_directives = 0;
830 }
831 }
832 break;
834 case '"': /* Fall through */
835 case '\'':
836 if (Quote != 0) {
837 if (Quote == c) {
838 Quote = 0;
839 } else {
840 /* copy quoted string */
841 *putP++ = c;
842 }
843 } else {
844 if (!in_arg) {
845 /* reference argument string in argument array */
846 argv[argc++] = putP;
847 in_arg=1;
848 }
849 Quote = c;
850 }
851 break;
853 default:
854 if (!Quote) {
855 if (!in_arg) {
856 /* start new argument */
857 argv[argc++] = putP;
858 in_arg = 1;
859 }
860 if (c == '>') {
861 if (tagcount) {
862 tagcount--;
863 }
864 }
865 if (c == '<') {
866 tagcount++;
867 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
868 curarg_contains_rrd_directives = 1;
869 }
870 }
871 }
872 *putP++ = c;
873 break;
874 }
876 /* check if our argument array is still large enough */
877 if (argc == argsz) {
878 /* resize argument array */
879 argsz *= 2;
880 argv = rrd_realloc(argv, argsz * sizeof(char *));
881 if (*argv == NULL) {
882 return NULL;
883 }
884 }
885 }
887 /* terminate last argument found */
888 *putP = '\0';
889 if (curarg_contains_rrd_directives) {
890 argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
891 }
893 #ifdef DEBUG_PARSER
894 if (argc > 0) {
895 int n;
896 printf("<-- arguments found [%d]\n", argc);
897 for (n=0; n<argc; n++) {
898 printf("arg %02d: '%s'\n", n, argv[n]);
899 }
900 printf("-->\n");
901 } else {
902 printf("<!-- No arguments found -->\n");
903 }
904 #endif
906 /* update caller's notion of the argument array and it's size */
907 *arguments = argv;
908 *argument_count = argc;
910 if (Quote) {
911 return NULL;
912 }
914 /* Return new scanning cursor:
915 pointer to char after closing bracket */
916 return getP;
917 }
920 /*
921 * Parse(): scan current portion of buffer for given tag.
922 * If found, parse tag arguments and call 'func' for it.
923 * The result of func is inserted at the current position
924 * in the buffer.
925 */
926 int
927 parse(
928 char **buf, /* buffer */
929 long i, /* offset in buffer */
930 char *tag, /* tag to handle */
931 char *(*func)(long , const char **) /* function to call for 'tag' */
932 )
933 {
934 /* the name of the vairable ... */
935 char *val;
936 long valln;
937 char **args;
938 char *end;
939 long end_offset;
940 int argc;
941 size_t taglen = strlen(tag);
943 /* Current position in buffer should start with 'tag' */
944 if (strncmp((*buf)+i, tag, taglen) != 0) {
945 return 0;
946 }
947 /* .. and match exactly (a whitespace following 'tag') */
948 if (! isspace(*((*buf) + i + taglen)) ) {
949 return 0;
950 }
952 #ifdef DEBUG_PARSER
953 printf("parse(): handling tag '%s'\n", tag);
954 #endif
956 /* Scan for arguments following the tag;
957 scanargs() puts \0 into *buf ... so after scanargs it is probably
958 not a good time to use strlen on buf */
959 end = scanargs((*buf) + i + taglen, &argc, &args);
960 if (end)
961 {
962 /* got arguments, call function for 'tag' with arguments */
963 val = func(argc, args);
964 free(args);
965 }
966 else
967 {
968 /* unable to parse arguments, undo 0-termination by scanargs */
969 for (; argc > 0; argc--) {
970 *((args[argc-1])-1) = ' ';
971 }
973 /* next call, try parsing at current offset +1 */
974 end = (*buf) + i + 1;
976 val = stralloc("[ERROR: Parsing Problem with the following text\n"
977 " Check original file. This may have been altered "
978 "by parsing.]\n\n");
979 }
981 /* remember offset where we have to continue parsing */
982 end_offset = end - (*buf);
984 valln = 0;
985 if (val) {
986 valln = strlen(val);
987 }
989 /* Optionally resize buffer to hold the replacement value:
990 Calculating the new length of the buffer is simple. add current
991 buffer pos (i) to length of string after replaced tag to length
992 of replacement string and add 1 for the final zero ... */
993 if (end - (*buf) < (i + valln)) {
994 /* make sure we do not shrink the mallocd block */
995 size_t newbufsize = i + strlen(end) + valln + 1;
996 *buf = rrd_realloc(*buf, newbufsize);
998 if (*buf == NULL) {
999 perror("Realoc buf:");
1000 exit(1);
1001 };
1002 }
1004 /* Update new end pointer:
1005 make sure the 'end' pointer gets moved along with the
1006 buf pointer when realloc moves memory ... */
1007 end = (*buf) + end_offset;
1009 /* splice the variable:
1010 step 1. Shift pending data to make room for 'val' */
1011 memmove((*buf) + i + valln, end, strlen(end) + 1);
1013 /* step 2. Insert val */
1014 if (val) {
1015 memmove((*buf)+i, val, valln);
1016 free(val);
1017 }
1018 return (valln > 0 ? valln-1: valln);
1019 }
1021 char *
1022 http_time(time_t *now) {
1023 struct tm *tmptime;
1024 static char buf[60];
1026 tmptime=gmtime(now);
1027 strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
1028 return(buf);
1029 }