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