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