Mercurial > pidgin
annotate src/mime.c @ 14100:85144ba7d726
[gaim-migrate @ 16730]
Fix the documentation to indicate that the result of xmlnode_get_data() needs to be g_free'd
committer: Tailor Script <tailor@pidgin.im>
author | Daniel Atallah <daniel.atallah@gmail.com> |
---|---|
date | Sun, 13 Aug 2006 02:18:28 +0000 |
parents | 443aaa05a7c3 |
children |
rev | line source |
---|---|
10978 | 1 /* |
14038 | 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 */ | |
10978 | 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 */ | |
14038 | 33 #include "debug.h" |
10978 | 34 #include "mime.h" |
14038 | 35 #include "util.h" |
10978 | 36 |
14038 | 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 */ | |
10978 | 44 struct mime_fields { |
14038 | 45 GHashTable *map; |
46 GList *keys; | |
10978 | 47 }; |
48 | |
49 struct _GaimMimeDocument { | |
14038 | 50 struct mime_fields fields; |
51 GList *parts; | |
10978 | 52 }; |
53 | |
54 struct _GaimMimePart { | |
14038 | 55 struct mime_fields fields; |
56 struct _GaimMimeDocument *doc; | |
57 GString *data; | |
10978 | 58 }; |
59 | |
14038 | 60 static void |
61 fields_set(struct mime_fields *mf, const char *key, const char *val) | |
62 { | |
63 char *k, *v; | |
10978 | 64 |
14038 | 65 g_return_if_fail(mf != NULL); |
66 g_return_if_fail(mf->map != NULL); | |
10978 | 67 |
14038 | 68 k = g_ascii_strdown(key, -1); |
69 v = g_strdup(val); | |
10978 | 70 |
14038 | 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 } | |
10978 | 75 |
14038 | 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); | |
10978 | 80 } |
81 | |
82 | |
14038 | 83 static const char * |
84 fields_get(struct mime_fields *mf, const char *key) | |
85 { | |
86 char *kdown; | |
87 const char *ret; | |
10978 | 88 |
14038 | 89 g_return_val_if_fail(mf != NULL, NULL); |
90 g_return_val_if_fail(mf->map != NULL, NULL); | |
10978 | 91 |
14038 | 92 kdown = g_ascii_strdown(key, -1); |
93 ret = g_hash_table_lookup(mf->map, kdown); | |
94 g_free(kdown); | |
10978 | 95 |
14038 | 96 return ret; |
10978 | 97 } |
98 | |
99 | |
14038 | 100 static void |
101 fields_init(struct mime_fields *mf) | |
102 { | |
103 g_return_if_fail(mf != NULL); | |
10978 | 104 |
14038 | 105 mf->map = g_hash_table_new_full(g_str_hash, g_str_equal, |
106 g_free, g_free); | |
10978 | 107 } |
108 | |
109 | |
14038 | 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; | |
10978 | 116 |
14038 | 117 /* feh, need it to be NUL terminated */ |
118 key = g_strndup(line, len); | |
10978 | 119 |
14038 | 120 /* split */ |
121 val = strchr(key, ':'); | |
122 if(! val) { | |
123 g_free(key); | |
124 return; | |
125 } | |
126 *val++ = '\0'; | |
10978 | 127 |
14038 | 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); | |
10978 | 133 |
14038 | 134 tokens = g_strsplit(val, "\t\r\n", 0); |
135 val = g_strjoinv("", tokens); | |
136 val = g_strstrip(val); | |
137 g_strfreev(tokens); | |
10978 | 138 |
14038 | 139 fields_set(mf, key, val); |
140 | |
141 g_free(key); | |
142 g_free(val); | |
10978 | 143 } |
144 | |
145 | |
14038 | 146 static void |
147 fields_load(struct mime_fields *mf, char **buf, gsize *len) | |
148 { | |
149 char *tail; | |
10978 | 150 |
14038 | 151 while ((tail = g_strstr_len(*buf, *len, "\r\n"))) |
152 { | |
153 char *line; | |
154 gsize ln; | |
10978 | 155 |
14038 | 156 /* determine the current line */ |
157 line = *buf; | |
158 ln = tail - line; | |
10978 | 159 |
14038 | 160 /* advance our search space past the CRLF */ |
161 *buf = tail + 2; | |
162 *len -= (ln + 2); | |
10978 | 163 |
14038 | 164 /* empty line, end of headers */ |
165 if(! ln) return; | |
10978 | 166 |
14038 | 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; | |
10978 | 172 |
14038 | 173 cln = tail - *buf; |
174 ln = tail - line; | |
10978 | 175 |
14038 | 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 } | |
10978 | 185 } |
186 | |
187 | |
14038 | 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); | |
10978 | 192 } |
193 | |
194 | |
14038 | 195 static void |
196 fields_write(struct mime_fields *mf, GString *str) | |
197 { | |
198 g_return_if_fail(mf != NULL); | |
10978 | 199 |
14038 | 200 g_hash_table_foreach(mf->map, (GHFunc) field_write, str); |
201 g_string_append(str, "\r\n"); | |
10978 | 202 } |
203 | |
204 | |
14038 | 205 static void |
206 fields_destroy(struct mime_fields *mf) | |
207 { | |
208 g_return_if_fail(mf != NULL); | |
10978 | 209 |
14038 | 210 g_hash_table_destroy(mf->map); |
211 g_list_free(mf->keys); | |
10978 | 212 |
14038 | 213 mf->map = NULL; |
214 mf->keys = NULL; | |
10978 | 215 } |
216 | |
217 | |
14038 | 218 static GaimMimePart * |
219 part_new(GaimMimeDocument *doc) | |
220 { | |
221 GaimMimePart *part; | |
10978 | 222 |
14038 | 223 part = g_new0(GaimMimePart, 1); |
224 fields_init(&part->fields); | |
225 part->doc = doc; | |
226 part->data = g_string_new(NULL); | |
10978 | 227 |
14038 | 228 doc->parts = g_list_prepend(doc->parts, part); |
229 | |
230 return part; | |
10978 | 231 } |
232 | |
233 | |
14038 | 234 static void |
235 part_load(GaimMimePart *part, const char *buf, gsize len) | |
236 { | |
10978 | 237 |
14038 | 238 char *b = (char *) buf; |
239 gsize n = len; | |
10978 | 240 |
14038 | 241 fields_load(&part->fields, &b, &n); |
10978 | 242 |
14038 | 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); | |
10978 | 248 } |
249 | |
250 | |
14038 | 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); | |
10978 | 256 } |
257 | |
258 | |
14038 | 259 static void |
260 part_free(GaimMimePart *part) | |
261 { | |
10978 | 262 |
14038 | 263 fields_destroy(&part->fields); |
10978 | 264 |
14038 | 265 g_string_free(part->data, TRUE); |
266 part->data = NULL; | |
267 | |
268 g_free(part); | |
10978 | 269 } |
270 | |
271 | |
14038 | 272 GaimMimePart * |
273 gaim_mime_part_new(GaimMimeDocument *doc) | |
274 { | |
275 g_return_val_if_fail(doc != NULL, NULL); | |
276 return part_new(doc); | |
10978 | 277 } |
278 | |
279 | |
14038 | 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; | |
10978 | 285 } |
286 | |
287 | |
14038 | 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); | |
10978 | 293 } |
294 | |
295 | |
14038 | 296 char * |
297 gaim_mime_part_get_field_decoded(GaimMimePart *part, const char *field) | |
298 { | |
299 const char *f; | |
10978 | 300 |
14038 | 301 g_return_val_if_fail(part != NULL, NULL); |
10978 | 302 |
14038 | 303 f = fields_get(&part->fields, field); |
304 return gaim_mime_decode_field(f); | |
10978 | 305 } |
306 | |
307 | |
14038 | 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); | |
10978 | 313 } |
314 | |
315 | |
14038 | 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); | |
10978 | 321 |
14038 | 322 return part->data->str; |
10978 | 323 } |
324 | |
325 | |
14038 | 326 void |
327 gaim_mime_part_get_data_decoded(GaimMimePart *part, guchar **data, gsize *len) | |
328 { | |
329 const char *enc; | |
10978 | 330 |
14038 | 331 g_return_if_fail(part != NULL); |
332 g_return_if_fail(data != NULL); | |
333 g_return_if_fail(len != NULL); | |
10978 | 334 |
14038 | 335 g_return_if_fail(part->data != NULL); |
10978 | 336 |
14038 | 337 enc = gaim_mime_part_get_field(part, "content-transfer-encoding"); |
10978 | 338 |
14038 | 339 if(! enc) { |
340 *data = (guchar *)g_strdup(part->data->str); | |
341 *len = part->data->len; | |
10978 | 342 |
14038 | 343 } else if(! g_ascii_strcasecmp(enc, "7bit")) { |
344 *data = (guchar *)g_strdup(part->data->str); | |
345 *len = part->data->len; | |
10978 | 346 |
14038 | 347 } else if(! g_ascii_strcasecmp(enc, "8bit")) { |
348 *data = (guchar *)g_strdup(part->data->str); | |
349 *len = part->data->len; | |
11066
2507d20c3d0b
[gaim-migrate @ 13047]
Christopher O'Brien <siege@pidgin.im>
parents:
10978
diff
changeset
|
350 |
14038 | 351 } else if(! g_ascii_strcasecmp(enc, "base16")) { |
352 *data = gaim_base16_decode(part->data->str, len); | |
10978 | 353 |
14038 | 354 } else if(! g_ascii_strcasecmp(enc, "base64")) { |
11127 | 355 *data = gaim_base64_decode(part->data->str, len); |
10978 | 356 |
14038 | 357 } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) { |
358 *data = gaim_quotedp_decode(part->data->str, len); | |
10978 | 359 |
14038 | 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 } | |
10978 | 366 } |
367 | |
368 | |
14038 | 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); | |
10978 | 374 |
14038 | 375 return part->data->len; |
10978 | 376 } |
377 | |
378 | |
14038 | 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); | |
10978 | 385 } |
386 | |
387 | |
14038 | 388 GaimMimeDocument * |
389 gaim_mime_document_new() | |
390 { | |
391 GaimMimeDocument *doc; | |
10978 | 392 |
14038 | 393 doc = g_new0(GaimMimeDocument, 1); |
394 fields_init(&doc->fields); | |
10978 | 395 |
14038 | 396 return doc; |
10978 | 397 } |
398 | |
399 | |
14038 | 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; | |
10978 | 405 |
14038 | 406 const char *bnd; |
407 gsize bl; | |
10978 | 408 |
14038 | 409 bnd = g_strdup_printf("--%s", boundary); |
410 bl = strlen(bnd); | |
10978 | 411 |
14038 | 412 for(b = g_strstr_len(b, n, bnd); b; ) { |
413 char *tail; | |
10978 | 414 |
14038 | 415 /* skip the boundary */ |
416 b += bl; | |
417 n -= bl; | |
10978 | 418 |
14038 | 419 /* skip the trailing \r\n or -- as well */ |
420 if(n >= 2) { | |
421 b += 2; | |
422 n -= 2; | |
423 } | |
10978 | 424 |
14038 | 425 /* find the next boundary */ |
426 tail = g_strstr_len(b, n, bnd); | |
10978 | 427 |
14038 | 428 if(tail) { |
429 gsize sl; | |
10978 | 430 |
14038 | 431 sl = tail - b; |
432 if(sl) { | |
433 GaimMimePart *part = part_new(doc); | |
434 part_load(part, b, sl); | |
435 } | |
436 } | |
10978 | 437 |
14038 | 438 b = tail; |
439 } | |
10978 | 440 } |
441 | |
442 | |
14038 | 443 GaimMimeDocument * |
444 gaim_mime_document_parsen(const char *buf, gsize len) | |
445 { | |
446 GaimMimeDocument *doc; | |
10978 | 447 |
14038 | 448 char *b = (char *) buf; |
449 gsize n = len; | |
10978 | 450 |
14038 | 451 g_return_val_if_fail(buf != NULL, NULL); |
452 | |
453 doc = gaim_mime_document_new(); | |
10978 | 454 |
14038 | 455 if (!len) |
456 return doc; | |
10978 | 457 |
14038 | 458 fields_load(&doc->fields, &b, &n); |
10978 | 459 |
14038 | 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 } | |
10978 | 469 |
14038 | 470 return doc; |
471 } | |
472 | |
10978 | 473 |
14038 | 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)); | |
10978 | 479 } |
480 | |
481 | |
14038 | 482 void |
483 gaim_mime_document_write(GaimMimeDocument *doc, GString *str) | |
484 { | |
485 const char *bd = NULL; | |
10978 | 486 |
14038 | 487 g_return_if_fail(doc != NULL); |
488 g_return_if_fail(str != NULL); | |
10978 | 489 |
14038 | 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 } | |
10978 | 497 |
14038 | 498 fields_write(&doc->fields, str); |
10978 | 499 |
14038 | 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); | |
10978 | 505 |
14038 | 506 part_write(l->data, str); |
10978 | 507 |
14038 | 508 if(! l->next) { |
509 g_string_append_printf(str, "--%s--\r\n", bd); | |
510 } | |
511 } | |
512 } | |
10978 | 513 } |
514 | |
515 | |
14038 | 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; | |
10978 | 521 } |
522 | |
523 | |
14038 | 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); | |
10978 | 537 } |
538 | |
539 | |
14038 | 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; | |
10978 | 545 } |
546 | |
547 | |
14038 | 548 void |
549 gaim_mime_document_free(GaimMimeDocument *doc) | |
550 { | |
551 if (!doc) | |
552 return; | |
10978 | 553 |
14038 | 554 fields_destroy(&doc->fields); |
10978 | 555 |
14038 | 556 while(doc->parts) { |
557 part_free(doc->parts->data); | |
558 doc->parts = g_list_delete_link(doc->parts, doc->parts); | |
559 } | |
10978 | 560 |
14038 | 561 g_free(doc); |
10978 | 562 } |