Branch data Line data Source code
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 : 9988 : long extract_key(char *target, char *source, long sourcelen, char *key, long keylen, char KeyEnd)
56 : : {
57 : 9988 : char *sptr, *ptr = NULL;
58 : 9988 : int double_quotes = 0;
59 : 9988 : long RealKeyLen = keylen;
60 : :
61 : 9988 : sptr = source;
62 : :
63 [ + + ]: 19976 : while (sptr != NULL)
64 : : {
65 : 9988 : ptr = bmstrcasestr_len(sptr, sourcelen - (sptr - source),
66 : : key, keylen);
67 [ + + ]: 9988 : if(ptr != NULL)
68 : : {
69 [ - + ]: 3052 : while (isspace(*(ptr + RealKeyLen)))
70 : 0 : RealKeyLen ++;
71 [ + - ]: 3052 : if (*(ptr + RealKeyLen) == KeyEnd)
72 : : {
73 : 3052 : sptr = NULL;
74 : 3052 : RealKeyLen ++;
75 : : }
76 : : else
77 : : {
78 : 3052 : sptr = ptr + RealKeyLen + 1;
79 : : }
80 : : }
81 : : else
82 : 6936 : sptr = ptr;
83 : : }
84 [ + + ]: 9988 : if (ptr == NULL) {
85 : 6936 : *target = '\0';
86 : 6936 : return 0;
87 : : }
88 : 3052 : strcpy(target, (ptr + RealKeyLen));
89 : :
90 [ + + ]: 61300 : 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 [ + + ][ + + ]: 58248 : if ( (double_quotes != 1) && (*ptr == ';')) {
94 : 149 : *ptr = 0;
95 : : }
96 : :
97 : : /* if we find double quotes, we've got a great set of string boundaries */
98 [ + + ]: 58248 : if (*ptr == '\"') {
99 : 6006 : ++double_quotes;
100 [ + + ]: 6006 : if (double_quotes == 1) {
101 : 2881 : strcpy(ptr, ptr+1);
102 : : }
103 : : else {
104 : 3125 : *ptr = 0;
105 : : }
106 : : }
107 : : }
108 : 3052 : *ptr = '\0';
109 : 9988 : return ptr - target;
110 : : }
111 : :
112 : :
113 : : /*
114 : : * For non-multipart messages, we need to generate a quickie partnum of "1"
115 : : * to return to callback functions. Some callbacks demand it.
116 : : */
117 : 2455 : char *fixed_partnum(char *supplied_partnum) {
118 [ - + ]: 2455 : if (supplied_partnum == NULL) return "1";
119 [ + + ]: 2455 : if (strlen(supplied_partnum)==0) return "1";
120 : 2455 : return supplied_partnum;
121 : : }
122 : :
123 : :
124 : 16500 : static inline unsigned int _decode_hex(const char *Source)
125 : : {
126 : 16500 : int ret = '?';
127 : : unsigned char LO_NIBBLE;
128 : : unsigned char HI_NIBBLE;
129 : :
130 : 16500 : HI_NIBBLE = FromHexTable[(unsigned char) *Source];
131 : 16500 : LO_NIBBLE = FromHexTable[(unsigned char) *(Source+1)];
132 : :
133 [ + - ][ - + ]: 16500 : if ((LO_NIBBLE == 0xFF) || (LO_NIBBLE == 0xFF))
134 : 0 : return ret;
135 : 16500 : ret = HI_NIBBLE;
136 : 16500 : ret = ret << 4;
137 : 16500 : ret = ret | LO_NIBBLE;
138 : 16500 : return ret;
139 : : }
140 : :
141 : 0 : unsigned int decode_hex(char *Source) {return _decode_hex(Source);}
142 : :
143 : : /*
144 : : * Convert "quoted-printable" to binary. Returns number of bytes decoded.
145 : : * according to RFC2045 section 6.7
146 : : */
147 : 125 : int CtdlDecodeQuotedPrintable(char *decoded, char *encoded, int sourcelen) {
148 : : unsigned int ch;
149 : 125 : int decoded_length = 0;
150 : 125 : int pos = 0;
151 : :
152 [ + + ]: 314572 : while (pos < sourcelen)
153 : : {
154 [ + + ]: 314447 : if (*(encoded + pos) == '=')
155 : : {
156 : 18985 : pos ++;
157 [ + + ]: 18985 : if (*(encoded + pos) == '\n')
158 : : {
159 : 2485 : pos ++;
160 : : }
161 [ - + ]: 16500 : else if (*(encoded + pos) == '\r')
162 : : {
163 : 0 : pos ++;
164 [ # # ]: 0 : if (*(encoded + pos) == '\n')
165 : 0 : pos++;
166 : : }
167 : : else
168 : : {
169 : 16500 : ch = 0;
170 : 16500 : ch = _decode_hex(&encoded[pos]);
171 : 16500 : pos += 2;
172 : 18985 : decoded[decoded_length++] = ch;
173 : : }
174 : : }
175 : : else
176 : : {
177 : 295462 : decoded[decoded_length++] = encoded[pos];
178 : 295462 : pos += 1;
179 : : }
180 : : }
181 : 125 : decoded[decoded_length] = 0;
182 : 125 : return(decoded_length);
183 : : }
184 : :
185 : :
186 : : /*
187 : : * Given a message or message-part body and a length, handle any necessary
188 : : * decoding and pass the request up the stack.
189 : : */
190 : 2623 : void mime_decode(char *partnum,
191 : : char *part_start, size_t length,
192 : : char *content_type, char *charset, char *encoding,
193 : : char *disposition,
194 : : char *id,
195 : : char *name, char *filename,
196 : : MimeParserCallBackType CallBack,
197 : : MimeParserCallBackType PreMultiPartCallBack,
198 : : MimeParserCallBackType PostMultiPartCallBack,
199 : : void *userdata,
200 : : int dont_decode)
201 : : {
202 : :
203 : : char *decoded;
204 : 2623 : size_t bytes_decoded = 0;
205 : :
206 : : /* Some encodings aren't really encodings */
207 [ + + ]: 2623 : if (!strcasecmp(encoding, "7bit"))
208 : 156 : strcpy(encoding, "");
209 [ + + ]: 2623 : if (!strcasecmp(encoding, "8bit"))
210 : 18 : strcpy(encoding, "");
211 [ - + ]: 2623 : if (!strcasecmp(encoding, "binary"))
212 : 0 : strcpy(encoding, "");
213 : :
214 : : /* If this part is not encoded, send as-is */
215 [ + + ][ + + ]: 2623 : if ( (strlen(encoding) == 0) || (dont_decode)) {
216 [ + + ]: 1224 : if (CallBack != NULL) {
217 : 1114 : CallBack(name,
218 : : filename,
219 : : fixed_partnum(partnum),
220 : : disposition,
221 : : part_start,
222 : : content_type,
223 : : charset,
224 : : length,
225 : : encoding,
226 : : id,
227 : : userdata);
228 : : }
229 : 1224 : return;
230 : : }
231 : :
232 : : /* Fail silently if we hit an unknown encoding. */
233 [ + + ][ + + ]: 1399 : if ((strcasecmp(encoding, "base64"))
234 : 167 : && (strcasecmp(encoding, "quoted-printable"))) {
235 : 58 : return;
236 : : }
237 : :
238 : : /*
239 : : * Allocate a buffer for the decoded data. The output buffer is slightly
240 : : * larger than the input buffer; this assumes that the decoded data
241 : : * will never be significantly larger than the encoded data. This is a
242 : : * safe assumption with base64, uuencode, and quoted-printable.
243 : : */
244 : 1341 : decoded = malloc(length + 32768);
245 [ - + ]: 1341 : if (decoded == NULL) {
246 : 0 : return;
247 : : }
248 : :
249 [ + + ]: 1341 : if (!strcasecmp(encoding, "base64")) {
250 : 1232 : bytes_decoded = CtdlDecodeBase64(decoded, part_start, length);
251 : : }
252 [ + - ]: 109 : else if (!strcasecmp(encoding, "quoted-printable")) {
253 : 109 : bytes_decoded = CtdlDecodeQuotedPrintable(decoded, part_start, length);
254 : : }
255 : :
256 [ + - ][ + - ]: 1341 : if (bytes_decoded > 0) if (CallBack != NULL) {
257 : : char encoding_buf[SIZ];
258 : :
259 : 1341 : strcpy(encoding_buf, "binary");
260 : 1341 : CallBack(name,
261 : : filename,
262 : : fixed_partnum(partnum),
263 : : disposition,
264 : : decoded,
265 : : content_type,
266 : : charset,
267 : : bytes_decoded,
268 : : encoding_buf,
269 : : id,
270 : : userdata);
271 : : }
272 : :
273 : 2623 : free(decoded);
274 : : }
275 : :
276 : : /*
277 : : * this is the extract of mime_decode which can be called if 'dont_decode' was set;
278 : : * to save the cpu intense process of decoding to the time when it realy wants the content.
279 : : * returns:
280 : : * - > 0 we decoded something, its on *decoded, you need to free it.
281 : : * - = 0 no need to decode stuff. *decoded will be NULL.
282 : : * - < 0 an error occured, either an unknown encoding, or alloc failed. no need to free.
283 : : */
284 : 103 : int mime_decode_now (char *part_start,
285 : : size_t length,
286 : : char *encoding,
287 : : char **decoded,
288 : : size_t *bytes_decoded)
289 : : {
290 : 103 : *bytes_decoded = 0;
291 : 103 : *decoded = NULL;
292 : : /* Some encodings aren't really encodings */
293 [ - + ]: 103 : if (!strcasecmp(encoding, "7bit"))
294 : 0 : strcpy(encoding, "");
295 [ - + ]: 103 : if (!strcasecmp(encoding, "8bit"))
296 : 0 : strcpy(encoding, "");
297 [ + + ]: 103 : if (!strcasecmp(encoding, "binary"))
298 : 51 : strcpy(encoding, "");
299 : :
300 : : /* If this part is not encoded, send as-is */
301 [ + - ]: 103 : if (strlen(encoding) == 0) {
302 : 103 : return 0;
303 : : }
304 : :
305 : :
306 : : /* Fail if we hit an unknown encoding. */
307 [ # # ][ # # ]: 0 : if ((strcasecmp(encoding, "base64"))
308 : 0 : && (strcasecmp(encoding, "quoted-printable"))) {
309 : 0 : return -1;
310 : : }
311 : :
312 : : /*
313 : : * Allocate a buffer for the decoded data. The output buffer is slightly
314 : : * larger than the input buffer; this assumes that the decoded data
315 : : * will never be significantly larger than the encoded data. This is a
316 : : * safe assumption with base64, uuencode, and quoted-printable.
317 : : */
318 : 0 : *decoded = malloc(length + 32768);
319 [ # # ]: 0 : if (decoded == NULL) {
320 : 0 : return -1;
321 : : }
322 : :
323 [ # # ]: 0 : if (!strcasecmp(encoding, "base64")) {
324 : 0 : *bytes_decoded = CtdlDecodeBase64(*decoded, part_start, length);
325 : 0 : return 1;
326 : : }
327 [ # # ]: 0 : else if (!strcasecmp(encoding, "quoted-printable")) {
328 : 0 : *bytes_decoded = CtdlDecodeQuotedPrintable(*decoded, part_start, length);
329 : 0 : return 1;
330 : : }
331 : 103 : return -1;
332 : : }
333 : :
334 : : typedef enum _eIntMimeHdrs {
335 : : boundary,
336 : : startary,
337 : : endary,
338 : : content_type,
339 : : charset,
340 : : encoding,
341 : : content_type_name,
342 : : content_disposition_name,
343 : : filename,
344 : : disposition,
345 : : id,
346 : : eMax /* don't move ! */
347 : : } eIntMimeHdrs;
348 : :
349 : : typedef struct _CBufStr {
350 : : char Key[SIZ];
351 : : long len;
352 : : }CBufStr;
353 : :
354 : : typedef struct _interesting_mime_headers {
355 : : CBufStr b[eMax];
356 : : long content_length;
357 : : long is_multipart;
358 : : } interesting_mime_headers;
359 : :
360 : :
361 : 3564 : static void FlushInterestingMimes(interesting_mime_headers *m)
362 : : {
363 : : int i;
364 : :
365 [ + + ]: 42768 : for (i = 0; i < eMax; i++) {
366 : 39204 : m->b[i].Key[0] = '\0';
367 : 39204 : m->b[i].len = 0;
368 : : }
369 : 3564 : m->content_length = -1;
370 : 3564 : }
371 : 908 : static interesting_mime_headers *InitInterestingMimes(void)
372 : : {
373 : : interesting_mime_headers *m;
374 : 908 : m = (interesting_mime_headers*) malloc( sizeof(interesting_mime_headers));
375 : :
376 : 908 : FlushInterestingMimes(m);
377 : :
378 : 908 : return m;
379 : : }
380 : :
381 : :
382 : 3974 : static long parse_MimeHeaders(interesting_mime_headers *m,
383 : : char** pcontent_start,
384 : : char *content_end)
385 : : {
386 : : char buf[SIZ];
387 : : char header[SIZ];
388 : : long headerlen;
389 : : char *ptr, *pch;
390 : 3974 : int buflen = 0;
391 : : int i;
392 : :
393 : : /* Learn interesting things from the headers */
394 : 3974 : ptr = *pcontent_start;
395 : 3974 : *header = '\0';
396 : 3974 : headerlen = 0;
397 : : do {
398 : 19370 : ptr = memreadlinelen(ptr, buf, SIZ, &buflen);
399 : :
400 [ + + ]: 634801 : for (i = 0; i < buflen; ++i) {
401 [ + + ]: 615431 : if (isspace(buf[i])) {
402 : 35548 : buf[i] = ' ';
403 : : }
404 : : }
405 : :
406 [ + + ][ + + ]: 19370 : if (!isspace(buf[0]) && (headerlen > 0)) {
407 [ + + ]: 12684 : if (!strncasecmp(header, "Content-type:", 13)) {
408 : 2626 : memcpy (m->b[content_type].Key, &header[13], headerlen - 12);
409 : 2626 : m->b[content_type].len = striplt (m->b[content_type].Key);
410 : :
411 : 2626 : m->b[content_type_name].len = extract_key(m->b[content_type_name].Key, CKEY(m->b[content_type]), HKEY("name"), '=');
412 : 2626 : m->b[charset].len = extract_key(m->b[charset].Key, CKEY(m->b[content_type]), HKEY("charset"), '=');
413 : 2626 : m->b[boundary].len = extract_key(m->b[boundary].Key, header, headerlen, HKEY("boundary"), '=');
414 : :
415 : : /* Deal with weird headers */
416 : 2626 : pch = strchr(m->b[content_type].Key, ' ');
417 [ + + ]: 2626 : if (pch != NULL) {
418 : 1930 : *pch = '\0';
419 : 1930 : m->b[content_type].len = m->b[content_type].Key - pch;
420 : : }
421 : 2626 : pch = strchr(m->b[content_type].Key, ';');
422 [ + + ]: 2626 : if (pch != NULL) {
423 : 1930 : *pch = '\0';
424 : 2626 : m->b[content_type].len = m->b[content_type].Key - pch;
425 : : }
426 : : }
427 [ + + ]: 10058 : else if (!strncasecmp(header, "Content-Disposition:", 20)) {
428 : 1055 : memcpy (m->b[disposition].Key, &header[20], headerlen - 19);
429 : 1055 : m->b[disposition].len = striplt(m->b[disposition].Key);
430 : :
431 : 1055 : m->b[content_disposition_name].len = extract_key(m->b[content_disposition_name].Key, CKEY(m->b[disposition]), HKEY("name"), '=');
432 : 1055 : m->b[filename].len = extract_key(m->b[filename].Key, CKEY(m->b[disposition]), HKEY("filename"), '=');
433 : 1055 : pch = strchr(m->b[disposition].Key, ';');
434 [ + + ]: 1055 : if (pch != NULL) *pch = '\0';
435 : 1055 : m->b[disposition].len = striplt(m->b[disposition].Key);
436 : : }
437 [ + + ]: 9003 : else if (!strncasecmp(header, "Content-ID:", 11)) {
438 : 1240 : memcpy(m->b[id].Key, &header[11], headerlen);
439 : 1240 : striplt(m->b[id].Key);
440 : 1240 : m->b[id].len = stripallbut(m->b[id].Key, '<', '>');
441 : : }
442 [ + + ]: 7763 : else if (!strncasecmp(header, "Content-length: ", 15)) {
443 : : char *clbuf;
444 : 66 : clbuf = &header[15];
445 [ + + ]: 132 : while (isspace(*clbuf))
446 : 66 : clbuf ++;
447 : 66 : m->content_length = (size_t) atol(clbuf);
448 : : }
449 [ + + ]: 7697 : else if (!strncasecmp(header, "Content-transfer-encoding: ", 26)) {
450 : 1749 : memcpy(m->b[encoding].Key, &header[26], headerlen - 26);
451 : 1749 : m->b[encoding].len = striplt(m->b[encoding].Key);
452 : : }
453 : 12684 : *header = '\0';
454 : 12684 : headerlen = 0;
455 : : }
456 [ + - ]: 19370 : if ((headerlen + buflen + 2) < SIZ) {
457 : 19370 : memcpy(&header[headerlen], buf, buflen);
458 : 19370 : headerlen += buflen;
459 : 19370 : header[headerlen] = '\0';
460 : : }
461 [ + + ]: 19370 : if (ptr >= content_end) {
462 : 64 : return -1;
463 : : }
464 [ + + ][ + - ]: 19306 : } while ((!IsEmptyStr(buf)) && (*ptr != 0));
465 : :
466 : 3910 : m->is_multipart = m->b[boundary].len != 0;
467 : 3910 : *pcontent_start = ptr;
468 : :
469 : 3974 : return 0;
470 : : }
471 : :
472 : :
473 : 3446 : static int IsAsciiEncoding(interesting_mime_headers *m)
474 : : {
475 : :
476 [ + + ][ + + ]: 3446 : if ((m->b[encoding].len != 0) &&
477 : 1724 : (strcasecmp(m->b[encoding].Key, "base64") == 0))
478 : 1344 : return 1;
479 [ + + ][ + + ]: 2102 : if ((m->b[encoding].len != 0) &&
480 : 380 : (strcmp(m->b[encoding].Key, "quoted-printable") == 0))
481 : 134 : return 1;
482 : :
483 : 3446 : return 0;
484 : : }
485 : :
486 : 3446 : static char *FindNextContent(char *ptr,
487 : : char *content_end,
488 : : interesting_mime_headers *SubMimeHeaders,
489 : : interesting_mime_headers *m)
490 : : {
491 : : char *next_boundary;
492 : : char tmp;
493 : :
494 [ + + ]: 3446 : if (IsAsciiEncoding(SubMimeHeaders)) {
495 : 1478 : tmp = *content_end;
496 : 1478 : *content_end = '\0';
497 : :
498 : : /**
499 : : * ok, if we have a content length of the mime part,
500 : : * try skipping the content on the search for the next
501 : : * boundary. since we don't trust the content_length
502 : : * to be all accurate, and suspect it to lose one digit
503 : : * per line with a line length of 80 chars, we need
504 : : * to start searching a little before..
505 : : */
506 : :
507 [ - + ][ # # ]: 1478 : if ((SubMimeHeaders->content_length != -1) &&
508 : 0 : (SubMimeHeaders->content_length > 10))
509 : : {
510 : : char *pptr;
511 : : long lines;
512 : :
513 : 0 : lines = SubMimeHeaders->content_length / 80;
514 : 0 : pptr = ptr + SubMimeHeaders->content_length - lines - 10;
515 [ # # ]: 0 : if (pptr < content_end)
516 : 0 : ptr = pptr;
517 : : }
518 : :
519 : 1478 : next_boundary = strstr(ptr, m->b[startary].Key);
520 : 1478 : *content_end = tmp;
521 : : }
522 : : else {
523 : : char *srch;
524 : : /**
525 : : * ok, if we have a content length of the mime part,
526 : : * try skipping the content on the search for the next
527 : : * boundary. since we don't trust the content_length
528 : : * to be all accurate, start searching a little before..
529 : : */
530 : :
531 [ + + ][ + - ]: 1968 : if ((SubMimeHeaders->content_length != -1) &&
532 : 18 : (SubMimeHeaders->content_length > 10))
533 : : {
534 : : char *pptr;
535 : 18 : pptr = ptr + SubMimeHeaders->content_length - 10;
536 [ + - ]: 18 : if (pptr < content_end)
537 : 18 : ptr = pptr;
538 : : }
539 : :
540 : :
541 : 1968 : next_boundary = NULL;
542 [ + + ][ + - ]: 42992 : for (srch=ptr;
543 : : (srch != NULL) && (srch < content_end);
544 : 41024 : srch = memchr(srch, '-', content_end - srch))
545 : : {
546 [ + + ]: 41024 : if (!memcmp(srch,
547 : 41024 : m->b[startary].Key,
548 : 41024 : m->b[startary].len))
549 : : {
550 : 1915 : next_boundary = srch;
551 : 1915 : srch = content_end;
552 : : }
553 : 39109 : else srch ++;
554 : :
555 : : }
556 : :
557 : : }
558 : 3446 : return next_boundary;
559 : : }
560 : :
561 : : /*
562 : : * Break out the components of a multipart message
563 : : * (This function expects to be fed HEADERS + CONTENT)
564 : : * Note: NULL can be supplied as content_end; in this case, the message is
565 : : * considered to have ended when the parser encounters a 0x00 byte.
566 : : */
567 : 3067 : static void recurseable_mime_parser(char *partnum,
568 : : char *content_start, char *content_end,
569 : : MimeParserCallBackType CallBack,
570 : : MimeParserCallBackType PreMultiPartCallBack,
571 : : MimeParserCallBackType PostMultiPartCallBack,
572 : : void *userdata,
573 : : int dont_decode,
574 : : interesting_mime_headers *m)
575 : : {
576 : : interesting_mime_headers *SubMimeHeaders;
577 : : char *ptr;
578 : : char *part_start;
579 : 3067 : char *part_end = NULL;
580 : 3067 : char *evaluate_crlf_ptr = NULL;
581 : : char *next_boundary;
582 : : char nested_partnum[256];
583 : 3067 : int crlf_in_use = 0;
584 : 3067 : int part_seq = 0;
585 : : CBufStr *chosen_name;
586 : :
587 : :
588 : : /* If this is a multipart message, then recursively process it */
589 : 3067 : ptr = content_start;
590 : 3067 : part_start = NULL;
591 [ + + ]: 3067 : if (m->is_multipart) {
592 : :
593 : : /* Tell the client about this message's multipartedness */
594 [ + + ]: 444 : if (PreMultiPartCallBack != NULL) {
595 : 90 : PreMultiPartCallBack("",
596 : : "",
597 : : partnum,
598 : : "",
599 : : NULL,
600 : 90 : m->b[content_type].Key,
601 : 90 : m->b[charset].Key,
602 : : 0,
603 : 90 : m->b[encoding].Key,
604 : 90 : m->b[id].Key,
605 : : userdata);
606 : : }
607 : :
608 : : /* Figure out where the boundaries are */
609 : 444 : m->b[startary].len = snprintf(m->b[startary].Key, SIZ, "--%s", m->b[boundary].Key);
610 : 444 : SubMimeHeaders = InitInterestingMimes ();
611 [ - + ]: 444 : if (*ptr == '\r')
612 : 0 : ptr ++;
613 [ + + ]: 444 : if (*ptr == '\n')
614 : 101 : ptr ++;
615 [ + + ]: 444 : if (strncmp(ptr, m->b[startary].Key, m->b[startary].len) == 0)
616 : 258 : ptr += m->b[startary].len;
617 [ + + ]: 444 : if (*ptr == '\r')
618 : 43 : ptr ++;
619 [ + + ]: 444 : if (*ptr == '\n')
620 : 258 : ptr ++;
621 : 444 : part_start = NULL;
622 : : do {
623 : :
624 [ + + ]: 3510 : if (parse_MimeHeaders(SubMimeHeaders, &ptr, content_end) != 0)
625 : 64 : break;
626 : 3446 : part_start = ptr;
627 : :
628 : 3446 : next_boundary = FindNextContent(ptr,
629 : : content_end,
630 : : SubMimeHeaders,
631 : : m);
632 [ + + + + ]: 3446 : if ((next_boundary != NULL) &&
633 : 3393 : (next_boundary - part_start < 3))
634 : 790 : continue;
635 : :
636 [ + - ][ + + ]: 2656 : if ( (part_start != NULL) && (next_boundary != NULL) ) {
637 : 2603 : part_end = next_boundary;
638 : 2603 : --part_end; /* omit the trailing LF */
639 [ + + ]: 2603 : if (crlf_in_use) {
640 : 409 : --part_end; /* omit the trailing CR */
641 : : }
642 : :
643 [ + + ]: 2603 : if (!IsEmptyStr(partnum)) {
644 : 558 : snprintf(nested_partnum,
645 : : sizeof nested_partnum,
646 : : "%s.%d", partnum,
647 : : ++part_seq);
648 : : }
649 : : else {
650 : 2045 : snprintf(nested_partnum,
651 : : sizeof nested_partnum,
652 : : "%d", ++part_seq);
653 : : }
654 : 2603 : recurseable_mime_parser(nested_partnum,
655 : : part_start,
656 : : part_end,
657 : : CallBack,
658 : : PreMultiPartCallBack,
659 : : PostMultiPartCallBack,
660 : : userdata,
661 : : dont_decode,
662 : : SubMimeHeaders);
663 : : }
664 : :
665 [ + + ]: 2656 : if (next_boundary != NULL) {
666 : : /* If we pass out of scope, don't attempt to
667 : : * read past the end boundary. */
668 [ + + ][ - + ]: 2603 : if ((*(next_boundary + m->b[startary].len + 1) == '-') &&
669 : 439 : (*(next_boundary + m->b[startary].len + 2) == '-') ){
670 : 0 : ptr = content_end;
671 : : }
672 : : else {
673 : : /* Set up for the next part. */
674 : 2603 : part_start = strstr(next_boundary, "\n");
675 : :
676 : : /* Determine whether newlines are LF or CRLF */
677 : 2603 : evaluate_crlf_ptr = part_start;
678 : 2603 : --evaluate_crlf_ptr;
679 [ + + ][ + - ]: 2603 : if ((*evaluate_crlf_ptr == '\r') &&
680 : 452 : (*(evaluate_crlf_ptr + 1) == '\n'))
681 : : {
682 : 452 : crlf_in_use = 1;
683 : : }
684 : : else {
685 : 2151 : crlf_in_use = 0;
686 : : }
687 : :
688 : : /* Advance past the LF ... now we're in the next part */
689 : 2603 : ++part_start;
690 : 2603 : ptr = part_start;
691 : : }
692 : : }
693 : : else {
694 : : /* Invalid end of multipart. Bail out! */
695 : 53 : ptr = content_end;
696 : : }
697 : 2656 : FlushInterestingMimes(SubMimeHeaders);
698 [ + + ][ + - ]: 3446 : } while ( (ptr < content_end) && (next_boundary != NULL) );
699 : :
700 : 444 : free(SubMimeHeaders);
701 : :
702 [ + + ]: 444 : if (PostMultiPartCallBack != NULL) {
703 : 444 : PostMultiPartCallBack("",
704 : : "",
705 : : partnum,
706 : : "",
707 : : NULL,
708 : 90 : m->b[content_type].Key,
709 : 90 : m->b[charset].Key,
710 : : 0,
711 : 90 : m->b[encoding].Key,
712 : 90 : m->b[id].Key,
713 : : userdata);
714 : : }
715 : : } /* If it's not a multipart message, then do something with it */
716 : : else {
717 : : size_t length;
718 : 2623 : part_start = ptr;
719 : 2623 : length = content_end - part_start;
720 : 2623 : ptr = part_end = content_end;
721 : :
722 : :
723 : : /* The following code will truncate the MIME part to the size
724 : : * specified by the Content-length: header. We have commented it
725 : : * out because these headers have a tendency to be wrong.
726 : : *
727 : : * if ( (content_length > 0) && (length > content_length) ) {
728 : : * length = content_length;
729 : : * }
730 : : */
731 : :
732 : : /* Sometimes the "name" field is tacked on to Content-type,
733 : : * and sometimes it's tacked on to Content-disposition. Use
734 : : * whichever one we have.
735 : : */
736 [ + + ]: 2623 : if (m->b[content_disposition_name].len > m->b[content_type_name].len) {
737 : 463 : chosen_name = &m->b[content_disposition_name];
738 : : }
739 : : else {
740 : 2160 : chosen_name = &m->b[content_type_name];
741 : : }
742 : :
743 : : /* Ok, we've got a non-multipart part here, so do something with it.
744 : : */
745 : 2623 : mime_decode(partnum,
746 : : part_start,
747 : : length,
748 : 2623 : m->b[content_type].Key,
749 : 2623 : m->b[charset].Key,
750 : 2623 : m->b[encoding].Key,
751 : 2623 : m->b[disposition].Key,
752 : 2623 : m->b[id].Key,
753 : : chosen_name->Key,
754 : 2623 : m->b[filename].Key,
755 : : CallBack,
756 : : NULL, NULL,
757 : : userdata,
758 : : dont_decode
759 : : );
760 : :
761 : : /*
762 : : * Now if it's an encapsulated message/rfc822 then we have to recurse into it
763 : : */
764 [ + + ]: 2623 : if (!strcasecmp(&m->b[content_type].Key[0], "message/rfc822")) {
765 : :
766 [ + + ]: 277 : if (PreMultiPartCallBack != NULL) {
767 : 45 : PreMultiPartCallBack("",
768 : : "",
769 : : partnum,
770 : : "",
771 : : NULL,
772 : 45 : m->b[content_type].Key,
773 : 45 : m->b[charset].Key,
774 : : 0,
775 : 45 : m->b[encoding].Key,
776 : 45 : m->b[id].Key,
777 : : userdata);
778 : : }
779 [ + + ]: 277 : if (CallBack != NULL) {
780 [ + - ]: 274 : if (strlen(partnum) > 0) {
781 : 274 : snprintf(nested_partnum,
782 : : sizeof nested_partnum,
783 : : "%s.%d", partnum,
784 : : ++part_seq);
785 : : }
786 : : else {
787 : 0 : snprintf(nested_partnum,
788 : : sizeof nested_partnum,
789 : : "%d", ++part_seq);
790 : : }
791 : 274 : the_mime_parser(nested_partnum,
792 : : part_start,
793 : : part_end,
794 : : CallBack,
795 : : PreMultiPartCallBack,
796 : : PostMultiPartCallBack,
797 : : userdata,
798 : : dont_decode
799 : : );
800 : : }
801 [ + + ]: 277 : if (PostMultiPartCallBack != NULL) {
802 : 45 : PostMultiPartCallBack("",
803 : : "",
804 : : partnum,
805 : : "",
806 : : NULL,
807 : 45 : m->b[content_type].Key,
808 : 45 : m->b[charset].Key,
809 : : 0,
810 : 45 : m->b[encoding].Key,
811 : 45 : m->b[id].Key,
812 : : userdata);
813 : : }
814 : :
815 : :
816 : : }
817 : :
818 : : }
819 : :
820 : 3067 : }
821 : :
822 : : /*
823 : : * Break out the components of a multipart message
824 : : * (This function expects to be fed HEADERS + CONTENT)
825 : : * Note: NULL can be supplied as content_end; in this case, the message is
826 : : * considered to have ended when the parser encounters a 0x00 byte.
827 : : */
828 : 464 : void the_mime_parser(char *partnum,
829 : : char *content_start, char *content_end,
830 : : MimeParserCallBackType CallBack,
831 : : MimeParserCallBackType PreMultiPartCallBack,
832 : : MimeParserCallBackType PostMultiPartCallBack,
833 : : void *userdata,
834 : : int dont_decode)
835 : : {
836 : : interesting_mime_headers *m;
837 : :
838 : : /* If the caller didn't supply an endpointer, generate one by measure */
839 [ - + ]: 464 : if (content_end == NULL) {
840 : 0 : content_end = &content_start[strlen(content_start)];
841 : : }
842 : :
843 : 464 : m = InitInterestingMimes();
844 : :
845 [ + - ]: 464 : if (!parse_MimeHeaders(m, &content_start, content_end))
846 : : {
847 : :
848 : 464 : recurseable_mime_parser(partnum,
849 : : content_start, content_end,
850 : : CallBack,
851 : : PreMultiPartCallBack,
852 : : PostMultiPartCallBack,
853 : : userdata,
854 : : dont_decode,
855 : : m);
856 : : }
857 : 464 : free(m);
858 : 464 : }
859 : :
860 : : /*
861 : : * Entry point for the MIME parser.
862 : : * (This function expects to be fed HEADERS + CONTENT)
863 : : * Note: NULL can be supplied as content_end; in this case, the message is
864 : : * considered to have ended when the parser encounters a 0x00 byte.
865 : : */
866 : 190 : void mime_parser(char *content_start,
867 : : char *content_end,
868 : : MimeParserCallBackType CallBack,
869 : : MimeParserCallBackType PreMultiPartCallBack,
870 : : MimeParserCallBackType PostMultiPartCallBack,
871 : : void *userdata,
872 : : int dont_decode)
873 : : {
874 : :
875 : 190 : the_mime_parser("", content_start, content_end,
876 : : CallBack,
877 : : PreMultiPartCallBack,
878 : : PostMultiPartCallBack,
879 : : userdata, dont_decode);
880 : 190 : }
881 : :
882 : :
883 : :
884 : :
885 : :
886 : :
887 : : typedef struct _MimeGuess {
888 : : const char *Pattern;
889 : : size_t PatternLen;
890 : : long PatternOffset;
891 : : const char *MimeString;
892 : : } MimeGuess;
893 : :
894 : : MimeGuess MyMimes [] = {
895 : : {
896 : : "GIF",
897 : : 3,
898 : : 0,
899 : : "image/gif"
900 : : },
901 : : {
902 : : "\xff\xd8",
903 : : 2,
904 : : 0,
905 : : "image/jpeg"
906 : : },
907 : : {
908 : : "\x89PNG",
909 : : 4,
910 : : 0,
911 : : "image/png"
912 : : },
913 : : { // last...
914 : : "",
915 : : 0,
916 : : 0,
917 : : ""
918 : : }
919 : : };
920 : :
921 : :
922 : 4 : const char *GuessMimeType(const char *data, size_t dlen)
923 : : {
924 : 4 : int MimeIndex = 0;
925 : :
926 [ + + ]: 12 : while (MyMimes[MimeIndex].PatternLen != 0)
927 : : {
928 [ + - ][ + + ]: 10 : if ((MyMimes[MimeIndex].PatternLen +
929 : 10 : MyMimes[MimeIndex].PatternOffset < dlen) &&
930 : : strncmp(MyMimes[MimeIndex].Pattern,
931 : 10 : &data[MyMimes[MimeIndex].PatternOffset],
932 : 20 : MyMimes[MimeIndex].PatternLen) == 0)
933 : : {
934 : 2 : return MyMimes[MimeIndex].MimeString;
935 : : }
936 : 8 : MimeIndex ++;
937 : : }
938 : : /*
939 : : * ok, our simple minded algorythm didn't find anything,
940 : : * let the big chegger try it, he wil default to application/octet-stream
941 : : */
942 : 4 : return (xdg_mime_get_mime_type_for_data(data, dlen));
943 : : }
944 : :
945 : :
946 : 21 : const char* GuessMimeByFilename(const char *what, size_t len)
947 : : {
948 : : /* we know some hardcoded on our own, try them... */
949 [ + + ][ + + ]: 21 : if ((len > 3) && !strncasecmp(&what[len - 4], ".gif", 4))
950 : 1 : return "image/gif";
951 [ + + ][ + + ]: 20 : else if ((len > 2) && !strncasecmp(&what[len - 3], ".js", 3))
952 : 1 : return "text/javascript";
953 [ + + ][ + + ]: 19 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".txt", 4))
954 : 1 : return "text/plain";
955 [ + + ][ + + ]: 18 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".css", 4))
956 : 1 : return "text/css";
957 [ + + ][ + + ]: 17 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".htc", 4))
958 : 1 : return "text/x-component";
959 [ + + ][ + + ]: 16 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".jpg", 4))
960 : 1 : return "image/jpeg";
961 [ + + ][ + + ]: 15 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".png", 4))
962 : 2 : return "image/png";
963 [ + + ][ + + ]: 13 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".ico", 4))
964 : 1 : return "image/x-icon";
965 [ + + ][ + + ]: 12 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".vcf", 4))
966 : 1 : return "text/x-vcard";
967 [ + + ][ + + ]: 11 : else if ((len > 4) && !strncasecmp(&what[len - 5], ".html", 5))
968 : 1 : return "text/html";
969 [ + + ][ + + ]: 10 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".htm", 4))
970 : 1 : return "text/html";
971 [ + + ][ + + ]: 9 : else if ((len > 3) && !strncasecmp(&what[len - 4], ".wml", 4))
972 : 1 : return "text/vnd.wap.wml";
973 [ + + ][ + + ]: 8 : else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmls", 5))
974 : 1 : return "text/vnd.wap.wmlscript";
975 [ + + ][ + + ]: 7 : else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmlc", 5))
976 : 1 : return "application/vnd.wap.wmlc";
977 [ + + ][ + + ]: 6 : else if ((len > 5) && !strncasecmp(&what[len - 6], ".wmlsc", 6))
978 : 1 : return "application/vnd.wap.wmlscriptc";
979 [ + + ][ + + ]: 5 : else if ((len > 4) && !strncasecmp(&what[len - 5], ".wbmp", 5))
980 : 1 : return "image/vnd.wap.wbmp";
981 : : else
982 : : /* and let xdgmime do the fallback. */
983 : 21 : return xdg_mime_get_mime_type_from_file_name(what);
984 : : }
985 : :
986 : : static HashList *IconHash = NULL;
987 : :
988 : : typedef struct IconName IconName;
989 : :
990 : : struct IconName {
991 : : char *FlatName;
992 : : char *FileName;
993 : : };
994 : :
995 : 0 : static void DeleteIcon(void *IconNamePtr)
996 : : {
997 : 0 : IconName *Icon = (IconName*) IconNamePtr;
998 : 0 : free(Icon->FlatName);
999 : 0 : free(Icon->FileName);
1000 : 0 : free(Icon);
1001 : 0 : }
1002 : :
1003 : : /*
1004 : : static const char *PrintFlat(void *IconNamePtr)
1005 : : {
1006 : : IconName *Icon = (IconName*) IconNamePtr;
1007 : : return Icon->FlatName;
1008 : : }
1009 : : static const char *PrintFile(void *IconNamePtr)
1010 : : {
1011 : : IconName *Icon = (IconName*) IconNamePtr;
1012 : : return Icon->FileName;
1013 : : }
1014 : : */
1015 : :
1016 : : #define GENSTR "x-generic"
1017 : : #define IGNORE_PREFIX_1 "gnome-mime"
1018 : 25 : int LoadIconDir(const char *DirName)
1019 : : {
1020 : 25 : DIR *filedir = NULL;
1021 : : struct dirent *filedir_entry;
1022 : : int d_namelen;
1023 : : int d_without_ext;
1024 : : IconName *Icon;
1025 : :
1026 : 25 : filedir = opendir (DirName);
1027 : 25 : IconHash = NewHash(1, NULL);
1028 [ + - ]: 25 : if (filedir == NULL) {
1029 : 25 : return 0;
1030 : : }
1031 : :
1032 [ # # ]: 0 : while ((filedir_entry = readdir(filedir)))
1033 : : {
1034 : : char *MinorPtr;
1035 : : char *PStart;
1036 : : #ifdef _DIRENT_HAVE_D_NAMELEN
1037 : : d_namelen = filedir_entry->d_namelen;
1038 : : #else
1039 : 0 : d_namelen = strlen(filedir_entry->d_name);
1040 : : #endif
1041 : 0 : d_without_ext = d_namelen;
1042 [ # # ][ # # ]: 0 : while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
1043 : 0 : d_without_ext --;
1044 [ # # ][ # # ]: 0 : if ((d_without_ext == 0) || (d_namelen < 3))
1045 : 0 : continue;
1046 : :
1047 [ # # ][ # # ]: 0 : if ((sizeof(IGNORE_PREFIX_1) < d_namelen) &&
1048 : 0 : (strncmp(IGNORE_PREFIX_1,
1049 : : filedir_entry->d_name,
1050 : : sizeof(IGNORE_PREFIX_1) - 1) == 0)) {
1051 : 0 : PStart = filedir_entry->d_name + sizeof(IGNORE_PREFIX_1);
1052 : 0 : d_without_ext -= sizeof(IGNORE_PREFIX_1);
1053 : : }
1054 : : else {
1055 : 0 : PStart = filedir_entry->d_name;
1056 : : }
1057 : 0 : Icon = malloc(sizeof(IconName));
1058 : :
1059 : 0 : Icon->FileName = malloc(d_namelen + 1);
1060 : 0 : memcpy(Icon->FileName, filedir_entry->d_name, d_namelen + 1);
1061 : :
1062 : 0 : Icon->FlatName = malloc(d_without_ext + 1);
1063 : 0 : memcpy(Icon->FlatName, PStart, d_without_ext);
1064 : 0 : Icon->FlatName[d_without_ext] = '\0';
1065 : : /* Try to find Minor type in image-jpeg */
1066 : 0 : MinorPtr = strchr(Icon->FlatName, '-');
1067 [ # # ]: 0 : if (MinorPtr != NULL) {
1068 : : size_t MinorLen;
1069 : 0 : MinorLen = 1 + d_without_ext - (MinorPtr - Icon->FlatName + 1);
1070 [ # # ][ # # ]: 0 : if ((MinorLen == sizeof(GENSTR)) &&
1071 : 0 : (strncmp(MinorPtr + 1, GENSTR, sizeof(GENSTR)) == 0)) {
1072 : : /* ok, we found a generic filename. cut the generic. */
1073 : 0 : *MinorPtr = '\0';
1074 : 0 : d_without_ext = d_without_ext - (MinorPtr - Icon->FlatName);
1075 : : }
1076 : : else { /* Map the major / minor separator to / */
1077 : 0 : *MinorPtr = '/';
1078 : : }
1079 : : }
1080 : :
1081 : : // PrintHash(IconHash, PrintFlat, PrintFile);
1082 : : // printf("%s - %s\n", Icon->FlatName, Icon->FileName);
1083 : 0 : Put(IconHash, Icon->FlatName, d_without_ext, Icon, DeleteIcon);
1084 : : // PrintHash(IconHash, PrintFlat, PrintFile);
1085 : : }
1086 : 0 : closedir(filedir);
1087 : 25 : return 1;
1088 : : }
1089 : :
1090 : 25 : const char *GetIconFilename(char *MimeType, size_t len)
1091 : : {
1092 : : void *vIcon;
1093 : : IconName *Icon;
1094 : :
1095 [ - + ]: 25 : if(IconHash == NULL)
1096 : 0 : return NULL;
1097 : :
1098 : 25 : GetHash(IconHash, MimeType, len, &vIcon), Icon = (IconName*) vIcon;
1099 : : /* didn't find the exact mimetype? try major only. */
1100 [ + - ]: 25 : if (Icon == NULL) {
1101 : : char * pMinor;
1102 : 25 : pMinor = strchr(MimeType, '/');
1103 [ + - ]: 25 : if (pMinor != NULL) {
1104 : 25 : *pMinor = '\0';
1105 : 25 : GetHash(IconHash, MimeType, pMinor - MimeType, &vIcon),
1106 : 25 : Icon = (IconName*) vIcon;
1107 : : }
1108 : : }
1109 [ + - ]: 25 : if (Icon == NULL) {
1110 : 25 : return NULL;
1111 : : }
1112 : :
1113 : : /*printf("Getting: [%s] == [%s] -> [%s]\n", MimeType, Icon->FlatName, Icon->FileName);*/
1114 : 25 : return Icon->FileName;
1115 : : }
1116 : :
1117 : 26 : void ShutDownLibCitadelMime(void)
1118 : : {
1119 : 26 : DeleteHash(&IconHash);
1120 : 26 : }
|