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