Mercurial > pidgin
annotate libgaim/mime.c @ 14922:bad6e64c6405
[gaim-migrate @ 17694]
A note!
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Tue, 07 Nov 2006 07:13:34 +0000 |
parents | 2e4690852fd7 |
children |
rev | line source |
---|---|
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")) { | |
14719
2e4690852fd7
[gaim-migrate @ 17473]
Etan Reisner <pidgin@unreliablesource.net>
parents:
14192
diff
changeset
|
355 *data = gaim_base64_decode(part->data->str, len); |
14192 | 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 } |