Added gettext.h convenience header to support --disable-nls
[barry.git] / src / vformat.c
blob2decab70fc3ddc82c71175941591ad409467e685
1 /*
2 * Copyright (C) 2003 Ximian, Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Author: Chris Toshok (toshok@ximian.com)
19 * Author: Armin Bauer (armin.bauer@opensync.org)
23 #include "vformat.h"
24 #include "clog.h"
26 //#ifdef HAVE_CONFIG_H
27 //#include "config.h"
28 //#endif
30 #include <string.h>
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <stdlib.h>
34 #include <iconv.h>
35 //#include <opensync/opensync.h>
37 #define TRACE_INTERNAL 1
38 #define TRACE_ENTRY 1
39 #define TRACE_EXIT 1
40 #define TRACE_ERROR 0
42 static size_t base64_encode_step(const unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save);
43 static size_t base64_decode_step(const unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save);
44 static size_t base64_decode_simple (char *data, size_t len);
45 static char *base64_encode_simple (const char *data, size_t len);
47 static size_t quoted_decode_simple (char *data, size_t len);
48 static char *quoted_encode_simple (const unsigned char *string, int len);
51 /**
52 * _helper_is_base64 is helper function to check i a string is "b" or "base64"
53 * @param check_string string that should be compared with "b" or "base64"
54 * @return 0 if check_string is not base64 and 1 if it is
56 static int _helper_is_base64(const char *check_string)
58 if(!g_ascii_strcasecmp ((char *) check_string, "BASE64") ||
59 !g_ascii_strcasecmp ((char *) check_string, "b") )
60 return (1);
61 return (0);
64 time_t b_vformat_time_to_unix(const char *inptime)
66 char *date = NULL;
67 char *time = NULL;
68 char *ftime = NULL;
69 if ((ftime = g_strrstr(inptime, "T"))) {
71 date = g_strndup(inptime, ftime - inptime);
72 if (ftime[3] == ':')
73 time = g_strndup(ftime + 1, 8);
74 else
75 time = g_strndup(ftime + 1, 6);
76 } else {
77 date = g_strdup(inptime);
80 struct tm btime;
81 memset(&btime, 0, sizeof(struct tm));
82 btime.tm_isdst = -1;
84 if (strlen(date) == 10) {
85 btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111 - 1900;
86 btime.tm_mon = date[5] * 10 + date[6] - '0' * 11 - 1;
87 btime.tm_mday = date[8] * 10 + date[9] - '0' * 11;
88 } else {
89 btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111- 1900;
90 btime.tm_mon = date[4] * 10 + date[5] - '0' * 11 - 1;
91 btime.tm_mday = date[6] * 10 + date[7] - '0' * 11;
94 if (time && strlen(time) == 8) {
95 //Time
96 btime.tm_hour = time[0] * 10 + time[1] - '0' * 11;
97 btime.tm_min = time[3] * 10 + time[4] - '0' * 11;
98 btime.tm_sec = time[6] * 10 + time[7] - '0' * 11;
99 } else if (time && strlen(time) == 6) {
100 btime.tm_hour = time[0] * 10 + time[1] - '0' * 11;
101 btime.tm_min = time[2] * 10 + time[3] - '0' * 11;
102 btime.tm_sec = time[4] * 10 + time[5] - '0' * 11;
105 time_t utime = mktime(&btime);
106 return utime;
109 static char *_fold_lines (char *buf)
111 GString *str = g_string_new ("");
112 GString *line = g_string_new ("");
113 char *p = buf;
114 char *next, *next2, *q;
115 gboolean newline = TRUE;
116 gboolean quotedprintable = FALSE;
119 * We're pretty liberal with line folding here. We handle
120 * lines folded with \r\n<WS>, \n\r<WS>, \n<WS>, =\r\n and =\n\r.
121 * We also turn single \r's and \n's not followed by <WS> into \r\n's.
124 while (*p) {
126 /* search new lines for quoted printable encoding */
127 if (newline) {
128 for (q=p; *q != '\n' && *q != '\0'; q++)
129 line = g_string_append_unichar (line, g_utf8_get_char (q));
131 if (strstr(line->str, "ENCODING=QUOTED-PRINTABLE"))
132 quotedprintable = TRUE;
134 g_string_free(line, TRUE);
135 line = g_string_new ("");
137 newline = FALSE;
141 if ((quotedprintable && *p == '=') || *p == '\r' || *p == '\n') {
142 next = g_utf8_next_char (p);
143 if (*next == '\n' || *next == '\r') {
144 next2 = g_utf8_next_char (next);
145 if (*next2 == '\n' || *next2 == '\r' || *next2 == ' ' || *next2 == '\t') {
146 p = g_utf8_next_char (next2);
148 else {
149 str = g_string_append (str, CRLF);
150 p = g_utf8_next_char (next);
151 newline = TRUE;
152 quotedprintable = FALSE;
155 else if (*p == '=') {
156 str = g_string_append_unichar (str, g_utf8_get_char (p));
157 p = g_utf8_next_char (p);
159 else if (*next == ' ' || *next == '\t') {
160 p = g_utf8_next_char (next);
162 else {
163 str = g_string_append (str, CRLF);
164 p = g_utf8_next_char (p);
165 newline = TRUE;
166 quotedprintable = FALSE;
169 else {
170 str = g_string_append_unichar (str, g_utf8_get_char (p));
171 p = g_utf8_next_char (p);
175 g_free (buf);
176 g_string_free(line, TRUE);
178 return g_string_free (str, FALSE);
181 /* skip forward until we hit the CRLF, or \0 */
182 static void _skip_to_next_line (char **p)
184 char *lp;
185 lp = *p;
187 while (*lp != '\r' && *lp != '\0')
188 lp = g_utf8_next_char (lp);
190 if (*lp == '\r') {
191 lp = g_utf8_next_char (lp); /* \n */
192 lp = g_utf8_next_char (lp); /* start of the next line */
195 *p = lp;
198 /* skip forward until we hit a character in @s, CRLF, or \0. leave *p
199 pointing at the character that causes us to stop */
200 static void _skip_until (char **p, char *s)
202 char *lp;
204 lp = *p;
206 while (*lp != '\r' && *lp != '\0') {
207 gboolean s_matches = FALSE;
208 char *ls;
209 for (ls = s; *ls; ls = g_utf8_next_char (ls)) {
210 if (g_utf8_get_char (ls) == g_utf8_get_char (lp)) {
211 s_matches = TRUE;
212 break;
216 if (s_matches)
217 break;
218 lp++;
221 *p = lp;
224 static void _read_attribute_value_add (b_VFormatAttribute *attr, GString *str, GString *charset)
226 /* don't convert empty strings */
227 if (str->len == 0) {
228 b_vformat_attribute_add_value(attr, str->str);
229 return;
232 char *inbuf, *outbuf, *p;
233 size_t inbytesleft, outbytesleft;
235 inbuf = str->str;
236 p = outbuf = malloc(str->len*2);
237 inbytesleft = str->len;
238 outbytesleft = str->len*2;
240 iconv_t cd;
242 /* if a CHARSET was given, let's try to convert inbuf to UTF-8 */
243 if (charset) {
245 cd = iconv_open("UTF-8", charset->str);
246 #ifdef SOLARIS
247 if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
248 #else
249 if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
250 #endif
251 *p = 0;
252 b_vformat_attribute_add_value(attr, outbuf);
254 } else {
256 /* hmm, should not happen */
257 b_vformat_attribute_add_value(attr, str->str);
261 iconv_close(cd);
263 } else {
265 /* no CHARSET was given, if inbuf is already UTF-8 we add str->str */
266 if (g_utf8_validate (inbuf, -1, NULL)) {
268 b_vformat_attribute_add_value (attr, str->str);
270 } else {
272 /* because inbuf is not UTF-8, we think it is ISO-8859-1 */
273 cd = iconv_open("UTF-8", "ISO-8859-1");
274 #ifdef SOLARIS
275 if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
276 #else
277 if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
278 #endif
279 *p = 0;
280 b_vformat_attribute_add_value (attr, outbuf);
282 } else {
284 b_vformat_attribute_add_value (attr, str->str);
288 iconv_close(cd);
294 free(outbuf);
298 static void _read_attribute_value (b_VFormatAttribute *attr, char **p, int format_encoding, GString *charset)
300 char *lp = *p;
301 GString *str;
303 /* read in the value */
304 str = g_string_new ("");
305 while (*lp != '\r' && *lp != '\0') {
306 if (*lp == '=' && format_encoding == VF_ENCODING_QP) {
307 char a, b, x1=0, x2=0;
309 if ((a = *(++lp)) == '\0') break;
310 if ((b = *(++lp)) == '\0') break;
312 if (isalnum(a)) {
313 if (isalnum(b)) {
314 /* e.g. ...N=C3=BCrnberg\r\n
315 * ^^^
317 x1=a;
318 x2=b;
320 else if (b == '=') {
321 /* e.g. ...N=C=\r\n
322 * ^^^
323 * 3=BCrnberg...
326 char *tmplp = lp;
327 if (*(++tmplp) == '\r' && *(++tmplp) == '\n' && isalnum(*(++tmplp))) {
328 x1 = a;
329 x2 = *tmplp;
330 lp = tmplp;
333 else {
334 /* append malformed input, and
335 continue parsing */
336 str = g_string_append_c(str, a);
337 str = g_string_append_c(str, b);
340 else if (a == '=') {
341 char *tmplp = lp;
342 char c, d, e;
343 c = *(++tmplp);
344 d = *(++tmplp);
345 e = *(++tmplp);
346 if (b == '\r' && c == '\n' && isalnum(d) && isalnum(e)) {
347 x1 = d;
348 x2 = e;
349 lp = tmplp;
351 else {
352 /* append malformed input, and
353 continue parsing */
354 str = g_string_append_c(str, a);
355 str = g_string_append_c(str, b);
358 else {
359 /* append malformed input, and
360 continue parsing */
361 str = g_string_append_c(str, a);
362 str = g_string_append_c(str, b);
364 if (x1 && x2) {
365 char c;
367 a = tolower (x1);
368 b = tolower (x2);
370 c = (((a>='a'?a-'a'+10:a-'0')&0x0f) << 4)
371 | ((b>='a'?b-'a'+10:b-'0')&0x0f);
373 str = g_string_append_c (str, c);
375 lp++;
376 x1 = x2 = 0;
378 else if (format_encoding == VF_ENCODING_BASE64) {
379 if((*lp != ' ') && (*lp != '\t') )
380 str = g_string_append_unichar (str, g_utf8_get_char (lp));
381 lp = g_utf8_next_char(lp);
383 else if (*lp == '\\') {
384 /* convert back to the non-escaped version of
385 the characters */
386 lp = g_utf8_next_char(lp);
387 if (*lp == '\0') {
388 str = g_string_append_c (str, '\\');
389 break;
391 switch (*lp) {
392 case 'n': str = g_string_append_c (str, '\n'); break;
393 case 'r': str = g_string_append_c (str, '\r'); break;
394 case ';': str = g_string_append_c (str, ';'); break;
395 case ',':
396 if (!g_ascii_strcasecmp (attr->name, "CATEGORIES")) {
397 //We need to handle categories here to work
398 //aroung a bug in evo2
399 _read_attribute_value_add (attr, str, charset);
400 g_string_assign (str, "");
401 } else
402 str = g_string_append_c (str, ',');
403 break;
404 case '\\': str = g_string_append_c (str, '\\'); break;
405 case '"': str = g_string_append_c (str, '"'); break;
406 /* \t is (incorrectly) used by kOrganizer, so handle it here */
407 case 't': str = g_string_append_c (str, '\t'); break;
408 default:
409 BarryLogf(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %u", (unsigned int)*lp);
410 str = g_string_append_c (str, '\\');
411 str = g_string_append_unichar (str, g_utf8_get_char(lp));
412 break;
414 lp = g_utf8_next_char(lp);
416 else if ((*lp == ';') ||
417 (*lp == ',' && !g_ascii_strcasecmp (attr->name, "CATEGORIES"))) {
418 _read_attribute_value_add (attr, str, charset);
419 g_string_assign (str, "");
420 lp = g_utf8_next_char(lp);
422 else {
423 str = g_string_append_unichar (str, g_utf8_get_char (lp));
424 lp = g_utf8_next_char(lp);
427 if (str) {
428 _read_attribute_value_add (attr, str, charset);
429 g_string_free (str, TRUE);
432 if (*lp == '\r') {
433 lp = g_utf8_next_char (lp); /* \n */
434 lp = g_utf8_next_char (lp); /* start of the next line */
437 *p = lp;
440 static void _read_attribute_params(b_VFormatAttribute *attr, char **p, int *format_encoding, GString **charset)
442 char *lp = *p;
443 GString *str;
444 b_VFormatParam *param = NULL;
445 gboolean in_quote = FALSE;
446 str = g_string_new ("");
448 while (*lp != '\0') {
449 if (*lp == '"') {
450 in_quote = !in_quote;
451 lp = g_utf8_next_char (lp);
453 else if (in_quote || g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/' || *lp == '.' || *lp == ' ') {
454 str = g_string_append_unichar (str, g_utf8_get_char (lp));
455 lp = g_utf8_next_char (lp);
457 /* accumulate until we hit the '=' or ';'. If we hit
458 * a '=' the string contains the parameter name. if
459 * we hit a ';' the string contains the parameter
460 * value and the name is either ENCODING (if value ==
461 * QUOTED-PRINTABLE) or TYPE (in any other case.)
463 else if (*lp == '=') {
464 if (str->len > 0) {
465 param = b_vformat_attribute_param_new (str->str);
466 g_string_assign (str, "");
467 lp = g_utf8_next_char (lp);
469 else {
470 _skip_until (&lp, ":;");
471 if (*lp == '\r') {
472 lp = g_utf8_next_char (lp); /* \n */
473 lp = g_utf8_next_char (lp); /* start of the next line */
474 break;
476 else if (*lp == ';')
477 lp = g_utf8_next_char (lp);
480 else if (*lp == ';' || *lp == ':' || *lp == ',') {
481 gboolean colon = (*lp == ':');
482 gboolean comma = (*lp == ',');
484 if (param) {
485 if (str->len > 0) {
486 b_vformat_attribute_param_add_value (param, str->str);
487 g_string_assign (str, "");
488 if (!colon)
489 lp = g_utf8_next_char (lp);
491 else {
492 /* we've got a parameter of the form:
493 * PARAM=(.*,)?[:;]
494 * so what we do depends on if there are already values
495 * for the parameter. If there are, we just finish
496 * this parameter and skip past the offending character
497 * (unless it's the ':'). If there aren't values, we free
498 * the parameter then skip past the character.
500 if (!param->values) {
501 b_vformat_attribute_param_free (param);
502 param = NULL;
503 if (!colon)
504 lp = g_utf8_next_char (lp);
508 if (param
509 && !g_ascii_strcasecmp (param->name, "encoding")) {
510 if (!g_ascii_strcasecmp (param->values->data, "quoted-printable")) {
511 *format_encoding = VF_ENCODING_QP;
512 b_vformat_attribute_param_free (param);
513 param = NULL;
514 } else if ( _helper_is_base64(param->values->data)) {
515 *format_encoding = VF_ENCODING_BASE64;
516 // b_vformat_attribute_param_free (param);
517 // param = NULL;
519 } else if (param && !g_ascii_strcasecmp(param->name, "charset")) {
520 *charset = g_string_new(param->values->data);
521 b_vformat_attribute_param_free (param);
522 param = NULL;
525 else {
526 if (str->len > 0) {
527 char *param_name;
528 if (!g_ascii_strcasecmp (str->str,
529 "quoted-printable")) {
530 param_name = "ENCODING";
531 *format_encoding = VF_ENCODING_QP;
533 /* apple's broken addressbook app outputs naked BASE64
534 parameters, which aren't even vcard 3.0 compliant. */
535 else if (!g_ascii_strcasecmp (str->str,
536 "base64")) {
537 param_name = "ENCODING";
538 g_string_assign (str, "b");
539 *format_encoding = VF_ENCODING_BASE64;
541 else {
542 param_name = "TYPE";
545 if (param_name) {
546 param = b_vformat_attribute_param_new (param_name);
547 b_vformat_attribute_param_add_value (param, str->str);
549 g_string_assign (str, "");
550 if (!colon)
551 lp = g_utf8_next_char (lp);
553 else {
554 /* we've got an attribute with a truly empty
555 attribute parameter. So it's of the form:
557 ATTR;[PARAM=value;]*;[PARAM=value;]*:
559 (note the extra ';')
561 the only thing to do here is, well.. nothing.
562 we skip over the character if it's not a colon,
563 and the rest is handled for us: We'll either
564 continue through the loop again if we hit a ';',
565 or we'll break out correct below if it was a ':' */
566 if (!colon)
567 lp = g_utf8_next_char (lp);
570 if (param && !comma) {
571 b_vformat_attribute_add_param (attr, param);
572 param = NULL;
574 if (colon)
575 break;
577 else {
578 BarryLogf(TRACE_INTERNAL, "invalid character found in parameter spec: \"%i\" String so far: %s", lp[0], str->str);
579 g_string_assign (str, "");
580 _skip_until (&lp, ":;");
584 if (str)
585 g_string_free (str, TRUE);
587 *p = lp;
590 /* reads an entire attribute from the input buffer, leaving p pointing
591 at the start of the next line (past the \r\n) */
592 static b_VFormatAttribute *_read_attribute (char **p)
594 char *attr_group = NULL;
595 char *attr_name = NULL;
596 b_VFormatAttribute *attr = NULL;
597 GString *str, *charset = NULL;
598 char *lp = *p;
600 gboolean is_qp = FALSE;
602 /* first read in the group/name */
603 str = g_string_new ("");
604 while (*lp != '\r' && *lp != '\0') {
605 if (*lp == ':' || *lp == ';') {
606 if (str->len != 0) {
607 /* we've got a name, break out to the value/attribute parsing */
608 attr_name = g_string_free (str, FALSE);
609 break;
611 else {
612 /* a line of the form:
613 * (group.)?[:;]
615 * since we don't have an attribute
616 * name, skip to the end of the line
617 * and try again.
619 g_string_free (str, TRUE);
620 *p = lp;
621 _skip_to_next_line(p);
622 goto lose;
625 else if (*lp == '.') {
626 if (attr_group) {
627 BarryLogf(TRACE_INTERNAL, "extra `.' in attribute specification. ignoring extra group `%s'", str->str);
628 g_string_free (str, TRUE);
629 str = g_string_new ("");
631 if (str->len != 0) {
632 attr_group = g_string_free (str, FALSE);
633 str = g_string_new ("");
636 else if (g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/') {
637 str = g_string_append_unichar (str, g_utf8_get_char (lp));
639 else {
640 BarryLogf(TRACE_INTERNAL, "invalid character found in attribute group/name: \"%i\" String so far: %s", lp[0], str->str);
641 g_string_free (str, TRUE);
642 *p = lp;
643 _skip_to_next_line(p);
644 goto lose;
647 lp = g_utf8_next_char(lp);
650 if (!attr_name) {
651 _skip_to_next_line (p);
652 goto lose;
655 attr = b_vformat_attribute_new (attr_group, attr_name);
656 g_free (attr_group);
657 g_free (attr_name);
659 if (*lp == ';') {
660 /* skip past the ';' */
661 lp = g_utf8_next_char(lp);
662 _read_attribute_params (attr, &lp, &is_qp, &charset);
664 if (*lp == ':') {
665 /* skip past the ':' */
666 lp = g_utf8_next_char(lp);
667 _read_attribute_value (attr, &lp, is_qp, charset);
670 if (charset) g_string_free(charset, TRUE);
671 *p = lp;
673 if (!attr->values)
674 goto lose;
676 return attr;
677 lose:
678 if (attr)
679 b_vformat_attribute_free (attr);
680 return NULL;
683 static void open_block(char **block, const char *block_name)
685 char *start = *block ? *block : "";
686 char *result = NULL;
688 result = g_strconcat(start, "/", block_name, NULL);
689 if( *block )
690 g_free(*block);
691 *block = result;
694 static void close_block(char **block, const char *block_name)
696 int name_len = strlen(block_name);
697 int block_len = *block ? strlen(*block) : 0;
698 char *cmp_start = NULL;
700 if( block_len < name_len + 1 )
701 return;
703 cmp_start = *block + (block_len - name_len - 1);
704 if( cmp_start[0] == '/' &&
705 g_ascii_strcasecmp(cmp_start+1, block_name) == 0 )
707 // end of block hierarchy contains block name,
708 // so safe to remove
710 // cut off the end of the string... no need to free/realloc
711 *cmp_start = '\0';
715 /* we try to be as forgiving as we possibly can here - this isn't a
716 * validator. Almost nothing is considered a fatal error. We always
717 * try to return *something*.
719 static void _parse(b_VFormat *evc, const char *str)
721 char *buf = g_strdup (str);
722 char *p, *end;
723 b_VFormatAttribute *attr;
725 /* first validate the string is valid utf8 */
726 if (!g_utf8_validate (buf, -1, (const char **)&end)) {
727 /* if the string isn't valid, we parse as much as we can from it */
728 BarryLogf(TRACE_INTERNAL, "invalid utf8 passed to b_VFormat. Limping along.");
729 *end = '\0';
732 buf = _fold_lines (buf);
734 p = buf;
736 attr = _read_attribute (&p);
737 if (!attr)
738 attr = _read_attribute (&p);
740 if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "begin")) {
741 BarryLogf(TRACE_INTERNAL, "vformat began without a BEGIN\n");
743 if (attr && !g_ascii_strcasecmp (attr->name, "begin"))
744 b_vformat_attribute_free (attr);
745 else if (attr)
746 b_vformat_add_attribute (evc, attr);
748 char *block = NULL;
749 while (*p) {
750 b_VFormatAttribute *next_attr = _read_attribute (&p);
752 if (next_attr) {
753 if( g_ascii_strcasecmp(next_attr->name, "begin") == 0 ) {
754 // add to block hierarchy string
755 char *value = b_vformat_attribute_get_value(next_attr);
756 open_block(&block, value);
757 //BarryLogf(TRACE_INTERNAL, "open block: %s", block);
758 g_free(value);
760 else if( g_ascii_strcasecmp(next_attr->name, "end") == 0 ) {
761 // close off the block
762 char *value = b_vformat_attribute_get_value(next_attr);
763 close_block(&block, value);
764 //BarryLogf(TRACE_INTERNAL, "close block: %s", block);
765 g_free(value);
768 // apply the block to the attr
769 next_attr->block = g_strdup(block);
771 // add!
772 b_vformat_add_attribute (evc, next_attr);
773 attr = next_attr;
777 if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "end")) {
778 BarryLogf(TRACE_INTERNAL, "vformat ended without END");
781 g_free (buf);
782 g_free (block);
785 char *b_vformat_escape_string (const char *s, b_VFormatType type)
787 GString *str;
788 const char *p;
790 str = g_string_new ("");
792 /* Escape a string as described in RFC2426, section 5 */
793 for (p = s; p && *p; p++) {
794 switch (*p) {
795 case '\n':
796 str = g_string_append (str, "\\n");
797 break;
798 case '\r':
799 if (*(p+1) == '\n')
800 p++;
801 str = g_string_append (str, "\\n");
802 break;
803 case ';':
804 str = g_string_append (str, "\\;");
805 break;
806 case ',':
807 if (type == VFORMAT_CARD_30 || type == VFORMAT_EVENT_20 || type == VFORMAT_TODO_20)
808 str = g_string_append (str, "\\,");
809 else
810 str = g_string_append_c (str, *p);
811 break;
812 case '\\':
814 * We won't escape backslashes
815 * on vcard 2.1, unless it is in the end of a value.
816 * See comments above for a better explanation
818 if (*p != '\0' && type == VFORMAT_CARD_21) {
819 BarryLogf(TRACE_INTERNAL, "[%s]We won't escape backslashes", __func__);
820 str = g_string_append_c(str, *p);
822 else {
823 BarryLogf(TRACE_INTERNAL, "[%s] escape backslashes!!", __func__);
824 str = g_string_append (str, "\\\\");
826 break;
827 default:
828 str = g_string_append_c (str, *p);
829 break;
833 return g_string_free (str, FALSE);
836 char*
837 b_vformat_unescape_string (const char *s)
839 GString *str;
840 const char *p;
842 g_return_val_if_fail (s != NULL, NULL);
844 str = g_string_new ("");
846 /* Unescape a string as described in RFC2426, section 4 (Formal Grammar) */
847 for (p = s; *p; p++) {
848 if (*p == '\\') {
849 p++;
850 if (*p == '\0') {
851 str = g_string_append_c (str, '\\');
852 break;
854 switch (*p) {
855 case 'n': str = g_string_append_c (str, '\n'); break;
856 case 'r': str = g_string_append_c (str, '\r'); break;
857 case ';': str = g_string_append_c (str, ';'); break;
858 case ',': str = g_string_append_c (str, ','); break;
859 case '\\': str = g_string_append_c (str, '\\'); break;
860 case '"': str = g_string_append_c (str, '"'); break;
861 /* \t is (incorrectly) used by kOrganizer, so handle it here */
862 case 't': str = g_string_append_c (str, '\t'); break;
863 default:
864 BarryLogf(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %u", (unsigned int)*p);
865 str = g_string_append_c (str, '\\');
866 str = g_string_append_unichar (str, g_utf8_get_char(p));
867 break;
872 return g_string_free (str, FALSE);
875 void
876 b_vformat_construct (b_VFormat *evc, const char *str)
878 g_return_if_fail (str != NULL);
880 if (*str)
881 _parse (evc, str);
884 void b_vformat_free(b_VFormat *format)
886 g_list_foreach (format->attributes, (GFunc)b_vformat_attribute_free, NULL);
887 g_list_free (format->attributes);
888 g_free(format);
891 b_VFormat *b_vformat_new_from_string (const char *str)
893 g_return_val_if_fail (str != NULL, NULL);
894 b_VFormat *evc = g_malloc0(sizeof(b_VFormat));
896 b_vformat_construct (evc, str);
898 return evc;
901 b_VFormat *b_vformat_new(void)
903 return b_vformat_new_from_string ("");
906 static int _block_match(b_VFormatAttribute *attr, const char *block)
908 // a block matches if the end of the attribute's block
909 // string matches a case insensitive compare with block
911 // for example, a calendar may or may not start with a
912 // BEGIN: VCALENDAR, so DTSTART's block string could be
913 // "/vcalendar/vevent" or just "/vevent". By passing
914 // "/vevent" or even "vevent" as the block argument above,
915 // we should get a match for any of the above.
917 int attr_len = attr->block ? strlen(attr->block) : 0;
918 int block_len = block ? strlen(block) : 0;
920 if( block == NULL )
921 return 1; // if block is null, match everything
923 if( attr_len < block_len )
924 return 0; // not enough string to compare
926 if( attr_len == 0 && block_len == 0 )
927 return 1; // empty and null strings match
929 if( attr->block == NULL )
930 return 0; // don't compare if one side is null
932 return g_ascii_strcasecmp(&attr->block[attr_len - block_len], block) == 0;
935 b_VFormatAttribute *b_vformat_find_attribute(b_VFormat *vcard, const char *name, int nth, const char *block)
937 GList *attributes = b_vformat_get_attributes(vcard);
938 GList *a = NULL;
939 int i = 0;
940 for (a = attributes; a; a = a->next) {
941 b_VFormatAttribute *attr = a->data;
942 if (!g_ascii_strcasecmp(b_vformat_attribute_get_name(attr), name)) {
943 if( block == NULL || _block_match(attr, block) ) {
944 if( i == nth )
945 return attr;
946 i++;
950 return NULL;
954 b_VFormatAttribute *b_vformat_find_attribute_next(b_VFormatAttribute *last,
955 const char *name,
956 int nth)
958 GList *attributes = last ? last->next : 0;
959 GList *a = NULL;
960 int i = 0;
961 for (a = attributes; a; a = a->next) {
962 b_VFormatAttribute *attr = a->data;
963 if (!g_ascii_strcasecmp(b_vformat_attribute_get_name(attr), name)) {
964 if( i == nth )
965 return attr;
966 i++;
969 return NULL;
973 char *b_vformat_to_string (b_VFormat *evc, b_VFormatType type)
975 BarryLogf(TRACE_ENTRY, "%s(%p, %i)", __func__, evc, type);
976 GList *l;
977 GList *v;
979 GString *str = g_string_new ("");
981 switch (type) {
982 case VFORMAT_CARD_21:
983 str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:2.1\r\n");
984 break;
985 case VFORMAT_CARD_30:
986 str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:3.0\r\n");
987 break;
988 case VFORMAT_TODO_10:
989 case VFORMAT_EVENT_10:
990 str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:1.0\r\n");
991 break;
992 case VFORMAT_TODO_20:
993 case VFORMAT_EVENT_20:
994 case VFORMAT_JOURNAL:
995 str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n");
996 break;
997 case VFORMAT_NOTE:
998 str = g_string_append (str, "BEGIN:VNOTE\r\nVERSION:1.1\r\n");
999 break;
1002 for (l = evc->attributes; l; l = l->next) {
1003 GList *p;
1004 b_VFormatAttribute *attr = l->data;
1005 GString *attr_str;
1006 int l;
1007 int format_encoding = VF_ENCODING_RAW;
1009 attr_str = g_string_new ("");
1011 /* From rfc2425, 5.8.2
1013 * contentline = [group "."] name *(";" param) ":" value CRLF
1016 if (attr->group) {
1017 attr_str = g_string_append (attr_str, attr->group);
1018 attr_str = g_string_append_c (attr_str, '.');
1020 attr_str = g_string_append (attr_str, attr->name);
1021 /* handle the parameters */
1022 for (p = attr->params; p; p = p->next) {
1023 b_VFormatParam *param = p->data;
1024 /* 5.8.2:
1025 * param = param-name "=" param-value *("," param-value)
1027 if( type == VFORMAT_CARD_30 || type == VFORMAT_TODO_20
1028 || type == VFORMAT_EVENT_20 || type == VFORMAT_JOURNAL) {
1031 * Character set can only be specified on the CHARSET
1032 * parameter on the Content-Type MIME header field.
1034 if (!g_ascii_strcasecmp (param->name, "CHARSET"))
1035 continue;
1036 attr_str = g_string_append_c (attr_str, ';');
1037 attr_str = g_string_append (attr_str, param->name);
1038 if (param->values) {
1039 attr_str = g_string_append_c (attr_str, '=');
1041 for (v = param->values; v; v = v->next) {
1042 if (_helper_is_base64((const char *) v->data)) {
1043 format_encoding = VF_ENCODING_BASE64;
1044 /*Only the "B" encoding of [RFC 2047] is an allowed*/
1045 v->data=g_strdup("B");
1048 * QUOTED-PRINTABLE inline encoding has been
1049 * eliminated.
1051 if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE")) {
1052 BarryLogf(TRACE_ERROR, "%s false encoding QUOTED-PRINTABLE is not allowed", __func__);
1053 format_encoding = VF_ENCODING_QP;
1055 attr_str = g_string_append (attr_str, v->data);
1057 if (v->next)
1058 attr_str = g_string_append_c (attr_str, ',');
1061 else {
1062 attr_str = g_string_append_c (attr_str, ';');
1064 * The "TYPE=" is optional skip it.
1065 * LOGO, PHOTO and SOUND multimedia formats MUST
1066 * have a "TYPE=" parameter
1068 gboolean must_have_type = FALSE;
1069 if (!g_ascii_strcasecmp (attr->name, "PHOTO") || !g_ascii_strcasecmp (attr->name, "LOGO") || !g_ascii_strcasecmp (attr->name, "SOUND") )
1070 must_have_type = TRUE;
1071 if ( must_have_type || g_ascii_strcasecmp (param->name, "TYPE") )
1072 attr_str = g_string_append (attr_str, param->name);
1073 if ( param->values && (must_have_type || g_ascii_strcasecmp (param->name, "TYPE")) )
1074 attr_str = g_string_append_c (attr_str, '=');
1075 for (v = param->values; v; v = v->next) {
1076 // check for quoted-printable encoding
1077 if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE"))
1078 format_encoding = VF_ENCODING_QP;
1079 // check for base64 encoding
1080 if (_helper_is_base64((const char *) v->data)) {
1081 format_encoding = VF_ENCODING_BASE64;
1082 v->data=g_strdup("BASE64");
1084 attr_str = g_string_append (attr_str, v->data);
1085 if (v->next)
1086 attr_str = g_string_append_c (attr_str, ',');
1091 attr_str = g_string_append_c (attr_str, ':');
1093 for (v = attr->values; v; v = v->next) {
1094 char *value = v->data;
1095 char *escaped_value = NULL;
1097 if (!g_ascii_strcasecmp (attr->name, "RRULE") &&
1098 strstr (value, "BYDAY") == v->data) {
1099 attr_str = g_string_append (attr_str, value);
1100 } else {
1101 escaped_value = b_vformat_escape_string (value, type);
1102 attr_str = g_string_append (attr_str, escaped_value);
1105 if (v->next) {
1107 /* XXX toshok - i hate you, rfc 2426.
1108 why doesn't CATEGORIES use a ; like
1109 a normal list attribute? */
1110 if (!g_ascii_strcasecmp (attr->name, "CATEGORIES"))
1111 attr_str = g_string_append_c (attr_str, ',');
1112 else
1113 attr_str = g_string_append_c (attr_str, ';');
1116 g_free (escaped_value);
1119 /* Folding lines:
1120 * ^^^^^^^^^^^^^^
1122 * rfc 2426 (vCard), 2.6 Line Delimiting and Folding:
1123 * After generating a content line,
1124 * lines longer than 75 characters SHOULD be folded according to the
1125 * folding procedure described in [MIME-DIR].
1127 * rfc 2445 (iCalendar), 4.1 Content Lines:
1128 * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
1129 * break. Long content lines SHOULD be split into a multiple line
1130 * representations using a line "folding" technique. That is, a long
1131 * line can be split between any two characters by inserting a CRLF
1132 * immediately followed by a single linear white space character (i.e.,
1133 * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
1134 * of CRLF followed immediately by a single linear white space character
1135 * is ignored (i.e., removed) when processing the content type.
1137 * SUMMARY: When generating a content line, lines longer then 75 characters SHOULD be folded!
1138 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1140 * Differences between encodings:
1141 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1143 * rfc 2425 [MIME-DIR], 5.8.1:
1144 * A logical line MAY be continued on the next physical line anywhere
1145 * between two characters by inserting a CRLF immediately followed by a
1146 * single <WS> (white space) character.
1148 * rfc 2045, 6.7, chapter 5:
1149 * The quoted-printable specs says that softbreaks should be generated by inserting a =\r\n
1150 * without follwing <WS>
1152 * UTF-8
1153 * ^^^^^
1155 * Note that all the line folding above is described in terms of characters
1156 * not bytes. In particular, it would be an error to put a line break
1157 * within a UTF-8 character.
1160 l = 0;
1161 do {
1162 if (g_utf8_strlen(attr_str->str, attr_str->len) - l > 75) {
1163 l += 75;
1165 /* If using QP, must be sure that we do not fold within a quote sequence */
1166 if (format_encoding == VF_ENCODING_QP) {
1167 if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-1)) == '=') l--;
1168 else if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-2)) == '=') l -= 2;
1171 char *p = g_utf8_offset_to_pointer(attr_str->str, l);
1173 if (format_encoding == VF_ENCODING_QP)
1174 attr_str = g_string_insert_len (attr_str, p - attr_str->str, "=" CRLF "", sizeof ("=" CRLF "") - 1);
1175 else
1176 attr_str = g_string_insert_len (attr_str, p - attr_str->str, CRLF " ", sizeof (CRLF " ") - 1);
1178 else
1179 break;
1180 } while (l < g_utf8_strlen(attr_str->str, attr_str->len));
1182 attr_str = g_string_append (attr_str, CRLF);
1184 * base64= <MIME RFC 1521 base64 text>
1185 * the end of the text is marked with two CRLF sequences
1186 * this results in one blank line before the start of the
1187 * next property
1189 if( format_encoding == VF_ENCODING_BASE64
1190 && (type == VFORMAT_CARD_21))
1191 attr_str = g_string_append (attr_str, CRLF);
1193 str = g_string_append (str, attr_str->str);
1194 g_string_free (attr_str, TRUE);
1197 switch (type) {
1198 case VFORMAT_CARD_21:
1199 str = g_string_append (str, "END:VCARD\r\n");
1200 break;
1201 case VFORMAT_CARD_30:
1202 str = g_string_append (str, "END:VCARD\r\n");
1203 break;
1204 case VFORMAT_TODO_10:
1205 case VFORMAT_EVENT_10:
1206 str = g_string_append (str, "END:VCALENDAR\r\n");
1207 break;
1208 case VFORMAT_TODO_20:
1209 case VFORMAT_EVENT_20:
1210 case VFORMAT_JOURNAL:
1211 str = g_string_append (str, "END:VCALENDAR\r\n");
1212 break;
1213 case VFORMAT_NOTE:
1214 str = g_string_append (str, "END:VNOTE\r\n");
1215 break;
1218 BarryLogf(TRACE_EXIT, "%s", __func__);
1219 return g_string_free (str, FALSE);
1222 void b_vformat_dump_structure (b_VFormat *evc)
1224 GList *a;
1225 GList *v;
1226 int i;
1228 printf ("b_VFormat\n");
1229 for (a = evc->attributes; a; a = a->next) {
1230 GList *p;
1231 b_VFormatAttribute *attr = a->data;
1232 printf ("+-- %s\n", attr->name);
1233 if (attr->params) {
1234 printf (" +- params=\n");
1236 for (p = attr->params, i = 0; p; p = p->next, i++) {
1237 b_VFormatParam *param = p->data;
1238 printf (" | [%d] = %s", i,param->name);
1239 printf ("(");
1240 for (v = param->values; v; v = v->next) {
1241 char *value = b_vformat_escape_string ((char*)v->data, VFORMAT_CARD_21);
1242 printf ("%s", value);
1243 if (v->next)
1244 printf (",");
1245 g_free (value);
1248 printf (")\n");
1251 printf (" +- values=\n");
1252 for (v = attr->values, i = 0; v; v = v->next, i++) {
1253 printf (" [%d] = `%s'\n", i, (char*)v->data);
1258 b_VFormatAttribute *b_vformat_attribute_new (const char *attr_group, const char *attr_name)
1260 b_VFormatAttribute *attr;
1262 attr = g_new0 (b_VFormatAttribute, 1);
1264 attr->group = g_strdup (attr_group);
1265 attr->name = g_strdup (attr_name);
1267 return attr;
1270 void
1271 b_vformat_attribute_free (b_VFormatAttribute *attr)
1273 g_return_if_fail (attr != NULL);
1275 g_free (attr->block);
1276 g_free (attr->group);
1277 g_free (attr->name);
1279 b_vformat_attribute_remove_values (attr);
1281 b_vformat_attribute_remove_params (attr);
1283 g_free (attr);
1286 b_VFormatAttribute*
1287 b_vformat_attribute_copy (b_VFormatAttribute *attr)
1289 b_VFormatAttribute *a;
1290 GList *p;
1292 g_return_val_if_fail (attr != NULL, NULL);
1294 a = b_vformat_attribute_new (b_vformat_attribute_get_group (attr),
1295 b_vformat_attribute_get_name (attr));
1297 for (p = attr->values; p; p = p->next)
1298 b_vformat_attribute_add_value (a, p->data);
1300 for (p = attr->params; p; p = p->next)
1301 b_vformat_attribute_add_param (a, b_vformat_attribute_param_copy (p->data));
1303 return a;
1306 void
1307 b_vformat_remove_attributes (b_VFormat *evc, const char *attr_group, const char *attr_name)
1309 GList *attr;
1311 g_return_if_fail (attr_name != NULL);
1313 attr = evc->attributes;
1314 while (attr) {
1315 GList *next_attr;
1316 b_VFormatAttribute *a = attr->data;
1318 next_attr = attr->next;
1320 if (((!attr_group && !a->group) ||
1321 (attr_group && !g_ascii_strcasecmp (attr_group, a->group))) &&
1322 ((!attr_name && !a->name) || !g_ascii_strcasecmp (attr_name, a->name))) {
1324 /* matches, remove/delete the attribute */
1325 evc->attributes = g_list_remove_link (evc->attributes, attr);
1327 b_vformat_attribute_free (a);
1330 attr = next_attr;
1334 void
1335 b_vformat_remove_attribute (b_VFormat *evc, b_VFormatAttribute *attr)
1337 g_return_if_fail (attr != NULL);
1339 evc->attributes = g_list_remove (evc->attributes, attr);
1340 b_vformat_attribute_free (attr);
1343 void
1344 b_vformat_add_attribute (b_VFormat *evc, b_VFormatAttribute *attr)
1346 g_return_if_fail (attr != NULL);
1348 evc->attributes = g_list_append (evc->attributes, attr);
1351 void
1352 b_vformat_add_attribute_with_value (b_VFormat *b_VFormat,
1353 b_VFormatAttribute *attr, const char *value)
1355 g_return_if_fail (attr != NULL);
1357 b_vformat_attribute_add_value (attr, value);
1359 b_vformat_add_attribute (b_VFormat, attr);
1362 void
1363 b_vformat_add_attribute_with_values (b_VFormat *b_VFormat, b_VFormatAttribute *attr, ...)
1365 va_list ap;
1366 char *v;
1368 g_return_if_fail (attr != NULL);
1370 va_start (ap, attr);
1372 while ((v = va_arg (ap, char*))) {
1373 b_vformat_attribute_add_value (attr, v);
1376 va_end (ap);
1378 b_vformat_add_attribute (b_VFormat, attr);
1381 void
1382 b_vformat_attribute_add_value (b_VFormatAttribute *attr, const char *value)
1384 g_return_if_fail (attr != NULL);
1386 attr->values = g_list_append (attr->values, g_strdup (value));
1389 void
1390 b_vformat_attribute_add_value_decoded (b_VFormatAttribute *attr, const char *value, int len)
1392 g_return_if_fail (attr != NULL);
1394 switch (attr->encoding) {
1395 case VF_ENCODING_RAW:
1396 BarryLogf(TRACE_INTERNAL, "can't add_value_decoded with an attribute using RAW encoding. you must set the ENCODING parameter first");
1397 break;
1398 case VF_ENCODING_BASE64: {
1399 char *b64_data = base64_encode_simple (value, len);
1400 GString *decoded = g_string_new_len (value, len);
1402 /* make sure the decoded list is up to date */
1403 b_vformat_attribute_get_values_decoded (attr);
1405 attr->values = g_list_append (attr->values, b64_data);
1406 attr->decoded_values = g_list_append (attr->decoded_values, decoded);
1407 break;
1409 case VF_ENCODING_QP: {
1410 char *qp_data = quoted_encode_simple ((unsigned char*)value, len);
1411 GString *decoded = g_string_new (value);
1413 /* make sure the decoded list is up to date */
1414 b_vformat_attribute_get_values_decoded (attr);
1416 attr->values = g_list_append (attr->values, qp_data);
1417 attr->decoded_values = g_list_append (attr->decoded_values, decoded);
1418 break;
1420 case VF_ENCODING_8BIT: {
1421 char *data = g_strdup(value);
1422 GString *decoded = g_string_new (value);
1424 /* make sure the decoded list is up to date */
1425 b_vformat_attribute_get_values_decoded (attr);
1427 attr->values = g_list_append (attr->values, data);
1428 attr->decoded_values = g_list_append (attr->decoded_values, decoded);
1429 break;
1434 void
1435 b_vformat_attribute_add_values (b_VFormatAttribute *attr, ...)
1437 va_list ap;
1438 char *v;
1440 g_return_if_fail (attr != NULL);
1442 va_start (ap, attr);
1444 while ((v = va_arg (ap, char*))) {
1445 b_vformat_attribute_add_value (attr, v);
1448 va_end (ap);
1451 static void
1452 free_gstring (GString *str)
1454 g_string_free (str, TRUE);
1457 void
1458 b_vformat_attribute_remove_values (b_VFormatAttribute *attr)
1460 g_return_if_fail (attr != NULL);
1462 g_list_foreach (attr->values, (GFunc)g_free, NULL);
1463 g_list_free (attr->values);
1464 attr->values = NULL;
1466 g_list_foreach (attr->decoded_values, (GFunc)free_gstring, NULL);
1467 g_list_free (attr->decoded_values);
1468 attr->decoded_values = NULL;
1471 void
1472 b_vformat_attribute_remove_params (b_VFormatAttribute *attr)
1474 g_return_if_fail (attr != NULL);
1476 g_list_foreach (attr->params, (GFunc)b_vformat_attribute_param_free, NULL);
1477 g_list_free (attr->params);
1478 attr->params = NULL;
1480 /* also remove the cached encoding on this attribute */
1481 attr->encoding_set = FALSE;
1482 attr->encoding = VF_ENCODING_RAW;
1485 b_VFormatParam*
1486 b_vformat_attribute_param_new (const char *name)
1488 b_VFormatParam *param = g_new0 (b_VFormatParam, 1);
1489 param->name = g_strdup (name);
1491 return param;
1494 void
1495 b_vformat_attribute_param_free (b_VFormatParam *param)
1497 g_return_if_fail (param != NULL);
1499 g_free (param->name);
1501 b_vformat_attribute_param_remove_values (param);
1503 g_free (param);
1506 b_VFormatParam*
1507 b_vformat_attribute_param_copy (b_VFormatParam *param)
1509 b_VFormatParam *p;
1510 GList *l;
1512 g_return_val_if_fail (param != NULL, NULL);
1514 p = b_vformat_attribute_param_new (b_vformat_attribute_param_get_name (param));
1516 for (l = param->values; l; l = l->next) {
1517 b_vformat_attribute_param_add_value (p, l->data);
1520 return p;
1523 void
1524 b_vformat_attribute_add_param (b_VFormatAttribute *attr,
1525 b_VFormatParam *param)
1527 g_return_if_fail (attr != NULL);
1528 g_return_if_fail (param != NULL);
1530 attr->params = g_list_append (attr->params, param);
1532 /* we handle our special encoding stuff here */
1534 if (!g_ascii_strcasecmp (param->name, "ENCODING")) {
1535 if (attr->encoding_set) {
1536 BarryLogf(TRACE_INTERNAL, "ENCODING specified twice");
1537 return;
1540 if (param->values && param->values->data) {
1541 if (_helper_is_base64((const char*)param->values->data))
1542 attr->encoding = VF_ENCODING_BASE64;
1543 else if (!g_ascii_strcasecmp ((char*)param->values->data, "QUOTED-PRINTABLE"))
1544 attr->encoding = VF_ENCODING_QP;
1545 else if (!g_ascii_strcasecmp ((char *)param->values->data, "8BIT"))
1546 attr->encoding = VF_ENCODING_8BIT;
1547 else {
1548 BarryLogf(TRACE_INTERNAL, "Unknown value `%s' for ENCODING parameter. values will be treated as raw", (char*)param->values->data);
1551 attr->encoding_set = TRUE;
1553 else {
1554 BarryLogf(TRACE_INTERNAL, "ENCODING parameter added with no value");
1559 b_VFormatParam *b_vformat_attribute_find_param(b_VFormatAttribute *attr, const char *name, int level)
1561 g_return_val_if_fail (attr != NULL, NULL);
1562 GList *p = NULL;
1563 for (p = attr->params; p; p = p->next) {
1564 b_VFormatParam *param = p->data;
1565 if (!g_ascii_strcasecmp (param->name, name)) {
1566 if( level == 0 )
1567 return param;
1568 else
1569 level--;
1572 return NULL;
1575 void
1576 b_vformat_attribute_set_value (b_VFormatAttribute *attr,
1577 int nth, const char *value)
1579 GList *param = g_list_nth(attr->values, nth);
1580 g_free(param->data);
1581 param->data = g_strdup(value);
1584 void
1585 b_vformat_attribute_param_add_value (b_VFormatParam *param,
1586 const char *value)
1588 g_return_if_fail (param != NULL);
1590 param->values = g_list_append (param->values, g_strdup (value));
1593 void
1594 b_vformat_attribute_param_add_values (b_VFormatParam *param,
1595 ...)
1597 va_list ap;
1598 char *v;
1600 g_return_if_fail (param != NULL);
1602 va_start (ap, param);
1604 while ((v = va_arg (ap, char*))) {
1605 b_vformat_attribute_param_add_value (param, v);
1608 va_end (ap);
1611 void
1612 b_vformat_attribute_add_param_with_value (b_VFormatAttribute *attr, const char *name, const char *value)
1614 g_return_if_fail (attr != NULL);
1615 g_return_if_fail (name != NULL);
1617 if (!value)
1618 return;
1620 b_VFormatParam *param = b_vformat_attribute_param_new(name);
1622 b_vformat_attribute_param_add_value (param, value);
1624 b_vformat_attribute_add_param (attr, param);
1627 void
1628 b_vformat_attribute_add_param_with_values (b_VFormatAttribute *attr,
1629 b_VFormatParam *param, ...)
1631 va_list ap;
1632 char *v;
1634 g_return_if_fail (attr != NULL);
1635 g_return_if_fail (param != NULL);
1637 va_start (ap, param);
1639 while ((v = va_arg (ap, char*))) {
1640 b_vformat_attribute_param_add_value (param, v);
1643 va_end (ap);
1645 b_vformat_attribute_add_param (attr, param);
1648 void
1649 b_vformat_attribute_param_remove_values (b_VFormatParam *param)
1651 g_return_if_fail (param != NULL);
1653 g_list_foreach (param->values, (GFunc)g_free, NULL);
1654 g_list_free (param->values);
1655 param->values = NULL;
1658 GList*
1659 b_vformat_get_attributes (b_VFormat *format)
1661 return format->attributes;
1664 const char*
1665 b_vformat_attribute_get_group (b_VFormatAttribute *attr)
1667 g_return_val_if_fail (attr != NULL, NULL);
1669 return attr->group;
1672 const char*
1673 b_vformat_attribute_get_name (b_VFormatAttribute *attr)
1675 g_return_val_if_fail (attr != NULL, NULL);
1677 return attr->name;
1680 const char*
1681 b_vformat_attribute_get_block (b_VFormatAttribute *attr)
1683 g_return_val_if_fail (attr != NULL, NULL);
1685 return attr->block;
1688 GList*
1689 b_vformat_attribute_get_values (b_VFormatAttribute *attr)
1691 g_return_val_if_fail (attr != NULL, NULL);
1693 return attr->values;
1696 GList*
1697 b_vformat_attribute_get_values_decoded (b_VFormatAttribute *attr)
1699 g_return_val_if_fail (attr != NULL, NULL);
1701 if (!attr->decoded_values) {
1702 GList *l;
1703 switch (attr->encoding) {
1704 case VF_ENCODING_RAW:
1705 case VF_ENCODING_8BIT:
1706 for (l = attr->values; l; l = l->next)
1707 attr->decoded_values = g_list_append (attr->decoded_values, g_string_new ((char*)l->data));
1708 break;
1709 case VF_ENCODING_BASE64:
1710 for (l = attr->values; l; l = l->next) {
1711 char *decoded = g_strdup ((char*)l->data);
1712 int len = base64_decode_simple (decoded, strlen (decoded));
1713 attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len));
1714 g_free (decoded);
1716 break;
1717 case VF_ENCODING_QP:
1718 for (l = attr->values; l; l = l->next) {
1719 if (!(l->data))
1720 continue;
1721 char *decoded = g_strdup ((char*)l->data);
1722 int len = quoted_decode_simple (decoded, strlen (decoded));
1723 attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len));
1724 g_free (decoded);
1726 break;
1730 return attr->decoded_values;
1733 gboolean
1734 b_vformat_attribute_is_single_valued (b_VFormatAttribute *attr)
1736 g_return_val_if_fail (attr != NULL, FALSE);
1738 if (attr->values == NULL
1739 || attr->values->next != NULL)
1740 return FALSE;
1742 return TRUE;
1745 char*
1746 b_vformat_attribute_get_value (b_VFormatAttribute *attr)
1748 GList *values;
1750 g_return_val_if_fail (attr != NULL, NULL);
1752 values = b_vformat_attribute_get_values (attr);
1754 if (!b_vformat_attribute_is_single_valued (attr))
1755 BarryLogf(TRACE_INTERNAL, "b_vformat_attribute_get_value called on multivalued attribute");
1757 return values ? g_strdup ((char*)values->data) : NULL;
1760 GString*
1761 b_vformat_attribute_get_value_decoded (b_VFormatAttribute *attr)
1763 GList *values;
1764 GString *str = NULL;
1766 g_return_val_if_fail (attr != NULL, NULL);
1768 values = b_vformat_attribute_get_values_decoded (attr);
1770 if (!b_vformat_attribute_is_single_valued (attr))
1771 BarryLogf(TRACE_INTERNAL, "b_vformat_attribute_get_value_decoded called on multivalued attribute");
1773 if (values)
1774 str = values->data;
1776 return str ? g_string_new_len (str->str, str->len) : NULL;
1779 const char *b_vformat_attribute_get_nth_value(b_VFormatAttribute *attr, int nth)
1781 GList *values = b_vformat_attribute_get_values_decoded(attr);
1782 if (!values)
1783 return NULL;
1784 GString *retstr = (GString *)g_list_nth_data(values, nth);
1785 if (!retstr)
1786 return NULL;
1788 if (!g_utf8_validate(retstr->str, -1, NULL)) {
1789 values = b_vformat_attribute_get_values(attr);
1790 if (!values)
1791 return NULL;
1792 return g_list_nth_data(values, nth);
1795 return retstr->str;
1798 gboolean
1799 b_vformat_attribute_has_type (b_VFormatAttribute *attr, const char *typestr)
1801 GList *params;
1802 GList *p;
1804 g_return_val_if_fail (attr != NULL, FALSE);
1805 g_return_val_if_fail (typestr != NULL, FALSE);
1807 params = b_vformat_attribute_get_params (attr);
1809 for (p = params; p; p = p->next) {
1810 b_VFormatParam *param = p->data;
1812 if (!strcasecmp (b_vformat_attribute_param_get_name (param), "TYPE")) {
1813 GList *values = b_vformat_attribute_param_get_values (param);
1814 GList *v;
1816 for (v = values; v; v = v->next) {
1817 if (!strcasecmp ((char*)v->data, typestr))
1818 return TRUE;
1823 return FALSE;
1827 gboolean b_vformat_attribute_has_param(b_VFormatAttribute *attr, const char *name)
1829 g_return_val_if_fail (attr != NULL, FALSE);
1830 g_return_val_if_fail (name != NULL, FALSE);
1832 GList *params = b_vformat_attribute_get_params(attr);
1833 GList *p;
1834 for (p = params; p; p = p->next) {
1835 b_VFormatParam *param = p->data;
1836 if (!strcasecmp(name, b_vformat_attribute_param_get_name(param)))
1837 return TRUE;
1839 return FALSE;
1842 GList*
1843 b_vformat_attribute_get_params (b_VFormatAttribute *attr)
1845 g_return_val_if_fail (attr != NULL, NULL);
1847 return attr->params;
1850 const char*
1851 b_vformat_attribute_param_get_name (b_VFormatParam *param)
1853 g_return_val_if_fail (param != NULL, NULL);
1855 return param->name;
1858 GList*
1859 b_vformat_attribute_param_get_values (b_VFormatParam *param)
1861 g_return_val_if_fail (param != NULL, NULL);
1863 return param->values;
1866 const char *b_vformat_attribute_param_get_nth_value(b_VFormatParam *param, int nth)
1868 const char *ret = NULL;
1869 GList *values = b_vformat_attribute_param_get_values(param);
1870 if (!values)
1871 return NULL;
1872 ret = g_list_nth_data(values, nth);
1873 return ret;
1876 static const char *base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1878 //static unsigned char _evc_base64_rank[256];
1880 static void base64_init(char *rank)
1882 int i;
1884 memset(rank, 0xff, sizeof(rank));
1885 for (i=0;i<64;i++) {
1886 rank[(unsigned int)base64_alphabet[i]] = i;
1888 rank['='] = 0;
1891 /* call this when finished encoding everything, to
1892 flush off the last little bit */
1893 static size_t base64_encode_close(const unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
1895 int c1, c2;
1896 unsigned char *outptr = out;
1898 if (inlen>0)
1899 outptr += base64_encode_step(in, inlen, break_lines, outptr, state, save);
1901 c1 = ((unsigned char *)save)[1];
1902 c2 = ((unsigned char *)save)[2];
1904 switch (((char *)save)[0]) {
1905 case 2:
1906 outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
1907 g_assert(outptr[2] != 0);
1908 goto skip;
1909 case 1:
1910 outptr[2] = '=';
1911 skip:
1912 outptr[0] = base64_alphabet[ c1 >> 2 ];
1913 outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
1914 outptr[3] = '=';
1915 outptr += 4;
1916 break;
1918 if (break_lines)
1919 *outptr++ = '\n';
1921 *save = 0;
1922 *state = 0;
1924 return outptr-out;
1928 performs an 'encode step', only encodes blocks of 3 characters to the
1929 output at a time, saves left-over state in state and save (initialise to
1930 0 on first invocation).
1932 static size_t base64_encode_step(const unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
1934 register const unsigned char *inptr;
1935 register unsigned char *outptr;
1937 if (len<=0)
1938 return 0;
1940 inptr = in;
1941 outptr = out;
1943 if (len + ((char *)save)[0] > 2) {
1944 const unsigned char *inend = in+len-2;
1945 register int c1, c2, c3;
1946 register int already;
1948 already = *state;
1950 switch (((char *)save)[0]) {
1951 case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
1952 case 2: c1 = ((unsigned char *)save)[1];
1953 c2 = ((unsigned char *)save)[2]; goto skip2;
1956 /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
1957 while (inptr < inend) {
1958 c1 = *inptr++;
1959 skip1:
1960 c2 = *inptr++;
1961 skip2:
1962 c3 = *inptr++;
1963 *outptr++ = base64_alphabet[ c1 >> 2 ];
1964 *outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
1965 *outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
1966 *outptr++ = base64_alphabet[ c3 & 0x3f ];
1967 /* this is a bit ugly ... */
1968 if (break_lines && (++already)>=19) {
1969 *outptr++='\n';
1970 already = 0;
1974 ((char *)save)[0] = 0;
1975 len = 2-(inptr-inend);
1976 *state = already;
1979 if (len>0) {
1980 register char *saveout;
1982 /* points to the slot for the next char to save */
1983 saveout = & (((char *)save)[1]) + ((char *)save)[0];
1985 /* len can only be 0 1 or 2 */
1986 switch(len) {
1987 case 2: *saveout++ = *inptr++;
1988 case 1: *saveout++ = *inptr++;
1990 ((char *)save)[0]+=len;
1993 return outptr-out;
1998 * base64_decode_step: decode a chunk of base64 encoded data
1999 * @in: input stream
2000 * @len: max length of data to decode
2001 * @out: output stream
2002 * @state: holds the number of bits that are stored in @save
2003 * @save: leftover bits that have not yet been decoded
2005 * Decodes a chunk of base64 encoded data
2007 static size_t base64_decode_step(const unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
2009 unsigned char base64_rank[256];
2010 base64_init((char*)base64_rank);
2012 register const unsigned char *inptr;
2013 register unsigned char *outptr;
2014 const unsigned char *inend;
2015 unsigned char c;
2016 register unsigned int v;
2017 int i;
2019 inend = in+len;
2020 outptr = out;
2022 /* convert 4 base64 bytes to 3 normal bytes */
2023 v=*save;
2024 i=*state;
2025 inptr = in;
2026 while (inptr<inend) {
2027 c = base64_rank[*inptr++];
2028 if (c != 0xff) {
2029 v = (v<<6) | c;
2030 i++;
2031 if (i==4) {
2032 *outptr++ = v>>16;
2033 *outptr++ = v>>8;
2034 *outptr++ = v;
2035 i=0;
2040 *save = v;
2041 *state = i;
2043 /* quick scan back for '=' on the end somewhere */
2044 /* fortunately we can drop 1 output char for each trailing = (upto 2) */
2045 i=2;
2046 while (inptr>in && i) {
2047 inptr--;
2048 if (base64_rank[*inptr] != 0xff) {
2049 if (*inptr == '=' && outptr>out)
2050 outptr--;
2051 i--;
2055 /* if i!= 0 then there is a truncation error! */
2056 return outptr-out;
2059 static char *base64_encode_simple (const char *data, size_t len)
2061 unsigned char *out;
2062 int state = 0, outlen;
2063 unsigned int save = 0;
2065 g_return_val_if_fail (data != NULL, NULL);
2067 out = g_malloc (len * 4 / 3 + 5);
2068 outlen = base64_encode_close ((unsigned char *)data, len, FALSE,
2069 out, &state, (int*)&save);
2070 out[outlen] = '\0';
2071 return (char *)out;
2074 static size_t base64_decode_simple (char *data, size_t len)
2076 int state = 0;
2077 unsigned int save = 0;
2079 g_return_val_if_fail (data != NULL, 0);
2081 return base64_decode_step ((unsigned char *)data, len,
2082 (unsigned char *)data, &state, &save);
2085 static char *quoted_encode_simple(const unsigned char *string, int len)
2087 GString *tmp = g_string_new("");
2089 int i = 0;
2090 while(string[i] != 0) {
2091 if (string[i] > 127 || string[i] == 13 || string[i] == 10 || string[i] == '=') {
2092 g_string_append_printf(tmp, "=%02X", string[i]);
2093 } else {
2094 g_string_append_c(tmp, string[i]);
2096 i++;
2099 char *ret = tmp->str;
2100 g_string_free(tmp, FALSE);
2101 return ret;
2105 static size_t quoted_decode_simple (char *data, size_t len)
2107 g_return_val_if_fail (data != NULL, 0);
2109 GString *string = g_string_new(data);
2110 if (!string)
2111 return 0;
2113 char hex[5];
2114 hex[4] = 0;
2116 while (1) {
2117 //Get the index of the next encoded char
2118 int i = strcspn(string->str, "=");
2119 if (i >= strlen(string->str))
2120 break;
2122 strcpy(hex, "0x");
2123 strncat(hex, &string->str[i + 1], 2);
2124 char rep = ((int)(strtod(hex, NULL)));
2125 g_string_erase(string, i, 2);
2126 g_string_insert_c(string, i, rep);
2129 memset(data, 0, strlen(data));
2130 strcpy(data, string->str);
2131 g_string_free(string, 1);
2133 return strlen(data);