1 /*
2
3 Copyright (c) 2016, Pranjal Bhor
4 Copyright (c) 2008-2016, Till Kamppeter
5 Copyright (c) 2011, Tim Waugh
6 Copyright (c) 2011-2013, Richard Hughes
7
8 Permission is hereby granted, free of charge, to any person obtaining
9 a copy of this software and associated documentation files (the
10 "Software"), to deal in the Software without restriction, including
11 without limitation the rights to use, copy, modify, merge, publish,
12 distribute, sublicense, and/or sell copies of the Software, and to
13 permit persons to whom the Software is furnished to do so, subject to
14 the following conditions:
15
16 The above copyright notice and this permission notice shall be included
17 in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27 MIT Open Source License - http://www.opensource.org/
28
29 */
30
31
32 /* PS/PDF to CUPS Raster filter based on mutool */
33
34 #include <config.h>
35 #include <cups/cups.h>
36 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6)
37 #define HAVE_CUPS_1_7 1
38 #endif
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <stdarg.h>
43 #include <fcntl.h>
44 #include <cups/raster.h>
45 #include <cupsfilters/colormanager.h>
46 #include <cupsfilters/raster.h>
47 #include <sys/types.h>
48 #include <sys/wait.h>
49 #include <signal.h>
50 #include <unistd.h>
51 #include <errno.h>
52
53 #define PDF_MAX_CHECK_COMMENT_LINES 20
54
55 #define CUPS_IPTEMPFILE "/tmp/ip-XXXXXX"
56 #define CUPS_OPTEMPFILE "/tmp/op-XXXXXX"
57
58 #ifdef CUPS_RASTER_SYNCv1
59 typedef cups_page_header2_t mupdf_page_header;
60 #else
61 typedef cups_page_header_t mupdf_page_header;
62 #endif /* CUPS_RASTER_SYNCv1 */
63
64
65 int
parse_doc_type(FILE * fp)66 parse_doc_type(FILE *fp)
67 {
68 char buf[5];
69 char *rc;
70
71 /* get the first few bytes of the file */
72 rewind(fp);
73 rc = fgets(buf,sizeof(buf),fp);
74 /* empty input */
75 if (rc == NULL)
76 return 1;
77
78 /* is PDF */
79 if (strncmp(buf,"%PDF",4) == 0)
80 return 0;
81
82 fprintf(stderr,"DEBUG: input file cannot be identified\n");
83 exit(EXIT_FAILURE);
84 }
85
86 static void
parse_pdf_header_options(FILE * fp,mupdf_page_header * h)87 parse_pdf_header_options(FILE *fp, mupdf_page_header *h)
88 {
89 char buf[4096];
90 int i;
91
92 rewind(fp);
93 /* skip until PDF start header */
94 while (fgets(buf,sizeof(buf),fp) != 0) {
95 if (strncmp(buf,"%PDF",4) == 0) {
96 break;
97 }
98 }
99 for (i = 0;i < PDF_MAX_CHECK_COMMENT_LINES;i++) {
100 if (fgets(buf,sizeof(buf),fp) == 0) break;
101 if (strncmp(buf,"%%PDFTOPDFNumCopies",19) == 0) {
102 char *p;
103
104 p = strchr(buf+19,':');
105 h->NumCopies = atoi(p+1);
106 } else if (strncmp(buf,"%%PDFTOPDFCollate",17) == 0) {
107 char *p;
108
109 p = strchr(buf+17,':');
110 while (*p == ' ' || *p == '\t') p++;
111 if (strncasecmp(p,"true",4) == 0) {
112 h->Collate = CUPS_TRUE;
113 } else {
114 h->Collate = CUPS_FALSE;
115 }
116 }
117 }
118 }
119
120 static void
add_pdf_header_options(mupdf_page_header * h,cups_array_t * mupdf_args)121 add_pdf_header_options(mupdf_page_header *h,
122 cups_array_t *mupdf_args)
123 {
124 char tmpstr[1024];
125
126 if ((h->HWResolution[0] != 100) || (h->HWResolution[1] != 100)) {
127 snprintf(tmpstr, sizeof(tmpstr), "-r%dx%d", h->HWResolution[0],
128 h->HWResolution[1]);
129 cupsArrayAdd(mupdf_args, strdup(tmpstr));
130 } else {
131 snprintf(tmpstr, sizeof(tmpstr), "-r100x100");
132 cupsArrayAdd(mupdf_args, strdup(tmpstr));
133 }
134
135 snprintf(tmpstr, sizeof(tmpstr), "-w%d", h->cupsWidth);
136 cupsArrayAdd(mupdf_args, strdup(tmpstr));
137
138 snprintf(tmpstr, sizeof(tmpstr), "-h%d", h->cupsHeight);
139 cupsArrayAdd(mupdf_args, strdup(tmpstr));
140
141 switch (h->cupsColorSpace) {
142 case CUPS_CSPACE_RGB:
143 case CUPS_CSPACE_CMY:
144 case CUPS_CSPACE_SRGB:
145 case CUPS_CSPACE_ADOBERGB:
146 snprintf(tmpstr, sizeof(tmpstr), "-crgb");
147 break;
148
149 case CUPS_CSPACE_CMYK:
150 snprintf(tmpstr, sizeof(tmpstr), "-ccmyk");
151 break;
152
153 case CUPS_CSPACE_SW:
154 snprintf(tmpstr, sizeof(tmpstr), "-cgray");
155 break;
156
157 default:
158 case CUPS_CSPACE_K:
159 case CUPS_CSPACE_W:
160 snprintf(tmpstr, sizeof(tmpstr), "-cmono");
161 break;
162 }
163 cupsArrayAdd(mupdf_args, strdup(tmpstr));
164 }
165
166 static int
mutool_spawn(const char * filename,cups_array_t * mupdf_args,char ** envp)167 mutool_spawn (const char *filename,
168 cups_array_t *mupdf_args,
169 char **envp)
170 {
171 char *argument;
172 char **mutoolargv;
173 const char* apos;
174 int i;
175 int numargs;
176 int pid;
177 int status = 65536;
178 int wstatus;
179
180 /* Put mutool command line argument into an array for the "exec()"
181 call */
182 numargs = cupsArrayCount(mupdf_args);
183 mutoolargv = calloc(numargs + 1, sizeof(char *));
184 for (argument = (char *)cupsArrayFirst(mupdf_args), i = 0; argument;
185 argument = (char *)cupsArrayNext(mupdf_args), i++) {
186 mutoolargv[i] = argument;
187 }
188 mutoolargv[i] = NULL;
189
190 /* Debug output: Full mutool command line and environment variables */
191 fprintf(stderr, "DEBUG: mutool command line:");
192 for (i = 0; mutoolargv[i]; i ++) {
193 if ((strchr(mutoolargv[i],' ')) || (strchr(mutoolargv[i],'\t')))
194 apos = "'";
195 else
196 apos = "";
197 fprintf(stderr, " %s%s%s", apos, mutoolargv[i], apos);
198 }
199 fprintf(stderr, "\n");
200
201 for (i = 0; envp[i]; i ++)
202 fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]);
203
204 if ((pid = fork()) == 0) {
205 /* Execute mutool command line ... */
206 execvpe(filename, mutoolargv, envp);
207 perror(filename);
208 goto out;
209 }
210
211 retry_wait:
212 if (waitpid (pid, &wstatus, 0) == -1) {
213 if (errno == EINTR)
214 goto retry_wait;
215 perror ("mutool");
216 goto out;
217 }
218
219 /* How did mutool process terminate */
220 if (WIFEXITED(wstatus))
221 /* Via exit() anywhere or return() in the main() function */
222 status = WEXITSTATUS(wstatus);
223 else if (WIFSIGNALED(wstatus))
224 /* Via signal */
225 status = 256 * WTERMSIG(wstatus);
226 fprintf(stderr, "DEBUG: mutool completed, status: %d\n", status);
227
228 out:
229 free(mutoolargv);
230 return status;
231 }
232
233 int
main(int argc,char ** argv,char * envp[])234 main (int argc, char **argv, char *envp[])
235 {
236 char buf[BUFSIZ];
237 char *icc_profile = NULL;
238 char tmpstr[1024];
239 const char *t = NULL;
240 cups_array_t *mupdf_args = NULL;
241 cups_option_t *options = NULL;
242 FILE *fp = NULL;
243 char infilename[1024];
244 mupdf_page_header h;
245 int fd = -1;
246 int cm_disabled;
247 int n;
248 int num_options;
249 int empty = 0;
250 int status = 1;
251 ppd_file_t *ppd = NULL;
252 struct sigaction sa;
253 cm_calibration_t cm_calibrate;
254 #ifdef HAVE_CUPS_1_7
255 ppd_attr_t *attr;
256 #endif /* HAVE_CUPS_1_7 */
257
258 if (argc < 6 || argc > 7) {
259 fprintf(stderr, "ERROR: %s job-id user title copies options [file]\n",
260 argv[0]);
261 goto out;
262 }
263
264 memset(&sa, 0, sizeof(sa));
265 /* Ignore SIGPIPE and have write return an error instead */
266 sa.sa_handler = SIG_IGN;
267 sigaction(SIGPIPE, &sa, NULL);
268
269 num_options = cupsParseOptions(argv[5], 0, &options);
270
271 t = getenv("PPD");
272 if (t && t[0] != '\0')
273 if ((ppd = ppdOpenFile(t)) == NULL) {
274 fprintf(stderr, "ERROR: Failed to open PPD: %s\n", t);
275 }
276
277 if (ppd) {
278 ppdMarkDefaults (ppd);
279 cupsMarkOptions (ppd, num_options, options);
280 }
281
282 if (argc == 6) {
283 /* stdin */
284
285 fd = cupsTempFd(infilename, 1024);
286 if (fd < 0) {
287 fprintf(stderr, "ERROR: Can't create temporary file\n");
288 goto out;
289 }
290
291 /* copy stdin to the tmp file */
292 while ((n = read(0,buf,BUFSIZ)) > 0) {
293 if (write(fd,buf,n) != n) {
294 fprintf(stderr, "ERROR: Can't copy stdin to temporary file\n");
295 close(fd);
296 goto out;
297 }
298 }
299 if (lseek(fd,0,SEEK_SET) < 0) {
300 fprintf(stderr, "ERROR: Can't rewind temporary file\n");
301 close(fd);
302 goto out;
303 }
304
305 if ((fp = fdopen(fd,"rb")) == 0) {
306 fprintf(stderr, "ERROR: Can't fdopen temporary file\n");
307 close(fd);
308 goto out;
309 }
310 } else {
311 /* argc == 7 filename is specified */
312
313 if ((fp = fopen(argv[6],"rb")) == 0) {
314 fprintf(stderr, "ERROR: Can't open input file %s\n",argv[6]);
315 goto out;
316 }
317 strncpy(infilename, argv[6], sizeof(infilename) - 1);
318 }
319
320 /* If doc type is not PDF exit */
321 if(parse_doc_type(fp))
322 empty = 1;
323
324 /* Check status of color management in CUPS */
325 cm_calibrate = cmGetCupsColorCalibrateMode(options, num_options);
326
327 if (cm_calibrate == CM_CALIBRATION_ENABLED)
328 cm_disabled = 1;
329 else
330 cm_disabled = cmIsPrinterCmDisabled(getenv("PRINTER"));
331
332 if (!cm_disabled)
333 cmGetPrinterIccProfile(getenv("PRINTER"), &icc_profile, ppd);
334
335 /* mutool parameters */
336 mupdf_args = cupsArrayNew(NULL, NULL);
337 if (!mupdf_args) {
338 fprintf(stderr, "ERROR: Unable to allocate memory for mutool arguments array\n");
339 goto out;
340 }
341
342 fprintf(stderr,"command: %s\n",CUPS_MUTOOL);
343 snprintf(tmpstr, sizeof(tmpstr), "%s", CUPS_MUTOOL);
344 cupsArrayAdd(mupdf_args, strdup(tmpstr));
345 cupsArrayAdd(mupdf_args, strdup("draw"));
346 cupsArrayAdd(mupdf_args, strdup("-L"));
347 cupsArrayAdd(mupdf_args, strdup("-o-"));
348 cupsArrayAdd(mupdf_args, strdup("-smtf"));
349
350 /* mutool output parameters */
351 cupsArrayAdd(mupdf_args, strdup("-Fpwg"));
352
353 /* Note that MuPDF only creates PWG Raster and never CUPS Raster,
354 so we always set the PWG Raster flag in the cupsRasterParseIPPOptions()
355 calls.
356 Make also sure that the width and height of the page in pixels is
357 the size of the full page (as PWG Raster and MuPDF require it) and not
358 only the printable area (as cupsRasterInterpretPPD() sets, to fulfill
359 CUPS Raster standard) */
360 if (ppd) {
361 cupsRasterInterpretPPD(&h, ppd, num_options, options, 0);
362 h.cupsWidth = h.HWResolution[0] * h.PageSize[0] / 72;
363 h.cupsHeight = h.HWResolution[1] * h.PageSize[1] / 72;
364 #ifdef HAVE_CUPS_1_7
365 cupsRasterParseIPPOptions(&h, num_options, options, 1, 0);
366 #endif /* HAVE_CUPS_1_7 */
367 } else {
368 #ifdef HAVE_CUPS_1_7
369 cupsRasterParseIPPOptions(&h, num_options, options, 1, 1);
370 #else
371 fprintf(stderr, "ERROR: No PPD file specified.\n");
372 goto out;
373 #endif /* HAVE_CUPS_1_7 */
374 }
375
376 if ((h.HWResolution[0] == 100) && (h.HWResolution[1] == 100)) {
377 /* No "Resolution" option */
378 if (ppd && (attr = ppdFindAttr(ppd, "DefaultResolution", 0)) != NULL) {
379 /* "*DefaultResolution" keyword in the PPD */
380 const char *p = attr->value;
381 h.HWResolution[0] = atoi(p);
382 if ((p = strchr(p, 'x')) != NULL)
383 h.HWResolution[1] = atoi(p);
384 else
385 h.HWResolution[1] = h.HWResolution[0];
386 if (h.HWResolution[0] <= 0)
387 h.HWResolution[0] = 300;
388 if (h.HWResolution[1] <= 0)
389 h.HWResolution[1] = h.HWResolution[0];
390 } else {
391 h.HWResolution[0] = 300;
392 h.HWResolution[1] = 300;
393 }
394 h.cupsWidth = h.HWResolution[0] * h.PageSize[0] / 72;
395 h.cupsHeight = h.HWResolution[1] * h.PageSize[1] / 72;
396 }
397
398 /* set PDF-specific options */
399 parse_pdf_header_options(fp, &h);
400
401 /* fixed other values that pdftopdf handles */
402 h.MirrorPrint = CUPS_FALSE;
403 h.Orientation = CUPS_ORIENT_0;
404
405 /* get all the data from the header and pass it to mutool */
406 add_pdf_header_options (&h, mupdf_args);
407
408 snprintf(tmpstr, sizeof(tmpstr), "%s", infilename);
409 cupsArrayAdd(mupdf_args, strdup(tmpstr));
410
411 /* Execute mutool command line ... */
412 snprintf(tmpstr, sizeof(tmpstr), "%s", CUPS_MUTOOL);
413
414 /* call mutool */
415 status = mutool_spawn (tmpstr, mupdf_args, envp);
416 if (status != 0) status = 1;
417
418 if(empty)
419 {
420 fprintf(stderr, "DEBUG: Input is empty, outputting empty file.\n");
421 status = 0;
422 }
423 out:
424 if (fp)
425 fclose(fp);
426 if (mupdf_args)
427 cupsArrayDelete(mupdf_args);
428
429 free(icc_profile);
430 if (ppd)
431 ppdClose(ppd);
432 if (fd >= 0)
433 unlink(infilename);
434 return status;
435 }
436