* mime_parser(), mime_decode(), the_mime_parser(); use typedef for callback functions...
[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                  MimeParserCallBackType CallBack,
178                  MimeParserCallBackType PreMultiPartCallBack,
179                  MimeParserCallBackType PostMultiPartCallBack,
180                  void *userdata,
181                  int dont_decode)
182 {
183
184         char *decoded;
185         size_t bytes_decoded = 0;
186
187         /* Some encodings aren't really encodings */
188         if (!strcasecmp(encoding, "7bit"))
189                 strcpy(encoding, "");
190         if (!strcasecmp(encoding, "8bit"))
191                 strcpy(encoding, "");
192         if (!strcasecmp(encoding, "binary"))
193                 strcpy(encoding, "");
194
195         /* If this part is not encoded, send as-is */
196         if ( (strlen(encoding) == 0) || (dont_decode)) {
197                 if (CallBack != NULL) {
198                         CallBack(name, filename, fixed_partnum(partnum),
199                                 disposition, part_start,
200                                 content_type, charset, length, encoding, id, userdata);
201                         }
202                 return;
203         }
204         
205         /* Fail silently if we hit an unknown encoding. */
206         if ((strcasecmp(encoding, "base64"))
207             && (strcasecmp(encoding, "quoted-printable"))) {
208                 return;
209         }
210
211         /*
212          * Allocate a buffer for the decoded data.  The output buffer is slightly
213          * larger than the input buffer; this assumes that the decoded data
214          * will never be significantly larger than the encoded data.  This is a
215          * safe assumption with base64, uuencode, and quoted-printable.
216          */
217         decoded = malloc(length + 32768);
218         if (decoded == NULL) {
219                 return;
220         }
221
222         if (!strcasecmp(encoding, "base64")) {
223                 bytes_decoded = CtdlDecodeBase64(decoded, part_start, length);
224         }
225         else if (!strcasecmp(encoding, "quoted-printable")) {
226                 bytes_decoded = CtdlDecodeQuotedPrintable(decoded, part_start, length);
227         }
228
229         if (bytes_decoded > 0) if (CallBack != NULL) {
230                 CallBack(name, filename, fixed_partnum(partnum),
231                         disposition, decoded,
232                         content_type, charset, bytes_decoded, "binary", id, userdata);
233         }
234
235         free(decoded);
236 }
237
238 /*
239  * Break out the components of a multipart message
240  * (This function expects to be fed HEADERS + CONTENT)
241  * Note: NULL can be supplied as content_end; in this case, the message is
242  * considered to have ended when the parser encounters a 0x00 byte.
243  */
244 void the_mime_parser(char *partnum,
245                      char *content_start, char *content_end,
246                      MimeParserCallBackType CallBack,
247                      MimeParserCallBackType PreMultiPartCallBack,
248                      MimeParserCallBackType PostMultiPartCallBack,
249                      void *userdata,
250                      int dont_decode)
251 {
252
253         char *ptr;
254         char *srch = NULL;
255         char *part_start, *part_end = NULL;
256         char buf[SIZ];
257         char *header;
258         char *boundary;
259         char *startary;
260         size_t startary_len = 0;
261         char *endary;
262         char *next_boundary;
263         char *content_type;
264         char *charset;
265         size_t content_type_len;
266         size_t content_length;
267         char *encoding;
268         char *disposition;
269         size_t disposition_len;
270         char *id;
271         char *name = NULL;
272         char *content_type_name;
273         char *content_disposition_name;
274         char *filename;
275         int is_multipart;
276         int part_seq = 0;
277         int i;
278         size_t length;
279         char nested_partnum[256];
280         int crlf_in_use = 0;
281         char *evaluate_crlf_ptr = NULL;
282         int buflen = 0;
283         int headerlen = 0;
284
285         ptr = content_start;
286         content_length = 0;
287
288         boundary = malloc(SIZ * 12);
289         *boundary = '\0';
290
291         startary = boundary + SIZ * 1;
292         *startary = '\0';
293
294         endary = boundary + SIZ * 2;
295         *endary = '\0';
296
297         header = boundary + SIZ * 3;
298         *header = '\0';
299
300         content_type = boundary + SIZ * 4;
301         *content_type = '\0';
302
303         charset = boundary + SIZ * 5;
304         *charset = '\0';
305
306         encoding = boundary + SIZ * 6;
307         *encoding = '\0';
308
309         content_type_name = boundary + SIZ * 7;
310         *content_type_name = '\0';
311
312         content_disposition_name = boundary + SIZ * 8;
313         *content_disposition_name = '\0';
314
315         filename = boundary + SIZ * 9;
316         *filename = '\0';
317
318         disposition = boundary + SIZ * 10;
319         *disposition = '\0';
320
321         id = boundary + SIZ * 11;
322         *id = '\0';
323
324         /* If the caller didn't supply an endpointer, generate one by measure */
325         if (content_end == NULL) {
326                 content_end = &content_start[strlen(content_start)];
327         }
328
329         /* Learn interesting things from the headers */
330         strcpy(header, "");
331         headerlen = 0;
332         do {
333                 ptr = memreadlinelen(ptr, buf, SIZ, &buflen);
334                 if (ptr >= content_end) {
335                         goto end_parser;
336                 }
337
338                 for (i = 0; i < buflen; ++i) {
339                         if (isspace(buf[i])) {
340                                 buf[i] = ' ';
341                         }
342                 }
343
344                 if (!isspace(buf[0])) {
345                         if (!strncasecmp(header, "Content-type:", 13)) {
346                                 memcpy (content_type, &header[13], headerlen - 12);
347                                 content_type_len = striplt (content_type);
348
349                                 extract_key(content_type_name, content_type, content_type_len, HKEY("name"), '=');
350                                 extract_key(charset,           content_type, content_type_len, HKEY("charset"), '=');
351                                 extract_key(boundary,          header,       headerlen,        HKEY("boundary"), '=');
352
353                                 /* Deal with weird headers */
354                                 if (strchr(content_type, ' '))
355                                         *(strchr(content_type, ' ')) = '\0';
356                                 if (strchr(content_type, ';'))
357                                         *(strchr(content_type, ';')) = '\0';
358                         }
359                         else if (!strncasecmp(header, "Content-Disposition:", 20)) {
360                                 memcpy (disposition, &header[20], headerlen - 19);
361                                 disposition_len = striplt(disposition);
362                                 extract_key(content_disposition_name, disposition, disposition_len,  HKEY("name"), '=');
363                                 extract_key(filename,                 disposition, disposition_len, HKEY("filename"), '=');
364                         }
365                         else if (!strncasecmp(header, "Content-ID:", 11)) {
366                                 strcpy(id, &header[11]);
367                                 striplt(id);
368                                 stripallbut(id, '<', '>');
369                         }
370                         else if (!strncasecmp(header, "Content-length: ", 15)) {
371                                 char clbuf[10];
372                                 safestrncpy(clbuf, &header[15], sizeof clbuf);
373                                 striplt(clbuf);
374                                 content_length = (size_t) atol(clbuf);
375                         }
376                         else if (!strncasecmp(header, "Content-transfer-encoding: ", 26)) {
377                                 strcpy(encoding, &header[26]);
378                                 striplt(encoding);
379                         }
380                         strcpy(header, "");
381                         headerlen = 0;
382                 }
383                 if ((headerlen + buflen + 2) < SIZ) {
384                         memcpy(&header[headerlen], buf, buflen);
385                         headerlen += buflen;
386                         header[headerlen] = '\0';
387                 }
388         } while ((!IsEmptyStr(buf)) && (*ptr != 0));
389
390         if (strchr(disposition, ';'))
391                 *(strchr(disposition, ';')) = '\0';
392         striplt(disposition);
393         if (strchr(content_type, ';'))
394                 *(strchr(content_type, ';')) = '\0';
395         striplt(content_type);
396
397         if (!IsEmptyStr(boundary)) {
398                 is_multipart = 1;
399         } else {
400                 is_multipart = 0;
401         }
402
403         /* If this is a multipart message, then recursively process it */
404         part_start = NULL;
405         if (is_multipart) {
406
407                 /* Tell the client about this message's multipartedness */
408                 if (PreMultiPartCallBack != NULL) {
409                         PreMultiPartCallBack("", "", partnum, "",
410                                 NULL, content_type, charset,
411                                 0, encoding, id, userdata);
412                 }
413
414                 /* Figure out where the boundaries are */
415                 startary_len = snprintf(startary, SIZ, "--%s", boundary);
416                 snprintf(endary, SIZ, "--%s--", boundary);
417
418                 part_start = NULL;
419                 do {
420                         next_boundary = NULL;
421                         for (srch=ptr; srch<content_end; ++srch) {
422                                 if (!memcmp(srch, startary, startary_len)) {
423                                         next_boundary = srch;
424                                         srch = content_end;
425                                 }
426                         }
427
428                         if ( (part_start != NULL) && (next_boundary != NULL) ) {
429                                 part_end = next_boundary;
430                                 --part_end;             /* omit the trailing LF */
431                                 if (crlf_in_use) {
432                                         --part_end;     /* omit the trailing CR */
433                                 }
434
435                                 if (!IsEmptyStr(partnum)) {
436                                         snprintf(nested_partnum,
437                                                  sizeof nested_partnum,
438                                                  "%s.%d", partnum,
439                                                  ++part_seq);
440                                 }
441                                 else {
442                                         snprintf(nested_partnum,
443                                                  sizeof nested_partnum,
444                                                  "%d", ++part_seq);
445                                 }
446                                 the_mime_parser(nested_partnum,
447                                             part_start, part_end,
448                                                 CallBack,
449                                                 PreMultiPartCallBack,
450                                                 PostMultiPartCallBack,
451                                                 userdata,
452                                                 dont_decode);
453                         }
454
455                         if (next_boundary != NULL) {
456                                 /* If we pass out of scope, don't attempt to
457                                  * read past the end boundary. */
458                                 if (!strcmp(next_boundary, endary)) {
459                                         ptr = content_end;
460                                 }
461                                 else {
462                                         /* Set up for the next part. */
463                                         part_start = strstr(next_boundary, "\n");
464                                         
465                                         /* Determine whether newlines are LF or CRLF */
466                                         evaluate_crlf_ptr = part_start;
467                                         --evaluate_crlf_ptr;
468                                         if (!memcmp(evaluate_crlf_ptr, "\r\n", 2)) {
469                                                 crlf_in_use = 1;
470                                         }
471                                         else {
472                                                 crlf_in_use = 0;
473                                         }
474
475                                         /* Advance past the LF ... now we're in the next part */
476                                         ++part_start;
477                                         ptr = part_start;
478                                 }
479                         }
480                         else {
481                                 /* Invalid end of multipart.  Bail out! */
482                                 ptr = content_end;
483                         }
484                 } while ( (ptr < content_end) && (next_boundary != NULL) );
485
486                 if (PostMultiPartCallBack != NULL) {
487                         PostMultiPartCallBack("", "", partnum, "", NULL,
488                                 content_type, charset, 0, encoding, id, userdata);
489                 }
490                 goto end_parser;
491         }
492
493         /* If it's not a multipart message, then do something with it */
494         if (!is_multipart) {
495                 part_start = ptr;
496                 length = 0;
497                 while (ptr < content_end) {
498                         ++ptr;
499                         ++length;
500                 }
501                 part_end = content_end;
502
503
504                 /* The following code will truncate the MIME part to the size
505                  * specified by the Content-length: header.   We have commented it
506                  * out because these headers have a tendency to be wrong.
507                  *
508                  *      if ( (content_length > 0) && (length > content_length) ) {
509                  *              length = content_length;
510                  *      }
511                  */
512
513                 /* Sometimes the "name" field is tacked on to Content-type,
514                  * and sometimes it's tacked on to Content-disposition.  Use
515                  * whichever one we have.
516                  */
517                 if (strlen(content_disposition_name) > strlen(content_type_name)) {
518                         name = content_disposition_name;
519                 }
520                 else {
521                         name = content_type_name;
522                 }
523         
524                 /* Ok, we've got a non-multipart part here, so do something with it.
525                  */
526                 mime_decode(partnum,
527                         part_start, length,
528                         content_type, charset, encoding, disposition, id,
529                         name, filename,
530                         CallBack, NULL, NULL,
531                         userdata, dont_decode
532                 );
533
534                 /*
535                  * Now if it's an encapsulated message/rfc822 then we have to recurse into it
536                  */
537                 if (!strcasecmp(content_type, "message/rfc822")) {
538
539                         if (PreMultiPartCallBack != NULL) {
540                                 PreMultiPartCallBack("", "", partnum, "",
541                                         NULL, content_type, charset,
542                                         0, encoding, id, userdata);
543                         }
544                         if (CallBack != NULL) {
545                                 if (strlen(partnum) > 0) {
546                                         snprintf(nested_partnum,
547                                                  sizeof nested_partnum,
548                                                  "%s.%d", partnum,
549                                                  ++part_seq);
550                                 }
551                                 else {
552                                         snprintf(nested_partnum,
553                                                  sizeof nested_partnum,
554                                                  "%d", ++part_seq);
555                                 }
556                                 the_mime_parser(nested_partnum,
557                                         part_start, part_end,
558                                         CallBack,
559                                         PreMultiPartCallBack,
560                                         PostMultiPartCallBack,
561                                         userdata,
562                                         dont_decode
563                                 );
564                         }
565                         if (PostMultiPartCallBack != NULL) {
566                                 PostMultiPartCallBack("", "", partnum, "", NULL,
567                                         content_type, charset, 0, encoding, id, userdata);
568                         }
569
570
571                 }
572
573         }
574
575 end_parser:     /* free the buffers!  end the oppression!! */
576         free(boundary);
577 }
578
579
580
581 /*
582  * Entry point for the MIME parser.
583  * (This function expects to be fed HEADERS + CONTENT)
584  * Note: NULL can be supplied as content_end; in this case, the message is
585  * considered to have ended when the parser encounters a 0x00 byte.
586  */
587 void mime_parser(char *content_start,
588                  char *content_end,
589                  MimeParserCallBackType CallBack,
590                  MimeParserCallBackType PreMultiPartCallBack,
591                  MimeParserCallBackType PostMultiPartCallBack,
592                  void *userdata,
593                  int dont_decode)
594 {
595
596         the_mime_parser("", content_start, content_end,
597                         CallBack,
598                         PreMultiPartCallBack,
599                         PostMultiPartCallBack,
600                         userdata, dont_decode);
601 }
602
603
604
605
606
607
608 typedef struct _MimeGuess {
609         const char *Pattern;
610         size_t PatternLen;
611         long PatternOffset;
612         const char *MimeString;
613 } MimeGuess;
614
615 MimeGuess MyMimes [] = {
616         {
617                 "GIF",
618                 3,
619                 0,
620                 "image/gif"
621         },
622         {
623                 "\xff\xd8",
624                 2,
625                 0,
626                 "image/jpeg"
627         },
628         {
629                 "\x89PNG",
630                 4,
631                 0,
632                 "image/png"
633         },
634         { // last...
635                 "",
636                 0,
637                 0,
638                 ""
639         }
640 };
641
642
643 const char *GuessMimeType(const char *data, size_t dlen)
644 {
645         int MimeIndex = 0;
646
647         while (MyMimes[MimeIndex].PatternLen != 0)
648         {
649                 if ((MyMimes[MimeIndex].PatternLen + 
650                      MyMimes[MimeIndex].PatternOffset < dlen) &&
651                     strncmp(MyMimes[MimeIndex].Pattern, 
652                             &data[MyMimes[MimeIndex].PatternOffset], 
653                             MyMimes[MimeIndex].PatternLen) == 0)
654                 {
655                         return MyMimes[MimeIndex].MimeString;
656                 }
657                 MimeIndex ++;
658         }
659         /* 
660          * ok, our simple minded algorythm didn't find anything, 
661          * let the big chegger try it, he wil default to application/octet-stream
662          */
663         return (xdg_mime_get_mime_type_for_data(data, dlen));
664 }
665
666
667 const char* GuessMimeByFilename(const char *what, size_t len)
668 {
669         /* we know some hardcoded on our own, try them... */
670         if ((len > 3) && !strncasecmp(&what[len - 4], ".gif", 4))
671                 return "image/gif";
672         else if ((len > 2) && !strncasecmp(&what[len - 3], ".js", 3))
673                 return  "text/javascript";
674         else if ((len > 3) && !strncasecmp(&what[len - 4], ".txt", 4))
675                 return "text/plain";
676         else if ((len > 3) && !strncasecmp(&what[len - 4], ".css", 4))
677                 return "text/css";
678         else if ((len > 3) && !strncasecmp(&what[len - 4], ".jpg", 4))
679                 return "image/jpeg";
680         else if ((len > 3) && !strncasecmp(&what[len - 4], ".png", 4))
681                 return "image/png";
682         else if ((len > 3) && !strncasecmp(&what[len - 4], ".ico", 4))
683                 return "image/x-icon";
684         else if ((len > 3) && !strncasecmp(&what[len - 4], ".vcf", 4))
685                 return "text/x-vcard";
686         else if ((len > 4) && !strncasecmp(&what[len - 5], ".html", 5))
687                 return "text/html";
688         else if ((len > 3) && !strncasecmp(&what[len - 4], ".htm", 4))
689                 return "text/html";
690         else if ((len > 3) && !strncasecmp(&what[len - 4], ".wml", 4))
691                 return "text/vnd.wap.wml";
692         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmls", 5))
693                 return "text/vnd.wap.wmlscript";
694         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmlc", 5))
695                 return "application/vnd.wap.wmlc";
696         else if ((len > 5) && !strncasecmp(&what[len - 6], ".wmlsc", 6))
697                 return "application/vnd.wap.wmlscriptc";
698         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wbmp", 5))
699                 return "image/vnd.wap.wbmp";
700         else
701                 /* and let xdgmime do the fallback. */
702                 return xdg_mime_get_mime_type_from_file_name(what);
703 }
704
705 static HashList *IconHash = NULL;
706
707 typedef struct IconName IconName;
708
709 struct IconName {
710         char *FlatName;
711         char *FileName;
712 };
713
714 static void DeleteIcon(void *IconNamePtr)
715 {
716         IconName *Icon = (IconName*) IconNamePtr;
717         free(Icon->FlatName);
718         free(Icon->FileName);
719         free(Icon);
720 }
721
722 /*
723 static const char *PrintFlat(void *IconNamePtr)
724 {
725         IconName *Icon = (IconName*) IconNamePtr;
726         return Icon->FlatName;
727 }
728 static const char *PrintFile(void *IconNamePtr)
729 {
730         IconName *Icon = (IconName*) IconNamePtr;
731         return Icon->FileName;
732 }
733 */
734
735 #define GENSTR "x-generic"
736 #define IGNORE_PREFIX_1 "gnome-mime"
737 int LoadIconDir(const char *DirName)
738 {
739         DIR *filedir = NULL;
740         struct dirent *filedir_entry;
741         int d_namelen;
742         int d_without_ext;
743         IconName *Icon;
744
745         filedir = opendir (DirName);
746         IconHash = NewHash(1, NULL);
747         if (filedir == NULL) {
748                 return 0;
749         }
750
751         while ((filedir_entry = readdir(filedir)))
752         {
753                 char *MinorPtr;
754                 char *PStart;
755 #ifdef _DIRENT_HAVE_D_NAMELEN
756                 d_namelen = filedir_entry->d_namelen;
757 #else
758                 d_namelen = strlen(filedir_entry->d_name);
759 #endif
760                 d_without_ext = d_namelen;
761                 while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
762                         d_without_ext --;
763                 if ((d_without_ext == 0) || (d_namelen < 3))
764                         continue;
765
766                 if ((sizeof(IGNORE_PREFIX_1) < d_namelen) &&
767                     (strncmp(IGNORE_PREFIX_1, 
768                              filedir_entry->d_name, 
769                              sizeof(IGNORE_PREFIX_1) - 1) == 0)) {
770                         PStart = filedir_entry->d_name + sizeof(IGNORE_PREFIX_1);
771                         d_without_ext -= sizeof(IGNORE_PREFIX_1);
772                 }
773                 else {
774                         PStart = filedir_entry->d_name;
775                 }
776                 Icon = malloc(sizeof(IconName));
777
778                 Icon->FileName = malloc(d_namelen + 1);
779                 memcpy(Icon->FileName, filedir_entry->d_name, d_namelen + 1);
780
781                 Icon->FlatName = malloc(d_without_ext + 1);
782                 memcpy(Icon->FlatName, PStart, d_without_ext);
783                 Icon->FlatName[d_without_ext] = '\0';
784                 /* Try to find Minor type in image-jpeg */
785                 MinorPtr = strchr(Icon->FlatName, '-');
786                 if (MinorPtr != NULL) {
787                         size_t MinorLen;
788                         MinorLen = 1 + d_without_ext - (MinorPtr - Icon->FlatName + 1);
789                         if ((MinorLen == sizeof(GENSTR)) && 
790                             (strncmp(MinorPtr + 1, GENSTR, sizeof(GENSTR)) == 0)) {
791                                 /* ok, we found a generic filename. cut the generic. */
792                                 *MinorPtr = '\0';
793                                 d_without_ext = d_without_ext - (MinorPtr - Icon->FlatName);
794                         }
795                         else { /* Map the major / minor separator to / */
796                                 *MinorPtr = '/';
797                         }
798                 }
799
800 //              PrintHash(IconHash, PrintFlat, PrintFile);
801 //              printf("%s - %s\n", Icon->FlatName, Icon->FileName);
802                 Put(IconHash, Icon->FlatName, d_without_ext, Icon, DeleteIcon);
803 //              PrintHash(IconHash, PrintFlat, PrintFile);
804         }
805         closedir(filedir);
806         return 1;
807 }
808
809 const char *GetIconFilename(char *MimeType, size_t len)
810 {
811         void *vIcon;
812         IconName *Icon;
813         
814         if(IconHash == NULL)
815                 return NULL;
816
817         GetHash(IconHash, MimeType, len, &vIcon), Icon = (IconName*) vIcon;
818         /* didn't find the exact mimetype? try major only. */
819         if (Icon == NULL) {
820                 char * pMinor;
821                 pMinor = strchr(MimeType, '/');
822                 if (pMinor != NULL) {
823                         *pMinor = '\0';
824                         GetHash(IconHash, MimeType, pMinor - MimeType, &vIcon),
825                                 Icon = (IconName*) vIcon;
826                 }
827         }
828         if (Icon == NULL) {
829                 return NULL;
830         }
831
832         /*printf("Getting: [%s] == [%s] -> [%s]\n", MimeType, Icon->FlatName, Icon->FileName);*/
833         return Icon->FileName;
834 }
835
836 void ShutDownLibCitadelMime(void)
837 {
838         DeleteHash(&IconHash);
839 }