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