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