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