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