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