Mercurial > pidgin.yaz
annotate libgaim/mime.c @ 15204:f814b2df9cce
[gaim-migrate @ 17993]
Blocking on Google Talk. Our Privacy API sucks so bad that even with no prior support for blocking in Jabber, this has no interface changes. If someone wanted to implement the deprecated Jabber privacy lists API, though, that would be ok by me.
committer: Tailor Script <tailor@pidgin.im>
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Thu, 14 Dec 2006 04:56:54 +0000 |
parents | 2e4690852fd7 |
children |
rev | line source |
---|---|
14192 | 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")) { | |
14719
2e4690852fd7
[gaim-migrate @ 17473]
Etan Reisner <pidgin@unreliablesource.net>
parents:
14192
diff
changeset
|
355 *data = gaim_base64_decode(part->data->str, len); |
14192 | 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 } |