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