Code sync
[external/cups.git] / scheduler / cupsfilter.c
1 /*
2  * "$Id: cupsfilter.c 9862 2011-08-03 02:44:09Z mike $"
3  *
4  *   Filtering program for CUPS.
5  *
6  *   Copyright 2007-2011 by Apple Inc.
7  *   Copyright 1997-2006 by Easy Software Products, all rights reserved.
8  *
9  *   These coded instructions, statements, and computer programs are the
10  *   property of Apple Inc. and are protected by Federal copyright
11  *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12  *   which should have been included with this file.  If this file is
13  *   file is missing or damaged, see the license at "http://www.cups.org/".
14  *
15  * Contents:
16  *
17  *   main()                - Main entry for the test program.
18  *   add_printer_filter()  - Add a single filters from a PPD file.
19  *   add_printer_filters() - Add filters from a PPD file.
20  *   check_cb()            - Callback function for _cupsFileCheck.
21  *   compare_pids()        - Compare two filter PIDs...
22  *   escape_options()      - Convert an options array to a string.
23  *   exec_filter()         - Execute a single filter.
24  *   exec_filters()        - Execute filters for the given file and options.
25  *   get_job_file()        - Get the specified job file.
26  *   open_pipe()           - Create a pipe which is closed on exec.
27  *   read_cupsd_conf()     - Read the cupsd.conf file to get the filter
28  *                           settings.
29  *   set_string()          - Copy and set a string.
30  *   sighandler()          - Signal catcher for when we print from stdin...
31  *   usage()               - Show program usage...
32  */
33
34 /*
35  * Include necessary headers...
36  */
37
38 #include <cups/cups-private.h>
39 #include <cups/file-private.h>
40 #include <cups/ppd-private.h>
41 #include "mime.h"
42 #include <limits.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45 #include <signal.h>
46 #include <sys/wait.h>
47 #if defined(__APPLE__)
48 #  include <libgen.h>
49 #endif /* __APPLE__ */
50
51
52 /*
53  * Local globals...
54  */
55
56 static char             *DataDir = NULL;/* CUPS_DATADIR environment variable */
57 static char             *FontPath = NULL;
58                                         /* CUPS_FONTPATH environment variable */
59 static mime_filter_t    GZIPFilter =    /* gziptoany filter */
60 {
61   NULL,                                 /* Source type */
62   NULL,                                 /* Destination type */
63   0,                                    /* Cost */
64   "gziptoany"                           /* Filter program to run */
65 };
66 static char             *Path = NULL;   /* PATH environment variable */
67 static char             *ServerBin = NULL;
68                                         /* CUPS_SERVERBIN environment variable */
69 static char             *ServerRoot = NULL;
70                                         /* CUPS_SERVERROOT environment variable */
71 static char             *RIPCache = NULL;
72                                         /* RIP_MAX_CACHE environment variable */
73 static char             TempFile[1024] = "";
74                                         /* Temporary file */
75
76
77 /*
78  * Local functions...
79  */
80
81 static void             add_printer_filter(const char *command, mime_t *mime,
82                                            mime_type_t *printer_type,
83                                            const char  *filter);
84 static mime_type_t      *add_printer_filters(const char *command,
85                                              mime_t *mime, const char *printer,
86                                              const char *ppdfile,
87                                              mime_type_t **prefilter_type);
88 static void             check_cb(void *context, _cups_fc_result_t result,
89                                  const char *message);
90 static int              compare_pids(mime_filter_t *a, mime_filter_t *b);
91 static char             *escape_options(int num_options, cups_option_t *options);
92 static int              exec_filter(const char *filter, char **argv,
93                                     char **envp, int infd, int outfd);
94 static int              exec_filters(mime_type_t *srctype,
95                                      cups_array_t *filters, const char *infile,
96                                      const char *outfile, const char *ppdfile,
97                                      const char *printer, const char *user,
98                                      const char *title, int num_options,
99                                      cups_option_t *options);
100 static void             get_job_file(const char *job);
101 static int              open_pipe(int *fds);
102 static int              read_cupsd_conf(const char *filename);
103 static void             set_string(char **s, const char *val);
104 static void             sighandler(int sig);
105 static void             usage(const char *command, const char *opt);
106
107
108 /*
109  * 'main()' - Main entry for the test program.
110  */
111
112 int                                     /* O - Exit status */
113 main(int  argc,                         /* I - Number of command-line args */
114      char *argv[])                      /* I - Command-line arguments */
115 {
116   int           i;                      /* Looping vars */
117   const char    *command,               /* Command name */
118                 *opt,                   /* Current option */
119                 *printer;               /* Printer name */
120   mime_type_t   *printer_type,          /* Printer MIME type */
121                 *prefilter_type;        /* Printer prefilter MIME type */
122   char          *srctype,               /* Source type */
123                 *dsttype,               /* Destination type */
124                 super[MIME_MAX_SUPER],  /* Super-type name */
125                 type[MIME_MAX_TYPE];    /* Type name */
126   int           compression;            /* Compression of file */
127   int           cost;                   /* Cost of filters */
128   mime_t        *mime;                  /* MIME database */
129   char          mimedir[1024];          /* MIME directory */
130   char          *infile,                /* File to filter */
131                 *outfile;               /* File to create */
132   char          cupsdconf[1024];        /* cupsd.conf file */
133   const char    *server_root;           /* CUPS_SERVERROOT environment variable */
134   mime_type_t   *src,                   /* Source type */
135                 *dst;                   /* Destination type */
136   cups_array_t  *filters;               /* Filters for the file */
137   int           num_options;            /* Number of options */
138   cups_option_t *options;               /* Options */
139   const char    *ppdfile;               /* PPD file */
140   const char    *title,                 /* Title string */
141                 *user;                  /* Username */
142   int           all_filters,            /* Use all filters */
143                 removeppd,              /* Remove PPD file */
144                 removeinfile;           /* Remove input file */
145   int           status;                 /* Execution status */
146
147
148  /*
149   * Setup defaults...
150   */
151
152   if ((command = strrchr(argv[0], '/')) != NULL)
153     command ++;
154   else
155     command = argv[0];
156
157   printer      = !strcmp(command, "convert") ? "tofile" : "cupsfilter";
158   mime         = NULL;
159   srctype      = NULL;
160   compression  = 0;
161   dsttype      = "application/pdf";
162   infile       = NULL;
163   outfile      = NULL;
164   num_options  = 0;
165   options      = NULL;
166   ppdfile      = NULL;
167   title        = NULL;
168   user         = cupsUser();
169   all_filters  = 0;
170   removeppd    = 0;
171   removeinfile = 0;
172
173   if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
174     server_root = CUPS_SERVERROOT;
175
176   snprintf(cupsdconf, sizeof(cupsdconf), "%s/cupsd.conf", server_root);
177
178  /*
179   * Process command-line arguments...
180   */
181
182   _cupsSetLocale(argv);
183
184   for (i = 1; i < argc; i ++)
185     if (argv[i][0] == '-')
186     {
187       for (opt = argv[i] + 1; *opt; opt ++)
188         switch (*opt)
189         {
190           case '-' : /* Next argument is a filename... */
191               i ++;
192               if (i < argc && !infile)
193                 infile = argv[i];
194               else
195                 usage(command, opt);
196               break;
197
198           case 'a' : /* Specify option... */
199               i ++;
200               if (i < argc)
201                 num_options = cupsParseOptions(argv[i], num_options, &options);
202               else
203                 usage(command, opt);
204               break;
205
206           case 'c' : /* Specify cupsd.conf file location... */
207               i ++;
208               if (i < argc)
209               {
210                 if (!strcmp(command, "convert"))
211                   num_options = cupsAddOption("copies", argv[i], num_options,
212                                               &options);
213                 else
214                   strlcpy(cupsdconf, argv[i], sizeof(cupsdconf));
215               }
216               else
217                 usage(command, opt);
218               break;
219
220           case 'd' : /* Specify the real printer name */
221               i ++;
222               if (i < argc)
223                 printer = argv[i];
224               else
225                 usage(command, opt);
226               break;
227
228           case 'D' : /* Delete input file after conversion */
229               removeinfile = 1;
230               break;
231
232           case 'e' : /* Use every filter from the PPD file */
233               all_filters = 1;
234               break;
235
236           case 'f' : /* Specify input file... */
237               i ++;
238               if (i < argc && !infile)
239                 infile = argv[i];
240               else
241                 usage(command, opt);
242               break;
243
244           case 'i' : /* Specify source MIME type... */
245               i ++;
246               if (i < argc)
247               {
248                 if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
249                   usage(command, opt);
250
251                 srctype = argv[i];
252               }
253               else
254                 usage(command, opt);
255               break;
256
257           case 'j' : /* Get job file or specify destination MIME type... */
258               if (strcmp(command, "convert"))
259               {
260                 i ++;
261                 if (i < argc)
262                 {
263                   get_job_file(argv[i]);
264                   infile = TempFile;
265                 }
266                 else
267                   usage(command, opt);
268
269                 break;
270               }
271
272           case 'm' : /* Specify destination MIME type... */
273               i ++;
274               if (i < argc)
275               {
276                 if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
277                   usage(command, opt);
278
279                 dsttype = argv[i];
280               }
281               else
282                 usage(command, opt);
283               break;
284
285           case 'n' : /* Specify number of copies... */
286               i ++;
287               if (i < argc)
288                 num_options = cupsAddOption("copies", argv[i], num_options,
289                                             &options);
290               else
291                 usage(command, opt);
292               break;
293
294           case 'o' : /* Specify option(s) or output filename */
295               i ++;
296               if (i < argc)
297               {
298                 if (!strcmp(command, "convert"))
299                 {
300                   if (outfile)
301                     usage(command, NULL);
302                   else
303                     outfile = argv[i];
304                 }
305                 else
306                   num_options = cupsParseOptions(argv[i], num_options,
307                                                  &options);
308               }
309               else
310                 usage(command, opt);
311               break;
312
313           case 'p' : /* Specify PPD file... */
314           case 'P' : /* Specify PPD file... */
315               i ++;
316               if (i < argc)
317                 ppdfile = argv[i];
318               else
319                 usage(command, opt);
320               break;
321
322           case 't' : /* Specify title... */
323           case 'J' : /* Specify title... */
324               i ++;
325               if (i < argc)
326                 title = argv[i];
327               else
328                 usage(command, opt);
329               break;
330
331           case 'u' : /* Delete PPD file after conversion */
332               removeppd = 1;
333               break;
334
335           case 'U' : /* Specify username... */
336               i ++;
337               if (i < argc)
338                 user = argv[i];
339               else
340                 usage(command, opt);
341               break;
342
343           default : /* Something we don't understand... */
344               usage(command, opt);
345               break;
346         }
347     }
348     else if (!infile)
349     {
350       if (strcmp(command, "convert"))
351         infile = argv[i];
352       else
353       {
354         _cupsLangPuts(stderr,
355                       _("convert: Use the -f option to specify a file to "
356                         "convert."));
357         usage(command, NULL);
358       }
359     }
360     else
361     {
362       _cupsLangPuts(stderr,
363                     _("cupsfilter: Only one filename can be specified."));
364       usage(command, NULL);
365     }
366
367   if (!infile && !srctype)
368     usage(command, NULL);
369
370   if (!title)
371   {
372     if (!infile)
373       title = "(stdin)";
374     else if ((title = strrchr(infile, '/')) != NULL)
375       title ++;
376     else
377       title = infile;
378   }
379
380  /*
381   * Load the cupsd.conf file and create the MIME database...
382   */
383
384   if (read_cupsd_conf(cupsdconf))
385     return (1);
386
387   snprintf(mimedir, sizeof(mimedir), "%s/mime", DataDir);
388
389   mime = mimeLoadTypes(NULL, mimedir);
390   mime = mimeLoadTypes(mime, ServerRoot);
391   mime = mimeLoadFilters(mime, mimedir, Path);
392   mime = mimeLoadFilters(mime, ServerRoot, Path);
393
394   if (!mime)
395   {
396     _cupsLangPrintf(stderr,
397                     _("%s: Unable to read MIME database from \"%s\" or "
398                       "\"%s\"."),
399                     command, mimedir, ServerRoot);
400     return (1);
401   }
402
403   prefilter_type = NULL;
404
405   if (all_filters)
406     printer_type = add_printer_filters(command, mime, printer, ppdfile,
407                                        &prefilter_type);
408   else
409     printer_type   = mimeType(mime, "application", "vnd.cups-postscript");
410
411  /*
412   * Get the source and destination types...
413   */
414
415   if (srctype)
416   {
417     sscanf(srctype, "%15[^/]/%255s", super, type);
418     if ((src = mimeType(mime, super, type)) == NULL)
419     {
420       _cupsLangPrintf(stderr,
421                       _("%s: Unknown source MIME type %s/%s."),
422                       command, super, type);
423       return (1);
424     }
425   }
426   else if ((src = mimeFileType(mime, infile, infile, &compression)) == NULL)
427   {
428     _cupsLangPrintf(stderr,
429                     _("%s: Unable to determine MIME type of \"%s\"."),
430                     command, infile);
431     return (1);
432   }
433
434   sscanf(dsttype, "%15[^/]/%255s", super, type);
435   if (!_cups_strcasecmp(super, "printer"))
436     dst = printer_type;
437   else if ((dst = mimeType(mime, super, type)) == NULL)
438   {
439     _cupsLangPrintf(stderr,
440                     _("%s: Unknown destination MIME type %s/%s."),
441                     command, super, type);
442     return (1);
443   }
444
445  /*
446   * Figure out how to filter the file...
447   */
448
449   if (src == dst)
450   {
451    /*
452     * Special case - no filtering needed...
453     */
454
455     filters = cupsArrayNew(NULL, NULL);
456     cupsArrayAdd(filters, &GZIPFilter);
457     GZIPFilter.src = src;
458     GZIPFilter.dst = dst;
459   }
460   else if ((filters = mimeFilter(mime, src, dst, &cost)) == NULL)
461   {
462     _cupsLangPrintf(stderr,
463                     _("%s: No filter to convert from %s/%s to %s/%s."),
464                     command, src->super, src->type, dst->super, dst->type);
465     return (1);
466   }
467   else if (compression)
468     cupsArrayInsert(filters, &GZIPFilter);
469
470   if (prefilter_type)
471   {
472    /*
473     * Add pre-filters...
474     */
475
476     mime_filter_t       *filter,        /* Current filter */
477                         *prefilter;     /* Current pre-filter */
478     cups_array_t        *prefilters = cupsArrayNew(NULL, NULL);
479                                         /* New filters array */
480
481
482     for (filter = (mime_filter_t *)cupsArrayFirst(filters);
483          filter;
484          filter = (mime_filter_t *)cupsArrayNext(filters))
485     {
486       if ((prefilter = mimeFilterLookup(mime, filter->src,
487                                         prefilter_type)) != NULL)
488         cupsArrayAdd(prefilters, prefilter);
489
490       cupsArrayAdd(prefilters, filter);
491     }
492
493     cupsArrayDelete(filters);
494     filters = prefilters;
495   }
496
497  /*
498   * Do it!
499   */
500
501   status = exec_filters(src, filters, infile, outfile, ppdfile, printer, user,
502                         title, num_options, options);
503
504  /*
505   * Remove files as needed, then exit...
506   */
507
508   if (TempFile[0])
509     unlink(TempFile);
510
511   if (removeppd && ppdfile)
512     unlink(ppdfile);
513
514   if (removeinfile && infile)
515     unlink(infile);
516
517   return (status);
518 }
519
520
521 /*
522  * 'add_printer_filter()' - Add a single filters from a PPD file.
523  */
524
525 static void
526 add_printer_filter(
527     const char  *command,               /* I - Command name */
528     mime_t      *mime,                  /* I - MIME database */
529     mime_type_t *filtertype,            /* I - Printer or prefilter MIME type */
530     const char  *filter)                /* I - Filter to add */
531 {
532   char          super[MIME_MAX_SUPER],  /* Super-type for filter */
533                 type[MIME_MAX_TYPE],    /* Type for filter */
534                 dsuper[MIME_MAX_SUPER], /* Destination super-type for filter */
535                 dtype[MIME_MAX_TYPE],   /* Destination type for filter */
536                 dest[MIME_MAX_SUPER + MIME_MAX_TYPE + 2],
537                                         /* Destination super/type */
538                 program[1024];          /* Program/filter name */
539   int           cost;                   /* Cost of filter */
540   size_t        maxsize = 0;            /* Maximum supported file size */
541   mime_type_t   *temptype,              /* MIME type looping var */
542                 *desttype;              /* Destination MIME type */
543   mime_filter_t *filterptr;             /* MIME filter */
544
545
546  /*
547   * Parse the filter string; it should be in one of the following formats:
548   *
549   *     source/type cost program
550   *     source/type cost maxsize(nnnn) program
551   *     source/type dest/type cost program
552   *     source/type dest/type cost maxsize(nnnn) program
553   */
554
555   if (sscanf(filter, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
556              super, type, dsuper, dtype, &cost, program) == 6)
557   {
558     snprintf(dest, sizeof(dest), "%s/%s/%s", filtertype->type, dsuper, dtype);
559
560     if ((desttype = mimeType(mime, "printer", dest)) == NULL)
561       desttype = mimeAddType(mime, "printer", dest);
562   }
563   else
564   {
565     if (sscanf(filter, "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type, &cost,
566                program) == 4)
567     {
568       desttype = filtertype;
569     }
570     else
571     {
572       _cupsLangPrintf(stderr, _("%s: Invalid filter string \"%s\"."), command,
573                       filter);
574       return;
575     }
576   }
577
578   if (!strncmp(program, "maxsize(", 8))
579   {
580     char        *ptr;                   /* Pointer into maxsize(nnnn) program */
581
582     maxsize = strtoll(program + 8, &ptr, 10);
583
584     if (*ptr != ')')
585     {
586       printf("testmime: Invalid filter string \"%s\".\n", filter);
587       return;
588     }
589
590     ptr ++;
591     while (_cups_isspace(*ptr))
592       ptr ++;
593
594     _cups_strcpy(program, ptr);
595   }
596
597  /*
598   * See if the filter program exists; if not, stop the printer and flag
599   * the error!
600   */
601
602   if (strcmp(program, "-"))
603   {
604     char filename[1024];                /* Full path to program */
605
606     if (program[0] == '/')
607       strlcpy(filename, program, sizeof(filename));
608     else
609       snprintf(filename, sizeof(filename), "%s/filter/%s", ServerBin, program);
610
611     if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(), check_cb,
612                        (void *)command))
613       return;
614   }
615
616  /*
617   * Add the filter to the MIME database, supporting wildcards as needed...
618   */
619
620   for (temptype = mimeFirstType(mime);
621        temptype;
622        temptype = mimeNextType(mime))
623     if (((super[0] == '*' && _cups_strcasecmp(temptype->super, "printer")) ||
624          !_cups_strcasecmp(temptype->super, super)) &&
625         (type[0] == '*' || !_cups_strcasecmp(temptype->type, type)))
626     {
627       if (desttype != filtertype)
628       {
629         filterptr = mimeAddFilter(mime, temptype, desttype, cost, program);
630
631         if (!mimeFilterLookup(mime, desttype, filtertype))
632           mimeAddFilter(mime, desttype, filtertype, 0, "-");
633       }
634       else
635         filterptr = mimeAddFilter(mime, temptype, filtertype, cost, program);
636
637       if (filterptr)
638         filterptr->maxsize = maxsize;
639     }
640 }
641
642
643 /*
644  * 'add_printer_filters()' - Add filters from a PPD file.
645  */
646
647 static mime_type_t *                    /* O - Printer type or NULL on error */
648 add_printer_filters(
649     const char  *command,               /* I - Command name */
650     mime_t      *mime,                  /* I - MIME database */
651     const char  *printer,               /* I - Printer name */
652     const char  *ppdfile,               /* I - PPD file */
653     mime_type_t **prefilter_type)       /* O - Prefilter type */
654 {
655   ppd_file_t    *ppd;                   /* PPD file data */
656   _ppd_cache_t  *pc;                    /* Cache data for PPD */
657   const char    *value;                 /* Filter definition value */
658   mime_type_t   *printer_type;          /* Printer filter type */
659
660
661   if ((ppd = ppdOpenFile(ppdfile)) == NULL)
662   {
663     ppd_status_t        status;         /* PPD load status */
664     int                 linenum;        /* Line number */
665
666     status = ppdLastError(&linenum);
667     _cupsLangPrintf(stderr, _("%s: Unable to open PPD file: %s on line %d."),
668                     command, ppdErrorString(status), linenum);
669     return (NULL);
670   }
671
672   pc = _ppdCacheCreateWithPPD(ppd);
673   if (!pc)
674     return (NULL);
675
676   printer_type    = mimeAddType(mime, "printer", printer);
677   *prefilter_type = NULL;
678
679   if (pc->filters)
680   {
681     for (value = (const char *)cupsArrayFirst(pc->filters);
682          value;
683          value = (const char *)cupsArrayNext(pc->filters))
684       add_printer_filter(command, mime, printer_type, value);
685   }
686   else
687   {
688     add_printer_filter(command, mime, printer_type,
689                        "application/vnd.cups-raw 0 -");
690     add_printer_filter(command, mime, printer_type,
691                        "application/vnd.cups-postscript 0 -");
692   }
693
694   if (pc->prefilters)
695   {
696     *prefilter_type = mimeAddType(mime, "prefilter", printer);
697
698     for (value = (const char *)cupsArrayFirst(pc->prefilters);
699          value;
700          value = (const char *)cupsArrayNext(pc->prefilters))
701       add_printer_filter(command, mime, *prefilter_type, value);
702   }
703
704   return (printer_type);
705 }
706
707
708 /*
709  * 'check_cb()' - Callback function for _cupsFileCheck.
710  */
711
712 static void
713 check_cb(void              *context,    /* I - Context (command name) */
714          _cups_fc_result_t result,      /* I - Result of check */
715          const char        *message)    /* I - Localized message */
716 {
717   (void)result;
718
719   _cupsLangPrintf(stderr, _("%s: %s"), (char *)context, message);
720 }
721
722
723 /*
724  * 'compare_pids()' - Compare two filter PIDs...
725  */
726
727 static int                              /* O - Result of comparison */
728 compare_pids(mime_filter_t *a,          /* I - First filter */
729              mime_filter_t *b)          /* I - Second filter */
730 {
731  /*
732   * Because we're particularly lazy, we store the process ID in the "cost"
733   * variable...
734   */
735
736   return (a->cost - b->cost);
737 }
738
739
740 /*
741  * 'escape_options()' - Convert an options array to a string.
742  */
743
744 static char *                           /* O - Option string */
745 escape_options(
746     int           num_options,          /* I - Number of options */
747     cups_option_t *options)             /* I - Options */
748 {
749   int           i;                      /* Looping var */
750   cups_option_t *option;                /* Current option */
751   int           bytes;                  /* Number of bytes needed */
752   char          *s,                     /* Option string */
753                 *sptr,                  /* Pointer into string */
754                 *vptr;                  /* Pointer into value */
755
756
757  /*
758   * Figure out the worst-case number of bytes we need for the option string.
759   */
760
761   for (i = num_options, option = options, bytes = 1; i > 0; i --, option ++)
762     bytes += 2 * (strlen(option->name) + strlen(option->value)) + 2;
763
764   if ((s = malloc(bytes)) == NULL)
765     return (NULL);
766
767  /*
768   * Copy the options to the string...
769   */
770
771   for (i = num_options, option = options, sptr = s; i > 0; i --, option ++)
772   {
773     if (!strcmp(option->name, "copies"))
774       continue;
775
776     if (sptr > s)
777       *sptr++ = ' ';
778
779     strcpy(sptr, option->name);
780     sptr += strlen(sptr);
781     *sptr++ = '=';
782
783     for (vptr = option->value; *vptr;)
784     {
785       if (strchr("\\ \t\n", *vptr))
786         *sptr++ = '\\';
787
788       *sptr++ = *vptr++;
789     }
790   }
791
792   *sptr = '\0';
793
794   return (s);
795 }
796
797
798 /*
799  * 'exec_filter()' - Execute a single filter.
800  */
801
802 static int                              /* O - Process ID or -1 on error */
803 exec_filter(const char *filter,         /* I - Filter to execute */
804             char       **argv,          /* I - Argument list */
805             char       **envp,          /* I - Environment list */
806             int        infd,            /* I - Stdin file descriptor */
807             int        outfd)           /* I - Stdout file descriptor */
808 {
809   int           pid,                    /* Process ID */
810                 fd;                     /* Temporary file descriptor */
811 #if defined(__APPLE__)
812   char          processPath[1024],      /* CFProcessPath environment variable */
813                 linkpath[1024];         /* Link path for symlinks... */
814   int           linkbytes;              /* Bytes for link path */
815
816
817  /*
818   * Add special voodoo magic for MacOS X - this allows MacOS X
819   * programs to access their bundle resources properly...
820   */
821
822   if ((linkbytes = readlink(filter, linkpath, sizeof(linkpath) - 1)) > 0)
823   {
824    /*
825     * Yes, this is a symlink to the actual program, nul-terminate and
826     * use it...
827     */
828
829     linkpath[linkbytes] = '\0';
830
831     if (linkpath[0] == '/')
832       snprintf(processPath, sizeof(processPath), "CFProcessPath=%s",
833                linkpath);
834     else
835       snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s",
836                dirname((char *)filter), linkpath);
837   }
838   else
839     snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", filter);
840
841   envp[0] = processPath;                /* Replace <CFProcessPath> string */
842 #endif  /* __APPLE__ */
843
844   if ((pid = fork()) == 0)
845   {
846    /*
847     * Child process goes here...
848     *
849     * Update stdin/stdout/stderr as needed...
850     */
851
852     if (infd != 0)
853     {
854       if (infd < 0)
855         infd = open("/dev/null", O_RDONLY);
856
857       if (infd > 0)
858       {
859         dup2(infd, 0);
860         close(infd);
861       }
862     }
863
864     if (outfd != 1)
865     {
866       if (outfd < 0)
867         outfd = open("/dev/null", O_WRONLY);
868
869       if (outfd > 1)
870       {
871         dup2(outfd, 1);
872         close(outfd);
873       }
874     }
875
876     if ((fd = open("/dev/null", O_RDWR)) > 3)
877     {
878       dup2(fd, 3);
879       close(fd);
880     }
881     fcntl(3, F_SETFL, O_NDELAY);
882
883     if ((fd = open("/dev/null", O_RDWR)) > 4)
884     {
885       dup2(fd, 4);
886       close(fd);
887     }
888     fcntl(4, F_SETFL, O_NDELAY);
889
890    /*
891     * Execute command...
892     */
893
894     execve(filter, argv, envp);
895
896     perror(filter);
897
898     exit(errno);
899   }
900
901   return (pid);
902 }
903
904
905 /*
906  * 'exec_filters()' - Execute filters for the given file and options.
907  */
908
909 static int                              /* O - 0 on success, 1 on error */
910 exec_filters(mime_type_t   *srctype,    /* I - Source type */
911              cups_array_t  *filters,    /* I - Array of filters to run */
912              const char    *infile,     /* I - File to filter */
913              const char    *outfile,    /* I - File to create */
914              const char    *ppdfile,    /* I - PPD file, if any */
915              const char    *printer,    /* I - Printer name */
916              const char    *user,       /* I - Username */
917              const char    *title,      /* I - Job title */
918              int           num_options, /* I - Number of filter options */
919              cups_option_t *options)    /* I - Filter options */
920 {
921   int           i;                      /* Looping var */
922   const char    *argv[8],               /* Command-line arguments */
923                 *envp[15],              /* Environment variables */
924                 *temp;                  /* Temporary string */
925   char          *optstr,                /* Filter options */
926                 content_type[1024],     /* CONTENT_TYPE */
927                 cups_datadir[1024],     /* CUPS_DATADIR */
928                 cups_fontpath[1024],    /* CUPS_FONTPATH */
929                 cups_serverbin[1024],   /* CUPS_SERVERBIN */
930                 cups_serverroot[1024],  /* CUPS_SERVERROOT */
931                 lang[1024],             /* LANG */
932                 path[1024],             /* PATH */
933                 ppd[1024],              /* PPD */
934                 printer_info[255],      /* PRINTER_INFO env variable */
935                 printer_location[255],  /* PRINTER_LOCATION env variable */
936                 printer_name[255],      /* PRINTER env variable */
937                 rip_max_cache[1024],    /* RIP_MAX_CACHE */
938                 userenv[1024],          /* USER */
939                 program[1024];          /* Program to run */
940   mime_filter_t *filter,                /* Current filter */
941                 *next;                  /* Next filter */
942   int           current,                /* Current filter */
943                 filterfds[2][2],        /* Pipes for filters */
944                 pid,                    /* Process ID of filter */
945                 status,                 /* Exit status */
946                 retval;                 /* Return value */
947   cups_array_t  *pids;                  /* Executed filters array */
948   mime_filter_t key;                    /* Search key for filters */
949   cups_lang_t   *language;              /* Current language */
950   cups_dest_t   *dest;                  /* Destination information */
951
952
953  /*
954   * Setup the filter environment and command-line...
955   */
956
957   optstr = escape_options(num_options, options);
958
959   snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
960            srctype->super, srctype->type);
961   snprintf(cups_datadir, sizeof(cups_datadir), "CUPS_DATADIR=%s", DataDir);
962   snprintf(cups_fontpath, sizeof(cups_fontpath), "CUPS_FONTPATH=%s", FontPath);
963   snprintf(cups_serverbin, sizeof(cups_serverbin), "CUPS_SERVERBIN=%s",
964            ServerBin);
965   snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s",
966            ServerRoot);
967   language = cupsLangDefault();
968   snprintf(lang, sizeof(lang), "LANG=%s.UTF8", language->language);
969   snprintf(path, sizeof(path), "PATH=%s", Path);
970   if (ppdfile)
971     snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile);
972   else if ((temp = getenv("PPD")) != NULL)
973     snprintf(ppd, sizeof(ppd), "PPD=%s", temp);
974   else
975 #ifdef __APPLE__
976   if (!access("/System/Library/Frameworks/ApplicationServices.framework/"
977               "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
978               "Resources/English.lproj/Generic.ppd", 0))
979     strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
980                  "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
981                  "Resources/English.lproj/Generic.ppd", sizeof(ppd));
982   else
983     strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
984                  "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
985                  "Resources/Generic.ppd", sizeof(ppd));
986 #else
987     snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir);
988 #endif /* __APPLE__ */
989   snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
990   snprintf(userenv, sizeof(userenv), "USER=%s", user);
991
992   if (printer &&
993       (dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer, NULL)) != NULL)
994   {
995     if ((temp = cupsGetOption("printer-info", dest->num_options,
996                               dest->options)) != NULL)
997       snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", temp);
998     else
999       snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", printer);
1000
1001     if ((temp = cupsGetOption("printer-location", dest->num_options,
1002                               dest->options)) != NULL)
1003       snprintf(printer_location, sizeof(printer_location),
1004                "PRINTER_LOCATION=%s", temp);
1005     else
1006       strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
1007               sizeof(printer_location));
1008   }
1009   else
1010   {
1011     snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s",
1012              printer ? printer : "Unknown");
1013     strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
1014             sizeof(printer_location));
1015   }
1016
1017   snprintf(printer_name, sizeof(printer_name), "PRINTER=%s",
1018            printer ? printer : "Unknown");
1019
1020   argv[0] = (char *)printer;
1021   argv[1] = "1";
1022   argv[2] = user;
1023   argv[3] = title;
1024   argv[4] = cupsGetOption("copies", num_options, options);
1025   argv[5] = optstr;
1026   argv[6] = infile;
1027   argv[7] = NULL;
1028
1029   if (!argv[4])
1030     argv[4] = "1";
1031
1032   envp[0]  = "<CFProcessPath>";
1033   envp[1]  = content_type;
1034   envp[2]  = cups_datadir;
1035   envp[3]  = cups_fontpath;
1036   envp[4]  = cups_serverbin;
1037   envp[5]  = cups_serverroot;
1038   envp[6]  = lang;
1039   envp[7]  = path;
1040   envp[8]  = ppd;
1041   envp[9]  = printer_info;
1042   envp[10] = printer_location;
1043   envp[11] = printer_name;
1044   envp[12] = rip_max_cache;
1045   envp[13] = userenv;
1046   envp[14] = NULL;
1047
1048   for (i = 0; argv[i]; i ++)
1049     fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
1050
1051   for (i = 0; envp[i]; i ++)
1052     fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]);
1053
1054  /*
1055   * Execute all of the filters...
1056   */
1057
1058   pids            = cupsArrayNew((cups_array_func_t)compare_pids, NULL);
1059   current         = 0;
1060   filterfds[0][0] = -1;
1061   filterfds[0][1] = -1;
1062   filterfds[1][0] = -1;
1063   filterfds[1][1] = -1;
1064
1065   if (!infile)
1066     filterfds[0][0] = 0;
1067
1068   for (filter = (mime_filter_t *)cupsArrayFirst(filters);
1069        filter;
1070        filter = next, current = 1 - current)
1071   {
1072     next = (mime_filter_t *)cupsArrayNext(filters);
1073
1074     if (filter->filter[0] == '/')
1075       strlcpy(program, filter->filter, sizeof(program));
1076     else
1077       snprintf(program, sizeof(program), "%s/filter/%s", ServerBin,
1078                filter->filter);
1079
1080     if (filterfds[!current][1] > 1)
1081     {
1082       close(filterfds[1 - current][0]);
1083       close(filterfds[1 - current][1]);
1084
1085       filterfds[1 - current][0] = -1;
1086       filterfds[1 - current][0] = -1;
1087     }
1088
1089     if (next)
1090       open_pipe(filterfds[1 - current]);
1091     else if (outfile)
1092     {
1093       filterfds[1 - current][1] = open(outfile, O_CREAT | O_TRUNC | O_WRONLY,
1094                                        0666);
1095
1096       if (filterfds[1 - current][1] < 0)
1097         fprintf(stderr, "ERROR: Unable to create \"%s\" - %s\n", outfile,
1098                 strerror(errno));
1099     }
1100     else
1101       filterfds[1 - current][1] = 1;
1102
1103     pid = exec_filter(program, (char **)argv, (char **)envp,
1104                       filterfds[current][0], filterfds[1 - current][1]);
1105
1106     if (pid > 0)
1107     {
1108       fprintf(stderr, "INFO: %s (PID %d) started.\n", filter->filter, pid);
1109
1110       filter->cost = pid;
1111       cupsArrayAdd(pids, filter);
1112     }
1113     else
1114       break;
1115
1116     argv[6] = NULL;
1117   }
1118
1119  /*
1120   * Close remaining pipes...
1121   */
1122
1123   if (filterfds[0][1] > 1)
1124   {
1125     close(filterfds[0][0]);
1126     close(filterfds[0][1]);
1127   }
1128
1129   if (filterfds[1][1] > 1)
1130   {
1131     close(filterfds[1][0]);
1132     close(filterfds[1][1]);
1133   }
1134
1135  /*
1136   * Wait for the children to exit...
1137   */
1138
1139   retval = 0;
1140
1141   while (cupsArrayCount(pids) > 0)
1142   {
1143     if ((pid = wait(&status)) < 0)
1144       continue;
1145
1146     key.cost = pid;
1147     if ((filter = (mime_filter_t *)cupsArrayFind(pids, &key)) != NULL)
1148     {
1149       cupsArrayRemove(pids, filter);
1150
1151       if (status)
1152       {
1153         if (WIFEXITED(status))
1154           fprintf(stderr, "ERROR: %s (PID %d) stopped with status %d\n",
1155                   filter->filter, pid, WEXITSTATUS(status));
1156         else
1157           fprintf(stderr, "ERROR: %s (PID %d) crashed on signal %d\n",
1158                   filter->filter, pid, WTERMSIG(status));
1159
1160         retval = 1;
1161       }
1162       else
1163         fprintf(stderr, "INFO: %s (PID %d) exited with no errors.\n",
1164                 filter->filter, pid);
1165     }
1166   }
1167
1168   cupsArrayDelete(pids);
1169
1170   return (retval);
1171 }
1172
1173
1174 /*
1175  * 'get_job_file()' - Get the specified job file.
1176  */
1177
1178 static void
1179 get_job_file(const char *job)           /* I - Job ID */
1180 {
1181   long          jobid,                  /* Job ID */
1182                 docnum;                 /* Document number */
1183   const char    *jobptr;                /* Pointer into job ID string */
1184   char          uri[1024];              /* job-uri */
1185   http_t        *http;                  /* Connection to server */
1186   ipp_t         *request;               /* Request data */
1187   int           tempfd;                 /* Temporary file */
1188
1189
1190  /*
1191   * Get the job ID and document number, if any...
1192   */
1193
1194   if ((jobptr = strrchr(job, '-')) != NULL)
1195     jobptr ++;
1196   else
1197     jobptr = job;
1198
1199   jobid = strtol(jobptr, (char **)&jobptr, 10);
1200
1201   if (*jobptr == ',')
1202     docnum = strtol(jobptr + 1, NULL, 10);
1203   else
1204     docnum = 1;
1205
1206   if (jobid < 1 || jobid > INT_MAX)
1207   {
1208     _cupsLangPrintf(stderr, _("cupsfilter: Invalid job ID %d."), (int)jobid);
1209     exit(1);
1210   }
1211
1212   if (docnum < 1 || docnum > INT_MAX)
1213   {
1214     _cupsLangPrintf(stderr, _("cupsfilter: Invalid document number %d."),
1215                     (int)docnum);
1216     exit(1);
1217   }
1218
1219  /*
1220   * Ask the server for the document file...
1221   */
1222
1223   if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
1224                                  cupsEncryption())) == NULL)
1225   {
1226     _cupsLangPrintf(stderr, _("%s: Unable to connect to server."),
1227                     "cupsfilter");
1228     exit(1);
1229   }
1230
1231   request = ippNewRequest(CUPS_GET_DOCUMENT);
1232
1233   snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", (int)jobid);
1234
1235   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
1236   ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "document-number",
1237                 (int)docnum);
1238
1239   if ((tempfd = cupsTempFd(TempFile, sizeof(TempFile))) == -1)
1240   {
1241     _cupsLangPrintError("ERROR", _("Unable to create temporary file"));
1242     httpClose(http);
1243     exit(1);
1244   }
1245
1246   signal(SIGTERM, sighandler);
1247
1248   ippDelete(cupsDoIORequest(http, request, "/", -1, tempfd));
1249
1250   close(tempfd);
1251
1252   httpClose(http);
1253
1254   if (cupsLastError() != IPP_OK)
1255   {
1256     _cupsLangPrintf(stderr, _("cupsfilter: Unable to get job file - %s"),
1257                     cupsLastErrorString());
1258     unlink(TempFile);
1259     exit(1);
1260   }
1261 }
1262
1263
1264 /*
1265  * 'open_pipe()' - Create a pipe which is closed on exec.
1266  */
1267
1268 static int                              /* O - 0 on success, -1 on error */
1269 open_pipe(int *fds)                     /* O - Pipe file descriptors (2) */
1270 {
1271  /*
1272   * Create the pipe...
1273   */
1274
1275   if (pipe(fds))
1276   {
1277     fds[0] = -1;
1278     fds[1] = -1;
1279
1280     return (-1);
1281   }
1282
1283  /*
1284   * Set the "close on exec" flag on each end of the pipe...
1285   */
1286
1287   if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
1288   {
1289     close(fds[0]);
1290     close(fds[1]);
1291
1292     fds[0] = -1;
1293     fds[1] = -1;
1294
1295     return (-1);
1296   }
1297
1298   if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
1299   {
1300     close(fds[0]);
1301     close(fds[1]);
1302
1303     fds[0] = -1;
1304     fds[1] = -1;
1305
1306     return (-1);
1307   }
1308
1309  /*
1310   * Return 0 indicating success...
1311   */
1312
1313   return (0);
1314 }
1315
1316
1317 /*
1318  * 'read_cupsd_conf()' - Read the cupsd.conf file to get the filter settings.
1319  */
1320
1321 static int                              /* O - 0 on success, 1 on error */
1322 read_cupsd_conf(const char *filename)   /* I - File to read */
1323 {
1324   cups_file_t   *fp;                    /* cupsd.conf file */
1325   const char    *temp;                  /* Temporary string */
1326   char          line[1024],             /* Line from file */
1327                 *ptr;                   /* Pointer into line */
1328   int           linenum;                /* Current line number */
1329
1330
1331   if ((temp = getenv("CUPS_DATADIR")) != NULL)
1332     set_string(&DataDir, temp);
1333   else
1334     set_string(&DataDir, CUPS_DATADIR);
1335
1336   if ((temp = getenv("CUPS_FONTPATH")) != NULL)
1337     set_string(&FontPath, temp);
1338   else
1339     set_string(&FontPath, CUPS_FONTPATH);
1340
1341   set_string(&RIPCache, "128m");
1342
1343   if ((temp = getenv("CUPS_SERVERBIN")) != NULL)
1344     set_string(&ServerBin, temp);
1345   else
1346     set_string(&ServerBin, CUPS_SERVERBIN);
1347
1348   strlcpy(line, filename, sizeof(line));
1349   if ((ptr = strrchr(line, '/')) != NULL)
1350     *ptr = '\0';
1351   else
1352     getcwd(line, sizeof(line));
1353
1354   set_string(&ServerRoot, line);
1355
1356   if ((fp = cupsFileOpen(filename, "r")) != NULL)
1357   {
1358     linenum = 0;
1359
1360     while (cupsFileGetConf(fp, line, sizeof(line), &ptr, &linenum))
1361     {
1362       if (!_cups_strcasecmp(line, "DataDir"))
1363         set_string(&DataDir, ptr);
1364       else if (!_cups_strcasecmp(line, "FontPath"))
1365         set_string(&FontPath, ptr);
1366       else if (!_cups_strcasecmp(line, "RIPCache"))
1367         set_string(&RIPCache, ptr);
1368       else if (!_cups_strcasecmp(line, "ServerBin"))
1369         set_string(&ServerBin, ptr);
1370       else if (!_cups_strcasecmp(line, "ServerRoot"))
1371         set_string(&ServerRoot, ptr);
1372     }
1373
1374     cupsFileClose(fp);
1375   }
1376
1377   snprintf(line, sizeof(line),
1378            "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin",
1379            ServerBin);
1380   set_string(&Path, line);
1381
1382   return (0);
1383 }
1384
1385
1386 /*
1387  * 'set_string()' - Copy and set a string.
1388  */
1389
1390 static void
1391 set_string(char       **s,              /* O - Copy of string */
1392            const char *val)             /* I - String to copy */
1393 {
1394   if (*s)
1395     free(*s);
1396
1397   *s = strdup(val);
1398 }
1399
1400
1401 /*
1402  * 'sighandler()' - Signal catcher for when we print from stdin...
1403  */
1404
1405 static void
1406 sighandler(int s)                       /* I - Signal number */
1407 {
1408  /*
1409   * Remove the temporary file we're using to print a job file...
1410   */
1411
1412   if (TempFile[0])
1413     unlink(TempFile);
1414
1415  /*
1416   * Exit...
1417   */
1418
1419   exit(s);
1420 }
1421
1422
1423 /*
1424  * 'usage()' - Show program usage...
1425  */
1426
1427 static void
1428 usage(const char *command,              /* I - Command name */
1429       const char *opt)                  /* I - Incorrect option, if any */
1430 {
1431   if (opt)
1432     _cupsLangPrintf(stderr, _("%s: Unknown option \"%c\"."), command, *opt);
1433
1434   if (!strcmp(command, "cupsfilter"))
1435   {
1436     _cupsLangPuts(stdout, _("Usage: cupsfilter [ options ] filename"));
1437     _cupsLangPuts(stdout, _("Options:"));
1438     _cupsLangPuts(stdout, _("  -D                      Remove the input file "
1439                             "when finished."));
1440     _cupsLangPuts(stdout, _("  -P filename.ppd         Set PPD file."));
1441     _cupsLangPuts(stdout, _("  -U username             Set username for job."));
1442     _cupsLangPuts(stdout, _("  -c cupsd.conf           Set cupsd.conf file to "
1443                             "use."));
1444     _cupsLangPuts(stdout, _("  -d printer              Use the named "
1445                             "printer."));
1446     _cupsLangPuts(stdout, _("  -e                      Use every filter from "
1447                             "the PPD file."));
1448     _cupsLangPuts(stdout, _("  -i mime/type            Set input MIME type "
1449                             "(otherwise auto-typed)."));
1450     _cupsLangPuts(stdout, _("  -j job-id[,N]           Filter file N from the "
1451                             "specified job (default is file 1)."));
1452     _cupsLangPuts(stdout, _("  -m mime/type            Set output MIME type "
1453                             "(otherwise application/pdf)."));
1454     _cupsLangPuts(stdout, _("  -n copies               Set number of copies."));
1455     _cupsLangPuts(stdout, _("  -o name=value           Set option(s)."));
1456     _cupsLangPuts(stdout, _("  -p filename.ppd         Set PPD file."));
1457     _cupsLangPuts(stdout, _("  -t title                Set title."));
1458     _cupsLangPuts(stdout, _("  -u                      Remove the PPD file "
1459                             "when finished."));
1460   }
1461   else
1462   {
1463     _cupsLangPuts(stdout, _("Usage: convert [ options ]"));
1464     _cupsLangPuts(stdout, _("Options:"));
1465     _cupsLangPuts(stdout, _("  -D                      Remove the input file "
1466                             "when finished."));
1467     _cupsLangPuts(stdout, _("  -J title                Set title."));
1468     _cupsLangPuts(stdout, _("  -P filename.ppd         Set PPD file."));
1469     _cupsLangPuts(stdout, _("  -U username             Set username for job."));
1470     _cupsLangPuts(stdout, _("  -a 'name=value ...'     Set option(s)."));
1471     _cupsLangPuts(stdout, _("  -c copies               Set number of copies."));
1472     _cupsLangPuts(stdout, _("  -d printer              Use the named "
1473                             "printer."));
1474     _cupsLangPuts(stdout, _("  -e                      Use every filter from "
1475                             "the PPD file."));
1476     _cupsLangPuts(stdout, _("  -f filename             Set file to be "
1477                             "converted (otherwise stdin)."));
1478     _cupsLangPuts(stdout, _("  -i mime/type            Set input MIME type "
1479                             "(otherwise auto-typed)."));
1480     _cupsLangPuts(stdout, _("  -j mime/type            Set output MIME type "
1481                             "(otherwise application/pdf)."));
1482     _cupsLangPuts(stdout, _("  -o filename             Set file to be "
1483                             "generated (otherwise stdout)."));
1484     _cupsLangPuts(stdout, _("  -u                      Remove the PPD file "
1485                             "when finished."));
1486   }
1487
1488   exit(1);
1489 }
1490
1491
1492 /*
1493  * End of "$Id: cupsfilter.c 9862 2011-08-03 02:44:09Z mike $".
1494  */