1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #include <ctype.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8 #include <unistd.h>
9 #include <fnmatch.h>
10
11 #include <Ecore.h>
12 #include <Ecore_File.h>
13
14 /* define macros and variable for using the eina logging system */
15 #define EFREET_MODULE_LOG_DOM _efreet_mime_log_dom
16 static int _efreet_mime_log_dom = -1;
17
18 #include "Efreet.h"
19 #include "Efreet_Mime.h"
20 #include "efreet_private.h"
21
22 static Eina_List *magics = NULL; // contains Efreet_Mime_Magic structs
23 static Eina_Hash *mime_icons = NULL; // contains cache with mime->icons
24 static Eina_Inlist *mime_icons_lru = NULL;
25 static unsigned int _efreet_mime_init_count = 0;
26
27 static const char *_mime_inode_symlink = NULL;
28 static const char *_mime_inode_fifo = NULL;
29 static const char *_mime_inode_chardevice = NULL;
30 static const char *_mime_inode_blockdevice = NULL;
31 static const char *_mime_inode_socket = NULL;
32 static const char *_mime_inode_mountpoint = NULL;
33 static const char *_mime_inode_directory = NULL;
34 static const char *_mime_application_x_executable = NULL;
35 static const char *_mime_application_octet_stream = NULL;
36 static const char *_mime_text_plain = NULL;
37
38 /**
39 * @internal
40 * @brief Holds whether we are big/little endian
41 * @note This is set during efreet_mime_init based on
42 * a runtime check.
43 */
44 static enum
45 {
46 EFREET_ENDIAN_BIG = 0,
47 EFREET_ENDIAN_LITTLE = 1
48 } efreet_mime_endianess = EFREET_ENDIAN_BIG;
49
50 /*
51 * Buffer sized used for magic checks. The default is good enough for the
52 * current set of magic rules. This setting is only here for the future.
53 */
54 #define EFREET_MIME_MAGIC_BUFFER_SIZE 512
55
56 /*
57 * Minimum timeout in seconds between mime-icons cache flush.
58 */
59 #define EFREET_MIME_ICONS_FLUSH_TIMEOUT 60
60
61 /*
62 * Timeout in seconds, when older mime-icons items are expired.
63 */
64 #define EFREET_MIME_ICONS_EXPIRE_TIMEOUT 600
65
66 /*
67 * mime-icons maximum population.
68 */
69 #define EFREET_MIME_ICONS_MAX_POPULATION 512
70
71 /*
72 * If defined, dump mime-icons statistics after flush.
73 */
74 //#define EFREET_MIME_ICONS_DEBUG
75
76 typedef struct Efreet_Mime_Magic Efreet_Mime_Magic;
77 struct Efreet_Mime_Magic
78 {
79 unsigned int priority;
80 const char *mime;
81 Eina_List *entries;
82 };
83
84 typedef struct Efreet_Mime_Magic_Entry Efreet_Mime_Magic_Entry;
85 struct Efreet_Mime_Magic_Entry
86 {
87 unsigned int indent;
88 unsigned int offset;
89 unsigned int word_size;
90 unsigned int range_len;
91 unsigned short value_len;
92 char *mask;
93 char *value;
94 };
95
96 typedef struct Efreet_Mime_Icon_Entry_Head Efreet_Mime_Icon_Entry_Head;
97 struct Efreet_Mime_Icon_Entry_Head
98 {
99 EINA_INLIST; /* node of mime_icons_lru */
100 Eina_Inlist *list;
101 const char *mime;
102 double timestamp;
103 };
104
105 typedef struct Efreet_Mime_Icon_Entry Efreet_Mime_Icon_Entry;
106 struct Efreet_Mime_Icon_Entry
107 {
108 EINA_INLIST;
109 const char *icon;
110 const char *theme;
111 unsigned int size;
112 };
113
114 static void efreet_mime_shared_mimeinfo_magic_load(const char *file);
115 static void efreet_mime_shared_mimeinfo_magic_parse(char *data, int size);
116 static const char *efreet_mime_magic_check_priority(const char *file,
117 unsigned int start,
118 unsigned int end);
119 static int efreet_mime_init_files(void);
120 static const char *efreet_mime_special_check(const char *file);
121 static const char *efreet_mime_fallback_check(const char *file);
122 static void efreet_mime_magic_free(void *data);
123 static void efreet_mime_magic_entry_free(void *data);
124 static int efreet_mime_glob_match(const char *str, const char *glob);
125 static int efreet_mime_glob_case_match(char *str, const char *glob);
126 static int efreet_mime_endian_check(void);
127
128 static void efreet_mime_icons_flush(double now);
129 static void efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry);
130 static void efreet_mime_icon_entry_add(const char *mime,
131 const char *icon,
132 const char *theme,
133 unsigned int size);
134 static const char *efreet_mime_icon_entry_find(const char *mime,
135 const char *theme,
136 unsigned int size);
137 static void efreet_mime_icons_debug(void);
138
139
140
141
142
143
144
145
146
147
148
149
150 static Eina_File *mimedb = NULL;
151 static unsigned char *mimedb_ptr = NULL;
152 static size_t mimedb_size = 0;
153
154 static void
_efreet_mimedb_shutdown(void)155 _efreet_mimedb_shutdown(void)
156 {
157 if (mimedb)
158 {
159 if (mimedb_ptr) eina_file_map_free(mimedb, mimedb_ptr);
160 eina_file_close(mimedb);
161 mimedb = NULL;
162 mimedb_ptr = NULL;
163 mimedb_size = 0;
164 }
165 }
166
167 static void
_efreet_mimedb_update(void)168 _efreet_mimedb_update(void)
169 {
170 char buf[PATH_MAX];
171
172 if (mimedb)
173 {
174 if (mimedb_ptr) eina_file_map_free(mimedb, mimedb_ptr);
175 eina_file_close(mimedb);
176 mimedb = NULL;
177 mimedb_ptr = NULL;
178 mimedb_size = 0;
179 }
180 #ifdef WORDS_BIGENDIAN
181 snprintf(buf, sizeof(buf), "%s/efreet/mime_cache_%s.be.dat",
182 efreet_cache_home_get(), efreet_hostname_get());
183 #else
184 snprintf(buf, sizeof(buf), "%s/efreet/mime_cache_%s.le.dat",
185 efreet_cache_home_get(), efreet_hostname_get());
186 #endif
187 mimedb = eina_file_open(buf, EINA_FALSE);
188 if (mimedb)
189 {
190 mimedb_ptr = eina_file_map_all(mimedb, EINA_FILE_POPULATE);
191 if (mimedb_ptr)
192 {
193 mimedb_size = eina_file_size_get(mimedb);
194 if ((mimedb_size >= (16 + 4 + 4 + 4) &&
195 (!strncmp((char *)mimedb_ptr, "EfrEeT-MiMeS-001", 16))))
196 {
197 // load ok - magic fine. more sanity checks?
198 }
199 else
200 {
201 eina_file_map_free(mimedb, mimedb_ptr);
202 mimedb_ptr = NULL;
203 eina_file_close(mimedb);
204 mimedb = NULL;
205 mimedb_size = 0;
206 }
207 }
208 else
209 {
210 eina_file_close(mimedb);
211 mimedb = NULL;
212 }
213 }
214 }
215
216 static const char *
_efreet_mimedb_str_get(unsigned int offset)217 _efreet_mimedb_str_get(unsigned int offset)
218 {
219 if (offset < (16 + 4 + 4 + 4)) return NULL;
220 if (offset >= mimedb_size) return NULL;
221 return (const char *)(mimedb_ptr + offset);
222 }
223
224 static unsigned int
_efreet_mimedb_uint_get(unsigned int index)225 _efreet_mimedb_uint_get(unsigned int index)
226 // index is the unit NUMBER AFTER the header
227 {
228 unsigned int *ptr;
229 ptr = ((unsigned int *)(mimedb_ptr + 16)) + index;
230 if ((size_t)(((unsigned char *)ptr) - mimedb_ptr) >= (mimedb_size - 4))
231 return 0;
232 return *ptr;
233 }
234
235 static unsigned int
_efreet_mimedb_mime_count(void)236 _efreet_mimedb_mime_count(void)
237 {
238 return _efreet_mimedb_uint_get(0);
239 }
240
241 /**** currently unused - here for symmetry and future use
242 static const char *
243 _efreet_mimedb_mime_get(unsigned int num)
244 {
245 unsigned int offset = _efreet_mimedb_uint_get
246 (1 + num);
247 return _efreet_mimedb_str_get(offset);
248 }
249 */
250
251 static unsigned int
_efreet_mimedb_extn_count(void)252 _efreet_mimedb_extn_count(void)
253 {
254 return _efreet_mimedb_uint_get(1 + _efreet_mimedb_mime_count());
255 }
256
257 static const char *
_efreet_mimedb_extn_get(unsigned int num)258 _efreet_mimedb_extn_get(unsigned int num)
259 {
260 unsigned int offset = _efreet_mimedb_uint_get
261 (1 + _efreet_mimedb_mime_count() + 1 + (num * 2));
262 return _efreet_mimedb_str_get(offset);
263 }
264
265 static const char *
_efreet_mimedb_extn_mime_get(unsigned int num)266 _efreet_mimedb_extn_mime_get(unsigned int num)
267 {
268 unsigned int offset = _efreet_mimedb_uint_get
269 (1 + _efreet_mimedb_mime_count() + 1 + (num * 2) + 1);
270 return _efreet_mimedb_str_get(offset);
271 }
272
273 static const char *
_efreet_mimedb_extn_find(const char * extn)274 _efreet_mimedb_extn_find(const char *extn)
275 {
276 unsigned int i, begin, end;
277 const char *s;
278
279 // binary search keys to get value
280 begin = 0;
281 end = _efreet_mimedb_extn_count();
282 i = (begin + end) / 2;
283 for (;;)
284 {
285 s = _efreet_mimedb_extn_get(i);
286 if (s)
287 {
288 int v = strcmp(extn, s);
289 if (v < 0)
290 {
291 end = i;
292 i = (begin + end) / 2;
293 if ((end - begin) == 0) break;
294 }
295 else if (v > 0)
296 {
297 if ((end - begin) > 1)
298 {
299 begin = i;
300 i = (begin + end) / 2;
301 if (i == end) break;
302 }
303 else
304 {
305 if ((end - begin) == 0) break;
306 begin = end;
307 i = end;
308 }
309 }
310 else if (v == 0)
311 return _efreet_mimedb_extn_mime_get(i);
312 }
313 else
314 {
315 break;
316 }
317 }
318 return NULL;
319 }
320
321 static unsigned int
_efreet_mimedb_glob_count(void)322 _efreet_mimedb_glob_count(void)
323 {
324 return _efreet_mimedb_uint_get
325 (1 + _efreet_mimedb_mime_count() +
326 1 + (_efreet_mimedb_extn_count() * 2));
327 }
328
329 static const char *
_efreet_mimedb_glob_get(unsigned int num)330 _efreet_mimedb_glob_get(unsigned int num)
331 {
332 unsigned int offset = _efreet_mimedb_uint_get
333 (1 + _efreet_mimedb_mime_count() +
334 1 + (_efreet_mimedb_extn_count() * 2) +
335 1 + (num * 2));
336 return _efreet_mimedb_str_get(offset);
337 }
338
339 static const char *
_efreet_mimedb_glob_mime_get(unsigned int num)340 _efreet_mimedb_glob_mime_get(unsigned int num)
341 {
342 unsigned int offset = _efreet_mimedb_uint_get
343 (1 + _efreet_mimedb_mime_count() +
344 1 + (_efreet_mimedb_extn_count() * 2) +
345 1 + (num * 2) + 1);
346 return _efreet_mimedb_str_get(offset);
347 }
348
349 /** --------------------------------- **/
350
351 int
efreet_internal_mime_init(void)352 efreet_internal_mime_init(void)
353 {
354 if (++_efreet_mime_init_count != 1)
355 return _efreet_mime_init_count;
356
357 _efreet_mime_log_dom = eina_log_domain_register
358 ("efreet_mime", EFREET_DEFAULT_LOG_COLOR);
359
360 if (_efreet_mime_log_dom < 0)
361 {
362 EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_mime.");
363 goto shutdown_efreet;
364 }
365
366 efreet_mime_endianess = efreet_mime_endian_check();
367 efreet_mime_type_cache_clear();
368
369 _efreet_mimedb_update();
370
371 if (!efreet_mime_init_files())
372 goto unregister_log_domain;
373
374 _efreet_mime_update_func = _efreet_mimedb_update;
375
376 return _efreet_mime_init_count;
377
378 unregister_log_domain:
379 eina_log_domain_unregister(_efreet_mime_log_dom);
380 _efreet_mime_log_dom = -1;
381 shutdown_efreet:
382 return --_efreet_mime_init_count;
383 }
384
385 int
efreet_internal_mime_shutdown(void)386 efreet_internal_mime_shutdown(void)
387 {
388 if (_efreet_mime_init_count == 0)
389 {
390 EINA_LOG_ERR("Init count not greater than 0 in shutdown.");
391 return 0;
392 }
393 if (--_efreet_mime_init_count != 0)
394 return _efreet_mime_init_count;
395
396 _efreet_mimedb_shutdown();
397 _efreet_mime_update_func = NULL;
398
399 efreet_mime_icons_debug();
400
401 IF_RELEASE(_mime_inode_symlink);
402 IF_RELEASE(_mime_inode_fifo);
403 IF_RELEASE(_mime_inode_chardevice);
404 IF_RELEASE(_mime_inode_blockdevice);
405 IF_RELEASE(_mime_inode_socket);
406 IF_RELEASE(_mime_inode_mountpoint);
407 IF_RELEASE(_mime_inode_directory);
408 IF_RELEASE(_mime_application_x_executable);
409 IF_RELEASE(_mime_application_octet_stream);
410 IF_RELEASE(_mime_text_plain);
411
412 IF_FREE_LIST(magics, efreet_mime_magic_free);
413 IF_FREE_HASH(mime_icons);
414 eina_log_domain_unregister(_efreet_mime_log_dom);
415 _efreet_mime_log_dom = -1;
416
417 return _efreet_mime_init_count;
418 }
419
420 EAPI int
efreet_mime_init(void)421 efreet_mime_init(void)
422 {
423 return efreet_init();
424 }
425
426 EAPI int
efreet_mime_shutdown(void)427 efreet_mime_shutdown(void)
428 {
429 return efreet_shutdown();
430 }
431
432 EAPI const char *
efreet_mime_type_get(const char * file)433 efreet_mime_type_get(const char *file)
434 {
435 const char *type = NULL;
436
437 EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
438 if (!mimedb_ptr) return NULL;
439
440 if ((type = efreet_mime_special_check(file)))
441 return type;
442
443 /* Check magics with priority > 80 */
444 if ((type = efreet_mime_magic_check_priority(file, 0, 80)))
445 return type;
446
447 /* Check globs */
448 if ((type = efreet_mime_globs_type_get(file)))
449 return type;
450
451 /* Check rest of magics */
452 if ((type = efreet_mime_magic_check_priority(file, 80, 0)))
453 return type;
454
455 return efreet_mime_fallback_check(file);
456 }
457
458 EAPI const char *
efreet_mime_type_icon_get(const char * mime,const char * theme,unsigned int size)459 efreet_mime_type_icon_get(const char *mime, const char *theme, unsigned int size)
460 {
461 const char *icon = NULL;
462 char *data;
463 Eina_List *icons = NULL;
464 const char *env = NULL;
465 char *p = NULL, *pp = NULL, *ppp = NULL;
466 char buf[PATH_MAX];
467 const char *cache;
468
469 EINA_SAFETY_ON_NULL_RETURN_VAL(mime, NULL);
470 EINA_SAFETY_ON_NULL_RETURN_VAL(theme, NULL);
471
472 mime = eina_stringshare_add(mime);
473 theme = eina_stringshare_add(theme);
474 cache = efreet_mime_icon_entry_find(mime, theme, size);
475 if (cache)
476 {
477 eina_stringshare_del(mime);
478 eina_stringshare_del(theme);
479 return cache;
480 }
481
482 /* Standard icon name */
483 p = strdup(mime);
484 pp = p;
485 while (*pp)
486 {
487 if (*pp == '/') *pp = '-';
488 pp++;
489 }
490 icons = eina_list_append(icons, p);
491
492 /* Environment Based icon names */
493 if ((env = efreet_desktop_environment_get()))
494 {
495 snprintf(buf, sizeof(buf), "%s-mime-%s", env, p);
496 icons = eina_list_append(icons, strdup(buf));
497
498 snprintf(buf, sizeof(buf), "%s-%s", env, p);
499 icons = eina_list_append(icons, strdup(buf));
500 }
501
502 /* Mime prefixed icon names */
503 snprintf(buf, sizeof(buf), "mime-%s", p);
504 icons = eina_list_append(icons, strdup(buf));
505
506 /* Generic icons */
507 pp = strdup(p);
508 while ((ppp = strrchr(pp, '-')))
509 {
510 *ppp = '\0';
511
512 snprintf(buf, sizeof(buf), "%s-x-generic", pp);
513 icons = eina_list_append(icons, strdup(buf));
514
515 snprintf(buf, sizeof(buf), "%s-generic", pp);
516 icons = eina_list_append(icons, strdup(buf));
517
518 snprintf(buf, sizeof(buf), "%s", pp);
519 icons = eina_list_append(icons, strdup(buf));
520 }
521 FREE(pp);
522
523 /* Search for icons using list */
524 icon = efreet_icon_list_find(theme, icons, size);
525 while (icons)
526 {
527 data = eina_list_data_get(icons);
528 free(data);
529 icons = eina_list_remove_list(icons, icons);
530 }
531
532 efreet_mime_icon_entry_add(mime, eina_stringshare_add(icon), theme, size);
533
534 return icon;
535 }
536
537 EAPI void
efreet_mime_type_cache_clear(void)538 efreet_mime_type_cache_clear(void)
539 {
540 if (mime_icons)
541 {
542 eina_hash_free(mime_icons);
543 mime_icons_lru = NULL;
544 }
545 mime_icons = eina_hash_stringshared_new(EINA_FREE_CB(efreet_mime_icon_entry_head_free));
546 }
547
548 EAPI void
efreet_mime_type_cache_flush(void)549 efreet_mime_type_cache_flush(void)
550 {
551 efreet_mime_icons_flush(ecore_loop_time_get());
552 }
553
554
555 EAPI const char *
efreet_mime_magic_type_get(const char * file)556 efreet_mime_magic_type_get(const char *file)
557 {
558 EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
559 return efreet_mime_magic_check_priority(file, 0, 0);
560 }
561
562 EAPI const char *
efreet_mime_globs_type_get(const char * file)563 efreet_mime_globs_type_get(const char *file)
564 {
565 char *sl, *p;
566 const char *s, *mime;
567 char *ext;
568 unsigned int i, n;
569
570 EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
571 if (!mimedb_ptr) return NULL;
572
573 /* Check in the extension hash for the type */
574 ext = strchr(file, '.');
575 if (ext)
576 {
577 sl = alloca(strlen(ext) + 1);
578 for (s = ext, p = sl; *s; s++, p++) *p = tolower(*s);
579 *p = 0;
580 p = sl;
581 while (p)
582 {
583 p++;
584 if (p && (mime = _efreet_mimedb_extn_find(p))) return mime;
585 p = strchr(p, '.');
586 }
587 }
588
589 // Fallback to the other globs if not found
590 n = _efreet_mimedb_glob_count();
591 for (i = 0; i < n; i++)
592 {
593 s = _efreet_mimedb_glob_get(i);
594 if (efreet_mime_glob_match(file, s))
595 return _efreet_mimedb_glob_mime_get(i);
596 }
597 ext = alloca(strlen(file) + 1);
598 for (s = file, p = ext; *s; s++, p++) *p = tolower(*s);
599 *p = 0;
600 for (i = 0; i < n; i++)
601 {
602 s = _efreet_mimedb_glob_get(i);
603 if (efreet_mime_glob_case_match(ext, s))
604 return _efreet_mimedb_glob_mime_get(i);
605 }
606 return NULL;
607 }
608
609 EAPI const char *
efreet_mime_special_type_get(const char * file)610 efreet_mime_special_type_get(const char *file)
611 {
612 EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
613 return efreet_mime_special_check(file);
614 }
615
616 EAPI const char *
efreet_mime_fallback_type_get(const char * file)617 efreet_mime_fallback_type_get(const char *file)
618 {
619 EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
620 return efreet_mime_fallback_check(file);
621 }
622
623 /**
624 * @internal
625 * @return Returns the endianess
626 * @brief Retreive the endianess of the machine
627 */
628 static int
efreet_mime_endian_check(void)629 efreet_mime_endian_check(void)
630 {
631 int test = 1;
632 return (*((char*)(&test)));
633 }
634
635 /**
636 * @internal
637 * @param datadirs List of XDG data dirs
638 * @param datahome Path to XDG data home directory
639 * @return Returns no value
640 * @brief Read all magic files in XDG data/home dirs.
641 */
642 static void
efreet_mime_load_magics(Eina_List * datadirs,const char * datahome)643 efreet_mime_load_magics(Eina_List *datadirs, const char *datahome)
644 {
645 Eina_List *l;
646 char buf[4096];
647 const char *datadir = NULL;
648
649 while (magics)
650 {
651 efreet_mime_magic_free(eina_list_data_get(magics));
652 magics = eina_list_remove_list(magics, magics);
653 }
654
655 datadir = datahome;
656 snprintf(buf, sizeof(buf), "%s/mime/magic", datadir);
657 efreet_mime_shared_mimeinfo_magic_load(buf);
658
659 EINA_LIST_FOREACH(datadirs, l, datadir)
660 {
661 snprintf(buf, sizeof(buf), "%s/mime/magic", datadir);
662 efreet_mime_shared_mimeinfo_magic_load(buf);
663 }
664 }
665
666 /**
667 * @internal
668 * @param datadirs List of XDG data dirs
669 * @param datahome Path to XDG data home directory
670 * @return Returns 1 on success, 0 on failure
671 * @brief Initializes globs, magics, and monitors lists.
672 */
673 static int
efreet_mime_init_files(void)674 efreet_mime_init_files(void)
675 {
676 Eina_List *datadirs = NULL;
677 const char *datahome;
678
679 if (!(datahome = efreet_data_home_get()))
680 return 0;
681
682 if (!(datadirs = efreet_data_dirs_get()))
683 return 0;
684
685 efreet_mime_load_magics(datadirs, datahome);
686
687 _mime_inode_symlink = eina_stringshare_add("inode/symlink");
688 _mime_inode_fifo = eina_stringshare_add("inode/fifo");
689 _mime_inode_chardevice = eina_stringshare_add("inode/chardevice");
690 _mime_inode_blockdevice = eina_stringshare_add("inode/blockdevice");
691 _mime_inode_socket = eina_stringshare_add("inode/socket");
692 _mime_inode_mountpoint = eina_stringshare_add("inode/mountpoint");
693 _mime_inode_directory = eina_stringshare_add("inode/directory");
694 _mime_application_x_executable = eina_stringshare_add("application/x-executable");
695 _mime_application_octet_stream = eina_stringshare_add("application/octet-stream");
696 _mime_text_plain = eina_stringshare_add("text/plain");
697
698 return 1;
699 }
700
701 /**
702 * @internal
703 * @param file File to examine
704 * @return Returns mime type if special file, else NULL
705 * @brief Returns a mime type based on the stat of a file.
706 * This is used first to catch directories and other special
707 * files. A NULL return doesn't necessarily mean failure, but
708 * can also mean the file is regular.
709 * @note Mapping of file types to mime types:
710 * Stat Macro File Type Mime Type
711 * S_IFREG regular NULL
712 * S_IFIFO named pipe (fifo) inode/fifo
713 * S_IFCHR character special inode/chardevice
714 * S_IFDIR directory inode/directory
715 * S_IFBLK block special inode/blockdevice
716 * S_IFLNK symbolic link inode/symlink
717 * S_IFSOCK socket inode/socket
718 *
719 * This function can also return inode/mount-point.
720 * This is calculated by comparing the st_dev of the directory
721 * against that of it's parent directory. If they differ it
722 * is considered a mount point.
723 */
724 static const char *
efreet_mime_special_check(const char * file)725 efreet_mime_special_check(const char *file)
726 {
727 struct stat s;
728 int path_len = 0;
729
730 /* no symlink on Windows */
731 #ifdef _WIN32
732 if (!stat(file, &s))
733 #else
734 if (!lstat(file, &s))
735 #endif
736 {
737 if (S_ISREG(s.st_mode))
738 return NULL;
739
740 #ifndef _WIN32
741 if (S_ISLNK(s.st_mode))
742 return _mime_inode_symlink;
743 #endif
744
745 if (S_ISFIFO(s.st_mode))
746 return _mime_inode_fifo;
747
748 if (S_ISCHR(s.st_mode))
749 return _mime_inode_chardevice;
750
751 if (S_ISBLK(s.st_mode))
752 return _mime_inode_blockdevice;
753
754 #ifndef _WIN32
755 if (S_ISSOCK(s.st_mode))
756 return _mime_inode_socket;
757 #endif
758
759 if (S_ISDIR(s.st_mode))
760 {
761 struct stat s2;
762 char parent[PATH_MAX];
763 char path[PATH_MAX];
764
765 strncpy(path, file, PATH_MAX);
766 path[PATH_MAX - 1] = '\0';
767
768 path_len = strlen(file);
769 strncpy(parent, path, PATH_MAX);
770 parent[PATH_MAX - 1] = '\0';
771
772 /* Kill any trailing slash */
773 if (parent[path_len - 1] == '/')
774 parent[--path_len] = '\0';
775
776 /* Truncate to last slash */
777 while ((path_len > 0) &&
778 (parent[--path_len] != '/'))
779 parent[path_len] = '\0';
780
781 #ifdef _WIN32
782 if (!stat(file, &s2))
783 #else
784 if (!lstat(parent, &s2))
785 #endif
786 {
787 if (s.st_dev != s2.st_dev)
788 return _mime_inode_mountpoint;
789 }
790
791 return _mime_inode_directory;
792 }
793
794 return NULL;
795 }
796
797 return NULL;
798 }
799
800 /**
801 * @internal
802 * @param file File to examine
803 * @return Returns mime type or NULL if the file doesn't exist
804 * @brief Returns text/plain if the file appears to contain text and
805 * returns application/octet-stream if it appears to be binary.
806 */
807 static const char *
efreet_mime_fallback_check(const char * file)808 efreet_mime_fallback_check(const char *file)
809 {
810 FILE *f = NULL;
811 char buf[32];
812 int i;
813
814 if (ecore_file_can_exec(file))
815 return _mime_application_x_executable;
816
817 if (!(f = fopen(file, "rb"))) return NULL;
818
819 i = fread(buf, 1, sizeof(buf), f);
820 fclose(f);
821
822 if (i == 0) return _mime_application_octet_stream;
823
824 /*
825 * Check for ASCII control characters in the first 32 bytes.
826 * Line Feeds, carriage returns, and tabs are ignored as they are
827 * quite common in text files in the first 32 chars.
828 */
829 for (i -= 1; i >= 0; --i)
830 {
831 if ((buf[i] < 0x20) &&
832 (buf[i] != '\n') && /* Line Feed */
833 (buf[i] != '\r') && /* Carriage Return */
834 (buf[i] != '\t')) /* Tab */
835 return _mime_application_octet_stream;
836 }
837
838 return _mime_text_plain;
839 }
840
841 /**
842 * @internal
843 * @param in Number to count the digits
844 * @return Returns number of digits
845 * @brief Calculates and returns the number of digits
846 * in a number.
847 */
848 static int
efreet_mime_count_digits(int in)849 efreet_mime_count_digits(int in)
850 {
851 int i = 1, j = in;
852
853 if (j < 10) return 1;
854 while ((j /= 10) > 0) ++i;
855
856 return i;
857 }
858
859 /**
860 * @internal
861 * @param file File to parse
862 * @return Returns no value
863 * @brief Loads a magic file and adds information to magics list
864 */
865 static void
efreet_mime_shared_mimeinfo_magic_load(const char * file)866 efreet_mime_shared_mimeinfo_magic_load(const char *file)
867 {
868 Eina_File *f;
869 void *data;
870
871 if (!file) return;
872
873 f = eina_file_open(file, EINA_FALSE);
874 if (!f) return ;
875
876 data = eina_file_map_all(f, EINA_FILE_WILLNEED);
877 if (!data) goto end;
878
879 efreet_mime_shared_mimeinfo_magic_parse(data, eina_file_size_get(f));
880
881 eina_file_map_free(f, data);
882 end:
883 eina_file_close(f);
884 }
885
886 /**
887 * @param data The data from the file
888 * @return Returns no value
889 * @brief Parses a magic file
890 * @note Format:
891 *
892 * ----------------------------------------------------------------------
893 * | HEX | ASCII |
894 * ----------------------------------------------------------------------
895 * |4D 49 4D 45 2D 4D 61 67 69 63 00 0A 5B 39 30 3A | MIME-Magic..[90: |
896 * |61 70 70 6C 69 63 61 74 69 6F 6E 2F 64 6F 63 62 | application/docb |
897 * |6F 6F 6B 2B 78 6D 6C 5D 0A 3E 30 3D 00 05 3C 3F | ook+xml].>0=..<? |
898 * |78 6D 6C 0A 31 3E 30 3D 00 19 2D 2F 2F 4F 41 53 | xml.1>0=..-//OAS |
899 * |49 53 2F 2F 44 54 44 20 44 6F 63 42 6F 6F 6B 20 | IS//DTD DocBook |
900 * |58 4D 4C 2B 31 30 31 0A 31 3E 30 3D 00 17 2D 2F | XML+101.1>0=..-/ |
901 * ----------------------------------------------------------------------
902 *
903 * indent
904 * The nesting depth of the rule, corresponding to the number of '>'
905 * characters in the traditional file format.
906 * ">" start-offset
907 * The offset into the file to look for a match.
908 * "=" value
909 * Two bytes giving the (big-endian) length of the value, followed by the
910 * value itself.
911 * "&" mask
912 * The mask, which (if present) is exactly the same length as the value.
913 * "~" word-size
914 * On little-endian machines, the size of each group to byte-swap.
915 * "+" range-length
916 * The length of the region in the file to check.
917 *
918 * The indent, range-length, word-size and mask components are optional.
919 * If missing, indent defaults to 0, range-length to 1, the word-size to 1,
920 * and the mask to all 'one' bits. In our case, mask is null as it is
921 * quicker, uses less memory and will achieve the same exact effect.
922 */
923 static void
efreet_mime_shared_mimeinfo_magic_parse(char * data,int size)924 efreet_mime_shared_mimeinfo_magic_parse(char *data, int size)
925 {
926 Efreet_Mime_Magic *mime = NULL;
927 Efreet_Mime_Magic_Entry *entry = NULL;
928 char *ptr;
929
930 ptr = data;
931
932 /* make sure we're a magic file */
933 if (!ptr || (size < 12) || strncmp(ptr, "MIME-Magic\0\n", 12))
934 return;
935
936 ptr += 12;
937
938 for (; (ptr - data) < size; )
939 {
940 if (*ptr == '[')
941 {
942 char *val, buf[512];
943
944 mime = NEW(Efreet_Mime_Magic, 1);
945 magics = eina_list_append(magics, mime);
946
947 val = ++ptr;
948 while ((*val != ':')) val++;
949 memcpy(&buf, ptr, val - ptr);
950 buf[val - ptr] = '\0';
951
952 mime->priority = atoi(buf);
953 ptr = ++val;
954
955 while ((*val != ']')) val++;
956 memcpy(&buf, ptr, val - ptr);
957 buf[val - ptr] = '\0';
958
959 mime->mime = eina_stringshare_add(buf);
960 ptr = ++val;
961
962 while (*ptr != '\n') ptr++;
963 ptr++;
964 }
965 else
966 {
967 short tshort;
968
969 if (!mime) continue;
970 if (!entry)
971 {
972 if (!(entry = NEW(Efreet_Mime_Magic_Entry, 1)))
973 {
974 IF_FREE_LIST(magics, efreet_mime_magic_free);
975 return;
976 }
977
978 entry->indent = 0;
979 entry->offset = 0;
980 entry->value_len = 0;
981 entry->word_size = 1;
982 entry->range_len = 1;
983 entry->mask = NULL;
984 entry->value = NULL;
985
986 mime->entries = eina_list_append(mime->entries, entry);
987 }
988
989 switch(*ptr)
990 {
991 case '>':
992 ptr ++;
993 entry->offset = atoi(ptr);
994 ptr += efreet_mime_count_digits(entry->offset);
995 break;
996
997 case '=':
998 ptr++;
999
1000 tshort = 0;
1001 memcpy(&tshort, ptr, sizeof(short));
1002 entry->value_len = eina_ntohs(tshort);
1003 ptr += 2;
1004
1005 entry->value = NEW(char, entry->value_len);
1006 memcpy(entry->value, ptr, entry->value_len);
1007 ptr += entry->value_len;
1008 break;
1009
1010 case '&':
1011 ptr++;
1012 entry->mask = NEW(char, entry->value_len);
1013 memcpy(entry->mask, ptr, entry->value_len);
1014 ptr += entry->value_len;
1015 break;
1016
1017 case '~':
1018 ptr++;
1019 entry->word_size = atoi(ptr);
1020 if ((entry->word_size != 0) && (((entry->word_size != 1)
1021 && (entry->word_size != 2)
1022 && (entry->word_size != 4))
1023 || (entry->value_len % entry->word_size)))
1024 {
1025 /* Invalid, Destroy */
1026 FREE(entry->value);
1027 FREE(entry->mask);
1028 FREE(entry);
1029
1030 while (*ptr != '\n') ptr++;
1031 break;
1032 }
1033
1034 if (efreet_mime_endianess == EFREET_ENDIAN_LITTLE)
1035 {
1036 int j;
1037
1038 for (j = 0; j < entry->value_len; j += entry->word_size)
1039 {
1040 if (entry->word_size == 2)
1041 {
1042 ((short*)entry->value)[j] =
1043 eina_ntohs(((short*)entry->value)[j]);
1044
1045 if (entry->mask)
1046 ((short*)entry->mask)[j] =
1047 eina_ntohs(((short*)entry->mask)[j]);
1048 }
1049 else if (entry->word_size == 4)
1050 {
1051 ((int*)entry->value)[j] =
1052 eina_ntohl(((int*)entry->value)[j]);
1053
1054 if (entry->mask)
1055 ((int*)entry->mask)[j] =
1056 eina_ntohl(((int*)entry->mask)[j]);
1057 }
1058 }
1059 }
1060
1061 ptr += efreet_mime_count_digits(entry->word_size);
1062 break;
1063
1064 case '+':
1065 ptr++;
1066 entry->range_len = atoi(ptr);
1067 ptr += efreet_mime_count_digits(entry->range_len);
1068 break;
1069
1070 case '\n':
1071 ptr++;
1072 entry = NULL;
1073 break;
1074
1075 default:
1076 if (isdigit(*ptr))
1077 {
1078 entry->indent = atoi(ptr);
1079 ptr += efreet_mime_count_digits(entry->indent);
1080 }
1081 break;
1082 }
1083 }
1084 }
1085 /*
1086 if (entry)
1087 {
1088 IF_FREE(entry->value);
1089 IF_FREE(entry->mask);
1090 FREE(entry);
1091 }
1092 */
1093 }
1094
1095 /**
1096 * @internal
1097 * @param file File to check
1098 * @param start Start priority, if 0 start at beginning
1099 * @param end End priority, should be less then start
1100 * unless start
1101 * @return Returns mime type for file if found, NULL if not
1102 * @brief Applies magic rules to a file given a start and end priority
1103 */
1104 static const char *
efreet_mime_magic_check_priority(const char * file,unsigned int start,unsigned int end)1105 efreet_mime_magic_check_priority(const char *file,
1106 unsigned int start,
1107 unsigned int end)
1108 {
1109 Efreet_Mime_Magic *m = NULL;
1110 Efreet_Mime_Magic_Entry *e = NULL;
1111 Eina_List *l, *ll;
1112 Eina_File *f = NULL;
1113 const char *mem = NULL;
1114 size_t sz;
1115 unsigned int i = 0, offset = 0,level = 0, match = 0;
1116 const char *last_mime = NULL;
1117 int c;
1118 char v;
1119
1120 if (!magics) return NULL;
1121
1122 f = eina_file_open(file, EINA_FALSE);
1123 if (!f) return NULL;
1124
1125 mem = eina_file_map_all(f, EINA_FILE_RANDOM);
1126 if (!mem) goto end;
1127
1128 sz = eina_file_size_get(f);
1129
1130 EINA_LIST_FOREACH(magics, l, m)
1131 {
1132 if ((start != 0) && (m->priority > start))
1133 continue;
1134
1135 if (m->priority < end)
1136 break;
1137
1138 EINA_LIST_FOREACH(m->entries, ll, e)
1139 {
1140 if ((level < e->indent) && !match)
1141 continue;
1142
1143 if ((level >= e->indent) && !match)
1144 {
1145 level = e->indent;
1146 }
1147 else if ((level > e->indent) && match)
1148 {
1149 goto end;
1150 }
1151
1152 for (offset = e->offset; offset < e->offset + e->range_len; offset++)
1153 {
1154 if (offset + e->value_len >= sz) break;
1155
1156 match = 1;
1157 for (i = 0; i < e->value_len; ++i)
1158 {
1159 c = mem[offset + i];
1160
1161 v = e->value[i];
1162 if (e->mask) c &= e->mask[i];
1163
1164 if (!(c == v))
1165 {
1166 match = 0;
1167 break;
1168 }
1169 }
1170
1171 if (match)
1172 {
1173 level += 1;
1174 last_mime = m->mime;
1175 break;
1176 }
1177 }
1178 }
1179
1180 if (match) break;
1181 }
1182
1183 end:
1184 if (mem) eina_file_map_free(f, (void*) mem);
1185
1186 eina_file_close(f);
1187
1188 return last_mime;
1189 }
1190
1191 /**
1192 * @internal
1193 * @param data Data pointer that is being destroyed
1194 * @return Returns no value
1195 * @brief Callback for magics destroy
1196 */
1197 static void
efreet_mime_magic_free(void * data)1198 efreet_mime_magic_free(void *data)
1199 {
1200 Efreet_Mime_Magic *m = data;
1201
1202 IF_RELEASE(m->mime);
1203 IF_FREE_LIST(m->entries, efreet_mime_magic_entry_free);
1204 IF_FREE(m);
1205 }
1206
1207 /**
1208 * @internal
1209 * @param data Data pointer that is being destroyed
1210 * @return Returns no value
1211 * @brief Callback for magic entry destroy
1212 */
1213 static void
efreet_mime_magic_entry_free(void * data)1214 efreet_mime_magic_entry_free(void *data)
1215 {
1216 Efreet_Mime_Magic_Entry *e = data;
1217
1218 IF_FREE(e->mask);
1219 IF_FREE(e->value);
1220 IF_FREE(e);
1221 }
1222
1223
1224 /**
1225 * @internal
1226 * @param str String (filename) to match
1227 * @param glob Glob to match str to
1228 * @return Returns 1 on success, 0 on failure
1229 * @brief Compares str to glob, case sensitive
1230 */
1231 static int
efreet_mime_glob_match(const char * str,const char * glob)1232 efreet_mime_glob_match(const char *str, const char *glob)
1233 {
1234 if (!str || !glob) return 0;
1235 if (glob[0] == 0)
1236 {
1237 if (str[0] == 0) return 1;
1238 return 0;
1239 }
1240 if (!fnmatch(glob, str, 0)) return 1;
1241 return 0;
1242 }
1243
1244 /**
1245 * @internal
1246 * @param str String (filename) to match
1247 * @param glob Glob to match str to
1248 * @return Returns 1 on success, 0 on failure
1249 * @brief Compares str to glob, case insensitive (expects str already in lower case)
1250 */
1251 static int
efreet_mime_glob_case_match(char * str,const char * glob)1252 efreet_mime_glob_case_match(char *str, const char *glob)
1253 {
1254 const char *p;
1255 char *tglob, *tp;
1256
1257 if (!str || !glob) return 0;
1258 if (glob[0] == 0)
1259 {
1260 if (str[0] == 0) return 1;
1261 return 0;
1262 }
1263 tglob = alloca(strlen(glob) + 1);
1264 for (tp = tglob, p = glob; *p; p++, tp++) *tp = tolower(*p);
1265 *tp = 0;
1266 if (!fnmatch(str, tglob, 0)) return 1;
1267 return 0;
1268 }
1269
1270 static void
efreet_mime_icons_flush(double now)1271 efreet_mime_icons_flush(double now)
1272 {
1273 Eina_Inlist *l;
1274 static double old = 0;
1275 int todo;
1276
1277 if (now - old < EFREET_MIME_ICONS_FLUSH_TIMEOUT)
1278 return;
1279 old = now;
1280
1281 todo = eina_hash_population(mime_icons) - EFREET_MIME_ICONS_MAX_POPULATION;
1282 if (todo <= 0)
1283 return;
1284
1285 l = mime_icons_lru->last; /* mime_icons_lru is not NULL, since todo > 0 */
1286 for (; todo > 0; todo--)
1287 {
1288 Efreet_Mime_Icon_Entry_Head *entry = (Efreet_Mime_Icon_Entry_Head *)l;
1289 Eina_Inlist *prev = l->prev;
1290
1291 mime_icons_lru = eina_inlist_remove(mime_icons_lru, l);
1292 eina_hash_del_by_key(mime_icons, entry->mime);
1293 l = prev;
1294 }
1295
1296 efreet_mime_icons_debug();
1297 }
1298
1299 static void
efreet_mime_icon_entry_free(Efreet_Mime_Icon_Entry * node)1300 efreet_mime_icon_entry_free(Efreet_Mime_Icon_Entry *node)
1301 {
1302 eina_stringshare_del(node->icon);
1303 eina_stringshare_del(node->theme);
1304 free(node);
1305 }
1306
1307 static void
efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head * entry)1308 efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry)
1309 {
1310 while (entry->list)
1311 {
1312 Efreet_Mime_Icon_Entry *n = (Efreet_Mime_Icon_Entry *)entry->list;
1313 entry->list = eina_inlist_remove(entry->list, entry->list);
1314 efreet_mime_icon_entry_free(n);
1315 }
1316
1317 eina_stringshare_del(entry->mime);
1318 free(entry);
1319 }
1320
1321 static Efreet_Mime_Icon_Entry *
efreet_mime_icon_entry_new(const char * icon,const char * theme,unsigned int size)1322 efreet_mime_icon_entry_new(const char *icon,
1323 const char *theme,
1324 unsigned int size)
1325 {
1326 Efreet_Mime_Icon_Entry *entry;
1327
1328 entry = malloc(sizeof(*entry));
1329 if (!entry)
1330 return NULL;
1331
1332 entry->icon = icon;
1333 entry->theme = theme;
1334 entry->size = size;
1335
1336 return entry;
1337 }
1338
1339 static void
efreet_mime_icon_entry_add(const char * mime,const char * icon,const char * theme,unsigned int size)1340 efreet_mime_icon_entry_add(const char *mime,
1341 const char *icon,
1342 const char *theme,
1343 unsigned int size)
1344 {
1345 Efreet_Mime_Icon_Entry_Head *entry;
1346 Efreet_Mime_Icon_Entry *n;
1347
1348 n = efreet_mime_icon_entry_new(icon, theme, size);
1349 if (!n)
1350 return;
1351 entry = eina_hash_find(mime_icons, mime);
1352
1353 if (entry)
1354 {
1355 Eina_Inlist *l;
1356
1357 l = EINA_INLIST_GET(n);
1358 entry->list = eina_inlist_prepend(entry->list, l);
1359
1360 l = EINA_INLIST_GET(entry);
1361 mime_icons_lru = eina_inlist_promote(mime_icons_lru, l);
1362 }
1363 else
1364 {
1365 Eina_Inlist *l;
1366
1367 entry = malloc(sizeof(*entry));
1368 if (!entry)
1369 {
1370 efreet_mime_icon_entry_free(n);
1371 return;
1372 }
1373
1374 l = EINA_INLIST_GET(n);
1375 entry->list = eina_inlist_prepend(NULL, l);
1376 entry->mime = mime;
1377 eina_hash_direct_add(mime_icons, mime, entry);
1378
1379 l = EINA_INLIST_GET(entry);
1380 mime_icons_lru = eina_inlist_prepend(mime_icons_lru, l);
1381 }
1382
1383 entry->timestamp = ecore_loop_time_get();
1384 efreet_mime_icons_flush(entry->timestamp);
1385 }
1386
1387 static const char *
efreet_mime_icon_entry_find(const char * mime,const char * theme,unsigned int size)1388 efreet_mime_icon_entry_find(const char *mime,
1389 const char *theme,
1390 unsigned int size)
1391 {
1392 Efreet_Mime_Icon_Entry_Head *entry;
1393 Efreet_Mime_Icon_Entry *n;
1394
1395 entry = eina_hash_find(mime_icons, mime);
1396 if (!entry)
1397 return NULL;
1398
1399 EINA_INLIST_FOREACH(entry->list, n)
1400 {
1401 if ((n->theme == theme) && (n->size == size))
1402 {
1403 Eina_Inlist *l;
1404
1405 l = EINA_INLIST_GET(n);
1406 if (entry->list != l)
1407 entry->list = eina_inlist_promote(entry->list, l);
1408
1409 l = EINA_INLIST_GET(entry);
1410 if (mime_icons_lru != l)
1411 mime_icons_lru = eina_inlist_promote(mime_icons_lru, l);
1412
1413 entry->timestamp = ecore_loop_time_get();
1414 return n->icon;
1415 }
1416 }
1417
1418 return NULL;
1419 }
1420
1421 #ifdef EFREET_MIME_ICONS_DEBUG
1422 static void
efreet_mime_icons_debug(void)1423 efreet_mime_icons_debug(void)
1424 {
1425 double now = ecore_loop_time_get();
1426 Efreet_Mime_Icon_Entry_Head *entry;
1427 EINA_INLIST_FOREACH(mime_icons_lru, entry)
1428 {
1429 Efreet_Mime_Icon_Entry *n;
1430
1431 if ((now > 0) &&
1432 (now - entry->timestamp >= EFREET_MIME_ICONS_EXPIRE_TIMEOUT))
1433 {
1434 puts("*** FOLLOWING ENTRIES ARE AGED AND CAN BE EXPIRED ***");
1435 now = 0;
1436 }
1437
1438 DBG("mime-icon entry: '%s' last used: %s",
1439 entry->mime, ctime(&entry->timestamp));
1440
1441 EINA_INLIST_FOREACH(entry->list, n)
1442 DBG("\tsize: %3u theme: '%s' icon: '%s'",
1443 n->theme, n->size, n->icon);
1444 }
1445 }
1446 #else
1447 static void
efreet_mime_icons_debug(void)1448 efreet_mime_icons_debug(void)
1449 {
1450 }
1451 #endif
1452