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