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