Merged pidgin/main into default
[pidgin-git.git] / libpurple / mime.c
blob8f68b55a3afdf26fb11b6e456367b1f87eae6db2
1 /*
2 * Purple
4 * Purple 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
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
21 * USA.
24 #include "internal.h"
26 /* this should become "util.h" if we ever get this into purple proper */
27 #include "debug.h"
28 #include "mime.h"
29 #include "util.h"
32 * Utility structure used in both MIME document and parts, which maps
33 * field names to their values, and keeps an easily accessible list of
34 * keys.
36 struct mime_fields {
37 GHashTable *map;
38 GList *keys;
41 struct _PurpleMimeDocument {
42 struct mime_fields fields;
43 GList *parts;
46 struct _PurpleMimePart {
47 struct mime_fields fields;
48 struct _PurpleMimeDocument *doc;
49 GString *data;
52 static void
53 fields_set(struct mime_fields *mf, const char *key, const char *val)
55 char *k, *v;
57 g_return_if_fail(mf != NULL);
58 g_return_if_fail(mf->map != NULL);
60 k = g_ascii_strdown(key, -1);
61 v = g_strdup(val);
63 /* append to the keys list only if it's not already there */
64 if(! g_hash_table_lookup(mf->map, k)) {
65 mf->keys = g_list_append(mf->keys, k);
68 /* important to use insert. If the key is already in the table, then
69 it's already in the keys list. Insert will free the new instance
70 of the key rather than the old instance. */
71 g_hash_table_insert(mf->map, k, v);
75 static const char *
76 fields_get(struct mime_fields *mf, const char *key)
78 char *kdown;
79 const char *ret;
81 g_return_val_if_fail(mf != NULL, NULL);
82 g_return_val_if_fail(mf->map != NULL, NULL);
84 kdown = g_ascii_strdown(key, -1);
85 ret = g_hash_table_lookup(mf->map, kdown);
86 g_free(kdown);
88 return ret;
92 static void
93 fields_init(struct mime_fields *mf)
95 g_return_if_fail(mf != NULL);
97 mf->map = g_hash_table_new_full(g_str_hash, g_str_equal,
98 g_free, g_free);
102 static void
103 fields_loadline(struct mime_fields *mf, const char *line, gsize len)
105 /* split the line into key: value */
106 char *key, *newkey, *val;
107 char **tokens;
109 /* feh, need it to be NUL terminated */
110 key = g_strndup(line, len);
112 /* split */
113 val = strchr(key, ':');
114 if(! val) {
115 g_free(key);
116 return;
118 *val++ = '\0';
120 /* normalize whitespace (sorta) and trim on key and value */
121 tokens = g_strsplit(key, "\t\r\n", 0);
122 newkey = g_strjoinv("", tokens);
123 g_strstrip(newkey);
124 g_strfreev(tokens);
126 tokens = g_strsplit(val, "\t\r\n", 0);
127 val = g_strjoinv("", tokens);
128 g_strstrip(val);
129 g_strfreev(tokens);
131 fields_set(mf, newkey, val);
133 g_free(newkey);
134 g_free(key);
135 g_free(val);
139 static void
140 fields_load(struct mime_fields *mf, char **buf, gsize *len)
142 char *tail;
144 while ((tail = g_strstr_len(*buf, *len, "\r\n")))
146 char *line;
147 gsize ln;
149 /* determine the current line */
150 line = *buf;
151 ln = tail - line;
153 /* advance our search space past the CRLF */
154 *buf = tail + 2;
155 *len -= (ln + 2);
157 /* empty line, end of headers */
158 if(! ln) return;
160 /* look out for line continuations */
161 if(line[ln-1] == ';') {
162 tail = g_strstr_len(*buf, *len, "\r\n");
163 if(tail) {
164 gsize cln;
166 cln = tail - *buf;
167 ln = tail - line;
169 /* advance our search space past the CRLF (again) */
170 *buf = tail + 2;
171 *len -= (cln + 2);
175 /* process our super-cool line */
176 fields_loadline(mf, line, ln);
181 static void
182 field_write(const char *key, const char *val, GString *str)
184 g_string_append_printf(str, "%s: %s\r\n", key, val);
188 static void
189 fields_write(struct mime_fields *mf, GString *str)
191 g_return_if_fail(mf != NULL);
193 g_hash_table_foreach(mf->map, (GHFunc) field_write, str);
194 g_string_append(str, "\r\n");
198 static void
199 fields_destroy(struct mime_fields *mf)
201 g_return_if_fail(mf != NULL);
203 g_hash_table_destroy(mf->map);
204 g_list_free(mf->keys);
206 mf->map = NULL;
207 mf->keys = NULL;
211 static PurpleMimePart *
212 part_new(PurpleMimeDocument *doc)
214 PurpleMimePart *part;
216 part = g_new0(PurpleMimePart, 1);
217 fields_init(&part->fields);
218 part->doc = doc;
219 part->data = g_string_new(NULL);
221 doc->parts = g_list_prepend(doc->parts, part);
223 return part;
227 static void
228 part_load(PurpleMimePart *part, const char *buf, gsize len)
231 char *b = (char *) buf;
232 gsize n = len;
234 fields_load(&part->fields, &b, &n);
236 /* the remainder will have a blank line, if there's anything at all,
237 so check if there's anything then trim off the trailing four
238 bytes, \r\n\r\n */
239 if(n > 4) n -= 4;
240 g_string_append_len(part->data, b, n);
244 static void
245 part_write(PurpleMimePart *part, GString *str)
247 fields_write(&part->fields, str);
248 g_string_append_printf(str, "%s\r\n\r\n", part->data->str);
252 static void
253 part_free(PurpleMimePart *part)
256 fields_destroy(&part->fields);
258 g_string_free(part->data, TRUE);
259 part->data = NULL;
261 g_free(part);
265 PurpleMimePart *
266 purple_mime_part_new(PurpleMimeDocument *doc)
268 g_return_val_if_fail(doc != NULL, NULL);
269 return part_new(doc);
273 GList *
274 purple_mime_part_get_fields(PurpleMimePart *part)
276 g_return_val_if_fail(part != NULL, NULL);
277 return part->fields.keys;
281 const char *
282 purple_mime_part_get_field(PurpleMimePart *part, const char *field)
284 g_return_val_if_fail(part != NULL, NULL);
285 return fields_get(&part->fields, field);
289 char *
290 purple_mime_part_get_field_decoded(PurpleMimePart *part, const char *field)
292 const char *f;
294 g_return_val_if_fail(part != NULL, NULL);
296 f = fields_get(&part->fields, field);
297 return purple_mime_decode_field(f);
301 void
302 purple_mime_part_set_field(PurpleMimePart *part, const char *field, const char *value)
304 g_return_if_fail(part != NULL);
305 fields_set(&part->fields, field, value);
309 const char *
310 purple_mime_part_get_data(PurpleMimePart *part)
312 g_return_val_if_fail(part != NULL, NULL);
313 g_return_val_if_fail(part->data != NULL, NULL);
315 return part->data->str;
319 void
320 purple_mime_part_get_data_decoded(PurpleMimePart *part, guchar **data, gsize *len)
322 const char *enc;
324 g_return_if_fail(part != NULL);
325 g_return_if_fail(data != NULL);
326 g_return_if_fail(len != NULL);
328 g_return_if_fail(part->data != NULL);
330 enc = purple_mime_part_get_field(part, "content-transfer-encoding");
332 if(! enc) {
333 *data = (guchar *)g_strdup(part->data->str);
334 *len = part->data->len;
336 } else if(! g_ascii_strcasecmp(enc, "7bit")) {
337 *data = (guchar *)g_strdup(part->data->str);
338 *len = part->data->len;
340 } else if(! g_ascii_strcasecmp(enc, "8bit")) {
341 *data = (guchar *)g_strdup(part->data->str);
342 *len = part->data->len;
344 } else if(! g_ascii_strcasecmp(enc, "base16")) {
345 *data = purple_base16_decode(part->data->str, len);
347 } else if(! g_ascii_strcasecmp(enc, "base64")) {
348 *data = g_base64_decode(part->data->str, len);
350 } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) {
351 *data = purple_quotedp_decode(part->data->str, len);
353 } else {
354 purple_debug_warning("mime", "purple_mime_part_get_data_decoded:"
355 " unknown encoding '%s'\n", enc);
356 *data = NULL;
357 *len = 0;
362 gsize
363 purple_mime_part_get_length(PurpleMimePart *part)
365 g_return_val_if_fail(part != NULL, 0);
366 g_return_val_if_fail(part->data != NULL, 0);
368 return part->data->len;
372 void
373 purple_mime_part_set_data(PurpleMimePart *part, const char *data)
375 g_return_if_fail(part != NULL);
376 g_string_free(part->data, TRUE);
377 part->data = g_string_new(data);
381 PurpleMimeDocument *
382 purple_mime_document_new()
384 PurpleMimeDocument *doc;
386 doc = g_new0(PurpleMimeDocument, 1);
387 fields_init(&doc->fields);
389 return doc;
393 static void
394 doc_parts_load(PurpleMimeDocument *doc, const char *boundary, const char *buf, gsize len)
396 char *b = (char *) buf;
397 gsize n = len;
399 char *bnd;
400 gsize bl;
402 bnd = g_strdup_printf("--%s", boundary);
403 bl = strlen(bnd);
405 for(b = g_strstr_len(b, n, bnd); b; ) {
406 char *tail;
408 /* skip the boundary */
409 b += bl;
410 n -= bl;
412 /* skip the trailing \r\n or -- as well */
413 if(n >= 2) {
414 b += 2;
415 n -= 2;
418 /* find the next boundary */
419 tail = g_strstr_len(b, n, bnd);
421 if(tail) {
422 gsize sl;
424 sl = tail - b;
425 if(sl) {
426 PurpleMimePart *part = part_new(doc);
427 part_load(part, b, sl);
431 b = tail;
434 g_free(bnd);
437 #define BOUNDARY "boundary="
438 static char *
439 parse_boundary(const char *ct)
441 char *boundary_begin = g_strstr_len(ct, -1, BOUNDARY);
442 char *boundary_end;
444 if (!boundary_begin)
445 return NULL;
447 boundary_begin += sizeof(BOUNDARY) - 1;
449 if (*boundary_begin == '"') {
450 boundary_end = strchr(++boundary_begin, '"');
451 if (!boundary_end)
452 return NULL;
453 } else {
454 boundary_end = strchr(boundary_begin, ' ');
455 if (!boundary_end) {
456 boundary_end = strchr(boundary_begin, ';');
457 if (!boundary_end)
458 boundary_end = boundary_begin + strlen(boundary_begin);
462 return g_strndup(boundary_begin, boundary_end - boundary_begin);
464 #undef BOUNDARY
466 PurpleMimeDocument *
467 purple_mime_document_parsen(const char *buf, gsize len)
469 PurpleMimeDocument *doc;
471 char *b = (char *) buf;
472 gsize n = len;
474 g_return_val_if_fail(buf != NULL, NULL);
476 doc = purple_mime_document_new();
478 if (!len)
479 return doc;
481 fields_load(&doc->fields, &b, &n);
484 const char *ct = fields_get(&doc->fields, "content-type");
485 if (ct && purple_str_has_prefix(ct, "multipart")) {
486 char *bd = parse_boundary(ct);
487 if (bd) {
488 doc_parts_load(doc, bd, b, n);
489 g_free(bd);
494 return doc;
498 PurpleMimeDocument *
499 purple_mime_document_parse(const char *buf)
501 g_return_val_if_fail(buf != NULL, NULL);
502 return purple_mime_document_parsen(buf, strlen(buf));
506 void
507 purple_mime_document_write(PurpleMimeDocument *doc, GString *str)
509 const char *bd = NULL;
511 g_return_if_fail(doc != NULL);
512 g_return_if_fail(str != NULL);
515 const char *ct = fields_get(&doc->fields, "content-type");
516 if(ct && purple_str_has_prefix(ct, "multipart")) {
517 char *b = strrchr(ct, '=');
518 if(b++) bd = b;
522 fields_write(&doc->fields, str);
524 if(bd) {
525 GList *l;
527 for(l = doc->parts; l; l = l->next) {
528 g_string_append_printf(str, "--%s\r\n", bd);
530 part_write(l->data, str);
532 if(! l->next) {
533 g_string_append_printf(str, "--%s--\r\n", bd);
540 GList *
541 purple_mime_document_get_fields(PurpleMimeDocument *doc)
543 g_return_val_if_fail(doc != NULL, NULL);
544 return doc->fields.keys;
548 const char *
549 purple_mime_document_get_field(PurpleMimeDocument *doc, const char *field)
551 g_return_val_if_fail(doc != NULL, NULL);
552 return fields_get(&doc->fields, field);
556 void
557 purple_mime_document_set_field(PurpleMimeDocument *doc, const char *field, const char *value)
559 g_return_if_fail(doc != NULL);
560 fields_set(&doc->fields, field, value);
564 GList *
565 purple_mime_document_get_parts(PurpleMimeDocument *doc)
567 g_return_val_if_fail(doc != NULL, NULL);
568 return doc->parts;
572 void
573 purple_mime_document_free(PurpleMimeDocument *doc)
575 if (!doc)
576 return;
578 fields_destroy(&doc->fields);
580 while(doc->parts) {
581 part_free(doc->parts->data);
582 doc->parts = g_list_delete_link(doc->parts, doc->parts);
585 g_free(doc);