comparison libpurple/mime.c @ 15373:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 32c366eeeb99
comparison
equal deleted inserted replaced
15372:f79e0f4df793 15373:5fe8042783c1
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")) {
355 *data = gaim_base64_decode(part->data->str, len);
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 }