New recursive parser for rrdcgi by Arend-Jan Wijtzes <ajwytzes@wise-guys.nl>
authoroetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa>
Sun, 23 Nov 2003 12:55:37 +0000 (12:55 +0000)
committeroetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa>
Sun, 23 Nov 2003 12:55:37 +0000 (12:55 +0000)
git-svn-id: svn://svn.oetiker.ch/rrdtool/trunk/program@226 a5681a0c-68f1-0310-ab6d-d61299d08faa

doc/rrdcgi.pod
src/rrd_cgi.c

index cfa79a5..30c6692 100644 (file)
@@ -30,7 +30,7 @@ Assume that rrdcgi is being run as a filter and not as a cgi.
 
 =back
 
-=head2 Pass 1
+=head2 Keywords
 
 =over 8
 
@@ -60,11 +60,6 @@ Get the value of an environment variable.
 might give you the name of the remote user given you are using
 some sort of access control on the directory
 
-=back
-
-=head2 Pass 2
-
-=over 8
 
 =item RRD::GOODFOR I<seconds>
 
@@ -86,6 +81,14 @@ could use
 to make sure everything is presented in Universal Time. Note that the
 values permitted to TZ depend on your OS.
 
+=item RRD::SETVAR I<variable> I<value>
+
+Analog to SETENV but for local variables
+
+=item RRD::GETVAR I<variable> 
+
+Analog to GETENV but for local variables
+
 =item RRD::TIME::LAST I<rrd-file> I<strftime-format>
 
 This gets replaced by the last modification time of the selected RRD. The
@@ -105,12 +108,6 @@ must be supplied as either could be relative to the other.  This is intended
 to allow pretty titles on graphs with times that are easier for non rrdtool
 folks to figure out than "-2weeks".
 
-=back
-
-=head2 Pass 3
-
-=over 8
-
 =item RRD::GRAPH I<rrdgraph arguments>
 
 This tag creates the RRD graph defined in its argument and then gets
index 7f248ab..4a70792 100644 (file)
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
+ * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2003
  *****************************************************************************
  * rrd_cgi.c  RRD Web Page Generator
  *****************************************************************************/
@@ -10,6 +10,8 @@
 
 
 #define MEMBLK 1024
+/*#define DEBUG_PARSER
+#define DEBUG_VARS*/
 
 /* global variable for libcgi */
 s_cgi **cgiArg;
@@ -32,7 +34,7 @@ char* cgigetq(long , char **);
 /* return a quoted and sanitized cgi variable */
 char* cgigetqp(long , char **);
 
-/* call rrd_graph and insert apropriate image tag */
+/* call rrd_graph and insert appropriate image tag */
 char* drawgraph(long, char **);
 
 /* return PRINT functions from last rrd_graph call */
@@ -44,10 +46,10 @@ char* printtimelast(long, char **);
 /* pretty-print current time */
 char* printtimenow(long,char **);
 
-/* set an evironment variable */
+/* set an environment variable */
 char* rrdsetenv(long, char **);
 
-/* get an evironment variable */
+/* get an environment variable */
 char* rrdgetenv(long, char **);
 
 /* include the named file at this point */
@@ -56,14 +58,171 @@ char* includefile(long, char **);
 /* for how long is the output of the cgi valid ? */
 char* rrdgoodfor(long, char **);
 
+char* rrdstrip(char *buf);
+char* scanargs(char *line, int *argc, char ***args);
+
 /* format at-time specified times using strftime */
 char* printstrftime(long, char**);
 
-/** http protocol needs special format, and GMT time **/
+/** HTTP protocol needs special format, and GMT time **/
 char *http_time(time_t *);
 
-/* return a pointer to newly alocated copy of this string */
-char *stralloc(char *);
+/* return a pointer to newly allocated copy of this string */
+char *stralloc(const char *);
+
+
+/* rrd interface to the variable functions {put,get}var() */
+char* rrdgetvar(long argc, char **args);
+char* rrdsetvar(long argc, char **args);
+char* rrdsetvarconst(long argc, char **args);
+
+
+/* variable store: put/get key-value pairs */
+static int   initvar();
+static void  donevar();
+static const char* getvar(const char* varname);
+static const char* putvar(const char* name, const char* value, int is_const);
+
+/* key value pair that makes up an entry in the variable store */
+typedef struct
+{
+       int is_const;           /* const variable or not */
+       const char* name;       /* variable name */
+       const char* value;      /* variable value */
+} vardata;
+
+/* the variable heap: 
+   start with a heapsize of 10 variables */
+#define INIT_VARSTORE_SIZE     10
+static vardata* varheap    = NULL;
+static size_t varheap_size = 0;
+
+/* allocate and initialize variable heap */
+static int
+initvar()
+{
+       varheap = (vardata*)malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
+       if (varheap == NULL) {
+               fprintf(stderr, "ERROR: unable to initialize variable store\n");
+               return -1;
+       }
+       memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
+       varheap_size = INIT_VARSTORE_SIZE;
+       return 0;
+}
+
+/* cleanup: free allocated memory */
+static void
+donevar()
+{
+       int i;
+       if (varheap) {
+               for (i=0; i<varheap_size; i++) {
+                       if (varheap[i].name) {
+                               free((char*)varheap[i].name);
+                       }
+                       if (varheap[i].value) {
+                               free((char*)varheap[i].value);
+                       }
+               }
+               free(varheap);
+       }
+}
+
+/* Get a variable from the variable store.
+   Return NULL in case the requested variable was not found. */
+static const char*
+getvar(const char* name)
+{
+       int i;
+       for (i=0; i<varheap_size && varheap[i].name; i++) {
+               if (0 == strcmp(name, varheap[i].name)) {
+#ifdef         DEBUG_VARS
+                       printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
+#endif
+                       return varheap[i].value;
+               }
+       }
+#ifdef DEBUG_VARS
+       printf("<!-- getvar(%s) -> Not found-->\n", name);
+#endif
+       return NULL;
+}
+
+/* Put a variable into the variable store. If a variable by that
+   name exists, it's value is overwritten with the new value unless it was
+   marked as 'const' (initialized by RRD::SETCONSTVAR).
+   Returns a copy the newly allocated value on success, NULL on error. */
+static const char*
+putvar(const char* name, const char* value, int is_const)
+{
+       int i;
+       for (i=0; i < varheap_size && varheap[i].name; i++) {
+               if (0 == strcmp(name, varheap[i].name)) {
+                       /* overwrite existing entry */
+                       if (varheap[i].is_const) {
+#ifdef                 DEBUG_VARS
+                               printf("<!-- setver(%s, %s): not assigning: "
+                                               "const variable -->\n", name, value);
+#                              endif
+                               return varheap[i].value;
+                       }
+#ifdef         DEBUG_VARS
+                       printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
+                                       name, value, varheap[i].value);
+#endif
+                       /* make it possible to promote a variable to readonly */
+                       varheap[i].is_const = is_const;
+                       free((char*)varheap[i].value);
+                       varheap[i].value = stralloc(value);
+                       return varheap[i].value;
+               }
+       }
+
+       /* no existing variable found by that name, add it */
+       if (i == varheap_size) {
+               /* ran out of heap: resize heap to double size */
+               size_t new_size = varheap_size * 2;
+               varheap = (vardata*)(realloc(varheap, sizeof(vardata) * new_size));
+               if (!varheap) {
+                       fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
+                       return NULL;
+               }
+               /* initialize newly allocated memory */;
+               memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
+               varheap_size = new_size;
+       }
+       varheap[i].is_const = is_const;
+       varheap[i].name  = stralloc(name);
+       varheap[i].value = stralloc(value);
+
+#ifdef         DEBUG_VARS
+       printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
+#endif
+       return varheap[i].value;
+}
+
+/* expand those RRD:* directives that can be used recursivly */
+static char*
+rrd_expand_vars(char* buffer)
+{
+       int i;
+
+#ifdef DEBUG_PARSER
+       printf("expanding variables in '%s'\n", buffer);
+#endif
+
+       for (i=0; buffer[i]; i++) {
+               if (buffer[i] != '<')
+                       continue;
+               parse(&buffer, i, "<RRD::CV", cgiget);
+               parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
+               parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
+               parse(&buffer, i, "<RRD::GETENV", rrdgetenv);    
+               parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);    
+       }
+       return buffer;
+}
 
 static long goodfor=0;
 static char **calcpr=NULL;
@@ -82,150 +241,217 @@ static void calfree (void){
 }
 
 /* create freeable version of the string */
-char * stralloc(char *str){
-  char *nstr;
-  if (str == NULL) {  
-        return NULL;
+char * stralloc(const char *str){
+  charnstr;
+  if (!str) {
+         return NULL;
   }
-  nstr = malloc((strlen(str)+1)*sizeof(char));
+  nstr = malloc((strlen(str)+1));
   strcpy(nstr,str);
   return(nstr);
 }
 
 int main(int argc, char *argv[]) {
-  long length;
-  char *buffer;
-  char *server_url = NULL;
-  long i;
-  long filter=0;
+       long length;
+       char *buffer;
+       char *server_url = NULL;
+       long i;
+       long filter=0;
 #ifdef MUST_DISABLE_SIGFPE
-  signal(SIGFPE,SIG_IGN);
+       signal(SIGFPE,SIG_IGN);
 #endif
 #ifdef MUST_DISABLE_FPMASK
-  fpsetmask(0);
+       fpsetmask(0);
 #endif
-  /* what do we get for cmdline arguments?
-  for (i=0;i<argc;i++)
-  printf("%d-'%s'\n",i,argv[i]); */
-  while (1){
-      static struct option long_options[] =
-      {
-         {"filter",          no_argument, 0, 'f'},
-         {0,0,0,0}
-      };
-      int option_index = 0;
-      int opt;
-      opt = getopt_long(argc, argv, "f", 
-                       long_options, &option_index);
-      if (opt == EOF)
-         break;
-      switch(opt) {
-      case 'f':
-         filter=1;
-         break;
-      case '?':
-            printf("unknown commandline option '%s'\n",argv[optind-1]);
-            return -1;
-      }
-  }
+       /* what do we get for cmdline arguments?
+       for (i=0;i<argc;i++)
+       printf("%d-'%s'\n",i,argv[i]); */
+       while (1) {
+               static struct option long_options[] = {
+                       { "filter", no_argument, 0, 'f' },
+                       { 0, 0, 0, 0}
+               };
+               int option_index = 0;
+               int opt;
+               opt = getopt_long(argc, argv, "f", long_options, &option_index);
+               if (opt == EOF) {
+                       break;
+               }
+
+               switch(opt) {
+               case 'f':
+                               filter=1;
+                       break;
+               case '?':
+                       printf("unknown commandline option '%s'\n",argv[optind-1]);
+                       return -1;
+               }
+       }
 
-  if(filter==0) {
-      cgiDebug(0,0);
-      cgiArg = cgiInit ();
-      server_url = getenv("SERVER_URL");
-  }
+       if (!filter) {
+               cgiDebug(0,0);
+               cgiArg = cgiInit();
+               server_url = getenv("SERVER_URL");
+       }
 
-  if ( (optind != argc-2 && strstr(getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) && optind != argc-1) {
-    fprintf(stderr, "ERROR: expected a filename\n");
-    exit(1);
-  } else {
-     length  = readfile(argv[optind], &buffer, 1);
-  }
-   
-  if(rrd_test_error()){
-      fprintf(stderr, "ERROR: %s\n",rrd_get_error());
-      exit(1);
-  }
+       /* make sure we have one extra argument, 
+          if there are others, we do not care Apache gives several */
 
+       /* if ( (optind != argc-2 
+          && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
+          && optind != argc-1) { */
 
-  if(filter==0) {
-  /* pass 1 makes only sense in cgi mode */
-      for (i=0;buffer[i] != '\0'; i++){    
-         i +=parse(&buffer,i,"<RRD::CV",cgiget);
-         i +=parse(&buffer,i,"<RRD::CV::QUOTE",cgigetq);
-         i +=parse(&buffer,i,"<RRD::CV::PATH",cgigetqp);
-         i +=parse(&buffer,i,"<RRD::GETENV",rrdgetenv);         
-      }
-  }
+       if ( optind >= argc ) { 
+               fprintf(stderr, "ERROR: expected a filename\n");
+               exit(1);
+       } else {
+               length = readfile(argv[optind], &buffer, 1);
+       }
 
-  /* pass 2 */
-  for (i=0;buffer[i] != '\0'; i++){    
-      i += parse(&buffer,i,"<RRD::GOODFOR",rrdgoodfor);
-      i += parse(&buffer,i,"<RRD::SETENV",rrdsetenv);
-      i += parse(&buffer,i,"<RRD::INCLUDE",includefile);
-      i += parse(&buffer,i,"<RRD::TIME::LAST",printtimelast);
-      i += parse(&buffer,i,"<RRD::TIME::NOW",printtimenow);
-      i += parse(&buffer,i,"<RRD::TIME::STRFTIME",printstrftime);
-  }
+       if(rrd_test_error()) {
+               fprintf(stderr, "ERROR: %s\n",rrd_get_error());
+               exit(1);
+       }
 
-  /* pass 3 */
-  for (i=0;buffer[i] != '\0'; i++){    
-    i += parse(&buffer,i,"<RRD::GRAPH",drawgraph);
-    i += parse(&buffer,i,"<RRD::PRINT",drawprint);
-  }
+       /* initialize variable heap */
+       initvar();
+
+       /* expand rrd directives in buffer recursivly */
+       for (i=0; buffer[i]; i++) {
+               if (buffer[i] != '<')
+                       continue;
+               if (!filter) {
+                       parse(&buffer, i, "<RRD::CV", cgiget);
+                       parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
+                       parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
+                       parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
+               }
+               parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
+               parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
+               parse(&buffer, i, "<RRD::GRAPH", drawgraph);
+               parse(&buffer, i, "<RRD::INCLUDE", includefile);
+               parse(&buffer, i, "<RRD::PRINT", drawprint);
+               parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
+               parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
+               parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
+               parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
+               parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
+               parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
+       }
 
-  if (filter==0){
-      printf ("Content-Type: text/html\n"
-             "Content-Length: %d\n", strlen(buffer));
-      if (labs(goodfor) > 0){
-                 time_t now;
-                 now = time(NULL);
-                 printf ("Last-Modified: %s\n",http_time(&now));
-                 now += labs(goodfor);
-                 printf ("Expires: %s\n",http_time(&now));
-                 if (goodfor < 0) {
-                   printf("Refresh: %ld\n", labs(goodfor));
-                 }
-      }
-      printf ("\n");
-  }
-  printf ("%s", buffer);
-  calfree();
-  if (buffer){
-     free(buffer);
-  }
-  exit(0);
+       if (!filter) {
+               printf ("Content-Type: text/html\n" 
+                               "Content-Length: %d\n", 
+                               strlen(buffer));
+
+               if (labs(goodfor) > 0) {
+                       time_t now;
+                       now = time(NULL);
+                       printf("Last-Modified: %s\n", http_time(&now));
+                       now += labs(goodfor);
+                       printf("Expires: %s\n", http_time(&now));
+                       if (goodfor < 0) {
+                               printf("Refresh: %ld\n", labs(goodfor));
+                       }
+               }
+               printf("\n");
+       }
+
+       /* output result */
+       printf("%s", buffer);
+
+       /* cleanup */
+       calfree();
+       if (buffer){
+               free(buffer);
+       }
+       donevar();
+       exit(0);
 }
 
-/* remove occurences of .. this is a general measure to make
+/* remove occurrences of .. this is a general measure to make
    paths which came in via cgi do not go UP ... */
 
-char* rrdsetenv(long argc, char **args){
-  if (argc >= 2) {
-      char *xyz=malloc((strlen(args[0])+strlen(args[1])+3)*sizeof(char));
-      if (xyz == NULL){        
-       return stralloc("[ERROR: allocating setenv buffer]");
-      };
-      sprintf(xyz,"%s=%s",args[0],args[1]);
-      if( putenv(xyz) == -1) {
-       return stralloc("[ERROR: faild to do putenv]");
-      };
-  } else {
-    return stralloc("[ERROR: setenv faild because not enough arguments were defined]");
-  }
-  return stralloc("");
+char* rrdsetenv(long argc, char **args) {
+       if (argc >= 2) {
+               char *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
+               if (xyz == NULL) {
+                       return stralloc("[ERROR: allocating setenv buffer]");
+               };
+               sprintf(xyz, "%s=%s", args[0], args[1]);
+               if(putenv(xyz) == -1) {
+                       free(xyz);
+                       return stralloc("[ERROR: failed to do putenv]");
+               };
+       }
+       return stralloc("[ERROR: setenv failed because not enough "
+                                       "arguments were defined]");
 }
 
-char* rrdgetenv(long argc, char **args){
-  if (argc != 1) {
-    return stralloc("[ERROR: getenv faild because it did not get 1 argument only]");
-  }
-  else if (getenv(args[0]) == NULL) {
-    return stralloc("");
-  }
-  else {
-    return stralloc(getenv(args[0]));
-  }
+/* rrd interface to the variable function putvar() */
+char*
+rrdsetvar(long argc, char **args)
+{
+       if (argc >= 2)
+       {
+               const char* result = putvar(args[0], args[1], 0 /* not const */);
+               if (result) {
+                       /* setvar does not return the value set */
+                       return stralloc("");
+               }
+               return stralloc("[ERROR: putvar failed]");
+       }
+       return stralloc("[ERROR: putvar failed because not enough arguments "
+                                       "were defined]");
+}
+
+/* rrd interface to the variable function putvar() */
+char*
+rrdsetvarconst(long argc, char **args)
+{
+       if (argc >= 2)
+       {
+               const char* result = putvar(args[0], args[1], 1 /* const */);
+               if (result) {
+                       /* setvar does not return the value set */
+                       return stralloc("");
+               }
+               return stralloc("[ERROR: putvar failed]");
+       }
+       return stralloc("[ERROR: putvar failed because not enough arguments "
+                                       "were defined]");
+}
+
+char* rrdgetenv(long argc, char **args) {
+       char buf[128];
+       const char* envvar;
+       if (argc != 1) {
+               return stralloc("[ERROR: getenv failed because it did not "
+                                               "get 1 argument only]");
+       };
+       envvar = getenv(args[0]);
+       if (envvar) {
+               return stralloc(envvar);
+       } else {
+               snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
+               return stralloc(buf);
+       }
+}
+
+char* rrdgetvar(long argc, char **args) {
+       char buf[128];
+       const char* value;
+       if (argc != 1) {
+               return stralloc("[ERROR: getvar failed because it did not "
+                                               "get 1 argument only]");
+       };
+       value = getvar(args[0]);
+       if (value) {
+               return stralloc(value);
+       } else {
+               snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
+               return stralloc(buf);
+       }
 }
 
 char* rrdgoodfor(long argc, char **args){
@@ -247,7 +473,7 @@ char* rrdgoodfor(long argc, char **args){
  * */
 #define MAX_STRFTIME_SIZE 256
 char* printstrftime(long argc, char **args){
-       struct  rrd_time_value start_tv, end_tv;
+       struct  time_value start_tv, end_tv;
        char    *parsetime_error = NULL;
        char    formatted[MAX_STRFTIME_SIZE];
        struct tm *the_tm;
@@ -301,9 +527,10 @@ char* printstrftime(long argc, char **args){
 char* includefile(long argc, char **args){
   char *buffer;
   if (argc >= 1) {
-      readfile(args[0], &buffer, 0);
+         char* filename = args[0];
+      readfile(filename, &buffer, 0);
       if (rrd_test_error()) {
-         char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
+               char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE));
          sprintf(err, "[ERROR: %s]",rrd_get_error());
          rrd_clear_error();
          return err;
@@ -317,17 +544,24 @@ char* includefile(long argc, char **args){
   }
 }
 
-static
-char* rrdstrip(char *buf){
-  char *start;
-  if (buf == NULL) return NULL;
-  buf = stralloc(buf); /* make a copy of the buffer */
-  if (buf == NULL) return NULL;
-  while ((start = strstr(buf,"<"))){
-    *start = '_';
+/* make a copy of buf and replace open/close brackets with '_' */
+char* rrdstrip(char *buf) {
+  char* p;
+  if (buf == NULL) {
+         return NULL;
   }
-  while ((start = strstr(buf,">"))){
-    *start = '_';
+  /* make a copy of the buffer */
+  buf = stralloc(buf);
+  if (buf == NULL) {
+         return NULL;
+  }
+
+  p = buf;
+  while (*p) {
+         if (*p == '<' || *p == '>') {
+                 *p = '_';
+         }
+         p++;
   }
   return buf;
 }
@@ -342,7 +576,7 @@ char* cgigetq(long argc, char **args){
 
     for(c=buf;*c != '\0';c++)
       if (*c == '"') qc++;
-    if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
+    if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
        perror("Malloc Buffer");
        exit(1);
     };
@@ -367,53 +601,59 @@ char* cgigetq(long argc, char **args){
   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
 }
 
-/* remove occurences of .. this is a general measure to make
+/* remove occurrences of .. this is a general measure to make
    paths which came in via cgi do not go UP ... */
 
 char* cgigetqp(long argc, char **args){
-  if (argc>= 1){
+  if (argc>= 1) {
     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
     char *buf2;
     char *c,*d;
     int  qc=0;
-    if (buf==NULL) return NULL;
 
-    for(c=buf;*c != '\0';c++)
-      if (*c == '"') qc++;
-    if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
-       perror("Malloc Buffer");
-       exit(1);
+    if (buf==NULL) 
+               return NULL;
+
+    for(c=buf;*c != '\0';c++) {
+       if (*c == '"') {
+                       qc++;
+               }
+       }
+
+    if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
+               perror("Malloc Buffer");
+               exit(1);
     };
+
     c=buf;
     d=buf2;
+
     *(d++) = '"';
-    while(*c != '\0'){
-       if (*c == '"') {
-           *(d++) = '"';
-           *(d++) = '\'';
-           *(d++) = '"';
-           *(d++) = '\'';
-       } 
-       if(*c == '/') {
-           *(d++) = '_';c++;
-       } else {
-           if (*c=='.' && *(c+1) == '.'){
-               c += 2;
-               *(d++) = '_'; *(d++) ='_';      
-           } else {
-               
-               *(d++) = *(c++);
-           }
-       }
+    while (*c != '\0') {
+               if (*c == '"') {
+                       *(d++) = '"';
+                       *(d++) = '\'';
+                       *(d++) = '"';
+                       *(d++) = '\'';
+               }
+               if(*c == '/') {
+                       *(d++) = '_';
+                       c++;
+               } else {
+                       if (*c=='.' && *(c+1) == '.') {
+                               c += 2;
+                               *(d++) = '_'; *(d++) ='_';      
+                       } else {
+                               *(d++) = *(c++);
+                       }
+               }
     }
     *(d++) = '"';
     *(d) = '\0';
     free(buf);
     return buf2;
   }
-
   return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
-
 }
 
 
@@ -437,7 +677,7 @@ char* drawgraph(long argc, char **args){
   optind=0; /* reset gnu getopt */
   opterr=0; /* reset gnu getopt */
   calfree();
-  if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize, NULL) != -1 ) {
+  if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize) != -1 ) {
     return stralloc(calcpr[0]);
   } else {
     if (rrd_test_error()) {
@@ -477,7 +717,7 @@ char* printtimelast(long argc, char **args) {
       rrd_clear_error();
       return err;
     }
-    localtime_r(&last, &tm_last);
+    tm_last = *localtime(&last);
     strftime(buf,254,args[1],&tm_last);
     return buf;
   }
@@ -496,7 +736,7 @@ char* printtimenow(long argc, char **args) {
     if (buf == NULL){  
        return stralloc("[ERROR: allocating strftime buffer]");
     };
-    localtime_r(&now, &tm_now);
+    tm_now = *localtime(&now);
     strftime(buf,254,args[0],&tm_now);
     return buf;
   }
@@ -506,146 +746,285 @@ char* printtimenow(long argc, char **args) {
   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
 }
 
-/* scan aLine until an unescaped '>' arives */
-static
-char* scanargs(char *aLine, long *argc, char ***args)
+/* Scan buffer until an unescaped '>' arives.
+ * Update argument array with arguments found.
+ * Return end cursor where parsing stopped, or NULL in case of failure.
+ *
+ * FIXME:
+ * To allow nested constructs, we call rrd_expand_vars() for arguments
+ * that contain RRD::x directives. These introduce a small memory leak
+ * since we have to stralloc the arguments the way parse() works.
+ */
+char*
+scanargs(char *line, int *argument_count, char ***arguments)
 {
-  char        *getP, *putP;
-  char        Quote = 0;
-  int argal = MEMBLK;
-  int braket = 0;
-  int inArg = 0;
-  if (((*args) = (char **) malloc(MEMBLK*sizeof(char *))) == NULL)   {
-    return NULL;
-  }
-  /* sikp leading blanks */
-  while (*aLine && *aLine <= ' ') aLine++;
-  
-  *argc = 0;
-  getP = aLine;
-  putP = aLine;
-  while (*getP && !( !Quote  && (braket == 0) && ((*getP) == '>'))){
-    if ((unsigned)*getP < ' ') *getP = ' '; /*remove all special chars*/
-    switch (*getP) {
-    case ' ': 
-      if (Quote){
-       *(putP++)=*getP;
-      } else 
-       if(inArg) {
-         *(putP++) = 0;
-         inArg = 0;
-       }
-      break;
-    case '"':
-    case '\'':
-      if (Quote != 0) {
-       if (Quote == *getP) 
-         Quote = 0;
-       else {
-         *(putP++)=*getP;
+       char    *getP;          /* read cursor */
+       char    *putP;          /* write cursor */
+       char    Quote;          /* type of quote if in quoted string, 0 otherwise */
+       int     tagcount;       /* open tag count */
+       int     in_arg;         /* if we currently are parsing an argument or not */
+       int     argsz;          /* argument array size */
+       int             curarg_contains_rrd_directives;
+
+       /* local array of arguments while parsing */
+       int argc = 0;
+       char** argv;
+
+#ifdef DEBUG_PARSER
+       printf("<-- scanargs(%s) -->\n", line);
+#endif
+
+       *arguments = NULL;
+       *argument_count = 0;
+
+       /* create initial argument array of char pointers */
+       argsz = 32;
+       argv = (char **)malloc(argsz * sizeof(char *));
+       if (!argv) {
+               return NULL;
        }
-      } else {
-       if(!inArg){
-         (*args)[++(*argc)] = putP;
-         inArg=1;
-       }           
-       Quote = *getP;
-      }
-      break;
-    default:
-      if (Quote == 0 && (*getP) == '<') {
-       braket++;
-      }
-      if (Quote == 0 && (*getP) == '>') {
-       braket--;
-      }
 
-      if(!inArg){
-       (*args)[++(*argc)] = putP;
-       inArg=1;
-      }
-      *(putP++)=*getP;
-      break;
-    }
-    if ((*argc) >= argal-10 ) {
-      argal += MEMBLK;
-    if (((*args)=rrd_realloc((*args),(argal)*sizeof(char *))) == NULL) {
-       return NULL;
-      }
-    }   
-    getP++;
-  }
-  
-  *putP = '\0';
-  (*argc)++;
-  if (Quote) 
-    return NULL;
-  else
-    return getP+1; /* pointer to next char after parameter */
+       /* skip leading blanks */
+       while (isspace((int)*line)) {
+               line++;
+       }
+
+       getP = line;
+       putP = line;
+
+       Quote    = 0;
+       in_arg   = 0;
+       tagcount = 0;
+
+       curarg_contains_rrd_directives = 0;
+
+       /* start parsing 'line' for arguments */
+       while (*getP)
+       {
+               unsigned char c = *getP++;
+
+               if (c == '>' && !Quote && !tagcount) {
+                       /* this is our closing tag, quit scanning */
+                       break;
+               }
+
+               /* remove all special chars */
+               if (c < ' ') {
+                       c = ' ';
+               }
+
+               switch (c)
+               {
+               case ' ': 
+                       if (Quote || tagcount) {
+                               /* copy quoted/tagged string */
+                               *putP++ = c;
+                       }
+                       else if (in_arg)
+                       {
+                               /* end argument string */
+                               *putP++ = 0;
+                               in_arg = 0;
+                               if (curarg_contains_rrd_directives) {
+                                       argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
+                                       curarg_contains_rrd_directives = 0;
+                               }
+                       }
+                       break;
+
+               case '"': /* Fall through */
+               case '\'':
+                       if (Quote != 0) {
+                               if (Quote == c) {
+                                       Quote = 0;
+                               } else {
+                                       /* copy quoted string */
+                                       *putP++ = c;
+                               }
+                       } else {
+                               if (!in_arg) {
+                                       /* reference argument string in argument array */
+                                       argv[argc++] = putP;
+                                       in_arg=1;
+                               }
+                               Quote = c;
+                       }
+                       break;
+
+               default:
+                       if (!Quote) {
+                               if (!in_arg) {
+                                       /* start new argument */
+                                       argv[argc++] = putP;
+                                       in_arg = 1;
+                               }
+                               if (c == '>') {
+                                       if (tagcount) {
+                                               tagcount--;
+                                       }
+                               }
+                               if (c == '<') {
+                                       tagcount++;
+                                       if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
+                                               curarg_contains_rrd_directives = 1;
+                                       }
+                               }
+                       }
+                       *putP++ = c;
+                       break;
+               }
+
+               /* check if our argument array is still large enough */
+               if (argc == argsz) {
+                       /* resize argument array */
+                       argsz *= 2;
+                       argv = rrd_realloc(argv, argsz * sizeof(char *));
+                       if (*argv == NULL) {
+                               return NULL;
+                       }
+               }
+       }
+
+       /* terminate last argument found */
+       *putP = '\0';
+       if (curarg_contains_rrd_directives) {
+               argv[argc-1] = rrd_expand_vars(stralloc(argv[argc-1]));
+       }
+
+#ifdef DEBUG_PARSER
+       if (argc > 0) {
+               int n;
+               printf("<-- arguments found [%d]\n", argc);
+               for (n=0; n<argc; n++) {
+                       printf("arg %02d: '%s'\n", n, argv[n]);
+               }
+               printf("-->\n");
+       } else {
+               printf("<!-- No arguments found -->\n");
+       }
+#endif
+
+       /* update caller's notion of the argument array and it's size */
+       *arguments = argv;
+       *argument_count = argc;
+
+       if (Quote) {
+               return NULL;
+       }
+
+       /* Return new scanning cursor:
+          pointer to char after closing bracket */
+       return getP;
 }
 
 
+/*
+ * Parse(): scan current portion of buffer for given tag.
+ * If found, parse tag arguments and call 'func' for it.
+ * The result of func is inserted at the current position
+ * in the buffer.
+ */
+int
+parse(
+       char **buf,     /* buffer */
+       long i,                 /* offset in buffer */
+       char *tag,      /* tag to handle  */
+       char *(*func)(long argc, char **args) /* function to call for 'tag' */
+       )
+{
+       /* the name of the vairable ... */
+       char *val;
+       long valln;  
+       char **args;
+       char *end;
+       long end_offset;
+       int  argc;
+       size_t taglen = strlen(tag);
+
+       /* Current position in buffer should start with 'tag' */
+       if (strncmp((*buf)+i, tag, taglen) != 0) {
+               return 0;
+       }
+       /* .. and match exactly (a whitespace following 'tag') */
+       if (! isspace(*((*buf) + i + taglen)) ) {
+               return 0;
+       }
 
-int parse(char **buf, long i, char *tag, 
-           char *(*func)(long argc, char **args)){
-
-  /* the name of the vairable ... */
-  char *val;
-  long valln;  
-  char **args;
-  char *end;
-  long end_offset;
-  long argc;
-  /* do we like it ? */
-  if (strncmp((*buf)+i, tag, strlen(tag))!=0) return 0;      
-  if (! isspace(*( (*buf) + i + strlen(tag) )) ) return 0;
-  /* scanargs puts \0 into *buf ... so after scanargs it is probably
-     not a good time to use strlen on buf */
-  end = scanargs((*buf)+i+strlen(tag),&argc,&args);
-  if (! end) {
-    for (;argc>2;argc--){
-      *((args[argc-1])-1)=' ';
-    }
-    val = stralloc("[ERROR: Parsing Problem with the following text\n"
-                  " Check original file. This may have been altered by parsing.]\n\n");
-    end = (*buf)+i+1;
-  } else {
-    val = func(argc-1,args+1);
-    free (args);
-  }
-  /* for (ii=0;ii<argc;ii++) printf("'%s'\n", args[ii]); */
-  if (val != NULL) {
-    valln = strlen(val); 
-  } else { valln = 0;}
-  
-  /* make enough room for replacement */
-  end_offset = end - (*buf);
-  if(end-(*buf) < i + valln){ /* make sure we do not shrink the mallocd block */
-  /* calculating the new length of the buffer is simple. add current
-     buffer pos (i) to length of string after replaced tag to length
-     of replacement string and add 1 for the final zero ... */
-    if(((*buf) = rrd_realloc((*buf),
-                        (i+strlen(end) + valln +1) * sizeof(char)))==NULL){
-      perror("Realoc buf:");
-      exit(1);
-    };
-  }
-  end = (*buf) + end_offset; /* make sure the 'end' pointer gets moved
-                                along with the buf pointer when realoc
-                                moves memmory ... */
-  /* splice the variable */
-  memmove ((*buf)+i+valln,end,strlen(end)+1);
-  if (val != NULL ) memmove ((*buf)+i,val, valln);
-  if (val){ free(val);}
-  return valln > 0 ? valln-1: valln;
+#ifdef DEBUG_PARSER
+       printf("parse(): handeling tag '%s'\n", tag);
+#endif
+
+       /* Scan for arguments following the tag;
+          scanargs() puts \0 into *buf ... so after scanargs it is probably
+          not a good time to use strlen on buf */
+       end = scanargs((*buf) + i + taglen, &argc, &args);
+       if (end)
+       {
+               /* got arguments, call function for 'tag' with arguments */
+               val = func(argc, args);
+               free(args);
+       }
+       else
+       {
+               /* unable to parse arguments, undo 0-termination by scanargs */
+               for (; argc > 0; argc--) {
+                       *((args[argc-1])-1) = ' ';
+               }
+
+               /* next call, try parsing at current offset +1 */
+               end = (*buf) + i + 1;
+
+               val = stralloc("[ERROR: Parsing Problem with the following text\n"
+                                               " Check original file. This may have been altered "
+                                               "by parsing.]\n\n");
+       }
+
+       /* remember offset where we have to continue parsing */
+       end_offset = end - (*buf);
+
+       valln = 0;
+       if (val) {
+               valln = strlen(val);
+       }
+
+       /* Optionally resize buffer to hold the replacement value:
+          Calculating the new length of the buffer is simple. add current
+          buffer pos (i) to length of string after replaced tag to length
+          of replacement string and add 1 for the final zero ... */
+       if (end - (*buf) < (i + valln)) {
+               /* make sure we do not shrink the mallocd block */
+               size_t newbufsize = i + strlen(end) + valln + 1;
+               *buf = rrd_realloc(*buf, newbufsize);
+
+               if (*buf == NULL) {
+                       perror("Realoc buf:");
+                       exit(1);
+               };
+       }
+
+       /* Update new end pointer:
+          make sure the 'end' pointer gets moved along with the 
+          buf pointer when realloc moves memory ... */
+       end = (*buf) + end_offset; 
+
+       /* splice the variable:
+          step 1. Shift pending data to make room for 'val' */
+       memmove((*buf) + i + valln, end, strlen(end) + 1);
+
+       /* step 2. Insert val */
+       if (val) {
+               memmove((*buf)+i, val, valln);
+               free(val);
+       }
+       return (valln > 0 ? valln-1: valln);
 }
 
 char *
 http_time(time_t *now) {
-        struct tm tmptime;
+        struct tm *tmptime;
         static char buf[60];
 
-        gmtime_r(now, &tmptime);
-        strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT", &tmptime);
+        tmptime=gmtime(now);
+        strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
         return(buf);
 }