Mercurial > pidgin
annotate src/mime.c @ 12798:09d4ea834370
[gaim-migrate @ 15145]
SF Patch #1389870 from Bleeter, building on work in SF Patch #1114194 from Alex Badea
"Following on from #1114194, where I thought that
sending audibles is a little twitty because we can't
see what's going on, I also noted that it would be nice
if we could display the URL wheree the audible swf file
lived. The example URL provided only supported US
locales, which also needed addressing.
I've tested this patch with the Taiwain audible, named
within the patch, by confirming that the URL pattern
match actually works. Whether it actually works when
sent from a Taiwanese YIM native client, I have no
idea. It looks like it should.
Just to re-iterate: This patch displays the the
received audible's URL and text only, not the audible
swf file itself, nor does it send anything."
committer: Tailor Script <tailor@pidgin.im>
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Mon, 09 Jan 2006 22:28:17 +0000 |
parents | d4cd7d443795 |
children | 443aaa05a7c3 |
rev | line source |
---|---|
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 | |
11291 | 69 g_return_if_fail(mf != NULL); |
70 g_return_if_fail(mf->map != NULL); | |
10978 | 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 | |
11291 | 92 g_return_val_if_fail(mf != NULL, NULL); |
93 g_return_val_if_fail(mf->map != NULL, NULL); | |
10978 | 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) { | |
11291 | 104 g_return_if_fail(mf != NULL); |
10978 | 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) { | |
11291 | 193 g_return_if_fail(mf != NULL); |
10978 | 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) { | |
11291 | 201 g_return_if_fail(mf != NULL); |
10978 | 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 | |
12105
d4cd7d443795
[gaim-migrate @ 14402]
Christopher O'Brien <siege@pidgin.im>
parents:
11780
diff
changeset
|
219 doc->parts = g_list_prepend(doc->parts, part); |
10978 | 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); | |
11291 | 300 g_return_val_if_fail(part->data != NULL, NULL); |
10978 | 301 |
302 return part->data->str; | |
303 } | |
304 | |
305 | |
306 void gaim_mime_part_get_data_decoded(GaimMimePart *part, | |
11137 | 307 guchar **data, gsize *len) { |
10978 | 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 | |
11291 | 314 g_return_if_fail(part->data != NULL); |
10978 | 315 |
316 enc = gaim_mime_part_get_field(part, "content-transfer-encoding"); | |
317 | |
318 if(! enc) { | |
11137 | 319 *data = (guchar *)g_strdup(part->data->str); |
10978 | 320 *len = part->data->len; |
321 | |
322 } else if(! g_ascii_strcasecmp(enc, "7bit")) { | |
11137 | 323 *data = (guchar *)g_strdup(part->data->str); |
10978 | 324 *len = part->data->len; |
325 | |
11066
2507d20c3d0b
[gaim-migrate @ 13047]
Christopher O'Brien <siege@pidgin.im>
parents:
10978
diff
changeset
|
326 } else if(! g_ascii_strcasecmp(enc, "8bit")) { |
11137 | 327 *data = (guchar *)g_strdup(part->data->str); |
11066
2507d20c3d0b
[gaim-migrate @ 13047]
Christopher O'Brien <siege@pidgin.im>
parents:
10978
diff
changeset
|
328 *len = part->data->len; |
2507d20c3d0b
[gaim-migrate @ 13047]
Christopher O'Brien <siege@pidgin.im>
parents:
10978
diff
changeset
|
329 |
10978 | 330 } else if(! g_ascii_strcasecmp(enc, "base16")) { |
11127 | 331 *data = gaim_base16_decode(part->data->str, len); |
10978 | 332 |
333 } else if(! g_ascii_strcasecmp(enc, "base64")) { | |
11127 | 334 *data = gaim_base64_decode(part->data->str, len); |
10978 | 335 |
336 } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) { | |
11132 | 337 *data = gaim_quotedp_decode(part->data->str, len); |
10978 | 338 |
339 } else { | |
340 gaim_debug_warning("mime", "gaim_mime_part_get_data_decoded:" | |
341 " unknown encoding '%s'\n", enc); | |
342 *data = NULL; | |
343 *len = 0; | |
344 } | |
345 } | |
346 | |
347 | |
348 gsize gaim_mime_part_get_length(GaimMimePart *part) { | |
349 g_return_val_if_fail(part != NULL, 0); | |
11291 | 350 g_return_val_if_fail(part->data != NULL, 0); |
10978 | 351 |
352 return part->data->len; | |
353 } | |
354 | |
355 | |
356 void gaim_mime_part_set_data(GaimMimePart *part, const char *data) { | |
357 g_return_if_fail(part != NULL); | |
358 g_string_free(part->data, TRUE); | |
359 part->data = g_string_new(data); | |
360 } | |
361 | |
362 | |
363 GaimMimeDocument *gaim_mime_document_new() { | |
364 GaimMimeDocument *doc; | |
365 | |
366 doc = g_new0(GaimMimeDocument, 1); | |
367 fields_init(&doc->fields); | |
368 | |
369 return doc; | |
370 } | |
371 | |
372 | |
373 static void doc_parts_load(GaimMimeDocument *doc, | |
374 const char *boundary, | |
375 const char *buf, gsize len) { | |
376 | |
377 char *b = (char *) buf; | |
378 gsize n = len; | |
379 | |
380 const char *bnd; | |
381 gsize bl; | |
382 | |
383 bnd = g_strdup_printf("--%s", boundary); | |
384 bl = strlen(bnd); | |
385 | |
386 for(b = g_strstr_len(b, n, bnd); b; ) { | |
387 char *tail; | |
388 | |
389 /* skip the boundary */ | |
390 b += bl; | |
391 n -= bl; | |
392 | |
393 /* skip the trailing \r\n or -- as well */ | |
394 if(n >= 2) { | |
395 b += 2; | |
396 n -= 2; | |
397 } | |
398 | |
399 /* find the next boundary */ | |
400 tail = g_strstr_len(b, n, bnd); | |
401 | |
402 if(tail) { | |
403 gsize sl; | |
404 | |
405 sl = tail - b; | |
406 if(sl) { | |
407 GaimMimePart *part = part_new(doc); | |
408 part_load(part, b, sl); | |
409 } | |
410 } | |
411 | |
412 b = tail; | |
413 } | |
414 } | |
415 | |
416 | |
417 GaimMimeDocument *gaim_mime_document_parsen(const char *buf, gsize len) { | |
418 | |
419 GaimMimeDocument *doc; | |
420 | |
421 char *b = (char *) buf; | |
422 gsize n = len; | |
423 | |
424 g_return_val_if_fail(buf != NULL, NULL); | |
425 | |
426 doc = gaim_mime_document_new(); | |
427 | |
428 if(! len) return doc; | |
429 | |
430 fields_load(&doc->fields, &b, &n); | |
431 | |
432 { | |
433 const char *ct = fields_get(&doc->fields, "content-type"); | |
11780 | 434 if(ct && gaim_str_has_prefix(ct, "multipart")) { |
10978 | 435 char *bd = strrchr(ct, '='); |
436 if(bd++) { | |
437 doc_parts_load(doc, bd, b, n); | |
438 } | |
439 } | |
440 } | |
441 | |
442 return doc; | |
443 } | |
444 | |
445 | |
446 GaimMimeDocument *gaim_mime_document_parse(const char *buf) { | |
447 g_return_val_if_fail(buf != NULL, NULL); | |
448 return gaim_mime_document_parsen(buf, strlen(buf)); | |
449 } | |
450 | |
451 | |
452 void gaim_mime_document_write(GaimMimeDocument *doc, GString *str) { | |
453 const char *bd = NULL; | |
454 | |
455 g_return_if_fail(doc != NULL); | |
456 g_return_if_fail(str != NULL); | |
457 | |
458 { | |
459 const char *ct = fields_get(&doc->fields, "content-type"); | |
11780 | 460 if(ct && gaim_str_has_prefix(ct, "multipart")) { |
10978 | 461 char *b = strrchr(ct, '='); |
462 if(b++) bd = b; | |
463 } | |
464 } | |
465 | |
466 fields_write(&doc->fields, str); | |
467 | |
468 if(bd) { | |
469 GList *l; | |
470 | |
471 for(l = doc->parts; l; l = l->next) { | |
472 g_string_append_printf(str, "--%s\r\n", bd); | |
473 | |
474 part_write(l->data, str); | |
475 | |
476 if(! l->next) { | |
477 g_string_append_printf(str, "--%s--\r\n", bd); | |
478 } | |
479 } | |
480 } | |
481 } | |
482 | |
483 | |
484 const GList *gaim_mime_document_get_fields(GaimMimeDocument *doc) { | |
485 g_return_val_if_fail(doc != NULL, NULL); | |
486 return doc->fields.keys; | |
487 } | |
488 | |
489 | |
490 const char *gaim_mime_document_get_field(GaimMimeDocument *doc, | |
491 const char *field) { | |
492 g_return_val_if_fail(doc != NULL, NULL); | |
493 return fields_get(&doc->fields, field); | |
494 } | |
495 | |
496 | |
497 void gaim_mime_document_set_field(GaimMimeDocument *doc, | |
498 const char *field, | |
499 const char *value) { | |
500 g_return_if_fail(doc != NULL); | |
501 fields_set(&doc->fields, field, value); | |
502 } | |
503 | |
504 | |
505 const GList *gaim_mime_document_get_parts(GaimMimeDocument *doc) { | |
506 g_return_val_if_fail(doc != NULL, NULL); | |
507 return doc->parts; | |
508 } | |
509 | |
510 | |
511 void gaim_mime_document_free(GaimMimeDocument *doc) { | |
512 if(! doc) return; | |
513 | |
514 fields_destroy(&doc->fields); | |
515 | |
516 while(doc->parts) { | |
517 part_free(doc->parts->data); | |
518 doc->parts = g_list_delete_link(doc->parts, doc->parts); | |
519 } | |
520 | |
521 g_free(doc); | |
522 } | |
523 |