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