/*
* Copyright (C) 2003 Ximian, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Chris Toshok (toshok@ximian.com)
* Author: Armin Bauer (armin.bauer@opensync.org)
*
*/
#include "vformat.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <iconv.h>
#include <opensync/opensync.h>
static size_t base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save);
static size_t base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save);
size_t base64_decode_simple (char *data, size_t len);
char *base64_encode_simple (const char *data, size_t len);
size_t quoted_decode_simple (char *data, size_t len);
char *quoted_encode_simple (const unsigned char *string, int len);
/**
* _helper_is_base64 is helper function to check i a string is "b" or "base64"
* @param check_string string that should be compared with "b" or "base64"
* @return 0 if check_string is not base64 and 1 if it is
*/
static int _helper_is_base64(const char *check_string)
{
if(!g_ascii_strcasecmp ((char *) check_string, "BASE64") ||
!g_ascii_strcasecmp ((char *) check_string, "b") )
return (1);
return (0);
}
time_t vformat_time_to_unix(const char *inptime)
{
char *date = NULL;
char *time = NULL;
char *ftime = NULL;
if ((ftime = g_strrstr(inptime, "T"))) {
date = g_strndup(inptime, ftime - inptime);
if (ftime[3] == ':')
time = g_strndup(ftime + 1, 8);
else
time = g_strndup(ftime + 1, 6);
} else {
date = g_strdup(inptime);
}
struct tm btime;
memset(&btime, 0, sizeof(struct tm));
if (strlen(date) == 10) {
btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111 - 1900;
btime.tm_mon = date[5] * 10 + date[6] - '0' * 11 - 1;
btime.tm_mday = date[8] * 10 + date[9] - '0' * 11;
} else {
btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111- 1900;
btime.tm_mon = date[4] * 10 + date[5] - '0' * 11 - 1;
btime.tm_mday = date[6] * 10 + date[7] - '0' * 11;
}
if (time && strlen(time) == 8) {
//Time
btime.tm_hour = time[0] * 10 + time[1] - '0' * 11;
btime.tm_min = time[3] * 10 + time[4] - '0' * 11;
btime.tm_sec = time[6] * 10 + time[7] - '0' * 11;
} else if (time && strlen(time) == 6) {
btime.tm_hour = time[0] * 10 + time[1] - '0' * 11;
btime.tm_min = time[2] * 10 + time[3] - '0' * 11;
btime.tm_sec = time[4] * 10 + time[5] - '0' * 11;
}
time_t utime = mktime(&btime);
return utime;
}
static char *_fold_lines (char *buf)
{
GString *str = g_string_new ("");
GString *line = g_string_new ("");
char *p = buf;
char *next, *next2, *q;
gboolean newline = TRUE;
gboolean quotedprintable = FALSE;
/*
* We're pretty liberal with line folding here. We handle
* lines folded with \r\n<WS>, \n\r<WS>, \n<WS>, =\r\n and =\n\r.
* We also turn single \r's and \n's not followed by <WS> into \r\n's.
*/
while (*p) {
/* search new lines for quoted printable encoding */
if (newline) {
for (q=p; *q != '\n' && *q != '\0'; q++)
line = g_string_append_unichar (line, g_utf8_get_char (q));
if (strstr(line->str, "ENCODING=QUOTED-PRINTABLE"))
quotedprintable = TRUE;
g_string_free(line, TRUE);
line = g_string_new ("");
newline = FALSE;
}
if ((quotedprintable && *p == '=') || *p == '\r' || *p == '\n') {
next = g_utf8_next_char (p);
if (*next == '\n' || *next == '\r') {
next2 = g_utf8_next_char (next);
if (*next2 == '\n' || *next2 == '\r' || *next2 == ' ' || *next2 == '\t') {
p = g_utf8_next_char (next2);
}
else {
str = g_string_append (str, CRLF);
p = g_utf8_next_char (next);
newline = TRUE;
quotedprintable = FALSE;
}
}
else if (*p == '=') {
str = g_string_append_unichar (str, g_utf8_get_char (p));
p = g_utf8_next_char (p);
}
else if (*next == ' ' || *next == '\t') {
p = g_utf8_next_char (next);
}
else {
str = g_string_append (str, CRLF);
p = g_utf8_next_char (p);
newline = TRUE;
quotedprintable = FALSE;
}
}
else {
str = g_string_append_unichar (str, g_utf8_get_char (p));
p = g_utf8_next_char (p);
}
}
g_free (buf);
g_string_free(line, TRUE);
return g_string_free (str, FALSE);
}
/* skip forward until we hit the CRLF, or \0 */
static void _skip_to_next_line (char **p)
{
char *lp;
lp = *p;
while (*lp != '\r' && *lp != '\0')
lp = g_utf8_next_char (lp);
if (*lp == '\r') {
lp = g_utf8_next_char (lp); /* \n */
lp = g_utf8_next_char (lp); /* start of the next line */
}
*p = lp;
}
/* skip forward until we hit a character in @s, CRLF, or \0. leave *p
pointing at the character that causes us to stop */
static void _skip_until (char **p, char *s)
{
char *lp;
lp = *p;
while (*lp != '\r' && *lp != '\0') {
gboolean s_matches = FALSE;
char *ls;
for (ls = s; *ls; ls = g_utf8_next_char (ls)) {
if (g_utf8_get_char (ls) == g_utf8_get_char (lp)) {
s_matches = TRUE;
break;
}
}
if (s_matches)
break;
lp++;
}
*p = lp;
}
static void _read_attribute_value_add (VFormatAttribute *attr, GString *str, GString *charset)
{
/* don't convert empty strings */
if (str->len == 0) {
vformat_attribute_add_value(attr, str->str);
return;
}
char *inbuf, *outbuf, *p;
size_t inbytesleft, outbytesleft;
inbuf = str->str;
p = outbuf = malloc(str->len*2);
inbytesleft = str->len;
outbytesleft = str->len*2;
iconv_t cd;
/* if a CHARSET was given, let's try to convert inbuf to UTF-8 */
if (charset) {
cd = iconv_open("UTF-8", charset->str);
#ifdef SOLARIS
if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#else
if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#endif
*p = 0;
vformat_attribute_add_value(attr, outbuf);
} else {
/* hmm, should not happen */
vformat_attribute_add_value(attr, str->str);
}
iconv_close(cd);
} else {
/* no CHARSET was given, if inbuf is already UTF-8 we add str->str */
if (g_utf8_validate (inbuf, -1, NULL)) {
vformat_attribute_add_value (attr, str->str);
} else {
/* because inbuf is not UTF-8, we think it is ISO-8859-1 */
cd = iconv_open("UTF-8", "ISO-8859-1");
#ifdef SOLARIS
if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#else
if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#endif
*p = 0;
vformat_attribute_add_value (attr, outbuf);
} else {
vformat_attribute_add_value (attr, str->str);
}
iconv_close(cd);
}
}
free(outbuf);
}
static void _read_attribute_value (VFormatAttribute *attr, char **p, int format_encoding, GString *charset)
{
char *lp = *p;
GString *str;
/* read in the value */
str = g_string_new ("");
while (*lp != '\r' && *lp != '\0') {
if (*lp == '=' && format_encoding == VF_ENCODING_QP) {
char a, b, x1=0, x2=0;
if ((a = *(++lp)) == '\0') break;
if ((b = *(++lp)) == '\0') break;
if (isalnum(a)) {
if (isalnum(b)) {
/* e.g. ...N=C3=BCrnberg\r\n
* ^^^
*/
x1=a;
x2=b;
}
else if (b == '=') {
/* e.g. ...N=C=\r\n
* ^^^
* 3=BCrnberg...
* ^
*/
char *tmplp = lp;
if (*(++tmplp) == '\r' && *(++tmplp) == '\n' && isalnum(*(++tmplp))) {
x1 = a;
x2 = *tmplp;
lp = tmplp;
}
}
else {
/* append malformed input, and
continue parsing */
str = g_string_append_c(str, a);
str = g_string_append_c(str, b);
}
}
else if (a == '=') {
char *tmplp = lp;
char c, d, e;
c = *(++tmplp);
d = *(++tmplp);
e = *(++tmplp);
if (b == '\r' && c == '\n' && isalnum(d) && isalnum(e)) {
x1 = d;
x2 = e;
lp = tmplp;
}
else {
/* append malformed input, and
continue parsing */
str = g_string_append_c(str, a);
str = g_string_append_c(str, b);
}
}
else {
/* append malformed input, and
continue parsing */
str = g_string_append_c(str, a);
str = g_string_append_c(str, b);
}
if (x1 && x2) {
char c;
a = tolower (x1);
b = tolower (x2);
c = (((a>='a'?a-'a'+10:a-'0')&0x0f) << 4)
| ((b>='a'?b-'a'+10:b-'0')&0x0f);
str = g_string_append_c (str, c);
}
lp++;
x1 = x2 = 0;
}
else if (format_encoding == VF_ENCODING_BASE64) {
if((*lp != ' ') && (*lp != '\t') )
str = g_string_append_unichar (str, g_utf8_get_char (lp));
lp = g_utf8_next_char(lp);
}
else if (*lp == '\\') {
/* convert back to the non-escaped version of
the characters */
lp = g_utf8_next_char(lp);
if (*lp == '\0') {
str = g_string_append_c (str, '\\');
break;
}
switch (*lp) {
case 'n': str = g_string_append_c (str, '\n'); break;
case 'r': str = g_string_append_c (str, '\r'); break;
case ';': str = g_string_append_c (str, ';'); break;
case ',':
if (!strcmp (attr->name, "CATEGORIES")) {
//We need to handle categories here to work
//aroung a bug in evo2
_read_attribute_value_add (attr, str, charset);
g_string_assign (str, "");
} else
str = g_string_append_c (str, ',');
break;
case '\\': str = g_string_append_c (str, '\\'); break;
case '"': str = g_string_append_c (str, '"'); break;
/* \t is (incorrectly) used by kOrganizer, so handle it here */
case 't': str = g_string_append_c (str, '\t'); break;
default:
osync_trace(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %i", *lp);
str = g_string_append_c (str, '\\');
str = g_string_append_unichar (str, g_utf8_get_char(lp));
break;
}
lp = g_utf8_next_char(lp);
}
else if ((*lp == ';') ||
(*lp == ',' && !strcmp (attr->name, "CATEGORIES"))) {
_read_attribute_value_add (attr, str, charset);
g_string_assign (str, "");
lp = g_utf8_next_char(lp);
}
else {
str = g_string_append_unichar (str, g_utf8_get_char (lp));
lp = g_utf8_next_char(lp);
}
}
if (str) {
_read_attribute_value_add (attr, str, charset);
g_string_free (str, TRUE);
}
if (*lp == '\r') {
lp = g_utf8_next_char (lp); /* \n */
lp = g_utf8_next_char (lp); /* start of the next line */
}
*p = lp;
}
static void _read_attribute_params(VFormatAttribute *attr, char **p, int *format_encoding, GString **charset)
{
char *lp = *p;
GString *str;
VFormatParam *param = NULL;
gboolean in_quote = FALSE;
str = g_string_new ("");
while (*lp != '\0') {
if (*lp == '"') {
in_quote = !in_quote;
lp = g_utf8_next_char (lp);
}
else if (in_quote || g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/' || *lp == '.' || *lp == ' ') {
str = g_string_append_unichar (str, g_utf8_get_char (lp));
lp = g_utf8_next_char (lp);
}
/* accumulate until we hit the '=' or ';'. If we hit
* a '=' the string contains the parameter name. if
* we hit a ';' the string contains the parameter
* value and the name is either ENCODING (if value ==
* QUOTED-PRINTABLE) or TYPE (in any other case.)
*/
else if (*lp == '=') {
if (str->len > 0) {
param = vformat_attribute_param_new (str->str);
g_string_assign (str, "");
lp = g_utf8_next_char (lp);
}
else {
_skip_until (&lp, ":;");
if (*lp == '\r') {
lp = g_utf8_next_char (lp); /* \n */
lp = g_utf8_next_char (lp); /* start of the next line */
break;
}
else if (*lp == ';')
lp = g_utf8_next_char (lp);
}
}
else if (*lp == ';' || *lp == ':' || *lp == ',') {
gboolean colon = (*lp == ':');
gboolean comma = (*lp == ',');
if (param) {
if (str->len > 0) {
vformat_attribute_param_add_value (param, str->str);
g_string_assign (str, "");
if (!colon)
lp = g_utf8_next_char (lp);
}
else {
/* we've got a parameter of the form:
* PARAM=(.*,)?[:;]
* so what we do depends on if there are already values
* for the parameter. If there are, we just finish
* this parameter and skip past the offending character
* (unless it's the ':'). If there aren't values, we free
* the parameter then skip past the character.
*/
if (!param->values) {
vformat_attribute_param_free (param);
param = NULL;
if (!colon)
lp = g_utf8_next_char (lp);
}
}
if (param
&& !g_ascii_strcasecmp (param->name, "encoding")) {
if (!g_ascii_strcasecmp (param->values->data, "quoted-printable")) {
*format_encoding = VF_ENCODING_QP;
vformat_attribute_param_free (param);
param = NULL;
} else if ( _helper_is_base64(param->values->data)) {
*format_encoding = VF_ENCODING_BASE64;
// vformat_attribute_param_free (param);
// param = NULL;
}
} else if (param && !g_ascii_strcasecmp(param->name, "charset")) {
*charset = g_string_new(param->values->data);
vformat_attribute_param_free (param);
param = NULL;
}
}
else {
if (str->len > 0) {
char *param_name;
if (!g_ascii_strcasecmp (str->str,
"quoted-printable")) {
param_name = "ENCODING";
*format_encoding = VF_ENCODING_QP;
}
/* apple's broken addressbook app outputs naked BASE64
parameters, which aren't even vcard 3.0 compliant. */
else if (!g_ascii_strcasecmp (str->str,
"base64")) {
param_name = "ENCODING";
g_string_assign (str, "b");
*format_encoding = VF_ENCODING_BASE64;
}
else {
param_name = "TYPE";
}
if (param_name) {
param = vformat_attribute_param_new (param_name);
vformat_attribute_param_add_value (param, str->str);
}
g_string_assign (str, "");
if (!colon)
lp = g_utf8_next_char (lp);
}
else {
/* we've got an attribute with a truly empty
attribute parameter. So it's of the form:
ATTR;[PARAM=value;]*;[PARAM=value;]*:
(note the extra ';')
the only thing to do here is, well.. nothing.
we skip over the character if it's not a colon,
and the rest is handled for us: We'll either
continue through the loop again if we hit a ';',
or we'll break out correct below if it was a ':' */
if (!colon)
lp = g_utf8_next_char (lp);
}
}
if (param && !comma) {
vformat_attribute_add_param (attr, param);
param = NULL;
}
if (colon)
break;
}
else {
osync_trace(TRACE_INTERNAL, "invalid character found in parameter spec: \"%i\" String so far: %s", lp[0], str->str);
g_string_assign (str, "");
_skip_until (&lp, ":;");
}
}
if (str)
g_string_free (str, TRUE);
*p = lp;
}
/* reads an entire attribute from the input buffer, leaving p pointing
at the start of the next line (past the \r\n) */
static VFormatAttribute *_read_attribute (char **p)
{
char *attr_group = NULL;
char *attr_name = NULL;
VFormatAttribute *attr = NULL;
GString *str, *charset = NULL;
char *lp = *p;
gboolean is_qp = FALSE;
/* first read in the group/name */
str = g_string_new ("");
while (*lp != '\r' && *lp != '\0') {
if (*lp == ':' || *lp == ';') {
if (str->len != 0) {
/* we've got a name, break out to the value/attribute parsing */
attr_name = g_string_free (str, FALSE);
break;
}
else {
/* a line of the form:
* (group.)?[:;]
*
* since we don't have an attribute
* name, skip to the end of the line
* and try again.
*/
g_string_free (str, TRUE);
*p = lp;
_skip_to_next_line(p);
goto lose;
}
}
else if (*lp == '.') {
if (attr_group) {
osync_trace(TRACE_INTERNAL, "extra `.' in attribute specification. ignoring extra group `%s'",
str->str);
g_string_free (str, TRUE);
str = g_string_new ("");
}
if (str->len != 0) {
attr_group = g_string_free (str, FALSE);
str = g_string_new ("");
}
}
else if (g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/') {
str = g_string_append_unichar (str, g_utf8_get_char (lp));
}
else {
osync_trace(TRACE_INTERNAL, "invalid character found in attribute group/name: \"%i\" String so far: %s", lp[0], str->str);
g_string_free (str, TRUE);
*p = lp;
_skip_to_next_line(p);
goto lose;
}
lp = g_utf8_next_char(lp);
}
if (!attr_name) {
_skip_to_next_line (p);
goto lose;
}
attr = vformat_attribute_new (attr_group, attr_name);
g_free (attr_group);
g_free (attr_name);
if (*lp == ';') {
/* skip past the ';' */
lp = g_utf8_next_char(lp);
_read_attribute_params (attr, &lp, &is_qp, &charset);
}
if (*lp == ':') {
/* skip past the ':' */
lp = g_utf8_next_char(lp);
_read_attribute_value (attr, &lp, is_qp, charset);
}
if (charset) g_string_free(charset, TRUE);
*p = lp;
if (!attr->values)
goto lose;
return attr;
lose:
if (attr)
vformat_attribute_free (attr);
return NULL;
}
/* we try to be as forgiving as we possibly can here - this isn't a
* validator. Almost nothing is considered a fatal error. We always
* try to return *something*.
*/
static void _parse(VFormat *evc, const char *str)
{
char *buf = g_strdup (str);
char *p, *end;
VFormatAttribute *attr;
/* first validate the string is valid utf8 */
if (!g_utf8_validate (buf, -1, (const char **)&end)) {
/* if the string isn't valid, we parse as much as we can from it */
osync_trace(TRACE_INTERNAL, "invalid utf8 passed to VFormat. Limping along.");
*end = '\0';
}
buf = _fold_lines (buf);
p = buf;
attr = _read_attribute (&p);
if (!attr)
attr = _read_attribute (&p);
if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "begin")) {
osync_trace(TRACE_INTERNAL, "vformat began without a BEGIN\n");
}
if (attr && !g_ascii_strcasecmp (attr->name, "begin"))
vformat_attribute_free (attr);
else if (attr)
vformat_add_attribute (evc, attr);
while (*p) {
VFormatAttribute *next_attr = _read_attribute (&p);
if (next_attr) {
//if (g_ascii_strcasecmp (next_attr->name, "end"))
vformat_add_attribute (evc, next_attr);
attr = next_attr;
}
}
if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "end")) {
osync_trace(TRACE_INTERNAL, "vformat ended without END");
}
g_free (buf);
}
char *vformat_escape_string (const char *s, VFormatType type)
{
GString *str;
const char *p;
str = g_string_new ("");
/* Escape a string as described in RFC2426, section 5 */
for (p = s; p && *p; p++) {
switch (*p) {
case '\n':
str = g_string_append (str, "\\n");
break;
case '\r':
if (*(p+1) == '\n')
p++;
str = g_string_append (str, "\\n");
break;
case ';':
str = g_string_append (str, "\\;");
break;
case ',':
if (type == VFORMAT_CARD_30 || type == VFORMAT_EVENT_20 || type == VFORMAT_TODO_20)
str = g_string_append (str, "\\,");
else
str = g_string_append_c (str, *p);
break;
case '\\':
/**
* We won't escape backslashes
* on vcard 2.1, unless it is in the end of a value.
* See comments above for a better explanation
**/
if (*p != '\0' && type == VFORMAT_CARD_21) {
osync_trace(TRACE_INTERNAL, "[%s]We won't escape backslashes", __func__);
str = g_string_append_c(str, *p);
}
else {
osync_trace(TRACE_INTERNAL, "[%s] escape backslashes!!", __func__);
str = g_string_append (str, "\\\\");
}
break;
default:
str = g_string_append_c (str, *p);
break;
}
}
return g_string_free (str, FALSE);
}
char*
vformat_unescape_string (const char *s)
{
GString *str;
const char *p;
g_return_val_if_fail (s != NULL, NULL);
str = g_string_new ("");
/* Unescape a string as described in RFC2426, section 4 (Formal Grammar) */
for (p = s; *p; p++) {
if (*p == '\\') {
p++;
if (*p == '\0') {
str = g_string_append_c (str, '\\');
break;
}
switch (*p) {
case 'n': str = g_string_append_c (str, '\n'); break;
case 'r': str = g_string_append_c (str, '\r'); break;
case ';': str = g_string_append_c (str, ';'); break;
case ',': str = g_string_append_c (str, ','); break;
case '\\': str = g_string_append_c (str, '\\'); break;
case '"': str = g_string_append_c (str, '"'); break;
/* \t is (incorrectly) used by kOrganizer, so handle it here */
case 't': str = g_string_append_c (str, '\t'); break;
default:
osync_trace(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %s", *p);
str = g_string_append_c (str, '\\');
str = g_string_append_unichar (str, g_utf8_get_char(p));
break;
}
}
}
return g_string_free (str, FALSE);
}
void
vformat_construct (VFormat *evc, const char *str)
{
g_return_if_fail (str != NULL);
if (*str)
_parse (evc, str);
}
void vformat_free(VFormat *format)
{
g_list_foreach (format->attributes, (GFunc)vformat_attribute_free, NULL);
g_list_free (format->attributes);
g_free(format);
}
VFormat *vformat_new_from_string (const char *str)
{
g_return_val_if_fail (str != NULL, NULL);
VFormat *evc = g_malloc0(sizeof(VFormat));
vformat_construct (evc, str);
return evc;
}
VFormat *vformat_new(void)
{
return vformat_new_from_string ("");
}
VFormatAttribute *vformat_find_attribute(VFormat *vcard, const char *name)
{
GList *attributes = vformat_get_attributes(vcard);
GList *a = NULL;
for (a = attributes; a; a = a->next) {
VFormatAttribute *attr = a->data;
if (!strcmp(vformat_attribute_get_name(attr), name)) {
return attr;
}
}
return NULL;
}
char *vformat_to_string (VFormat *evc, VFormatType type)
{
osync_trace(TRACE_ENTRY, "%s(%p, %i)", __func__, type);
GList *l;
GList *v;
GString *str = g_string_new ("");
switch (type) {
case VFORMAT_CARD_21:
str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:2.1\r\n");
break;
case VFORMAT_CARD_30:
str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:3.0\r\n");
break;
case VFORMAT_TODO_10:
case VFORMAT_EVENT_10:
str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:1.0\r\n");
break;
case VFORMAT_TODO_20:
case VFORMAT_EVENT_20:
str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n");
break;
case VFORMAT_NOTE:
str = g_string_append (str, "BEGIN:VNOTE\r\nVERSION:1.1\r\n");
break;
}
for (l = evc->attributes; l; l = l->next) {
GList *p;
VFormatAttribute *attr = l->data;
GString *attr_str;
int l;
int format_encoding = VF_ENCODING_RAW;
attr_str = g_string_new ("");
/* From rfc2425, 5.8.2
*
* contentline = [group "."] name *(";" param) ":" value CRLF
*/
if (attr->group) {
attr_str = g_string_append (attr_str, attr->group);
attr_str = g_string_append_c (attr_str, '.');
}
attr_str = g_string_append (attr_str, attr->name);
/* handle the parameters */
for (p = attr->params; p; p = p->next) {
VFormatParam *param = p->data;
/* 5.8.2:
* param = param-name "=" param-value *("," param-value)
*/
if( type == VFORMAT_CARD_30 || type == VFORMAT_TODO_20
|| type == VFORMAT_EVENT_20) {
/**
* Character set can only be specified on the CHARSET
* parameter on the Content-Type MIME header field.
**/
if (!g_ascii_strcasecmp (param->name, "CHARSET"))
continue;
attr_str = g_string_append_c (attr_str, ';');
attr_str = g_string_append (attr_str, param->name);
if (param->values) {
attr_str = g_string_append_c (attr_str, '=');
}
for (v = param->values; v; v = v->next) {
if (_helper_is_base64((const char *) v->data)) {
format_encoding = VF_ENCODING_BASE64;
/*Only the "B" encoding of [RFC 2047] is an allowed*/
v->data="B";
}
/**
* QUOTED-PRINTABLE inline encoding has been
* eliminated.
**/
if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE")) {
osync_trace(TRACE_ERROR, "%s false encoding QUOTED-PRINTABLE is not allowed", __func__);
format_encoding = VF_ENCODING_QP;
}
attr_str = g_string_append (attr_str, v->data);
if (v->next)
attr_str = g_string_append_c (attr_str, ',');
}
}
else {
attr_str = g_string_append_c (attr_str, ';');
/**
* The "TYPE=" is optional skip it.
* LOGO, PHOTO and SOUND multimedia formats MUST
* have a "TYPE=" parameter
**/
gboolean must_have_type = FALSE;
if (!g_ascii_strcasecmp (attr->name, "PHOTO") || !g_ascii_strcasecmp (attr->name, "LOGO") || !g_ascii_strcasecmp (attr->name, "SOUND") )
must_have_type = TRUE;
if ( must_have_type || g_ascii_strcasecmp (param->name, "TYPE") )
attr_str = g_string_append (attr_str, param->name);
if ( param->values && (must_have_type || g_ascii_strcasecmp (param->name, "TYPE")) )
attr_str = g_string_append_c (attr_str, '=');
for (v = param->values; v; v = v->next) {
// check for quoted-printable encoding
if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE"))
format_encoding = VF_ENCODING_QP;
// check for base64 encoding
if (_helper_is_base64((const char *) v->data)) {
format_encoding = VF_ENCODING_BASE64;
v->data="BASE64";
}
attr_str = g_string_append (attr_str, v->data);
if (v->next)
attr_str = g_string_append_c (attr_str, ',');
}
}
}
attr_str = g_string_append_c (attr_str, ':');
for (v = attr->values; v; v = v->next) {
char *value = v->data;
char *escaped_value = NULL;
if (!strcmp (attr->name, "RRULE") &&
strstr (value, "BYDAY") == v->data) {
attr_str = g_string_append (attr_str, value);
} else {
escaped_value = vformat_escape_string (value, type);
attr_str = g_string_append (attr_str, escaped_value);
}
if (v->next) {
/* XXX toshok - i hate you, rfc 2426.
why doesn't CATEGORIES use a ; like
a normal list attribute? */
if (!strcmp (attr->name, "CATEGORIES"))
attr_str = g_string_append_c (attr_str, ',');
else
attr_str = g_string_append_c (attr_str, ';');
}
g_free (escaped_value);
}
/* Folding lines:
* ^^^^^^^^^^^^^^
*
* rfc 2426 (vCard), 2.6 Line Delimiting and Folding:
* After generating a content line,
* lines longer than 75 characters SHOULD be folded according to the
* folding procedure described in [MIME-DIR].
*
* rfc 2445 (iCalendar), 4.1 Content Lines:
* Lines of text SHOULD NOT be longer than 75 octets, excluding the line
* break. Long content lines SHOULD be split into a multiple line
* representations using a line "folding" technique. That is, a long
* line can be split between any two characters by inserting a CRLF
* immediately followed by a single linear white space character (i.e.,
* SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
* of CRLF followed immediately by a single linear white space character
* is ignored (i.e., removed) when processing the content type.
*
* SUMMARY: When generating a content line, lines longer then 75 characters SHOULD be folded!
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Differences between encodings:
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* rfc 2425 [MIME-DIR], 5.8.1:
* A logical line MAY be continued on the next physical line anywhere
* between two characters by inserting a CRLF immediately followed by a
* single <WS> (white space) character.
*
* rfc 2045, 6.7, chapter 5:
* The quoted-printable specs says that softbreaks should be generated by inserting a =\r\n
* without follwing <WS>
*
* UTF-8
* ^^^^^
*
* Note that all the line folding above is described in terms of characters
* not bytes. In particular, it would be an error to put a line break
* within a UTF-8 character.
*/
l = 0;
do {
if (g_utf8_strlen(attr_str->str, attr_str->len) - l > 75) {
l += 75;
/* If using QP, must be sure that we do not fold within a quote sequence */
if (format_encoding == VF_ENCODING_QP) {
if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-1)) == '=') l--;
else if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-2)) == '=') l -= 2;
}
char *p = g_utf8_offset_to_pointer(attr_str->str, l);
if (format_encoding == VF_ENCODING_QP)
attr_str = g_string_insert_len (attr_str, p - attr_str->str, "=" CRLF "", sizeof ("=" CRLF "") - 1);
else
attr_str = g_string_insert_len (attr_str, p - attr_str->str, CRLF " ", sizeof (CRLF " ") - 1);
}
else
break;
} while (l < g_utf8_strlen(attr_str->str, attr_str->len));
attr_str = g_string_append (attr_str, CRLF);
/**
* base64= <MIME RFC 1521 base64 text>
* the end of the text is marked with two CRLF sequences
* this results in one blank line before the start of the
* next property
**/
if( format_encoding == VF_ENCODING_BASE64
&& (type == VFORMAT_CARD_21))
attr_str = g_string_append (attr_str, CRLF);
str = g_string_append (str, attr_str->str);
g_string_free (attr_str, TRUE);
}
switch (type) {
case VFORMAT_CARD_21:
str = g_string_append (str, "END:VCARD\r\n");
break;
case VFORMAT_CARD_30:
str = g_string_append (str, "END:VCARD\r\n");
break;
case VFORMAT_TODO_10:
case VFORMAT_EVENT_10:
str = g_string_append (str, "END:VCALENDAR\r\n");
break;
case VFORMAT_TODO_20:
case VFORMAT_EVENT_20:
str = g_string_append (str, "END:VCALENDAR\r\n");
break;
case VFORMAT_NOTE:
str = g_string_append (str, "END:VNOTE\r\n");
break;
}
osync_trace(TRACE_EXIT, "%s(%p, %i)", __func__, type);
return g_string_free (str, FALSE);
}
void vformat_dump_structure (VFormat *evc)
{
GList *a;
GList *v;
int i;
printf ("VFormat\n");
for (a = evc->attributes; a; a = a->next) {
GList *p;
VFormatAttribute *attr = a->data;
printf ("+-- %s\n", attr->name);
if (attr->params) {
printf (" +- params=\n");
for (p = attr->params, i = 0; p; p = p->next, i++) {
VFormatParam *param = p->data;
printf (" | [%d] = %s", i,param->name);
printf ("(");
for (v = param->values; v; v = v->next) {
char *value = vformat_escape_string ((char*)v->data, VFORMAT_CARD_21);
printf ("%s", value);
if (v->next)
printf (",");
g_free (value);
}
printf (")\n");
}
}
printf (" +- values=\n");
for (v = attr->values, i = 0; v; v = v->next, i++) {
printf (" [%d] = `%s'\n", i, (char*)v->data);
}
}
}
VFormatAttribute *vformat_attribute_new (const char *attr_group, const char *attr_name)
{
VFormatAttribute *attr;
attr = g_new0 (VFormatAttribute, 1);
attr->group = g_strdup (attr_group);
attr->name = g_strdup (attr_name);
return attr;
}
void
vformat_attribute_free (VFormatAttribute *attr)
{
g_return_if_fail (attr != NULL);
g_free (attr->group);
g_free (attr->name);
vformat_attribute_remove_values (attr);
vformat_attribute_remove_params (attr);
g_free (attr);
}
VFormatAttribute*
vformat_attribute_copy (VFormatAttribute *attr)
{
VFormatAttribute *a;
GList *p;
g_return_val_if_fail (attr != NULL, NULL);
a = vformat_attribute_new (vformat_attribute_get_group (attr),
vformat_attribute_get_name (attr));
for (p = attr->values; p; p = p->next)
vformat_attribute_add_value (a, p->data);
for (p = attr->params; p; p = p->next)
vformat_attribute_add_param (a, vformat_attribute_param_copy (p->data));
return a;
}
void
vformat_remove_attributes (VFormat *evc, const char *attr_group, const char *attr_name)
{
GList *attr;
g_return_if_fail (attr_name != NULL);
attr = evc->attributes;
while (attr) {
GList *next_attr;
VFormatAttribute *a = attr->data;
next_attr = attr->next;
if (((!attr_group && !a->group) ||
(attr_group && !g_ascii_strcasecmp (attr_group, a->group))) &&
((!attr_name && !a->name) || !g_ascii_strcasecmp (attr_name, a->name))) {
/* matches, remove/delete the attribute */
evc->attributes = g_list_remove_link (evc->attributes, attr);
vformat_attribute_free (a);
}
attr = next_attr;
}
}
void
vformat_remove_attribute (VFormat *evc, VFormatAttribute *attr)
{
g_return_if_fail (attr != NULL);
evc->attributes = g_list_remove (evc->attributes, attr);
vformat_attribute_free (attr);
}
void
vformat_add_attribute (VFormat *evc, VFormatAttribute *attr)
{
g_return_if_fail (attr != NULL);
evc->attributes = g_list_append (evc->attributes, attr);
}
void
vformat_add_attribute_with_value (VFormat *VFormat,
VFormatAttribute *attr, const char *value)
{
g_return_if_fail (attr != NULL);
vformat_attribute_add_value (attr, value);
vformat_add_attribute (VFormat, attr);
}
void
vformat_add_attribute_with_values (VFormat *VFormat, VFormatAttribute *attr, ...)
{
va_list ap;
char *v;
g_return_if_fail (attr != NULL);
va_start (ap, attr);
while ((v = va_arg (ap, char*))) {
vformat_attribute_add_value (attr, v);
}
va_end (ap);
vformat_add_attribute (VFormat, attr);
}
void
vformat_attribute_add_value (VFormatAttribute *attr, const char *value)
{
g_return_if_fail (attr != NULL);
attr->values = g_list_append (attr->values, g_strdup (value));
}
void
vformat_attribute_add_value_decoded (VFormatAttribute *attr, const char *value, int len)
{
g_return_if_fail (attr != NULL);
switch (attr->encoding) {
case VF_ENCODING_RAW:
osync_trace(TRACE_INTERNAL, "can't add_value_decoded with an attribute using RAW encoding. you must set the ENCODING parameter first");
break;
case VF_ENCODING_BASE64: {
char *b64_data = base64_encode_simple (value, len);
GString *decoded = g_string_new_len (value, len);
/* make sure the decoded list is up to date */
vformat_attribute_get_values_decoded (attr);
attr->values = g_list_append (attr->values, b64_data);
attr->decoded_values = g_list_append (attr->decoded_values, decoded);
break;
}
case VF_ENCODING_QP: {
char *qp_data = quoted_encode_simple ((unsigned char*)value, len);
GString *decoded = g_string_new (value);
/* make sure the decoded list is up to date */
vformat_attribute_get_values_decoded (attr);
attr->values = g_list_append (attr->values, qp_data);
attr->decoded_values = g_list_append (attr->decoded_values, decoded);
break;
}
case VF_ENCODING_8BIT: {
char *data = g_strdup(value);
GString *decoded = g_string_new (value);
/* make sure the decoded list is up to date */
vformat_attribute_get_values_decoded (attr);
attr->values = g_list_append (attr->values, data);
attr->decoded_values = g_list_append (attr->decoded_values, decoded);
break;
}
}
}
void
vformat_attribute_add_values (VFormatAttribute *attr, ...)
{
va_list ap;
char *v;
g_return_if_fail (attr != NULL);
va_start (ap, attr);
while ((v = va_arg (ap, char*))) {
vformat_attribute_add_value (attr, v);
}
va_end (ap);
}
static void
free_gstring (GString *str)
{
g_string_free (str, TRUE);
}
void
vformat_attribute_remove_values (VFormatAttribute *attr)
{
g_return_if_fail (attr != NULL);
g_list_foreach (attr->values, (GFunc)g_free, NULL);
g_list_free (attr->values);
attr->values = NULL;
g_list_foreach (attr->decoded_values, (GFunc)free_gstring, NULL);
g_list_free (attr->decoded_values);
attr->decoded_values = NULL;
}
void
vformat_attribute_remove_params (VFormatAttribute *attr)
{
g_return_if_fail (attr != NULL);
g_list_foreach (attr->params, (GFunc)vformat_attribute_param_free, NULL);
g_list_free (attr->params);
attr->params = NULL;
/* also remove the cached encoding on this attribute */
attr->encoding_set = FALSE;
attr->encoding = VF_ENCODING_RAW;
}
VFormatParam*
vformat_attribute_param_new (const char *name)
{
VFormatParam *param = g_new0 (VFormatParam, 1);
param->name = g_strdup (name);
return param;
}
void
vformat_attribute_param_free (VFormatParam *param)
{
g_return_if_fail (param != NULL);
g_free (param->name);
vformat_attribute_param_remove_values (param);
g_free (param);
}
VFormatParam*
vformat_attribute_param_copy (VFormatParam *param)
{
VFormatParam *p;
GList *l;
g_return_val_if_fail (param != NULL, NULL);
p = vformat_attribute_param_new (vformat_attribute_param_get_name (param));
for (l = param->values; l; l = l->next) {
vformat_attribute_param_add_value (p, l->data);
}
return p;
}
void
vformat_attribute_add_param (VFormatAttribute *attr,
VFormatParam *param)
{
g_return_if_fail (attr != NULL);
g_return_if_fail (param != NULL);
attr->params = g_list_append (attr->params, param);
/* we handle our special encoding stuff here */
if (!g_ascii_strcasecmp (param->name, "ENCODING")) {
if (attr->encoding_set) {
osync_trace(TRACE_INTERNAL, "ENCODING specified twice");
return;
}
if (param->values && param->values->data) {
if (_helper_is_base64((const char*)param->values->data))
attr->encoding = VF_ENCODING_BASE64;
else if (!g_ascii_strcasecmp ((char*)param->values->data, "QUOTED-PRINTABLE"))
attr->encoding = VF_ENCODING_QP;
else if (!g_ascii_strcasecmp ((char *)param->values->data, "8BIT"))
attr->encoding = VF_ENCODING_8BIT;
else {
osync_trace(TRACE_INTERNAL, "Unknown value `%s' for ENCODING parameter. values will be treated as raw",
(char*)param->values->data);
}
attr->encoding_set = TRUE;
}
else {
osync_trace(TRACE_INTERNAL, "ENCODING parameter added with no value");
}
}
}
VFormatParam *vformat_attribute_find_param(VFormatAttribute *attr, const char *name)
{
g_return_val_if_fail (attr != NULL, NULL);
GList *p = NULL;
for (p = attr->params; p; p = p->next) {
VFormatParam *param = p->data;
if (!g_ascii_strcasecmp (param->name, name))
return param;
}
return NULL;
}
void
vformat_attribute_set_value (VFormatAttribute *attr,
int nth, const char *value)
{
GList *param = g_list_nth(attr->values, nth);
g_free(param->data);
param->data = g_strdup(value);
}
void
vformat_attribute_param_add_value (VFormatParam *param,
const char *value)
{
g_return_if_fail (param != NULL);
param->values = g_list_append (param->values, g_strdup (value));
}
void
vformat_attribute_param_add_values (VFormatParam *param,
...)
{
va_list ap;
char *v;
g_return_if_fail (param != NULL);
va_start (ap, param);
while ((v = va_arg (ap, char*))) {
vformat_attribute_param_add_value (param, v);
}
va_end (ap);
}
void
vformat_attribute_add_param_with_value (VFormatAttribute *attr, const char *name, const char *value)
{
g_return_if_fail (attr != NULL);
g_return_if_fail (name != NULL);
if (!value)
return;
VFormatParam *param = vformat_attribute_param_new(name);
vformat_attribute_param_add_value (param, value);
vformat_attribute_add_param (attr, param);
}
void
vformat_attribute_add_param_with_values (VFormatAttribute *attr,
VFormatParam *param, ...)
{
va_list ap;
char *v;
g_return_if_fail (attr != NULL);
g_return_if_fail (param != NULL);
va_start (ap, param);
while ((v = va_arg (ap, char*))) {
vformat_attribute_param_add_value (param, v);
}
va_end (ap);
vformat_attribute_add_param (attr, param);
}
void
vformat_attribute_param_remove_values (VFormatParam *param)
{
g_return_if_fail (param != NULL);
g_list_foreach (param->values, (GFunc)g_free, NULL);
g_list_free (param->values);
param->values = NULL;
}
GList*
vformat_get_attributes (VFormat *format)
{
return format->attributes;
}
const char*
vformat_attribute_get_group (VFormatAttribute *attr)
{
g_return_val_if_fail (attr != NULL, NULL);
return attr->group;
}
const char*
vformat_attribute_get_name (VFormatAttribute *attr)
{
g_return_val_if_fail (attr != NULL, NULL);
return attr->name;
}
GList*
vformat_attribute_get_values (VFormatAttribute *attr)
{
g_return_val_if_fail (attr != NULL, NULL);
return attr->values;
}
GList*
vformat_attribute_get_values_decoded (VFormatAttribute *attr)
{
g_return_val_if_fail (attr != NULL, NULL);
if (!attr->decoded_values) {
GList *l;
switch (attr->encoding) {
case VF_ENCODING_RAW:
case VF_ENCODING_8BIT:
for (l = attr->values; l; l = l->next)
attr->decoded_values = g_list_append (attr->decoded_values, g_string_new ((char*)l->data));
break;
case VF_ENCODING_BASE64:
for (l = attr->values; l; l = l->next) {
char *decoded = g_strdup ((char*)l->data);
int len = base64_decode_simple (decoded, strlen (decoded));
attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len));
g_free (decoded);
}
break;
case VF_ENCODING_QP:
for (l = attr->values; l; l = l->next) {
if (!(l->data))
continue;
char *decoded = g_strdup ((char*)l->data);
int len = quoted_decode_simple (decoded, strlen (decoded));
attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len));
g_free (decoded);
}
break;
}
}
return attr->decoded_values;
}
gboolean
vformat_attribute_is_single_valued (VFormatAttribute *attr)
{
g_return_val_if_fail (attr != NULL, FALSE);
if (attr->values == NULL
|| attr->values->next != NULL)
return FALSE;
return TRUE;
}
char*
vformat_attribute_get_value (VFormatAttribute *attr)
{
GList *values;
g_return_val_if_fail (attr != NULL, NULL);
values = vformat_attribute_get_values (attr);
if (!vformat_attribute_is_single_valued (attr))
osync_trace(TRACE_INTERNAL, "vformat_attribute_get_value called on multivalued attribute");
return values ? g_strdup ((char*)values->data) : NULL;
}
GString*
vformat_attribute_get_value_decoded (VFormatAttribute *attr)
{
GList *values;
GString *str = NULL;
g_return_val_if_fail (attr != NULL, NULL);
values = vformat_attribute_get_values_decoded (attr);
if (!vformat_attribute_is_single_valued (attr))
osync_trace(TRACE_INTERNAL, "vformat_attribute_get_value_decoded called on multivalued attribute");
if (values)
str = values->data;
return str ? g_string_new_len (str->str, str->len) : NULL;
}
const char *vformat_attribute_get_nth_value(VFormatAttribute *attr, int nth)
{
GList *values = vformat_attribute_get_values_decoded(attr);
if (!values)
return NULL;
GString *retstr = (GString *)g_list_nth_data(values, nth);
if (!retstr)
return NULL;
if (!g_utf8_validate(retstr->str, -1, NULL)) {
values = vformat_attribute_get_values(attr);
if (!values)
return NULL;
return g_list_nth_data(values, nth);
}
return retstr->str;
}
gboolean
vformat_attribute_has_type (VFormatAttribute *attr, const char *typestr)
{
GList *params;
GList *p;
g_return_val_if_fail (attr != NULL, FALSE);
g_return_val_if_fail (typestr != NULL, FALSE);
params = vformat_attribute_get_params (attr);
for (p = params; p; p = p->next) {
VFormatParam *param = p->data;
if (!strcasecmp (vformat_attribute_param_get_name (param), "TYPE")) {
GList *values = vformat_attribute_param_get_values (param);
GList *v;
for (v = values; v; v = v->next) {
if (!strcasecmp ((char*)v->data, typestr))
return TRUE;
}
}
}
return FALSE;
}
gboolean vformat_attribute_has_param(VFormatAttribute *attr, const char *name)
{
g_return_val_if_fail (attr != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
GList *params = vformat_attribute_get_params(attr);
GList *p;
for (p = params; p; p = p->next) {
VFormatParam *param = p->data;
if (!strcasecmp(name, vformat_attribute_param_get_name(param)))
return TRUE;
}
return FALSE;
}
GList*
vformat_attribute_get_params (VFormatAttribute *attr)
{
g_return_val_if_fail (attr != NULL, NULL);
return attr->params;
}
const char*
vformat_attribute_param_get_name (VFormatParam *param)
{
g_return_val_if_fail (param != NULL, NULL);
return param->name;
}
GList*
vformat_attribute_param_get_values (VFormatParam *param)
{
g_return_val_if_fail (param != NULL, NULL);
return param->values;
}
const char *vformat_attribute_param_get_nth_value(VFormatParam *param, int nth)
{
const char *ret = NULL;
GList *values = vformat_attribute_param_get_values(param);
if (!values)
return NULL;
ret = g_list_nth_data(values, nth);
return ret;
}
static const char *base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//static unsigned char _evc_base64_rank[256];
static void base64_init(char *rank)
{
int i;
memset(rank, 0xff, sizeof(rank));
for (i=0;i<64;i++) {
rank[(unsigned int)base64_alphabet[i]] = i;
}
rank['='] = 0;
}
/* call this when finished encoding everything, to
flush off the last little bit */
static size_t base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
{
int c1, c2;
unsigned char *outptr = out;
if (inlen>0)
outptr += base64_encode_step(in, inlen, break_lines, outptr, state, save);
c1 = ((unsigned char *)save)[1];
c2 = ((unsigned char *)save)[2];
switch (((char *)save)[0]) {
case 2:
outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
g_assert(outptr[2] != 0);
goto skip;
case 1:
outptr[2] = '=';
skip:
outptr[0] = base64_alphabet[ c1 >> 2 ];
outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
outptr[3] = '=';
outptr += 4;
break;
}
if (break_lines)
*outptr++ = '\n';
*save = 0;
*state = 0;
return outptr-out;
}
/*
performs an 'encode step', only encodes blocks of 3 characters to the
output at a time, saves left-over state in state and save (initialise to
0 on first invocation).
*/
static size_t base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
{
register unsigned char *inptr, *outptr;
if (len<=0)
return 0;
inptr = in;
outptr = out;
if (len + ((char *)save)[0] > 2) {
unsigned char *inend = in+len-2;
register int c1, c2, c3;
register int already;
already = *state;
switch (((char *)save)[0]) {
case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
case 2: c1 = ((unsigned char *)save)[1];
c2 = ((unsigned char *)save)[2]; goto skip2;
}
/* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
while (inptr < inend) {
c1 = *inptr++;
skip1:
c2 = *inptr++;
skip2:
c3 = *inptr++;
*outptr++ = base64_alphabet[ c1 >> 2 ];
*outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
*outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
*outptr++ = base64_alphabet[ c3 & 0x3f ];
/* this is a bit ugly ... */
if (break_lines && (++already)>=19) {
*outptr++='\n';
already = 0;
}
}
((char *)save)[0] = 0;
len = 2-(inptr-inend);
*state = already;
}
if (len>0) {
register char *saveout;
/* points to the slot for the next char to save */
saveout = & (((char *)save)[1]) + ((char *)save)[0];
/* len can only be 0 1 or 2 */
switch(len) {
case 2: *saveout++ = *inptr++;
case 1: *saveout++ = *inptr++;
}
((char *)save)[0]+=len;
}
return outptr-out;
}
/**
* base64_decode_step: decode a chunk of base64 encoded data
* @in: input stream
* @len: max length of data to decode
* @out: output stream
* @state: holds the number of bits that are stored in @save
* @save: leftover bits that have not yet been decoded
*
* Decodes a chunk of base64 encoded data
**/
static size_t base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
{
unsigned char base64_rank[256];
base64_init((char*)base64_rank);
register unsigned char *inptr, *outptr;
unsigned char *inend, c;
register unsigned int v;
int i;
inend = in+len;
outptr = out;
/* convert 4 base64 bytes to 3 normal bytes */
v=*save;
i=*state;
inptr = in;
while (inptr<inend) {
c = base64_rank[*inptr++];
if (c != 0xff) {
v = (v<<6) | c;
i++;
if (i==4) {
*outptr++ = v>>16;
*outptr++ = v>>8;
*outptr++ = v;
i=0;
}
}
}
*save = v;
*state = i;
/* quick scan back for '=' on the end somewhere */
/* fortunately we can drop 1 output char for each trailing = (upto 2) */
i=2;
while (inptr>in && i) {
inptr--;
if (base64_rank[*inptr] != 0xff) {
if (*inptr == '=' && outptr>out)
outptr--;
i--;
}
}
/* if i!= 0 then there is a truncation error! */
return outptr-out;
}
char *base64_encode_simple (const char *data, size_t len)
{
unsigned char *out;
int state = 0, outlen;
unsigned int save = 0;
g_return_val_if_fail (data != NULL, NULL);
out = g_malloc (len * 4 / 3 + 5);
outlen = base64_encode_close ((unsigned char *)data, len, FALSE,
out, &state, (int*)&save);
out[outlen] = '\0';
return (char *)out;
}
size_t base64_decode_simple (char *data, size_t len)
{
int state = 0;
unsigned int save = 0;
g_return_val_if_fail (data != NULL, 0);
return base64_decode_step ((unsigned char *)data, len,
(unsigned char *)data, &state, &save);
}
char *quoted_encode_simple(const unsigned char *string, int len)
{
GString *tmp = g_string_new("");
int i = 0;
while(string[i] != 0) {
if (string[i] > 127 || string[i] == 13 || string[i] == 10 || string[i] == '=') {
g_string_append_printf(tmp, "=%02X", string[i]);
} else {
g_string_append_c(tmp, string[i]);
}
i++;
}
char *ret = tmp->str;
g_string_free(tmp, FALSE);
return ret;
}
size_t quoted_decode_simple (char *data, size_t len)
{
g_return_val_if_fail (data != NULL, 0);
GString *string = g_string_new(data);
if (!string)
return 0;
char hex[5];
hex[4] = 0;
while (1) {
//Get the index of the next encoded char
int i = strcspn(string->str, "=");
if (i >= strlen(string->str))
break;
strcpy(hex, "0x");
strncat(hex, &string->str[i + 1], 2);
char rep = ((int)(strtod(hex, NULL)));
g_string_erase(string, i, 2);
g_string_insert_c(string, i, rep);
}
memset(data, 0, strlen(data));
strcpy(data, string->str);
g_string_free(string, 1);
return strlen(data);
}
syntax highlighted by Code2HTML, v. 0.9.1