1 /*
2 * File type conversion routines for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright 2007-2011 by Apple Inc.
6 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
9 */
10
11 /*
12 * Include necessary headers...
13 */
14
15 #include <cups/string-private.h>
16 #include "mime.h"
17
18
19 /*
20 * Debug macros that used to be private API...
21 */
22
23 #define DEBUG_puts(x)
24 #define DEBUG_printf(...)
25
26
27 /*
28 * Local types...
29 */
30
31 typedef struct _mime_typelist_s /**** List of source types ****/
32 {
33 struct _mime_typelist_s *next; /* Next source type */
34 mime_type_t *src; /* Source type */
35 } _mime_typelist_t;
36
37
38 /*
39 * Local functions...
40 */
41
42 static int mime_compare_filters(mime_filter_t *, mime_filter_t *);
43 static int mime_compare_srcs(mime_filter_t *, mime_filter_t *);
44 static cups_array_t *mime_find_filters(mime_t *mime, mime_type_t *src,
45 size_t srcsize, mime_type_t *dst,
46 int *cost, _mime_typelist_t *visited);
47
48
49 /*
50 * 'mimeAddFilter()' - Add a filter to the current MIME database.
51 */
52
53 mime_filter_t * /* O - New filter */
mimeAddFilter(mime_t * mime,mime_type_t * src,mime_type_t * dst,int cost,const char * filter)54 mimeAddFilter(mime_t *mime, /* I - MIME database */
55 mime_type_t *src, /* I - Source type */
56 mime_type_t *dst, /* I - Destination type */
57 int cost, /* I - Relative time/resource cost */
58 const char *filter) /* I - Filter program to run */
59 {
60 mime_filter_t *temp; /* New filter */
61
62
63 DEBUG_printf(("mimeAddFilter(mime=%p, src=%p(%s/%s), dst=%p(%s/%s), cost=%d, "
64 "filter=\"%s\")", mime,
65 src, src ? src->super : "???", src ? src->type : "???",
66 dst, dst ? dst->super : "???", dst ? dst->type : "???",
67 cost, filter));
68
69 /*
70 * Range-check the input...
71 */
72
73 if (!mime || !src || !dst || !filter)
74 {
75 DEBUG_puts("1mimeAddFilter: Returning NULL.");
76 return (NULL);
77 }
78
79 /*
80 * See if we already have an existing filter for the given source and
81 * destination...
82 */
83
84 if ((temp = mimeFilterLookup(mime, src, dst)) != NULL)
85 {
86 /*
87 * Yup, does the existing filter have a higher cost? If so, copy the
88 * filter and cost to the existing filter entry and return it...
89 */
90
91 if (temp->cost > cost)
92 {
93 DEBUG_printf(("1mimeAddFilter: Replacing filter \"%s\", cost %d.",
94 temp->filter, temp->cost));
95 temp->cost = cost;
96 strlcpy(temp->filter, filter, sizeof(temp->filter));
97 }
98 }
99 else
100 {
101 /*
102 * Nope, add a new one...
103 */
104
105 if (!mime->filters)
106 mime->filters = cupsArrayNew((cups_array_func_t)mime_compare_filters, NULL);
107
108 if (!mime->filters)
109 return (NULL);
110
111 if ((temp = calloc(1, sizeof(mime_filter_t))) == NULL)
112 return (NULL);
113
114 /*
115 * Copy the information over and sort if necessary...
116 */
117
118 temp->src = src;
119 temp->dst = dst;
120 temp->cost = cost;
121 strlcpy(temp->filter, filter, sizeof(temp->filter));
122
123 DEBUG_puts("1mimeAddFilter: Adding new filter.");
124 cupsArrayAdd(mime->filters, temp);
125 cupsArrayAdd(mime->srcs, temp);
126 }
127
128 /*
129 * Return the new/updated filter...
130 */
131
132 DEBUG_printf(("1mimeAddFilter: Returning %p.", temp));
133
134 return (temp);
135 }
136
137
138 /*
139 * 'mimeFilter()' - Find the fastest way to convert from one type to another.
140 */
141
142 cups_array_t * /* O - Array of filters to run */
mimeFilter(mime_t * mime,mime_type_t * src,mime_type_t * dst,int * cost)143 mimeFilter(mime_t *mime, /* I - MIME database */
144 mime_type_t *src, /* I - Source file type */
145 mime_type_t *dst, /* I - Destination file type */
146 int *cost) /* O - Cost of filters */
147 {
148 DEBUG_printf(("mimeFilter(mime=%p, src=%p(%s/%s), dst=%p(%s/%s), "
149 "cost=%p(%d))", mime,
150 src, src ? src->super : "???", src ? src->type : "???",
151 dst, dst ? dst->super : "???", dst ? dst->type : "???",
152 cost, cost ? *cost : 0));
153
154 return (mimeFilter2(mime, src, 0, dst, cost));
155 }
156
157
158 /*
159 * 'mimeFilter2()' - Find the fastest way to convert from one type to another,
160 * including file size.
161 */
162
163 cups_array_t * /* O - Array of filters to run */
mimeFilter2(mime_t * mime,mime_type_t * src,size_t srcsize,mime_type_t * dst,int * cost)164 mimeFilter2(mime_t *mime, /* I - MIME database */
165 mime_type_t *src, /* I - Source file type */
166 size_t srcsize, /* I - Size of source file */
167 mime_type_t *dst, /* I - Destination file type */
168 int *cost) /* O - Cost of filters */
169 {
170 cups_array_t *filters; /* Array of filters to run */
171
172
173 /*
174 * Range-check the input...
175 */
176
177 DEBUG_printf(("mimeFilter2(mime=%p, src=%p(%s/%s), srcsize=" CUPS_LLFMT
178 ", dst=%p(%s/%s), cost=%p(%d))", mime,
179 src, src ? src->super : "???", src ? src->type : "???",
180 CUPS_LLCAST srcsize,
181 dst, dst ? dst->super : "???", dst ? dst->type : "???",
182 cost, cost ? *cost : 0));
183
184 if (cost)
185 *cost = 0;
186
187 if (!mime || !src || !dst)
188 return (NULL);
189
190 /*
191 * (Re)build the source lookup array as needed...
192 */
193
194 if (!mime->srcs)
195 {
196 mime_filter_t *current; /* Current filter */
197
198 mime->srcs = cupsArrayNew((cups_array_func_t)mime_compare_srcs, NULL);
199
200 for (current = mimeFirstFilter(mime);
201 current;
202 current = mimeNextFilter(mime))
203 cupsArrayAdd(mime->srcs, current);
204 }
205
206 /*
207 * Find the filters...
208 */
209
210 filters = mime_find_filters(mime, src, srcsize, dst, cost, NULL);
211
212 DEBUG_printf(("1mimeFilter2: Returning %d filter(s), cost %d:",
213 cupsArrayCount(filters), cost ? *cost : -1));
214 #ifdef DEBUG
215 {
216 mime_filter_t *filter; /* Current filter */
217
218 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
219 filter;
220 filter = (mime_filter_t *)cupsArrayNext(filters))
221 DEBUG_printf(("1mimeFilter2: %s/%s %s/%s %d %s", filter->src->super,
222 filter->src->type, filter->dst->super, filter->dst->type,
223 filter->cost, filter->filter));
224 }
225 #endif /* DEBUG */
226
227 return (filters);
228 }
229
230
231 /*
232 * 'mimeFilterLookup()' - Lookup a filter.
233 */
234
235 mime_filter_t * /* O - Filter for src->dst */
mimeFilterLookup(mime_t * mime,mime_type_t * src,mime_type_t * dst)236 mimeFilterLookup(mime_t *mime, /* I - MIME database */
237 mime_type_t *src, /* I - Source type */
238 mime_type_t *dst) /* I - Destination type */
239 {
240 mime_filter_t key, /* Key record for filter search */
241 *filter; /* Matching filter */
242
243
244 DEBUG_printf(("2mimeFilterLookup(mime=%p, src=%p(%s/%s), dst=%p(%s/%s))", mime,
245 src, src ? src->super : "???", src ? src->type : "???",
246 dst, dst ? dst->super : "???", dst ? dst->type : "???"));
247
248 key.src = src;
249 key.dst = dst;
250
251 filter = (mime_filter_t *)cupsArrayFind(mime->filters, &key);
252 DEBUG_printf(("3mimeFilterLookup: Returning %p(%s).", filter,
253 filter ? filter->filter : "???"));
254 return (filter);
255 }
256
257
258 /*
259 * 'mime_compare_filters()' - Compare two filters.
260 */
261
262 static int /* O - Comparison result */
mime_compare_filters(mime_filter_t * f0,mime_filter_t * f1)263 mime_compare_filters(mime_filter_t *f0, /* I - First filter */
264 mime_filter_t *f1) /* I - Second filter */
265 {
266 int i; /* Result of comparison */
267
268
269 if ((i = strcmp(f0->src->super, f1->src->super)) == 0)
270 if ((i = strcmp(f0->src->type, f1->src->type)) == 0)
271 if ((i = strcmp(f0->dst->super, f1->dst->super)) == 0)
272 i = strcmp(f0->dst->type, f1->dst->type);
273
274 return (i);
275 }
276
277
278 /*
279 * 'mime_compare_srcs()' - Compare two filter source types.
280 */
281
282 static int /* O - Comparison result */
mime_compare_srcs(mime_filter_t * f0,mime_filter_t * f1)283 mime_compare_srcs(mime_filter_t *f0, /* I - First filter */
284 mime_filter_t *f1) /* I - Second filter */
285 {
286 int i; /* Result of comparison */
287
288
289 if ((i = strcmp(f0->src->super, f1->src->super)) == 0)
290 i = strcmp(f0->src->type, f1->src->type);
291
292 return (i);
293 }
294
295
296 /*
297 * 'mime_find_filters()' - Find the filters to convert from one type to another.
298 */
299
300 static cups_array_t * /* O - Array of filters to run */
mime_find_filters(mime_t * mime,mime_type_t * src,size_t srcsize,mime_type_t * dst,int * cost,_mime_typelist_t * list)301 mime_find_filters(
302 mime_t *mime, /* I - MIME database */
303 mime_type_t *src, /* I - Source file type */
304 size_t srcsize, /* I - Size of source file */
305 mime_type_t *dst, /* I - Destination file type */
306 int *cost, /* O - Cost of filters */
307 _mime_typelist_t *list) /* I - Source types we've used */
308 {
309 int tempcost, /* Temporary cost */
310 mincost; /* Current minimum */
311 cups_array_t *temp, /* Temporary filter */
312 *mintemp; /* Current minimum */
313 mime_filter_t *current, /* Current filter */
314 srckey; /* Source type key */
315 _mime_typelist_t listnode, /* New list node */
316 *listptr; /* Pointer in list */
317
318
319 DEBUG_printf(("2mime_find_filters(mime=%p, src=%p(%s/%s), srcsize=" CUPS_LLFMT
320 ", dst=%p(%s/%s), cost=%p, list=%p)", mime, src, src->super,
321 src->type, CUPS_LLCAST srcsize, dst, dst->super, dst->type,
322 cost, list));
323
324 /*
325 * See if there is a filter that can convert the files directly...
326 */
327
328 if ((current = mimeFilterLookup(mime, src, dst)) != NULL &&
329 (current->maxsize == 0 || srcsize <= current->maxsize))
330 {
331 /*
332 * Got a direct filter!
333 */
334
335 DEBUG_puts("3mime_find_filters: Direct filter found.");
336
337 if ((mintemp = cupsArrayNew(NULL, NULL)) == NULL)
338 {
339 DEBUG_puts("3mime_find_filters: Returning NULL (out of memory).");
340 return (NULL);
341 }
342
343 cupsArrayAdd(mintemp, current);
344
345 mincost = current->cost;
346
347 if (!cost)
348 {
349 DEBUG_printf(("3mime_find_filters: Returning 1 filter, cost %d:",
350 mincost));
351 DEBUG_printf(("3mime_find_filters: %s/%s %s/%s %d %s",
352 current->src->super, current->src->type,
353 current->dst->super, current->dst->type,
354 current->cost, current->filter));
355 return (mintemp);
356 }
357 }
358 else
359 {
360 /*
361 * No direct filter...
362 */
363
364 mintemp = NULL;
365 mincost = 9999999;
366 }
367
368 /*
369 * Initialize this node in the type list...
370 */
371
372 listnode.next = list;
373
374 /*
375 * OK, now look for filters from the source type to any other type...
376 */
377
378 srckey.src = src;
379
380 for (current = (mime_filter_t *)cupsArrayFind(mime->srcs, &srckey);
381 current && current->src == src;
382 current = (mime_filter_t *)cupsArrayNext(mime->srcs))
383 {
384 /*
385 * See if we have already tried the destination type as a source
386 * type (this avoids extra filter looping...)
387 */
388
389 mime_type_t *current_dst; /* Current destination type */
390
391 if (current->maxsize > 0 && srcsize > current->maxsize)
392 continue;
393
394 for (listptr = list, current_dst = current->dst;
395 listptr;
396 listptr = listptr->next)
397 if (current_dst == listptr->src)
398 break;
399
400 if (listptr)
401 continue;
402
403 /*
404 * See if we have any filters that can convert from the destination type
405 * of this filter to the final type...
406 */
407
408 listnode.src = current->src;
409
410 cupsArraySave(mime->srcs);
411 temp = mime_find_filters(mime, current->dst, srcsize, dst, &tempcost,
412 &listnode);
413 cupsArrayRestore(mime->srcs);
414
415 if (!temp)
416 continue;
417
418 if (!cost)
419 {
420 DEBUG_printf(("3mime_find_filters: Returning %d filter(s), cost %d:",
421 cupsArrayCount(temp), tempcost));
422
423 #ifdef DEBUG
424 for (current = (mime_filter_t *)cupsArrayFirst(temp);
425 current;
426 current = (mime_filter_t *)cupsArrayNext(temp))
427 DEBUG_printf(("3mime_find_filters: %s/%s %s/%s %d %s",
428 current->src->super, current->src->type,
429 current->dst->super, current->dst->type,
430 current->cost, current->filter));
431 #endif /* DEBUG */
432
433 return (temp);
434 }
435
436 /*
437 * Found a match; see if this one is less costly than the last (if
438 * any...)
439 */
440
441 tempcost += current->cost;
442
443 if (tempcost < mincost)
444 {
445 cupsArrayDelete(mintemp);
446
447 /*
448 * Hey, we got a match! Add the current filter to the beginning of the
449 * filter list...
450 */
451
452 mintemp = temp;
453 mincost = tempcost;
454 cupsArrayInsert(mintemp, current);
455 }
456 else
457 cupsArrayDelete(temp);
458 }
459
460 if (mintemp)
461 {
462 /*
463 * Hey, we got a match!
464 */
465
466 DEBUG_printf(("3mime_find_filters: Returning %d filter(s), cost %d:",
467 cupsArrayCount(mintemp), mincost));
468
469 #ifdef DEBUG
470 for (current = (mime_filter_t *)cupsArrayFirst(mintemp);
471 current;
472 current = (mime_filter_t *)cupsArrayNext(mintemp))
473 DEBUG_printf(("3mime_find_filters: %s/%s %s/%s %d %s",
474 current->src->super, current->src->type,
475 current->dst->super, current->dst->type,
476 current->cost, current->filter));
477 #endif /* DEBUG */
478
479 if (cost)
480 *cost = mincost;
481
482 return (mintemp);
483 }
484
485 DEBUG_puts("3mime_find_filters: Returning NULL (no matches).");
486
487 return (NULL);
488 }
489