comparison src/mime.c @ 10978:8ab19bf9c3bc

[gaim-migrate @ 12804] some mime utilities. These could probably use a little bit of work, but right now the only thing using them is the sametime plugin, and it handles the multi-part message parsing there just fine. committer: Tailor Script <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Tue, 07 Jun 2005 02:56:11 +0000
parents
children 2507d20c3d0b
comparison
equal deleted inserted replaced
10977:2ce8ec01a064 10978:8ab19bf9c3bc
1
2 /*
3 Gaim
4
5 Gaim is the legal property of its developers, whose names are too
6 numerous to list here. Please refer to the COPYRIGHT file distributed
7 with this source distribution
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or (at
12 your option) any later version.
13
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
22 USA.
23 */
24
25 #include <stdio.h>
26 #include <string.h>
27
28 #include <glib.h>
29 #include <glib/ghash.h>
30 #include <glib/glist.h>
31 #include <glib/gstring.h>
32
33 /* this should become "util.h" if we ever get this into gaim proper */
34 #include <debug.h>
35 #include <util.h>
36
37 #include "mime.h"
38
39
40 /** @struct mime_fields
41
42 Utility structure used in both MIME document and parts, which maps
43 field names to their values, and keeps an easily accessible list of
44 keys
45 */
46 struct mime_fields {
47 GHashTable *map;
48 GList *keys;
49 };
50
51
52 struct _GaimMimeDocument {
53 struct mime_fields fields;
54 GList *parts;
55 };
56
57
58 struct _GaimMimePart {
59 struct mime_fields fields;
60 struct _GaimMimeDocument *doc;
61 GString *data;
62 };
63
64
65 static void fields_set(struct mime_fields *mf,
66 const char *key, const char *val) {
67 char *k, *v;
68
69 g_assert(mf != NULL);
70 g_assert(mf->map != NULL);
71
72 k = g_ascii_strdown(key, -1);
73 v = g_strdup(val);
74
75 /* append to the keys list only if it's not already there */
76 if(! g_hash_table_lookup(mf->map, k)) {
77 mf->keys = g_list_append(mf->keys, k);
78 }
79
80 /* important to use insert. If the key is already in the table, then
81 it's already in the keys list. Insert will free the new instance
82 of the key rather than the old instance. */
83 g_hash_table_insert(mf->map, k, v);
84 }
85
86
87 static const char *fields_get(struct mime_fields *mf,
88 const char *key) {
89 char *kdown;
90 const char *ret;
91
92 g_assert(mf != NULL);
93 g_assert(mf->map != NULL);
94
95 kdown = g_ascii_strdown(key, -1);
96 ret = g_hash_table_lookup(mf->map, kdown);
97 g_free(kdown);
98
99 return ret;
100 }
101
102
103 static void fields_init(struct mime_fields *mf) {
104 g_assert(mf != NULL);
105
106 mf->map = g_hash_table_new_full(g_str_hash, g_str_equal,
107 g_free, g_free);
108 }
109
110
111 static void fields_loadline(struct mime_fields *mf,
112 const char *line, gsize len) {
113
114 /* split the line into key: value */
115 char *key, *val;
116 char **tokens;
117
118 /* feh, need it to be NUL terminated */
119 key = g_strndup(line, len);
120
121 /* split */
122 val = strchr(key, ':');
123 if(! val) {
124 g_free(key);
125 return;
126 }
127 *val++ = '\0';
128
129 /* normalize whitespace (sorta) and trim on key and value */
130 tokens = g_strsplit(key, "\t\r\n", 0);
131 key = g_strjoinv("", tokens);
132 key = g_strstrip(key);
133 g_strfreev(tokens);
134
135 tokens = g_strsplit(val, "\t\r\n", 0);
136 val = g_strjoinv("", tokens);
137 val = g_strstrip(val);
138 g_strfreev(tokens);
139
140 fields_set(mf, key, val);
141
142 g_free(key);
143 g_free(val);
144 }
145
146
147 static void fields_load(struct mime_fields *mf,
148 char **buf, gsize *len) {
149 char *tail;
150
151 while( (tail = g_strstr_len(*buf, *len, "\r\n")) ) {
152 char *line;
153 gsize ln;
154
155 /* determine the current line */
156 line = *buf;
157 ln = tail - line;
158
159 /* advance our search space past the CRLF */
160 *buf = tail + 2;
161 *len -= (ln + 2);
162
163 /* empty line, end of headers */
164 if(! ln) return;
165
166 /* look out for line continuations */
167 if(line[ln-1] == ';') {
168 tail = g_strstr_len(*buf, *len, "\r\n");
169 if(tail) {
170 gsize cln;
171
172 cln = tail - *buf;
173 ln = tail - line;
174
175 /* advance our search space past the CRLF (again) */
176 *buf = tail + 2;
177 *len -= (cln + 2);
178 }
179 }
180
181 /* process our super-cool line */
182 fields_loadline(mf, line, ln);
183 }
184 }
185
186
187 static void field_write(const char *key, const char *val, GString *str) {
188 g_string_append_printf(str, "%s: %s\r\n", key, val);
189 }
190
191
192 static void fields_write(struct mime_fields *mf, GString *str) {
193 g_assert(mf != NULL);
194
195 g_hash_table_foreach(mf->map, (GHFunc) field_write, str);
196 g_string_append(str, "\r\n");
197 }
198
199
200 static void fields_destroy(struct mime_fields *mf) {
201 g_assert(mf != NULL);
202
203 g_hash_table_destroy(mf->map);
204 g_list_free(mf->keys);
205
206 mf->map = NULL;
207 mf->keys = NULL;
208 }
209
210
211 static GaimMimePart *part_new(GaimMimeDocument *doc) {
212 GaimMimePart *part;
213
214 part = g_new0(GaimMimePart, 1);
215 fields_init(&part->fields);
216 part->doc = doc;
217 part->data = g_string_new(NULL);
218
219 doc->parts = g_list_append(doc->parts, part);
220
221 return part;
222 }
223
224
225 static void part_load(GaimMimePart *part,
226 const char *buf, gsize len) {
227
228 char *b = (char *) buf;
229 gsize n = len;
230
231 fields_load(&part->fields, &b, &n);
232
233 /* the remainder will have a blank line, if there's anything at all,
234 so check if there's anything then trim off the trailing four
235 bytes, \r\n\r\n */
236 if(n > 4) n -= 4;
237 g_string_append_len(part->data, b, n);
238 }
239
240
241 static void part_write(GaimMimePart *part, GString *str) {
242 fields_write(&part->fields, str);
243 g_string_append_printf(str, "%s\r\n\r\n", part->data->str);
244 }
245
246
247 static void part_free(GaimMimePart *part) {
248
249 fields_destroy(&part->fields);
250
251 g_string_free(part->data, TRUE);
252 part->data = NULL;
253
254 g_free(part);
255 }
256
257
258 GaimMimePart *gaim_mime_part_new(GaimMimeDocument *doc) {
259 g_return_val_if_fail(doc != NULL, NULL);
260 return part_new(doc);
261 }
262
263
264 const GList *gaim_mime_part_get_fields(GaimMimePart *part) {
265 g_return_val_if_fail(part != NULL, NULL);
266 return part->fields.keys;
267 }
268
269
270 const char *gaim_mime_part_get_field(GaimMimePart *part,
271 const char *field) {
272
273 g_return_val_if_fail(part != NULL, NULL);
274 return fields_get(&part->fields, field);
275 }
276
277
278 char *gaim_mime_part_get_field_decoded(GaimMimePart *part,
279 const char *field) {
280
281 const char *f;
282
283 g_return_val_if_fail(part != NULL, NULL);
284
285 f = fields_get(&part->fields, field);
286 return gaim_mime_decode_field(f);
287 }
288
289
290 void gaim_mime_part_set_field(GaimMimePart *part,
291 const char *field,
292 const char *value) {
293 g_return_if_fail(part != NULL);
294 fields_set(&part->fields, field, value);
295 }
296
297
298 const char *gaim_mime_part_get_data(GaimMimePart *part) {
299 g_return_val_if_fail(part != NULL, NULL);
300 g_assert(part->data != NULL);
301
302 return part->data->str;
303 }
304
305
306 void gaim_mime_part_get_data_decoded(GaimMimePart *part,
307 char **data, gsize *len) {
308 const char *enc;
309
310 g_return_if_fail(part != NULL);
311 g_return_if_fail(data != NULL);
312 g_return_if_fail(len != NULL);
313
314 g_assert(part->data != NULL);
315
316 enc = gaim_mime_part_get_field(part, "content-transfer-encoding");
317
318 if(! enc) {
319 *data = g_strdup(part->data->str);
320 *len = part->data->len;
321
322 } else if(! g_ascii_strcasecmp(enc, "7bit")) {
323 *data = g_strdup(part->data->str);
324 *len = part->data->len;
325
326 } else if(! g_ascii_strcasecmp(enc, "base16")) {
327 *len = gaim_base16_decode(part->data->str, (unsigned char **) data);
328
329 } else if(! g_ascii_strcasecmp(enc, "base64")) {
330 gaim_base64_decode(part->data->str, data, len);
331
332 } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) {
333 gaim_quotedp_decode(part->data->str, data, len);
334
335 } else {
336 gaim_debug_warning("mime", "gaim_mime_part_get_data_decoded:"
337 " unknown encoding '%s'\n", enc);
338 *data = NULL;
339 *len = 0;
340 }
341 }
342
343
344 gsize gaim_mime_part_get_length(GaimMimePart *part) {
345 g_return_val_if_fail(part != NULL, 0);
346 g_assert(part->data != NULL);
347
348 return part->data->len;
349 }
350
351
352 void gaim_mime_part_set_data(GaimMimePart *part, const char *data) {
353 g_return_if_fail(part != NULL);
354 g_string_free(part->data, TRUE);
355 part->data = g_string_new(data);
356 }
357
358
359 GaimMimeDocument *gaim_mime_document_new() {
360 GaimMimeDocument *doc;
361
362 doc = g_new0(GaimMimeDocument, 1);
363 fields_init(&doc->fields);
364
365 return doc;
366 }
367
368
369 static void doc_parts_load(GaimMimeDocument *doc,
370 const char *boundary,
371 const char *buf, gsize len) {
372
373 char *b = (char *) buf;
374 gsize n = len;
375
376 const char *bnd;
377 gsize bl;
378
379 bnd = g_strdup_printf("--%s", boundary);
380 bl = strlen(bnd);
381
382 for(b = g_strstr_len(b, n, bnd); b; ) {
383 char *tail;
384
385 /* skip the boundary */
386 b += bl;
387 n -= bl;
388
389 /* skip the trailing \r\n or -- as well */
390 if(n >= 2) {
391 b += 2;
392 n -= 2;
393 }
394
395 /* find the next boundary */
396 tail = g_strstr_len(b, n, bnd);
397
398 if(tail) {
399 gsize sl;
400
401 sl = tail - b;
402 if(sl) {
403 GaimMimePart *part = part_new(doc);
404 part_load(part, b, sl);
405 }
406 }
407
408 b = tail;
409 }
410 }
411
412
413 GaimMimeDocument *gaim_mime_document_parsen(const char *buf, gsize len) {
414
415 GaimMimeDocument *doc;
416
417 char *b = (char *) buf;
418 gsize n = len;
419
420 g_return_val_if_fail(buf != NULL, NULL);
421
422 doc = gaim_mime_document_new();
423
424 if(! len) return doc;
425
426 fields_load(&doc->fields, &b, &n);
427
428 {
429 const char *ct = fields_get(&doc->fields, "content-type");
430 if(ct && g_str_has_prefix(ct, "multipart")) {
431 char *bd = strrchr(ct, '=');
432 if(bd++) {
433 doc_parts_load(doc, bd, b, n);
434 }
435 }
436 }
437
438 return doc;
439 }
440
441
442 GaimMimeDocument *gaim_mime_document_parse(const char *buf) {
443 g_return_val_if_fail(buf != NULL, NULL);
444 return gaim_mime_document_parsen(buf, strlen(buf));
445 }
446
447
448 void gaim_mime_document_write(GaimMimeDocument *doc, GString *str) {
449 const char *bd = NULL;
450
451 g_return_if_fail(doc != NULL);
452 g_return_if_fail(str != NULL);
453
454 {
455 const char *ct = fields_get(&doc->fields, "content-type");
456 if(ct && g_str_has_prefix(ct, "multipart")) {
457 char *b = strrchr(ct, '=');
458 if(b++) bd = b;
459 }
460 }
461
462 fields_write(&doc->fields, str);
463
464 if(bd) {
465 GList *l;
466
467 for(l = doc->parts; l; l = l->next) {
468 g_string_append_printf(str, "--%s\r\n", bd);
469
470 part_write(l->data, str);
471
472 if(! l->next) {
473 g_string_append_printf(str, "--%s--\r\n", bd);
474 }
475 }
476 }
477 }
478
479
480 const GList *gaim_mime_document_get_fields(GaimMimeDocument *doc) {
481 g_return_val_if_fail(doc != NULL, NULL);
482 return doc->fields.keys;
483 }
484
485
486 const char *gaim_mime_document_get_field(GaimMimeDocument *doc,
487 const char *field) {
488 g_return_val_if_fail(doc != NULL, NULL);
489 return fields_get(&doc->fields, field);
490 }
491
492
493 void gaim_mime_document_set_field(GaimMimeDocument *doc,
494 const char *field,
495 const char *value) {
496 g_return_if_fail(doc != NULL);
497 fields_set(&doc->fields, field, value);
498 }
499
500
501 const GList *gaim_mime_document_get_parts(GaimMimeDocument *doc) {
502 g_return_val_if_fail(doc != NULL, NULL);
503 return doc->parts;
504 }
505
506
507 void gaim_mime_document_free(GaimMimeDocument *doc) {
508 if(! doc) return;
509
510 fields_destroy(&doc->fields);
511
512 while(doc->parts) {
513 part_free(doc->parts->data);
514 doc->parts = g_list_delete_link(doc->parts, doc->parts);
515 }
516
517 g_free(doc);
518 }
519