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