14192
|
1 /*
|
|
2 * Gaim
|
|
3 *
|
|
4 * Gaim is the legal property of its developers, whose names are too
|
|
5 * numerous to list here. Please refer to the COPYRIGHT file distributed
|
|
6 * with this source distribution
|
|
7 *
|
|
8 * This program is free software; you can redistribute it and/or modify
|
|
9 * it under the terms of the GNU General Public License as published by
|
|
10 * the Free Software Foundation; either version 2 of the License, or (at
|
|
11 * your option) any later version.
|
|
12 *
|
|
13 * This program is distributed in the hope that it will be useful, but
|
|
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
16 * General Public License for more details.
|
|
17 *
|
|
18 * You should have received a copy of the GNU General Public License
|
|
19 * along with this program; if not, write to the Free Software
|
|
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
21 * USA.
|
|
22 */
|
|
23
|
|
24 #include <stdio.h>
|
|
25 #include <string.h>
|
|
26
|
|
27 #include <glib.h>
|
|
28 #include <glib/ghash.h>
|
|
29 #include <glib/glist.h>
|
|
30 #include <glib/gstring.h>
|
|
31
|
|
32 /* this should become "util.h" if we ever get this into gaim proper */
|
|
33 #include "debug.h"
|
|
34 #include "mime.h"
|
|
35 #include "util.h"
|
|
36
|
|
37 /**
|
|
38 * @struct mime_fields
|
|
39 *
|
|
40 * Utility structure used in both MIME document and parts, which maps
|
|
41 * field names to their values, and keeps an easily accessible list of
|
|
42 * keys.
|
|
43 */
|
|
44 struct mime_fields {
|
|
45 GHashTable *map;
|
|
46 GList *keys;
|
|
47 };
|
|
48
|
|
49 struct _GaimMimeDocument {
|
|
50 struct mime_fields fields;
|
|
51 GList *parts;
|
|
52 };
|
|
53
|
|
54 struct _GaimMimePart {
|
|
55 struct mime_fields fields;
|
|
56 struct _GaimMimeDocument *doc;
|
|
57 GString *data;
|
|
58 };
|
|
59
|
|
60 static void
|
|
61 fields_set(struct mime_fields *mf, const char *key, const char *val)
|
|
62 {
|
|
63 char *k, *v;
|
|
64
|
|
65 g_return_if_fail(mf != NULL);
|
|
66 g_return_if_fail(mf->map != NULL);
|
|
67
|
|
68 k = g_ascii_strdown(key, -1);
|
|
69 v = g_strdup(val);
|
|
70
|
|
71 /* append to the keys list only if it's not already there */
|
|
72 if(! g_hash_table_lookup(mf->map, k)) {
|
|
73 mf->keys = g_list_append(mf->keys, k);
|
|
74 }
|
|
75
|
|
76 /* important to use insert. If the key is already in the table, then
|
|
77 it's already in the keys list. Insert will free the new instance
|
|
78 of the key rather than the old instance. */
|
|
79 g_hash_table_insert(mf->map, k, v);
|
|
80 }
|
|
81
|
|
82
|
|
83 static const char *
|
|
84 fields_get(struct mime_fields *mf, const char *key)
|
|
85 {
|
|
86 char *kdown;
|
|
87 const char *ret;
|
|
88
|
|
89 g_return_val_if_fail(mf != NULL, NULL);
|
|
90 g_return_val_if_fail(mf->map != NULL, NULL);
|
|
91
|
|
92 kdown = g_ascii_strdown(key, -1);
|
|
93 ret = g_hash_table_lookup(mf->map, kdown);
|
|
94 g_free(kdown);
|
|
95
|
|
96 return ret;
|
|
97 }
|
|
98
|
|
99
|
|
100 static void
|
|
101 fields_init(struct mime_fields *mf)
|
|
102 {
|
|
103 g_return_if_fail(mf != NULL);
|
|
104
|
|
105 mf->map = g_hash_table_new_full(g_str_hash, g_str_equal,
|
|
106 g_free, g_free);
|
|
107 }
|
|
108
|
|
109
|
|
110 static void
|
|
111 fields_loadline(struct mime_fields *mf, const char *line, gsize len)
|
|
112 {
|
|
113 /* split the line into key: value */
|
|
114 char *key, *val;
|
|
115 char **tokens;
|
|
116
|
|
117 /* feh, need it to be NUL terminated */
|
|
118 key = g_strndup(line, len);
|
|
119
|
|
120 /* split */
|
|
121 val = strchr(key, ':');
|
|
122 if(! val) {
|
|
123 g_free(key);
|
|
124 return;
|
|
125 }
|
|
126 *val++ = '\0';
|
|
127
|
|
128 /* normalize whitespace (sorta) and trim on key and value */
|
|
129 tokens = g_strsplit(key, "\t\r\n", 0);
|
|
130 key = g_strjoinv("", tokens);
|
|
131 key = g_strstrip(key);
|
|
132 g_strfreev(tokens);
|
|
133
|
|
134 tokens = g_strsplit(val, "\t\r\n", 0);
|
|
135 val = g_strjoinv("", tokens);
|
|
136 val = g_strstrip(val);
|
|
137 g_strfreev(tokens);
|
|
138
|
|
139 fields_set(mf, key, val);
|
|
140
|
|
141 g_free(key);
|
|
142 g_free(val);
|
|
143 }
|
|
144
|
|
145
|
|
146 static void
|
|
147 fields_load(struct mime_fields *mf, char **buf, gsize *len)
|
|
148 {
|
|
149 char *tail;
|
|
150
|
|
151 while ((tail = g_strstr_len(*buf, *len, "\r\n")))
|
|
152 {
|
|
153 char *line;
|
|
154 gsize ln;
|
|
155
|
|
156 /* determine the current line */
|
|
157 line = *buf;
|
|
158 ln = tail - line;
|
|
159
|
|
160 /* advance our search space past the CRLF */
|
|
161 *buf = tail + 2;
|
|
162 *len -= (ln + 2);
|
|
163
|
|
164 /* empty line, end of headers */
|
|
165 if(! ln) return;
|
|
166
|
|
167 /* look out for line continuations */
|
|
168 if(line[ln-1] == ';') {
|
|
169 tail = g_strstr_len(*buf, *len, "\r\n");
|
|
170 if(tail) {
|
|
171 gsize cln;
|
|
172
|
|
173 cln = tail - *buf;
|
|
174 ln = tail - line;
|
|
175
|
|
176 /* advance our search space past the CRLF (again) */
|
|
177 *buf = tail + 2;
|
|
178 *len -= (cln + 2);
|
|
179 }
|
|
180 }
|
|
181
|
|
182 /* process our super-cool line */
|
|
183 fields_loadline(mf, line, ln);
|
|
184 }
|
|
185 }
|
|
186
|
|
187
|
|
188 static void
|
|
189 field_write(const char *key, const char *val, GString *str)
|
|
190 {
|
|
191 g_string_append_printf(str, "%s: %s\r\n", key, val);
|
|
192 }
|
|
193
|
|
194
|
|
195 static void
|
|
196 fields_write(struct mime_fields *mf, GString *str)
|
|
197 {
|
|
198 g_return_if_fail(mf != NULL);
|
|
199
|
|
200 g_hash_table_foreach(mf->map, (GHFunc) field_write, str);
|
|
201 g_string_append(str, "\r\n");
|
|
202 }
|
|
203
|
|
204
|
|
205 static void
|
|
206 fields_destroy(struct mime_fields *mf)
|
|
207 {
|
|
208 g_return_if_fail(mf != NULL);
|
|
209
|
|
210 g_hash_table_destroy(mf->map);
|
|
211 g_list_free(mf->keys);
|
|
212
|
|
213 mf->map = NULL;
|
|
214 mf->keys = NULL;
|
|
215 }
|
|
216
|
|
217
|
|
218 static GaimMimePart *
|
|
219 part_new(GaimMimeDocument *doc)
|
|
220 {
|
|
221 GaimMimePart *part;
|
|
222
|
|
223 part = g_new0(GaimMimePart, 1);
|
|
224 fields_init(&part->fields);
|
|
225 part->doc = doc;
|
|
226 part->data = g_string_new(NULL);
|
|
227
|
|
228 doc->parts = g_list_prepend(doc->parts, part);
|
|
229
|
|
230 return part;
|
|
231 }
|
|
232
|
|
233
|
|
234 static void
|
|
235 part_load(GaimMimePart *part, const char *buf, gsize len)
|
|
236 {
|
|
237
|
|
238 char *b = (char *) buf;
|
|
239 gsize n = len;
|
|
240
|
|
241 fields_load(&part->fields, &b, &n);
|
|
242
|
|
243 /* the remainder will have a blank line, if there's anything at all,
|
|
244 so check if there's anything then trim off the trailing four
|
|
245 bytes, \r\n\r\n */
|
|
246 if(n > 4) n -= 4;
|
|
247 g_string_append_len(part->data, b, n);
|
|
248 }
|
|
249
|
|
250
|
|
251 static void
|
|
252 part_write(GaimMimePart *part, GString *str)
|
|
253 {
|
|
254 fields_write(&part->fields, str);
|
|
255 g_string_append_printf(str, "%s\r\n\r\n", part->data->str);
|
|
256 }
|
|
257
|
|
258
|
|
259 static void
|
|
260 part_free(GaimMimePart *part)
|
|
261 {
|
|
262
|
|
263 fields_destroy(&part->fields);
|
|
264
|
|
265 g_string_free(part->data, TRUE);
|
|
266 part->data = NULL;
|
|
267
|
|
268 g_free(part);
|
|
269 }
|
|
270
|
|
271
|
|
272 GaimMimePart *
|
|
273 gaim_mime_part_new(GaimMimeDocument *doc)
|
|
274 {
|
|
275 g_return_val_if_fail(doc != NULL, NULL);
|
|
276 return part_new(doc);
|
|
277 }
|
|
278
|
|
279
|
|
280 const GList *
|
|
281 gaim_mime_part_get_fields(GaimMimePart *part)
|
|
282 {
|
|
283 g_return_val_if_fail(part != NULL, NULL);
|
|
284 return part->fields.keys;
|
|
285 }
|
|
286
|
|
287
|
|
288 const char *
|
|
289 gaim_mime_part_get_field(GaimMimePart *part, const char *field)
|
|
290 {
|
|
291 g_return_val_if_fail(part != NULL, NULL);
|
|
292 return fields_get(&part->fields, field);
|
|
293 }
|
|
294
|
|
295
|
|
296 char *
|
|
297 gaim_mime_part_get_field_decoded(GaimMimePart *part, const char *field)
|
|
298 {
|
|
299 const char *f;
|
|
300
|
|
301 g_return_val_if_fail(part != NULL, NULL);
|
|
302
|
|
303 f = fields_get(&part->fields, field);
|
|
304 return gaim_mime_decode_field(f);
|
|
305 }
|
|
306
|
|
307
|
|
308 void
|
|
309 gaim_mime_part_set_field(GaimMimePart *part, const char *field, const char *value)
|
|
310 {
|
|
311 g_return_if_fail(part != NULL);
|
|
312 fields_set(&part->fields, field, value);
|
|
313 }
|
|
314
|
|
315
|
|
316 const char *
|
|
317 gaim_mime_part_get_data(GaimMimePart *part)
|
|
318 {
|
|
319 g_return_val_if_fail(part != NULL, NULL);
|
|
320 g_return_val_if_fail(part->data != NULL, NULL);
|
|
321
|
|
322 return part->data->str;
|
|
323 }
|
|
324
|
|
325
|
|
326 void
|
|
327 gaim_mime_part_get_data_decoded(GaimMimePart *part, guchar **data, gsize *len)
|
|
328 {
|
|
329 const char *enc;
|
|
330
|
|
331 g_return_if_fail(part != NULL);
|
|
332 g_return_if_fail(data != NULL);
|
|
333 g_return_if_fail(len != NULL);
|
|
334
|
|
335 g_return_if_fail(part->data != NULL);
|
|
336
|
|
337 enc = gaim_mime_part_get_field(part, "content-transfer-encoding");
|
|
338
|
|
339 if(! enc) {
|
|
340 *data = (guchar *)g_strdup(part->data->str);
|
|
341 *len = part->data->len;
|
|
342
|
|
343 } else if(! g_ascii_strcasecmp(enc, "7bit")) {
|
|
344 *data = (guchar *)g_strdup(part->data->str);
|
|
345 *len = part->data->len;
|
|
346
|
|
347 } else if(! g_ascii_strcasecmp(enc, "8bit")) {
|
|
348 *data = (guchar *)g_strdup(part->data->str);
|
|
349 *len = part->data->len;
|
|
350
|
|
351 } else if(! g_ascii_strcasecmp(enc, "base16")) {
|
|
352 *data = gaim_base16_decode(part->data->str, len);
|
|
353
|
|
354 } else if(! g_ascii_strcasecmp(enc, "base64")) {
|
|
355 *data = gaim_base64_decode(part->data->str, len);
|
|
356
|
|
357 } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) {
|
|
358 *data = gaim_quotedp_decode(part->data->str, len);
|
|
359
|
|
360 } else {
|
|
361 gaim_debug_warning("mime", "gaim_mime_part_get_data_decoded:"
|
|
362 " unknown encoding '%s'\n", enc);
|
|
363 *data = NULL;
|
|
364 *len = 0;
|
|
365 }
|
|
366 }
|
|
367
|
|
368
|
|
369 gsize
|
|
370 gaim_mime_part_get_length(GaimMimePart *part)
|
|
371 {
|
|
372 g_return_val_if_fail(part != NULL, 0);
|
|
373 g_return_val_if_fail(part->data != NULL, 0);
|
|
374
|
|
375 return part->data->len;
|
|
376 }
|
|
377
|
|
378
|
|
379 void
|
|
380 gaim_mime_part_set_data(GaimMimePart *part, const char *data)
|
|
381 {
|
|
382 g_return_if_fail(part != NULL);
|
|
383 g_string_free(part->data, TRUE);
|
|
384 part->data = g_string_new(data);
|
|
385 }
|
|
386
|
|
387
|
|
388 GaimMimeDocument *
|
|
389 gaim_mime_document_new()
|
|
390 {
|
|
391 GaimMimeDocument *doc;
|
|
392
|
|
393 doc = g_new0(GaimMimeDocument, 1);
|
|
394 fields_init(&doc->fields);
|
|
395
|
|
396 return doc;
|
|
397 }
|
|
398
|
|
399
|
|
400 static void
|
|
401 doc_parts_load(GaimMimeDocument *doc, const char *boundary, const char *buf, gsize len)
|
|
402 {
|
|
403 char *b = (char *) buf;
|
|
404 gsize n = len;
|
|
405
|
|
406 const char *bnd;
|
|
407 gsize bl;
|
|
408
|
|
409 bnd = g_strdup_printf("--%s", boundary);
|
|
410 bl = strlen(bnd);
|
|
411
|
|
412 for(b = g_strstr_len(b, n, bnd); b; ) {
|
|
413 char *tail;
|
|
414
|
|
415 /* skip the boundary */
|
|
416 b += bl;
|
|
417 n -= bl;
|
|
418
|
|
419 /* skip the trailing \r\n or -- as well */
|
|
420 if(n >= 2) {
|
|
421 b += 2;
|
|
422 n -= 2;
|
|
423 }
|
|
424
|
|
425 /* find the next boundary */
|
|
426 tail = g_strstr_len(b, n, bnd);
|
|
427
|
|
428 if(tail) {
|
|
429 gsize sl;
|
|
430
|
|
431 sl = tail - b;
|
|
432 if(sl) {
|
|
433 GaimMimePart *part = part_new(doc);
|
|
434 part_load(part, b, sl);
|
|
435 }
|
|
436 }
|
|
437
|
|
438 b = tail;
|
|
439 }
|
|
440 }
|
|
441
|
|
442
|
|
443 GaimMimeDocument *
|
|
444 gaim_mime_document_parsen(const char *buf, gsize len)
|
|
445 {
|
|
446 GaimMimeDocument *doc;
|
|
447
|
|
448 char *b = (char *) buf;
|
|
449 gsize n = len;
|
|
450
|
|
451 g_return_val_if_fail(buf != NULL, NULL);
|
|
452
|
|
453 doc = gaim_mime_document_new();
|
|
454
|
|
455 if (!len)
|
|
456 return doc;
|
|
457
|
|
458 fields_load(&doc->fields, &b, &n);
|
|
459
|
|
460 {
|
|
461 const char *ct = fields_get(&doc->fields, "content-type");
|
|
462 if(ct && gaim_str_has_prefix(ct, "multipart")) {
|
|
463 char *bd = strrchr(ct, '=');
|
|
464 if(bd++) {
|
|
465 doc_parts_load(doc, bd, b, n);
|
|
466 }
|
|
467 }
|
|
468 }
|
|
469
|
|
470 return doc;
|
|
471 }
|
|
472
|
|
473
|
|
474 GaimMimeDocument *
|
|
475 gaim_mime_document_parse(const char *buf)
|
|
476 {
|
|
477 g_return_val_if_fail(buf != NULL, NULL);
|
|
478 return gaim_mime_document_parsen(buf, strlen(buf));
|
|
479 }
|
|
480
|
|
481
|
|
482 void
|
|
483 gaim_mime_document_write(GaimMimeDocument *doc, GString *str)
|
|
484 {
|
|
485 const char *bd = NULL;
|
|
486
|
|
487 g_return_if_fail(doc != NULL);
|
|
488 g_return_if_fail(str != NULL);
|
|
489
|
|
490 {
|
|
491 const char *ct = fields_get(&doc->fields, "content-type");
|
|
492 if(ct && gaim_str_has_prefix(ct, "multipart")) {
|
|
493 char *b = strrchr(ct, '=');
|
|
494 if(b++) bd = b;
|
|
495 }
|
|
496 }
|
|
497
|
|
498 fields_write(&doc->fields, str);
|
|
499
|
|
500 if(bd) {
|
|
501 GList *l;
|
|
502
|
|
503 for(l = doc->parts; l; l = l->next) {
|
|
504 g_string_append_printf(str, "--%s\r\n", bd);
|
|
505
|
|
506 part_write(l->data, str);
|
|
507
|
|
508 if(! l->next) {
|
|
509 g_string_append_printf(str, "--%s--\r\n", bd);
|
|
510 }
|
|
511 }
|
|
512 }
|
|
513 }
|
|
514
|
|
515
|
|
516 const GList *
|
|
517 gaim_mime_document_get_fields(GaimMimeDocument *doc)
|
|
518 {
|
|
519 g_return_val_if_fail(doc != NULL, NULL);
|
|
520 return doc->fields.keys;
|
|
521 }
|
|
522
|
|
523
|
|
524 const char *
|
|
525 gaim_mime_document_get_field(GaimMimeDocument *doc, const char *field)
|
|
526 {
|
|
527 g_return_val_if_fail(doc != NULL, NULL);
|
|
528 return fields_get(&doc->fields, field);
|
|
529 }
|
|
530
|
|
531
|
|
532 void
|
|
533 gaim_mime_document_set_field(GaimMimeDocument *doc, const char *field, const char *value)
|
|
534 {
|
|
535 g_return_if_fail(doc != NULL);
|
|
536 fields_set(&doc->fields, field, value);
|
|
537 }
|
|
538
|
|
539
|
|
540 const GList *
|
|
541 gaim_mime_document_get_parts(GaimMimeDocument *doc)
|
|
542 {
|
|
543 g_return_val_if_fail(doc != NULL, NULL);
|
|
544 return doc->parts;
|
|
545 }
|
|
546
|
|
547
|
|
548 void
|
|
549 gaim_mime_document_free(GaimMimeDocument *doc)
|
|
550 {
|
|
551 if (!doc)
|
|
552 return;
|
|
553
|
|
554 fields_destroy(&doc->fields);
|
|
555
|
|
556 while(doc->parts) {
|
|
557 part_free(doc->parts->data);
|
|
558 doc->parts = g_list_delete_link(doc->parts, doc->parts);
|
|
559 }
|
|
560
|
|
561 g_free(doc);
|
|
562 }
|