Coverage Report

Created: 2025-06-13 06:55

/src/glib/gio/xdgmime/xdgmimecache.c
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: C; c-file-style: "gnu" -*- */
2
/* xdgmimealias.c: Private file.  mmappable caches for mime data
3
 *
4
 * More info can be found at http://www.freedesktop.org/standards/
5
 *
6
 * Copyright (C) 2005  Matthias Clasen <mclasen@redhat.com>
7
 *
8
 * SPDX-License-Identifier: LGPL-2.1-or-later or AFL-2.0
9
 */
10
11
#ifdef HAVE_CONFIG_H
12
#include <config.h>
13
#endif
14
15
#include <stdio.h>
16
#include <stdlib.h>
17
#include <string.h>
18
19
#include <fcntl.h>
20
#include <unistd.h>
21
#include <errno.h>
22
#include <fnmatch.h>
23
#include <assert.h>
24
25
#include <netinet/in.h> /* for ntohl/ntohs */
26
27
#ifdef HAVE_MMAP
28
#include <sys/mman.h>
29
#else
30
#warning Building xdgmime without MMAP support. Binary "mime.cache" files will not be used.
31
#endif
32
33
#include <sys/stat.h>
34
#include <sys/types.h>
35
36
#include "xdgmimecache.h"
37
#include "xdgmimeint.h"
38
39
#ifndef MAX
40
0
#define MAX(a,b) ((a) > (b) ? (a) : (b))
41
#endif
42
43
#ifndef FALSE
44
#define FALSE (0)
45
#endif
46
47
#ifndef TRUE
48
#define TRUE  (!FALSE)
49
#endif
50
51
#ifndef _O_BINARY
52
0
#define _O_BINARY 0
53
#endif
54
55
#ifndef MAP_FAILED
56
#define MAP_FAILED ((void *) -1)
57
#endif
58
59
#ifndef S_ISREG
60
#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
61
#endif
62
63
0
#define MAJOR_VERSION 1
64
0
#define MINOR_VERSION_MIN 1
65
0
#define MINOR_VERSION_MAX 2
66
67
struct _XdgMimeCache
68
{
69
  int ref_count;
70
  int minor;
71
72
  size_t  size;
73
  char   *buffer;
74
};
75
76
0
#define GET_UINT16(cache,offset) (ntohs(*(xdg_uint16_t*)((cache) + (offset))))
77
0
#define GET_UINT32(cache,offset) (ntohl(*(xdg_uint32_t*)((cache) + (offset))))
78
79
// Validates that it is safe to call GET_UINT32() at
80
// cache->buffer[offset + (n * record_size)]. Ensures that offset is aligned to
81
// a 4-byte boundary, and that offset+(n*record_size) does not overflow.
82
// `record_size` values are known constants and never 0.
83
#define OUT_OF_BOUNDS(offset,n,record_size,max) \
84
0
  (((offset) & 0x3) || (offset) > (max) || (n) > ((max) - (offset)) / (record_size))
85
86
XdgMimeCache *
87
_xdg_mime_cache_ref (XdgMimeCache *cache)
88
0
{
89
0
  cache->ref_count++;
90
0
  return cache;
91
0
}
92
93
void
94
_xdg_mime_cache_unref (XdgMimeCache *cache)
95
0
{
96
0
  cache->ref_count--;
97
98
0
  if (cache->ref_count == 0)
99
0
    {
100
0
#ifdef HAVE_MMAP
101
0
      munmap (cache->buffer, cache->size);
102
0
#endif
103
0
      free (cache);
104
0
    }
105
0
}
106
107
XdgMimeCache *
108
_xdg_mime_cache_new_from_file (const char *file_name)
109
0
{
110
0
  XdgMimeCache *cache = NULL;
111
112
0
#ifdef HAVE_MMAP
113
0
  int fd = -1;
114
0
  struct stat st;
115
0
  char *buffer = NULL;
116
0
  int minor;
117
118
  /* Open the file and map it into memory */
119
0
  do {
120
0
    fd = open (file_name, O_RDONLY|_O_BINARY, 0);
121
0
  } while (fd == -1 && errno == EINTR);
122
123
0
  if (fd < 0)
124
0
    return NULL;
125
  
126
  // A valid cache must be at least 40 bytes for the header.
127
0
  if (fstat (fd, &st) < 0 || st.st_size < 40)
128
0
    goto done;
129
130
0
  buffer = (char *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
131
132
0
  if (buffer == MAP_FAILED)
133
0
    goto done;
134
135
0
  minor = GET_UINT16 (buffer, 2);
136
  /* Verify version */
137
0
  if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
138
0
      (minor < MINOR_VERSION_MIN ||
139
0
       minor > MINOR_VERSION_MAX))
140
0
    {
141
0
      munmap (buffer, st.st_size);
142
143
0
      goto done;
144
0
    }
145
  
146
0
  cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache));
147
0
  cache->minor = minor;
148
0
  cache->ref_count = 1;
149
0
  cache->buffer = buffer;
150
0
  cache->size = st.st_size;
151
152
0
 done:
153
0
  if (fd != -1)
154
0
    close (fd);
155
156
#else /* HAVE_MMAP */
157
  cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache));
158
  cache->minor = 0;
159
  cache->ref_count = 1;
160
  cache->buffer = NULL;
161
  cache->size = 0;
162
#endif  /* HAVE_MMAP */
163
164
0
  return cache;
165
0
}
166
167
static int
168
cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, 
169
              xdg_uint32_t  offset,
170
              const void   *data,
171
              size_t        len)
172
0
{
173
0
  xdg_uint32_t range_start = GET_UINT32 (cache->buffer, offset);
174
0
  xdg_uint32_t range_length = GET_UINT32 (cache->buffer, offset + 4);
175
0
  xdg_uint32_t data_length = GET_UINT32 (cache->buffer, offset + 12);
176
0
  xdg_uint32_t data_offset = GET_UINT32 (cache->buffer, offset + 16);
177
0
  xdg_uint32_t mask_offset = GET_UINT32 (cache->buffer, offset + 20);
178
  
179
0
  xdg_uint32_t i, j;
180
181
0
  for (i = range_start; i < range_start + range_length; i++)
182
0
    {
183
0
      int valid_matchlet = TRUE;
184
      
185
0
      if (i + data_length > len)
186
0
  return FALSE;
187
188
0
      if (mask_offset)
189
0
  {
190
0
    for (j = 0; j < data_length; j++)
191
0
      {
192
0
        if ((((unsigned char *)cache->buffer)[data_offset + j] & ((unsigned char *)cache->buffer)[mask_offset + j]) !=
193
0
      ((((unsigned char *) data)[j + i]) & ((unsigned char *)cache->buffer)[mask_offset + j]))
194
0
    {
195
0
      valid_matchlet = FALSE;
196
0
      break;
197
0
    }
198
0
      }
199
0
  }
200
0
      else
201
0
  {
202
0
    valid_matchlet = memcmp(cache->buffer + data_offset, (unsigned char *)data + i, data_length) == 0;
203
0
  }
204
205
0
      if (valid_matchlet)
206
0
  return TRUE;
207
0
    }
208
  
209
0
  return FALSE;  
210
0
}
211
212
static int
213
cache_magic_matchlet_compare (XdgMimeCache *cache, 
214
            xdg_uint32_t  offset,
215
            const void   *data,
216
            size_t        len)
217
0
{
218
0
  xdg_uint32_t n_children = GET_UINT32 (cache->buffer, offset + 24);
219
0
  xdg_uint32_t child_offset = GET_UINT32 (cache->buffer, offset + 28);
220
0
  if (OUT_OF_BOUNDS (child_offset, n_children, 32, cache->size))
221
0
    return FALSE;
222
223
0
  xdg_uint32_t i;
224
  
225
0
  if (cache_magic_matchlet_compare_to_data (cache, offset, data, len))
226
0
    {
227
0
      if (n_children == 0)
228
0
  return TRUE;
229
      
230
0
      for (i = 0; i < n_children; i++)
231
0
  {
232
0
    if (cache_magic_matchlet_compare (cache, child_offset + 32 * i,
233
0
              data, len))
234
0
      return TRUE;
235
0
  }
236
0
    }
237
  
238
0
  return FALSE;  
239
0
}
240
241
static const char *
242
cache_magic_compare_to_data (XdgMimeCache *cache, 
243
           xdg_uint32_t  offset,
244
           const void   *data, 
245
           size_t        len, 
246
           int          *prio)
247
0
{
248
0
  xdg_uint32_t priority = GET_UINT32 (cache->buffer, offset);
249
0
  xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, offset + 4);
250
0
  xdg_uint32_t n_matchlets = GET_UINT32 (cache->buffer, offset + 8);
251
0
  xdg_uint32_t matchlet_offset = GET_UINT32 (cache->buffer, offset + 12);
252
0
  if (OUT_OF_BOUNDS (matchlet_offset, n_matchlets, 32, cache->size))
253
0
    return NULL;
254
255
0
  xdg_uint32_t i;
256
257
0
  for (i = 0; i < n_matchlets; i++)
258
0
    {
259
0
      if (cache_magic_matchlet_compare (cache, matchlet_offset + i * 32, 
260
0
          data, len))
261
0
  {
262
0
    *prio = priority;
263
    
264
0
    return cache->buffer + mimetype_offset;
265
0
  }
266
0
    }
267
268
0
  return NULL;
269
0
}
270
271
static const char *
272
cache_magic_lookup_data (XdgMimeCache *cache, 
273
       const void   *data, 
274
       size_t        len, 
275
       int          *prio)
276
0
{
277
0
  xdg_uint32_t list_offset;
278
0
  xdg_uint32_t n_entries;
279
0
  xdg_uint32_t offset;
280
281
0
  xdg_uint32_t j;
282
283
0
  *prio = 0;
284
285
0
  list_offset = GET_UINT32 (cache->buffer, 24);
286
0
  if (OUT_OF_BOUNDS (list_offset, 1, 12, cache->size))
287
0
    return NULL;
288
289
0
  n_entries = GET_UINT32 (cache->buffer, list_offset);
290
0
  offset = GET_UINT32 (cache->buffer, list_offset + 8);
291
0
  if (OUT_OF_BOUNDS (offset, n_entries, 16, cache->size))
292
0
    return NULL;
293
  
294
0
  for (j = 0; j < n_entries; j++)
295
0
    {
296
0
      const char *match;
297
298
0
      match = cache_magic_compare_to_data (cache, offset + 16 * j, 
299
0
             data, len, prio);
300
0
      if (match)
301
0
  return match;
302
0
    }
303
304
0
  return NULL;
305
0
}
306
307
static const char *
308
cache_alias_lookup (const char *alias)
309
0
{
310
0
  const char *ptr;
311
0
  int i, min, max, mid, cmp;
312
313
0
  for (i = 0; _caches[i]; i++)
314
0
    {
315
0
      XdgMimeCache *cache = _caches[i];
316
0
      xdg_uint32_t list_offset;
317
0
      xdg_uint32_t n_entries;
318
0
      xdg_uint32_t offset;
319
320
0
      if (cache->buffer == NULL)
321
0
        continue;
322
323
0
      list_offset = GET_UINT32 (cache->buffer, 4);
324
0
      if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
325
0
        continue;
326
327
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
328
0
      if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
329
0
        continue;
330
331
0
      min = 0; 
332
0
      max = n_entries - 1;
333
0
      while (max >= min) 
334
0
  {
335
0
    mid = (min + max) / 2;
336
337
0
    offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid);
338
0
    ptr = cache->buffer + offset;
339
0
    cmp = strcmp (ptr, alias);
340
    
341
0
    if (cmp < 0)
342
0
      min = mid + 1;
343
0
    else if (cmp > 0)
344
0
      max = mid - 1;
345
0
    else
346
0
      {
347
0
        offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4);
348
0
        return cache->buffer + offset;
349
0
      }
350
0
  }
351
0
    }
352
353
0
  return NULL;
354
0
}
355
356
typedef struct {
357
  const char *mime;
358
  int weight;
359
} MimeWeight;
360
361
static int
362
cache_glob_lookup_literal (const char *file_name,
363
         const char *mime_types[],
364
         int         n_mime_types,
365
         int         case_sensitive_check)
366
0
{
367
0
  const char *ptr;
368
0
  int i, min, max, mid, cmp;
369
370
0
  assert (n_mime_types > 0);
371
372
0
  for (i = 0; _caches[i]; i++)
373
0
    {
374
0
      XdgMimeCache *cache = _caches[i];
375
0
      xdg_uint32_t list_offset;
376
0
      xdg_uint32_t n_entries;
377
0
      xdg_uint32_t offset;
378
379
0
      if (cache->buffer == NULL)
380
0
        continue;
381
382
0
      list_offset = GET_UINT32 (cache->buffer, 12);
383
0
      if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
384
0
        continue;
385
386
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
387
0
      if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 12, cache->size))
388
0
        continue;
389
390
0
      min = 0; 
391
0
      max = n_entries - 1;
392
0
      while (max >= min) 
393
0
  {
394
0
    mid = (min + max) / 2;
395
396
0
    offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid);
397
0
    ptr = cache->buffer + offset;
398
0
    cmp = strcmp (ptr, file_name);
399
400
0
    if (cmp < 0)
401
0
      min = mid + 1;
402
0
    else if (cmp > 0)
403
0
      max = mid - 1;
404
0
    else
405
0
      {
406
0
        int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 8);
407
0
        int case_sensitive = weight & 0x100;
408
0
        weight = weight & 0xff;
409
410
0
        if (case_sensitive_check || !case_sensitive)
411
0
    {
412
0
      offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 4);
413
0
      mime_types[0] = (const char *)(cache->buffer + offset);
414
415
0
      return 1;
416
0
    }
417
0
        return 0;
418
0
      }
419
0
  }
420
0
    }
421
422
0
  return 0;
423
0
}
424
425
static int
426
cache_glob_lookup_fnmatch (const char *file_name,
427
         MimeWeight  mime_types[],
428
         int         n_mime_types,
429
         int         case_sensitive_check)
430
0
{
431
0
  const char *mime_type;
432
0
  const char *ptr;
433
434
0
  int i, n;
435
0
  xdg_uint32_t j;
436
437
0
  n = 0;
438
0
  for (i = 0; _caches[i]; i++)
439
0
    {
440
0
      XdgMimeCache *cache = _caches[i];
441
442
0
      xdg_uint32_t list_offset;
443
0
      xdg_uint32_t n_entries;
444
445
0
      if (cache->buffer == NULL)
446
0
        continue;
447
448
0
      list_offset = GET_UINT32 (cache->buffer, 20);
449
0
      if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
450
0
        continue;
451
452
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
453
0
      if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 12, cache->size))
454
0
        continue;
455
456
0
      for (j = 0; j < n_entries && n < n_mime_types; j++)
457
0
  {
458
0
    xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j);
459
0
    xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 4);
460
0
    int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 8);
461
0
    int case_sensitive = weight & 0x100;
462
0
    weight = weight & 0xff;
463
0
    ptr = cache->buffer + offset;
464
0
    mime_type = cache->buffer + mimetype_offset;
465
0
    if (case_sensitive_check || !case_sensitive)
466
0
      {
467
        /* FIXME: Not UTF-8 safe */
468
0
        if (fnmatch (ptr, file_name, 0) == 0)
469
0
          {
470
0
            mime_types[n].mime = mime_type;
471
0
            mime_types[n].weight = weight;
472
0
            n++;
473
0
          }
474
0
      }
475
0
  }
476
477
0
      if (n == n_mime_types)
478
0
  break;
479
0
    }
480
481
0
  return n;
482
0
}
483
484
static int
485
cache_glob_node_lookup_suffix (XdgMimeCache  *cache,
486
             xdg_uint32_t   n_entries,
487
             xdg_uint32_t   offset,
488
             const char    *file_name,
489
             int            len,
490
             int            case_sensitive_check,
491
             MimeWeight     mime_types[],
492
             int            n_mime_types)
493
0
{
494
0
  xdg_unichar_t character;
495
0
  xdg_unichar_t match_char;
496
0
  xdg_uint32_t mimetype_offset;
497
0
  xdg_uint32_t n_children;
498
0
  xdg_uint32_t child_offset; 
499
0
  int weight;
500
0
  int case_sensitive;
501
502
0
  xdg_uint32_t i;
503
0
  int min, max, mid, n;
504
505
0
  character = file_name[len - 1];
506
507
0
  assert (character != 0);
508
509
0
  min = 0;
510
0
  max = n_entries - 1;
511
0
  while (max >= min)
512
0
    {
513
0
      mid = (min + max) /  2;
514
0
      match_char = GET_UINT32 (cache->buffer, offset + 12 * mid);
515
0
      if (match_char < character)
516
0
  min = mid + 1;
517
0
      else if (match_char > character)
518
0
  max = mid - 1;
519
0
      else 
520
0
  {
521
0
          len--;
522
0
          n = 0;
523
0
          n_children = GET_UINT32 (cache->buffer, offset + 12 * mid + 4);
524
0
          child_offset = GET_UINT32 (cache->buffer, offset + 12 * mid + 8);
525
0
          if (OUT_OF_BOUNDS (child_offset, n_children, 12, cache->size))
526
0
            continue;
527
      
528
0
          if (len > 0)
529
0
            {
530
0
              n = cache_glob_node_lookup_suffix (cache, 
531
0
                                                 n_children, child_offset,
532
0
                                                 file_name, len, 
533
0
                                                 case_sensitive_check,
534
0
                                                 mime_types,
535
0
                                                 n_mime_types);
536
0
            }
537
0
          if (n == 0)
538
0
            {
539
0
        i = 0;
540
0
        while (n < n_mime_types && i < n_children)
541
0
    {
542
0
      match_char = GET_UINT32 (cache->buffer, child_offset + 12 * i);
543
0
      if (match_char != 0)
544
0
        break;
545
546
0
      mimetype_offset = GET_UINT32 (cache->buffer, child_offset + 12 * i + 4);
547
0
      weight = GET_UINT32 (cache->buffer, child_offset + 12 * i + 8);
548
0
      case_sensitive = weight & 0x100;
549
0
      weight = weight & 0xff;
550
551
0
      if (case_sensitive_check || !case_sensitive)
552
0
        {
553
0
          mime_types[n].mime = cache->buffer + mimetype_offset;
554
0
          mime_types[n].weight = weight;
555
0
          n++;
556
0
        }
557
0
      i++;
558
0
    }
559
0
      }
560
0
    return n;
561
0
  }
562
0
    }
563
0
  return 0;
564
0
}
565
566
static int
567
cache_glob_lookup_suffix (const char *file_name,
568
        int         len,
569
        int         ignore_case,
570
        MimeWeight  mime_types[],
571
        int         n_mime_types)
572
0
{
573
0
  int i, n;
574
575
0
  n = 0;
576
0
  for (i = 0; _caches[i]; i++)
577
0
    {
578
0
      XdgMimeCache *cache = _caches[i];
579
580
0
      xdg_uint32_t list_offset;
581
0
      xdg_uint32_t n_entries;
582
0
      xdg_uint32_t offset;
583
584
0
      if (cache->buffer == NULL)
585
0
        continue;
586
587
0
      list_offset = GET_UINT32 (cache->buffer, 16);
588
0
      if (OUT_OF_BOUNDS (list_offset, 1, 8, cache->size))
589
0
        continue;
590
591
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
592
0
      offset = GET_UINT32 (cache->buffer, list_offset + 4);
593
0
      if (OUT_OF_BOUNDS (offset, n_entries, 12, cache->size))
594
0
        continue;
595
596
0
      n += cache_glob_node_lookup_suffix (cache,
597
0
            n_entries, offset,
598
0
            file_name, len,
599
0
            ignore_case,
600
0
            mime_types + n,
601
0
            n_mime_types - n);
602
0
      if (n == n_mime_types)
603
0
  break;
604
0
    }
605
606
0
  return n;
607
0
}
608
609
static int compare_mime_weight (const void *a, const void *b)
610
0
{
611
0
  const MimeWeight *aa = (const MimeWeight *)a;
612
0
  const MimeWeight *bb = (const MimeWeight *)b;
613
614
0
  return bb->weight - aa->weight;
615
0
}
616
617
0
#define ISUPPER(c)    ((c) >= 'A' && (c) <= 'Z')
618
static char *
619
ascii_tolower (const char *str)
620
0
{
621
0
  char *p, *lower;
622
623
0
  lower = strdup (str);
624
0
  p = lower;
625
0
  while (*p != 0)
626
0
    {
627
0
      char c = *p;
628
0
      *p++ = ISUPPER (c) ? c - 'A' + 'a' : c;
629
0
    }
630
0
  return lower;
631
0
}
632
633
static int
634
filter_out_dupes (MimeWeight mimes[], int n_mimes)
635
0
{
636
0
  int last;
637
0
  int i, j;
638
639
0
  last = n_mimes;
640
641
0
  for (i = 0; i < last; i++)
642
0
    {
643
0
      j = i + 1;
644
0
      while (j < last)
645
0
        {
646
0
          if (strcmp (mimes[i].mime, mimes[j].mime) == 0)
647
0
            {
648
0
              mimes[i].weight = MAX (mimes[i].weight, mimes[j].weight);
649
0
              last--;
650
0
              mimes[j].mime = mimes[last].mime;
651
0
              mimes[j].weight = mimes[last].weight;
652
0
            }
653
0
          else
654
0
            j++;
655
0
        }
656
0
    }
657
658
0
  return last;
659
0
}
660
661
static int
662
cache_glob_lookup_file_name (const char *file_name,
663
           const char *mime_types[],
664
           int         n_mime_types)
665
0
{
666
0
  int n;
667
0
  MimeWeight mimes[10];
668
0
  int n_mimes = 10;
669
0
  int i;
670
0
  int len;
671
0
  char *lower_case;
672
673
0
  assert (file_name != NULL && n_mime_types > 0);
674
675
  /* First, check the literals */
676
677
0
  lower_case = ascii_tolower (file_name);
678
679
0
  n = cache_glob_lookup_literal (lower_case, mime_types, n_mime_types, FALSE);
680
0
  if (n > 0)
681
0
    {
682
0
      free (lower_case);
683
0
      return n;
684
0
    }
685
686
0
  n = cache_glob_lookup_literal (file_name, mime_types, n_mime_types, TRUE);
687
0
  if (n > 0)
688
0
    {
689
0
      free (lower_case);
690
0
      return n;
691
0
    }
692
693
0
  len = strlen (file_name);
694
0
  n = cache_glob_lookup_suffix (lower_case, len, FALSE, mimes, n_mimes);
695
0
  if (n < 2)
696
0
    n += cache_glob_lookup_suffix (file_name, len, TRUE, mimes + n, n_mimes - n);
697
698
  /* Last, try fnmatch */
699
0
  if (n == 0)
700
0
    n = cache_glob_lookup_fnmatch (lower_case, mimes, n_mimes, FALSE);
701
0
  if (n < 2)
702
0
    n += cache_glob_lookup_fnmatch (file_name, mimes + n, n_mimes - n, TRUE);
703
704
0
  n = filter_out_dupes (mimes, n);
705
706
0
  free (lower_case);
707
708
0
  qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight);
709
710
0
  if (n_mime_types < n)
711
0
    n = n_mime_types;
712
713
0
  for (i = 0; i < n; i++)
714
0
    mime_types[i] = mimes[i].mime;
715
716
0
  return n;
717
0
}
718
719
int
720
_xdg_mime_cache_get_max_buffer_extents (void)
721
0
{
722
0
  xdg_uint32_t offset;
723
0
  xdg_uint32_t max_extent;
724
0
  int i;
725
726
0
  max_extent = 0;
727
0
  for (i = 0; _caches[i]; i++)
728
0
    {
729
0
      XdgMimeCache *cache = _caches[i];
730
731
0
      if (cache->buffer == NULL)
732
0
        continue;
733
734
0
      offset = GET_UINT32 (cache->buffer, 24);
735
0
      if (OUT_OF_BOUNDS (offset, 1, 8, cache->size))
736
0
        continue;
737
738
0
      max_extent = MAX (max_extent, GET_UINT32 (cache->buffer, offset + 4));
739
0
    }
740
741
0
  return max_extent;
742
0
}
743
744
static const char *
745
cache_get_mime_type_for_data (const void *data,
746
            size_t      len,
747
            int        *result_prio,
748
            const char *mime_types[],
749
            int         n_mime_types)
750
0
{
751
0
  const char *mime_type;
752
0
  int i, n, priority;
753
754
0
  priority = 0;
755
0
  mime_type = NULL;
756
0
  for (i = 0; _caches[i]; i++)
757
0
    {
758
0
      XdgMimeCache *cache = _caches[i];
759
760
0
      int prio;
761
0
      const char *match;
762
763
0
      if (cache->buffer == NULL)
764
0
        continue;
765
766
0
      match = cache_magic_lookup_data (cache, data, len, &prio);
767
0
      if (prio > priority)
768
0
  {
769
0
    priority = prio;
770
0
    mime_type = match;
771
0
  }
772
0
    }
773
774
0
  if (result_prio)
775
0
    *result_prio = priority;
776
777
0
  if (priority > 0)
778
0
    {
779
      /* Pick glob-result R where mime_type inherits from R */
780
0
      for (n = 0; n < n_mime_types; n++)
781
0
        {
782
0
          if (mime_types[n] && _xdg_mime_cache_mime_type_subclass (mime_types[n], mime_type, NULL))
783
0
            return mime_types[n];
784
0
        }
785
0
      if (n == 0)
786
0
        {
787
          /* No globs: return magic match */
788
0
          return mime_type;
789
0
        }
790
0
    }
791
792
  /* Pick first glob result, as fallback */
793
0
  for (n = 0; n < n_mime_types; n++)
794
0
    {
795
0
      if (mime_types[n])
796
0
        return mime_types[n];
797
0
    }
798
799
0
  return NULL;
800
0
}
801
802
const char *
803
_xdg_mime_cache_get_mime_type_for_data (const void *data,
804
          size_t      len,
805
          int        *result_prio)
806
0
{
807
0
  return cache_get_mime_type_for_data (data, len, result_prio, NULL, 0);
808
0
}
809
810
const char *
811
_xdg_mime_cache_get_mime_type_for_file (const char  *file_name,
812
          struct stat *statbuf)
813
0
{
814
0
  const char *mime_type;
815
0
  const char *mime_types[10];
816
0
  FILE *file;
817
0
  unsigned char *data;
818
0
  int max_extent;
819
0
  int bytes_read;
820
0
  struct stat buf;
821
0
  const char *base_name;
822
0
  int n;
823
824
0
  if (file_name == NULL)
825
0
    return NULL;
826
827
0
  if (! _xdg_utf8_validate (file_name))
828
0
    return NULL;
829
830
0
  base_name = _xdg_get_base_name (file_name);
831
0
  n = cache_glob_lookup_file_name (base_name, mime_types, 10);
832
833
0
  if (n == 1)
834
0
    return mime_types[0];
835
836
0
  if (!statbuf)
837
0
    {
838
0
      if (stat (file_name, &buf) != 0)
839
0
  return XDG_MIME_TYPE_UNKNOWN;
840
841
0
      statbuf = &buf;
842
0
    }
843
844
0
  if (statbuf->st_size == 0)
845
0
    return XDG_MIME_TYPE_EMPTY;
846
847
0
  if (!S_ISREG (statbuf->st_mode))
848
0
    return XDG_MIME_TYPE_UNKNOWN;
849
850
  /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
851
   * be large and need getting from a stream instead of just reading it all
852
   * in. */
853
0
  max_extent = _xdg_mime_cache_get_max_buffer_extents ();
854
0
  data = malloc (max_extent);
855
0
  if (data == NULL)
856
0
    return XDG_MIME_TYPE_UNKNOWN;
857
        
858
0
  file = fopen (file_name, "r");
859
0
  if (file == NULL)
860
0
    {
861
0
      free (data);
862
0
      return XDG_MIME_TYPE_UNKNOWN;
863
0
    }
864
865
0
  bytes_read = fread (data, 1, max_extent, file);
866
0
  if (ferror (file))
867
0
    {
868
0
      free (data);
869
0
      fclose (file);
870
0
      return XDG_MIME_TYPE_UNKNOWN;
871
0
    }
872
873
0
  mime_type = cache_get_mime_type_for_data (data, bytes_read, NULL,
874
0
              mime_types, n);
875
876
0
  if (!mime_type)
877
0
    mime_type = _xdg_binary_or_text_fallback (data, bytes_read);
878
879
0
  free (data);
880
0
  fclose (file);
881
882
0
  return mime_type;
883
0
}
884
885
const char *
886
_xdg_mime_cache_get_mime_type_from_file_name (const char *file_name)
887
0
{
888
0
  const char *mime_type;
889
890
0
  if (cache_glob_lookup_file_name (file_name, &mime_type, 1))
891
0
    return mime_type;
892
0
  else
893
0
    return XDG_MIME_TYPE_UNKNOWN;
894
0
}
895
896
int
897
_xdg_mime_cache_get_mime_types_from_file_name (const char *file_name,
898
                 const char  *mime_types[],
899
                 int          n_mime_types)
900
0
{
901
0
  return cache_glob_lookup_file_name (file_name, mime_types, n_mime_types);
902
0
}
903
904
#if 1
905
static int
906
ends_with (const char *str,
907
           const char *suffix)
908
0
{
909
0
  int length;
910
0
  int suffix_length;
911
912
0
  length = strlen (str);
913
0
  suffix_length = strlen (suffix);
914
0
  if (length < suffix_length)
915
0
    return 0;
916
917
0
  if (strcmp (str + length - suffix_length, suffix) == 0)
918
0
    return 1;
919
920
0
  return 0;
921
0
}
922
923
static int
924
is_super_type (const char *mime)
925
0
{
926
0
  return ends_with (mime, "/*");
927
0
}
928
#endif
929
930
int
931
_xdg_mime_cache_mime_type_subclass (const char *mime,
932
            const char *base,
933
            const char ***seen)
934
0
{
935
0
  const char *umime, *ubase, *parent;
936
0
  const char **first_seen = NULL, **new_seen;
937
938
0
  xdg_uint32_t j;
939
0
  int i, k, min, max, med, cmp, ret = 0;
940
941
0
  umime = _xdg_mime_cache_unalias_mime_type (mime);
942
0
  ubase = _xdg_mime_cache_unalias_mime_type (base);
943
944
0
  if (strcmp (umime, ubase) == 0)
945
0
    return 1;
946
947
  /* We really want to handle text/ * in GtkFileFilter, so we just
948
   * turn on the supertype matching
949
   */
950
0
#if 1
951
  /* Handle supertypes */
952
0
  if (is_super_type (ubase) &&
953
0
      xdg_mime_media_type_equal (umime, ubase))
954
0
    return 1;
955
0
#endif
956
957
  /*  Handle special cases text/plain and application/octet-stream */
958
0
  if (strcmp (ubase, "text/plain") == 0 && 
959
0
      strncmp (umime, "text/", 5) == 0)
960
0
    return 1;
961
962
0
  if (strcmp (ubase, "application/octet-stream") == 0 &&
963
0
      strncmp (umime, "inode/", 6) != 0)
964
0
    return 1;
965
966
0
  if (!seen)
967
0
    {
968
0
      first_seen = calloc (1, sizeof (char *));
969
0
      seen = &first_seen;
970
0
    }
971
972
0
  for (i = 0; _caches[i]; i++)
973
0
    {
974
0
      XdgMimeCache *cache = _caches[i];
975
0
      xdg_uint32_t list_offset;
976
0
      xdg_uint32_t n_entries;
977
0
      xdg_uint32_t offset, n_parents, parent_offset;
978
979
0
      if (cache->buffer == NULL)
980
0
        continue;
981
982
0
      list_offset = GET_UINT32 (cache->buffer, 8);
983
0
      if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
984
0
        continue;
985
986
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
987
0
      if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
988
0
        continue;
989
990
0
      min = 0; 
991
0
      max = n_entries - 1;
992
0
      while (max >= min)
993
0
  {
994
0
    med = (min + max)/2;
995
    
996
0
    offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med);
997
0
    cmp = strcmp (cache->buffer + offset, umime);
998
0
    if (cmp < 0)
999
0
      min = med + 1;
1000
0
    else if (cmp > 0)
1001
0
      max = med - 1;
1002
0
    else
1003
0
      {
1004
0
        offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med + 4);
1005
0
        n_parents = GET_UINT32 (cache->buffer, offset);
1006
        
1007
0
        for (j = 0; j < n_parents; j++)
1008
0
    {
1009
0
      parent_offset = GET_UINT32 (cache->buffer, offset + 4 + 4 * j);
1010
0
      parent = cache->buffer + parent_offset;
1011
1012
      /* Detect and avoid buggy circular relationships */
1013
0
      for (k = 0; (*seen)[k] != NULL; k++)
1014
0
        if (parent == (*seen)[k])
1015
0
          goto next_parent;
1016
0
      new_seen = realloc (*seen, (k + 2) * sizeof (char *));
1017
0
      if (!new_seen)
1018
0
        goto done;
1019
0
      new_seen[k] = parent;
1020
0
      new_seen[k + 1] = NULL;
1021
0
      *seen = new_seen;
1022
1023
0
      if (_xdg_mime_cache_mime_type_subclass (parent, ubase, seen))
1024
0
        {
1025
0
          ret = 1;
1026
0
          goto done;
1027
0
        }
1028
1029
0
    next_parent:
1030
0
      continue;
1031
0
    }
1032
1033
0
        break;
1034
0
      }
1035
0
  }
1036
0
    }
1037
1038
0
done:
1039
0
  free (first_seen);
1040
0
  return ret;
1041
0
}
1042
1043
const char *
1044
_xdg_mime_cache_unalias_mime_type (const char *mime)
1045
0
{
1046
0
  const char *lookup;
1047
  
1048
0
  lookup = cache_alias_lookup (mime);
1049
  
1050
0
  if (lookup)
1051
0
    return lookup;
1052
  
1053
0
  return mime;  
1054
0
}
1055
1056
char **
1057
_xdg_mime_cache_list_mime_parents (const char *mime)
1058
0
{
1059
0
  int i, l, p;
1060
0
  xdg_uint32_t j, k;
1061
0
  char *all_parents[128]; /* we'll stop at 128 */ 
1062
0
  char **result;
1063
1064
0
  mime = xdg_mime_unalias_mime_type (mime);
1065
1066
0
  p = 0;
1067
0
  for (i = 0; _caches[i]; i++)
1068
0
    {
1069
0
      XdgMimeCache *cache = _caches[i];
1070
0
      xdg_uint32_t list_offset;
1071
0
      xdg_uint32_t n_entries;
1072
1073
0
      if (cache->buffer == NULL)
1074
0
        continue;
1075
1076
0
      list_offset = GET_UINT32 (cache->buffer, 8);
1077
0
      if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
1078
0
        continue;
1079
1080
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
1081
0
      if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
1082
0
        continue;
1083
1084
0
      for (j = 0; j < n_entries; j++)
1085
0
  {
1086
0
    xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j);
1087
0
    xdg_uint32_t parents_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j + 4);
1088
1089
0
    if (strcmp (cache->buffer + mimetype_offset, mime) == 0)
1090
0
      {
1091
0
        xdg_uint32_t parent_mime_offset;
1092
0
        xdg_uint32_t n_parents = GET_UINT32 (cache->buffer, parents_offset);
1093
1094
0
        for (k = 0; k < n_parents && p < 127; k++)
1095
0
    {
1096
0
      parent_mime_offset = GET_UINT32 (cache->buffer, parents_offset + 4 + 4 * k);
1097
1098
      /* Don't add same parent multiple times.
1099
       * This can happen for instance if the same type is listed in multiple directories
1100
       */
1101
0
      for (l = 0; l < p; l++)
1102
0
        {
1103
0
          if (strcmp (all_parents[l], cache->buffer + parent_mime_offset) == 0)
1104
0
      break;
1105
0
        }
1106
1107
0
      if (l == p)
1108
0
        all_parents[p++] = cache->buffer + parent_mime_offset;
1109
0
    }
1110
1111
0
        break;
1112
0
      }
1113
0
  }
1114
0
    }
1115
0
  all_parents[p++] = NULL;
1116
  
1117
0
  result = (char **) malloc (p * sizeof (char *));
1118
0
  memcpy (result, all_parents, p * sizeof (char *));
1119
1120
0
  return result;
1121
0
}
1122
1123
static const char *
1124
cache_lookup_icon (const char *mime, size_t header)
1125
0
{
1126
0
  const char *ptr;
1127
0
  int i, min, max, mid, cmp;
1128
1129
0
  for (i = 0; _caches[i]; i++)
1130
0
    {
1131
0
      XdgMimeCache *cache = _caches[i];
1132
0
      xdg_uint32_t list_offset;
1133
0
      xdg_uint32_t n_entries;
1134
0
      xdg_uint32_t offset;
1135
1136
0
      if (cache->buffer == NULL)
1137
0
        continue;
1138
1139
0
      if (OUT_OF_BOUNDS (header, 1, 4, cache->size))
1140
0
        continue;
1141
1142
0
      list_offset = GET_UINT32 (cache->buffer, header);
1143
0
      if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
1144
0
        continue;
1145
1146
0
      n_entries = GET_UINT32 (cache->buffer, list_offset);
1147
0
      if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
1148
0
        continue;
1149
1150
0
      min = 0; 
1151
0
      max = n_entries - 1;
1152
0
      while (max >= min) 
1153
0
        {
1154
0
          mid = (min + max) / 2;
1155
1156
0
          offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid);
1157
0
          ptr = cache->buffer + offset;
1158
0
          cmp = strcmp (ptr, mime);
1159
         
1160
0
          if (cmp < 0)
1161
0
            min = mid + 1;
1162
0
          else if (cmp > 0)
1163
0
            max = mid - 1;
1164
0
          else
1165
0
            {
1166
0
              offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4);
1167
0
              return cache->buffer + offset;
1168
0
            }
1169
0
        }
1170
0
    }
1171
1172
0
  return NULL;
1173
0
}
1174
1175
const char *
1176
_xdg_mime_cache_get_generic_icon (const char *mime)
1177
0
{
1178
0
  return cache_lookup_icon (mime, 36);
1179
0
}
1180
1181
const char *
1182
_xdg_mime_cache_get_icon (const char *mime)
1183
0
{
1184
0
  return cache_lookup_icon (mime, 32);
1185
0
}
1186
1187
static void
1188
dump_glob_node (XdgMimeCache *cache,
1189
    xdg_uint32_t  offset,
1190
    int           depth)
1191
0
{
1192
0
  xdg_unichar_t character;
1193
0
  xdg_uint32_t mime_offset;
1194
0
  xdg_uint32_t n_children;
1195
0
  xdg_uint32_t child_offset;
1196
0
  xdg_uint32_t k;
1197
0
  int i;
1198
1199
0
  character = GET_UINT32 (cache->buffer, offset);
1200
0
  mime_offset = GET_UINT32 (cache->buffer, offset + 4);
1201
0
  n_children = GET_UINT32 (cache->buffer, offset + 8);
1202
0
  child_offset = GET_UINT32 (cache->buffer, offset + 12);
1203
0
  if (OUT_OF_BOUNDS (child_offset, n_children, 20, cache->size))
1204
0
    return;
1205
1206
0
  for (i = 0; i < depth; i++)
1207
0
    printf (" ");
1208
0
  printf ("%c", character);
1209
0
  if (mime_offset)
1210
0
    printf (" - %s", cache->buffer + mime_offset);
1211
0
  printf ("\n");
1212
0
  if (child_offset)
1213
0
  {
1214
0
    for (k = 0; k < n_children; k++)
1215
0
      dump_glob_node (cache, child_offset + 20 * k, depth + 1);
1216
0
  }
1217
0
}
1218
1219
void
1220
_xdg_mime_cache_glob_dump (void)
1221
0
{
1222
0
  xdg_uint32_t i, j;
1223
0
  for (i = 0; _caches[i]; i++)
1224
0
  {
1225
0
    XdgMimeCache *cache = _caches[i];
1226
0
    xdg_uint32_t list_offset;
1227
0
    xdg_uint32_t n_entries;
1228
0
    xdg_uint32_t offset;
1229
1230
0
    if (cache->buffer == NULL)
1231
0
      continue;
1232
1233
0
    list_offset = GET_UINT32 (cache->buffer, 16);
1234
0
    if (OUT_OF_BOUNDS (list_offset, 1, 8, cache->size))
1235
0
      return;
1236
1237
0
    n_entries = GET_UINT32 (cache->buffer, list_offset);
1238
0
    offset = GET_UINT32 (cache->buffer, list_offset + 4);
1239
0
    if (OUT_OF_BOUNDS (offset, n_entries, 20, cache->size))
1240
0
      return;
1241
1242
0
    for (j = 0; j < n_entries; j++)
1243
0
      dump_glob_node (cache, offset + 20 * j, 0);
1244
0
  }
1245
0
}
1246
1247