fix off by 1 error
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.4.3  Copyright by Tobi Oetiker, 1997-2010
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 #ifdef WIN32
13    #define strcasecmp stricmp
14    #define strcasencmp strnicmp
15 #endif
16
17 #define MEMBLK 1024
18 /*#define DEBUG_PARSER
19 #define DEBUG_VARS*/
20
21 typedef struct var_s {
22     char     *name, *value;
23 } s_var;
24
25 typedef struct cgi_s {
26     s_var   **vars;
27 } s_cgi;
28
29 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
30    and replace by result of arg[2] call */
31 int       parse(
32     char **,
33     long,
34     char *,
35     char *    (*)(long,
36                   const char **));
37
38 /**************************************************/
39 /* tag replacers ... they are called from parse   */
40 /* through function pointers                      */
41 /**************************************************/
42
43 /* return cgi var named arg[0] */
44 char     *cgiget(
45     long,
46     const char **);
47
48 /* return a quoted cgi var named arg[0] */
49 char     *cgigetq(
50     long,
51     const char **);
52
53 /* return a quoted and sanitized cgi variable */
54 char     *cgigetqp(
55     long,
56     const char **);
57
58 /* call rrd_graph and insert appropriate image tag */
59 char     *drawgraph(
60     long,
61     const char **);
62
63 /* return PRINT functions from last rrd_graph call */
64 char     *drawprint(
65     long,
66     const char **);
67
68 /* pretty-print the <last></last> value for some.rrd via strftime() */
69 char     *printtimelast(
70     long,
71     const char **);
72
73 /* pretty-print current time */
74 char     *printtimenow(
75     long,
76     const char **);
77
78 /* set an environment variable */
79 char     *rrdsetenv(
80     long,
81     const char **);
82
83 /* get an environment variable */
84 char     *rrdgetenv(
85     long,
86     const char **);
87
88 /* include the named file at this point */
89 char     *includefile(
90     long,
91     const char **);
92
93 /* for how long is the output of the cgi valid ? */
94 char     *rrdgoodfor(
95     long,
96     const char **);
97
98 /* return rrdcgi version string */
99 char     *rrdgetinternal(
100     long,
101     const char **);
102
103 char     *rrdstrip(
104     char *buf);
105 char     *scanargs(
106     char *line,
107     int *argc,
108     char ***args);
109
110 /* format at-time specified times using strftime */
111 char     *printstrftime(
112     long,
113     const char **);
114
115 /** HTTP protocol needs special format, and GMT time **/
116 char     *http_time(
117     time_t *);
118
119 /* return a pointer to newly allocated copy of this string */
120 char     *stralloc(
121     const char *);
122
123 /* global variable for rrdcgi */
124 s_cgi    *rrdcgiArg;
125
126 /* rrdcgiHeader
127  * 
128  *  Prints a valid CGI Header (Content-type...) etc.
129  */
130 void      rrdcgiHeader(
131     void);
132
133 /* rrdcgiDecodeString
134  * decode html escapes
135  */
136
137 char     *rrdcgiDecodeString(
138     char *text);
139
140 /* rrdcgiDebug
141  * 
142  *  Set/unsets debugging
143  */
144 void      rrdcgiDebug(
145     int level,
146     int where);
147
148 /* rrdcgiInit
149  *
150  *  Reads in variables set via POST or stdin.
151  */
152 s_cgi    *rrdcgiInit(
153     void);
154
155 /* rrdcgiGetValue
156  *
157  *  Returns the value of the specified variable or NULL if it's empty
158  *  or doesn't exist.
159  */
160 char     *rrdcgiGetValue(
161     s_cgi * parms,
162     const char *name);
163
164 /* rrdcgiFreeList
165  *
166  * Frees a list as returned by rrdcgiGetVariables()
167  */
168 void      rrdcgiFreeList(
169     char **list);
170
171 /* rrdcgiFree
172  *
173  * Frees the internal data structures
174  */
175 void      rrdcgiFree(
176     s_cgi * parms);
177
178 /*  rrdcgiReadVariables()
179  *
180  *  Read from stdin if no string is provided via CGI.  Variables that
181  *  doesn't have a value associated with it doesn't get stored.
182  */
183 s_var   **rrdcgiReadVariables(
184     void);
185
186
187 int       rrdcgiDebugLevel = 0;
188 int       rrdcgiDebugStderr = 1;
189 char     *rrdcgiHeaderString = NULL;
190 char     *rrdcgiType = NULL;
191
192 /* rrd interface to the variable functions {put,get}var() */
193 char     *rrdgetvar(
194     long argc,
195     const char **args);
196 char     *rrdsetvar(
197     long argc,
198     const char **args);
199 char     *rrdsetvarconst(
200     long argc,
201     const char **args);
202
203
204 /* variable store: put/get key-value pairs */
205 static int initvar(
206     );
207 static void donevar(
208     );
209 static const char *getvar(
210     const char *varname);
211 static const char *putvar(
212     const char *name,
213     const char *value,
214     int is_const);
215
216 /* key value pair that makes up an entry in the variable store */
217 typedef struct {
218     int       is_const; /* const variable or not */
219     const char *name;   /* variable name */
220     const char *value;  /* variable value */
221 } vardata;
222
223 /* the variable heap: 
224    start with a heapsize of 10 variables */
225 #define INIT_VARSTORE_SIZE      10
226 static vardata *varheap = NULL;
227 static size_t varheap_size = 0;
228
229 /* allocate and initialize variable heap */
230 static int initvar(
231     void)
232 {
233     varheap = (vardata *) malloc(sizeof(vardata) * INIT_VARSTORE_SIZE);
234     if (varheap == NULL) {
235         fprintf(stderr, "ERROR: unable to initialize variable store\n");
236         return -1;
237     }
238     memset(varheap, 0, sizeof(vardata) * INIT_VARSTORE_SIZE);
239     varheap_size = INIT_VARSTORE_SIZE;
240     return 0;
241 }
242
243 /* cleanup: free allocated memory */
244 static void donevar(
245     void)
246 {
247     int       i;
248
249     if (varheap) {
250         for (i = 0; i < (int) varheap_size; i++) {
251             if (varheap[i].name) {
252                 free((char *) varheap[i].name);
253             }
254             if (varheap[i].value) {
255                 free((char *) varheap[i].value);
256             }
257         }
258         free(varheap);
259     }
260 }
261
262 /* Get a variable from the variable store.
263    Return NULL in case the requested variable was not found. */
264 static const char *getvar(
265     const char *name)
266 {
267     int       i;
268
269     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
270         if (0 == strcmp(name, varheap[i].name)) {
271 #ifdef          DEBUG_VARS
272             printf("<!-- getvar(%s) -> %s -->\n", name, varheap[i].value);
273 #endif
274             return varheap[i].value;
275         }
276     }
277 #ifdef DEBUG_VARS
278     printf("<!-- getvar(%s) -> Not found-->\n", name);
279 #endif
280     return NULL;
281 }
282
283 /* Put a variable into the variable store. If a variable by that
284    name exists, it's value is overwritten with the new value unless it was
285    marked as 'const' (initialized by RRD::SETCONSTVAR).
286    Returns a copy the newly allocated value on success, NULL on error. */
287 static const char *putvar(
288     const char *name,
289     const char *value,
290     int is_const)
291 {
292     int       i;
293
294     for (i = 0; i < (int) varheap_size && varheap[i].name; i++) {
295         if (0 == strcmp(name, varheap[i].name)) {
296             /* overwrite existing entry */
297             if (varheap[i].is_const) {
298 #ifdef  DEBUG_VARS
299                 printf("<!-- setver(%s, %s): not assigning: "
300                        "const variable -->\n", name, value);
301 #endif
302                 return varheap[i].value;
303             }
304 #ifdef  DEBUG_VARS
305             printf("<!-- setvar(%s, %s): overwriting old value (%s) -->\n",
306                    name, value, varheap[i].value);
307 #endif
308             /* make it possible to promote a variable to readonly */
309             varheap[i].is_const = is_const;
310             free((char *) varheap[i].value);
311             varheap[i].value = stralloc(value);
312             return varheap[i].value;
313         }
314     }
315
316     /* no existing variable found by that name, add it */
317     if (i == (int) varheap_size) {
318         /* ran out of heap: resize heap to double size */
319         size_t    new_size = varheap_size * 2;
320
321         varheap = (vardata *) (realloc(varheap, sizeof(vardata) * new_size));
322         if (!varheap) {
323             fprintf(stderr, "ERROR: Unable to realloc variable heap\n");
324             return NULL;
325         }
326         /* initialize newly allocated memory */ ;
327         memset(&varheap[varheap_size], 0, sizeof(vardata) * varheap_size);
328         varheap_size = new_size;
329     }
330     varheap[i].is_const = is_const;
331     varheap[i].name = stralloc(name);
332     varheap[i].value = stralloc(value);
333
334 #ifdef          DEBUG_VARS
335     printf("<!-- setvar(%s, %s): adding new variable -->\n", name, value);
336 #endif
337     return varheap[i].value;
338 }
339
340 /* expand those RRD:* directives that can be used recursivly */
341 static char *rrd_expand_vars(
342     char *buffer)
343 {
344     int       i;
345
346 #ifdef DEBUG_PARSER
347     printf("expanding variables in '%s'\n", buffer);
348 #endif
349
350     for (i = 0; buffer[i]; i++) {
351         if (buffer[i] != '<')
352             continue;
353         parse(&buffer, i, "<RRD::CV", cgiget);
354         parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
355         parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
356         parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
357         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
358         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
359         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
360         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
361         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
362     }
363     return buffer;
364 }
365
366 static long goodfor = 0;
367 static char **calcpr = NULL;
368 static void calfree(
369     void)
370 {
371     if (calcpr) {
372         long      i;
373
374         for (i = 0; calcpr[i]; i++) {
375             if (calcpr[i]) {
376                 free(calcpr[i]);
377             }
378         }
379         if (calcpr) {
380             free(calcpr);
381         }
382         calcpr = NULL;
383     }
384 }
385
386 /* create freeable version of the string */
387 char     *stralloc(
388     const char *str)
389 {
390     if (!str) {
391         return NULL;
392     }
393     return strdup(str);
394 }
395
396 static int readfile(
397     const char *file_name,
398     char **buffer,
399     int skipfirst)
400 {
401     long      writecnt = 0, totalcnt = MEMBLK;
402     long      offset = 0;
403     FILE     *input = NULL;
404     char      c;
405
406     if ((strcmp("-", file_name) == 0)) {
407         input = stdin;
408     } else {
409         if ((input = fopen(file_name, "rb")) == NULL) {
410             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
411             return (-1);
412         }
413     }
414     if (skipfirst) {
415         do {
416             c = getc(input);
417             offset++;
418         } while (c != '\n' && !feof(input));
419     }
420     if (strcmp("-", file_name)) {
421         fseek(input, 0, SEEK_END);
422         /* have extra space for detecting EOF without realloc */
423         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
424         if (totalcnt < MEMBLK)
425             totalcnt = MEMBLK;  /* sanitize */
426         fseek(input, offset * sizeof(char), SEEK_SET);
427     }
428     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
429         perror("Allocate Buffer:");
430         exit(1);
431     };
432     do {
433         writecnt +=
434             fread((*buffer) + writecnt, 1,
435                   (totalcnt - writecnt) * sizeof(char), input);
436         if (writecnt >= totalcnt) {
437             totalcnt += MEMBLK;
438             if (((*buffer) =
439                  rrd_realloc((*buffer),
440                              (totalcnt + 4) * sizeof(char))) == NULL) {
441                 perror("Realloc Buffer:");
442                 exit(1);
443             };
444         }
445     } while (!feof(input));
446     (*buffer)[writecnt] = '\0';
447     if (strcmp("-", file_name) != 0) {
448         fclose(input);
449     };
450     return writecnt;
451 }
452
453 int main(
454     int argc,
455     char *argv[])
456 {
457     char     *buffer;
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     }
500
501     /* make sure we have one extra argument, 
502        if there are others, we do not care Apache gives several */
503
504     /* if ( (optind != argc-2 
505        && strstr( getenv("SERVER_SOFTWARE"),"Apache/2") != NULL) 
506        && optind != argc-1) { */
507
508     if (optind >= argc) {
509         fprintf(stderr, "ERROR: expected a filename\n");
510         exit(1);
511     } else {
512         readfile(argv[optind], &buffer, 1);
513     }
514
515     if (rrd_test_error()) {
516         fprintf(stderr, "ERROR: %s\n", rrd_get_error());
517         exit(1);
518     }
519
520     /* initialize variable heap */
521     initvar();
522
523 #ifdef DEBUG_PARSER
524     /* some fake header for testing */
525     printf("Content-Type: text/html\nContent-Length: 10000000\n\n\n");
526 #endif
527
528
529     /* expand rrd directives in buffer recursivly */
530     for (i = 0; buffer[i]; i++) {
531         if (buffer[i] != '<')
532             continue;
533         if (!filter) {
534             parse(&buffer, i, "<RRD::CV", cgiget);
535             parse(&buffer, i, "<RRD::CV::PATH", cgigetqp);
536             parse(&buffer, i, "<RRD::CV::QUOTE", cgigetq);
537             parse(&buffer, i, "<RRD::GETENV", rrdgetenv);
538         }
539         parse(&buffer, i, "<RRD::GETVAR", rrdgetvar);
540         parse(&buffer, i, "<RRD::GOODFOR", rrdgoodfor);
541         parse(&buffer, i, "<RRD::GRAPH", drawgraph);
542         parse(&buffer, i, "<RRD::INCLUDE", includefile);
543         parse(&buffer, i, "<RRD::PRINT", drawprint);
544         parse(&buffer, i, "<RRD::SETCONSTVAR", rrdsetvarconst);
545         parse(&buffer, i, "<RRD::SETENV", rrdsetenv);
546         parse(&buffer, i, "<RRD::SETVAR", rrdsetvar);
547         parse(&buffer, i, "<RRD::TIME::LAST", printtimelast);
548         parse(&buffer, i, "<RRD::TIME::NOW", printtimenow);
549         parse(&buffer, i, "<RRD::TIME::STRFTIME", printstrftime);
550         parse(&buffer, i, "<RRD::INTERNAL", rrdgetinternal);
551     }
552
553     if (!filter) {
554         printf("Content-Type: text/html\n"
555                "Content-Length: %zd\n", strlen(buffer));
556
557         if (labs(goodfor) > 0) {
558             time_t    now;
559
560             now = time(NULL);
561             printf("Last-Modified: %s\n", http_time(&now));
562             now += labs(goodfor);
563             printf("Expires: %s\n", http_time(&now));
564             if (goodfor < 0) {
565                 printf("Refresh: %ld\n", labs(goodfor));
566             }
567         }
568         printf("\n");
569     }
570
571     /* output result */
572     printf("%s", buffer);
573
574     /* cleanup */
575     calfree();
576     if (buffer) {
577         free(buffer);
578     }
579     donevar();
580     exit(0);
581 }
582
583 /* remove occurrences of .. this is a general measure to make
584    paths which came in via cgi do not go UP ... */
585
586 char     *rrdsetenv(
587     long argc,
588     const char **args)
589 {
590     if (argc >= 2) {
591         const size_t len = strlen(args[0]) + strlen(args[1]) + 2;
592         char *xyz = malloc(len);
593
594         if (xyz == NULL) {
595             return stralloc("[ERROR: allocating setenv buffer]");
596         };
597         snprintf(xyz, len, "%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             const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
786             char *err = malloc(len);
787
788             snprintf(err, len, "[ERROR: %s]", rrd_get_error());
789             rrd_clear_error();
790             return err;
791         } else {
792             return buffer;
793         }
794     } else {
795         return stralloc("[ERROR: No Inclue file defined]");
796     }
797 }
798
799 /* make a copy of buf and replace open/close brackets with '_' */
800 char     *rrdstrip(
801     char *buf)
802 {
803     char     *p;
804
805     if (buf == NULL) {
806         return NULL;
807     }
808     /* make a copy of the buffer */
809     buf = stralloc(buf);
810     if (buf == NULL) {
811         return NULL;
812     }
813
814     p = buf;
815     while (*p) {
816         if (*p == '<' || *p == '>') {
817             *p = '_';
818         }
819         p++;
820     }
821     return buf;
822 }
823
824 char     *cgigetq(
825     long argc,
826     const char **args)
827 {
828     if (argc >= 1) {
829         char     *buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
830         char     *buf2;
831         char     *c, *d;
832         int       qc = 0;
833
834         if (buf == NULL)
835             return NULL;
836
837         for (c = buf; *c != '\0'; c++)
838             if (*c == '"')
839                 qc++;
840         if ((buf2 = malloc((strlen(buf) + 4 * qc + 4))) == NULL) {
841             perror("Malloc Buffer");
842             exit(1);
843         };
844         c = buf;
845         d = buf2;
846         *(d++) = '"';
847         while (*c != '\0') {
848             if (*c == '"') {
849                 *(d++) = '"';
850                 *(d++) = '\'';
851                 *(d++) = '"';
852                 *(d++) = '\'';
853             }
854             *(d++) = *(c++);
855         }
856         *(d++) = '"';
857         *(d) = '\0';
858         free(buf);
859         return buf2;
860     }
861
862     return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
863 }
864
865 /* remove occurrences of .. this is a general measure to make
866    paths which came in via cgi do not go UP ... */
867
868 char     *cgigetqp(
869     long argc,
870     const char **args)
871 {
872     char     *buf;
873     char     *buf2;
874     char     *p;
875     char     *d;
876
877     if (argc < 1) {
878         return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
879     }
880
881     buf = rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
882     if (!buf) {
883         return NULL;
884     }
885
886     buf2 = malloc(strlen(buf) + 1);
887     if (!buf2) {
888         perror("cgigetqp(): Malloc Path Buffer");
889         exit(1);
890     };
891
892     p = buf;
893     d = buf2;
894
895     while (*p) {
896         /* prevent mallicious paths from entering the system */
897         if (p[0] == '.' && p[1] == '.') {
898             p += 2;
899             *d++ = '_';
900             *d++ = '_';
901         } else {
902             *d++ = *p++;
903         }
904     }
905
906     *d = 0;
907     free(buf);
908
909     /* Make sure the path is relative, e.g. does not start with '/' */
910     p = buf2;
911     while ('/' == *p) {
912         *p++ = '_';
913     }
914
915     return buf2;
916 }
917
918
919 char     *cgiget(
920     long argc,
921     const char **args)
922 {
923     if (argc >= 1)
924         return rrdstrip(rrdcgiGetValue(rrdcgiArg, args[0]));
925     else
926         return stralloc("[ERROR: not enough arguments for RRD::CV]");
927 }
928
929
930
931 char     *drawgraph(
932     long argc,
933     const char **args)
934 {
935     int       i, xsize, ysize;
936     double    ymin, ymax;
937
938     for (i = 0; i < argc; i++)
939         if (strcmp(args[i], "--imginfo") == 0 || strcmp(args[i], "-g") == 0)
940             break;
941     if (i == argc) {
942         args[argc++] = "--imginfo";
943         args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
944     }
945     calfree();
946     if (rrd_graph
947         (argc + 1, (char **) args - 1, &calcpr, &xsize, &ysize, NULL, &ymin,
948          &ymax) != -1) {
949         return stralloc(calcpr[0]);
950     } else {
951         if (rrd_test_error()) {
952             const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
953             char *err = malloc(len);
954             snprintf(err, len, "[ERROR: %s]", rrd_get_error());
955             rrd_clear_error();
956             return err;
957         }
958     }
959     return NULL;
960 }
961
962 char     *drawprint(
963     long argc,
964     const char **args)
965 {
966     if (argc == 1 && calcpr) {
967         long      i = 0;
968
969         while (calcpr[i] != NULL)
970             i++;        /*determine number lines in calcpr */
971         if (atol(args[0]) < i - 1)
972             return stralloc(calcpr[atol(args[0]) + 1]);
973     }
974     return stralloc("[ERROR: RRD::PRINT argument error]");
975 }
976
977 char     *printtimelast(
978     long argc,
979     const char **args)
980 {
981     time_t    last;
982     struct tm tm_last;
983     char     *buf;
984
985     if (argc == 2) {
986         buf = malloc(255);
987         if (buf == NULL) {
988             return stralloc("[ERROR: allocating strftime buffer]");
989         };
990         /* not raising argc in step with args - 1 since the last argument
991            will be used below for strftime  */
992
993         last = rrd_last(argc, (char **) args - 1);
994         if (rrd_test_error()) {
995             const size_t len = strlen(rrd_get_error()) + DS_NAM_SIZE;
996             char *err = malloc(len);
997             snprintf(err, len, "[ERROR: %s]", rrd_get_error());
998             rrd_clear_error();
999             return err;
1000         }
1001         tm_last = *localtime(&last);
1002         strftime(buf, 254, args[1], &tm_last);
1003         return buf;
1004     }
1005     return stralloc("[ERROR: expected <RRD::TIME::LAST file.rrd strftime-format>]");
1006 }
1007
1008 char     *printtimenow(
1009     long argc,
1010     const char **args)
1011 {
1012     time_t    now = time(NULL);
1013     struct tm tm_now;
1014     char     *buf;
1015
1016     if (argc == 1) {
1017         buf = malloc(255);
1018         if (buf == NULL) {
1019             return stralloc("[ERROR: allocating strftime buffer]");
1020         };
1021         tm_now = *localtime(&now);
1022         strftime(buf, 254, args[0], &tm_now);
1023         return buf;
1024     }
1025     if (argc < 1) {
1026         return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
1027     }
1028     return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
1029 }
1030
1031 /* Scan buffer until an unescaped '>' arives.
1032  * Update argument array with arguments found.
1033  * Return end cursor where parsing stopped, or NULL in case of failure.
1034  *
1035  * FIXME:
1036  * To allow nested constructs, we call rrd_expand_vars() for arguments
1037  * that contain RRD::x directives. These introduce a small memory leak
1038  * since we have to stralloc the arguments the way parse() works.
1039  */
1040 char     *scanargs(
1041     char *line,
1042     int *argument_count,
1043     char ***arguments)
1044 {
1045     char     *getP;     /* read cursor */
1046     char     *putP;     /* write cursor */
1047     char      Quote;    /* type of quote if in quoted string, 0 otherwise */
1048     int       tagcount; /* open tag count */
1049     int       in_arg;   /* if we currently are parsing an argument or not */
1050     int       argsz;    /* argument array size */
1051     int       curarg_contains_rrd_directives;
1052
1053     /* local array of arguments while parsing */
1054     int       argc = 1;
1055     char    **argv;
1056
1057 #ifdef DEBUG_PARSER
1058     printf("<-- scanargs(%s) -->\n", line);
1059 #endif
1060
1061     *arguments = NULL;
1062     *argument_count = 0;
1063
1064     /* create initial argument array of char pointers */
1065     argsz = 32;
1066     argv = (char **) malloc(argsz * sizeof(char *));
1067     if (!argv) {
1068         return NULL;
1069     }
1070     argv[0] = "rrdcgi";
1071
1072     /* skip leading blanks */
1073     while (isspace((int) *line)) {
1074         line++;
1075     }
1076
1077     getP = line;
1078     putP = line;
1079
1080     Quote = 0;
1081     in_arg = 0;
1082     tagcount = 0;
1083
1084     curarg_contains_rrd_directives = 0;
1085
1086     /* start parsing 'line' for arguments */
1087     while (*getP) {
1088         unsigned char c = *getP++;
1089
1090         if (c == '>' && !Quote && !tagcount) {
1091             /* this is our closing tag, quit scanning */
1092             break;
1093         }
1094
1095         /* remove all special chars */
1096         if (c < ' ') {
1097             c = ' ';
1098         }
1099
1100         switch (c) {
1101         case ' ':
1102             if (Quote || tagcount) {
1103                 /* copy quoted/tagged (=RRD expanded) string */
1104                 *putP++ = c;
1105             } else if (in_arg) {
1106                 /* end argument string */
1107                 *putP++ = 0;
1108                 in_arg = 0;
1109                 if (curarg_contains_rrd_directives) {
1110                     argv[argc - 1] =
1111                         rrd_expand_vars(stralloc(argv[argc - 1]));
1112                     curarg_contains_rrd_directives = 0;
1113                 }
1114             }
1115             break;
1116
1117         case '"':      /* Fall through */
1118         case '\'':
1119             if (Quote != 0) {
1120                 if (Quote == c) {
1121                     Quote = 0;
1122                 } else {
1123                     /* copy quoted string */
1124                     *putP++ = c;
1125                 }
1126             } else {
1127                 if (!in_arg) {
1128                     /* reference start of argument string in argument array */
1129                     argv[argc++] = putP;
1130                     in_arg = 1;
1131                 }
1132                 Quote = c;
1133             }
1134             break;
1135
1136         default:
1137             if (!in_arg) {
1138                 /* start new argument */
1139                 argv[argc++] = putP;
1140                 in_arg = 1;
1141             }
1142             if (c == '>') {
1143                 if (tagcount) {
1144                     tagcount--;
1145                 }
1146             }
1147             if (c == '<') {
1148                 tagcount++;
1149                 if (0 == strncmp(getP, "RRD::", strlen("RRD::"))) {
1150                     curarg_contains_rrd_directives = 1;
1151                 }
1152             }
1153             *putP++ = c;
1154             break;
1155         }
1156
1157         /* check if our argument array is still large enough */
1158         if (argc == argsz) {
1159             /* resize argument array */
1160             argsz *= 2;
1161             argv = rrd_realloc(argv, argsz * sizeof(char *));
1162             if (*argv == NULL) {
1163                 return NULL;
1164             }
1165         }
1166     }
1167
1168     /* terminate last argument found */
1169     *putP = '\0';
1170     if (curarg_contains_rrd_directives) {
1171         argv[argc - 1] = rrd_expand_vars(stralloc(argv[argc - 1]));
1172     }
1173 #ifdef DEBUG_PARSER
1174     if (argc > 1) {
1175         int       n;
1176
1177         printf("<-- arguments found [%d]\n", argc);
1178         for (n = 0; n < argc; n++) {
1179             printf("arg %02d: '%s'\n", n, argv[n]);
1180         }
1181         printf("-->\n");
1182     } else {
1183         printf("<!-- No arguments found -->\n");
1184     }
1185 #endif
1186
1187     /* update caller's notion of the argument array and it's size */
1188
1189     /* note this is a bit of a hack since the rrd_cgi code used to just put
1190        its arguments into a normal array starting at 0 ... since the rrd_*
1191        commands expect and argc/argv array we used to just shift everything
1192        by -1 ... this in turn exploded when a rrd_* function tried to print
1193        argv[0] ... hence we are now doing everything in argv style but hand
1194        over seemingly the old array ... but doing argv-1 will actually end
1195        up in a 'good' place now. */
1196
1197     *arguments = argv+1;
1198     *argument_count = argc-1;
1199
1200     if (Quote) {
1201         return NULL;
1202     }
1203
1204     /* Return new scanning cursor:
1205        pointer to char after closing bracket */
1206     return getP;
1207 }
1208
1209
1210 /*
1211  * Parse(): scan current portion of buffer for given tag.
1212  * If found, parse tag arguments and call 'func' for it.
1213  * The result of func is inserted at the current position
1214  * in the buffer.
1215  */
1216 int parse(
1217     char **buf,         /* buffer */
1218     long i,             /* offset in buffer */
1219     char *tag,          /* tag to handle  */
1220     char *    (*func) (long,
1221                        const char **)   /* function to call for 'tag' */
1222     )
1223 {
1224     /* the name of the vairable ... */
1225     char     *val;
1226     long      valln;
1227     char    **args;
1228     char     *end;
1229     long      end_offset;
1230     int       argc;
1231     size_t    taglen = strlen(tag);
1232
1233     /* Current position in buffer should start with 'tag' */
1234     if (strncmp((*buf) + i, tag, taglen) != 0) {
1235         return 0;
1236     }
1237     /* .. and match exactly (a whitespace following 'tag') */
1238     if (!isspace(*((*buf) + i + taglen))) {
1239         return 0;
1240     }
1241 #ifdef DEBUG_PARSER
1242     printf("parse(): handling tag '%s'\n", tag);
1243 #endif
1244
1245     /* Scan for arguments following the tag;
1246        scanargs() puts \0 into *buf ... so after scanargs it is probably
1247        not a good time to use strlen on buf */
1248     end = scanargs((*buf) + i + taglen, &argc, &args);
1249     if (end) {
1250         /* got arguments, call function for 'tag' with arguments */
1251         val = func(argc, (const char **) args);
1252         free(args-1);
1253     } else {
1254         /* next call, try parsing at current offset +1 */
1255         end = (*buf) + i + 1;
1256
1257         val = stralloc("[ERROR: Parsing Problem with the following text\n"
1258                        " Check original file. This may have been altered "
1259                        "by parsing.]\n\n");
1260     }
1261
1262     /* remember offset where we have to continue parsing */
1263     end_offset = end - (*buf);
1264
1265     valln = 0;
1266     if (val) {
1267         valln = strlen(val);
1268     }
1269
1270     /* Optionally resize buffer to hold the replacement value:
1271        Calculating the new length of the buffer is simple. add current
1272        buffer pos (i) to length of string after replaced tag to length
1273        of replacement string and add 1 for the final zero ... */
1274     if (end - (*buf) < (i + valln)) {
1275         /* make sure we do not shrink the mallocd block */
1276         size_t    newbufsize = i + strlen(end) + valln + 1;
1277
1278         *buf = rrd_realloc(*buf, newbufsize);
1279
1280         if (*buf == NULL) {
1281             perror("Realoc buf:");
1282             exit(1);
1283         };
1284     }
1285
1286     /* Update new end pointer:
1287        make sure the 'end' pointer gets moved along with the 
1288        buf pointer when realloc moves memory ... */
1289     end = (*buf) + end_offset;
1290
1291     /* splice the variable:
1292        step 1. Shift pending data to make room for 'val' */
1293     memmove((*buf) + i + valln, end, strlen(end) + 1);
1294
1295     /* step 2. Insert val */
1296     if (val) {
1297         memmove((*buf) + i, val, valln);
1298         free(val);
1299     }
1300     return (valln > 0 ? valln - 1 : valln);
1301 }
1302
1303 char     *http_time(
1304     time_t *now)
1305 {
1306     struct tm *tmptime;
1307     static char buf[60];
1308
1309     tmptime = gmtime(now);
1310     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", tmptime);
1311     return (buf);
1312 }
1313
1314 void rrdcgiHeader(
1315     void)
1316 {
1317     if (rrdcgiType)
1318         printf("Content-type: %s\n", rrdcgiType);
1319     else
1320         printf("Content-type: text/html\n");
1321     if (rrdcgiHeaderString)
1322         printf("%s", rrdcgiHeaderString);
1323     printf("\n");
1324 }
1325
1326 void rrdcgiDebug(
1327     int level,
1328     int where)
1329 {
1330     if (level > 0)
1331         rrdcgiDebugLevel = level;
1332     else
1333         rrdcgiDebugLevel = 0;
1334     if (where)
1335         rrdcgiDebugStderr = 0;
1336     else
1337         rrdcgiDebugStderr = 1;
1338 }
1339
1340 char     *rrdcgiDecodeString(
1341     char *text)
1342 {
1343     char     *cp, *xp;
1344
1345     for (cp = text, xp = text; *cp; cp++) {
1346         if (*cp == '%') {
1347             if (strchr("0123456789ABCDEFabcdef", *(cp + 1))
1348                 && strchr("0123456789ABCDEFabcdef", *(cp + 2))) {
1349                 if (islower(*(cp + 1)))
1350                     *(cp + 1) = toupper(*(cp + 1));
1351                 if (islower(*(cp + 2)))
1352                     *(cp + 2) = toupper(*(cp + 2));
1353                 *(xp) =
1354                     (*(cp + 1) >=
1355                      'A' ? *(cp + 1) - 'A' + 10 : *(cp + 1) - '0') * 16 +
1356                     (*(cp + 2) >=
1357                      'A' ? *(cp + 2) - 'A' + 10 : *(cp + 2) - '0');
1358                 xp++;
1359                 cp += 2;
1360             }
1361         } else {
1362             *(xp++) = *cp;
1363         }
1364     }
1365     memset(xp, 0, cp - xp);
1366     return text;
1367 }
1368
1369 /*  rrdcgiReadVariables()
1370  *
1371  *  Read from stdin if no string is provided via CGI.  Variables that
1372  *  doesn't have a value associated with it doesn't get stored.
1373  */
1374 s_var   **rrdcgiReadVariables(
1375     void)
1376 {
1377     int       length;
1378     char     *line = NULL;
1379     int       numargs;
1380     char     *cp, *ip, *esp, *sptr;
1381     s_var   **result;
1382     int       i, k, len;
1383     char      tmp[101];
1384     size_t    tmplen;
1385
1386     cp = getenv("REQUEST_METHOD");
1387     ip = getenv("CONTENT_LENGTH");
1388
1389     if (cp && !strcmp(cp, "POST")) {
1390         if (ip) {
1391             length = atoi(ip);
1392             if ((line = (char *) malloc(length + 2)) == NULL)
1393                 return NULL;
1394             if (fgets(line, length + 1, stdin) == NULL)
1395                 return NULL;
1396         } else
1397             return NULL;
1398     } else if (cp && !strcmp(cp, "GET")) {
1399         esp = getenv("QUERY_STRING");
1400         if (esp && strlen(esp)) {
1401             if ((line = strdup(esp)) == NULL)
1402                 return NULL;
1403         } else
1404             return NULL;
1405     } else {
1406         length = 0;
1407         printf("(offline mode: enter name=value pairs on standard input)\n");
1408         memset(tmp, 0, sizeof(tmp));
1409         while ((cp = fgets(tmp, 100, stdin)) != NULL) {
1410             if ((tmplen = strlen(tmp)) != 0) {
1411                 if (tmp[tmplen - 1] == '\n')
1412                     tmp[tmplen - 1] = '&';
1413                 length += tmplen;
1414                 len = (length + 1) * sizeof(char);
1415                 if ((unsigned) length > tmplen) {
1416                     if ((line = (char *) realloc(line, len)) == NULL)
1417                         return NULL;
1418                     strncat(line, tmp, tmplen);
1419                 } else {
1420                     if ((line = strdup(tmp)) == NULL)
1421                         return NULL;
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 = strlen(result[k]->value) + (ip - esp) + 2;
1520                 if ((sptr = (char *) calloc(len, sizeof(char))) == NULL)
1521                     return NULL;
1522                 snprintf(sptr, len, "%s\n%s", result[k]->value, cp);
1523                 free(result[k]->value);
1524                 result[k]->value = rrdcgiDecodeString(sptr);
1525             }
1526         }
1527         cp = ++ip;
1528     }
1529     return result;
1530 }
1531
1532 /*  rrdcgiInit()
1533  *
1534  *  Read from stdin if no string is provided via CGI.  Variables that
1535  *  doesn't have a value associated with it doesn't get stored.
1536  */
1537 s_cgi    *rrdcgiInit(
1538     void)
1539 {
1540     s_cgi    *res;
1541     s_var   **vars;
1542
1543     vars = rrdcgiReadVariables();
1544
1545     if (!vars)
1546         return NULL;
1547
1548     if ((res = (s_cgi *) malloc(sizeof(s_cgi))) == NULL)
1549         return NULL;
1550     res->vars = vars;
1551
1552     return res;
1553 }
1554
1555 char     *rrdcgiGetValue(
1556     s_cgi * parms,
1557     const char *name)
1558 {
1559     int       i;
1560
1561     if (!parms || !parms->vars)
1562         return NULL;
1563     for (i = 0; parms->vars[i]; i++)
1564         if (!strcmp(name, parms->vars[i]->name)) {
1565             if (rrdcgiDebugLevel > 0) {
1566                 if (rrdcgiDebugStderr)
1567                     fprintf(stderr, "%s found as %s\n", name,
1568                             parms->vars[i]->value);
1569                 else
1570                     printf("%s found as %s<br>\n", name,
1571                            parms->vars[i]->value);
1572             }
1573             return parms->vars[i]->value;
1574         }
1575     if (rrdcgiDebugLevel) {
1576         if (rrdcgiDebugStderr)
1577             fprintf(stderr, "%s not found\n", name);
1578         else
1579             printf("%s not found<br>\n", name);
1580     }
1581     return NULL;
1582 }
1583
1584 void rrdcgiFreeList(
1585     char **list)
1586 {
1587     int       i;
1588
1589     for (i = 0; list[i] != NULL; i++)
1590         free(list[i]);
1591     free(list);
1592 }
1593
1594 void rrdcgiFree(
1595     s_cgi * parms)
1596 {
1597     int       i;
1598
1599     if (!parms)
1600         return;
1601     if (parms->vars) {
1602         for (i = 0; parms->vars[i]; i++) {
1603             if (parms->vars[i]->name)
1604                 free(parms->vars[i]->name);
1605             if (parms->vars[i]->value)
1606                 free(parms->vars[i]->value);
1607             free(parms->vars[i]);
1608         }
1609         free(parms->vars);
1610     }
1611     free(parms);
1612
1613     if (rrdcgiHeaderString) {
1614         free(rrdcgiHeaderString);
1615         rrdcgiHeaderString = NULL;
1616     }
1617     if (rrdcgiType) {
1618         free(rrdcgiType);
1619         rrdcgiType = NULL;
1620     }
1621 }