85f955c0c5e10b57b3a88b6dda8ace7d7e9644b4
[citadel.git] / libcitadel / lib / mime_parser.c
1 /*
2  * $Id$
3  *
4  * This is the MIME parser for Citadel.
5  *
6  * Copyright (c) 1998-2007 by the citadel.org development team.
7  * This code is distributed under the GNU General Public License v3.
8  *
9  */
10
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <stdio.h>
14 #include <signal.h>
15 #include <sys/types.h>
16 #include <ctype.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <dirent.h>
21 #include <errno.h>
22
23 #include "xdgmime/xdgmime.h"
24 #include "libcitadel.h"
25 #include "libcitadellocal.h"
26
27 void extract_key(char *target, char *source, char *key)
28 {
29         char *ptr;
30         char looking_for[256];
31         int double_quotes = 0;
32
33         snprintf(looking_for, sizeof looking_for, "%s=", key);
34
35         ptr = bmstrcasestr(source, looking_for);
36         if (ptr == NULL) {
37                 strcpy(target, "");
38                 return;
39         }
40         strcpy(target, (ptr + strlen(looking_for)));
41
42         for (ptr=target; (*ptr != 0); ++ptr) {
43
44                 /* A semicolon means we've hit the end of the key, unless we're inside double quotes */
45                 if ( (double_quotes != 1) && (*ptr == ';')) {
46                         *ptr = 0;
47                 }
48
49                 /* if we find double quotes, we've got a great set of string boundaries */
50                 if (*ptr == '\"') {
51                         ++double_quotes;
52                         if (double_quotes == 1) {
53                                 strcpy(ptr, ptr+1);
54                         }
55                         else {
56                                 *ptr = 0;
57                         }
58                 }
59         }
60 }
61
62
63 /*
64  * For non-multipart messages, we need to generate a quickie partnum of "1"
65  * to return to callback functions.  Some callbacks demand it.
66  */
67 char *fixed_partnum(char *supplied_partnum) {
68         if (supplied_partnum == NULL) return "1";
69         if (strlen(supplied_partnum)==0) return "1";
70         return supplied_partnum;
71 }
72
73
74 static inline unsigned int _decode_hex(char *Source)
75 {
76         int ret = 0;
77         if      ((*Source >= '0') && (*Source <= '9')) {
78                 ret += (*Source - '0');
79         }
80         else if ((*Source >= 'A') && (*Source <= 'F')) {
81                 ret += (*Source - 'A' + 10);
82         }
83         else if ((*Source >= 'a') && (*Source <= 'f')) {
84                 ret += (*Source - 'a' + 10);
85         }
86         else {
87                 return '?';
88         }
89                 
90         ret = ret << 4;
91         
92         if      ((*(Source + 1) >= '0') && (*(Source + 1) <= '9')) {
93                 ret |= (*(Source + 1) - '0');
94         }
95         else if ((*(Source + 1) >= 'A') && (*(Source + 1) <= 'F')) {
96                 ret |= (*(Source + 1) - 'A' + 10);
97         }
98         else if ((*(Source + 1) >= 'a') && (*(Source + 1) <= 'f')) {
99                 ret |= (*(Source + 1) - 'a' + 10);
100         }
101         else {
102                 return '?';
103         }
104         return ret;
105 }
106
107 unsigned int decode_hex(char *Source) {return _decode_hex(Source);}
108
109 /*
110  * Convert "quoted-printable" to binary.  Returns number of bytes decoded.
111  * according to RFC2045 section 6.7
112  */
113 int CtdlDecodeQuotedPrintable(char *decoded, char *encoded, int sourcelen) {
114         unsigned int ch;
115         int decoded_length = 0;
116         int pos = 0;
117
118         while (pos < sourcelen)
119         {
120                 if (!strncmp(&encoded[pos], "=\r\n", 3))
121                 {
122                         pos += 3;
123                 }
124                 else if (!strncmp(&encoded[pos], "=\n", 2))
125                 {
126                         pos += 2;
127                 }
128                 else if (encoded[pos] == '=')
129                 {
130                         ch = 0;
131                         ch = _decode_hex(&encoded[pos+1]);
132                         pos += 3;
133                         decoded[decoded_length++] = ch;
134                 }
135                 else
136                 {
137                         decoded[decoded_length++] = encoded[pos];
138                         pos += 1;
139                 }
140         }
141         decoded[decoded_length] = 0;
142         return(decoded_length);
143 }
144
145
146 /*
147  * Given a message or message-part body and a length, handle any necessary
148  * decoding and pass the request up the stack.
149  */
150 void mime_decode(char *partnum,
151                  char *part_start, size_t length,
152                  char *content_type, char *charset, char *encoding,
153                  char *disposition,
154                  char *id,
155                  char *name, char *filename,
156                  void (*CallBack)
157                   (char *cbname,
158                    char *cbfilename,
159                    char *cbpartnum,
160                    char *cbdisp,
161                    void *cbcontent,
162                    char *cbtype,
163                    char *cbcharset,
164                    size_t cblength,
165                    char *cbencoding,
166                    char *cbid,
167                    void *cbuserdata),
168                  void (*PreMultiPartCallBack)
169                   (char *cbname,
170                    char *cbfilename,
171                    char *cbpartnum,
172                    char *cbdisp,
173                    void *cbcontent,
174                    char *cbtype,
175                    char *cbcharset,
176                    size_t cblength,
177                    char *cbencoding,
178                    char *cbid,
179                    void *cbuserdata),
180                  void (*PostMultiPartCallBack)
181                   (char *cbname,
182                    char *cbfilename,
183                    char *cbpartnum,
184                    char *cbdisp,
185                    void *cbcontent,
186                    char *cbtype,
187                    char *cbcharset,
188                    size_t cblength,
189                    char *cbencoding,
190                    char *cbid,
191                    void *cbuserdata),
192                   void *userdata,
193                   int dont_decode
194 )
195 {
196
197         char *decoded;
198         size_t bytes_decoded = 0;
199
200         /* Some encodings aren't really encodings */
201         if (!strcasecmp(encoding, "7bit"))
202                 strcpy(encoding, "");
203         if (!strcasecmp(encoding, "8bit"))
204                 strcpy(encoding, "");
205         if (!strcasecmp(encoding, "binary"))
206                 strcpy(encoding, "");
207
208         /* If this part is not encoded, send as-is */
209         if ( (strlen(encoding) == 0) || (dont_decode)) {
210                 if (CallBack != NULL) {
211                         CallBack(name, filename, fixed_partnum(partnum),
212                                 disposition, part_start,
213                                 content_type, charset, length, encoding, id, userdata);
214                         }
215                 return;
216         }
217         
218         /* Fail silently if we hit an unknown encoding. */
219         if ((strcasecmp(encoding, "base64"))
220             && (strcasecmp(encoding, "quoted-printable"))) {
221                 return;
222         }
223
224         /*
225          * Allocate a buffer for the decoded data.  The output buffer is slightly
226          * larger than the input buffer; this assumes that the decoded data
227          * will never be significantly larger than the encoded data.  This is a
228          * safe assumption with base64, uuencode, and quoted-printable.
229          */
230         decoded = malloc(length + 32768);
231         if (decoded == NULL) {
232                 return;
233         }
234
235         if (!strcasecmp(encoding, "base64")) {
236                 bytes_decoded = CtdlDecodeBase64(decoded, part_start, length);
237         }
238         else if (!strcasecmp(encoding, "quoted-printable")) {
239                 bytes_decoded = CtdlDecodeQuotedPrintable(decoded, part_start, length);
240         }
241
242         if (bytes_decoded > 0) if (CallBack != NULL) {
243                 CallBack(name, filename, fixed_partnum(partnum),
244                         disposition, decoded,
245                         content_type, charset, bytes_decoded, "binary", id, userdata);
246         }
247
248         free(decoded);
249 }
250
251 /*
252  * Break out the components of a multipart message
253  * (This function expects to be fed HEADERS + CONTENT)
254  * Note: NULL can be supplied as content_end; in this case, the message is
255  * considered to have ended when the parser encounters a 0x00 byte.
256  */
257 void the_mime_parser(char *partnum,
258                      char *content_start, char *content_end,
259                      void (*CallBack)
260                       (char *cbname,
261                        char *cbfilename,
262                        char *cbpartnum,
263                        char *cbdisp,
264                        void *cbcontent,
265                        char *cbtype,
266                        char *cbcharset,
267                        size_t cblength,
268                        char *cbencoding,
269                        char *cbid,
270                        void *cbuserdata),
271                      void (*PreMultiPartCallBack)
272                       (char *cbname,
273                        char *cbfilename,
274                        char *cbpartnum,
275                        char *cbdisp,
276                        void *cbcontent,
277                        char *cbtype,
278                        char *cbcharset,
279                        size_t cblength,
280                        char *cbencoding,
281                        char *cbid,
282                        void *cbuserdata),
283                      void (*PostMultiPartCallBack)
284                       (char *cbname,
285                        char *cbfilename,
286                        char *cbpartnum,
287                        char *cbdisp,
288                        void *cbcontent,
289                        char *cbtype,
290                        char *cbcharset,
291                        size_t cblength,
292                        char *cbencoding,
293                        char *cbid,
294                        void *cbuserdata),
295                       void *userdata,
296                       int dont_decode
297 )
298 {
299
300         char *ptr;
301         char *srch = NULL;
302         char *part_start, *part_end = NULL;
303         char buf[SIZ];
304         char *header;
305         char *boundary;
306         char *startary;
307         size_t startary_len = 0;
308         char *endary;
309         char *next_boundary;
310         char *content_type;
311         char *charset;
312         size_t content_length;
313         char *encoding;
314         char *disposition;
315         char *id;
316         char *name = NULL;
317         char *content_type_name;
318         char *content_disposition_name;
319         char *filename;
320         int is_multipart;
321         int part_seq = 0;
322         int i;
323         size_t length;
324         char nested_partnum[256];
325         int crlf_in_use = 0;
326         char *evaluate_crlf_ptr = NULL;
327         int buflen = 0;
328         int headerlen = 0;
329
330         ptr = content_start;
331         content_length = 0;
332
333         boundary = malloc(SIZ);
334         memset(boundary, 0, SIZ);
335
336         startary = malloc(SIZ);
337         memset(startary, 0, SIZ);
338
339         endary = malloc(SIZ);
340         memset(endary, 0, SIZ);
341
342         header = malloc(SIZ);
343         memset(header, 0, SIZ);
344
345         content_type = malloc(SIZ);
346         memset(content_type, 0, SIZ);
347
348         charset = malloc(SIZ);
349         memset(charset, 0, SIZ);
350
351         encoding = malloc(SIZ);
352         memset(encoding, 0, SIZ);
353
354         content_type_name = malloc(SIZ);
355         memset(content_type_name, 0, SIZ);
356
357         content_disposition_name = malloc(SIZ);
358         memset(content_disposition_name, 0, SIZ);
359
360         filename = malloc(SIZ);
361         memset(filename, 0, SIZ);
362
363         disposition = malloc(SIZ);
364         memset(disposition, 0, SIZ);
365
366         id = malloc(SIZ);
367         memset(id, 0, SIZ);
368
369         /* If the caller didn't supply an endpointer, generate one by measure */
370         if (content_end == NULL) {
371                 content_end = &content_start[strlen(content_start)];
372         }
373
374         /* Learn interesting things from the headers */
375         strcpy(header, "");
376         headerlen = 0;
377         do {
378                 ptr = memreadlinelen(ptr, buf, SIZ, &buflen);
379                 if (ptr >= content_end) {
380                         goto end_parser;
381                 }
382
383                 for (i = 0; i < buflen; ++i) {
384                         if (isspace(buf[i])) {
385                                 buf[i] = ' ';
386                         }
387                 }
388
389                 if (!isspace(buf[0])) {
390                         if (!strncasecmp(header, "Content-type:", 13)) {
391                                 strcpy(content_type, &header[13]);
392                                 striplt(content_type);
393                                 extract_key(content_type_name, content_type, "name");
394                                 extract_key(charset, content_type, "charset");
395                                 extract_key(boundary, header, "boundary");
396                                 /* Deal with weird headers */
397                                 if (strchr(content_type, ' '))
398                                         *(strchr(content_type, ' ')) = '\0';
399                                 if (strchr(content_type, ';'))
400                                         *(strchr(content_type, ';')) = '\0';
401                         }
402                         if (!strncasecmp(header, "Content-Disposition:", 20)) {
403                                 strcpy(disposition, &header[20]);
404                                 striplt(disposition);
405                                 extract_key(content_disposition_name, disposition, "name");
406                                 extract_key(filename, disposition, "filename");
407                         }
408                         if (!strncasecmp(header, "Content-ID:", 11)) {
409                                 strcpy(id, &header[11]);
410                                 striplt(id);
411                                 stripallbut(id, '<', '>');
412                         }
413                         if (!strncasecmp(header, "Content-length: ", 15)) {
414                                 char clbuf[10];
415                                 safestrncpy(clbuf, &header[15], sizeof clbuf);
416                                 striplt(clbuf);
417                                 content_length = (size_t) atol(clbuf);
418                         }
419                         if (!strncasecmp(header, "Content-transfer-encoding: ", 26)) {
420                                 strcpy(encoding, &header[26]);
421                                 striplt(encoding);
422                         }
423                         strcpy(header, "");
424                         headerlen = 0;
425                 }
426                 if ((headerlen + buflen + 2) < SIZ) {
427                         memcpy(&header[headerlen], buf, buflen);
428                         headerlen += buflen;
429                         header[headerlen] = '\0';
430                 }
431         } while ((!IsEmptyStr(buf)) && (*ptr != 0));
432
433         if (strchr(disposition, ';'))
434                 *(strchr(disposition, ';')) = '\0';
435         striplt(disposition);
436         if (strchr(content_type, ';'))
437                 *(strchr(content_type, ';')) = '\0';
438         striplt(content_type);
439
440         if (!IsEmptyStr(boundary)) {
441                 is_multipart = 1;
442         } else {
443                 is_multipart = 0;
444         }
445
446         /* If this is a multipart message, then recursively process it */
447         part_start = NULL;
448         if (is_multipart) {
449
450                 /* Tell the client about this message's multipartedness */
451                 if (PreMultiPartCallBack != NULL) {
452                         PreMultiPartCallBack("", "", partnum, "",
453                                 NULL, content_type, charset,
454                                 0, encoding, id, userdata);
455                 }
456
457                 /* Figure out where the boundaries are */
458                 snprintf(startary, SIZ, "--%s", boundary);
459                 snprintf(endary, SIZ, "--%s--", boundary);
460                 startary_len = strlen(startary);
461
462                 part_start = NULL;
463                 do {
464                         next_boundary = NULL;
465                         for (srch=ptr; srch<content_end; ++srch) {
466                                 if (!memcmp(srch, startary, startary_len)) {
467                                         next_boundary = srch;
468                                         srch = content_end;
469                                 }
470                         }
471
472                         if ( (part_start != NULL) && (next_boundary != NULL) ) {
473                                 part_end = next_boundary;
474                                 --part_end;             /* omit the trailing LF */
475                                 if (crlf_in_use) {
476                                         --part_end;     /* omit the trailing CR */
477                                 }
478
479                                 if (!IsEmptyStr(partnum)) {
480                                         snprintf(nested_partnum,
481                                                  sizeof nested_partnum,
482                                                  "%s.%d", partnum,
483                                                  ++part_seq);
484                                 }
485                                 else {
486                                         snprintf(nested_partnum,
487                                                  sizeof nested_partnum,
488                                                  "%d", ++part_seq);
489                                 }
490                                 the_mime_parser(nested_partnum,
491                                             part_start, part_end,
492                                                 CallBack,
493                                                 PreMultiPartCallBack,
494                                                 PostMultiPartCallBack,
495                                                 userdata,
496                                                 dont_decode);
497                         }
498
499                         if (next_boundary != NULL) {
500                                 /* If we pass out of scope, don't attempt to
501                                  * read past the end boundary. */
502                                 if (!strcmp(next_boundary, endary)) {
503                                         ptr = content_end;
504                                 }
505                                 else {
506                                         /* Set up for the next part. */
507                                         part_start = strstr(next_boundary, "\n");
508                                         
509                                         /* Determine whether newlines are LF or CRLF */
510                                         evaluate_crlf_ptr = part_start;
511                                         --evaluate_crlf_ptr;
512                                         if (!memcmp(evaluate_crlf_ptr, "\r\n", 2)) {
513                                                 crlf_in_use = 1;
514                                         }
515                                         else {
516                                                 crlf_in_use = 0;
517                                         }
518
519                                         /* Advance past the LF ... now we're in the next part */
520                                         ++part_start;
521                                         ptr = part_start;
522                                 }
523                         }
524                         else {
525                                 /* Invalid end of multipart.  Bail out! */
526                                 ptr = content_end;
527                         }
528                 } while ( (ptr < content_end) && (next_boundary != NULL) );
529
530                 if (PostMultiPartCallBack != NULL) {
531                         PostMultiPartCallBack("", "", partnum, "", NULL,
532                                 content_type, charset, 0, encoding, id, userdata);
533                 }
534                 goto end_parser;
535         }
536
537         /* If it's not a multipart message, then do something with it */
538         if (!is_multipart) {
539                 part_start = ptr;
540                 length = 0;
541                 while (ptr < content_end) {
542                         ++ptr;
543                         ++length;
544                 }
545                 part_end = content_end;
546
547
548                 /* The following code will truncate the MIME part to the size
549                  * specified by the Content-length: header.   We have commented it
550                  * out because these headers have a tendency to be wrong.
551                  *
552                  *      if ( (content_length > 0) && (length > content_length) ) {
553                  *              length = content_length;
554                  *      }
555                  */
556
557                 /* Sometimes the "name" field is tacked on to Content-type,
558                  * and sometimes it's tacked on to Content-disposition.  Use
559                  * whichever one we have.
560                  */
561                 if (strlen(content_disposition_name) > strlen(content_type_name)) {
562                         name = content_disposition_name;
563                 }
564                 else {
565                         name = content_type_name;
566                 }
567         
568                 /* Ok, we've got a non-multipart part here, so do something with it.
569                  */
570                 mime_decode(partnum,
571                         part_start, length,
572                         content_type, charset, encoding, disposition, id,
573                         name, filename,
574                         CallBack, NULL, NULL,
575                         userdata, dont_decode
576                 );
577
578                 /*
579                  * Now if it's an encapsulated message/rfc822 then we have to recurse into it
580                  */
581                 if (!strcasecmp(content_type, "message/rfc822")) {
582
583                         if (PreMultiPartCallBack != NULL) {
584                                 PreMultiPartCallBack("", "", partnum, "",
585                                         NULL, content_type, charset,
586                                         0, encoding, id, userdata);
587                         }
588                         if (CallBack != NULL) {
589                                 if (strlen(partnum) > 0) {
590                                         snprintf(nested_partnum,
591                                                  sizeof nested_partnum,
592                                                  "%s.%d", partnum,
593                                                  ++part_seq);
594                                 }
595                                 else {
596                                         snprintf(nested_partnum,
597                                                  sizeof nested_partnum,
598                                                  "%d", ++part_seq);
599                                 }
600                                 the_mime_parser(nested_partnum,
601                                         part_start, part_end,
602                                         CallBack,
603                                         PreMultiPartCallBack,
604                                         PostMultiPartCallBack,
605                                         userdata,
606                                         dont_decode
607                                 );
608                         }
609                         if (PostMultiPartCallBack != NULL) {
610                                 PostMultiPartCallBack("", "", partnum, "", NULL,
611                                         content_type, charset, 0, encoding, id, userdata);
612                         }
613
614
615                 }
616
617         }
618
619 end_parser:     /* free the buffers!  end the oppression!! */
620         free(boundary);
621         free(startary);
622         free(endary);   
623         free(header);
624         free(content_type);
625         free(charset);
626         free(encoding);
627         free(content_type_name);
628         free(content_disposition_name);
629         free(filename);
630         free(disposition);
631         free(id);
632 }
633
634
635
636 /*
637  * Entry point for the MIME parser.
638  * (This function expects to be fed HEADERS + CONTENT)
639  * Note: NULL can be supplied as content_end; in this case, the message is
640  * considered to have ended when the parser encounters a 0x00 byte.
641  */
642 void mime_parser(char *content_start,
643                 char *content_end,
644
645                  void (*CallBack)
646                   (char *cbname,
647                    char *cbfilename,
648                    char *cbpartnum,
649                    char *cbdisp,
650                    void *cbcontent,
651                    char *cbtype,
652                    char *cbcharset,
653                    size_t cblength,
654                    char *cbencoding,
655                    char *cbid,
656                    void *cbuserdata),
657
658                  void (*PreMultiPartCallBack)
659                   (char *cbname,
660                    char *cbfilename,
661                    char *cbpartnum,
662                    char *cbdisp,
663                    void *cbcontent,
664                    char *cbtype,
665                    char *cbcharset,
666                    size_t cblength,
667                    char *cbencoding,
668                    char *cbid,
669                    void *cbuserdata),
670
671                  void (*PostMultiPartCallBack)
672                   (char *cbname,
673                    char *cbfilename,
674                    char *cbpartnum,
675                    char *cbdisp,
676                    void *cbcontent,
677                    char *cbtype,
678                    char *cbcharset,
679                    size_t cblength,
680                    char *cbencoding,
681                    char *cbid,
682                    void *cbuserdata),
683
684                   void *userdata,
685                   int dont_decode
686 )
687 {
688
689         the_mime_parser("", content_start, content_end,
690                         CallBack,
691                         PreMultiPartCallBack,
692                         PostMultiPartCallBack,
693                         userdata, dont_decode);
694 }
695
696
697
698
699
700
701 typedef struct _MimeGuess {
702         const char *Pattern;
703         size_t PatternLen;
704         long PatternOffset;
705         const char *MimeString;
706 } MimeGuess;
707
708 MimeGuess MyMimes [] = {
709         {
710                 "GIF",
711                 3,
712                 0,
713                 "image/gif"
714         },
715         {
716                 "\xff\xd8",
717                 2,
718                 0,
719                 "image/jpeg"
720         },
721         {
722                 "\x89PNG",
723                 4,
724                 0,
725                 "image/png"
726         },
727         { // last...
728                 "",
729                 0,
730                 0,
731                 ""
732         }
733 };
734
735
736 const char *GuessMimeType(const char *data, size_t dlen)
737 {
738         int MimeIndex = 0;
739
740         while (MyMimes[MimeIndex].PatternLen != 0)
741         {
742                 if ((MyMimes[MimeIndex].PatternLen + 
743                      MyMimes[MimeIndex].PatternOffset < dlen) &&
744                     strncmp(MyMimes[MimeIndex].Pattern, 
745                             &data[MyMimes[MimeIndex].PatternOffset], 
746                             MyMimes[MimeIndex].PatternLen) == 0)
747                 {
748                         return MyMimes[MimeIndex].MimeString;
749                 }
750                 MimeIndex ++;
751         }
752         /* 
753          * ok, our simple minded algorythm didn't find anything, 
754          * let the big chegger try it, he wil default to application/octet-stream
755          */
756         return (xdg_mime_get_mime_type_for_data(data, dlen));
757 }
758
759
760 const char* GuessMimeByFilename(const char *what, size_t len)
761 {
762         /* we know some hardcoded on our own, try them... */
763         if ((len > 3) && !strncasecmp(&what[len - 4], ".gif", 4))
764                 return "image/gif";
765         else if ((len > 2) && !strncasecmp(&what[len - 3], ".js", 3))
766                 return  "text/javascript";
767         else if ((len > 3) && !strncasecmp(&what[len - 4], ".txt", 4))
768                 return "text/plain";
769         else if ((len > 3) && !strncasecmp(&what[len - 4], ".css", 4))
770                 return "text/css";
771         else if ((len > 3) && !strncasecmp(&what[len - 4], ".jpg", 4))
772                 return "image/jpeg";
773         else if ((len > 3) && !strncasecmp(&what[len - 4], ".png", 4))
774                 return "image/png";
775         else if ((len > 3) && !strncasecmp(&what[len - 4], ".ico", 4))
776                 return "image/x-icon";
777         else if ((len > 3) && !strncasecmp(&what[len - 4], ".vcf", 4))
778                 return "text/x-vcard";
779         else if ((len > 4) && !strncasecmp(&what[len - 5], ".html", 5))
780                 return "text/html";
781         else if ((len > 3) && !strncasecmp(&what[len - 4], ".htm", 4))
782                 return "text/html";
783         else if ((len > 3) && !strncasecmp(&what[len - 4], ".wml", 4))
784                 return "text/vnd.wap.wml";
785         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmls", 5))
786                 return "text/vnd.wap.wmlscript";
787         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmlc", 5))
788                 return "application/vnd.wap.wmlc";
789         else if ((len > 5) && !strncasecmp(&what[len - 6], ".wmlsc", 6))
790                 return "application/vnd.wap.wmlscriptc";
791         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wbmp", 5))
792                 return "image/vnd.wap.wbmp";
793         else
794                 /* and let xdgmime do the fallback. */
795                 return xdg_mime_get_mime_type_from_file_name(what);
796 }
797
798 static HashList *IconHash = NULL;
799
800 typedef struct IconName IconName;
801
802 struct IconName {
803         char *FlatName;
804         char *FileName;
805 };
806
807 static void DeleteIcon(void *IconNamePtr)
808 {
809         IconName *Icon = (IconName*) IconNamePtr;
810         free(Icon->FlatName);
811         free(Icon->FileName);
812         free(Icon);
813 }
814
815 /*
816 static const char *PrintFlat(void *IconNamePtr)
817 {
818         IconName *Icon = (IconName*) IconNamePtr;
819         return Icon->FlatName;
820 }
821 static const char *PrintFile(void *IconNamePtr)
822 {
823         IconName *Icon = (IconName*) IconNamePtr;
824         return Icon->FileName;
825 }
826 */
827
828 #define GENSTR "x-generic"
829 #define IGNORE_PREFIX_1 "gnome-mime"
830 int LoadIconDir(const char *DirName)
831 {
832         DIR *filedir = NULL;
833         struct dirent *filedir_entry;
834         int d_namelen;
835         int d_without_ext;
836         IconName *Icon;
837
838         filedir = opendir (DirName);
839         IconHash = NewHash(1, NULL);
840         if (filedir == NULL) {
841                 return 0;
842         }
843
844         while ((filedir_entry = readdir(filedir)))
845         {
846                 char *MinorPtr;
847                 char *PStart;
848 #ifdef _DIRENT_HAVE_D_NAMELEN
849                 d_namelen = filedir_entry->d_namelen;
850 #else
851                 d_namelen = strlen(filedir_entry->d_name);
852 #endif
853                 d_without_ext = d_namelen;
854                 while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
855                         d_without_ext --;
856                 if ((d_without_ext == 0) || (d_namelen < 3))
857                         continue;
858
859                 if ((sizeof(IGNORE_PREFIX_1) < d_namelen) &&
860                     (strncmp(IGNORE_PREFIX_1, 
861                              filedir_entry->d_name, 
862                              sizeof(IGNORE_PREFIX_1) - 1) == 0)) {
863                         PStart = filedir_entry->d_name + sizeof(IGNORE_PREFIX_1);
864                         d_without_ext -= sizeof(IGNORE_PREFIX_1);
865                 }
866                 else {
867                         PStart = filedir_entry->d_name;
868                 }
869                 Icon = malloc(sizeof(IconName));
870
871                 Icon->FileName = malloc(d_namelen + 1);
872                 memcpy(Icon->FileName, filedir_entry->d_name, d_namelen + 1);
873
874                 Icon->FlatName = malloc(d_without_ext + 1);
875                 memcpy(Icon->FlatName, PStart, d_without_ext);
876                 Icon->FlatName[d_without_ext] = '\0';
877                 /* Try to find Minor type in image-jpeg */
878                 MinorPtr = strchr(Icon->FlatName, '-');
879                 if (MinorPtr != NULL) {
880                         size_t MinorLen;
881                         MinorLen = 1 + d_without_ext - (MinorPtr - Icon->FlatName + 1);
882                         if ((MinorLen == sizeof(GENSTR)) && 
883                             (strncmp(MinorPtr + 1, GENSTR, sizeof(GENSTR)) == 0)) {
884                                 /* ok, we found a generic filename. cut the generic. */
885                                 *MinorPtr = '\0';
886                                 d_without_ext = d_without_ext - (MinorPtr - Icon->FlatName);
887                         }
888                         else { /* Map the major / minor separator to / */
889                                 *MinorPtr = '/';
890                         }
891                 }
892
893 //              PrintHash(IconHash, PrintFlat, PrintFile);
894 //              printf("%s - %s\n", Icon->FlatName, Icon->FileName);
895                 Put(IconHash, Icon->FlatName, d_without_ext, Icon, DeleteIcon);
896 //              PrintHash(IconHash, PrintFlat, PrintFile);
897         }
898         closedir(filedir);
899         return 1;
900 }
901
902 const char *GetIconFilename(char *MimeType, size_t len)
903 {
904         void *vIcon;
905         IconName *Icon;
906         
907         if(IconHash == NULL)
908                 return NULL;
909
910         GetHash(IconHash, MimeType, len, &vIcon), Icon = (IconName*) vIcon;
911         /* didn't find the exact mimetype? try major only. */
912         if (Icon == NULL) {
913                 char * pMinor;
914                 pMinor = strchr(MimeType, '/');
915                 if (pMinor != NULL) {
916                         *pMinor = '\0';
917                         GetHash(IconHash, MimeType, pMinor - MimeType, &vIcon),
918                                 Icon = (IconName*) vIcon;
919                 }
920         }
921         if (Icon == NULL) {
922                 return NULL;
923         }
924
925         /*printf("Getting: [%s] == [%s] -> [%s]\n", MimeType, Icon->FlatName, Icon->FileName);*/
926         return Icon->FileName;
927 }
928
929 void ShutDownLibCitadelMime(void)
930 {
931         DeleteHash(&IconHash);
932 }