2 * "$Id: type.c 9793 2011-05-20 03:49:49Z mike $"
4 * MIME typing routines for CUPS.
6 * Copyright 2007-2011 by Apple Inc.
7 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
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/".
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.
27 * Include necessary headers...
30 #include <cups/string-private.h>
31 #include <cups/debug-private.h>
40 typedef struct _mime_filebuf_s /**** File buffer for MIME typing ****/
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 */
53 static int mime_compare_types(mime_type_t *t0, mime_type_t *t1);
54 static int mime_check_rules(const char *filename, _mime_filebuf_t *fb,
56 static int mime_patmatch(const char *s, const char *pat);
64 static const char * const debug_ops[] =
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 */
84 * 'mimeAddType()' - Add a MIME type to a database.
87 mime_type_t * /* O - New (or existing) MIME type */
88 mimeAddType(mime_t *mime, /* I - MIME database */
89 const char *super, /* I - Super-type name */
90 const char *type) /* I - Type name */
92 mime_type_t *temp; /* New MIME type */
93 size_t typelen; /* Length of type name */
96 DEBUG_printf(("mimeAddType(mime=%p, super=\"%s\", type=\"%s\")", mime, super,
100 * Range check input...
103 if (!mime || !super || !type)
105 DEBUG_puts("1mimeAddType: Returning NULL (bad arguments).");
110 * See if the type already exists; if so, return the existing type...
113 if ((temp = mimeType(mime, super, type)) != NULL)
115 DEBUG_printf(("1mimeAddType: Returning %p (existing).", temp));
120 * The type doesn't exist; add it...
124 mime->types = cupsArrayNew((cups_array_func_t)mime_compare_types, NULL);
128 DEBUG_puts("1mimeAddType: Returning NULL (no types).");
132 typelen = strlen(type) + 1;
134 if ((temp = calloc(1, sizeof(mime_type_t) - MIME_MAX_TYPE + typelen)) == NULL)
136 DEBUG_puts("1mimeAddType: Returning NULL (out of memory).");
140 strlcpy(temp->super, super, sizeof(temp->super));
141 memcpy(temp->type, type, typelen);
142 temp->priority = 100;
144 cupsArrayAdd(mime->types, temp);
146 DEBUG_printf(("1mimeAddType: Returning %p (new).", temp));
152 * 'mimeAddTypeRule()' - Add a detection rule for a file type.
155 int /* O - 0 on success, -1 on failure */
156 mimeAddTypeRule(mime_type_t *mt, /* I - Type to add to */
157 const char *rule) /* I - Rule to add */
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 */
172 DEBUG_printf(("mimeAddTypeRule(mt=%p(%s/%s), rule=\"%s\")", mt,
173 mt ? mt->super : "???", mt ? mt->type : "???", rule));
176 * Range check input...
183 * Find the last rule in the top-level of the rules tree.
186 for (current = mt->rules; current; current = current->next)
191 * Parse the rules string. Most rules are either a file extension or a
192 * comparison function:
195 * function(parameters)
198 logic = MIME_MAGIC_NOP;
201 while (*rule != '\0')
203 while (isspace(*rule & 255))
208 DEBUG_puts("1mimeAddTypeRule: New parenthesis group");
209 logic = MIME_MAGIC_NOP;
212 else if (*rule == ')')
214 DEBUG_puts("1mimeAddTypeRule: Close paren...");
215 if (current == NULL || current->parent == NULL)
218 current = current->parent;
220 if (current->parent == NULL)
221 logic = MIME_MAGIC_OR;
223 logic = current->parent->op;
227 else if (*rule == '+' && current != NULL)
229 if (logic != MIME_MAGIC_AND &&
230 current != NULL && current->prev != NULL)
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...
237 if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
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;
246 current->prev = NULL;
247 current->parent = temp;
249 DEBUG_printf(("1mimeAddTypeRule: Creating new AND group %p.", temp));
251 else if (current->parent)
253 DEBUG_printf(("1mimeAddTypeRule: Setting group %p op to AND.",
255 current->parent->op = MIME_MAGIC_AND;
258 logic = MIME_MAGIC_AND;
261 else if (*rule == ',')
263 if (logic != MIME_MAGIC_OR && current != NULL)
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.
270 if (current->parent == NULL)
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.
277 if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
280 DEBUG_printf(("1mimeAddTypeRule: Creating new AND group %p inside OR "
283 while (current->prev != NULL)
285 current->parent = temp;
286 current = current->prev;
289 current->parent = temp;
290 temp->op = MIME_MAGIC_AND;
291 temp->child = current;
293 mt->rules = current = temp;
298 * This isn't the top rule, so go up one level...
301 DEBUG_puts("1mimeAddTypeRule: Going up one level.");
302 current = current->parent;
306 logic = MIME_MAGIC_OR;
309 else if (*rule == '!')
311 DEBUG_puts("1mimeAddTypeRule: NOT");
315 else if (isalnum(*rule & 255))
318 * Read an extension name or a function...
322 while (isalnum(*rule & 255) && (ptr - name) < (sizeof(name) - 1))
330 * Read function parameters...
335 num_values < (sizeof(value) / sizeof(value[0]));
338 ptr = value[num_values];
340 while ((ptr - value[num_values]) < (sizeof(value[0]) - 1) &&
341 *rule != '\0' && *rule != ',' && *rule != ')')
343 if (isspace(*rule & 255))
346 * Ignore whitespace...
352 else if (*rule == '\"' || *rule == '\'')
355 * Copy quoted strings literally...
360 while (*rule != '\0' && *rule != quote &&
361 (ptr - value[num_values]) < (sizeof(value[0]) - 1))
369 else if (*rule == '<')
373 while (*rule != '>' && *rule != '\0' &&
374 (ptr - value[num_values]) < (sizeof(value[0]) - 1))
376 if (isxdigit(rule[0] & 255) && isxdigit(rule[1] & 255))
379 *ptr = (*rule++ - '0') << 4;
381 *ptr = (tolower(*rule++) - 'a' + 10) << 4;
384 *ptr++ |= *rule++ - '0';
386 *ptr++ |= tolower(*rule++) - 'a' + 10;
402 length[num_values] = ptr - value[num_values];
419 * Figure out the function...
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"))
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)
444 mt->priority = atoi(value[0]);
453 * This is just a filename match on the extension...
456 snprintf(value[0], sizeof(value[0]), "*.%s", name);
457 length[0] = strlen(value[0]);
458 op = MIME_MAGIC_MATCH;
462 * Add a rule for this operation.
465 if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
468 temp->invert = invert;
471 temp->parent = current->parent;
472 current->next = temp;
477 temp->prev = current;
479 if (logic == MIME_MAGIC_NOP)
482 * Add parenthetical grouping...
485 DEBUG_printf(("1mimeAddTypeRule: Making new OR group %p for "
486 "parenthesis.", temp));
488 temp->op = MIME_MAGIC_OR;
490 if ((temp->child = calloc(1, sizeof(mime_magic_t))) == NULL)
493 temp->child->parent = temp;
494 temp->child->invert = temp->invert;
498 logic = MIME_MAGIC_OR;
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));
506 * Fill in data for the rule...
515 case MIME_MAGIC_MATCH :
516 if (length[0] > (sizeof(temp->value.matchv) - 1))
518 strcpy(temp->value.matchv, value[0]);
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;
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))
532 temp->length = length[1];
533 memcpy(temp->value.stringv, value[1], length[1]);
535 case MIME_MAGIC_CHAR :
536 temp->offset = strtol(value[0], NULL, 0);
538 temp->value.charv = value[1][0];
540 temp->value.charv = (unsigned char)strtol(value[1], NULL, 0);
542 DEBUG_printf(("1mimeAddTypeRule: CHAR(%d,0x%02x)", temp->offset,
545 case MIME_MAGIC_SHORT :
546 temp->offset = strtol(value[0], NULL, 0);
547 temp->value.shortv = (unsigned short)strtol(value[1], NULL, 0);
549 case MIME_MAGIC_INT :
550 temp->offset = strtol(value[0], NULL, 0);
551 temp->value.intv = (unsigned)strtol(value[1], NULL, 0);
553 case MIME_MAGIC_LOCALE :
554 if (length[0] > (sizeof(temp->value.localev) - 1))
557 strcpy(temp->value.localev, value[0]);
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))
564 temp->length = length[2];
565 memcpy(temp->value.stringv, value[2], length[2]);
578 * 'mimeFileType()' - Determine the type of a file.
581 mime_type_t * /* O - Type of file */
582 mimeFileType(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? */
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 */
593 DEBUG_printf(("mimeFileType(mime=%p, pathname=\"%s\", filename=\"%s\", "
594 "compression=%p)", mime, pathname, filename, compression));
597 * Range check input parameters...
600 if (!mime || !pathname)
602 DEBUG_puts("1mimeFileType: Returning NULL.");
607 * Try to open the file...
610 if ((fb.fp = cupsFileOpen(pathname, "r")) == NULL)
612 DEBUG_printf(("1mimeFileType: Unable to open \"%s\": %s", pathname,
614 DEBUG_puts("1mimeFileType: Returning NULL.");
622 * Figure out the base filename (without directory portion)...
627 if ((base = strrchr(filename, '/')) != NULL)
632 else if ((base = strrchr(pathname, '/')) != NULL)
638 * Then check it against all known types...
641 for (type = (mime_type_t *)cupsArrayFirst(mime->types), best = NULL;
643 type = (mime_type_t *)cupsArrayNext(mime->types))
644 if (mime_check_rules(base, &fb, type->rules))
646 if (!best || type->priority > best->priority)
651 * Finally, close the file and return a match (if any)...
656 *compression = cupsFileCompression(fb.fp);
657 DEBUG_printf(("1mimeFileType: *compression=%d", *compression));
660 cupsFileClose(fb.fp);
662 DEBUG_printf(("1mimeFileType: Returning %p(%s/%s).", best,
663 best ? best->super : "???", best ? best->type : "???"));
669 * 'mimeType()' - Lookup a file type.
672 mime_type_t * /* O - Matching file type definition */
673 mimeType(mime_t *mime, /* I - MIME database */
674 const char *super, /* I - Super-type name */
675 const char *type) /* I - Type name */
677 mime_type_t key, /* MIME type search key */
678 *mt; /* Matching type */
681 DEBUG_printf(("mimeType(mime=%p, super=\"%s\", type=\"%s\")", mime, super,
685 * Range check input...
688 if (!mime || !super || !type)
690 DEBUG_puts("1mimeType: Returning NULL.");
695 * Lookup the type in the array...
698 strlcpy(key.super, super, sizeof(key.super));
699 strlcpy(key.type, type, sizeof(key.type));
701 mt = (mime_type_t *)cupsArrayFind(mime->types, &key);
702 DEBUG_printf(("1mimeType: Returning %p.", mt));
708 * 'mime_compare_types()' - Compare two MIME super/type names.
711 static int /* O - Result of comparison */
712 mime_compare_types(mime_type_t *t0, /* I - First type */
713 mime_type_t *t1) /* I - Second type */
715 int i; /* Result of comparison */
718 if ((i = _cups_strcasecmp(t0->super, t1->super)) == 0)
719 i = _cups_strcasecmp(t0->type, t1->type);
726 * 'mime_check_rules()' - Check each rule in a list.
729 static int /* O - 1 if match, 0 if no match */
731 const char *filename, /* I - Filename */
732 _mime_filebuf_t *fb, /* I - File to check */
733 mime_magic_t *rules) /* I - Rules to check */
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 */
744 DEBUG_printf(("4mime_check_rules(filename=\"%s\", fb=%p, rules=%p)", filename,
750 if (rules->parent == NULL)
751 logic = MIME_MAGIC_OR;
753 logic = rules->parent->op;
757 while (rules != NULL)
760 * Compute the result of this rule...
765 case MIME_MAGIC_MATCH :
766 result = mime_patmatch(filename, rules->value.matchv);
769 case MIME_MAGIC_ASCII :
771 * Load the buffer if necessary...
774 if (fb->offset < 0 || rules->offset < fb->offset ||
775 (rules->offset + rules->length) > (fb->offset + fb->length))
778 * Reload file buffer...
781 cupsFileSeek(fb->fp, rules->offset);
782 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
784 fb->offset = rules->offset;
788 * Test for ASCII printable characters plus standard control chars.
791 if ((rules->offset + rules->length) > (fb->offset + fb->length))
792 n = fb->offset + fb->length - rules->offset;
796 bufptr = fb->buffer + rules->offset - fb->offset;
798 if ((*bufptr >= 32 && *bufptr <= 126) ||
799 (*bufptr >= 8 && *bufptr <= 13) ||
800 *bufptr == 26 || *bufptr == 27)
811 case MIME_MAGIC_PRINTABLE :
813 * Load the buffer if necessary...
816 if (fb->offset < 0 || rules->offset < fb->offset ||
817 (rules->offset + rules->length) > (fb->offset + fb->length))
820 * Reload file buffer...
823 cupsFileSeek(fb->fp, rules->offset);
824 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
826 fb->offset = rules->offset;
830 * Test for 8-bit printable characters plus standard control chars.
833 if ((rules->offset + rules->length) > (fb->offset + fb->length))
834 n = fb->offset + fb->length - rules->offset;
838 bufptr = fb->buffer + rules->offset - fb->offset;
841 if (*bufptr >= 128 ||
842 (*bufptr >= 32 && *bufptr <= 126) ||
843 (*bufptr >= 8 && *bufptr <= 13) ||
844 *bufptr == 26 || *bufptr == 27)
855 case MIME_MAGIC_STRING :
856 DEBUG_printf(("5mime_check_rules: string(%d, \"%s\")", rules->offset,
857 rules->value.stringv));
860 * Load the buffer if necessary...
863 if (fb->offset < 0 || rules->offset < fb->offset ||
864 (rules->offset + rules->length) > (fb->offset + fb->length))
867 * Reload file buffer...
870 cupsFileSeek(fb->fp, rules->offset);
871 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
873 fb->offset = rules->offset;
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]));
882 * Compare the buffer against the string. If the file is too
883 * short then don't compare - it can't match...
886 if ((rules->offset + rules->length) > (fb->offset + fb->length))
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));
894 case MIME_MAGIC_ISTRING :
896 * Load the buffer if necessary...
899 if (fb->offset < 0 || rules->offset < fb->offset ||
900 (rules->offset + rules->length) > (fb->offset + fb->length))
903 * Reload file buffer...
906 cupsFileSeek(fb->fp, rules->offset);
907 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
909 fb->offset = rules->offset;
913 * Compare the buffer against the string. If the file is too
914 * short then don't compare - it can't match...
917 if ((rules->offset + rules->length) > (fb->offset + fb->length))
920 result = (_cups_strncasecmp((char *)fb->buffer + rules->offset -
922 rules->value.stringv, rules->length) == 0);
925 case MIME_MAGIC_CHAR :
927 * Load the buffer if necessary...
930 if (fb->offset < 0 || rules->offset < fb->offset)
933 * Reload file buffer...
936 cupsFileSeek(fb->fp, rules->offset);
937 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
939 fb->offset = rules->offset;
943 * Compare the character values; if the file is too short, it
950 result = (fb->buffer[rules->offset - fb->offset] ==
954 case MIME_MAGIC_SHORT :
956 * Load the buffer if necessary...
959 if (fb->offset < 0 || rules->offset < fb->offset ||
960 (rules->offset + 2) > (fb->offset + fb->length))
963 * Reload file buffer...
966 cupsFileSeek(fb->fp, rules->offset);
967 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
969 fb->offset = rules->offset;
973 * Compare the short values; if the file is too short, it
981 bufptr = fb->buffer + rules->offset - fb->offset;
982 shortv = (bufptr[0] << 8) | bufptr[1];
983 result = (shortv == rules->value.shortv);
987 case MIME_MAGIC_INT :
989 * Load the buffer if necessary...
992 if (fb->offset < 0 || rules->offset < fb->offset ||
993 (rules->offset + 4) > (fb->offset + fb->length))
996 * Reload file buffer...
999 cupsFileSeek(fb->fp, rules->offset);
1000 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
1001 sizeof(fb->buffer));
1002 fb->offset = rules->offset;
1006 * Compare the int values; if the file is too short, it
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);
1021 case MIME_MAGIC_LOCALE :
1022 #if defined(WIN32) || defined(__EMX__) || defined(__APPLE__)
1023 result = (strcmp(rules->value.localev,
1024 setlocale(LC_ALL, "")) == 0);
1026 result = (strcmp(rules->value.localev,
1027 setlocale(LC_MESSAGES, "")) == 0);
1028 #endif /* __APPLE__ */
1031 case MIME_MAGIC_CONTAINS :
1033 * Load the buffer if necessary...
1036 if (fb->offset < 0 || rules->offset < fb->offset ||
1037 (rules->offset + rules->region) > (fb->offset + fb->length))
1040 * Reload file buffer...
1043 cupsFileSeek(fb->fp, rules->offset);
1044 fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
1045 sizeof(fb->buffer));
1046 fb->offset = rules->offset;
1050 * Compare the buffer against the string. If the file is too
1051 * short then don't compare - it can't match...
1054 if ((rules->offset + rules->length) > (fb->offset + fb->length))
1058 if (fb->length > rules->region)
1059 region = rules->region - rules->length;
1061 region = fb->length - rules->length;
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)
1072 if (rules->child != NULL)
1073 result = mime_check_rules(filename, fb, rules->child);
1080 * If the logic is inverted, invert the result...
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...
1092 DEBUG_printf(("5mime_check_rules: result of test %p (MIME_MAGIC_%s) is %d",
1093 rules, debug_ops[rules->op], result));
1095 if ((result && logic == MIME_MAGIC_OR) ||
1096 (!result && logic == MIME_MAGIC_AND))
1100 * Otherwise the jury is still out on this one, so move to the next rule.
1103 rules = rules->next;
1111 * 'mime_patmatch()' - Pattern matching.
1114 static int /* O - 1 if match, 0 if no match */
1115 mime_patmatch(const char *s, /* I - String to match against */
1116 const char *pat) /* I - Pattern to match against */
1119 * Range check the input...
1122 if (s == NULL || pat == NULL)
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.
1130 while (*s != '\0' && *pat != '\0')
1135 * Wildcard - 0 or more characters...
1140 return (1); /* Last pattern char is *, so everything matches... */
1143 * Test all remaining combinations until we get to the end of the string.
1148 if (mime_patmatch(s, pat))
1154 else if (*pat == '?')
1157 * Wildcard - 1 character...
1164 else if (*pat == '[')
1167 * Match a character from the input set [chars]...
1171 while (*pat != ']' && *pat != '\0')
1177 if (*pat == ']' || *pat == '\0')
1180 while (*pat != ']' && *pat != '\0')
1188 else if (*pat == '\\')
1191 * Handle quoted characters...
1198 * Stop if the pattern and string don't match...
1206 * Done parsing the pattern and string; return 1 if the last character
1207 * matches and 0 otherwise...
1210 return (*s == *pat);
1215 * End of "$Id: type.c 9793 2011-05-20 03:49:49Z mike $".