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