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