10978
|
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
|