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