allow rrd_cgi to deal with umlauts -- Alexander Schwartz <alexander.schwartz@gmx...
[rrdtool.git] / src / rrd_cgi.c
1 /*****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  *****************************************************************************
4  * rrd_cgi.c  RRD Web Page Generator
5  *****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include <cgi.h>
9 #include <time.h>
10
11
12 #define MEMBLK 1024
13
14 /* global variable for libcgi */
15 s_cgi **cgiArg;
16
17 /* in arg[0] find tags beginning with arg[1] call arg[2] on them
18    and replace by result of arg[2] call */
19 int parse(char **, long, char *, char *(*)(long , char **));
20
21 /**************************************************/
22 /* tag replacers ... they are called from parse   */
23 /* through function pointers                      */
24 /**************************************************/
25
26 /* return cgi var named arg[0] */ 
27 char* cgiget(long , char **);
28
29 /* return a quoted cgi var named arg[0] */ 
30 char* cgigetq(long , char **);
31
32 /* return a quoted and sanitized cgi variable */
33 char* cgigetqp(long , char **);
34
35 /* call rrd_graph and insert apropriate image tag */
36 char* drawgraph(long, char **);
37
38 /* return PRINT functions from last rrd_graph call */
39 char* drawprint(long, char **);
40
41 /* pretty-print the <last></last> value for some.rrd via strftime() */
42 char* printtimelast(long, char **);
43
44 /* pretty-print current time */
45 char* printtimenow(long,char **);
46
47 /* set an evironment variable */
48 char* rrdsetenv(long, char **);
49
50 /* get an evironment variable */
51 char* rrdgetenv(long, char **);
52
53 /* include the named file at this point */
54 char* includefile(long, char **);
55
56 /* for how long is the output of the cgi valid ? */
57 char* rrdgoodfor(long, char **);
58
59 /** http protocol needs special format, and GMT time **/
60 char *http_time(time_t *);
61
62 /* return a pointer to newly alocated copy of this string */
63 char *stralloc(char *);
64
65 static long goodfor=0;
66 static char **calcpr=NULL;
67 static void calfree (void){
68   if (calcpr) {
69     long i;
70     for(i=0;calcpr[i];i++){
71       if (calcpr[i]){
72               free(calcpr[i]);
73       }
74     } 
75     if (calcpr) {
76             free(calcpr);
77     }
78   }
79 }
80
81 /* create freeable version of the string */
82 char * stralloc(char *str){
83   char *nstr = malloc((strlen(str)+1)*sizeof(char));
84   strcpy(nstr,str);
85   return(nstr);
86 }
87
88 int main(int argc, char *argv[]) {
89   long length;
90   char *buffer;
91   char *server_url = NULL;
92   long i;
93   long filter=0;
94 #ifdef MUST_DISABLE_SIGFPE
95   signal(SIGFPE,SIG_IGN);
96 #endif
97 #ifdef MUST_DISABLE_FPMASK
98   fpsetmask(0);
99 #endif
100   /* what do we get for cmdline arguments?
101   for (i=0;i<argc;i++)
102   printf("%d-'%s'\n",i,argv[i]); */
103   while (1){
104       static struct option long_options[] =
105       {
106           {"filter",          no_argument, 0, 'f'},
107           {0,0,0,0}
108       };
109       int option_index = 0;
110       int opt;
111       opt = getopt_long(argc, argv, "f", 
112                         long_options, &option_index);
113       if (opt == EOF)
114           break;
115       switch(opt) {
116       case 'f':
117           filter=1;
118           break;
119       case '?':
120             printf("unknown commandline option '%s'\n",argv[optind-1]);
121             return -1;
122       }
123   }
124
125   if(filter==0) {
126       cgiDebug(0,0);
127       cgiArg = cgiInit ();
128       server_url = getenv("SERVER_URL");
129   }
130
131   if (optind != argc-1) { 
132      fprintf(stderr, "ERROR: expected a filename\n");
133      exit(1);
134   } else {
135      length  = readfile(argv[optind], &buffer, 1);
136   }
137    
138   if(rrd_test_error()){
139       fprintf(stderr, "ERROR: %s\n",rrd_get_error());
140       exit(1);
141   }
142
143
144   if(filter==0) {
145   /* pass 1 makes only sense in cgi mode */
146       for (i=0;buffer[i] != '\0'; i++){    
147           i +=parse(&buffer,i,"<RRD::CV",cgiget);
148           i +=parse(&buffer,i,"<RRD::CV::QUOTE",cgigetq);
149           i +=parse(&buffer,i,"<RRD::CV::PATH",cgigetqp);
150           i +=parse(&buffer,i,"<RRD::GETENV",rrdgetenv);         
151       }
152   }
153
154   /* pass 2 */
155   for (i=0;buffer[i] != '\0'; i++){    
156       i += parse(&buffer,i,"<RRD::GOODFOR",rrdgoodfor);
157       i += parse(&buffer,i,"<RRD::SETENV",rrdsetenv);
158       i += parse(&buffer,i,"<RRD::INCLUDE",includefile);
159       i += parse(&buffer,i,"<RRD::TIME::LAST",printtimelast);
160       i += parse(&buffer,i,"<RRD::TIME::NOW",printtimenow);
161   }
162
163   /* pass 3 */
164   for (i=0;buffer[i] != '\0'; i++){    
165     i += parse(&buffer,i,"<RRD::GRAPH",drawgraph);
166     i += parse(&buffer,i,"<RRD::PRINT",drawprint);
167   }
168
169   if (filter==0){
170       printf ("Content-Type: text/html\n"
171               "Content-Length: %d\n", strlen(buffer));
172       if (labs(goodfor) > 0){
173                   time_t now;
174                   now = time(NULL);
175                   printf ("Last-Modified: %s\n",http_time(&now));
176                   now += labs(goodfor);
177                   printf ("Expires: %s\n",http_time(&now));
178                   if (goodfor < 0) {
179                     printf("Refresh: %ld\n", labs(goodfor));
180                   }
181       }
182       printf ("\n");
183   }
184   printf ("%s", buffer);
185   calfree();
186   if (buffer){
187      free(buffer);
188   }
189   exit(0);
190 }
191
192 /* remove occurences of .. this is a general measure to make
193    paths which came in via cgi do not go UP ... */
194
195 char* rrdsetenv(long argc, char **args){
196   if (argc >= 2) {
197       char *xyz=malloc((strlen(args[0])+strlen(args[1])+3)*sizeof(char));
198       if (xyz == NULL){ 
199         return stralloc("[ERROR: allocating setenv buffer]");
200       };
201       sprintf(xyz,"%s=%s",args[0],args[1]);
202       if( putenv(xyz) == -1) {
203         return stralloc("[ERROR: faild to do putenv]");
204       };
205   } else {
206     return stralloc("[ERROR: setenv faild because not enough arguments were defined]");
207   }
208   return stralloc("");
209 }
210
211 char* rrdgetenv(long argc, char **args){
212   if (argc != 1) {
213     return stralloc("[ERROR: getenv faild because it did not get 1 argument only]");
214   }
215   else if (getenv(args[0]) == NULL) {
216     return stralloc("");
217   }
218   else {
219     return stralloc(getenv(args[0]));
220   }
221 }
222
223 char* rrdgoodfor(long argc, char **args){
224   if (argc == 1) {
225       goodfor = atol(args[0]);
226   } else {
227     return stralloc("[ERROR: goodfor expected 1 argument]");
228   }
229    
230   if (goodfor == 0){
231      return stralloc("[ERROR: goodfor value must not be 0]");
232   }
233    
234   return stralloc("");
235 }
236
237 char* includefile(long argc, char **args){
238   char *buffer;
239   if (argc >= 1) {
240       readfile(args[0], &buffer, 0);
241       if (rrd_test_error()) {
242           char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
243           sprintf(err, "[ERROR: %s]",rrd_get_error());
244           rrd_clear_error();
245           return err;
246       } else {
247        return buffer;
248       }
249   }
250   else
251   {
252       return stralloc("[ERROR: No Inclue file defined]");
253   }
254 }
255
256 char* rrdstrip(char *buf){
257   char *start;
258   if (buf == NULL) return NULL;
259   buf = stralloc(buf); /* make a copy of the buffer */
260   if (buf == NULL) return NULL;
261   while ((start = strstr(buf,"<"))){
262     *start = '_';
263   }
264   while ((start = strstr(buf,">"))){
265     *start = '_';
266   }
267   return buf;
268 }
269
270 char* cgigetq(long argc, char **args){
271   if (argc>= 1){
272     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
273     char *buf2;
274     char *c,*d;
275     int  qc=0;
276     if (buf==NULL) return NULL;
277
278     for(c=buf;*c != '\0';c++)
279       if (*c == '"') qc++;
280     if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
281         perror("Malloc Buffer");
282         exit(1);
283     };
284     c=buf;
285     d=buf2;
286     *(d++) = '"';
287     while(*c != '\0'){
288         if (*c == '"') {
289             *(d++) = '"';
290             *(d++) = '\'';
291             *(d++) = '"';
292             *(d++) = '\'';
293         } 
294         *(d++) = *(c++);
295     }
296     *(d++) = '"';
297     *(d) = '\0';
298     free(buf);
299     return buf2;
300   }
301
302   return stralloc("[ERROR: not enough argument for RRD::CV::QUOTE]");
303 }
304
305 /* remove occurences of .. this is a general measure to make
306    paths which came in via cgi do not go UP ... */
307
308 char* cgigetqp(long argc, char **args){
309   if (argc>= 1){
310     char *buf = rrdstrip(cgiGetValue(cgiArg,args[0]));
311     char *buf2;
312     char *c,*d;
313     int  qc=0;
314     if (buf==NULL) return NULL;
315
316     for(c=buf;*c != '\0';c++)
317       if (*c == '"') qc++;
318     if((buf2=malloc((strlen(buf) + qc*4 +4) * sizeof(char)))==NULL){
319         perror("Malloc Buffer");
320         exit(1);
321     };
322     c=buf;
323     d=buf2;
324     *(d++) = '"';
325     while(*c != '\0'){
326         if (*c == '"') {
327             *(d++) = '"';
328             *(d++) = '\'';
329             *(d++) = '"';
330             *(d++) = '\'';
331         } 
332         if(*c == '/') {
333             *(d++) = '_';c++;
334         } else {
335             if (*c=='.' && *(c+1) == '.'){
336                 c += 2;
337                 *(d++) = '_'; *(d++) ='_';      
338             } else {
339                 
340                 *(d++) = *(c++);
341             }
342         }
343     }
344     *(d++) = '"';
345     *(d) = '\0';
346     free(buf);
347     return buf2;
348   }
349
350   return stralloc("[ERROR: not enough arguments for RRD::CV::PATH]");
351
352 }
353
354
355 char* cgiget(long argc, char **args){
356   if (argc>= 1)
357     return rrdstrip(cgiGetValue(cgiArg,args[0]));
358   else
359     return stralloc("[ERROR: not enough arguments for RRD::CV]");
360 }
361
362
363
364 char* drawgraph(long argc, char **args){
365   int i,xsize, ysize;
366   for(i=0;i<argc;i++)
367     if(strcmp(args[i],"--imginfo")==0 || strcmp(args[i],"-g")==0) break;
368   if(i==argc) {
369     args[argc++] = "--imginfo";
370     args[argc++] = "<IMG SRC=\"./%s\" WIDTH=\"%lu\" HEIGHT=\"%lu\">";
371   }
372   optind=0; /* reset gnu getopt */
373   opterr=0; /* reset gnu getopt */
374   calfree();
375   if( rrd_graph(argc+1, args-1, &calcpr, &xsize, &ysize) != -1 ) {
376     return stralloc(calcpr[0]);
377   } else {
378     if (rrd_test_error()) {
379       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
380       sprintf(err, "[ERROR: %s]",rrd_get_error());
381       rrd_clear_error();
382       calfree();
383       return err;
384     }
385   }
386   return NULL;
387 }
388
389 char* drawprint(long argc, char **args){
390   if (argc==1 && calcpr){
391     long i=0;
392     while (calcpr[i] != NULL) i++; /*determine number lines in calcpr*/
393     if (atol(args[0])<i-1)
394       return stralloc(calcpr[atol(args[0])+1]);    
395   }
396   return stralloc("[ERROR: RRD::PRINT argument error]");
397 }
398
399 char* printtimelast(long argc, char **args) {
400   time_t last;
401   struct tm tm_last;
402   char *buf;
403   if ( argc == 2 ) {
404     buf = malloc(255);
405     if (buf == NULL){   
406         return stralloc("[ERROR: allocating strftime buffer]");
407     };
408     last = rrd_last(argc+1, args-1); 
409     if (rrd_test_error()) {
410       char *err = malloc((strlen(rrd_get_error())+DS_NAM_SIZE)*sizeof(char));
411       sprintf(err, "[ERROR: %s]",rrd_get_error());
412       rrd_clear_error();
413       return err;
414     }
415     tm_last = *localtime(&last);
416     strftime(buf,254,args[1],&tm_last);
417     return buf;
418   }
419   if ( argc < 2 ) {
420     return stralloc("[ERROR: too few arguments for RRD::TIME::LAST]");
421   }
422   return stralloc("[ERROR: not enough arguments for RRD::TIME::LAST]");
423 }
424
425 char* printtimenow(long argc, char **args) {
426   time_t now = time(NULL);
427   struct tm tm_now;
428   char *buf;
429   if ( argc == 1 ) {
430     buf = malloc(255);
431     if (buf == NULL){   
432         return stralloc("[ERROR: allocating strftime buffer]");
433     };
434     tm_now = *localtime(&now);
435     strftime(buf,254,args[0],&tm_now);
436     return buf;
437   }
438   if ( argc < 1 ) {
439     return stralloc("[ERROR: too few arguments for RRD::TIME::NOW]");
440   }
441   return stralloc("[ERROR: not enough arguments for RRD::TIME::NOW]");
442 }
443
444 /* scan aLine until an unescaped '>' arives */
445 char* scanargs(char *aLine, long *argc, char ***args)
446 {
447   char        *getP, *putP;
448   char        Quote = 0;
449   int argal = MEMBLK;
450   int braket = 0;
451   int inArg = 0;
452   if (((*args) = (char **) malloc(MEMBLK*sizeof(char *))) == NULL)   {
453     return NULL;
454   }
455   /* sikp leading blanks */
456   while (*aLine && *aLine <= ' ') aLine++;
457   
458   *argc = 0;
459   getP = aLine;
460   putP = aLine;
461   while (*getP && !( !Quote  && (braket == 0) && ((*getP) == '>'))){
462     if ((unsigned)*getP < ' ') *getP = ' '; /*remove all special chars*/
463     switch (*getP) {
464     case ' ': 
465       if (Quote){
466         *(putP++)=*getP;
467       } else 
468         if(inArg) {
469           *(putP++) = 0;
470           inArg = 0;
471         }
472       break;
473     case '"':
474     case '\'':
475       if (Quote != 0) {
476         if (Quote == *getP) 
477           Quote = 0;
478         else {
479           *(putP++)=*getP;
480         }
481       } else {
482         if(!inArg){
483           (*args)[++(*argc)] = putP;
484           inArg=1;
485         }           
486         Quote = *getP;
487       }
488       break;
489     default:
490       if (Quote == 0 && (*getP) == '<') {
491         braket++;
492       }
493       if (Quote == 0 && (*getP) == '>') {
494         braket--;
495       }
496
497       if(!inArg){
498         (*args)[++(*argc)] = putP;
499         inArg=1;
500       }
501       *(putP++)=*getP;
502       break;
503     }
504     if ((*argc) >= argal-10 ) {
505       argal += MEMBLK;
506     if (((*args)=rrd_realloc((*args),(argal)*sizeof(char *))) == NULL) {
507         return NULL;
508       }
509     }   
510     getP++;
511   }
512   
513   *putP = '\0';
514   (*argc)++;
515   if (Quote) 
516     return NULL;
517   else
518     return getP+1; /* pointer to next char after parameter */
519 }
520
521
522
523 int parse(char **buf, long i, char *tag, 
524             char *(*func)(long argc, char **args)){
525
526   /* the name of the vairable ... */
527   char *val;
528   long valln;  
529   char **args;
530   char *end;
531   long end_offset;
532   long argc;
533   /* do we like it ? */
534   if (strncmp((*buf)+i, tag, strlen(tag))!=0) return 0;      
535   if (! isspace(*( (*buf) + i + strlen(tag) )) ) return 0;
536   /* scanargs puts \0 into *buf ... so after scanargs it is probably
537      not a good time to use strlen on buf */
538   end = scanargs((*buf)+i+strlen(tag),&argc,&args);
539   if (! end) {
540     for (;argc>2;argc--){
541       *((args[argc-1])-1)=' ';
542     }
543     val = stralloc("[ERROR: Parsing Problem with the following text\n"
544                    " Check original file. This may have been altered by parsing.]\n\n");
545     end = (*buf)+i+1;
546   } else {
547     val = func(argc-1,args+1);
548     free (args);
549   }
550   /* for (ii=0;ii<argc;ii++) printf("'%s'\n", args[ii]); */
551   if (val != NULL) {
552     valln = strlen(val); 
553   } else { valln = 0;}
554   
555   /* make enough room for replacement */
556   end_offset = end - (*buf);
557   if(end-(*buf) < i + valln){ /* make sure we do not shrink the mallocd block */
558   /* calculating the new length of the buffer is simple. add current
559      buffer pos (i) to length of string after replaced tag to length
560      of replacement string and add 1 for the final zero ... */
561     if(((*buf) = rrd_realloc((*buf),
562                          (i+strlen(end) + valln +1) * sizeof(char)))==NULL){
563       perror("Realoc buf:");
564       exit(1);
565     };
566   }
567   end = (*buf) + end_offset; /* make sure the 'end' pointer gets moved
568                                 along with the buf pointer when realoc
569                                 moves memmory ... */
570   /* splice the variable */
571   memmove ((*buf)+i+valln,end,strlen(end)+1);
572   if (val != NULL ) memmove ((*buf)+i,val, valln);
573   if (val){ free(val);}
574   return valln > 0 ? valln-1: valln;
575 }
576
577 char *
578 http_time(time_t *now) {
579         struct tm *tmptime;
580         static char buf[60];
581
582         tmptime=gmtime(now);
583         strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",tmptime);
584         return(buf);
585 }