Fix a crash when remote users have certain characters in their
[pidgin-git.git] / libpurple / mime.c
blobc63542d1ea2e642adba51042b0e3fece82d40f8f
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"
31 /**
32 * @struct mime_fields
34 * Utility structure used in both MIME document and parts, which maps
35 * field names to their values, and keeps an easily accessible list of
36 * keys.
38 struct mime_fields {
39 GHashTable *map;
40 GList *keys;
43 struct _PurpleMimeDocument {
44 struct mime_fields fields;
45 GList *parts;
48 struct _PurpleMimePart {
49 struct mime_fields fields;
50 struct _PurpleMimeDocument *doc;
51 GString *data;
54 static void
55 fields_set(struct mime_fields *mf, const char *key, const char *val)
57 char *k, *v;
59 g_return_if_fail(mf != NULL);
60 g_return_if_fail(mf->map != NULL);
62 k = g_ascii_strdown(key, -1);
63 v = g_strdup(val);
65 /* append to the keys list only if it's not already there */
66 if(! g_hash_table_lookup(mf->map, k)) {
67 mf->keys = g_list_append(mf->keys, k);
70 /* important to use insert. If the key is already in the table, then
71 it's already in the keys list. Insert will free the new instance
72 of the key rather than the old instance. */
73 g_hash_table_insert(mf->map, k, v);
77 static const char *
78 fields_get(struct mime_fields *mf, const char *key)
80 char *kdown;
81 const char *ret;
83 g_return_val_if_fail(mf != NULL, NULL);
84 g_return_val_if_fail(mf->map != NULL, NULL);
86 kdown = g_ascii_strdown(key, -1);
87 ret = g_hash_table_lookup(mf->map, kdown);
88 g_free(kdown);
90 return ret;
94 static void
95 fields_init(struct mime_fields *mf)
97 g_return_if_fail(mf != NULL);
99 mf->map = g_hash_table_new_full(g_str_hash, g_str_equal,
100 g_free, g_free);
104 static void
105 fields_loadline(struct mime_fields *mf, const char *line, gsize len)
107 /* split the line into key: value */
108 char *key, *newkey, *val;
109 char **tokens;
111 /* feh, need it to be NUL terminated */
112 key = g_strndup(line, len);
114 /* split */
115 val = strchr(key, ':');
116 if(! val) {
117 g_free(key);
118 return;
120 *val++ = '\0';
122 /* normalize whitespace (sorta) and trim on key and value */
123 tokens = g_strsplit(key, "\t\r\n", 0);
124 newkey = g_strjoinv("", tokens);
125 g_strstrip(newkey);
126 g_strfreev(tokens);
128 tokens = g_strsplit(val, "\t\r\n", 0);
129 val = g_strjoinv("", tokens);
130 g_strstrip(val);
131 g_strfreev(tokens);
133 fields_set(mf, newkey, val);
135 g_free(newkey);
136 g_free(key);
137 g_free(val);
141 static void
142 fields_load(struct mime_fields *mf, char **buf, gsize *len)
144 char *tail;
146 while ((tail = g_strstr_len(*buf, *len, "\r\n")))
148 char *line;
149 gsize ln;
151 /* determine the current line */
152 line = *buf;
153 ln = tail - line;
155 /* advance our search space past the CRLF */
156 *buf = tail + 2;
157 *len -= (ln + 2);
159 /* empty line, end of headers */
160 if(! ln) return;
162 /* look out for line continuations */
163 if(line[ln-1] == ';') {
164 tail = g_strstr_len(*buf, *len, "\r\n");
165 if(tail) {
166 gsize cln;
168 cln = tail - *buf;
169 ln = tail - line;
171 /* advance our search space past the CRLF (again) */
172 *buf = tail + 2;
173 *len -= (cln + 2);
177 /* process our super-cool line */
178 fields_loadline(mf, line, ln);
183 static void
184 field_write(const char *key, const char *val, GString *str)
186 g_string_append_printf(str, "%s: %s\r\n", key, val);
190 static void
191 fields_write(struct mime_fields *mf, GString *str)
193 g_return_if_fail(mf != NULL);
195 g_hash_table_foreach(mf->map, (GHFunc) field_write, str);
196 g_string_append(str, "\r\n");
200 static void
201 fields_destroy(struct mime_fields *mf)
203 g_return_if_fail(mf != NULL);
205 g_hash_table_destroy(mf->map);
206 g_list_free(mf->keys);
208 mf->map = NULL;
209 mf->keys = NULL;
213 static PurpleMimePart *
214 part_new(PurpleMimeDocument *doc)
216 PurpleMimePart *part;
218 part = g_new0(PurpleMimePart, 1);
219 fields_init(&part->fields);
220 part->doc = doc;
221 part->data = g_string_new(NULL);
223 doc->parts = g_list_prepend(doc->parts, part);
225 return part;
229 static void
230 part_load(PurpleMimePart *part, const char *buf, gsize len)
233 char *b = (char *) buf;
234 gsize n = len;
236 fields_load(&part->fields, &b, &n);
238 /* the remainder will have a blank line, if there's anything at all,
239 so check if there's anything then trim off the trailing four
240 bytes, \r\n\r\n */
241 if(n > 4) n -= 4;
242 g_string_append_len(part->data, b, n);
246 static void
247 part_write(PurpleMimePart *part, GString *str)
249 fields_write(&part->fields, str);
250 g_string_append_printf(str, "%s\r\n\r\n", part->data->str);
254 static void
255 part_free(PurpleMimePart *part)
258 fields_destroy(&part->fields);
260 g_string_free(part->data, TRUE);
261 part->data = NULL;
263 g_free(part);
267 PurpleMimePart *
268 purple_mime_part_new(PurpleMimeDocument *doc)
270 g_return_val_if_fail(doc != NULL, NULL);
271 return part_new(doc);
275 GList *
276 purple_mime_part_get_fields(PurpleMimePart *part)
278 g_return_val_if_fail(part != NULL, NULL);
279 return part->fields.keys;
283 const char *
284 purple_mime_part_get_field(PurpleMimePart *part, const char *field)
286 g_return_val_if_fail(part != NULL, NULL);
287 return fields_get(&part->fields, field);
291 char *
292 purple_mime_part_get_field_decoded(PurpleMimePart *part, const char *field)
294 const char *f;
296 g_return_val_if_fail(part != NULL, NULL);
298 f = fields_get(&part->fields, field);
299 return purple_mime_decode_field(f);
303 void
304 purple_mime_part_set_field(PurpleMimePart *part, const char *field, const char *value)
306 g_return_if_fail(part != NULL);
307 fields_set(&part->fields, field, value);
311 const char *
312 purple_mime_part_get_data(PurpleMimePart *part)
314 g_return_val_if_fail(part != NULL, NULL);
315 g_return_val_if_fail(part->data != NULL, NULL);
317 return part->data->str;
321 void
322 purple_mime_part_get_data_decoded(PurpleMimePart *part, guchar **data, gsize *len)
324 const char *enc;
326 g_return_if_fail(part != NULL);
327 g_return_if_fail(data != NULL);
328 g_return_if_fail(len != NULL);
330 g_return_if_fail(part->data != NULL);
332 enc = purple_mime_part_get_field(part, "content-transfer-encoding");
334 if(! enc) {
335 *data = (guchar *)g_strdup(part->data->str);
336 *len = part->data->len;
338 } else if(! g_ascii_strcasecmp(enc, "7bit")) {
339 *data = (guchar *)g_strdup(part->data->str);
340 *len = part->data->len;
342 } else if(! g_ascii_strcasecmp(enc, "8bit")) {
343 *data = (guchar *)g_strdup(part->data->str);
344 *len = part->data->len;
346 } else if(! g_ascii_strcasecmp(enc, "base16")) {
347 *data = purple_base16_decode(part->data->str, len);
349 } else if(! g_ascii_strcasecmp(enc, "base64")) {
350 *data = purple_base64_decode(part->data->str, len);
352 } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) {
353 *data = purple_quotedp_decode(part->data->str, len);
355 } else {
356 purple_debug_warning("mime", "purple_mime_part_get_data_decoded:"
357 " unknown encoding '%s'\n", enc);
358 *data = NULL;
359 *len = 0;
364 gsize
365 purple_mime_part_get_length(PurpleMimePart *part)
367 g_return_val_if_fail(part != NULL, 0);
368 g_return_val_if_fail(part->data != NULL, 0);
370 return part->data->len;
374 void
375 purple_mime_part_set_data(PurpleMimePart *part, const char *data)
377 g_return_if_fail(part != NULL);
378 g_string_free(part->data, TRUE);
379 part->data = g_string_new(data);
383 PurpleMimeDocument *
384 purple_mime_document_new()
386 PurpleMimeDocument *doc;
388 doc = g_new0(PurpleMimeDocument, 1);
389 fields_init(&doc->fields);
391 return doc;
395 static void
396 doc_parts_load(PurpleMimeDocument *doc, const char *boundary, const char *buf, gsize len)
398 char *b = (char *) buf;
399 gsize n = len;
401 char *bnd;
402 gsize bl;
404 bnd = g_strdup_printf("--%s", boundary);
405 bl = strlen(bnd);
407 for(b = g_strstr_len(b, n, bnd); b; ) {
408 char *tail;
410 /* skip the boundary */
411 b += bl;
412 n -= bl;
414 /* skip the trailing \r\n or -- as well */
415 if(n >= 2) {
416 b += 2;
417 n -= 2;
420 /* find the next boundary */
421 tail = g_strstr_len(b, n, bnd);
423 if(tail) {
424 gsize sl;
426 sl = tail - b;
427 if(sl) {
428 PurpleMimePart *part = part_new(doc);
429 part_load(part, b, sl);
433 b = tail;
436 g_free(bnd);
439 #define BOUNDARY "boundary="
440 static char *
441 parse_boundary(const char *ct)
443 char *boundary_begin = g_strstr_len(ct, -1, BOUNDARY);
444 char *boundary_end;
446 if (!boundary_begin)
447 return NULL;
449 boundary_begin += sizeof(BOUNDARY) - 1;
451 if (*boundary_begin == '"') {
452 boundary_end = strchr(++boundary_begin, '"');
453 if (!boundary_end)
454 return NULL;
455 } else {
456 boundary_end = strchr(boundary_begin, ' ');
457 if (!boundary_end) {
458 boundary_end = strchr(boundary_begin, ';');
459 if (!boundary_end)
460 boundary_end = boundary_begin + strlen(boundary_begin);
464 return g_strndup(boundary_begin, boundary_end - boundary_begin);
466 #undef BOUNDARY
468 PurpleMimeDocument *
469 purple_mime_document_parsen(const char *buf, gsize len)
471 PurpleMimeDocument *doc;
473 char *b = (char *) buf;
474 gsize n = len;
476 g_return_val_if_fail(buf != NULL, NULL);
478 doc = purple_mime_document_new();
480 if (!len)
481 return doc;
483 fields_load(&doc->fields, &b, &n);
486 const char *ct = fields_get(&doc->fields, "content-type");
487 if (ct && purple_str_has_prefix(ct, "multipart")) {
488 char *bd = parse_boundary(ct);
489 if (bd) {
490 doc_parts_load(doc, bd, b, n);
491 g_free(bd);
496 return doc;
500 PurpleMimeDocument *
501 purple_mime_document_parse(const char *buf)
503 g_return_val_if_fail(buf != NULL, NULL);
504 return purple_mime_document_parsen(buf, strlen(buf));
508 void
509 purple_mime_document_write(PurpleMimeDocument *doc, GString *str)
511 const char *bd = NULL;
513 g_return_if_fail(doc != NULL);
514 g_return_if_fail(str != NULL);
517 const char *ct = fields_get(&doc->fields, "content-type");
518 if(ct && purple_str_has_prefix(ct, "multipart")) {
519 char *b = strrchr(ct, '=');
520 if(b++) bd = b;
524 fields_write(&doc->fields, str);
526 if(bd) {
527 GList *l;
529 for(l = doc->parts; l; l = l->next) {
530 g_string_append_printf(str, "--%s\r\n", bd);
532 part_write(l->data, str);
534 if(! l->next) {
535 g_string_append_printf(str, "--%s--\r\n", bd);
542 GList *
543 purple_mime_document_get_fields(PurpleMimeDocument *doc)
545 g_return_val_if_fail(doc != NULL, NULL);
546 return doc->fields.keys;
550 const char *
551 purple_mime_document_get_field(PurpleMimeDocument *doc, const char *field)
553 g_return_val_if_fail(doc != NULL, NULL);
554 return fields_get(&doc->fields, field);
558 void
559 purple_mime_document_set_field(PurpleMimeDocument *doc, const char *field, const char *value)
561 g_return_if_fail(doc != NULL);
562 fields_set(&doc->fields, field, value);
566 GList *
567 purple_mime_document_get_parts(PurpleMimeDocument *doc)
569 g_return_val_if_fail(doc != NULL, NULL);
570 return doc->parts;
574 void
575 purple_mime_document_free(PurpleMimeDocument *doc)
577 if (!doc)
578 return;
580 fields_destroy(&doc->fields);
582 while(doc->parts) {
583 part_free(doc->parts->data);
584 doc->parts = g_list_delete_link(doc->parts, doc->parts);
587 g_free(doc);