From: Sebastian Harl sh tokkee.org
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.3rc7  Copyright by Tobi Oetiker, 1997-2008
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #ifdef HAVE_STDLIB_H
9 #include <stdlib.h>
10 #endif
11
12 #define MEMBLK 1024
13 /*#define DEBUG_PARSER
14 #define DEBUG_VARS*/
15
16 typedef struct var_s {
17     char     *name, *value;
18 } s_var;
19
20 typedef struct cgi_s {
21     s_var   **vars;
22 } s_cgi;
23
24 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
25    and replace by result of arg[2] call */
26 int       parse(
27     char **,
28     long,
29     char *,
30     char *    (*)(long,
31                   const char **));
32
33 /**************************************************/
34 /* tag replacers ... they are called from parse   */
35 /* through function pointers                      */
36 /**************************************************/
37
38 /* return cgi var named arg[0] */
39 char     *cgiget(
40     long,
41     const char **);
42
43 /* return a quoted cgi var named arg[0] */
44 char     *cgigetq(
45     long,
46     const char **);
47
48 /* return a quoted and sanitized cgi variable */
49 char     *cgigetqp(
50     long,
51     const char **);
52
53 /* call rrd_graph and insert appropriate image tag */
54 char     *drawgraph(
55     long,
56     const char **);
57
58 /* return PRINT functions from last rrd_graph call */
59 char     *drawprint(
60     long,
61     const char **);
62
63 /* pretty-print the <last></last> value for some.rrd via strftime() */
64 char     *printtimelast(
65     long,
66     const char **);
67
68 /* pretty-print current time */
69 char     *printtimenow(
70     long,
71     const char **);
72
73 /* set an environment variable */
74 char     *rrdsetenv(
75     long,
76     const char **);
77
78 /* get an environment variable */
79 char     *rrdgetenv(
80     long,
81     const char **);
82
83 /* include the named file at this point */
84 char     *includefile(
85     long,
86     const char **);
87
88 /* for how long is the output of the cgi valid ? */
89 char     *rrdgoodfor(
90     long,
91     const char **);
92
93 /* return rrdcgi version string */
94 char     *rrdgetinternal(
95     long,
96     const char **);
97
98 char     *rrdstrip(
99     char *buf);
100 char     *scanargs(
101     char *line,
102     int *argc,
103     char ***args);
104
105 /* format at-time specified times using strftime */
106 char     *printstrftime(
107     long,
108     const char **);
109
110 /** HTTP protocol needs special format, and GMT time **/
111 char     *http_time(
112     time_t *);
113
114 /* return a pointer to newly allocated copy of this string */
115 char     *stralloc(
116     const char *);
117
118 /* global variable for rrdcgi */
119 s_cgi    *rrdcgiArg;
120
121 /* rrdcgiHeader
122  * 
123  *  Prints a valid CGI Header (Content-type...) etc.
124  */
125 void      rrdcgiHeader(
126     void);
127
128 /* rrdcgiDecodeString
129  * decode html escapes
130  */
131
132 char     *rrdcgiDecodeString(
133     char *text);
134
135 /* rrdcgiDebug
136  * 
137  *  Set/unsets debugging
138  */
139 void      rrdcgiDebug(
140     int level,
141     int where);
142
143 /* rrdcgiInit
144  *
145  *  Reads in variables set via POST or stdin.
146  */
147 s_cgi    *rrdcgiInit(
148     void);
149
150 /* rrdcgiGetValue
151  *
152  *  Returns the value of the specified variable or NULL if it's empty
153  *  or doesn't exist.
154  */
155 char     *rrdcgiGetValue(
156     s_cgi * parms,
157     const char *name);
158
159 /* rrdcgiFreeList
160  *
161  * Frees a list as returned by rrdcgiGetVariables()
162  */
163 void      rrdcgiFreeList(
164     char **list);
165
166 /* rrdcgiFree
167  *
168  * Frees the internal data structures
169  */
170 void      rrdcgiFree(
171     s_cgi * parms);
172
173 /*  rrdcgiReadVariables()
174  *
175  *  Read from stdin if no string is provided via CGI.  Variables that
176  *  doesn't have a value associated with it doesn't get stored.
177  */
178 s_var   **rrdcgiReadVariables(
179     void);
180
181
182 int       rrdcgiDebugLevel = 0;
183 int       rrdcgiDebugStderr = 1;
184 char     *rrdcgiHeaderString = NULL;
185 char     *rrdcgiType = NULL;
186
187 /* rrd interface to the variable functions {put,get}var() */
188 char     *rrdgetvar(
189     long argc,
190     const char **args);
191 char     *rrdsetvar(
192     long argc,
193     const char **args);
194 char     *rrdsetvarconst(
195     long argc,
196     const char **args);
197
198
199 /* variable store: put/get key-value pairs */
200 static int initvar(
201     );
202 static void donevar(
203     );
204 static const char *getvar(
205     const char *varname);
206 static const char *putvar(
207     const char *name,
208     const char *value,
209     int is_const);
210
211 /* key value pair that makes up an entry in the variable store */
212 typedef struct {
213     int       is_const; /* const variable or not */
214     const char *name;   /* variable name */
215     const char *value;  /* variable value */
216 } vardata;
217
218 /* the variable heap: 
219    start with a heapsize of 10 variables */
220 #define INIT_VARSTORE_SIZE      10
221 static vardata *varheap = NULL;
222 static size_t varheap_size = 0;
223
224 /* allocate and initialize variable heap */
225 static int initvar(
226     void)
227 {
228     varheap = (vardata *) malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
229     if (varheap == NULL) {
230         fprintf(stderr, "ERROR: unable to initialize variable store\n");
231         return -1;
232     }
233     memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
234     varheap_size = INIT_VARSTORE_SIZE;
235     return 0;
236 }
237
238 /* cleanup: free allocated memory */
239 static void donevar(
240     void)
241 {
242     int       i;
243
244     if (varheap) {
245         for (i = 0; i < (int) varheap_size; i++) {
246             if (varheap[i].name) {
247                 free((char *) varheap[i].name);
248             }
249             if (varheap[i].value) {
250                 free((char *) varheap[i].value);
251             }
252         }
253         free(varheap);
254     }
255 }
256
257 /* Get a variable from the variable store.
258    Return NULL in case the requested variable was not found. */
259 static const char *getvar(
260     const char *name)
261 {
262     int       i;
263
264     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
265         if (0 == strcmp(name, varheap[i].name)) {
266 #ifdef          DEBUG_VARS
267             printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
268 #endif
269             return varheap[i].value;
270         }
271     }
272 #ifdef DEBUG_VARS
273     printf("<!-- getvar(%s) -> Not found-->\n", name);
274 #endif
275     return NULL;
276 }
277
278 /* Put a variable into the variable store. If a variable by that
279    name exists, it's value is overwritten with the new value unless it was
280    marked as 'const' (initialized by RRD::SETCONSTVAR).
281    Returns a copy the newly allocated value on success, NULL on error. */
282 static const char *putvar(
283     const char *name,
284     const char *value,
285     int is_const)
286 {
287     int       i;
288
289     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
290         if (0 == strcmp(name, varheap[i].name)) {
291             /* overwrite existing entry */
292             if (varheap[i].is_const) {
293 #ifdef  DEBUG_VARS
294                 printf("<!-- setver(%s, %s): not assigning: "
295                        "const variable -->\n", name, value);
296 #endif
297                 return varheap[i].value;
298             }
299 #ifdef  DEBUG_VARS
300             printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
301                    name, value, varheap[i].value);
302 #endif
303             /* make it possible to promote a variable to readonly */
304             varheap[i].is_const = is_const;
305             free((char *) varheap[i].value);
306             varheap[i].value = stralloc(value);
307             return varheap[i].value;
308         }
309     }
310
311     /* no existing variable found by that name, add it */
312     if (i == (int) varheap_size) {
313         /* ran out of heap: resize heap to double size */
314         size_t    new_size = varheap_size * 2;
315
316         varheap = (vardata *) (realloc(varheap, sizeof(vardata) * new_size));
317         if (!varheap) {
318             fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
319             return NULL;
320         }
321         /* initialize newly allocated memory */ ;
322         memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
323         varheap_size = new_size;
324     }
325     varheap[i].is_const = is_const;
326     varheap[i].name = stralloc(name);
327     varheap[i].value = stralloc(value);
328
329 #ifdef          DEBUG_VARS
330     printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
331 #endif
332     return varheap[i].value;
333 }
334
335 /* expand those RRD:* directives that can be used recursivly */
336 static char *rrd_expand_vars(
337     char *buffer)
338 {
339     int       i;
340
341 #ifdef DEBUG_PARSER
342     printf("expanding variables in '%s'\n", buffer);
343 #endif
344
345     for (i = 0; buffer[i]; i++) {
346         if (buffer[i] != '<')
347             continue;
348         parse(&buffer, i, "<RRD::CV", cgiget);
349         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
350         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
351         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
352         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
353         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
354         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
355         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
356         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
357     }
358     return buffer;
359 }
360
361 static long goodfor = 0;
362 static char **calcpr = NULL;
363 static void calfree(
364     void)
365 {
366     if (calcpr) {
367         long      i;
368
369         for (i = 0; calcpr[i]; i++) {
370             if (calcpr[i]) {
371                 free(calcpr[i]);
372             }
373         }
374         if (calcpr) {
375             free(calcpr);
376         }
377     }
378 }
379
380 /* create freeable version of the string */
381 char     *stralloc(
382     const char *str)
383 {
384     char     *nstr;
385
386     if (!str) {
387         return NULL;
388     }
389     nstr = malloc((strlen(str) + 1));
390     strcpy(nstr, str);
391     return (nstr);
392 }
393
394 static int readfile(
395     const char *file_name,
396     char **buffer,
397     int skipfirst)
398 {
399     long      writecnt = 0, totalcnt = MEMBLK;
400     long      offset = 0;
401     FILE     *input = NULL;
402     char      c;
403
404     if ((strcmp("-", file_name) == 0)) {
405         input = stdin;
406     } else {
407         if ((input = fopen(file_name, "rb")) == NULL) {
408             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
409             return (-1);
410         }
411     }
412     if (skipfirst) {
413         do {
414             c = getc(input);
415             offset++;
416         } while (c != '\n' && !feof(input));
417     }
418     if (strcmp("-", file_name)) {
419         fseek(input, 0, SEEK_END);
420         /* have extra space for detecting EOF without realloc */
421         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
422         if (totalcnt < MEMBLK)
423             totalcnt = MEMBLK;  /* sanitize */
424         fseek(input, offset * sizeof(char), SEEK_SET);
425     }
426     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
427         perror("Allocate Buffer:");
428         exit(1);
429     };
430     do {
431         writecnt +=
432             fread((*buffer) + writecnt, 1,
433                   (totalcnt - writecnt) * sizeof(char), input);
434         if (writecnt >= totalcnt) {
435             totalcnt += MEMBLK;
436             if (((*buffer) =
437                  rrd_realloc((*buffer),
438                              (totalcnt + 4) * sizeof(char))) == NULL) {
439                 perror("Realloc Buffer:");
440                 exit(1);
441             };
442         }
443     } while (!feof(input));
444     (*buffer)[writecnt] = '\0';
445     if (strcmp("-", file_name) != 0) {
446         fclose(input);
447     };
448     return writecnt;
449 }
450
451 int main(
452     int argc,
453     char *argv[])
454 {
455     long      length;
456     char     *buffer;
457     char     *server_url = NULL;
458     long      i;
459     long      filter = 0;
460     struct option long_options[] = {
461         {"filter", no_argument, 0, 'f'},
462         {0, 0, 0, 0}
463     };
464
465 #ifdef MUST_DISABLE_SIGFPE
466     signal(SIGFPE, SIG_IGN);
467 #endif
468 #ifdef MUST_DISABLE_FPMASK
469     fpsetmask(0);
470 #endif
471     optind = 0;
472     opterr = 0;         /* initialize getopt */
473
474     /* what do we get for cmdline arguments?
475        for (i=0;i<argc;i++)
476        printf("%d-'%s'\n",i,argv[i]); */
477     while (1) {
478         int       option_index = 0;
479         int       opt;
480
481         opt = getopt_long(argc, argv, "f", long_options, &option_index);
482         if (opt == EOF) {
483             break;
484         }
485
486         switch (opt) {
487         case 'f':
488             filter = 1;
489             break;
490         case '?':
491             printf("unknown commandline option '%s'\n", argv[optind - 1]);
492             return -1;
493         }
494     }
495
496     if (!filter) {
497         rrdcgiDebug(0, 0);
498         rrdcgiArg = rrdcgiInit();
499         server_url = getenv("SERVER_URL");
500     }
501
502     /* make sure we have one extra argument, 
503        if there are others, we do not care Apache gives several */
504
505     /* if ( (optind != argc-2 
506        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
507        && optind != argc-1) { */
508
509     if (optind >= argc) {
510         fprintf(stderr, "ERROR: expected a filename\n");
511         exit(1);
512     } else {
513         length = readfile(argv[optind], &buffer, 1);
514     }
515
516     if (rrd_test_error()) {
517         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
518         exit(1);
519     }
520
521     /* initialize variable heap */
522     initvar();
523
524 #ifdef DEBUG_PARSER
525     /* some fake header for testing */
526     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
527 #endif
528
529
530     /* expand rrd directives in buffer recursivly */
531     for (i = 0; buffer[i]; i++) {
532         if (buffer[i] != '<')
533             continue;
534         if (!filter) {
535             parse(&buffer, i, "<RRD::CV", cgiget);
536             parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
537             parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
538             parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
539         }
540         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
541         parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
542         parse(&buffer, i, "<RRD::GRAPH", drawgraph);
543         parse(&buffer, i, "<RRD::INCLUDE", includefile);
544         parse(&buffer, i, "<RRD::PRINT", drawprint);
545         parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
546         parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
547         parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
548         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
549         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
550         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
551         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
552     }
553
554     if (!filter) {
555         printf("Content-Type: text/html\n"
556                "Content-Length: %zd\n", strlen(buffer));
557
558         if (labs(goodfor) > 0) {
559             time_t    now;
560
561             now = time(NULL);
562             printf("Last-Modified: %s\n", http_time(&now));
563             now += labs(goodfor);
564             printf("Expires: %s\n", http_time(&now));
565             if (goodfor < 0) {
566                 printf("Refresh: %ld\n", labs(goodfor));
567             }
568         }
569         printf("\n");
570     }
571
572     /* output result */
573     printf("%s", buffer);
574
575     /* cleanup */
576     calfree();
577     if (buffer) {
578         free(buffer);
579     }
580     donevar();
581     exit(0);
582 }
583
584 /* remove occurrences of .. this is a general measure to make
585    paths which came in via cgi do not go UP ... */
586
587 char     *rrdsetenv(
588     long argc,
589     const char **args)
590 {
591     if (argc >= 2) {
592         char     *xyz = malloc((strlen(args[0]) + strlen(args[1]) + 2));
593
594         if (xyz == NULL) {
595             return stralloc("[ERROR: allocating setenv buffer]");
596         };
597         sprintf(xyz, "%s=%s", args[0], args[1]);
598         if (putenv(xyz) == -1) {
599             free(xyz);
600             return stralloc("[ERROR: failed to do putenv]");
601         };
602         return stralloc("");
603     }
604     return stralloc("[ERROR: setenv failed because not enough "
605                     "arguments were defined]");
606 }
607
608 /* rrd interface to the variable function putvar() */
609 char     *rrdsetvar(
610     long argc,
611     const char **args)
612 {
613     if (argc >= 2) {
614         const char *result = putvar(args[0], args[1], 0 /* not const */ );
615
616         if (result) {
617             /* setvar does not return the value set */
618             return stralloc("");
619         }
620         return stralloc("[ERROR: putvar failed]");
621     }
622     return stralloc("[ERROR: putvar failed because not enough arguments "
623                     "were defined]");
624 }
625
626 /* rrd interface to the variable function putvar() */
627 char     *rrdsetvarconst(
628     long argc,
629     const char **args)
630 {
631     if (argc >= 2) {
632         const char *result = putvar(args[0], args[1], 1 /* const */ );
633
634         if (result) {
635             /* setvar does not return the value set */
636             return stralloc("");
637         }
638         return stralloc("[ERROR: putvar failed]");
639     }
640     return stralloc("[ERROR: putvar failed because not enough arguments "
641                     "were defined]");
642 }
643
644 char     *rrdgetenv(
645     long argc,
646     const char **args)
647 {
648     char      buf[128];
649     const char *envvar;
650
651     if (argc != 1) {
652         return stralloc("[ERROR: getenv failed because it did not "
653                         "get 1 argument only]");
654     };
655     envvar = getenv(args[0]);
656     if (envvar) {
657         return stralloc(envvar);
658     } else {
659         snprintf(buf, sizeof(buf), "[ERROR:_getenv_'%s'_failed", args[0]);
660         return stralloc(buf);
661     }
662 }
663
664 char     *rrdgetvar(
665     long argc,
666     const char **args)
667 {
668     char      buf[128];
669     const char *value;
670
671     if (argc != 1) {
672         return stralloc("[ERROR: getvar failed because it did not "
673                         "get 1 argument only]");
674     };
675     value = getvar(args[0]);
676     if (value) {
677         return stralloc(value);
678     } else {
679         snprintf(buf, sizeof(buf), "[ERROR:_getvar_'%s'_failed", args[0]);
680         return stralloc(buf);
681     }
682 }
683
684 char     *rrdgoodfor(
685     long argc,
686     const char **args)
687 {
688     if (argc == 1) {
689         goodfor = atol(args[0]);
690     } else {
691         return stralloc("[ERROR: goodfor expected 1 argument]");
692     }
693
694     if (goodfor == 0) {
695         return stralloc("[ERROR: goodfor value must not be 0]");
696     }
697
698     return stralloc("");
699 }
700
701 char     *rrdgetinternal(
702     long argc,
703     const char **args)
704 {
705     if (argc == 1) {
706         if (strcasecmp(args[0], "VERSION") == 0) {
707             return stralloc(PACKAGE_VERSION);
708         } else if (strcasecmp(args[0], "COMPILETIME") == 0) {
709             return stralloc(__DATE__ " " __TIME__);
710         } else {
711             return stralloc("[ERROR: internal unknown argument]");
712         }
713     } else {
714         return stralloc("[ERROR: internal expected 1 argument]");
715     }
716 }
717
718 /* Format start or end times using strftime.  We always need both the
719  * start and end times, because, either might be relative to the other.
720  * */
721 #define MAX_STRFTIME_SIZE 256
722 char     *printstrftime(
723     long argc,
724     const char **args)
725 {
726     rrd_time_value_t start_tv, end_tv;
727     char     *parsetime_error = NULL;
728     char      formatted[MAX_STRFTIME_SIZE];
729     struct tm *the_tm;
730     time_t    start_tmp, end_tmp;
731
732     /* Make sure that we were given the right number of args */
733     if (argc != 4) {
734         rrd_set_error("wrong number of args %d", argc);
735         return stralloc("");
736     }
737
738     /* Init start and end time */
739     rrd_parsetime("end-24h", &start_tv);
740     rrd_parsetime("now", &end_tv);
741
742     /* Parse the start and end times we were given */
743     if ((parsetime_error = rrd_parsetime(args[1], &start_tv))) {
744         rrd_set_error("start time: %s", parsetime_error);
745         return stralloc("");
746     }
747     if ((parsetime_error = rrd_parsetime(args[2], &end_tv))) {
748         rrd_set_error("end time: %s", parsetime_error);
749         return stralloc("");
750     }
751     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
752         return stralloc("");
753     }
754
755     /* Do we do the start or end */
756     if (strcasecmp(args[0], "START") == 0) {
757         the_tm = localtime(&start_tmp);
758     } else if (strcasecmp(args[0], "END") == 0) {
759         the_tm = localtime(&end_tmp);
760     } else {
761         rrd_set_error("start/end not found in '%s'", args[0]);
762         return stralloc("");
763     }
764
765     /* now format it */
766     if (strftime(formatted, MAX_STRFTIME_SIZE, args[3], the_tm)) {
767         return (stralloc(formatted));
768     } else {
769         rrd_set_error("strftime failed");
770         return stralloc("");
771     }
772 }
773
774 char     *includefile(
775     long argc,
776     const char **args)
777 {
778     char     *buffer;
779
780     if (argc >= 1) {
781         const char *filename = args[0];
782
783         readfile(filename, &buffer, 0);
784         if (rrd_test_error()) {
785             char     *err = malloc((strlen(rrd_get_error()) + DS_NAM_SIZE));
786
787             sprintf(err, "[ERROR: %s]", rrd_get_error());
788             rrd_clear_error();
789             return err;
790         } else {
791             return buffer;
792         }
793     } else {
794         return stralloc("[ERROR: No Inclue file defined]");
795     }
796 }
797
798 /* make a copy of buf and replace open/close brackets with '_' */
799 char     *rrdstrip(
800     char *buf)
801 {
802     char     *p;
803
804     if (buf == NULL) {
805         return NULL;
806     }
807     /* make a copy of the buffer */
808     buf = stralloc(buf);
809     if (buf == NULL) {
810         return NULL;
811     }
812
813     p = buf;
814     while (*p) {
815         if (*p == '<' || *p == '>') {
816             *p = '_';
817         }
818         p++;
819     }
820     return buf;
821 }
822
823 char     *cgigetq(
824     long argc,
825     const char **args)
826 {
827     if (argc >= 1) {
828         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
829         char     *buf2;
830         char     *c, *d;
831         int       qc = 0;
832
833         if (buf == NULL)
834             return NULL;
835
836         for (c = buf; *c != '\0'; c++)
837             if (*c == '"')
838                 qc++;
839         if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
840             perror("Malloc Buffer");
841             exit(1);
842         };
843         c = buf;
844         d = buf2;
845         *(d++) = '"';
846         while (*c != '\0') {
847             if (*c == '"') {
848                 *(d++) = '"';
849                 *(d++) = '\'';
850                 *(d++) = '"';
851                 *(d++) = '\'';
852             }
853             *(d++) = *(c++);
854         }
855         *(d++) = '"';
856         *(d) = '\0';
857         free(buf);
858         return buf2;
859     }
860
861     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
862 }
863
864 /* remove occurrences of .. this is a general measure to make
865    paths which came in via cgi do not go UP ... */
866
867 char     *cgigetqp(
868     long argc,
869     const char **args)
870 {
871     char     *buf;
872     char     *buf2;
873     char     *p;
874     char     *d;
875
876     if (argc < 1) {
877         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
878     }
879
880     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
881     if (!buf) {
882         return NULL;
883     }
884
885     buf2 = malloc(strlen(buf) + 1);
886     if (!buf2) {
887         perror("cgigetqp(): Malloc Path Buffer");
888         exit(1);
889     };
890
891     p = buf;
892     d = buf2;
893
894     while (*p) {
895         /* prevent mallicious paths from entering the system */
896         if (p[0] == '.' && p[1] == '.') {
897             p += 2;
898             *d++ = '_';
899             *d++ = '_';
900         } else {
901             *d++ = *p++;
902         }
903     }
904
905     *d = 0;
906     free(buf);
907
908     /* Make sure the path is relative, e.g. does not start with '/' */
909     p = buf2;
910     while ('/' == *p) {
911         *p++ = '_';
912     }
913
914     return buf2;
915 }
916
917
918 char     *cgiget(
919     long argc,
920     const char **args)
921 {
922     if (argc >= 1)
923         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
924     else
925         return stralloc("[ERROR: not enough arguments for RRD::CV]");
926 }
927
928
929
930 char     *drawgraph(
931     long argc,
932     const char **args)
933 {
934     int       i, xsize, ysize;
935     double    ymin, ymax;
936
937     for (i = 0; i < argc; i++)
938         if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
939             break;
940     if (i == argc) {
941         args[argc++] = "--imginfo";
942         args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
943     }
944     calfree();
945     if (rrd_graph
946         (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
947          &ymax) != -1) {
948         return stralloc(calcpr[0]);
949     } else {
950         if (rrd_test_error()) {
951             char     *err =
952                 malloc((strlen(rrd_get_error()) +
953                         DS_NAM_SIZE) * sizeof(char));
954             sprintf(err, "[ERROR: %s]", rrd_get_error());
955             rrd_clear_error();
956             calfree();
957             return err;
958         }
959     }
960     return NULL;
961 }
962
963 char     *drawprint(
964     long argc,
965     const char **args)
966 {
967     if (argc == 1 && calcpr) {
968         long      i = 0;
969
970         while (calcpr[i] != NULL)
971             i++;        /*determine number lines in calcpr */
972         if (atol(args[0]) < i - 1)
973             return stralloc(calcpr[atol(args[0]) + 1]);
974     }
975     return stralloc("[ERROR: RRD::PRINT argument error]");
976 }
977
978 char     *printtimelast(
979     long argc,
980     const char **args)
981 {
982     time_t    last;
983     struct tm tm_last;
984     char     *buf;
985
986     if (argc == 2) {
987         buf = malloc(255);
988         if (buf == NULL) {
989             return stralloc("[ERROR: allocating strftime buffer]");
990         };
991         last = rrd_last(argc + 1, (char **) args - 1);
992         if (rrd_test_error()) {
993             char     *err =
994                 malloc((strlen(rrd_get_error()) +
995                         DS_NAM_SIZE) * sizeof(char));
996             sprintf(err, "[ERROR: %s]", rrd_get_error());
997             rrd_clear_error();
998             return err;
999         }
1000         tm_last = *localtime(&last);
1001         strftime(buf, 254, args[1], &tm_last);
1002         return buf;
1003     }
1004     if (argc < 2) {
1005         return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
1006     }
1007     return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
1008 }
1009
1010 char     *printtimenow(
1011     long argc,
1012     const char **args)
1013 {
1014     time_t    now = time(NULL);
1015     struct tm tm_now;
1016     char     *buf;
1017
1018     if (argc == 1) {
1019         buf = malloc(255);
1020         if (buf == NULL) {
1021             return stralloc("[ERROR: allocating strftime buffer]");
1022         };
1023         tm_now = *localtime(&now);
1024         strftime(buf, 254, args[0], &tm_now);
1025         return buf;
1026     }
1027     if (argc < 1) {
1028         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1029     }
1030     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1031 }
1032
1033 /* Scan buffer until an unescaped '>' arives.
1034  * Update argument array with arguments found.
1035  * Return end cursor where parsing stopped, or NULL in case of failure.
1036  *
1037  * FIXME:
1038  * To allow nested constructs, we call rrd_expand_vars() for arguments
1039  * that contain RRD::x directives. These introduce a small memory leak
1040  * since we have to stralloc the arguments the way parse() works.
1041  */
1042 char     *scanargs(
1043     char *line,
1044     int *argument_count,
1045     char ***arguments)
1046 {
1047     char     *getP;     /* read cursor */
1048     char     *putP;     /* write cursor */
1049     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1050     int       tagcount; /* open tag count */
1051     int       in_arg;   /* if we currently are parsing an argument or not */
1052     int       argsz;    /* argument array size */
1053     int       curarg_contains_rrd_directives;
1054
1055     /* local array of arguments while parsing */
1056     int       argc = 0;
1057     char    **argv;
1058
1059 #ifdef DEBUG_PARSER
1060     printf("<-- scanargs(%s) -->\n", line);
1061 #endif
1062
1063     *arguments = NULL;
1064     *argument_count = 0;
1065
1066     /* create initial argument array of char pointers */
1067     argsz = 32;
1068     argv = (char **) malloc(argsz * sizeof(char *));
1069     if (!argv) {
1070         return NULL;
1071     }
1072
1073     /* skip leading blanks */
1074     while (isspace((int) *line)) {
1075         line++;
1076     }
1077
1078     getP = line;
1079     putP = line;
1080
1081     Quote = 0;
1082     in_arg = 0;
1083     tagcount = 0;
1084
1085     curarg_contains_rrd_directives = 0;
1086
1087     /* start parsing 'line' for arguments */
1088     while (*getP) {
1089         unsigned char c = *getP++;
1090
1091         if (c == '>' && !Quote && !tagcount) {
1092             /* this is our closing tag, quit scanning */
1093             break;
1094         }
1095
1096         /* remove all special chars */
1097         if (c < ' ') {
1098             c = ' ';
1099         }
1100
1101         switch (c) {
1102         case ' ':
1103             if (Quote || tagcount) {
1104                 /* copy quoted/tagged (=RRD expanded) string */
1105                 *putP++ = c;
1106             } else if (in_arg) {
1107                 /* end argument string */
1108                 *putP++ = 0;
1109                 in_arg = 0;
1110                 if (curarg_contains_rrd_directives) {
1111                     argv[argc - 1] =
1112                         rrd_expand_vars(stralloc(argv[argc - 1]));
1113                     curarg_contains_rrd_directives = 0;
1114                 }
1115             }
1116             break;
1117
1118         case '"':      /* Fall through */
1119         case '\'':
1120             if (Quote != 0) {
1121                 if (Quote == c) {
1122                     Quote = 0;
1123                 } else {
1124                     /* copy quoted string */
1125                     *putP++ = c;
1126                 }
1127             } else {
1128                 if (!in_arg) {
1129                     /* reference start of argument string in argument array */
1130                     argv[argc++] = putP;
1131                     in_arg = 1;
1132                 }
1133                 Quote = c;
1134             }
1135             break;
1136
1137         default:
1138             if (!in_arg) {
1139                 /* start new argument */
1140                 argv[argc++] = putP;
1141                 in_arg = 1;
1142             }
1143             if (c == '>') {
1144                 if (tagcount) {
1145                     tagcount--;
1146                 }
1147             }
1148             if (c == '<') {
1149                 tagcount++;
1150                 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1151                     curarg_contains_rrd_directives = 1;
1152                 }
1153             }
1154             *putP++ = c;
1155             break;
1156         }
1157
1158         /* check if our argument array is still large enough */
1159         if (argc == argsz) {
1160             /* resize argument array */
1161             argsz *= 2;
1162             argv = rrd_realloc(argv, argsz * sizeof(char *));
1163             if (*argv == NULL) {
1164                 return NULL;
1165             }
1166         }
1167     }
1168
1169     /* terminate last argument found */
1170     *putP = '\0';
1171     if (curarg_contains_rrd_directives) {
1172         argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1173     }
1174 #ifdef DEBUG_PARSER
1175     if (argc > 0) {
1176         int       n;
1177
1178         printf("<-- arguments found [%d]\n", argc);
1179         for (n = 0; n < argc; n++) {
1180             printf("arg %02d: '%s'\n", n, argv[n]);
1181         }
1182         printf("-->\n");
1183     } else {
1184         printf("<!-- No arguments found -->\n");
1185     }
1186 #endif
1187
1188     /* update caller's notion of the argument array and it's size */
1189     *arguments = argv;
1190     *argument_count = argc;
1191
1192     if (Quote) {
1193         return NULL;
1194     }
1195
1196     /* Return new scanning cursor:
1197        pointer to char after closing bracket */
1198     return getP;
1199 }
1200
1201
1202 /*
1203  * Parse(): scan current portion of buffer for given tag.
1204  * If found, parse tag arguments and call 'func' for it.
1205  * The result of func is inserted at the current position
1206  * in the buffer.
1207  */
1208 int parse(
1209     char **buf,         /* buffer */
1210     long i,             /* offset in buffer */
1211     char *tag,          /* tag to handle  */
1212     char *    (*func) (long,
1213                        const char **)   /* function to call for 'tag' */
1214     )
1215 {
1216     /* the name of the vairable ... */
1217     char     *val;
1218     long      valln;
1219     char    **args;
1220     char     *end;
1221     long      end_offset;
1222     int       argc;
1223     size_t    taglen = strlen(tag);
1224
1225     /* Current position in buffer should start with 'tag' */
1226     if (strncmp((*buf) + i, tag, taglen) != 0) {
1227         return 0;
1228     }
1229     /* .. and match exactly (a whitespace following 'tag') */
1230     if (!isspace(*((*buf) + i + taglen))) {
1231         return 0;
1232     }
1233 #ifdef DEBUG_PARSER
1234     printf("parse(): handling tag '%s'\n", tag);
1235 #endif
1236
1237     /* Scan for arguments following the tag;
1238        scanargs() puts \0 into *buf ... so after scanargs it is probably
1239        not a good time to use strlen on buf */
1240     end = scanargs((*buf) + i + taglen, &argc, &args);
1241     if (end) {
1242         /* got arguments, call function for 'tag' with arguments */
1243         val = func(argc, (const char **) args);
1244         free(args);
1245     } else {
1246         /* unable to parse arguments, undo 0-termination by scanargs */
1247         for (; argc > 0; argc--) {
1248             *((args[argc - 1]) - 1) = ' ';
1249         }
1250
1251         /* next call, try parsing at current offset +1 */
1252         end = (*buf) + i + 1;
1253
1254         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1255                        " Check original file. This may have been altered "
1256                        "by parsing.]\n\n");
1257     }
1258
1259     /* remember offset where we have to continue parsing */
1260     end_offset = end - (*buf);
1261
1262     valln = 0;
1263     if (val) {
1264         valln = strlen(val);
1265     }
1266
1267     /* Optionally resize buffer to hold the replacement value:
1268        Calculating the new length of the buffer is simple. add current
1269        buffer pos (i) to length of string after replaced tag to length
1270        of replacement string and add 1 for the final zero ... */
1271     if (end - (*buf) < (i + valln)) {
1272         /* make sure we do not shrink the mallocd block */
1273         size_t    newbufsize = i + strlen(end) + valln + 1;
1274
1275         *buf = rrd_realloc(*buf, newbufsize);
1276
1277         if (*buf == NULL) {
1278             perror("Realoc buf:");
1279             exit(1);
1280         };
1281     }
1282
1283     /* Update new end pointer:
1284        make sure the 'end' pointer gets moved along with the 
1285        buf pointer when realloc moves memory ... */
1286     end = (*buf) + end_offset;
1287
1288     /* splice the variable:
1289        step 1. Shift pending data to make room for 'val' */
1290     memmove((*buf) + i + valln, end, strlen(end) + 1);
1291
1292     /* step 2. Insert val */
1293     if (val) {
1294         memmove((*buf) + i, val, valln);
1295         free(val);
1296     }
1297     return (valln > 0 ? valln - 1 : valln);
1298 }
1299
1300 char     *http_time(
1301     time_t *now)
1302 {
1303     struct tm *tmptime;
1304     static char buf[60];
1305
1306     tmptime = gmtime(now);
1307     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1308     return (buf);
1309 }
1310
1311 void rrdcgiHeader(
1312     void)
1313 {
1314     if (rrdcgiType)
1315         printf("Content-type: %s\n", rrdcgiType);
1316     else
1317         printf("Content-type: text/html\n");
1318     if (rrdcgiHeaderString)
1319         printf("%s", rrdcgiHeaderString);
1320     printf("\n");
1321 }
1322
1323 void rrdcgiDebug(
1324     int level,
1325     int where)
1326 {
1327     if (level > 0)
1328         rrdcgiDebugLevel = level;
1329     else
1330         rrdcgiDebugLevel = 0;
1331     if (where)
1332         rrdcgiDebugStderr = 0;
1333     else
1334         rrdcgiDebugStderr = 1;
1335 }
1336
1337 char     *rrdcgiDecodeString(
1338     char *text)
1339 {
1340     char     *cp, *xp;
1341
1342     for (cp = text, xp = text; *cp; cp++) {
1343         if (*cp == '%') {
1344             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1345                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1346                 if (islower(*(cp + 1)))
1347                     *(cp + 1) = toupper(*(cp + 1));
1348                 if (islower(*(cp + 2)))
1349                     *(cp + 2) = toupper(*(cp + 2));
1350                 *(xp) =
1351                     (*(cp + 1) >=
1352                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1353                     (*(cp + 2) >=
1354                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1355                 xp++;
1356                 cp += 2;
1357             }
1358         } else {
1359             *(xp++) = *cp;
1360         }
1361     }
1362     memset(xp, 0, cp - xp);
1363     return text;
1364 }
1365
1366 /*  rrdcgiReadVariables()
1367  *
1368  *  Read from stdin if no string is provided via CGI.  Variables that
1369  *  doesn't have a value associated with it doesn't get stored.
1370  */
1371 s_var   **rrdcgiReadVariables(
1372     void)
1373 {
1374     int       length;
1375     char     *line = NULL;
1376     int       numargs;
1377     char     *cp, *ip, *esp, *sptr;
1378     s_var   **result;
1379     int       i, k, len;
1380     char      tmp[101];
1381
1382     cp = getenv("REQUEST_METHOD");
1383     ip = getenv("CONTENT_LENGTH");
1384
1385     if (cp && !strcmp(cp, "POST")) {
1386         if (ip) {
1387             length = atoi(ip);
1388             if ((line = (char *) malloc(length + 2)) == NULL)
1389                 return NULL;
1390             fgets(line, length + 1, stdin);
1391         } else
1392             return NULL;
1393     } else if (cp && !strcmp(cp, "GET")) {
1394         esp = getenv("QUERY_STRING");
1395         if (esp && strlen(esp)) {
1396             if ((line = (char *) malloc(strlen(esp) + 2)) == NULL)
1397                 return NULL;
1398             sprintf(line, "%s", esp);
1399         } else
1400             return NULL;
1401     } else {
1402         length = 0;
1403         printf("(offline mode: enter name=value pairs on standard input)\n");
1404         memset(tmp, 0, sizeof(tmp));
1405         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1406             if (strlen(tmp)) {
1407                 if (tmp[strlen(tmp) - 1] == '\n')
1408                     tmp[strlen(tmp) - 1] = '&';
1409                 if (length) {
1410                     length += strlen(tmp);
1411                     len = (length + 1) * sizeof(char);
1412                     if ((line = (char *) realloc(line, len)) == NULL)
1413                         return NULL;
1414                     strcat(line, tmp);
1415                 } else {
1416                     length = strlen(tmp);
1417                     len = (length + 1) * sizeof(char);
1418                     if ((line = (char *) malloc(len)) == NULL)
1419                         return NULL;
1420                     memset(line, 0, len);
1421                     strcpy(line, tmp);
1422                 }
1423             }
1424             memset(tmp, 0, sizeof(tmp));
1425         }
1426         if (!line)
1427             return NULL;
1428         if (line[strlen(line) - 1] == '&')
1429             line[strlen(line) - 1] = '\0';
1430     }
1431
1432     /*
1433      *  From now on all cgi variables are stored in the variable line
1434      *  and look like  foo=bar&foobar=barfoo&foofoo=
1435      */
1436
1437     if (rrdcgiDebugLevel > 0) {
1438         if (rrdcgiDebugStderr)
1439             fprintf(stderr, "Received cgi input: %s\n", line);
1440         else
1441             printf
1442                 ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n",
1443                  line);
1444     }
1445
1446     for (cp = line; *cp; cp++)
1447         if (*cp == '+')
1448             *cp = ' ';
1449
1450     if (strlen(line)) {
1451         for (numargs = 1, cp = line; *cp; cp++)
1452             if (*cp == '&')
1453                 numargs++;
1454     } else
1455         numargs = 0;
1456     if (rrdcgiDebugLevel > 0) {
1457         if (rrdcgiDebugStderr)
1458             fprintf(stderr, "%d cgi variables found.\n", numargs);
1459         else
1460             printf("%d cgi variables found.<br>\n", numargs);
1461     }
1462
1463     len = (numargs + 1) * sizeof(s_var *);
1464     if ((result = (s_var **) malloc(len)) == NULL)
1465         return NULL;
1466     memset(result, 0, len);
1467
1468     cp = line;
1469     i = 0;
1470     while (*cp) {
1471         if ((ip = (char *) strchr(cp, '&')) != NULL) {
1472             *ip = '\0';
1473         } else
1474             ip = cp + strlen(cp);
1475
1476         if ((esp = (char *) strchr(cp, '=')) == NULL) {
1477             cp = ++ip;
1478             continue;
1479         }
1480
1481         if (!strlen(esp)) {
1482             cp = ++ip;
1483             continue;
1484         }
1485
1486         if (i < numargs) {
1487
1488             /* try to find out if there's already such a variable */
1489             for (k = 0; k < i && (strncmp(result[k]->name, cp, esp - cp)
1490                                   || !(strlen(result[k]->name) ==
1491                                        (size_t) (esp - cp))); k++);
1492
1493             if (k == i) {   /* No such variable yet */
1494                 if ((result[i] = (s_var *) malloc(sizeof(s_var))) == NULL)
1495                     return NULL;
1496                 if ((result[i]->name =
1497                      (char *) malloc((esp - cp + 1) * sizeof(char))) == NULL)
1498                     return NULL;
1499                 memset(result[i]->name, 0, esp - cp + 1);
1500                 strncpy(result[i]->name, cp, esp - cp);
1501                 cp = ++esp;
1502                 if ((result[i]->value =
1503                      (char *) malloc((ip - esp + 1) * sizeof(char))) == NULL)
1504                     return NULL;
1505                 memset(result[i]->value, 0, ip - esp + 1);
1506                 strncpy(result[i]->value, cp, ip - esp);
1507                 result[i]->value = rrdcgiDecodeString(result[i]->value);
1508                 if (rrdcgiDebugLevel) {
1509                     if (rrdcgiDebugStderr)
1510                         fprintf(stderr, "%s: %s\n", result[i]->name,
1511                                 result[i]->value);
1512                     else
1513                         printf("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n",
1514                                result[i]->name, result[i]->value);
1515                 }
1516                 i++;
1517             } else {    /* There is already such a name, suppose a mutiple field */
1518                 cp = ++esp;
1519                 len =
1520                     (strlen(result[k]->value) + (ip - esp) +
1521                      2) * sizeof(char);
1522                 if ((sptr = (char *) malloc(len)) == NULL)
1523                     return NULL;
1524                 memset(sptr, 0, len);
1525                 sprintf(sptr, "%s\n", result[k]->value);
1526                 strncat(sptr, cp, ip - esp);
1527                 free(result[k]->value);
1528                 result[k]->value = rrdcgiDecodeString(sptr);
1529             }
1530         }
1531         cp = ++ip;
1532     }
1533     return result;
1534 }
1535
1536 /*  rrdcgiInit()
1537  *
1538  *  Read from stdin if no string is provided via CGI.  Variables that
1539  *  doesn't have a value associated with it doesn't get stored.
1540  */
1541 s_cgi    *rrdcgiInit(
1542     void)
1543 {
1544     s_cgi    *res;
1545     s_var   **vars;
1546
1547     vars = rrdcgiReadVariables();
1548
1549     if (!vars)
1550         return NULL;
1551
1552     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1553         return NULL;
1554     res->vars = vars;
1555
1556     return res;
1557 }
1558
1559 char     *rrdcgiGetValue(
1560     s_cgi * parms,
1561     const char *name)
1562 {
1563     int       i;
1564
1565     if (!parms || !parms->vars)
1566         return NULL;
1567     for (i = 0; parms->vars[i]; i++)
1568         if (!strcmp(name, parms->vars[i]->name)) {
1569             if (rrdcgiDebugLevel > 0) {
1570                 if (rrdcgiDebugStderr)
1571                     fprintf(stderr, "%s found as %s\n", name,
1572                             parms->vars[i]->value);
1573                 else
1574                     printf("%s found as %s<br>\n", name,
1575                            parms->vars[i]->value);
1576             }
1577             return parms->vars[i]->value;
1578         }
1579     if (rrdcgiDebugLevel) {
1580         if (rrdcgiDebugStderr)
1581             fprintf(stderr, "%s not found\n", name);
1582         else
1583             printf("%s not found<br>\n", name);
1584     }
1585     return NULL;
1586 }
1587
1588 void rrdcgiFreeList(
1589     char **list)
1590 {
1591     int       i;
1592
1593     for (i = 0; list[i] != NULL; i++)
1594         free(list[i]);
1595     free(list);
1596 }
1597
1598 void rrdcgiFree(
1599     s_cgi * parms)
1600 {
1601     int       i;
1602
1603     if (!parms)
1604         return;
1605     if (parms->vars) {
1606         for (i = 0; parms->vars[i]; i++) {
1607             if (parms->vars[i]->name)
1608                 free(parms->vars[i]->name);
1609             if (parms->vars[i]->value)
1610                 free(parms->vars[i]->value);
1611             free(parms->vars[i]);
1612         }
1613         free(parms->vars);
1614     }
1615     free(parms);
1616
1617     if (rrdcgiHeaderString) {
1618         free(rrdcgiHeaderString);
1619         rrdcgiHeaderString = NULL;
1620     }
1621     if (rrdcgiType) {
1622         free(rrdcgiType);
1623         rrdcgiType = NULL;
1624     }
1625 }