Change the "length of bstream" data type to be a gsize, since it represents
[pidgin-git.git] / libpurple / mime.c
blob4038141744f35fca765e661151510e58360d46f7
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);
440 PurpleMimeDocument *
441 purple_mime_document_parsen(const char *buf, gsize len)
443 PurpleMimeDocument *doc;
445 char *b = (char *) buf;
446 gsize n = len;
448 g_return_val_if_fail(buf != NULL, NULL);
450 doc = purple_mime_document_new();
452 if (!len)
453 return doc;
455 fields_load(&doc->fields, &b, &n);
458 const char *ct = fields_get(&doc->fields, "content-type");
459 if(ct && purple_str_has_prefix(ct, "multipart")) {
460 char *bd = strrchr(ct, '=');
461 if(bd++) {
462 doc_parts_load(doc, bd, b, n);
467 return doc;
471 PurpleMimeDocument *
472 purple_mime_document_parse(const char *buf)
474 g_return_val_if_fail(buf != NULL, NULL);
475 return purple_mime_document_parsen(buf, strlen(buf));
479 void
480 purple_mime_document_write(PurpleMimeDocument *doc, GString *str)
482 const char *bd = NULL;
484 g_return_if_fail(doc != NULL);
485 g_return_if_fail(str != NULL);
488 const char *ct = fields_get(&doc->fields, "content-type");
489 if(ct && purple_str_has_prefix(ct, "multipart")) {
490 char *b = strrchr(ct, '=');
491 if(b++) bd = b;
495 fields_write(&doc->fields, str);
497 if(bd) {
498 GList *l;
500 for(l = doc->parts; l; l = l->next) {
501 g_string_append_printf(str, "--%s\r\n", bd);
503 part_write(l->data, str);
505 if(! l->next) {
506 g_string_append_printf(str, "--%s--\r\n", bd);
513 GList *
514 purple_mime_document_get_fields(PurpleMimeDocument *doc)
516 g_return_val_if_fail(doc != NULL, NULL);
517 return doc->fields.keys;
521 const char *
522 purple_mime_document_get_field(PurpleMimeDocument *doc, const char *field)
524 g_return_val_if_fail(doc != NULL, NULL);
525 return fields_get(&doc->fields, field);
529 void
530 purple_mime_document_set_field(PurpleMimeDocument *doc, const char *field, const char *value)
532 g_return_if_fail(doc != NULL);
533 fields_set(&doc->fields, field, value);
537 GList *
538 purple_mime_document_get_parts(PurpleMimeDocument *doc)
540 g_return_val_if_fail(doc != NULL, NULL);
541 return doc->parts;
545 void
546 purple_mime_document_free(PurpleMimeDocument *doc)
548 if (!doc)
549 return;
551 fields_destroy(&doc->fields);
553 while(doc->parts) {
554 part_free(doc->parts->data);
555 doc->parts = g_list_delete_link(doc->parts, doc->parts);
558 g_free(doc);