• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * MIME typing routines for CUPS.
3  *
4  * Copyright © 2020-2024 by OpenPrinting.
5  * Copyright © 2007-2019 by Apple Inc.
6  * Copyright © 1997-2006 by Easy Software Products, all rights reserved.
7  *
8  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
9  * information.
10  */
11 
12 /*
13  * Include necessary headers...
14  */
15 
16 #include <cups/string-private.h>
17 #include <locale.h>
18 #include "mime.h"
19 
20 
21 /*
22  * Debug macros that used to be private API...
23  */
24 
25 #define DEBUG_puts(x)
26 #define DEBUG_printf(...)
27 
28 
29 /*
30  * Local types...
31  */
32 
33 typedef struct _mime_filebuf_s		/**** File buffer for MIME typing ****/
34 {
35   cups_file_t	*fp;			/* File pointer */
36   int		offset,			/* Offset in file */
37 		length;			/* Length of buffered data */
38   unsigned char	buffer[MIME_MAX_BUFFER];/* Buffered data */
39 } _mime_filebuf_t;
40 
41 
42 /*
43  * Local functions...
44  */
45 
46 static int	mime_compare_types(mime_type_t *t0, mime_type_t *t1);
47 static int	mime_check_rules(const char *filename, _mime_filebuf_t *fb,
48 		                 mime_magic_t *rules);
49 static int	mime_patmatch(const char *s, const char *pat);
50 
51 
52 /*
53  * Local globals...
54  */
55 
56 #ifdef MIME_DEBUG
57 static const char * const debug_ops[] =
58 		{			/* Test names... */
59 		  "NOP",		/* No operation */
60 		  "AND",		/* Logical AND of all children */
61 		  "OR",			/* Logical OR of all children */
62 		  "MATCH",		/* Filename match */
63 		  "ASCII",		/* ASCII characters in range */
64 		  "PRINTABLE",		/* Printable characters (32-255) */
65 		  "STRING",		/* String matches */
66 		  "CHAR",		/* Character/byte matches */
67 		  "SHORT",		/* Short/16-bit word matches */
68 		  "INT",		/* Integer/32-bit word matches */
69 		  "LOCALE",		/* Current locale matches string */
70 		  "CONTAINS",		/* File contains a string */
71 		  "ISTRING",		/* Case-insensitive string matches */
72 		  "REGEX"		/* Regular expression matches */
73 		};
74 #endif /* DEBUG */
75 
76 
77 /*
78  * 'mimeAddType()' - Add a MIME type to a database.
79  */
80 
81 mime_type_t *				/* O - New (or existing) MIME type */
mimeAddType(mime_t * mime,const char * super,const char * type)82 mimeAddType(mime_t     *mime,		/* I - MIME database */
83             const char *super,		/* I - Super-type name */
84 	    const char *type)		/* I - Type name */
85 {
86   mime_type_t	*temp;			/* New MIME type */
87   size_t	typelen;		/* Length of type name */
88 
89 
90   DEBUG_printf(("mimeAddType(mime=%p, super=\"%s\", type=\"%s\")", mime, super,
91                 type));
92 
93  /*
94   * Range check input...
95   */
96 
97   if (!mime || !super || !type)
98   {
99     DEBUG_puts("1mimeAddType: Returning NULL (bad arguments).");
100     return (NULL);
101   }
102 
103  /*
104   * See if the type already exists; if so, return the existing type...
105   */
106 
107   if ((temp = mimeType(mime, super, type)) != NULL)
108   {
109     DEBUG_printf(("1mimeAddType: Returning %p (existing).", temp));
110     return (temp);
111   }
112 
113  /*
114   * The type doesn't exist; add it...
115   */
116 
117   if (!mime->types)
118     mime->types = cupsArrayNew((cups_array_func_t)mime_compare_types, NULL);
119 
120   if (!mime->types)
121   {
122     DEBUG_puts("1mimeAddType: Returning NULL (no types).");
123     return (NULL);
124   }
125 
126   typelen = strlen(type) + 1;
127 
128   if ((temp = calloc(1, sizeof(mime_type_t) - MIME_MAX_TYPE + typelen)) == NULL)
129   {
130     DEBUG_puts("1mimeAddType: Returning NULL (out of memory).");
131     return (NULL);
132   }
133 
134   strlcpy(temp->super, super, sizeof(temp->super));
135   memcpy(temp->type, type, typelen);
136   temp->priority = 100;
137 
138   cupsArrayAdd(mime->types, temp);
139 
140   DEBUG_printf(("1mimeAddType: Returning %p (new).", temp));
141   return (temp);
142 }
143 
144 
145 /*
146  * 'mimeAddTypeRule()' - Add a detection rule for a file type.
147  */
148 
149 int					/* O - 0 on success, -1 on failure */
mimeAddTypeRule(mime_type_t * mt,const char * rule)150 mimeAddTypeRule(mime_type_t *mt,	/* I - Type to add to */
151                 const char  *rule)	/* I - Rule to add */
152 {
153   int		num_values,		/* Number of values seen */
154 		op,			/* Operation code */
155 		logic,			/* Logic for next rule */
156 		invert;			/* Invert following rule? */
157   char		name[255],		/* Name in rule string */
158 		value[3][255],		/* Value in rule string */
159 		*ptr,			/* Position in name or value */
160 		quote;			/* Quote character */
161   int		length[3];		/* Length of each parameter */
162   mime_magic_t	*temp,			/* New rule */
163 		*current;  		/* Current rule */
164 
165 
166   DEBUG_printf(("mimeAddTypeRule(mt=%p(%s/%s), rule=\"%s\")", mt,
167                 mt ? mt->super : "???", mt ? mt->type : "???", rule));
168 
169  /*
170   * Range check input...
171   */
172 
173   if (!mt || !rule)
174     return (-1);
175 
176  /*
177   * Find the last rule in the top-level of the rules tree.
178   */
179 
180   for (current = mt->rules; current; current = current->next)
181     if (!current->next)
182       break;
183 
184  /*
185   * Parse the rules string.  Most rules are either a file extension or a
186   * comparison function:
187   *
188   *    extension
189   *    function(parameters)
190   */
191 
192   logic  = MIME_MAGIC_NOP;
193   invert = 0;
194 
195   while (*rule != '\0')
196   {
197     while (isspace(*rule & 255))
198       rule ++;
199 
200     if (*rule == '(')
201     {
202       DEBUG_puts("1mimeAddTypeRule: New parenthesis group");
203       logic = MIME_MAGIC_NOP;
204       rule ++;
205     }
206     else if (*rule == ')')
207     {
208       DEBUG_puts("1mimeAddTypeRule: Close paren...");
209       if (current == NULL || current->parent == NULL)
210         return (-1);
211 
212       current = current->parent;
213 
214       if (current->parent == NULL)
215         logic = MIME_MAGIC_OR;
216       else
217         logic = current->parent->op;
218 
219       rule ++;
220     }
221     else if (*rule == '+' && current != NULL)
222     {
223       if (logic != MIME_MAGIC_AND &&
224           current->prev != NULL)
225       {
226        /*
227         * OK, we have more than 1 rule in the current tree level...  Make a
228 	* new group tree and move the previous rule to it...
229 	*/
230 
231 	if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
232 	  return (-1);
233 
234         temp->op            = MIME_MAGIC_AND;
235         temp->child         = current;
236         temp->parent        = current->parent;
237 	current->prev->next = temp;
238 	temp->prev          = current->prev;
239 
240         current->prev   = NULL;
241 	current->parent = temp;
242 
243         DEBUG_printf(("1mimeAddTypeRule: Creating new AND group %p.", temp));
244       }
245       else if (current->parent)
246       {
247         DEBUG_printf(("1mimeAddTypeRule: Setting group %p op to AND.",
248 	              current->parent));
249         current->parent->op = MIME_MAGIC_AND;
250       }
251 
252       logic = MIME_MAGIC_AND;
253       rule ++;
254     }
255     else if (*rule == ',')
256     {
257       if (logic != MIME_MAGIC_OR && current != NULL)
258       {
259        /*
260         * OK, we have two possibilities; either this is the top-level rule or
261 	* we have a bunch of AND rules at this level.
262 	*/
263 
264 	if (current->parent == NULL)
265 	{
266 	 /*
267 	  * This is the top-level rule; we have to move *all* of the AND rules
268 	  * down a level, as AND has precedence over OR.
269 	  */
270 
271 	  if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
272 	    return (-1);
273 
274           DEBUG_printf(("1mimeAddTypeRule: Creating new AND group %p inside OR "
275 	                "group.", temp));
276 
277           while (current->prev != NULL)
278 	  {
279 	    current->parent = temp;
280 	    current         = current->prev;
281 	  }
282 
283           current->parent = temp;
284           temp->op        = MIME_MAGIC_AND;
285           temp->child     = current;
286 
287           mt->rules = current = temp;
288 	}
289 	else
290 	{
291 	 /*
292 	  * This isn't the top rule, so go up one level...
293 	  */
294 
295           DEBUG_puts("1mimeAddTypeRule: Going up one level.");
296 	  current = current->parent;
297 	}
298       }
299 
300       logic = MIME_MAGIC_OR;
301       rule ++;
302     }
303     else if (*rule == '!')
304     {
305       DEBUG_puts("1mimeAddTypeRule: NOT");
306       invert = 1;
307       rule ++;
308     }
309     else if (isalnum(*rule & 255))
310     {
311      /*
312       * Read an extension name or a function...
313       */
314 
315       ptr = name;
316       while (isalnum(*rule & 255) && (size_t)(ptr - name) < (sizeof(name) - 1))
317         *ptr++ = *rule++;
318 
319       *ptr = '\0';
320 
321       if (*rule == '(')
322       {
323        /*
324         * Read function parameters...
325 	*/
326 
327 	rule ++;
328 	for (num_values = 0;
329 	     num_values < (int)(sizeof(value) / sizeof(value[0]));
330 	     num_values ++)
331 	{
332 	  ptr = value[num_values];
333 
334 	  while ((size_t)(ptr - value[num_values]) < (sizeof(value[0]) - 1) &&
335 	         *rule != '\0' && *rule != ',' && *rule != ')')
336 	  {
337 	    if (isspace(*rule & 255))
338 	    {
339 	     /*
340 	      * Ignore whitespace...
341 	      */
342 
343 	      rule ++;
344 	      continue;
345 	    }
346 	    else if (*rule == '\"' || *rule == '\'')
347 	    {
348 	     /*
349 	      * Copy quoted strings literally...
350 	      */
351 
352 	      quote = *rule++;
353 
354 	      while (*rule != '\0' && *rule != quote &&
355 	             (size_t)(ptr - value[num_values]) < (sizeof(value[0]) - 1))
356 	        *ptr++ = *rule++;
357 
358               if (*rule == quote)
359 	        rule ++;
360 	      else
361 		return (-1);
362 	    }
363 	    else if (*rule == '<')
364 	    {
365 	      rule ++;
366 
367 	      while (*rule != '>' && *rule != '\0' &&
368 	             (size_t)(ptr - value[num_values]) < (sizeof(value[0]) - 1))
369 	      {
370 	        if (isxdigit(rule[0] & 255) && isxdigit(rule[1] & 255))
371 		{
372 		  if (isdigit(*rule))
373 		    *ptr = (char)((*rule++ - '0') << 4);
374 		  else
375 		    *ptr = (char)((tolower(*rule++) - 'a' + 10) << 4);
376 
377 		  if (isdigit(*rule))
378 		    *ptr++ |= *rule++ - '0';
379 		  else
380 		    *ptr++ |= tolower(*rule++) - 'a' + 10;
381 		}
382 		else
383 	          return (-1);
384 	      }
385 
386               if (*rule == '>')
387 	        rule ++;
388 	      else
389 		return (-1);
390 	    }
391 	    else
392 	      *ptr++ = *rule++;
393 	  }
394 
395           *ptr = '\0';
396 	  length[num_values] = ptr - value[num_values];
397 
398           if (*rule != ',')
399 	  {
400 	    num_values ++;
401 	    break;
402 	  }
403 
404           rule ++;
405 	}
406 
407         if (*rule != ')')
408 	  return (-1);
409 
410 	rule ++;
411 
412        /*
413         * Figure out the function...
414 	*/
415 
416         if (!strcmp(name, "match"))
417 	  op = MIME_MAGIC_MATCH;
418 	else if (!strcmp(name, "ascii"))
419 	  op = MIME_MAGIC_ASCII;
420 	else if (!strcmp(name, "printable"))
421 	  op = MIME_MAGIC_PRINTABLE;
422 	else if (!strcmp(name, "regex"))
423 	  op = MIME_MAGIC_REGEX;
424 	else if (!strcmp(name, "string"))
425 	  op = MIME_MAGIC_STRING;
426 	else if (!strcmp(name, "istring"))
427 	  op = MIME_MAGIC_ISTRING;
428 	else if (!strcmp(name, "char"))
429 	  op = MIME_MAGIC_CHAR;
430 	else if (!strcmp(name, "short"))
431 	  op = MIME_MAGIC_SHORT;
432 	else if (!strcmp(name, "int"))
433 	  op = MIME_MAGIC_INT;
434 	else if (!strcmp(name, "locale"))
435 	  op = MIME_MAGIC_LOCALE;
436 	else if (!strcmp(name, "contains"))
437 	  op = MIME_MAGIC_CONTAINS;
438 	else if (!strcmp(name, "priority") && num_values == 1)
439 	{
440 	  mt->priority = atoi(value[0]);
441 	  continue;
442 	}
443 	else
444 	  return (-1);
445       }
446       else
447       {
448        /*
449         * This is just a filename match on the extension...
450 	*/
451 
452 	snprintf(value[0], sizeof(value[0]), "*.%s", name);
453 	length[0]  = (int)strlen(value[0]);
454 	op         = MIME_MAGIC_MATCH;
455 	num_values = 1;
456       }
457 
458      /*
459       * Add a rule for this operation.
460       */
461 
462       if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
463 	return (-1);
464 
465       temp->invert = (short)invert;
466       if (current != NULL)
467       {
468 	temp->parent  = current->parent;
469 	current->next = temp;
470       }
471       else
472         mt->rules = temp;
473 
474       temp->prev = current;
475 
476       if (logic == MIME_MAGIC_NOP)
477       {
478        /*
479         * Add parenthetical grouping...
480 	*/
481 
482         DEBUG_printf(("1mimeAddTypeRule: Making new OR group %p for "
483 	              "parenthesis.", temp));
484 
485         temp->op = MIME_MAGIC_OR;
486 
487 	if ((temp->child = calloc(1, sizeof(mime_magic_t))) == NULL)
488 	  return (-1);
489 
490 	temp->child->parent = temp;
491 	temp->child->invert = temp->invert;
492 	temp->invert        = 0;
493 
494 	temp  = temp->child;
495         logic = MIME_MAGIC_OR;
496       }
497 
498       DEBUG_printf(("1mimeAddTypeRule: Adding %p: %s, op=MIME_MAGIC_%s(%d), "
499 		    "logic=MIME_MAGIC_%s, invert=%d.", temp, name,
500 		    debug_ops[op], op, debug_ops[logic], invert));
501 
502      /*
503       * Fill in data for the rule...
504       */
505 
506       current  = temp;
507       temp->op = (short)op;
508       invert   = 0;
509 
510       switch (op)
511       {
512         case MIME_MAGIC_MATCH :
513 	    if ((size_t)length[0] > (sizeof(temp->value.matchv) - 1))
514 	      return (-1);
515 	    strlcpy(temp->value.matchv, value[0], sizeof(temp->value.matchv));
516 	    break;
517 	case MIME_MAGIC_ASCII :
518 	case MIME_MAGIC_PRINTABLE :
519 	    temp->offset = strtol(value[0], NULL, 0);
520 	    temp->length = strtol(value[1], NULL, 0);
521 	    if (temp->length > MIME_MAX_BUFFER)
522 	      temp->length = MIME_MAX_BUFFER;
523 	    break;
524 	case MIME_MAGIC_REGEX :
525 	    temp->offset = strtol(value[0], NULL, 0);
526 	    temp->length = MIME_MAX_BUFFER;
527 	    if (regcomp(&(temp->value.rev), value[1], REG_NOSUB | REG_EXTENDED))
528 	      return (-1);
529 	    break;
530 	case MIME_MAGIC_STRING :
531 	case MIME_MAGIC_ISTRING :
532 	    temp->offset = strtol(value[0], NULL, 0);
533 	    if (num_values < 2 || (size_t)length[1] > sizeof(temp->value.stringv))
534 	      return (-1);
535 	    temp->length = length[1];
536 	    memcpy(temp->value.stringv, value[1], (size_t)length[1]);
537 	    break;
538 	case MIME_MAGIC_CHAR :
539 	    temp->offset = strtol(value[0], NULL, 0);
540 	    if (num_values < 2)
541 	      return (-1);
542 	    else if (length[1] == 1)
543 	      temp->value.charv = (unsigned char)value[1][0];
544 	    else
545 	      temp->value.charv = (unsigned char)strtol(value[1], NULL, 0);
546 
547 	    DEBUG_printf(("1mimeAddTypeRule: CHAR(%d,0x%02x)", temp->offset,
548 	                  temp->value.charv));
549 	    break;
550 	case MIME_MAGIC_SHORT :
551 	    temp->offset       = strtol(value[0], NULL, 0);
552 	    temp->value.shortv = (unsigned short)strtol(value[1], NULL, 0);
553 	    break;
554 	case MIME_MAGIC_INT :
555 	    temp->offset     = strtol(value[0], NULL, 0);
556 	    temp->value.intv = (unsigned)strtol(value[1], NULL, 0);
557 	    break;
558 	case MIME_MAGIC_LOCALE :
559 	    if ((size_t)length[0] > (sizeof(temp->value.localev) - 1))
560 	      return (-1);
561 
562 	    strlcpy(temp->value.localev, value[0], sizeof(temp->value.localev));
563 	    break;
564 	case MIME_MAGIC_CONTAINS :
565 	    temp->offset = strtol(value[0], NULL, 0);
566 	    temp->region = strtol(value[1], NULL, 0);
567 	    if (num_values < 3 || (size_t)length[2] > sizeof(temp->value.stringv))
568 	      return (-1);
569 	    temp->length = length[2];
570 	    memcpy(temp->value.stringv, value[2], (size_t)length[2]);
571 	    break;
572       }
573     }
574     else
575       break;
576   }
577 
578   return (0);
579 }
580 
581 
582 /*
583  * 'mimeFileType()' - Determine the type of a file.
584  */
585 
586 mime_type_t *				/* O - Type of file */
mimeFileType(mime_t * mime,const char * pathname,const char * filename,int * compression)587 mimeFileType(mime_t     *mime,		/* I - MIME database */
588              const char *pathname,	/* I - Name of file to check on disk */
589 	     const char *filename,	/* I - Original filename or NULL */
590 	     int        *compression)	/* O - Is the file compressed? */
591 {
592   _mime_filebuf_t	fb;		/* File buffer */
593   const char		*base;		/* Base filename of file */
594   mime_type_t		*type,		/* File type */
595 			*best;		/* Best match */
596 
597 
598   DEBUG_printf(("mimeFileType(mime=%p, pathname=\"%s\", filename=\"%s\", "
599                 "compression=%p)", mime, pathname, filename, compression));
600 
601  /*
602   * Range check input parameters...
603   */
604 
605   if (!mime || !pathname)
606   {
607     DEBUG_puts("1mimeFileType: Returning NULL.");
608     return (NULL);
609   }
610 
611  /*
612   * Try to open the file...
613   */
614 
615   if ((fb.fp = cupsFileOpen(pathname, "r")) == NULL)
616   {
617     DEBUG_printf(("1mimeFileType: Unable to open \"%s\": %s", pathname,
618                   strerror(errno)));
619     DEBUG_puts("1mimeFileType: Returning NULL.");
620     return (NULL);
621   }
622 
623  /*
624   * Then preload the first MIME_MAX_BUFFER bytes of the file into the file
625   * buffer, returning an error if we can't read anything...
626   */
627 
628   fb.offset = 0;
629   fb.length = (int)cupsFileRead(fb.fp, (char *)fb.buffer, MIME_MAX_BUFFER);
630 
631   if (fb.length <= 0)
632   {
633     DEBUG_printf(("1mimeFileType: Unable to read from \"%s\": %s", pathname, strerror(errno)));
634     DEBUG_puts("1mimeFileType: Returning NULL.");
635 
636     cupsFileClose(fb.fp);
637 
638     return (NULL);
639   }
640 
641  /*
642   * Figure out the base filename (without directory portion)...
643   */
644 
645   if (filename)
646   {
647     if ((base = strrchr(filename, '/')) != NULL)
648       base ++;
649     else
650       base = filename;
651   }
652   else if ((base = strrchr(pathname, '/')) != NULL)
653     base ++;
654   else
655     base = pathname;
656 
657  /*
658   * Then check it against all known types...
659   */
660 
661   for (type = (mime_type_t *)cupsArrayFirst(mime->types), best = NULL;
662        type;
663        type = (mime_type_t *)cupsArrayNext(mime->types))
664     if (mime_check_rules(base, &fb, type->rules))
665     {
666       if (!best || type->priority > best->priority)
667         best = type;
668     }
669 
670  /*
671   * Finally, close the file and return a match (if any)...
672   */
673 
674   if (compression)
675   {
676     *compression = cupsFileCompression(fb.fp);
677     DEBUG_printf(("1mimeFileType: *compression=%d", *compression));
678   }
679 
680   cupsFileClose(fb.fp);
681 
682   DEBUG_printf(("1mimeFileType: Returning %p(%s/%s).", best,
683                 best ? best->super : "???", best ? best->type : "???"));
684   return (best);
685 }
686 
687 
688 /*
689  * 'mimeType()' - Lookup a file type.
690  */
691 
692 mime_type_t *				/* O - Matching file type definition */
mimeType(mime_t * mime,const char * super,const char * type)693 mimeType(mime_t     *mime,		/* I - MIME database */
694          const char *super,		/* I - Super-type name */
695 	 const char *type)		/* I - Type name */
696 {
697   mime_type_t	key,			/* MIME type search key */
698 		*mt;			/* Matching type */
699 
700 
701   DEBUG_printf(("mimeType(mime=%p, super=\"%s\", type=\"%s\")", mime, super,
702                 type));
703 
704  /*
705   * Range check input...
706   */
707 
708   if (!mime || !super || !type)
709   {
710     DEBUG_puts("1mimeType: Returning NULL.");
711     return (NULL);
712   }
713 
714  /*
715   * Lookup the type in the array...
716   */
717 
718   strlcpy(key.super, super, sizeof(key.super));
719   strlcpy(key.type, type, sizeof(key.type));
720 
721   mt = (mime_type_t *)cupsArrayFind(mime->types, &key);
722   DEBUG_printf(("1mimeType: Returning %p.", mt));
723   return (mt);
724 }
725 
726 
727 /*
728  * 'mime_compare_types()' - Compare two MIME super/type names.
729  */
730 
731 static int				/* O - Result of comparison */
mime_compare_types(mime_type_t * t0,mime_type_t * t1)732 mime_compare_types(mime_type_t *t0,	/* I - First type */
733                    mime_type_t *t1)	/* I - Second type */
734 {
735   int	i;				/* Result of comparison */
736 
737 
738   if ((i = _cups_strcasecmp(t0->super, t1->super)) == 0)
739     i = _cups_strcasecmp(t0->type, t1->type);
740 
741   return (i);
742 }
743 
744 
745 /*
746  * 'mime_check_rules()' - Check each rule in a list.
747  */
748 
749 static int				/* O - 1 if match, 0 if no match */
mime_check_rules(const char * filename,_mime_filebuf_t * fb,mime_magic_t * rules)750 mime_check_rules(
751     const char      *filename,		/* I - Filename */
752     _mime_filebuf_t *fb,		/* I - File to check */
753     mime_magic_t    *rules)		/* I - Rules to check */
754 {
755   int		n;			/* Looping var */
756   int		region;			/* Region to look at */
757   int		logic,			/* Logic to apply */
758 		result;			/* Result of test */
759   unsigned	intv;			/* Integer value */
760   short		shortv;			/* Short value */
761   unsigned char	*bufptr;		/* Pointer into buffer */
762 
763 
764   DEBUG_printf(("4mime_check_rules(filename=\"%s\", fb=%p, rules=%p)", filename,
765                 fb, rules));
766 
767   if (rules == NULL)
768     return (0);
769 
770   if (rules->parent == NULL)
771     logic = MIME_MAGIC_OR;
772   else
773     logic = rules->parent->op;
774 
775   result = 0;
776 
777   while (rules != NULL)
778   {
779    /*
780     * Compute the result of this rule...
781     */
782 
783     switch (rules->op)
784     {
785       case MIME_MAGIC_MATCH :
786           result = mime_patmatch(filename, rules->value.matchv);
787 	  break;
788 
789       case MIME_MAGIC_ASCII :
790          /*
791 	  * Load the buffer if necessary...
792 	  */
793 
794           if (fb->offset < 0 || rules->offset < fb->offset ||
795 	      (rules->offset + rules->length) > (fb->offset + fb->length))
796 	  {
797 	   /*
798 	    * Reload file buffer...
799 	    */
800 
801             if (cupsFileSeek(fb->fp, rules->offset) < 0)
802             {
803               fb->length = 0;
804               fb->offset = 0;
805             }
806             else
807             {
808 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
809 	      fb->offset = rules->offset;
810 	    }
811 
812 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_ASCII fb->length=%d", fb->length));
813 	  }
814 
815          /*
816 	  * Test for ASCII printable characters plus standard control chars.
817 	  */
818 
819 	  if ((rules->offset + rules->length) > (fb->offset + fb->length))
820 	    n = fb->offset + fb->length - rules->offset;
821 	  else
822 	    n = rules->length;
823 
824           bufptr = fb->buffer + rules->offset - fb->offset;
825 	  while (n > 0)
826 	    if ((*bufptr >= 32 && *bufptr <= 126) ||
827 	        (*bufptr >= 8 && *bufptr <= 13) ||
828 		*bufptr == 26 || *bufptr == 27)
829 	    {
830 	      n --;
831 	      bufptr ++;
832 	    }
833 	    else
834 	      break;
835 
836 	  result = (n == 0);
837 	  break;
838 
839       case MIME_MAGIC_PRINTABLE :
840          /*
841 	  * Load the buffer if necessary...
842 	  */
843 
844           if (fb->offset < 0 || rules->offset < fb->offset ||
845 	      (rules->offset + rules->length) > (fb->offset + fb->length))
846 	  {
847 	   /*
848 	    * Reload file buffer...
849 	    */
850 
851             if (cupsFileSeek(fb->fp, rules->offset) < 0)
852             {
853               fb->length = 0;
854               fb->offset = 0;
855             }
856             else
857             {
858 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
859 	      fb->offset = rules->offset;
860 	    }
861 
862 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_PRINTABLE fb->length=%d", fb->length));
863 	  }
864 
865          /*
866 	  * Test for 8-bit printable characters plus standard control chars.
867 	  */
868 
869 	  if ((rules->offset + rules->length) > (fb->offset + fb->length))
870 	    n = fb->offset + fb->length - rules->offset;
871 	  else
872 	    n = rules->length;
873 
874           bufptr = fb->buffer + rules->offset - fb->offset;
875 
876 	  while (n > 0)
877 	    if (*bufptr >= 128 ||
878 	        (*bufptr >= 32 && *bufptr <= 126) ||
879 	        (*bufptr >= 8 && *bufptr <= 13) ||
880 		*bufptr == 26 || *bufptr == 27)
881 	    {
882 	      n --;
883 	      bufptr ++;
884 	    }
885 	    else
886 	      break;
887 
888 	  result = (n == 0);
889 	  break;
890 
891       case MIME_MAGIC_REGEX :
892           DEBUG_printf(("5mime_check_rules: regex(%d, \"%s\")", rules->offset,
893 	                rules->value.stringv));
894 
895          /*
896 	  * Load the buffer if necessary...
897 	  */
898 
899           if (fb->offset < 0 || rules->offset < fb->offset ||
900 	      (rules->offset + rules->length) > (fb->offset + fb->length))
901 	  {
902 	   /*
903 	    * Reload file buffer...
904 	    */
905 
906             if (cupsFileSeek(fb->fp, rules->offset) < 0)
907             {
908               fb->length = 0;
909               fb->offset = 0;
910             }
911             else
912             {
913 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
914 	      fb->offset = rules->offset;
915 	    }
916 
917 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_REGEX fb->length=%d", fb->length));
918 
919             DEBUG_printf(("5mime_check_rules: loaded %d byte fb->buffer at %d, starts "
920 	                  "with \"%c%c%c%c\".",
921 	                  fb->length, fb->offset, fb->buffer[0], fb->buffer[1],
922 			  fb->buffer[2], fb->buffer[3]));
923 	  }
924 
925          /*
926 	  * Compare the buffer against the string.  If the file is too
927 	  * short then don't compare - it can't match...
928 	  */
929 
930           if (fb->length > 0)
931           {
932             char temp[MIME_MAX_BUFFER + 1];
933 					/* Temporary buffer */
934 
935             memcpy(temp, fb->buffer, (size_t)fb->length);
936             temp[fb->length] = '\0';
937             result = !regexec(&(rules->value.rev), temp, 0, NULL, 0);
938           }
939 
940           DEBUG_printf(("5mime_check_rules: result=%d", result));
941 	  break;
942 
943       case MIME_MAGIC_STRING :
944           DEBUG_printf(("5mime_check_rules: string(%d, \"%s\")", rules->offset,
945 	                rules->value.stringv));
946 
947          /*
948 	  * Load the buffer if necessary...
949 	  */
950 
951           if (fb->offset < 0 || rules->offset < fb->offset ||
952 	      (rules->offset + rules->length) > (fb->offset + fb->length))
953 	  {
954 	   /*
955 	    * Reload file buffer...
956 	    */
957 
958             if (cupsFileSeek(fb->fp, rules->offset) < 0)
959             {
960               fb->length = 0;
961               fb->offset = 0;
962             }
963             else
964             {
965 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
966 	      fb->offset = rules->offset;
967 	    }
968 
969 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_STRING fb->length=%d", fb->length));
970 
971             DEBUG_printf(("5mime_check_rules: loaded %d byte fb->buffer at %d, starts "
972 	                  "with \"%c%c%c%c\".",
973 	                  fb->length, fb->offset, fb->buffer[0], fb->buffer[1],
974 			  fb->buffer[2], fb->buffer[3]));
975 	  }
976 
977          /*
978 	  * Compare the buffer against the string.  If the file is too
979 	  * short then don't compare - it can't match...
980 	  */
981 
982 	  if ((rules->offset + rules->length) > (fb->offset + fb->length))
983 	    result = 0;
984 	  else
985             result = !memcmp(fb->buffer + rules->offset - fb->offset, rules->value.stringv, (size_t)rules->length);
986           DEBUG_printf(("5mime_check_rules: result=%d", result));
987 	  break;
988 
989       case MIME_MAGIC_ISTRING :
990          /*
991 	  * Load the buffer if necessary...
992 	  */
993 
994           if (fb->offset < 0 || rules->offset < fb->offset ||
995 	      (rules->offset + rules->length) > (fb->offset + fb->length))
996 	  {
997 	   /*
998 	    * Reload file buffer...
999 	    */
1000 
1001             if (cupsFileSeek(fb->fp, rules->offset) < 0)
1002             {
1003               fb->length = 0;
1004               fb->offset = 0;
1005             }
1006             else
1007             {
1008 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
1009 	      fb->offset = rules->offset;
1010 	    }
1011 
1012 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_ISTRING fb->length=%d", fb->length));
1013 	  }
1014 
1015          /*
1016 	  * Compare the buffer against the string.  If the file is too
1017 	  * short then don't compare - it can't match...
1018 	  */
1019 
1020 	  if ((rules->offset + rules->length) > (fb->offset + fb->length))
1021 	    result = 0;
1022 	  else
1023             result = !_cups_strncasecmp((char *)fb->buffer + rules->offset - fb->offset, rules->value.stringv, (size_t)rules->length);
1024 	  break;
1025 
1026       case MIME_MAGIC_CHAR :
1027          /*
1028 	  * Load the buffer if necessary...
1029 	  */
1030 
1031           if (fb->offset < 0 || rules->offset < fb->offset)
1032 	  {
1033 	   /*
1034 	    * Reload file buffer...
1035 	    */
1036 
1037             if (cupsFileSeek(fb->fp, rules->offset) < 0)
1038             {
1039               fb->length = 0;
1040               fb->offset = 0;
1041             }
1042             else
1043             {
1044 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
1045 	      fb->offset = rules->offset;
1046 	    }
1047 
1048 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_CHAR fb->length=%d", fb->length));
1049 	  }
1050 
1051 	 /*
1052 	  * Compare the character values; if the file is too short, it
1053 	  * can't match...
1054 	  */
1055 
1056 	  if (fb->length < 1)
1057 	    result = 0;
1058 	  else
1059 	    result = (fb->buffer[rules->offset - fb->offset] == rules->value.charv);
1060 	  break;
1061 
1062       case MIME_MAGIC_SHORT :
1063          /*
1064 	  * Load the buffer if necessary...
1065 	  */
1066 
1067           if (fb->offset < 0 || rules->offset < fb->offset ||
1068 	      (rules->offset + 2) > (fb->offset + fb->length))
1069 	  {
1070 	   /*
1071 	    * Reload file buffer...
1072 	    */
1073 
1074             if (cupsFileSeek(fb->fp, rules->offset) < 0)
1075             {
1076               fb->length = 0;
1077               fb->offset = 0;
1078             }
1079             else
1080             {
1081 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
1082 	      fb->offset = rules->offset;
1083 	    }
1084 
1085 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_SHORT fb->length=%d", fb->length));
1086 	  }
1087 
1088 	 /*
1089 	  * Compare the short values; if the file is too short, it
1090 	  * can't match...
1091 	  */
1092 
1093 	  if (fb->length < 2)
1094 	  {
1095 	    result = 0;
1096 	  }
1097 	  else
1098 	  {
1099 	    bufptr = fb->buffer + rules->offset - fb->offset;
1100 	    shortv = (short)((bufptr[0] << 8) | bufptr[1]);
1101 	    result = (shortv == rules->value.shortv);
1102 	  }
1103 	  break;
1104 
1105       case MIME_MAGIC_INT :
1106          /*
1107 	  * Load the buffer if necessary...
1108 	  */
1109 
1110           if (fb->offset < 0 || rules->offset < fb->offset ||
1111 	      (rules->offset + 4) > (fb->offset + fb->length))
1112 	  {
1113 	   /*
1114 	    * Reload file buffer...
1115 	    */
1116 
1117             if (cupsFileSeek(fb->fp, rules->offset) < 0)
1118             {
1119               fb->length = 0;
1120               fb->offset = 0;
1121             }
1122             else
1123             {
1124 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
1125 	      fb->offset = rules->offset;
1126 	    }
1127 
1128 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_INT fb->length=%d", fb->length));
1129 	  }
1130 
1131 	 /*
1132 	  * Compare the int values; if the file is too short, it
1133 	  * can't match...
1134 	  */
1135 
1136 	  if (fb->length < 4)
1137 	  {
1138 	    result = 0;
1139 	  }
1140 	  else
1141 	  {
1142 	    bufptr = fb->buffer + rules->offset - fb->offset;
1143 	    intv   = (unsigned)((bufptr[0] << 24) | (bufptr[1] << 16) | (bufptr[2] << 8) | bufptr[3]);
1144 	    result = (intv == rules->value.intv);
1145 	  }
1146 	  break;
1147 
1148       case MIME_MAGIC_LOCALE :
1149 #if defined(_WIN32) || defined(__EMX__) || defined(__APPLE__)
1150           result = !strcmp(rules->value.localev, setlocale(LC_ALL, ""));
1151 #else
1152           result = !strcmp(rules->value.localev, setlocale(LC_MESSAGES, ""));
1153 #endif /* __APPLE__ */
1154 	  break;
1155 
1156       case MIME_MAGIC_CONTAINS :
1157          /*
1158 	  * Load the buffer if necessary...
1159 	  */
1160 
1161           if (fb->offset < 0 || rules->offset < fb->offset ||
1162 	      (rules->offset + rules->region) > (fb->offset + fb->length))
1163 	  {
1164 	   /*
1165 	    * Reload file buffer...
1166 	    */
1167 
1168             if (cupsFileSeek(fb->fp, rules->offset) < 0)
1169             {
1170               fb->length = 0;
1171               fb->offset = 0;
1172             }
1173             else
1174             {
1175 	      fb->length = cupsFileRead(fb->fp, (char *)fb->buffer, sizeof(fb->buffer));
1176 	      fb->offset = rules->offset;
1177 	    }
1178 
1179 	    DEBUG_printf(("4mime_check_rules: MIME_MAGIC_CONTAINS fb->length=%d", fb->length));
1180 	  }
1181 
1182          /*
1183 	  * Compare the buffer against the string.  If the file is too
1184 	  * short then don't compare - it can't match...
1185 	  */
1186 
1187 	  result = 0;
1188 	  if ((rules->offset + rules->length) <= (fb->offset + fb->length))
1189 	  {
1190 	    if (fb->length > rules->region)
1191 	      region = rules->region - rules->length;
1192 	    else
1193 	      region = fb->length - rules->length;
1194 
1195 		  for (n = 0; n < region; n ++)
1196 	      if (!memcmp(fb->buffer + rules->offset - fb->offset + n, rules->value.stringv, (size_t)rules->length))
1197 		    {
1198 		      result = 1;
1199 		      break;
1200 		    }
1201 	  }
1202 	  break;
1203 
1204       default :
1205           if (rules->child != NULL)
1206 	    result = mime_check_rules(filename, fb, rules->child);
1207 	  else
1208 	    result = 0;
1209 	  break;
1210     }
1211 
1212    /*
1213     * If the logic is inverted, invert the result...
1214     */
1215 
1216     if (rules->invert)
1217       result = !result;
1218 
1219    /*
1220     * OK, now if the current logic is OR and this result is true, this
1221     * rule set is true.  If the current logic is AND and this result is false,
1222     * the rule set is false...
1223     */
1224 
1225     DEBUG_printf(("5mime_check_rules: result of test %p (MIME_MAGIC_%s) is %d",
1226                   rules, debug_ops[rules->op], result));
1227 
1228     if ((result && logic == MIME_MAGIC_OR) ||
1229         (!result && logic == MIME_MAGIC_AND))
1230       return (result);
1231 
1232    /*
1233     * Otherwise the jury is still out on this one, so move to the next rule.
1234     */
1235 
1236     rules = rules->next;
1237   }
1238 
1239   return (result);
1240 }
1241 
1242 
1243 /*
1244  * 'mime_patmatch()' - Pattern matching.
1245  */
1246 
1247 static int				/* O - 1 if match, 0 if no match */
mime_patmatch(const char * s,const char * pat)1248 mime_patmatch(const char *s,		/* I - String to match against */
1249               const char *pat)		/* I - Pattern to match against */
1250 {
1251  /*
1252   * Range check the input...
1253   */
1254 
1255   if (s == NULL || pat == NULL)
1256     return (0);
1257 
1258  /*
1259   * Loop through the pattern and match strings, and stop if we come to a
1260   * point where the strings don't match or we find a complete match.
1261   */
1262 
1263   while (*s != '\0' && *pat != '\0')
1264   {
1265     if (*pat == '*')
1266     {
1267      /*
1268       * Wildcard - 0 or more characters...
1269       */
1270 
1271       pat ++;
1272       if (*pat == '\0')
1273         return (1);	/* Last pattern char is *, so everything matches... */
1274 
1275      /*
1276       * Test all remaining combinations until we get to the end of the string.
1277       */
1278 
1279       while (*s != '\0')
1280       {
1281         if (mime_patmatch(s, pat))
1282 	  return (1);
1283 
1284 	s ++;
1285       }
1286     }
1287     else if (*pat == '?')
1288     {
1289      /*
1290       * Wildcard - 1 character...
1291       */
1292 
1293       pat ++;
1294       s ++;
1295       continue;
1296     }
1297     else if (*pat == '[')
1298     {
1299      /*
1300       * Match a character from the input set [chars]...
1301       */
1302 
1303       pat ++;
1304       while (*pat != ']' && *pat != '\0')
1305         if (*s == *pat)
1306 	  break;
1307 	else
1308 	  pat ++;
1309 
1310       if (*pat == ']' || *pat == '\0')
1311         return (0);
1312 
1313       while (*pat != ']' && *pat != '\0')
1314         pat ++;
1315 
1316       if (*pat == ']')
1317         pat ++;
1318 
1319       continue;
1320     }
1321     else if (*pat == '\\')
1322     {
1323      /*
1324       * Handle quoted characters...
1325       */
1326 
1327       pat ++;
1328     }
1329 
1330    /*
1331     * Stop if the pattern and string don't match...
1332     */
1333 
1334     if (*pat++ != *s++)
1335       return (0);
1336   }
1337 
1338  /*
1339   * Done parsing the pattern and string; return 1 if the last character
1340   * matches and 0 otherwise...
1341   */
1342 
1343   return (*s == *pat);
1344 }
1345