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