* ignore content-length headers, since they tend to lie at us
[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                 /* Content length headers tend to be wrong since mails
506                  * gain/loose linefeeds/QP all over their way
507                  * so, we ignore the size for now and trust our own calculation.
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,
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, 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, userdata);
568                         }
569
570
571                 }
572
573         }
574
575 end_parser:     /* free the buffers!  end the oppression!! */
576         free(boundary);
577         free(startary);
578         free(endary);   
579         free(header);
580         free(content_type);
581         free(charset);
582         free(encoding);
583         free(content_type_name);
584         free(content_disposition_name);
585         free(filename);
586         free(disposition);
587 }
588
589
590
591 /*
592  * Entry point for the MIME parser.
593  * (This function expects to be fed HEADERS + CONTENT)
594  * Note: NULL can be supplied as content_end; in this case, the message is
595  * considered to have ended when the parser encounters a 0x00 byte.
596  */
597 void mime_parser(char *content_start,
598                 char *content_end,
599
600                  void (*CallBack)
601                   (char *cbname,
602                    char *cbfilename,
603                    char *cbpartnum,
604                    char *cbdisp,
605                    void *cbcontent,
606                    char *cbtype,
607                    char *cbcharset,
608                    size_t cblength,
609                    char *cbencoding,
610                    void *cbuserdata),
611
612                  void (*PreMultiPartCallBack)
613                   (char *cbname,
614                    char *cbfilename,
615                    char *cbpartnum,
616                    char *cbdisp,
617                    void *cbcontent,
618                    char *cbtype,
619                    char *cbcharset,
620                    size_t cblength,
621                    char *cbencoding,
622                    void *cbuserdata),
623
624                  void (*PostMultiPartCallBack)
625                   (char *cbname,
626                    char *cbfilename,
627                    char *cbpartnum,
628                    char *cbdisp,
629                    void *cbcontent,
630                    char *cbtype,
631                    char *cbcharset,
632                    size_t cblength,
633                    char *cbencoding,
634                    void *cbuserdata),
635
636                   void *userdata,
637                   int dont_decode
638 )
639 {
640
641         the_mime_parser("", content_start, content_end,
642                         CallBack,
643                         PreMultiPartCallBack,
644                         PostMultiPartCallBack,
645                         userdata, dont_decode);
646 }
647
648
649
650
651
652
653 typedef struct _MimeGuess {
654         const char *Pattern;
655         size_t PatternLen;
656         long PatternOffset;
657         const char *MimeString;
658 } MimeGuess;
659
660 MimeGuess MyMimes [] = {
661         {
662                 "GIF",
663                 3,
664                 0,
665                 "image/gif"
666         },
667         {
668                 "\xff\xd8",
669                 2,
670                 0,
671                 "image/jpeg"
672         },
673         {
674                 "\x89PNG",
675                 4,
676                 0,
677                 "image/png"
678         },
679         { // last...
680                 "",
681                 0,
682                 0,
683                 ""
684         }
685 };
686
687
688 const char *GuessMimeType(char *data, size_t dlen)
689 {
690         int MimeIndex = 0;
691
692         while (MyMimes[MimeIndex].PatternLen != 0)
693         {
694                 if ((MyMimes[MimeIndex].PatternLen + 
695                      MyMimes[MimeIndex].PatternOffset < dlen) &&
696                     strncmp(MyMimes[MimeIndex].Pattern, 
697                             &data[MyMimes[MimeIndex].PatternOffset], 
698                             MyMimes[MimeIndex].PatternLen) == 0)
699                 {
700                         return MyMimes[MimeIndex].MimeString;
701                 }
702                 MimeIndex ++;
703         }
704         /* 
705          * ok, our simple minded algorythm didn't find anything, 
706          * let the big chegger try it, he wil default to application/octet-stream
707          */
708         return (xdg_mime_get_mime_type_for_data(data, dlen));
709 }
710
711
712 const char* GuessMimeByFilename(const char *what, size_t len)
713 {
714         /* we know some hardcoded on our own, try them... */
715         if (!strncasecmp(&what[len - 4], ".gif", 4))
716                 return "image/gif";
717         else if (!strncasecmp(&what[len - 3], ".js", 3))
718                 return  "text/javascript";
719         else if (!strncasecmp(&what[len - 4], ".txt", 4))
720                 return "text/plain";
721         else if (!strncasecmp(&what[len - 4], ".css", 4))
722                 return "text/css";
723         else if (!strncasecmp(&what[len - 4], ".jpg", 4))
724                 return "image/jpeg";
725         else if (!strncasecmp(&what[len - 4], ".png", 4))
726                 return "image/png";
727         else if (!strncasecmp(&what[len - 4], ".ico", 4))
728                 return "image/x-icon";
729         else if (!strncasecmp(&what[len - 5], ".html", 5))
730                 return "text/html";
731         else if (!strncasecmp(&what[len - 4], ".htm", 4))
732                 return "text/html";
733         else if (!strncasecmp(&what[len - 4], ".wml", 4))
734                 return "text/vnd.wap.wml";
735         else if (!strncasecmp(&what[len - 5], ".wmls", 5))
736                 return "text/vnd.wap.wmlscript";
737         else if (!strncasecmp(&what[len - 5], ".wmlc", 5))
738                 return "application/vnd.wap.wmlc";
739         else if (!strncasecmp(&what[len - 6], ".wmlsc", 6))
740                 return "application/vnd.wap.wmlscriptc";
741         else if (!strncasecmp(&what[len - 5], ".wbmp", 5))
742                 return "image/vnd.wap.wbmp";
743         else
744                 /* and let xdgmime do the fallback. */
745                 return xdg_mime_get_mime_type_from_file_name(what);
746 }
747
748 static HashList *IconHash = NULL;
749
750 typedef struct IconName IconName;
751
752 struct IconName {
753         char *FlatName;
754         char *FileName;
755 };
756
757 static void DeleteIcon(void *IconNamePtr)
758 {
759         IconName *Icon = (IconName*) IconNamePtr;
760         free(Icon->FlatName);
761         free(Icon->FileName);
762 }
763
764 static const char *PrintFlat(void *IconNamePtr)
765 {
766         IconName *Icon = (IconName*) IconNamePtr;
767         return Icon->FlatName;
768 }
769 static const char *PrintFile(void *IconNamePtr)
770 {
771         IconName *Icon = (IconName*) IconNamePtr;
772         return Icon->FileName;
773 }
774 #define GENSTR "x-generic"
775 #define IGNORE_PREFIX_1 "gnome-mime"
776 int LoadIconDir(const char *DirName)
777 {
778         DIR *filedir = NULL;
779         struct dirent *filedir_entry;
780         int d_namelen;
781         int d_without_ext;
782         IconName *Icon;
783
784         filedir = opendir (DirName);
785         IconHash = NewHash(1, NULL);
786         if (filedir == NULL) {
787                 return 0;
788         }
789
790         while ((filedir_entry = readdir(filedir)))
791         {
792                 char *MinorPtr;
793                 char *PStart;
794 #ifdef _DIRENT_HAVE_D_NAMELEN
795                 d_namelen = filedir_entry->d_namelen;
796 #else
797                 d_namelen = strlen(filedir_entry->d_name);
798 #endif
799                 d_without_ext = d_namelen;
800                 while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
801                         d_without_ext --;
802                 if ((d_without_ext == 0) || (d_namelen < 3))
803                         continue;
804
805                 if ((sizeof(IGNORE_PREFIX_1) < d_namelen) &&
806                     (strncmp(IGNORE_PREFIX_1, 
807                              filedir_entry->d_name, 
808                              sizeof(IGNORE_PREFIX_1) - 1) == 0)) {
809                         PStart = filedir_entry->d_name + sizeof(IGNORE_PREFIX_1);
810                         d_without_ext -= sizeof(IGNORE_PREFIX_1);
811                 }
812                 else {
813                         PStart = filedir_entry->d_name;
814                 }
815                 Icon = malloc(sizeof(IconName));
816
817                 Icon->FileName = malloc(d_namelen + 1);
818                 memcpy(Icon->FileName, filedir_entry->d_name, d_namelen + 1);
819
820                 Icon->FlatName = malloc(d_without_ext + 1);
821                 memcpy(Icon->FlatName, PStart, d_without_ext);
822                 Icon->FlatName[d_without_ext] = '\0';
823                 /* Try to find Minor type in image-jpeg */
824                 MinorPtr = strchr(Icon->FlatName, '-');
825                 if (MinorPtr != NULL) {
826                         size_t MinorLen;
827                         MinorLen = 1 + d_without_ext - (MinorPtr - Icon->FlatName + 1);
828                         if ((MinorLen == sizeof(GENSTR)) && 
829                             (strncmp(MinorPtr + 1, GENSTR, sizeof(GENSTR)) == 0)) {
830                                 /* ok, we found a generic filename. cut the generic. */
831                                 *MinorPtr = '\0';
832                                 d_without_ext = d_without_ext - (MinorPtr - Icon->FlatName);
833                         }
834                         else { /* Map the major / minor separator to / */
835                                 *MinorPtr = '/';
836                         }
837                 }
838
839 //              PrintHash(IconHash, PrintFlat, PrintFile);
840 //              printf("%s - %s\n", Icon->FlatName, Icon->FileName);
841                 Put(IconHash, Icon->FlatName, d_without_ext, Icon, DeleteIcon);
842 //              PrintHash(IconHash, PrintFlat, PrintFile);
843         }
844         return 1;
845 }
846
847 const char *GetIconFilename(char *MimeType, size_t len)
848 {
849         void *vIcon;
850         IconName *Icon;
851         
852         if(IconHash == NULL)
853                 return NULL;
854
855         GetHash(IconHash, MimeType, len, &vIcon), Icon = (IconName*) vIcon;
856         /* didn't find the exact mimetype? try major only. */
857         if (Icon == NULL) {
858                 char * pMinor;
859                 pMinor = strchr(MimeType, '/');
860                 if (pMinor != NULL) {
861                         *pMinor = '\0';
862                         GetHash(IconHash, MimeType, pMinor - MimeType, &vIcon),
863                                 Icon = (IconName*) vIcon;
864                 }
865         }
866         if (Icon == NULL) {
867                 return NULL;
868         }
869
870         /*printf("Getting: [%s] == [%s] -> [%s]\n", MimeType, Icon->FlatName, Icon->FileName);*/
871         return Icon->FileName;
872 }
873
874 void ShutDownLibCitadel(void)
875 {
876         DeleteHash(&IconHash);
877 }