1 /*
2  * CGI template function.
3  *
4  * Copyright 2007-2015 by Apple Inc.
5  * Copyright 1997-2006 by Easy Software Products.
6  *
7  * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
8  */
9 
10 #include "cgi-private.h"
11 #include <errno.h>
12 #include <regex.h>
13 
14 
15 /*
16  * Local functions...
17  */
18 
19 static void	cgi_copy(FILE *out, FILE *in, int element, char term,
20 		         int indent);
21 static void	cgi_puts(const char *s, FILE *out);
22 static void	cgi_puturi(const char *s, FILE *out);
23 
24 
25 /*
26  * 'cgiCopyTemplateFile()' - Copy a template file and replace all the
27  *                           '{variable}' strings with the variable value.
28  */
29 
30 void
cgiCopyTemplateFile(FILE * out,const char * tmpl)31 cgiCopyTemplateFile(FILE       *out,	/* I - Output file */
32                     const char *tmpl)	/* I - Template file to read */
33 {
34   FILE	*in;				/* Input file */
35 
36 
37   fprintf(stderr, "DEBUG2: cgiCopyTemplateFile(out=%p, tmpl=\"%s\")\n", out,
38           tmpl ? tmpl : "(null)");
39 
40  /*
41   * Range check input...
42   */
43 
44   if (!tmpl || !out)
45     return;
46 
47  /*
48   * Open the template file...
49   */
50 
51   if ((in = fopen(tmpl, "r")) == NULL)
52   {
53     fprintf(stderr, "ERROR: Unable to open template file \"%s\" - %s\n",
54             tmpl ? tmpl : "(null)", strerror(errno));
55     return;
56   }
57 
58  /*
59   * Parse the file to the end...
60   */
61 
62   cgi_copy(out, in, 0, 0, 0);
63 
64  /*
65   * Close the template file and return...
66   */
67 
68   fclose(in);
69 }
70 
71 
72 /*
73  * 'cgiCopyTemplateLang()' - Copy a template file using a language...
74  */
75 
76 void
cgiCopyTemplateLang(const char * tmpl)77 cgiCopyTemplateLang(const char *tmpl)	/* I - Base filename */
78 {
79   char		filename[1024],		/* Filename */
80 		locale[16],		/* Locale name */
81 		*locptr;		/* Pointer into locale name */
82   const char	*directory,		/* Directory for templates */
83 		*lang;			/* Language */
84   FILE		*in;			/* Input file */
85 
86 
87   fprintf(stderr, "DEBUG2: cgiCopyTemplateLang(tmpl=\"%s\")\n",
88           tmpl ? tmpl : "(null)");
89 
90  /*
91   * Convert the language to a locale name...
92   */
93 
94   locale[0] = '\0';
95 
96   if ((lang = getenv("LANG")) != NULL)
97   {
98     locale[0] = '/';
99     strlcpy(locale + 1, lang, sizeof(locale) - 1);
100 
101     if ((locptr = strchr(locale, '.')) != NULL)
102       *locptr = '\0';			/* Strip charset */
103   }
104 
105   fprintf(stderr, "DEBUG2: lang=\"%s\", locale=\"%s\"...\n",
106           lang ? lang : "(null)", locale);
107 
108  /*
109   * See if we have a template file for this language...
110   */
111 
112   directory = cgiGetTemplateDir();
113 
114   snprintf(filename, sizeof(filename), "%s%s/%s", directory, locale, tmpl);
115   if ((in = fopen(filename, "r")) == NULL)
116   {
117     locale[3] = '\0';
118 
119     snprintf(filename, sizeof(filename), "%s%s/%s", directory, locale, tmpl);
120     if ((in = fopen(filename, "r")) == NULL)
121     {
122       snprintf(filename, sizeof(filename), "%s/%s", directory, tmpl);
123       in = fopen(filename, "r");
124     }
125   }
126 
127   fprintf(stderr, "DEBUG2: Template file is \"%s\"...\n", filename);
128 
129  /*
130   * Open the template file...
131   */
132 
133   if (!in)
134   {
135     fprintf(stderr, "ERROR: Unable to open template file \"%s\" - %s\n",
136             filename, strerror(errno));
137     return;
138   }
139 
140  /*
141   * Parse the file to the end...
142   */
143 
144   cgi_copy(stdout, in, 0, 0, 0);
145 
146  /*
147   * Close the template file and return...
148   */
149 
150   fclose(in);
151 }
152 
153 
154 /*
155  * 'cgiGetTemplateDir()' - Get the templates directory...
156  */
157 
158 char *					/* O - Template directory */
cgiGetTemplateDir(void)159 cgiGetTemplateDir(void)
160 {
161   const char	*datadir;		/* CUPS_DATADIR env var */
162   static char	templates[1024] = "";	/* Template directory */
163 
164 
165   if (!templates[0])
166   {
167    /*
168     * Build the template directory pathname...
169     */
170 
171     if ((datadir = getenv("CUPS_DATADIR")) == NULL)
172       datadir = CUPS_DATADIR;
173 
174     snprintf(templates, sizeof(templates), "%s/templates", datadir);
175   }
176 
177   return (templates);
178 }
179 
180 
181 /*
182  * 'cgiSetServerVersion()' - Set the server name and CUPS version...
183  */
184 
185 void
cgiSetServerVersion(void)186 cgiSetServerVersion(void)
187 {
188   cgiSetVariable("SERVER_NAME", getenv("SERVER_NAME"));
189   cgiSetVariable("REMOTE_USER", getenv("REMOTE_USER"));
190   cgiSetVariable("CUPS_VERSION", CUPS_SVERSION);
191 
192 #ifdef LC_TIME
193   setlocale(LC_TIME, "");
194 #endif /* LC_TIME */
195 }
196 
197 
198 /*
199  * 'cgi_copy()' - Copy the template file, substituting as needed...
200  */
201 
202 static void
cgi_copy(FILE * out,FILE * in,int element,char term,int indent)203 cgi_copy(FILE *out,			/* I - Output file */
204          FILE *in,			/* I - Input file */
205 	 int  element,			/* I - Element number (0 to N) */
206 	 char term,			/* I - Terminating character */
207 	 int  indent)			/* I - Debug info indentation */
208 {
209   int		ch;			/* Character from file */
210   char		op;			/* Operation */
211   char		name[255],		/* Name of variable */
212 		*nameptr,		/* Pointer into name */
213 		innername[255],		/* Inner comparison name */
214 		*innerptr,		/* Pointer into inner name */
215 		*s;			/* String pointer */
216   const char	*value;			/* Value of variable */
217   const char	*innerval;		/* Inner value */
218   const char	*outptr;		/* Output string pointer */
219   char		outval[1024],		/* Formatted output string */
220 		compare[1024];		/* Comparison string */
221   int		result;			/* Result of comparison */
222   int		uriencode;		/* Encode as URI */
223   regex_t	re;			/* Regular expression to match */
224 
225 
226   fprintf(stderr, "DEBUG2: %*sStarting at file position %ld...\n", indent, "",
227           ftell(in));
228 
229  /*
230   * Parse the file to the end...
231   */
232 
233   while ((ch = getc(in)) != EOF)
234     if (ch == term)
235       break;
236     else if (ch == '{')
237     {
238      /*
239       * Get a variable name...
240       */
241 
242       uriencode = 0;
243 
244       for (s = name; (ch = getc(in)) != EOF;)
245         if (strchr("}]<>=!~ \t\n", ch))
246           break;
247 	else if (s == name && ch == '%')
248 	  uriencode = 1;
249         else if (s > name && ch == '?')
250 	  break;
251 	else if (s < (name + sizeof(name) - 1))
252           *s++ = (char)ch;
253 
254       *s = '\0';
255 
256       if (s == name && isspace(ch & 255))
257       {
258         fprintf(stderr, "DEBUG2: %*sLone { at %ld...\n", indent, "", ftell(in));
259 
260         if (out)
261 	{
262           putc('{', out);
263 	  putc(ch, out);
264         }
265 
266 	continue;
267       }
268 
269       if (ch == '}')
270 	fprintf(stderr, "DEBUG2: %*s\"{%s}\" at %ld...\n", indent, "", name,
271         	ftell(in));
272 
273      /*
274       * See if it has a value...
275       */
276 
277       if (name[0] == '?')
278       {
279        /*
280         * Insert value only if it exists...
281 	*/
282 
283 	if ((nameptr = strrchr(name, '-')) != NULL && isdigit(nameptr[1] & 255))
284 	{
285 	  *nameptr++ = '\0';
286 
287 	  if ((value = cgiGetArray(name + 1, atoi(nameptr) - 1)) != NULL)
288 	    outptr = value;
289 	  else
290 	  {
291 	    outval[0] = '\0';
292 	    outptr    = outval;
293 	  }
294 	}
295         else if ((value = cgiGetArray(name + 1, element)) != NULL)
296 	  outptr = value;
297 	else
298 	{
299 	  outval[0] = '\0';
300 	  outptr    = outval;
301 	}
302       }
303       else if (name[0] == '#')
304       {
305        /*
306         * Insert count...
307 	*/
308 
309         if (name[1])
310           sprintf(outval, "%d", cgiGetSize(name + 1));
311 	else
312 	  sprintf(outval, "%d", element + 1);
313 
314         outptr = outval;
315       }
316       else if (name[0] == '[')
317       {
318        /*
319         * Loop for # of elements...
320 	*/
321 
322 	int  i;		/* Looping var */
323         long pos;	/* File position */
324 	int  count;	/* Number of elements */
325 
326 
327         if (isdigit(name[1] & 255))
328 	  count = atoi(name + 1);
329 	else
330           count = cgiGetSize(name + 1);
331 
332 	pos = ftell(in);
333 
334         fprintf(stderr, "DEBUG2: %*sLooping on \"%s\" at %ld, count=%d...\n",
335 	        indent, "", name + 1, pos, count);
336 
337         if (count > 0)
338 	{
339           for (i = 0; i < count; i ++)
340 	  {
341 	    if (i)
342 	      fseek(in, pos, SEEK_SET);
343 
344 	    cgi_copy(out, in, i, '}', indent + 2);
345 	  }
346         }
347 	else
348 	  cgi_copy(NULL, in, 0, '}', indent + 2);
349 
350         fprintf(stderr, "DEBUG2: %*sFinished looping on \"%s\"...\n", indent,
351 	        "", name + 1);
352 
353         continue;
354       }
355       else if (name[0] == '$')
356       {
357        /*
358         * Insert cookie value or nothing if not defined.
359 	*/
360 
361         if ((value = cgiGetCookie(name + 1)) != NULL)
362 	  outptr = value;
363 	else
364 	{
365 	  outval[0] = '\0';
366 	  outptr    = outval;
367 	}
368       }
369       else
370       {
371        /*
372         * Insert variable or variable name (if element is NULL)...
373 	*/
374 
375 	if ((nameptr = strrchr(name, '-')) != NULL && isdigit(nameptr[1] & 255))
376 	{
377 	  *nameptr++ = '\0';
378 	  if ((value = cgiGetArray(name, atoi(nameptr) - 1)) == NULL)
379           {
380 	    snprintf(outval, sizeof(outval), "{%s}", name);
381 	    outptr = outval;
382 	  }
383 	  else
384 	    outptr = value;
385 	}
386 	else if ((value = cgiGetArray(name, element)) == NULL)
387         {
388 	  snprintf(outval, sizeof(outval), "{%s}", name);
389 	  outptr = outval;
390 	}
391 	else
392 	  outptr = value;
393       }
394 
395      /*
396       * See if the terminating character requires another test...
397       */
398 
399       if (ch == '}')
400       {
401        /*
402         * End of substitution...
403         */
404 
405 	if (out)
406 	{
407 	  if (uriencode)
408 	    cgi_puturi(outptr, out);
409 	  else if (!_cups_strcasecmp(name, "?cupsdconf_default"))
410 	    fputs(outptr, stdout);
411 	  else
412 	    cgi_puts(outptr, out);
413         }
414 
415         continue;
416       }
417 
418      /*
419       * OK, process one of the following checks:
420       *
421       *   {name?exist:not-exist}     Exists?
422       *   {name=value?true:false}    Equal
423       *   {name<value?true:false}    Less than
424       *   {name>value?true:false}    Greater than
425       *   {name!value?true:false}    Not equal
426       *   {name~refex?true:false}    Regex match
427       */
428 
429       op = (char)ch;
430 
431       if (ch == '?')
432       {
433        /*
434         * Test for existance...
435 	*/
436 
437         if (name[0] == '?')
438 	  result = cgiGetArray(name + 1, element) != NULL;
439 	else if (name[0] == '#')
440 	  result = cgiGetVariable(name + 1) != NULL;
441         else
442           result = cgiGetArray(name, element) != NULL;
443 
444 	result     = result && outptr[0];
445 	compare[0] = '\0';
446       }
447       else
448       {
449        /*
450         * Compare to a string...
451 	*/
452 
453 	for (s = compare; (ch = getc(in)) != EOF;)
454           if (ch == '?')
455             break;
456 	  else if (s >= (compare + sizeof(compare) - 1))
457 	    continue;
458 	  else if (ch == '#')
459 	  {
460 	    sprintf(s, "%d", element + 1);
461 	    s += strlen(s);
462 	  }
463 	  else if (ch == '{')
464 	  {
465 	   /*
466 	    * Grab the value of a variable...
467 	    */
468 
469 	    innerptr = innername;
470 	    while ((ch = getc(in)) != EOF && ch != '}')
471 	      if (innerptr < (innername + sizeof(innername) - 1))
472 	        *innerptr++ = (char)ch;
473 	    *innerptr = '\0';
474 
475             if (innername[0] == '#')
476 	      sprintf(s, "%d", cgiGetSize(innername + 1));
477 	    else if ((innerptr = strrchr(innername, '-')) != NULL &&
478 	             isdigit(innerptr[1] & 255))
479             {
480 	      *innerptr++ = '\0';
481 	      if ((innerval = cgiGetArray(innername, atoi(innerptr) - 1)) == NULL)
482 	        *s = '\0';
483 	      else
484 	        strlcpy(s, innerval, sizeof(compare) - (size_t)(s - compare));
485 	    }
486 	    else if (innername[0] == '?')
487 	    {
488 	      if ((innerval = cgiGetArray(innername + 1, element)) == NULL)
489 		*s = '\0';
490 	      else
491 	        strlcpy(s, innerval, sizeof(compare) - (size_t)(s - compare));
492             }
493 	    else if ((innerval = cgiGetArray(innername, element)) == NULL)
494 	      snprintf(s, sizeof(compare) - (size_t)(s - compare), "{%s}", innername);
495 	    else
496 	      strlcpy(s, innerval, sizeof(compare) - (size_t)(s - compare));
497 
498             s += strlen(s);
499 	  }
500           else if (ch == '\\')
501 	    *s++ = (char)getc(in);
502 	  else
503             *s++ = (char)ch;
504 
505         *s = '\0';
506 
507         if (ch != '?')
508 	{
509 	  fprintf(stderr,
510 	          "DEBUG2: %*sBad terminator '%c' at file position %ld...\n",
511 	          indent, "", ch, ftell(in));
512 	  return;
513 	}
514 
515        /*
516         * Do the comparison...
517 	*/
518 
519         switch (op)
520 	{
521 	  case '<' :
522 	      result = _cups_strcasecmp(outptr, compare) < 0;
523 	      break;
524 	  case '>' :
525 	      result = _cups_strcasecmp(outptr, compare) > 0;
526 	      break;
527 	  case '=' :
528 	      result = _cups_strcasecmp(outptr, compare) == 0;
529 	      break;
530 	  case '!' :
531 	      result = _cups_strcasecmp(outptr, compare) != 0;
532 	      break;
533 	  case '~' :
534 	      fprintf(stderr, "DEBUG: Regular expression \"%s\"\n", compare);
535 
536 	      if (regcomp(&re, compare, REG_EXTENDED | REG_ICASE))
537 	      {
538 	        fprintf(stderr,
539 		        "ERROR: Unable to compile regular expression \"%s\"!\n",
540 			compare);
541 		result = 0;
542 	      }
543 	      else
544 	      {
545 	        regmatch_t matches[10];
546 
547 		result = 0;
548 
549 	        if (!regexec(&re, outptr, 10, matches, 0))
550 		{
551 		  int i;
552 		  for (i = 0; i < 10; i ++)
553 		  {
554 		    fprintf(stderr, "DEBUG: matches[%d].rm_so=%d\n", i,
555 		            (int)matches[i].rm_so);
556 		    if (matches[i].rm_so < 0)
557 		      break;
558 
559 		    result ++;
560 		  }
561 		}
562 
563 		regfree(&re);
564 	      }
565 	      break;
566 	  default :
567 	      result = 1;
568 	      break;
569 	}
570       }
571 
572       fprintf(stderr,
573               "DEBUG2: %*sStarting \"{%s%c%s\" at %ld, result=%d...\n",
574 	      indent, "", name, op, compare, ftell(in), result);
575 
576       if (result)
577       {
578        /*
579 	* Comparison true; output first part and ignore second...
580 	*/
581 
582         fprintf(stderr, "DEBUG2: %*sOutput first part...\n", indent, "");
583 	cgi_copy(out, in, element, ':', indent + 2);
584 
585         fprintf(stderr, "DEBUG2: %*sSkip second part...\n", indent, "");
586 	cgi_copy(NULL, in, element, '}', indent + 2);
587       }
588       else
589       {
590        /*
591 	* Comparison false; ignore first part and output second...
592 	*/
593 
594         fprintf(stderr, "DEBUG2: %*sSkip first part...\n", indent, "");
595 	cgi_copy(NULL, in, element, ':', indent + 2);
596 
597         fprintf(stderr, "DEBUG2: %*sOutput second part...\n", indent, "");
598 	cgi_copy(out, in, element, '}', indent + 2);
599       }
600 
601       fprintf(stderr, "DEBUG2: %*sFinished \"{%s%c%s\", out=%p...\n", indent, "",
602               name, op, compare, out);
603     }
604     else if (ch == '\\')	/* Quoted char */
605     {
606       if (out)
607         putc(getc(in), out);
608       else
609         getc(in);
610     }
611     else if (out)
612       putc(ch, out);
613 
614   if (ch == EOF)
615     fprintf(stderr, "DEBUG2: %*sReturning at file position %ld on EOF...\n",
616 	    indent, "", ftell(in));
617   else
618     fprintf(stderr,
619             "DEBUG2: %*sReturning at file position %ld on character '%c'...\n",
620 	    indent, "", ftell(in), ch);
621 
622   if (ch == EOF && term)
623     fprintf(stderr, "ERROR: %*sSaw EOF, expected '%c'!\n", indent, "", term);
624 
625  /*
626   * Flush any pending output...
627   */
628 
629   if (out)
630     fflush(out);
631 }
632 
633 
634 /*
635  * 'cgi_puts()' - Put a string to the output file, quoting as needed...
636  */
637 
638 static void
cgi_puts(const char * s,FILE * out)639 cgi_puts(const char *s,			/* I - String to output */
640          FILE       *out)		/* I - Output file */
641 {
642   while (*s)
643   {
644     if (*s == '<')
645       fputs("&lt;", out);
646     else if (*s == '>')
647       fputs("&gt;", out);
648     else if (*s == '\"')
649       fputs("&quot;", out);
650     else if (*s == '\'')
651       fputs("&#39;", out);
652     else if (*s == '&')
653       fputs("&amp;", out);
654     else
655       putc(*s, out);
656 
657     s ++;
658   }
659 }
660 
661 
662 /*
663  * 'cgi_puturi()' - Put a URI string to the output file, quoting as needed...
664  */
665 
666 static void
cgi_puturi(const char * s,FILE * out)667 cgi_puturi(const char *s,		/* I - String to output */
668            FILE       *out)		/* I - Output file */
669 {
670   while (*s)
671   {
672     if (strchr("%@&+ <>#=", *s) || *s < ' ' || *s & 128)
673       fprintf(out, "%%%02X", *s & 255);
674     else
675       putc(*s, out);
676 
677     s ++;
678   }
679 }
680