diff options
author | hiro <hiro@ee746299-78ed-0310-b773-934348b2243d> | 2005-08-31 06:10:31 +0000 |
---|---|---|
committer | hiro <hiro@ee746299-78ed-0310-b773-934348b2243d> | 2005-08-31 06:10:31 +0000 |
commit | f36577b27b6f352f140cf1f25755d39661bd4072 (patch) | |
tree | 664d196337dc86ddafc6218c8c9f19055e22e155 /libsylph | |
parent | 6ae811ae5e6a0463dadc9ebb6f833dc5154700bd (diff) |
made some core modules library (libsylph).
git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@528 ee746299-78ed-0310-b773-934348b2243d
Diffstat (limited to 'libsylph')
-rw-r--r-- | libsylph/Makefile.am | 25 | ||||
-rw-r--r-- | libsylph/base64.c | 168 | ||||
-rw-r--r-- | libsylph/base64.h | 46 | ||||
-rw-r--r-- | libsylph/codeconv.c | 1982 | ||||
-rw-r--r-- | libsylph/codeconv.h | 239 | ||||
-rw-r--r-- | libsylph/defs.h | 122 | ||||
-rw-r--r-- | libsylph/prefs.c | 465 | ||||
-rw-r--r-- | libsylph/prefs.h | 80 | ||||
-rw-r--r-- | libsylph/quoted-printable.c | 232 | ||||
-rw-r--r-- | libsylph/quoted-printable.h | 36 | ||||
-rw-r--r-- | libsylph/session.c | 793 | ||||
-rw-r--r-- | libsylph/session.h | 205 | ||||
-rw-r--r-- | libsylph/socket.c | 1397 | ||||
-rw-r--r-- | libsylph/socket.h | 124 | ||||
-rw-r--r-- | libsylph/ssl.c | 175 | ||||
-rw-r--r-- | libsylph/ssl.h | 58 | ||||
-rw-r--r-- | libsylph/stringtable.c | 163 | ||||
-rw-r--r-- | libsylph/stringtable.h | 38 | ||||
-rw-r--r-- | libsylph/unmime.c | 134 | ||||
-rw-r--r-- | libsylph/unmime.h | 27 | ||||
-rw-r--r-- | libsylph/utils.c | 3436 | ||||
-rw-r--r-- | libsylph/utils.h | 493 | ||||
-rw-r--r-- | libsylph/uuencode.c | 101 | ||||
-rw-r--r-- | libsylph/uuencode.h | 24 | ||||
-rw-r--r-- | libsylph/xml.c | 655 | ||||
-rw-r--r-- | libsylph/xml.h | 108 |
26 files changed, 11326 insertions, 0 deletions
diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am new file mode 100644 index 00000000..bb2eec32 --- /dev/null +++ b/libsylph/Makefile.am @@ -0,0 +1,25 @@ + +AM_CPPFLAGS = -DG_LOG_DOMAIN=\"LibSylph\" + +INCLUDES = $(GLIB_CFLAGS) -I$(top_srcdir) -I$(includedir) + +#lib_LTLIBRARIES = libsylph.la +noinst_LTLIBRARIES = libsylph.la + +libsylph_la_SOURCES = \ + defs.h \ + base64.c base64.h \ + codeconv.c codeconv.h \ + prefs.c prefs.h \ + quoted-printable.c quoted-printable.h \ + session.c session.h \ + socket.c socket.h \ + ssl.c ssl.h \ + stringtable.c stringtable.h \ + unmime.c unmime.h \ + utils.c utils.h \ + uuencode.c uuencode.h \ + xml.c xml.h + +libsylph_la_LDFLAGS = +libsylph_la_LIBADD = $(GLIB_LIBS) diff --git a/libsylph/base64.c b/libsylph/base64.c new file mode 100644 index 00000000..484cd286 --- /dev/null +++ b/libsylph/base64.c @@ -0,0 +1,168 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <glib.h> +#include <ctype.h> +#include <string.h> + +#include "base64.h" + +static const gchar base64char[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const gchar base64val[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +}; + +#define BASE64VAL(c) (isascii((guchar)c) ? base64val[(gint)(c)] : -1) + +void base64_encode(gchar *out, const guchar *in, gint inlen) +{ + const guchar *inp = in; + gchar *outp = out; + + while (inlen >= 3) { + *outp++ = base64char[(inp[0] >> 2) & 0x3f]; + *outp++ = base64char[((inp[0] & 0x03) << 4) | + ((inp[1] >> 4) & 0x0f)]; + *outp++ = base64char[((inp[1] & 0x0f) << 2) | + ((inp[2] >> 6) & 0x03)]; + *outp++ = base64char[inp[2] & 0x3f]; + + inp += 3; + inlen -= 3; + } + + if (inlen > 0) { + *outp++ = base64char[(inp[0] >> 2) & 0x3f]; + if (inlen == 1) { + *outp++ = base64char[(inp[0] & 0x03) << 4]; + *outp++ = '='; + } else { + *outp++ = base64char[((inp[0] & 0x03) << 4) | + ((inp[1] >> 4) & 0x0f)]; + *outp++ = base64char[((inp[1] & 0x0f) << 2)]; + } + *outp++ = '='; + } + + *outp = '\0'; +} + +gint base64_decode(guchar *out, const gchar *in, gint inlen) +{ + const gchar *inp = in; + guchar *outp = out; + gchar buf[4]; + + if (inlen < 0) + inlen = G_MAXINT; + + while (inlen >= 4 && *inp != '\0') { + buf[0] = *inp++; + inlen--; + if (BASE64VAL(buf[0]) == -1) break; + + buf[1] = *inp++; + inlen--; + if (BASE64VAL(buf[1]) == -1) break; + + buf[2] = *inp++; + inlen--; + if (buf[2] != '=' && BASE64VAL(buf[2]) == -1) break; + + buf[3] = *inp++; + inlen--; + if (buf[3] != '=' && BASE64VAL(buf[3]) == -1) break; + + *outp++ = ((BASE64VAL(buf[0]) << 2) & 0xfc) | + ((BASE64VAL(buf[1]) >> 4) & 0x03); + if (buf[2] != '=') { + *outp++ = ((BASE64VAL(buf[1]) & 0x0f) << 4) | + ((BASE64VAL(buf[2]) >> 2) & 0x0f); + if (buf[3] != '=') { + *outp++ = ((BASE64VAL(buf[2]) & 0x03) << 6) | + (BASE64VAL(buf[3]) & 0x3f); + } + } + } + + return outp - out; +} + +Base64Decoder *base64_decoder_new(void) +{ + Base64Decoder *decoder; + + decoder = g_new0(Base64Decoder, 1); + return decoder; +} + +void base64_decoder_free(Base64Decoder *decoder) +{ + g_free(decoder); +} + +gint base64_decoder_decode(Base64Decoder *decoder, + const gchar *in, guchar *out) +{ + gint len, total_len = 0; + gint buf_len; + gchar buf[4]; + + g_return_val_if_fail(decoder != NULL, -1); + g_return_val_if_fail(in != NULL, -1); + g_return_val_if_fail(out != NULL, -1); + + buf_len = decoder->buf_len; + memcpy(buf, decoder->buf, sizeof(buf)); + + for (;;) { + while (buf_len < 4) { + gchar c = *in; + + in++; + if (c == '\0') break; + if (c == '\r' || c == '\n') continue; + if (c != '=' && BASE64VAL(c) == -1) + return -1; + buf[buf_len++] = c; + } + if (buf_len < 4 || buf[0] == '=' || buf[1] == '=') { + decoder->buf_len = buf_len; + memcpy(decoder->buf, buf, sizeof(buf)); + return total_len; + } + len = base64_decode(out, buf, 4); + out += len; + total_len += len; + buf_len = 0; + if (len < 3) { + decoder->buf_len = 0; + return total_len; + } + } +} diff --git a/libsylph/base64.h b/libsylph/base64.h new file mode 100644 index 00000000..4aa55758 --- /dev/null +++ b/libsylph/base64.h @@ -0,0 +1,46 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __BASE64_H__ +#define __BASE64_H__ + +#include <glib.h> + +typedef struct _Base64Decoder Base64Decoder; + +struct _Base64Decoder +{ + gint buf_len; + gchar buf[4]; +}; + +void base64_encode (gchar *out, + const guchar *in, + gint inlen); +gint base64_decode (guchar *out, + const gchar *in, + gint inlen); + +Base64Decoder *base64_decoder_new (void); +void base64_decoder_free (Base64Decoder *decoder); +gint base64_decoder_decode (Base64Decoder *decoder, + const gchar *in, + guchar *out); + +#endif /* __BASE64_H__ */ diff --git a/libsylph/codeconv.c b/libsylph/codeconv.c new file mode 100644 index 00000000..c88b588c --- /dev/null +++ b/libsylph/codeconv.c @@ -0,0 +1,1982 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> + +#if HAVE_LOCALE_H +# include <locale.h> +#endif + +#include <iconv.h> + +#include "codeconv.h" +#include "unmime.h" +#include "base64.h" +#include "quoted-printable.h" +#include "utils.h" + +typedef enum +{ + JIS_ASCII, + JIS_KANJI, + JIS_HWKANA, + JIS_AUXKANJI +} JISState; + +#define SUBST_CHAR '_' +#define ESC '\033' + +#define iseuckanji(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xfe) +#define iseuchwkana1(c) \ + (((c) & 0xff) == 0x8e) +#define iseuchwkana2(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf) +#define iseucaux(c) \ + (((c) & 0xff) == 0x8f) +#define issjiskanji1(c) \ + ((((c) & 0xff) >= 0x81 && ((c) & 0xff) <= 0x9f) || \ + (((c) & 0xff) >= 0xe0 && ((c) & 0xff) <= 0xfc)) +#define issjiskanji2(c) \ + ((((c) & 0xff) >= 0x40 && ((c) & 0xff) <= 0x7e) || \ + (((c) & 0xff) >= 0x80 && ((c) & 0xff) <= 0xfc)) +#define issjishwkana(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf) + +#define K_IN() \ + if (state != JIS_KANJI) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = 'B'; \ + state = JIS_KANJI; \ + } + +#define K_OUT() \ + if (state != JIS_ASCII) { \ + *out++ = ESC; \ + *out++ = '('; \ + *out++ = 'B'; \ + state = JIS_ASCII; \ + } + +#define HW_IN() \ + if (state != JIS_HWKANA) { \ + *out++ = ESC; \ + *out++ = '('; \ + *out++ = 'I'; \ + state = JIS_HWKANA; \ + } + +#define AUX_IN() \ + if (state != JIS_AUXKANJI) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = '('; \ + *out++ = 'D'; \ + state = JIS_AUXKANJI; \ + } + +static gchar *conv_jistoeuc(const gchar *inbuf, gint *error); +static gchar *conv_euctojis(const gchar *inbuf, gint *error); +static gchar *conv_sjistoeuc(const gchar *inbuf, gint *error); + +static gchar *conv_jistoutf8(const gchar *inbuf, gint *error); +static gchar *conv_sjistoutf8(const gchar *inbuf, gint *error); +static gchar *conv_euctoutf8(const gchar *inbuf, gint *error); +static gchar *conv_anytoutf8(const gchar *inbuf, gint *error); + +static gchar *conv_utf8toeuc(const gchar *inbuf, gint *error); +static gchar *conv_utf8tojis(const gchar *inbuf, gint *error); + +/* static void conv_unreadable_eucjp(gchar *str); */ +static void conv_unreadable_8bit(gchar *str); +/* static void conv_unreadable_latin(gchar *str); */ + +static gchar *conv_jistodisp(const gchar *inbuf, gint *error); +static gchar *conv_sjistodisp(const gchar *inbuf, gint *error); +static gchar *conv_euctodisp(const gchar *inbuf, gint *error); + +static gchar *conv_anytodisp(const gchar *inbuf, gint *error); +static gchar *conv_ustodisp(const gchar *inbuf, gint *error); +static gchar *conv_noconv(const gchar *inbuf, gint *error); + +static gchar *conv_jistoeuc(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + JISState state = JIS_ASCII; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 2 + 1); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (*in == ESC) { + in++; + if (*in == '$') { + if (*(in + 1) == '@' || *(in + 1) == 'B') { + state = JIS_KANJI; + in += 2; + } else if (*(in + 1) == '(' && + *(in + 2) == 'D') { + state = JIS_AUXKANJI; + in += 3; + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else if (*in == '(') { + if (*(in + 1) == 'B' || *(in + 1) == 'J') { + state = JIS_ASCII; + in += 2; + } else if (*(in + 1) == 'I') { + state = JIS_HWKANA; + in += 2; + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else if (*in == 0x0e) { + state = JIS_HWKANA; + in++; + } else if (*in == 0x0f) { + state = JIS_ASCII; + in++; + } else { + switch (state) { + case JIS_ASCII: + *out++ = *in++; + break; + case JIS_KANJI: + *out++ = *in++ | 0x80; + if (*in == '\0') break; + *out++ = *in++ | 0x80; + break; + case JIS_HWKANA: + *out++ = 0x8e; + *out++ = *in++ | 0x80; + break; + case JIS_AUXKANJI: + *out++ = 0x8f; + *out++ = *in++ | 0x80; + if (*in == '\0') break; + *out++ = *in++ | 0x80; + break; + } + } + } + + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +#define JIS_HWDAKUTEN 0x5e +#define JIS_HWHANDAKUTEN 0x5f + +static gint conv_jis_hantozen(guchar *outbuf, guchar jis_code, guchar sound_sym) +{ + static guint16 h2z_tbl[] = { + /* 0x20 - 0x2f */ + 0x0000, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521, + 0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543, + /* 0x30 - 0x3f */ + 0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d, + 0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d, + /* 0x40 - 0x4f */ + 0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c, + 0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e, + /* 0x50 - 0x5f */ + 0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569, + 0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c + }; + + static guint16 dakuten_tbl[] = { + /* 0x30 - 0x3f */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x252c, 0x252e, + 0x2530, 0x2532, 0x2534, 0x2536, 0x2538, 0x253a, 0x253c, 0x253e, + /* 0x40 - 0x4f */ + 0x2540, 0x2542, 0x2545, 0x2547, 0x2549, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x2550, 0x2553, 0x2556, 0x2559, 0x255c, 0x0000 + }; + + static guint16 handakuten_tbl[] = { + /* 0x4a - 0x4e */ + 0x2551, 0x2554, 0x2557, 0x255a, 0x255d + }; + + guint16 out_code; + + jis_code &= 0x7f; + sound_sym &= 0x7f; + + if (jis_code < 0x21 || jis_code > 0x5f) + return 0; + + if (sound_sym == JIS_HWDAKUTEN && + jis_code >= 0x36 && jis_code <= 0x4e) { + out_code = dakuten_tbl[jis_code - 0x30]; + if (out_code != 0) { + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 2; + } + } + + if (sound_sym == JIS_HWHANDAKUTEN && + jis_code >= 0x4a && jis_code <= 0x4e) { + out_code = handakuten_tbl[jis_code - 0x4a]; + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 2; + } + + out_code = h2z_tbl[jis_code - 0x20]; + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 1; +} + +static gchar *conv_euctojis(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + JISState state = JIS_ASCII; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 3 + 4); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (isascii(*in)) { + K_OUT(); + *out++ = *in++; + } else if (iseuckanji(*in)) { + if (iseuckanji(*(in + 1))) { + K_IN(); + *out++ = *in++ & 0x7f; + *out++ = *in++ & 0x7f; + } else { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (iseuchwkana1(*in)) { + if (iseuchwkana2(*(in + 1))) { + //if (prefs_common.allow_jisx0201_kana) { + if (0) { + HW_IN(); + in++; + *out++ = *in++ & 0x7f; + } else { + guchar jis_ch[2]; + gint len; + + if (iseuchwkana1(*(in + 2)) && + iseuchwkana2(*(in + 3))) + len = conv_jis_hantozen + (jis_ch, + *(in + 1), *(in + 3)); + else + len = conv_jis_hantozen + (jis_ch, + *(in + 1), '\0'); + if (len == 0) + in += 2; + else { + K_IN(); + in += len * 2; + *out++ = jis_ch[0]; + *out++ = jis_ch[1]; + } + } + } else { + error_ = -1; + K_OUT(); + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (iseucaux(*in)) { + in++; + if (iseuckanji(*in) && iseuckanji(*(in + 1))) { + AUX_IN(); + *out++ = *in++ & 0x7f; + *out++ = *in++ & 0x7f; + } else { + error_ = -1; + K_OUT(); + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } + } else { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + } + } + + K_OUT(); + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static gchar *conv_sjistoeuc(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 2 + 1); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (isascii(*in)) { + *out++ = *in++; + } else if (issjiskanji1(*in)) { + if (issjiskanji2(*(in + 1))) { + guchar out1 = *in; + guchar out2 = *(in + 1); + guchar row; + + row = out1 < 0xa0 ? 0x70 : 0xb0; + if (out2 < 0x9f) { + out1 = (out1 - row) * 2 - 1; + out2 -= out2 > 0x7f ? 0x20 : 0x1f; + } else { + out1 = (out1 - row) * 2; + out2 -= 0x7e; + } + + *out++ = out1 | 0x80; + *out++ = out2 | 0x80; + in += 2; + } else { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (issjishwkana(*in)) { + *out++ = 0x8e; + *out++ = *in++; + } else { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + } + } + + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static gchar *conv_jistoutf8(const gchar *inbuf, gint *error) +{ + gchar *eucstr, *utf8str; + gint e_error = 0, u_error = 0; + + eucstr = conv_jistoeuc(inbuf, &e_error); + utf8str = conv_euctoutf8(eucstr, &u_error); + g_free(eucstr); + + if (error) + *error = (e_error | u_error); + + return utf8str; +} + +static gchar *conv_sjistoutf8(const gchar *inbuf, gint *error) +{ + gchar *utf8str; + + utf8str = conv_iconv_strdup(inbuf, CS_SHIFT_JIS, CS_UTF_8, error); + if (!utf8str) + utf8str = g_strdup(inbuf); + + return utf8str; +} + +static gchar *conv_euctoutf8(const gchar *inbuf, gint *error) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + + if (cd == (iconv_t)-1) { + if (!iconv_ok) { + if (error) + *error = -1; + return g_strdup(inbuf); + } + + cd = iconv_open(CS_UTF_8, CS_EUC_JP_MS); + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_UTF_8, CS_EUC_JP); + if (cd == (iconv_t)-1) { + g_warning("conv_euctoutf8(): %s\n", + g_strerror(errno)); + iconv_ok = FALSE; + if (error) + *error = -1; + return g_strdup(inbuf); + } + } + } + + return conv_iconv_strdup_with_cd(inbuf, cd, error); +} + +static gchar *conv_anytoutf8(const gchar *inbuf, gint *error) +{ + switch (conv_guess_ja_encoding(inbuf)) { + case C_ISO_2022_JP: + return conv_jistoutf8(inbuf, error); + case C_SHIFT_JIS: + return conv_sjistoutf8(inbuf, error); + case C_EUC_JP: + return conv_euctoutf8(inbuf, error); + default: + if (error) + *error = 0; + return g_strdup(inbuf); + } +} + +static gchar *conv_utf8toeuc(const gchar *inbuf, gint *error) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + + if (cd == (iconv_t)-1) { + if (!iconv_ok) { + if (error) + *error = -1; + return g_strdup(inbuf); + } + + cd = iconv_open(CS_EUC_JP_MS, CS_UTF_8); + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_EUC_JP, CS_UTF_8); + if (cd == (iconv_t)-1) { + g_warning("conv_utf8toeuc(): %s\n", + g_strerror(errno)); + iconv_ok = FALSE; + if (error) + *error = -1; + return g_strdup(inbuf); + } + } + } + + return conv_iconv_strdup_with_cd(inbuf, cd, error); +} + +static gchar *conv_utf8tojis(const gchar *inbuf, gint *error) +{ + gchar *eucstr, *jisstr; + gint e_error = 0, j_error = 0; + + eucstr = conv_utf8toeuc(inbuf, &e_error); + jisstr = conv_euctojis(eucstr, &j_error); + g_free(eucstr); + + if (error) + *error = (e_error | j_error); + + return jisstr; +} + +#if 0 +static gchar valid_eucjp_tbl[][96] = { + /* 0xa2a0 - 0xa2ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0 }, + + /* 0xa3a0 - 0xa3ff */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, + + /* 0xa4a0 - 0xa4ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa5a0 - 0xa5ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa6a0 - 0xa6ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa7a0 - 0xa7ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa8a0 - 0xa8ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +static gboolean isprintableeuckanji(guchar c1, guchar c2) +{ + if (c1 <= 0xa0 || c1 >= 0xf5) + return FALSE; + if (c2 <= 0xa0 || c2 == 0xff) + return FALSE; + + if (c1 >= 0xa9 && c1 <= 0xaf) + return FALSE; + + if (c1 >= 0xa2 && c1 <= 0xa8) + return (gboolean)valid_eucjp_tbl[c1 - 0xa2][c2 - 0xa0]; + + if (c1 == 0xcf) { + if (c2 >= 0xd4 && c2 <= 0xfe) + return FALSE; + } else if (c1 == 0xf4) { + if (c2 >= 0xa7 && c2 <= 0xfe) + return FALSE; + } + + return TRUE; +} + +static void conv_unreadable_eucjp(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + if (isascii(*p)) { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + /* printable 7 bit code */ + p++; + } else if (iseuckanji(*p)) { + if (isprintableeuckanji(*p, *(p + 1))) { + /* printable euc-jp code */ + p += 2; + } else { + /* substitute unprintable code */ + *p++ = SUBST_CHAR; + if (*p != '\0') { + if (isascii(*p)) + p++; + else + *p++ = SUBST_CHAR; + } + } + } else if (iseuchwkana1(*p)) { + if (iseuchwkana2(*(p + 1))) + /* euc-jp hankaku kana */ + p += 2; + else + *p++ = SUBST_CHAR; + } else if (iseucaux(*p)) { + if (iseuckanji(*(p + 1)) && iseuckanji(*(p + 2))) { + /* auxiliary kanji */ + p += 3; + } else + *p++ = SUBST_CHAR; + } else + /* substitute unprintable 1 byte code */ + *p++ = SUBST_CHAR; + } +} +#endif + +static void conv_unreadable_8bit(gchar *str) +{ + register gchar *p = str; + + while (*p != '\0') { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + else if (!isascii(*(guchar *)p)) *p = SUBST_CHAR; + p++; + } +} + +#if 0 +static void conv_unreadable_latin(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + else if ((*p & 0xff) >= 0x7f && (*p & 0xff) <= 0x9f) + *p = SUBST_CHAR; + p++; + } +} +#endif + +#define NCV '\0' + +void conv_mb_alnum(gchar *str) +{ + static guchar char_tbl[] = { + /* 0xa0 - 0xaf */ + NCV, ' ', NCV, NCV, ',', '.', NCV, ':', + ';', '?', '!', NCV, NCV, NCV, NCV, NCV, + /* 0xb0 - 0xbf */ + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + /* 0xc0 - 0xcf */ + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, '(', ')', NCV, NCV, '[', ']', + /* 0xd0 - 0xdf */ + '{', '}', NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, NCV, NCV, '+', '-', NCV, NCV, + /* 0xe0 - 0xef */ + NCV, '=', NCV, '<', '>', NCV, NCV, NCV, + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV + }; + + register guchar *p = (guchar *)str; + register gint len; + + len = strlen(str); + + while (len > 1) { + if (*p == 0xa3) { + register guchar ch = *(p + 1); + + if (ch >= 0xb0 && ch <= 0xfa) { + /* [a-zA-Z] */ + *p = ch & 0x7f; + p++; + len--; + memmove(p, p + 1, len); + len--; + } else { + p += 2; + len -= 2; + } + } else if (*p == 0xa1) { + register guchar ch = *(p + 1); + + if (ch >= 0xa0 && ch <= 0xef && + NCV != char_tbl[ch - 0xa0]) { + *p = char_tbl[ch - 0xa0]; + p++; + len--; + memmove(p, p + 1, len); + len--; + } else { + p += 2; + len -= 2; + } + } else if (iseuckanji(*p)) { + p += 2; + len -= 2; + } else { + p++; + len--; + } + } +} + +CharSet conv_guess_ja_encoding(const gchar *str) +{ + const guchar *p = (const guchar *)str; + CharSet guessed = C_US_ASCII; + + while (*p != '\0') { + if (*p == ESC && (*(p + 1) == '$' || *(p + 1) == '(')) { + if (guessed == C_US_ASCII) + return C_ISO_2022_JP; + p += 2; + } else if (isascii(*p)) { + p++; + } else if (iseuckanji(*p) && iseuckanji(*(p + 1))) { + if (*p >= 0xfd && *p <= 0xfe) + return C_EUC_JP; + else if (guessed == C_SHIFT_JIS) { + if ((issjiskanji1(*p) && + issjiskanji2(*(p + 1))) || + issjishwkana(*p)) + guessed = C_SHIFT_JIS; + else + guessed = C_EUC_JP; + } else + guessed = C_EUC_JP; + p += 2; + } else if (issjiskanji1(*p) && issjiskanji2(*(p + 1))) { + if (iseuchwkana1(*p) && iseuchwkana2(*(p + 1))) + guessed = C_SHIFT_JIS; + else + return C_SHIFT_JIS; + p += 2; + } else if (issjishwkana(*p)) { + guessed = C_SHIFT_JIS; + p++; + } else { + p++; + } + } + + return guessed; +} + +static gchar *conv_jistodisp(const gchar *inbuf, gint *error) +{ + return conv_jistoutf8(inbuf, error); +} + +static gchar *conv_sjistodisp(const gchar *inbuf, gint *error) +{ + return conv_sjistoutf8(inbuf, error); +} + +static gchar *conv_euctodisp(const gchar *inbuf, gint *error) +{ + return conv_euctoutf8(inbuf, error); +} + +gchar *conv_utf8todisp(const gchar *inbuf, gint *error) +{ + if (g_utf8_validate(inbuf, -1, NULL) == TRUE) { + if (error) + *error = 0; + return g_strdup(inbuf); + } else + return conv_ustodisp(inbuf, error); +} + +static gchar *conv_anytodisp(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + + outbuf = conv_anytoutf8(inbuf, error); + if (g_utf8_validate(outbuf, -1, NULL) != TRUE) { + if (error) + *error = -1; + conv_unreadable_8bit(outbuf); + } + + return outbuf; +} + +static gchar *conv_ustodisp(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + + outbuf = g_strdup(inbuf); + conv_unreadable_8bit(outbuf); + if (error) + *error = 0; + + return outbuf; +} + +gchar *conv_localetodisp(const gchar *inbuf, gint *error) +{ + gchar *str; + + str = conv_iconv_strdup(inbuf, conv_get_locale_charset_str(), + CS_INTERNAL, error); + if (!str) + str = conv_utf8todisp(inbuf, NULL); + + return str; +} + +static gchar *conv_noconv(const gchar *inbuf, gint *error) +{ + if (error) + *error = 0; + return g_strdup(inbuf); +} + +static const gchar * +conv_get_fallback_for_private_encoding(const gchar *encoding) +{ + if (encoding && (encoding[0] == 'X' || encoding[0] == 'x') && + encoding[1] == '-') { + if (!g_ascii_strcasecmp(encoding, CS_X_GBK)) + return CS_GBK; + } + + return encoding; +} + +CodeConverter *conv_code_converter_new(const gchar *src_encoding, + const gchar *dest_encoding) +{ + CodeConverter *conv; + + src_encoding = conv_get_fallback_for_private_encoding(src_encoding); + + conv = g_new0(CodeConverter, 1); + conv->code_conv_func = + conv_get_code_conv_func(src_encoding, dest_encoding); + conv->src_encoding = g_strdup(src_encoding); + conv->dest_encoding = g_strdup(dest_encoding); + + return conv; +} + +void conv_code_converter_destroy(CodeConverter *conv) +{ + g_free(conv->src_encoding); + g_free(conv->dest_encoding); + g_free(conv); +} + +gchar *conv_convert(CodeConverter *conv, const gchar *inbuf) +{ + if (conv->code_conv_func != conv_noconv) + return conv->code_conv_func(inbuf, NULL); + else + return conv_iconv_strdup + (inbuf, conv->src_encoding, conv->dest_encoding, NULL); +} + +gchar *conv_codeset_strdup_full(const gchar *inbuf, + const gchar *src_encoding, + const gchar *dest_encoding, + gint *error) +{ + CodeConvFunc conv_func; + + src_encoding = conv_get_fallback_for_private_encoding(src_encoding); + + conv_func = conv_get_code_conv_func(src_encoding, dest_encoding); + if (conv_func != conv_noconv) + return conv_func(inbuf, error); + + return conv_iconv_strdup(inbuf, src_encoding, dest_encoding, error); +} + +CodeConvFunc conv_get_code_conv_func(const gchar *src_encoding, + const gchar *dest_encoding) +{ + CodeConvFunc code_conv = conv_noconv; + CharSet src_charset; + CharSet dest_charset; + + if (!src_encoding) + src_charset = conv_get_locale_charset(); + else + src_charset = conv_get_charset_from_str(src_encoding); + + /* auto detection mode */ + if (!src_encoding && !dest_encoding) { + if (conv_is_ja_locale()) + return conv_anytodisp; + else + return conv_noconv; + } + + dest_charset = conv_get_charset_from_str(dest_encoding); + + if (dest_charset == C_US_ASCII) + return conv_ustodisp; + + switch (src_charset) { + case C_US_ASCII: + case C_ISO_8859_1: + case C_ISO_8859_2: + case C_ISO_8859_3: + case C_ISO_8859_4: + case C_ISO_8859_5: + case C_ISO_8859_6: + case C_ISO_8859_7: + case C_ISO_8859_8: + case C_ISO_8859_9: + case C_ISO_8859_10: + case C_ISO_8859_11: + case C_ISO_8859_13: + case C_ISO_8859_14: + case C_ISO_8859_15: + break; + case C_ISO_2022_JP: + case C_ISO_2022_JP_2: + case C_ISO_2022_JP_3: + if (dest_charset == C_AUTO) + code_conv = conv_jistodisp; + else if (dest_charset == C_EUC_JP) + code_conv = conv_jistoeuc; + else if (dest_charset == C_UTF_8) + code_conv = conv_jistoutf8; + break; + case C_SHIFT_JIS: + if (dest_charset == C_AUTO) + code_conv = conv_sjistodisp; + else if (dest_charset == C_EUC_JP) + code_conv = conv_sjistoeuc; + else if (dest_charset == C_UTF_8) + code_conv = conv_sjistoutf8; + break; + case C_EUC_JP: + if (dest_charset == C_AUTO) + code_conv = conv_euctodisp; + else if (dest_charset == C_ISO_2022_JP || + dest_charset == C_ISO_2022_JP_2 || + dest_charset == C_ISO_2022_JP_3) + code_conv = conv_euctojis; + else if (dest_charset == C_UTF_8) + code_conv = conv_euctoutf8; + break; + case C_UTF_8: + if (dest_charset == C_EUC_JP) + code_conv = conv_utf8toeuc; + else if (dest_charset == C_ISO_2022_JP || + dest_charset == C_ISO_2022_JP_2 || + dest_charset == C_ISO_2022_JP_3) + code_conv = conv_utf8tojis; + break; + default: + break; + } + + return code_conv; +} + +gchar *conv_iconv_strdup(const gchar *inbuf, + const gchar *src_code, const gchar *dest_code, + gint *error) +{ + iconv_t cd; + gchar *outbuf; + + if (!src_code) + src_code = conv_get_locale_charset_str(); + if (!dest_code) + dest_code = CS_INTERNAL; + + cd = iconv_open(dest_code, src_code); + if (cd == (iconv_t)-1) { + if (error) + *error = -1; + return NULL; + } + + outbuf = conv_iconv_strdup_with_cd(inbuf, cd, error); + + iconv_close(cd); + + return outbuf; +} + +gchar *conv_iconv_strdup_with_cd(const gchar *inbuf, iconv_t cd, gint *error) +{ + const gchar *inbuf_p; + gchar *outbuf; + gchar *outbuf_p; + size_t in_size; + size_t in_left; + size_t out_size; + size_t out_left; + size_t n_conv; + size_t len; + gint error_ = 0; + + inbuf_p = inbuf; + in_size = strlen(inbuf); + in_left = in_size; + out_size = (in_size + 1) * 2; + outbuf = g_malloc(out_size); + outbuf_p = outbuf; + out_left = out_size; + +#define EXPAND_BUF() \ +{ \ + len = outbuf_p - outbuf; \ + out_size *= 2; \ + outbuf = g_realloc(outbuf, out_size); \ + outbuf_p = outbuf + len; \ + out_left = out_size - len; \ +} + + while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left, + &outbuf_p, &out_left)) == (size_t)-1) { + if (EILSEQ == errno) { + /* g_print("iconv(): at %d: %s\n", in_size - in_left, g_strerror(errno)); */ + error_ = -1; + inbuf_p++; + in_left--; + if (out_left == 0) { + EXPAND_BUF(); + } + *outbuf_p++ = SUBST_CHAR; + out_left--; + } else if (EINVAL == errno) { + error_ = -1; + break; + } else if (E2BIG == errno) { + EXPAND_BUF(); + } else { + g_warning("conv_iconv_strdup(): %s\n", + g_strerror(errno)); + error_ = -1; + break; + } + } + + while ((n_conv = iconv(cd, NULL, NULL, &outbuf_p, &out_left)) == + (size_t)-1) { + if (E2BIG == errno) { + EXPAND_BUF(); + } else { + g_warning("conv_iconv_strdup(): %s\n", + g_strerror(errno)); + error_ = -1; + break; + } + } + +#undef EXPAND_BUF + + len = outbuf_p - outbuf; + outbuf = g_realloc(outbuf, len + 1); + outbuf[len] = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static const struct { + CharSet charset; + gchar *const name; +} charsets[] = { + {C_US_ASCII, CS_US_ASCII}, + {C_US_ASCII, CS_ANSI_X3_4_1968}, + {C_UTF_8, CS_UTF_8}, + {C_UTF_7, CS_UTF_7}, + {C_ISO_8859_1, CS_ISO_8859_1}, + {C_ISO_8859_2, CS_ISO_8859_2}, + {C_ISO_8859_3, CS_ISO_8859_3}, + {C_ISO_8859_4, CS_ISO_8859_4}, + {C_ISO_8859_5, CS_ISO_8859_5}, + {C_ISO_8859_6, CS_ISO_8859_6}, + {C_ISO_8859_7, CS_ISO_8859_7}, + {C_ISO_8859_8, CS_ISO_8859_8}, + {C_ISO_8859_9, CS_ISO_8859_9}, + {C_ISO_8859_10, CS_ISO_8859_10}, + {C_ISO_8859_11, CS_ISO_8859_11}, + {C_ISO_8859_13, CS_ISO_8859_13}, + {C_ISO_8859_14, CS_ISO_8859_14}, + {C_ISO_8859_15, CS_ISO_8859_15}, + {C_BALTIC, CS_BALTIC}, + {C_CP1250, CS_CP1250}, + {C_CP1251, CS_CP1251}, + {C_CP1252, CS_CP1252}, + {C_CP1253, CS_CP1253}, + {C_CP1254, CS_CP1254}, + {C_CP1255, CS_CP1255}, + {C_CP1256, CS_CP1256}, + {C_CP1257, CS_CP1257}, + {C_CP1258, CS_CP1258}, + {C_WINDOWS_1250, CS_WINDOWS_1250}, + {C_WINDOWS_1251, CS_WINDOWS_1251}, + {C_WINDOWS_1252, CS_WINDOWS_1252}, + {C_WINDOWS_1253, CS_WINDOWS_1253}, + {C_WINDOWS_1254, CS_WINDOWS_1254}, + {C_WINDOWS_1255, CS_WINDOWS_1255}, + {C_WINDOWS_1256, CS_WINDOWS_1256}, + {C_WINDOWS_1257, CS_WINDOWS_1257}, + {C_WINDOWS_1258, CS_WINDOWS_1258}, + {C_KOI8_R, CS_KOI8_R}, + {C_KOI8_T, CS_KOI8_T}, + {C_KOI8_U, CS_KOI8_U}, + {C_ISO_2022_JP, CS_ISO_2022_JP}, + {C_ISO_2022_JP_2, CS_ISO_2022_JP_2}, + {C_ISO_2022_JP_3, CS_ISO_2022_JP_3}, + {C_EUC_JP, CS_EUC_JP}, + {C_EUC_JP, CS_EUCJP}, + {C_EUC_JP_MS, CS_EUC_JP_MS}, + {C_SHIFT_JIS, CS_SHIFT_JIS}, + {C_SHIFT_JIS, CS_SHIFT__JIS}, + {C_SHIFT_JIS, CS_SJIS}, + {C_ISO_2022_KR, CS_ISO_2022_KR}, + {C_EUC_KR, CS_EUC_KR}, + {C_ISO_2022_CN, CS_ISO_2022_CN}, + {C_EUC_CN, CS_EUC_CN}, + {C_GB2312, CS_GB2312}, + {C_GBK, CS_GBK}, + {C_EUC_TW, CS_EUC_TW}, + {C_BIG5, CS_BIG5}, + {C_BIG5_HKSCS, CS_BIG5_HKSCS}, + {C_TIS_620, CS_TIS_620}, + {C_WINDOWS_874, CS_WINDOWS_874}, + {C_GEORGIAN_PS, CS_GEORGIAN_PS}, + {C_TCVN5712_1, CS_TCVN5712_1}, +}; + +static const struct { + gchar *const locale; + CharSet charset; + CharSet out_charset; +} locale_table[] = { + {"ja_JP.eucJP" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.EUC-JP" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.EUC" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.ujis" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.SJIS" , C_SHIFT_JIS , C_ISO_2022_JP}, + {"ja_JP.JIS" , C_ISO_2022_JP , C_ISO_2022_JP}, +#ifdef G_OS_WIN32 + {"ja_JP" , C_SHIFT_JIS , C_ISO_2022_JP}, +#else + {"ja_JP" , C_EUC_JP , C_ISO_2022_JP}, +#endif + {"ko_KR.EUC-KR" , C_EUC_KR , C_EUC_KR}, + {"ko_KR" , C_EUC_KR , C_EUC_KR}, + {"zh_CN.GB2312" , C_GB2312 , C_GB2312}, + {"zh_CN.GBK" , C_GBK , C_GBK}, + {"zh_CN" , C_GB2312 , C_GB2312}, + {"zh_HK" , C_BIG5_HKSCS , C_BIG5_HKSCS}, + {"zh_TW.eucTW" , C_EUC_TW , C_BIG5}, + {"zh_TW.EUC-TW" , C_EUC_TW , C_BIG5}, + {"zh_TW.Big5" , C_BIG5 , C_BIG5}, + {"zh_TW" , C_BIG5 , C_BIG5}, + + {"ru_RU.KOI8-R" , C_KOI8_R , C_KOI8_R}, + {"ru_RU.KOI8R" , C_KOI8_R , C_KOI8_R}, + {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R}, + {"ru_RU" , C_ISO_8859_5 , C_KOI8_R}, + {"tg_TJ" , C_KOI8_T , C_KOI8_T}, + {"ru_UA" , C_KOI8_U , C_KOI8_U}, + {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U}, + {"uk_UA" , C_KOI8_U , C_KOI8_U}, + + {"be_BY" , C_WINDOWS_1251, C_WINDOWS_1251}, + {"bg_BG" , C_WINDOWS_1251, C_WINDOWS_1251}, + + {"yi_US" , C_WINDOWS_1255, C_WINDOWS_1255}, + + {"af_ZA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"br_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ca_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"da_DK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_AT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_DE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_LU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_AU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_BW" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_CA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_DK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_HK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_IE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_NZ" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_PH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_SG" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_US" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_ZA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_ZW" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_AR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_BO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_DO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_EC" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_GT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_HN" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_MX" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_NI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_SV" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_US" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_UY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_VE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"et_EE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"eu_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fi_FI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fo_FO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_CA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_LU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ga_IE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"gl_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"gv_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"id_ID" , C_ISO_8859_1 , C_ISO_8859_1}, + {"is_IS" , C_ISO_8859_1 , C_ISO_8859_1}, + {"it_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"it_IT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"kl_GL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"kw_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ms_MY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nl_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nl_NL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nn_NO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"no_NO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"oc_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"pt_BR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"pt_PT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sq_AL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sv_FI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sv_SE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"tl_PH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"uz_UZ" , C_ISO_8859_1 , C_ISO_8859_1}, + {"wa_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + + {"bs_BA" , C_ISO_8859_2 , C_ISO_8859_2}, + {"cs_CZ" , C_ISO_8859_2 , C_ISO_8859_2}, + {"hr_HR" , C_ISO_8859_2 , C_ISO_8859_2}, + {"hu_HU" , C_ISO_8859_2 , C_ISO_8859_2}, + {"pl_PL" , C_ISO_8859_2 , C_ISO_8859_2}, + {"ro_RO" , C_ISO_8859_2 , C_ISO_8859_2}, + {"sk_SK" , C_ISO_8859_2 , C_ISO_8859_2}, + {"sl_SI" , C_ISO_8859_2 , C_ISO_8859_2}, + + {"sr_YU@cyrillic" , C_ISO_8859_5 , C_ISO_8859_5}, + {"sr_YU" , C_ISO_8859_2 , C_ISO_8859_2}, + + {"mt_MT" , C_ISO_8859_3 , C_ISO_8859_3}, + + {"lt_LT.iso88594" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT.ISO8859-4" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT.ISO_8859-4" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT" , C_ISO_8859_13 , C_ISO_8859_13}, + + {"mk_MK" , C_ISO_8859_5 , C_ISO_8859_5}, + + {"ar_AE" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_BH" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_DZ" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_EG" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_IQ" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_JO" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_KW" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_LB" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_LY" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_MA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_OM" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_QA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SD" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SY" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_TN" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_YE" , C_ISO_8859_6 , C_ISO_8859_6}, + + {"el_GR" , C_ISO_8859_7 , C_ISO_8859_7}, + {"he_IL" , C_ISO_8859_8 , C_ISO_8859_8}, + {"iw_IL" , C_ISO_8859_8 , C_ISO_8859_8}, + {"tr_TR" , C_ISO_8859_9 , C_ISO_8859_9}, + + {"lv_LV" , C_ISO_8859_13 , C_ISO_8859_13}, + {"mi_NZ" , C_ISO_8859_13 , C_ISO_8859_13}, + + {"cy_GB" , C_ISO_8859_14 , C_ISO_8859_14}, + + {"ar_IN" , C_UTF_8 , C_UTF_8}, + {"en_IN" , C_UTF_8 , C_UTF_8}, + {"se_NO" , C_UTF_8 , C_UTF_8}, + {"ta_IN" , C_UTF_8 , C_UTF_8}, + {"te_IN" , C_UTF_8 , C_UTF_8}, + {"ur_PK" , C_UTF_8 , C_UTF_8}, + + {"th_TH" , C_TIS_620 , C_TIS_620}, + /* {"th_TH" , C_WINDOWS_874}, */ + /* {"th_TH" , C_ISO_8859_11}, */ + + {"ka_GE" , C_GEORGIAN_PS , C_GEORGIAN_PS}, + {"vi_VN.TCVN" , C_TCVN5712_1 , C_TCVN5712_1}, + + {"C" , C_US_ASCII , C_US_ASCII}, + {"POSIX" , C_US_ASCII , C_US_ASCII}, + {"ANSI_X3.4-1968" , C_US_ASCII , C_US_ASCII}, +}; + +static GHashTable *conv_get_charset_to_str_table(void) +{ + static GHashTable *table; + gint i; + + if (table) + return table; + + table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) { + if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset)) + == NULL) { + g_hash_table_insert + (table, GUINT_TO_POINTER(charsets[i].charset), + charsets[i].name); + } + } + + return table; +} + +static GHashTable *conv_get_charset_from_str_table(void) +{ + static GHashTable *table; + gint i; + + if (table) + return table; + + table = g_hash_table_new(str_case_hash, str_case_equal); + + for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) { + g_hash_table_insert(table, charsets[i].name, + GUINT_TO_POINTER(charsets[i].charset)); + } + + return table; +} + +const gchar *conv_get_charset_str(CharSet charset) +{ + GHashTable *table; + + table = conv_get_charset_to_str_table(); + return g_hash_table_lookup(table, GUINT_TO_POINTER(charset)); +} + +CharSet conv_get_charset_from_str(const gchar *charset) +{ + GHashTable *table; + + if (!charset) return C_AUTO; + + table = conv_get_charset_from_str_table(); + return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset)); +} + +CharSet conv_get_locale_charset(void) +{ + static CharSet cur_charset = -1; + const gchar *cur_locale; + const gchar *p; + gint i; + + if (cur_charset != -1) + return cur_charset; + + cur_locale = conv_get_current_locale(); + if (!cur_locale) { + cur_charset = C_US_ASCII; + return cur_charset; + } + + if (strcasestr(cur_locale, "UTF-8")) { + cur_charset = C_UTF_8; + return cur_charset; + } + + if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') { + cur_charset = C_ISO_8859_15; + return cur_charset; + } + + for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) { + const gchar *p; + + /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and + "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */ + if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale, + strlen(locale_table[i].locale))) { + cur_charset = locale_table[i].charset; + return cur_charset; + } else if ((p = strchr(locale_table[i].locale, '_')) && + !strchr(p + 1, '.')) { + if (strlen(cur_locale) == 2 && + !g_ascii_strncasecmp(cur_locale, + locale_table[i].locale, 2)) { + cur_charset = locale_table[i].charset; + return cur_charset; + } + } + } + + cur_charset = C_AUTO; + return cur_charset; +} + +const gchar *conv_get_locale_charset_str(void) +{ + static const gchar *codeset = NULL; + + if (!codeset) + codeset = conv_get_charset_str(conv_get_locale_charset()); + + return codeset ? codeset : CS_INTERNAL; +} + +CharSet conv_get_internal_charset(void) +{ + return C_INTERNAL; +} + +const gchar *conv_get_internal_charset_str(void) +{ + return CS_INTERNAL; +} + +CharSet conv_get_outgoing_charset(void) +{ + static CharSet out_charset = -1; + const gchar *cur_locale; + const gchar *p; + gint i; + + if (out_charset != -1) + return out_charset; + + cur_locale = conv_get_current_locale(); + if (!cur_locale) { + out_charset = C_AUTO; + return out_charset; + } + + if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') { + out_charset = C_ISO_8859_15; + return out_charset; + } + + for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) { + const gchar *p; + + if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale, + strlen(locale_table[i].locale))) { + out_charset = locale_table[i].out_charset; + break; + } else if ((p = strchr(locale_table[i].locale, '_')) && + !strchr(p + 1, '.')) { + if (strlen(cur_locale) == 2 && + !g_ascii_strncasecmp(cur_locale, + locale_table[i].locale, 2)) { + out_charset = locale_table[i].out_charset; + break; + } + } + } + + return out_charset; +} + +const gchar *conv_get_outgoing_charset_str(void) +{ + CharSet out_charset; + const gchar *str; + + out_charset = conv_get_outgoing_charset(); + str = conv_get_charset_str(out_charset); + + return str ? str : CS_UTF_8; +} + +gboolean conv_is_multibyte_encoding(CharSet encoding) +{ + switch (encoding) { + case C_EUC_JP: + case C_EUC_JP_MS: + case C_EUC_KR: + case C_EUC_TW: + case C_EUC_CN: + case C_ISO_2022_JP: + case C_ISO_2022_JP_2: + case C_ISO_2022_JP_3: + case C_ISO_2022_KR: + case C_ISO_2022_CN: + case C_SHIFT_JIS: + case C_GB2312: + case C_GBK: + case C_BIG5: + case C_UTF_8: + case C_UTF_7: + return TRUE; + default: + return FALSE; + } +} + +const gchar *conv_get_current_locale(void) +{ + static const gchar *cur_locale; + + if (!cur_locale) { +#ifdef G_OS_WIN32 + cur_locale = g_win32_getlocale(); +#else + cur_locale = g_getenv("LC_ALL"); + if (!cur_locale) cur_locale = g_getenv("LC_CTYPE"); + if (!cur_locale) cur_locale = g_getenv("LANG"); + if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL); +#endif /* G_OS_WIN32 */ + + debug_print("current locale: %s\n", + cur_locale ? cur_locale : "(none)"); + } + + return cur_locale; +} + +gboolean conv_is_ja_locale(void) +{ + static gint is_ja_locale = -1; + const gchar *cur_locale; + + if (is_ja_locale != -1) + return is_ja_locale != 0; + + is_ja_locale = 0; + cur_locale = conv_get_current_locale(); + if (cur_locale) { + if (g_ascii_strncasecmp(cur_locale, "ja", 2) == 0) + is_ja_locale = 1; + } + + return is_ja_locale != 0; +} + +gchar *conv_unmime_header(const gchar *str, const gchar *default_encoding) +{ + gchar *buf; + gchar *decoded_str; + + if (is_ascii_str(str)) + return unmime_header(str); + + if (default_encoding) { + buf = conv_codeset_strdup + (str, default_encoding, CS_INTERNAL); + if (buf) { + decoded_str = unmime_header(buf); + g_free(buf); + return decoded_str; + } + } + + if (conv_is_ja_locale()) + buf = conv_anytodisp(str, NULL); + else + buf = conv_localetodisp(str, NULL); + + decoded_str = unmime_header(buf); + g_free(buf); + + return decoded_str; +} + +#define MAX_LINELEN 76 +#define MAX_HARD_LINELEN 996 +#define MIMESEP_BEGIN "=?" +#define MIMESEP_END "?=" + +#define B64LEN(len) ((len) / 3 * 4 + ((len) % 3 ? 4 : 0)) + +#define LBREAK_IF_REQUIRED(cond, is_plain_text) \ +{ \ + if (len - (destp - dest) < MAX_LINELEN + 2) { \ + *destp = '\0'; \ + return; \ + } \ + \ + if ((cond) && *srcp) { \ + if (destp > dest && left < MAX_LINELEN - 1) { \ + if (g_ascii_isspace(*(destp - 1))) \ + destp--; \ + else if (is_plain_text && \ + g_ascii_isspace(*srcp)) \ + srcp++; \ + if (*srcp) { \ + *destp++ = '\n'; \ + *destp++ = ' '; \ + left = MAX_LINELEN - 1; \ + } \ + } \ + } \ +} + +void conv_encode_header(gchar *dest, gint len, const gchar *src, + gint header_len, gboolean addr_field, + const gchar *out_encoding) +{ + const gchar *cur_encoding; + gint mimestr_len; + gchar *mimesep_enc; + gint left; + const gchar *srcp = src; + gchar *destp = dest; + gboolean use_base64; + + g_return_if_fail(g_utf8_validate(src, -1, NULL) == TRUE); + + if (MB_CUR_MAX > 1) { + use_base64 = TRUE; + mimesep_enc = "?B?"; + } else { + use_base64 = FALSE; + mimesep_enc = "?Q?"; + } + + cur_encoding = CS_INTERNAL; + if (!out_encoding) + out_encoding = conv_get_outgoing_charset_str(); + if (!strcmp(out_encoding, CS_US_ASCII)) + out_encoding = CS_ISO_8859_1; + + mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) + + strlen(mimesep_enc) + strlen(MIMESEP_END); + + left = MAX_LINELEN - header_len; + + while (*srcp) { + LBREAK_IF_REQUIRED(left <= 0, TRUE); + + while (g_ascii_isspace(*srcp)) { + *destp++ = *srcp++; + left--; + LBREAK_IF_REQUIRED(left <= 0, TRUE); + } + + /* output as it is if the next word is ASCII string */ + if (!is_next_nonascii(srcp)) { + gint word_len; + + word_len = get_next_word_len(srcp); + LBREAK_IF_REQUIRED(left < word_len, TRUE); + while (word_len > 0) { + LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE) + *destp++ = *srcp++; + left--; + word_len--; + } + + continue; + } + + /* don't include parentheses in encoded strings */ + if (addr_field && (*srcp == '(' || *srcp == ')')) { + LBREAK_IF_REQUIRED(left < 2, FALSE); + *destp++ = *srcp++; + left--; + } + + while (1) { + gint mb_len = 0; + gint cur_len = 0; + gchar *part_str; + gchar *out_str; + gchar *enc_str; + const gchar *p = srcp; + gint out_str_len; + gint out_enc_str_len; + gint mime_block_len; + gboolean cont = FALSE; + + while (*p != '\0') { + if (g_ascii_isspace(*p) && + !is_next_nonascii(p + 1)) + break; + /* don't include parentheses in encoded + strings */ + if (addr_field && (*p == '(' || *p == ')')) + break; + + mb_len = g_utf8_skip[*(guchar *)p]; + + Xstrndup_a(part_str, srcp, cur_len + mb_len, ); + out_str = conv_codeset_strdup + (part_str, cur_encoding, out_encoding); + if (!out_str) { + g_warning("conv_encode_header(): code conversion failed\n"); + conv_unreadable_8bit(part_str); + out_str = g_strdup(part_str); + } + out_str_len = strlen(out_str); + + if (use_base64) + out_enc_str_len = B64LEN(out_str_len); + else + out_enc_str_len = + qp_get_q_encoding_len + ((guchar *)out_str); + + g_free(out_str); + + if (mimestr_len + out_enc_str_len <= left) { + cur_len += mb_len; + p += mb_len; + } else if (cur_len == 0) { + LBREAK_IF_REQUIRED(1, FALSE); + continue; + } else { + cont = TRUE; + break; + } + } + + if (cur_len > 0) { + Xstrndup_a(part_str, srcp, cur_len, ); + out_str = conv_codeset_strdup + (part_str, cur_encoding, out_encoding); + if (!out_str) { + g_warning("conv_encode_header(): code conversion failed\n"); + conv_unreadable_8bit(part_str); + out_str = g_strdup(part_str); + } + out_str_len = strlen(out_str); + + if (use_base64) + out_enc_str_len = B64LEN(out_str_len); + else + out_enc_str_len = + qp_get_q_encoding_len + ((guchar *)out_str); + + Xalloca(enc_str, out_enc_str_len + 1, ); + if (use_base64) + base64_encode(enc_str, + (guchar *)out_str, + out_str_len); + else + qp_q_encode(enc_str, (guchar *)out_str); + + g_free(out_str); + + /* output MIME-encoded string block */ + mime_block_len = mimestr_len + strlen(enc_str); + g_snprintf(destp, mime_block_len + 1, + MIMESEP_BEGIN "%s%s%s" MIMESEP_END, + out_encoding, mimesep_enc, enc_str); + destp += mime_block_len; + srcp += cur_len; + + left -= mime_block_len; + } + + LBREAK_IF_REQUIRED(cont, FALSE); + + if (cur_len == 0) + break; + } + } + + *destp = '\0'; +} + +#undef LBREAK_IF_REQUIRED + +gint conv_copy_file(const gchar *src, const gchar *dest, const gchar *encoding) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + CodeConverter *conv; + gboolean err = FALSE; + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + conv = conv_code_converter_new(encoding, NULL); + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gchar *outbuf; + + outbuf = conv_convert(conv, buf); + if (outbuf) { + fputs(outbuf, dest_fp); + g_free(outbuf); + } else + fputs(buf, dest_fp); + } + + conv_code_converter_destroy(conv); + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +gint conv_copy_dir(const gchar *src, const gchar *dest, const gchar *encoding) +{ + GDir *dir; + const gchar *dir_name; + gchar *src_file; + gchar *dest_file; + + if ((dir = g_dir_open(src, 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", src); + return -1; + } + + if (make_dir_hier(dest) < 0) { + g_dir_close(dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dir)) != NULL) { + src_file = g_strconcat(src, G_DIR_SEPARATOR_S, dir_name, NULL); + dest_file = g_strconcat(dest, G_DIR_SEPARATOR_S, dir_name, + NULL); + if (is_file_exist(src_file)) + conv_copy_file(src_file, dest_file, encoding); + g_free(dest_file); + g_free(src_file); + } + + g_dir_close(dir); + + return 0; +} + +gchar *conv_filename_from_utf8(const gchar *utf8_file) +{ + gchar *fs_file; + GError *error = NULL; + + fs_file = g_filename_from_utf8(utf8_file, -1, NULL, NULL, &error); + if (error) { + g_warning("failed to convert encoding of file name: %s\n", + error->message); + g_error_free(error); + } + if (!fs_file) + fs_file = g_strdup(utf8_file); + + return fs_file; +} + +gchar *conv_filename_to_utf8(const gchar *fs_file) +{ + gchar *utf8_file; + GError *error = NULL; + + utf8_file = g_filename_to_utf8(fs_file, -1, NULL, NULL, &error); + if (error) { + g_warning("failed to convert encoding of file name: %s\n", + error->message); + g_error_free(error); + } + if (!utf8_file) + utf8_file = g_strdup(fs_file); + + return utf8_file; +} diff --git a/libsylph/codeconv.h b/libsylph/codeconv.h new file mode 100644 index 00000000..833b1402 --- /dev/null +++ b/libsylph/codeconv.h @@ -0,0 +1,239 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __CODECONV_H__ +#define __CODECONV_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <iconv.h> + +typedef struct _CodeConverter CodeConverter; + +typedef enum +{ + C_AUTO, + C_US_ASCII, + C_UTF_8, + C_UTF_7, + C_ISO_8859_1, + C_ISO_8859_2, + C_ISO_8859_3, + C_ISO_8859_4, + C_ISO_8859_5, + C_ISO_8859_6, + C_ISO_8859_7, + C_ISO_8859_8, + C_ISO_8859_9, + C_ISO_8859_10, + C_ISO_8859_11, + C_ISO_8859_13, + C_ISO_8859_14, + C_ISO_8859_15, + C_BALTIC, + C_CP1250, + C_CP1251, + C_CP1252, + C_CP1253, + C_CP1254, + C_CP1255, + C_CP1256, + C_CP1257, + C_CP1258, + C_WINDOWS_1250, + C_WINDOWS_1251, + C_WINDOWS_1252, + C_WINDOWS_1253, + C_WINDOWS_1254, + C_WINDOWS_1255, + C_WINDOWS_1256, + C_WINDOWS_1257, + C_WINDOWS_1258, + C_KOI8_R, + C_KOI8_T, + C_KOI8_U, + C_ISO_2022_JP, + C_ISO_2022_JP_2, + C_ISO_2022_JP_3, + C_EUC_JP, + C_EUC_JP_MS, + C_SHIFT_JIS, + C_ISO_2022_KR, + C_EUC_KR, + C_ISO_2022_CN, + C_EUC_CN, + C_GB2312, + C_GBK, + C_EUC_TW, + C_BIG5, + C_BIG5_HKSCS, + C_TIS_620, + C_WINDOWS_874, + C_GEORGIAN_PS, + C_TCVN5712_1 +} CharSet; + +typedef gchar *(*CodeConvFunc) (const gchar *inbuf, gint *error); + +struct _CodeConverter +{ + CodeConvFunc code_conv_func; + gchar *src_encoding; + gchar *dest_encoding; +}; + +#define CS_AUTO "AUTO" +#define CS_US_ASCII "US-ASCII" +#define CS_ANSI_X3_4_1968 "ANSI_X3.4-1968" +#define CS_UTF_8 "UTF-8" +#define CS_UTF_7 "UTF-7" +#define CS_ISO_8859_1 "ISO-8859-1" +#define CS_ISO_8859_2 "ISO-8859-2" +#define CS_ISO_8859_3 "ISO-8859-3" +#define CS_ISO_8859_4 "ISO-8859-4" +#define CS_ISO_8859_5 "ISO-8859-5" +#define CS_ISO_8859_6 "ISO-8859-6" +#define CS_ISO_8859_7 "ISO-8859-7" +#define CS_ISO_8859_8 "ISO-8859-8" +#define CS_ISO_8859_9 "ISO-8859-9" +#define CS_ISO_8859_10 "ISO-8859-10" +#define CS_ISO_8859_11 "ISO-8859-11" +#define CS_ISO_8859_13 "ISO-8859-13" +#define CS_ISO_8859_14 "ISO-8859-14" +#define CS_ISO_8859_15 "ISO-8859-15" +#define CS_BALTIC "BALTIC" +#define CS_CP1250 "CP1250" +#define CS_CP1251 "CP1251" +#define CS_CP1252 "CP1252" +#define CS_CP1253 "CP1253" +#define CS_CP1254 "CP1254" +#define CS_CP1255 "CP1255" +#define CS_CP1256 "CP1256" +#define CS_CP1257 "CP1257" +#define CS_CP1258 "CP1258" +#define CS_WINDOWS_1250 "Windows-1250" +#define CS_WINDOWS_1251 "Windows-1251" +#define CS_WINDOWS_1252 "Windows-1252" +#define CS_WINDOWS_1253 "Windows-1253" +#define CS_WINDOWS_1254 "Windows-1254" +#define CS_WINDOWS_1255 "Windows-1255" +#define CS_WINDOWS_1256 "Windows-1256" +#define CS_WINDOWS_1257 "Windows-1257" +#define CS_WINDOWS_1258 "Windows-1258" +#define CS_KOI8_R "KOI8-R" +#define CS_KOI8_T "KOI8-T" +#define CS_KOI8_U "KOI8-U" +#define CS_ISO_2022_JP "ISO-2022-JP" +#define CS_ISO_2022_JP_2 "ISO-2022-JP-2" +#define CS_ISO_2022_JP_3 "ISO-2022-JP-3" +#define CS_EUC_JP "EUC-JP" +#define CS_EUCJP "EUCJP" +#define CS_EUC_JP_MS "EUC-JP-MS" +#define CS_SHIFT_JIS "Shift_JIS" +#define CS_SHIFT__JIS "SHIFT-JIS" +#define CS_SJIS "SJIS" +#define CS_X_SJIS "X-SJIS" +#define CS_ISO_2022_KR "ISO-2022-KR" +#define CS_EUC_KR "EUC-KR" +#define CS_ISO_2022_CN "ISO-2022-CN" +#define CS_EUC_CN "EUC-CN" +#define CS_GB2312 "GB2312" +#define CS_GBK "GBK" +#define CS_X_GBK "X-GBK" +#define CS_EUC_TW "EUC-TW" +#define CS_BIG5 "Big5" +#define CS_BIG5_HKSCS "BIG5-HKSCS" +#define CS_TIS_620 "TIS-620" +#define CS_WINDOWS_874 "Windows-874" +#define CS_GEORGIAN_PS "GEORGIAN-PS" +#define CS_TCVN5712_1 "TCVN5712-1" + +#define C_INTERNAL C_UTF_8 +#define CS_INTERNAL CS_UTF_8 + +//void conv_mb_alnum(gchar *str); + +CharSet conv_guess_ja_encoding (const gchar *str); + +gchar *conv_utf8todisp (const gchar *inbuf, + gint *error); +gchar *conv_localetodisp (const gchar *inbuf, + gint *error); + +CodeConverter *conv_code_converter_new (const gchar *src_encoding, + const gchar *dest_encoding); +void conv_code_converter_destroy (CodeConverter *conv); +gchar *conv_convert (CodeConverter *conv, + const gchar *inbuf); + +#define conv_codeset_strdup(inbuf, src_code, dest_code) \ + (conv_codeset_strdup_full(inbuf, src_code, dest_code, NULL)) + +gchar *conv_codeset_strdup_full (const gchar *inbuf, + const gchar *src_encoding, + const gchar *dest_encoding, + gint *error); + +CodeConvFunc conv_get_code_conv_func (const gchar *src_encoding, + const gchar *dest_encoding); + +gchar *conv_iconv_strdup (const gchar *inbuf, + const gchar *src_encoding, + const gchar *dest_encoding, + gint *error); +gchar *conv_iconv_strdup_with_cd (const gchar *inbuf, + iconv_t cd, + gint *error); + +const gchar *conv_get_charset_str (CharSet charset); +CharSet conv_get_charset_from_str (const gchar *charset); +CharSet conv_get_locale_charset (void); +const gchar *conv_get_locale_charset_str (void); +CharSet conv_get_internal_charset (void); +const gchar *conv_get_internal_charset_str (void); +CharSet conv_get_outgoing_charset (void); +const gchar *conv_get_outgoing_charset_str (void); +gboolean conv_is_multibyte_encoding (CharSet encoding); + +const gchar *conv_get_current_locale (void); +gboolean conv_is_ja_locale (void); + +gchar *conv_unmime_header (const gchar *str, + const gchar *default_encoding); +void conv_encode_header (gchar *dest, + gint len, + const gchar *src, + gint header_len, + gboolean addr_field, + const gchar *out_encoding); + +gint conv_copy_file (const gchar *src, + const gchar *dest, + const gchar *src_encoding); +gint conv_copy_dir (const gchar *src, + const gchar *dest, + const gchar *src_encoding); + +gchar *conv_filename_from_utf8 (const gchar *utf8_file); +gchar *conv_filename_to_utf8 (const gchar *fs_file); + +#endif /* __CODECONV_H__ */ diff --git a/libsylph/defs.h b/libsylph/defs.h new file mode 100644 index 00000000..9683c28d --- /dev/null +++ b/libsylph/defs.h @@ -0,0 +1,122 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __DEFS_H__ +#define __DEFS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glibconfig.h> + +#ifdef G_OS_WIN32 +# include <glib/gwin32.h> +#endif + +#if HAVE_PATHS_H +# include <paths.h> +#endif + +#if HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif + +#define INBOX_DIR "inbox" +#define OUTBOX_DIR "sent" +#define QUEUE_DIR "queue" +#define DRAFT_DIR "draft" +#define TRASH_DIR "trash" +#ifdef G_OS_WIN32 +# define RC_DIR "Sylpheed" +#else +# define RC_DIR ".sylpheed-2.0" +#endif +#define OLD_RC_DIR ".sylpheed" +#define NEWS_CACHE_DIR "newscache" +#define IMAP_CACHE_DIR "imapcache" +#define MIME_TMP_DIR "mimetmp" +#define COMMON_RC "sylpheedrc" +#define ACCOUNT_RC "accountrc" +#define FILTER_RC "filterrc" +#define FILTER_LIST "filter.xml" +#define FILTER_HEADER_RC "filterheaderrc" +#define CUSTOM_HEADER_RC "customheaderrc" +#define DISPLAY_HEADER_RC "dispheaderrc" +#define MENU_RC "menurc" +#define ACTIONS_RC "actionsrc" +#define COMMAND_HISTORY "command_history" +#define TEMPLATE_DIR "templates" +#define TMP_DIR "tmp" +#define UIDL_DIR "uidl" +#define NEWSGROUP_LIST ".newsgroup_list" +#define ADDRESS_BOOK "addressbook.xml" +#define MANUAL_HTML_INDEX "sylpheed.html" +#define FAQ_HTML_INDEX "sylpheed-faq.html" +#define HOMEPAGE_URI "http://sylpheed.good-day.net/" +#define FOLDER_LIST "folderlist.xml" +#define CACHE_FILE ".sylpheed_cache" +#define MARK_FILE ".sylpheed_mark" +#define CACHE_VERSION 0x21 +#define MARK_VERSION 2 + +#ifdef G_OS_WIN32 +# define DEFAULT_SIGNATURE "signature.txt" +#else +# define DEFAULT_SIGNATURE ".signature" +#endif +#define DEFAULT_INC_PATH "/usr/bin/mh/inc" +#define DEFAULT_INC_PROGRAM "inc" +/* #define DEFAULT_INC_PATH "/usr/bin/imget" */ +/* #define DEFAULT_INC_PROGRAM "imget" */ +#define DEFAULT_SENDMAIL_CMD "/usr/sbin/sendmail -t -i" +#define DEFAULT_BROWSER_CMD "mozilla-firefox -remote 'openURL(%s,new-window)'" + +#ifdef _PATH_MAILDIR +# define DEFAULT_SPOOL_PATH _PATH_MAILDIR +#else +# define DEFAULT_SPOOL_PATH "/var/spool/mail" +#endif + +#define BUFFSIZE 8192 + +#ifndef MAXPATHLEN +# define MAXPATHLEN 4095 +#endif + +#define DEFAULT_HEIGHT 460 +#define DEFAULT_FOLDERVIEW_WIDTH 179 +#define DEFAULT_MAINVIEW_WIDTH 600 +#define DEFAULT_SUMMARY_HEIGHT 140 +#define DEFAULT_HEADERVIEW_HEIGHT 40 +#define DEFAULT_COMPOSE_HEIGHT 560 +#define BORDER_WIDTH 2 +#define CTREE_INDENT 18 +#define FOLDER_SPACING 4 +#define MAX_ENTRY_LENGTH 8191 +#define COLOR_DIM 35000 +#define UI_REFRESH_INTERVAL 50000 /* usec */ +#define FOLDER_UPDATE_INTERVAL 1500 /* msec */ +#define PROGRESS_UPDATE_INTERVAL 200 /* msec */ +#define SESSION_TIMEOUT_INTERVAL 60 /* sec */ +#define MAX_HISTORY_SIZE 16 + +#define DEFAULT_MESSAGE_FONT "Monospace 12" + +#endif /* __DEFS_H__ */ diff --git a/libsylph/prefs.c b/libsylph/prefs.c new file mode 100644 index 00000000..4579df4f --- /dev/null +++ b/libsylph/prefs.c @@ -0,0 +1,465 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "prefs.h" +#include "codeconv.h" +#include "utils.h" + +typedef enum +{ + DUMMY_PARAM +} DummyEnum; + +void prefs_read_config(PrefParam *param, const gchar *label, + const gchar *rcfile, const gchar *encoding) +{ + FILE *fp; + gchar buf[PREFSBUFSIZE]; + gchar *block_label; + + g_return_if_fail(param != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(rcfile != NULL); + + debug_print("Reading configuration...\n"); + + prefs_set_default(param); + + if ((fp = g_fopen(rcfile, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcfile, "fopen"); + return; + } + + block_label = g_strdup_printf("[%s]", label); + + /* search aiming block */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + gint val; + + if (encoding) { + gchar *conv_str; + + conv_str = conv_codeset_strdup + (buf, encoding, CS_INTERNAL); + if (!conv_str) + conv_str = g_strdup(buf); + val = strncmp + (conv_str, block_label, strlen(block_label)); + g_free(conv_str); + } else + val = strncmp(buf, block_label, strlen(block_label)); + if (val == 0) { + debug_print("Found %s\n", block_label); + break; + } + } + g_free(block_label); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + /* reached next block */ + if (buf[0] == '[') break; + + if (encoding) { + gchar *conv_str; + + conv_str = conv_codeset_strdup + (buf, encoding, CS_INTERNAL); + if (!conv_str) + conv_str = g_strdup(buf); + prefs_config_parse_one_line(param, conv_str); + g_free(conv_str); + } else + prefs_config_parse_one_line(param, buf); + } + + debug_print("Finished reading configuration.\n"); + fclose(fp); +} + +void prefs_config_parse_one_line(PrefParam *param, const gchar *buf) +{ + gint i; + gint name_len; + const gchar *value; + + for (i = 0; param[i].name != NULL; i++) { + name_len = strlen(param[i].name); + if (g_ascii_strncasecmp(buf, param[i].name, name_len)) + continue; + if (buf[name_len] != '=') + continue; + value = buf + name_len + 1; + /* debug_print("%s = %s\n", param[i].name, value); */ + + switch (param[i].type) { + case P_STRING: + g_free(*((gchar **)param[i].data)); + *((gchar **)param[i].data) = + *value ? g_strdup(value) : NULL; + break; + case P_INT: + *((gint *)param[i].data) = + (gint)atoi(value); + break; + case P_BOOL: + *((gboolean *)param[i].data) = + (*value == '0' || *value == '\0') + ? FALSE : TRUE; + break; + case P_ENUM: + *((DummyEnum *)param[i].data) = + (DummyEnum)atoi(value); + break; + case P_USHORT: + *((gushort *)param[i].data) = + (gushort)atoi(value); + break; + default: + break; + } + } +} + +#define TRY(func) \ +if (!(func)) \ +{ \ + g_warning(_("failed to write configuration to file\n")); \ + if (orig_fp) fclose(orig_fp); \ + prefs_file_close_revert(pfile); \ + g_free(rcpath); \ + g_free(block_label); \ + return; \ +} \ + +void prefs_write_config(PrefParam *param, const gchar *label, + const gchar *rcfile) +{ + FILE *orig_fp; + PrefFile *pfile; + gchar *rcpath; + gchar buf[PREFSBUFSIZE]; + gchar *block_label = NULL; + gboolean block_matched = FALSE; + + g_return_if_fail(param != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(rcfile != NULL); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL); + if ((orig_fp = g_fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + } + + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning(_("failed to write configuration to file\n")); + if (orig_fp) fclose(orig_fp); + g_free(rcpath); + return; + } + + block_label = g_strdup_printf("[%s]", label); + + /* search aiming block */ + if (orig_fp) { + while (fgets(buf, sizeof(buf), orig_fp) != NULL) { + gint val; + + val = strncmp(buf, block_label, strlen(block_label)); + if (val == 0) { + debug_print(_("Found %s\n"), block_label); + block_matched = TRUE; + break; + } else + TRY(fputs(buf, pfile->fp) != EOF); + } + } + + TRY(fprintf(pfile->fp, "%s\n", block_label) > 0); + g_free(block_label); + block_label = NULL; + + /* write all param data to file */ + TRY(prefs_file_write_param(pfile, param) == 0); + + if (block_matched) { + while (fgets(buf, sizeof(buf), orig_fp) != NULL) { + /* next block */ + if (buf[0] == '[') { + TRY(fputc('\n', pfile->fp) != EOF && + fputs(buf, pfile->fp) != EOF); + break; + } + } + while (fgets(buf, sizeof(buf), orig_fp) != NULL) + TRY(fputs(buf, pfile->fp) != EOF); + } + + if (orig_fp) fclose(orig_fp); + if (prefs_file_close(pfile) < 0) + g_warning(_("failed to write configuration to file\n")); + g_free(rcpath); + + debug_print(_("Configuration is saved.\n")); +} + +gint prefs_file_write_param(PrefFile *pfile, PrefParam *param) +{ + gint i; + gchar buf[PREFSBUFSIZE]; + + for (i = 0; param[i].name != NULL; i++) { + switch (param[i].type) { + case P_STRING: + g_snprintf(buf, sizeof(buf), "%s=%s\n", param[i].name, + *((gchar **)param[i].data) ? + *((gchar **)param[i].data) : ""); + break; + case P_INT: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gint *)param[i].data)); + break; + case P_BOOL: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gboolean *)param[i].data)); + break; + case P_ENUM: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((DummyEnum *)param[i].data)); + break; + case P_USHORT: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gushort *)param[i].data)); + break; + default: + buf[0] = '\0'; + } + + if (buf[0] != '\0') { + if (fputs(buf, pfile->fp) == EOF) { + perror("fputs"); + return -1; + } + } + } + + return 0; +} + +PrefFile *prefs_file_open(const gchar *path) +{ + PrefFile *pfile; + gchar *tmppath; + FILE *fp; + + g_return_val_if_fail(path != NULL, NULL); + + tmppath = g_strconcat(path, ".tmp", NULL); + if ((fp = g_fopen(tmppath, "wb")) == NULL) { + FILE_OP_ERROR(tmppath, "fopen"); + g_free(tmppath); + return NULL; + } + + if (change_file_mode_rw(fp, tmppath) < 0) + FILE_OP_ERROR(tmppath, "chmod"); + + g_free(tmppath); + + pfile = g_new(PrefFile, 1); + pfile->fp = fp; + pfile->path = g_strdup(path); + + return pfile; +} + +gint prefs_file_close(PrefFile *pfile) +{ + FILE *fp; + gchar *path; + gchar *tmppath; + gchar *bakpath = NULL; + + g_return_val_if_fail(pfile != NULL, -1); + + fp = pfile->fp; + path = pfile->path; + g_free(pfile); + + tmppath = g_strconcat(path, ".tmp", NULL); + if (fclose(fp) == EOF) { + FILE_OP_ERROR(tmppath, "fclose"); + g_unlink(tmppath); + g_free(path); + g_free(tmppath); + return -1; + } + + if (is_file_exist(path)) { + bakpath = g_strconcat(path, ".bak", NULL); + if (rename_force(path, bakpath) < 0) { + FILE_OP_ERROR(path, "rename"); + g_unlink(tmppath); + g_free(path); + g_free(tmppath); + g_free(bakpath); + return -1; + } + } + + if (rename_force(tmppath, path) < 0) { + FILE_OP_ERROR(tmppath, "rename"); + g_unlink(tmppath); + g_free(path); + g_free(tmppath); + g_free(bakpath); + return -1; + } + + g_free(path); + g_free(tmppath); + g_free(bakpath); + return 0; +} + +gint prefs_file_close_revert(PrefFile *pfile) +{ + gchar *tmppath; + + g_return_val_if_fail(pfile != NULL, -1); + + tmppath = g_strconcat(pfile->path, ".tmp", NULL); + fclose(pfile->fp); + if (g_unlink(tmppath) < 0) + FILE_OP_ERROR(tmppath, "unlink"); + g_free(tmppath); + g_free(pfile->path); + g_free(pfile); + + return 0; +} + +void prefs_set_default(PrefParam *param) +{ + gint i; + + g_return_if_fail(param != NULL); + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].data) continue; + + switch (param[i].type) { + case P_STRING: + if (param[i].defval != NULL) { + if (!g_ascii_strncasecmp(param[i].defval, "ENV_", 4)) { + const gchar *envstr; + gchar *tmp = NULL; + + envstr = g_getenv(param[i].defval + 4); + if (envstr) { + tmp = conv_codeset_strdup + (envstr, + conv_get_locale_charset_str(), + CS_UTF_8); + if (!tmp) { + g_warning("failed to convert character set."); + tmp = g_strdup(envstr); + } + } + *((gchar **)param[i].data) = tmp; + } else if (param[i].defval[0] == '~') + *((gchar **)param[i].data) = + g_strconcat(get_home_dir(), + param[i].defval + 1, + NULL); + else if (param[i].defval[0] != '\0') + *((gchar **)param[i].data) = + g_strdup(param[i].defval); + else + *((gchar **)param[i].data) = NULL; + } else + *((gchar **)param[i].data) = NULL; + break; + case P_INT: + if (param[i].defval != NULL) + *((gint *)param[i].data) = + (gint)atoi(param[i].defval); + else + *((gint *)param[i].data) = 0; + break; + case P_BOOL: + if (param[i].defval != NULL) { + if (!g_ascii_strcasecmp(param[i].defval, "TRUE")) + *((gboolean *)param[i].data) = TRUE; + else + *((gboolean *)param[i].data) = + atoi(param[i].defval) ? TRUE : FALSE; + } else + *((gboolean *)param[i].data) = FALSE; + break; + case P_ENUM: + if (param[i].defval != NULL) + *((DummyEnum*)param[i].data) = + (DummyEnum)atoi(param[i].defval); + else + *((DummyEnum *)param[i].data) = 0; + break; + case P_USHORT: + if (param[i].defval != NULL) + *((gushort *)param[i].data) = + (gushort)atoi(param[i].defval); + else + *((gushort *)param[i].data) = 0; + break; + default: + break; + } + } +} + +void prefs_free(PrefParam *param) +{ + gint i; + + g_return_if_fail(param != NULL); + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].data) continue; + + switch (param[i].type) { + case P_STRING: + g_free(*((gchar **)param[i].data)); + break; + default: + break; + } + } +} diff --git a/libsylph/prefs.h b/libsylph/prefs.h new file mode 100644 index 00000000..f062d1eb --- /dev/null +++ b/libsylph/prefs.h @@ -0,0 +1,80 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __PREFS_H__ +#define __PREFS_H__ + +#include <glib.h> +#include <stdio.h> + +typedef struct _PrefParam PrefParam; +typedef struct _PrefFile PrefFile; + +#define PREFSBUFSIZE 1024 + +#define P_WID(wid) ((gpointer *)(wid)) + +typedef enum +{ + P_STRING, + P_INT, + P_BOOL, + P_ENUM, + P_USHORT, + P_OTHER +} PrefType; + +typedef void (*DataSetFunc) (PrefParam *pparam); +typedef void (*WidgetSetFunc) (PrefParam *pparam); + +struct _PrefParam { + gchar *name; + gchar *defval; + gpointer data; + PrefType type; + gpointer *widget; + DataSetFunc data_set_func; + WidgetSetFunc widget_set_func; +}; + +struct _PrefFile { + FILE *fp; + gchar *path; +}; + +void prefs_read_config (PrefParam *param, + const gchar *label, + const gchar *rcfile, + const gchar *encoding); +void prefs_config_parse_one_line(PrefParam *param, + const gchar *buf); +void prefs_write_config (PrefParam *param, + const gchar *label, + const gchar *rcfile); + +PrefFile *prefs_file_open (const gchar *path); +gint prefs_file_write_param (PrefFile *pfile, + PrefParam *param); +gint prefs_file_close (PrefFile *pfile); +gint prefs_file_close_revert (PrefFile *pfile); + +void prefs_set_default (PrefParam *param); +void prefs_free (PrefParam *param); + +#endif /* __PREFS_H__ */ diff --git a/libsylph/quoted-printable.c b/libsylph/quoted-printable.c new file mode 100644 index 00000000..cea0b704 --- /dev/null +++ b/libsylph/quoted-printable.c @@ -0,0 +1,232 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <glib.h> +#include <ctype.h> + +static gboolean get_hex_value(guchar *out, gchar c1, gchar c2); +static void get_hex_str(gchar *out, guchar ch); + +#define MAX_LINELEN 76 + +#define IS_LBREAK(p) \ + (*(p) == '\0' || *(p) == '\n' || (*(p) == '\r' && *((p) + 1) == '\n')) + +#define SOFT_LBREAK_IF_REQUIRED(n) \ + if (len + (n) > MAX_LINELEN || \ + (len + (n) == MAX_LINELEN && (!IS_LBREAK(inp + 1)))) { \ + *outp++ = '='; \ + *outp++ = '\n'; \ + len = 0; \ + } + +void qp_encode_line(gchar *out, const guchar *in) +{ + const guchar *inp = in; + gchar *outp = out; + guchar ch; + gint len = 0; + + while (*inp != '\0') { + ch = *inp; + + if (IS_LBREAK(inp)) { + *outp++ = '\n'; + len = 0; + if (*inp == '\r') + inp++; + inp++; + } else if (ch == '\t' || ch == ' ') { + if (IS_LBREAK(inp + 1)) { + SOFT_LBREAK_IF_REQUIRED(3); + *outp++ = '='; + get_hex_str(outp, ch); + outp += 2; + len += 3; + inp++; + } else { + SOFT_LBREAK_IF_REQUIRED(1); + *outp++ = *inp++; + len++; + } + } else if ((ch >= 33 && ch <= 60) || (ch >= 62 && ch <= 126)) { + SOFT_LBREAK_IF_REQUIRED(1); + *outp++ = *inp++; + len++; + } else { + SOFT_LBREAK_IF_REQUIRED(3); + *outp++ = '='; + get_hex_str(outp, ch); + outp += 2; + len += 3; + inp++; + } + } + + if (len > 0) + *outp++ = '\n'; + + *outp = '\0'; +} + +gint qp_decode_line(gchar *str) +{ + gchar *inp = str, *outp = str; + + while (*inp != '\0') { + if (*inp == '=') { + if (inp[1] && inp[2] && + get_hex_value((guchar *)outp, inp[1], inp[2]) + == TRUE) { + inp += 3; + } else if (inp[1] == '\0' || g_ascii_isspace(inp[1])) { + /* soft line break */ + break; + } else { + /* broken QP string */ + *outp = *inp++; + } + } else { + *outp = *inp++; + } + outp++; + } + + *outp = '\0'; + + return outp - str; +} + +gint qp_decode_q_encoding(guchar *out, const gchar *in, gint inlen) +{ + const gchar *inp = in; + guchar *outp = out; + + if (inlen < 0) + inlen = G_MAXINT; + + while (inp - in < inlen && *inp != '\0') { + if (*inp == '=' && inp + 3 - in <= inlen) { + if (get_hex_value(outp, inp[1], inp[2]) == TRUE) { + inp += 3; + } else { + *outp = *inp++; + } + } else if (*inp == '_') { + *outp = ' '; + inp++; + } else { + *outp = *inp++; + } + outp++; + } + + *outp = '\0'; + + return outp - out; +} + +gint qp_get_q_encoding_len(const guchar *str) +{ + const guchar *inp = str; + gint len = 0; + + while (*inp != '\0') { + if (*inp == 0x20) + len++; + else if (*inp == '=' || *inp == '?' || *inp == '_' || + *inp < 32 || *inp > 127 || g_ascii_isspace(*inp)) + len += 3; + else + len++; + + inp++; + } + + return len; +} + +void qp_q_encode(gchar *out, const guchar *in) +{ + const guchar *inp = in; + gchar *outp = out; + + while (*inp != '\0') { + if (*inp == 0x20) + *outp++ = '_'; + else if (*inp == '=' || *inp == '?' || *inp == '_' || + *inp < 32 || *inp > 127 || g_ascii_isspace(*inp)) { + *outp++ = '='; + get_hex_str(outp, *inp); + outp += 2; + } else + *outp++ = *inp; + + inp++; + } + + *outp = '\0'; +} + +#define HEX_TO_INT(val, hex) \ +{ \ + gchar c = hex; \ + \ + if ('0' <= c && c <= '9') { \ + val = c - '0'; \ + } else if ('a' <= c && c <= 'f') { \ + val = c - 'a' + 10; \ + } else if ('A' <= c && c <= 'F') { \ + val = c - 'A' + 10; \ + } else { \ + val = -1; \ + } \ +} + +static gboolean get_hex_value(guchar *out, gchar c1, gchar c2) +{ + gint hi, lo; + + HEX_TO_INT(hi, c1); + HEX_TO_INT(lo, c2); + + if (hi == -1 || lo == -1) + return FALSE; + + *out = (hi << 4) + lo; + return TRUE; +} + +#define INT_TO_HEX(hex, val) \ +{ \ + if ((val) < 10) \ + hex = '0' + (val); \ + else \ + hex = 'A' + (val) - 10; \ +} + +static void get_hex_str(gchar *out, guchar ch) +{ + gchar hex; + + INT_TO_HEX(hex, ch >> 4); + *out++ = hex; + INT_TO_HEX(hex, ch & 0x0f); + *out++ = hex; +} diff --git a/libsylph/quoted-printable.h b/libsylph/quoted-printable.h new file mode 100644 index 00000000..e5abf4f7 --- /dev/null +++ b/libsylph/quoted-printable.h @@ -0,0 +1,36 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __QUOTED_PRINTABLE_H__ +#define __QUOTED_PRINTABLE_H__ + +#include <glib.h> + +void qp_encode_line (gchar *out, + const guchar *in); +gint qp_decode_line (gchar *str); + +gint qp_decode_q_encoding (guchar *out, + const gchar *in, + gint inlen); +gint qp_get_q_encoding_len (const guchar *str); +void qp_q_encode (gchar *out, + const guchar *in); + +#endif /* __QUOTED_PRINTABLE_H__ */ diff --git a/libsylph/session.c b/libsylph/session.c new file mode 100644 index 00000000..6e7fa4e9 --- /dev/null +++ b/libsylph/session.c @@ -0,0 +1,793 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include <glib.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include <errno.h> + +#include "session.h" +#include "utils.h" + +static gint session_connect_cb (SockInfo *sock, + gpointer data); +static gint session_close (Session *session); + +static gboolean session_timeout_cb (gpointer data); + +static gboolean session_recv_msg_idle_cb (gpointer data); +static gboolean session_recv_data_idle_cb (gpointer data); + +static gboolean session_read_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_read_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); + + +void session_init(Session *session) +{ + session->type = SESSION_UNKNOWN; + session->sock = NULL; + session->server = NULL; + session->port = 0; +#if USE_SSL + session->ssl_type = SSL_NONE; +#endif + session->nonblocking = TRUE; + session->state = SESSION_READY; + session->last_access_time = time(NULL); + + g_get_current_time(&session->tv_prev); + + session->conn_id = 0; + + session->io_tag = 0; + + session->read_buf_p = session->read_buf; + session->read_buf_len = 0; + + session->read_msg_buf = g_string_sized_new(1024); + session->read_data_buf = g_byte_array_new(); + + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + session->write_data = NULL; + session->write_data_p = NULL; + session->write_data_len = 0; + + session->timeout_tag = 0; + session->timeout_interval = 0; + + session->data = NULL; +} + +gint session_connect(Session *session, const gchar *server, gushort port) +{ +#ifdef G_OS_UNIX + session->server = g_strdup(server); + session->port = port; + + session->conn_id = sock_connect_async(server, port, session_connect_cb, + session); + if (session->conn_id < 0) { + g_warning("can't connect to server."); + session_close(session); + return -1; + } + + return 0; +#else + SockInfo *sock; + + session->server = g_strdup(server); + session->port = port; + + sock = sock_connect(server, port); + if (sock == NULL) { + g_warning("can't connect to server."); + session_close(session); + return -1; + } + + return session_connect_cb(sock, session); +#endif +} + +static gint session_connect_cb(SockInfo *sock, gpointer data) +{ + Session *session = SESSION(data); + + session->conn_id = 0; + + if (!sock) { + g_warning("can't connect to server."); + session->state = SESSION_ERROR; + return -1; + } + + session->sock = sock; + +#if USE_SSL + if (session->ssl_type == SSL_TUNNEL) { + sock_set_nonblocking_mode(sock, FALSE); + if (!ssl_init_socket(sock)) { + g_warning("can't initialize SSL."); + session->state = SESSION_ERROR; + return -1; + } + } +#endif + + sock_set_nonblocking_mode(sock, session->nonblocking); + + debug_print("session (%p): connected\n", session); + + session->state = SESSION_RECV; + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, + session); + + return 0; +} + +gint session_disconnect(Session *session) +{ + session_close(session); + return 0; +} + +void session_destroy(Session *session) +{ + g_return_if_fail(session != NULL); + g_return_if_fail(session->destroy != NULL); + + session_close(session); + session->destroy(session); + g_free(session->server); + g_string_free(session->read_msg_buf, TRUE); + g_byte_array_free(session->read_data_buf, TRUE); + g_free(session->read_data_terminator); + g_free(session->write_buf); + + debug_print("session (%p): destroyed\n", session); + + g_free(session); +} + +gboolean session_is_connected(Session *session) +{ + return (session->state == SESSION_READY || + session->state == SESSION_SEND || + session->state == SESSION_RECV); +} + +void session_set_access_time(Session *session) +{ + session->last_access_time = time(NULL); +} + +void session_set_timeout(Session *session, guint interval) +{ + if (session->timeout_tag > 0) + g_source_remove(session->timeout_tag); + + session->timeout_interval = interval; + if (interval > 0) + session->timeout_tag = + g_timeout_add(interval, session_timeout_cb, session); + else + session->timeout_tag = 0; +} + +static gboolean session_timeout_cb(gpointer data) +{ + Session *session = SESSION(data); + + g_warning("session timeout.\n"); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session->timeout_tag = 0; + session->state = SESSION_TIMEOUT; + + return FALSE; +} + +void session_set_recv_message_notify(Session *session, + RecvMsgNotify notify_func, gpointer data) +{ + session->recv_msg_notify = notify_func; + session->recv_msg_notify_data = data; +} + +void session_set_recv_data_progressive_notify + (Session *session, + RecvDataProgressiveNotify notify_func, + gpointer data) +{ + session->recv_data_progressive_notify = notify_func, + session->recv_data_progressive_notify_data = data; +} + +void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func, + gpointer data) +{ + session->recv_data_notify = notify_func; + session->recv_data_notify_data = data; +} + +void session_set_send_data_progressive_notify + (Session *session, + SendDataProgressiveNotify notify_func, + gpointer data) +{ + session->send_data_progressive_notify = notify_func; + session->send_data_progressive_notify_data = data; +} + +void session_set_send_data_notify(Session *session, SendDataNotify notify_func, + gpointer data) +{ + session->send_data_notify = notify_func; + session->send_data_notify_data = data; +} + +static gint session_close(Session *session) +{ + g_return_val_if_fail(session != NULL, -1); + +#ifdef G_OS_UNIX + if (session->conn_id > 0) { + sock_connect_async_cancel(session->conn_id); + session->conn_id = 0; + debug_print("session (%p): connection cancelled\n", session); + } +#endif + + session_set_timeout(session, 0); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + if (session->sock) { + sock_close(session->sock); + session->sock = NULL; + session->state = SESSION_DISCONNECTED; + debug_print("session (%p): closed\n", session); + } + + return 0; +} + +#if USE_SSL +gint session_start_tls(Session *session) +{ + gboolean nb_mode; + + nb_mode = sock_is_nonblocking_mode(session->sock); + + if (nb_mode) + sock_set_nonblocking_mode(session->sock, FALSE); + + if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) { + g_warning("can't start TLS session.\n"); + if (nb_mode) + sock_set_nonblocking_mode(session->sock, TRUE); + return -1; + } + + if (nb_mode) + sock_set_nonblocking_mode(session->sock, session->nonblocking); + + return 0; +} +#endif + +gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg) +{ + gboolean ret; + + g_return_val_if_fail(session->write_buf == NULL, -1); + g_return_val_if_fail(msg != NULL, -1); + g_return_val_if_fail(msg[0] != '\0', -1); + + session->state = SESSION_SEND; + session->write_buf = g_strconcat(msg, "\r\n", NULL); + session->write_buf_p = session->write_buf; + session->write_buf_len = strlen(msg) + 2; + + ret = session_write_msg_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_msg_cb, session); + else if (session->state == SESSION_ERROR) + return -1; + + return 0; +} + +gint session_recv_msg(Session *session) +{ + g_return_val_if_fail(session->read_msg_buf->len == 0, -1); + + session->state = SESSION_RECV; + + if (session->read_buf_len > 0) + g_idle_add(session_recv_msg_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return 0; +} + +static gboolean session_recv_msg_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + + ret = session_read_msg_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return FALSE; +} + +gint session_send_data(Session *session, const guchar *data, guint size) +{ + gboolean ret; + + g_return_val_if_fail(session->write_data == NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(size != 0, -1); + + session->state = SESSION_SEND; + + session->write_data = data; + session->write_data_p = session->write_data; + session->write_data_len = size; + g_get_current_time(&session->tv_prev); + + ret = session_write_data_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_data_cb, + session); + else if (session->state == SESSION_ERROR) + return -1; + + return 0; +} + +gint session_recv_data(Session *session, guint size, const gchar *terminator) +{ + g_return_val_if_fail(session->read_data_buf->len == 0, -1); + + session->state = SESSION_RECV; + + g_free(session->read_data_terminator); + session->read_data_terminator = g_strdup(terminator); + g_get_current_time(&session->tv_prev); + + if (session->read_buf_len > 0) + g_idle_add(session_recv_data_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); + + return 0; +} + +static gboolean session_recv_data_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + + ret = session_read_data_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); + + return FALSE; +} + +static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + gchar buf[SESSION_BUFFSIZE]; + gint line_len; + gchar *newline; + gchar *msg; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + session_set_timeout(session, session->timeout_interval); + + if (session->read_buf_len == 0) { + gint read_len; + + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE - 1); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("sock_read: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len)) + != NULL) + line_len = newline - session->read_buf_p + 1; + else + line_len = session->read_buf_len; + + if (line_len == 0) + return TRUE; + + memcpy(buf, session->read_buf_p, line_len); + buf[line_len] = '\0'; + + g_string_append(session->read_msg_buf, buf); + + session->read_buf_len -= line_len; + if (session->read_buf_len == 0) + session->read_buf_p = session->read_buf; + else + session->read_buf_p += line_len; + + /* incomplete read */ + if (buf[line_len - 1] != '\n') + return TRUE; + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + msg = g_strdup(session->read_msg_buf->str); + strretchomp(msg); + g_string_truncate(session->read_msg_buf, 0); + + ret = session->recv_msg(session, msg); + session->recv_msg_notify(session, msg, session->recv_msg_notify_data); + + g_free(msg); + + if (ret < 0) + session->state = SESSION_ERROR; + + return FALSE; +} + +static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + GByteArray *data_buf; + gint terminator_len; + gboolean complete = FALSE; + guint data_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + session_set_timeout(session, session->timeout_interval); + + if (session->read_buf_len == 0) { + gint read_len; + + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("sock_read: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + data_buf = session->read_data_buf; + terminator_len = strlen(session->read_data_terminator); + + if (session->read_buf_len == 0) + return TRUE; + + g_byte_array_append(data_buf, session->read_buf_p, + session->read_buf_len); + + session->read_buf_len = 0; + session->read_buf_p = session->read_buf; + + /* check if data is terminated */ + if (data_buf->len >= terminator_len) { + if (memcmp(data_buf->data, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + else if (data_buf->len >= terminator_len + 2 && + memcmp(data_buf->data + data_buf->len - + (terminator_len + 2), "\r\n", 2) == 0 && + memcmp(data_buf->data + data_buf->len - + terminator_len, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + } + + /* incomplete read */ + if (!complete) { + GTimeVal tv_cur; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session->recv_data_progressive_notify + (session, data_buf->len, 0, + session->recv_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + return TRUE; + } + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + data_len = data_buf->len - terminator_len; + + /* callback */ + ret = session->recv_data_finished(session, (gchar *)data_buf->data, + data_len); + + g_byte_array_set_size(data_buf, 0); + + session->recv_data_notify(session, data_len, + session->recv_data_notify_data); + + if (ret < 0) + session->state = SESSION_ERROR; + + return FALSE; +} + +static gint session_write_buf(Session *session) +{ + gint write_len; + gint to_write_len; + + g_return_val_if_fail(session->write_buf != NULL, -1); + g_return_val_if_fail(session->write_buf_p != NULL, -1); + g_return_val_if_fail(session->write_buf_len > 0, -1); + + to_write_len = session->write_buf_len - + (session->write_buf_p - session->write_buf); + to_write_len = MIN(to_write_len, SESSION_BUFFSIZE); + + write_len = sock_write(session->sock, session->write_buf_p, + to_write_len); + + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; + break; + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return -1; + } + } + + /* incomplete write */ + if (session->write_buf_p - session->write_buf + write_len < + session->write_buf_len) { + session->write_buf_p += write_len; + return 1; + } + + g_free(session->write_buf); + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + return 0; +} + +static gint session_write_data(Session *session) +{ + gint write_len; + gint to_write_len; + + g_return_val_if_fail(session->write_data != NULL, -1); + g_return_val_if_fail(session->write_data_p != NULL, -1); + g_return_val_if_fail(session->write_data_len > 0, -1); + + to_write_len = session->write_data_len - + (session->write_data_p - session->write_data); + to_write_len = MIN(to_write_len, SESSION_BUFFSIZE); + + write_len = sock_write(session->sock, session->write_data_p, + to_write_len); + + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; + break; + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return -1; + } + } + + /* incomplete write */ + if (session->write_data_p - session->write_data + write_len < + session->write_data_len) { + session->write_data_p += write_len; + return 1; + } + + session->write_data = NULL; + session->write_data_p = NULL; + session->write_data_len = 0; + + return 0; +} + +static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + gint ret; + + g_return_val_if_fail(condition == G_IO_OUT, FALSE); + g_return_val_if_fail(session->write_buf != NULL, FALSE); + g_return_val_if_fail(session->write_buf_p != NULL, FALSE); + g_return_val_if_fail(session->write_buf_len > 0, FALSE); + + ret = session_write_buf(session); + + if (ret < 0) { + session->state = SESSION_ERROR; + return FALSE; + } else if (ret > 0) + return TRUE; + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session_recv_msg(session); + + return FALSE; +} + +static gboolean session_write_data_cb(SockInfo *source, + GIOCondition condition, gpointer data) +{ + Session *session = SESSION(data); + guint write_data_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_OUT, FALSE); + g_return_val_if_fail(session->write_data != NULL, FALSE); + g_return_val_if_fail(session->write_data_p != NULL, FALSE); + g_return_val_if_fail(session->write_data_len > 0, FALSE); + + write_data_len = session->write_data_len; + + ret = session_write_data(session); + + if (ret < 0) { + session->state = SESSION_ERROR; + return FALSE; + } else if (ret > 0) { + GTimeVal tv_cur; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session_set_timeout(session, session->timeout_interval); + session->send_data_progressive_notify + (session, + session->write_data_p - session->write_data, + write_data_len, + session->send_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + return TRUE; + } + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + ret = session->send_data_finished(session, write_data_len); + session->send_data_notify(session, write_data_len, + session->send_data_notify_data); + + return FALSE; +} diff --git a/libsylph/session.h b/libsylph/session.h new file mode 100644 index 00000000..9ed0eaf4 --- /dev/null +++ b/libsylph/session.h @@ -0,0 +1,205 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SESSION_H__ +#define __SESSION_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> + +#include <time.h> +#include <unistd.h> + +#include "socket.h" + +#define SESSION_BUFFSIZE 4096 + +typedef struct _Session Session; + +#define SESSION(obj) ((Session *)obj) + +typedef enum { + SESSION_UNKNOWN, + SESSION_IMAP, + SESSION_NEWS, + SESSION_SMTP, + SESSION_POP3 +} SessionType; + +typedef enum { + SESSION_READY, + SESSION_SEND, + SESSION_RECV, + SESSION_EOF, + SESSION_TIMEOUT, + SESSION_ERROR, + SESSION_DISCONNECTED +} SessionState; + +typedef enum +{ + SESSION_MSG_NORMAL, + SESSION_MSG_SEND_DATA, + SESSION_MSG_RECV_DATA, + SESSION_MSG_CONTROL, + SESSION_MSG_ERROR, + SESSION_MSG_UNKNOWN +} SessionMsgType; + +typedef gint (*RecvMsgNotify) (Session *session, + const gchar *msg, + gpointer user_data); +typedef gint (*RecvDataProgressiveNotify) (Session *session, + guint cur_len, + guint total_len, + gpointer user_data); +typedef gint (*RecvDataNotify) (Session *session, + guint len, + gpointer user_data); +typedef gint (*SendDataProgressiveNotify) (Session *session, + guint cur_len, + guint total_len, + gpointer user_data); +typedef gint (*SendDataNotify) (Session *session, + guint len, + gpointer user_data); + +struct _Session +{ + SessionType type; + + SockInfo *sock; + + gchar *server; + gushort port; + +#if USE_SSL + SSLType ssl_type; +#endif + + gboolean nonblocking; + + SessionState state; + + time_t last_access_time; + GTimeVal tv_prev; + + gint conn_id; + + gint io_tag; + + gchar read_buf[SESSION_BUFFSIZE]; + gchar *read_buf_p; + gint read_buf_len; + + GString *read_msg_buf; + GByteArray *read_data_buf; + gchar *read_data_terminator; + + /* buffer for short messages */ + gchar *write_buf; + gchar *write_buf_p; + gint write_buf_len; + + /* buffer for large data */ + const guchar *write_data; + const guchar *write_data_p; + gint write_data_len; + + guint timeout_tag; + guint timeout_interval; + + gpointer data; + + /* virtual methods to parse server responses */ + gint (*recv_msg) (Session *session, + const gchar *msg); + + gint (*send_data_finished) (Session *session, + guint len); + gint (*recv_data_finished) (Session *session, + guchar *data, + guint len); + + void (*destroy) (Session *session); + + /* notification functions */ + RecvMsgNotify recv_msg_notify; + RecvDataProgressiveNotify recv_data_progressive_notify; + RecvDataNotify recv_data_notify; + SendDataProgressiveNotify send_data_progressive_notify; + SendDataNotify send_data_notify; + + gpointer recv_msg_notify_data; + gpointer recv_data_progressive_notify_data; + gpointer recv_data_notify_data; + gpointer send_data_progressive_notify_data; + gpointer send_data_notify_data; +}; + +void session_init (Session *session); +gint session_connect (Session *session, + const gchar *server, + gushort port); +gint session_disconnect (Session *session); +void session_destroy (Session *session); +gboolean session_is_connected (Session *session); + +void session_set_access_time (Session *session); + +void session_set_timeout (Session *session, + guint interval); + +void session_set_recv_message_notify (Session *session, + RecvMsgNotify notify_func, + gpointer data); +void session_set_recv_data_progressive_notify + (Session *session, + RecvDataProgressiveNotify notify_func, + gpointer data); +void session_set_recv_data_notify (Session *session, + RecvDataNotify notify_func, + gpointer data); +void session_set_send_data_progressive_notify + (Session *session, + SendDataProgressiveNotify notify_func, + gpointer data); +void session_set_send_data_notify (Session *session, + SendDataNotify notify_func, + gpointer data); + +#if USE_SSL +gint session_start_tls (Session *session); +#endif + +gint session_send_msg (Session *session, + SessionMsgType type, + const gchar *msg); +gint session_recv_msg (Session *session); +gint session_send_data (Session *session, + const guchar *data, + guint size); +gint session_recv_data (Session *session, + guint size, + const gchar *terminator); + +#endif /* __SESSION_H__ */ diff --git a/libsylph/socket.c b/libsylph/socket.c new file mode 100644 index 00000000..17063ab1 --- /dev/null +++ b/libsylph/socket.c @@ -0,0 +1,1397 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <sys/time.h> +#include <sys/types.h> +#ifdef G_OS_WIN32 +# include <winsock2.h> +#else +# if HAVE_SYS_WAIT_H +# include <sys/wait.h> +# endif +# include <sys/socket.h> +# include <sys/un.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +#endif /* G_OS_WIN32 */ +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> +#include <setjmp.h> +#if HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif + +#include "socket.h" +#if USE_SSL +# include "ssl.h" +#endif + +#define BUFFSIZE 8192 + +typedef gint (*SockAddrFunc) (GList *addr_list, + gpointer data); + +typedef struct _SockConnectData SockConnectData; +typedef struct _SockLookupData SockLookupData; +typedef struct _SockAddrData SockAddrData; +typedef struct _SockSource SockSource; + +struct _SockConnectData { + gint id; + gchar *hostname; + gushort port; + GList *addr_list; + GList *cur_addr; + SockLookupData *lookup_data; + GIOChannel *channel; + guint io_tag; + SockConnectFunc func; + gpointer data; +}; + +struct _SockLookupData { + gchar *hostname; + pid_t child_pid; + GIOChannel *channel; + guint io_tag; + SockAddrFunc func; + gpointer data; +}; + +struct _SockAddrData { + gint family; + gint socktype; + gint protocol; + gint addr_len; + struct sockaddr *addr; +}; + +struct _SockSource { + GSource parent; + SockInfo *sock; +}; + +static guint io_timeout = 60; + +static GList *sock_connect_data_list = NULL; + +static gboolean sock_prepare (GSource *source, + gint *timeout); +static gboolean sock_check (GSource *source); +static gboolean sock_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); + +GSourceFuncs sock_watch_funcs = { + sock_prepare, + sock_check, + sock_dispatch, + NULL +}; + +static gint sock_connect_with_timeout (gint sock, + const struct sockaddr *serv_addr, + gint addrlen, + guint timeout_secs); + +#ifndef INET6 +static gint sock_connect_by_hostname (gint sock, + const gchar *hostname, + gushort port); +#else +static gint sock_connect_by_getaddrinfo (const gchar *hostname, + gushort port); +#endif + +#ifdef G_OS_UNIX +static void sock_address_list_free (GList *addr_list); + +static gboolean sock_connect_async_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); +static gint sock_connect_async_get_address_info_cb + (GList *addr_list, + gpointer data); + +static gint sock_connect_address_list_async (SockConnectData *conn_data); + +static gboolean sock_get_address_info_async_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); +static SockLookupData *sock_get_address_info_async + (const gchar *hostname, + gushort port, + SockAddrFunc func, + gpointer data); +static gint sock_get_address_info_async_cancel (SockLookupData *lookup_data); +#endif /* G_OS_UNIX */ + + +gint sock_init(void) +{ +#ifdef G_OS_WIN32 + WSADATA wsadata; + gint result; + + result = WSAStartup(MAKEWORD(2, 2), &wsadata); + if (result != NO_ERROR) { + g_warning("WSAStartup() failed\n"); + return -1; + } +#endif + return 0; +} + +gint sock_cleanup(void) +{ +#ifdef G_OS_WIN32 + WSACleanup(); +#endif + return 0; +} + +gint sock_set_io_timeout(guint sec) +{ + io_timeout = sec; + return 0; +} + +gint fd_connect_unix(const gchar *path) +{ +#ifdef G_OS_UNIX + gint sock; + struct sockaddr_un addr; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("sock_connect_unix(): socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fd_close(sock); + return -1; + } + + return sock; +#else + return -1; +#endif +} + +gint fd_open_unix(const gchar *path) +{ +#ifdef G_OS_UNIX + gint sock; + struct sockaddr_un addr; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + + if (sock < 0) { + perror("sock_open_unix(): socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + fd_close(sock); + return -1; + } + + if (listen(sock, 1) < 0) { + perror("listen"); + fd_close(sock); + return -1; + } + + return sock; +#else + return -1; +#endif +} + +gint fd_accept(gint sock) +{ +#ifdef G_OS_UNIX + struct sockaddr_in caddr; + guint caddr_len; + + caddr_len = sizeof(caddr); + return accept(sock, (struct sockaddr *)&caddr, &caddr_len); +#else + return -1; +#endif +} + + +static gint set_nonblocking_mode(gint fd, gboolean nonblock) +{ +#ifdef G_OS_UNIX + gint flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return -1; + } + + if (nonblock) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + return fcntl(fd, F_SETFL, flags); +#else + return -1; +#endif +} + +gint sock_set_nonblocking_mode(SockInfo *sock, gboolean nonblock) +{ + g_return_val_if_fail(sock != NULL, -1); + + return set_nonblocking_mode(sock->sock, nonblock); +} + +static gboolean is_nonblocking_mode(gint fd) +{ +#ifdef G_OS_UNIX + gint flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return FALSE; + } + + return ((flags & O_NONBLOCK) != 0); +#else + return FALSE; +#endif +} + +gboolean sock_is_nonblocking_mode(SockInfo *sock) +{ + g_return_val_if_fail(sock != NULL, FALSE); + + return is_nonblocking_mode(sock->sock); +} + + +static gboolean sock_prepare(GSource *source, gint *timeout) +{ + *timeout = 1; + return FALSE; +} + +static gboolean sock_check(GSource *source) +{ + SockInfo *sock = ((SockSource *)source)->sock; + struct timeval timeout = {0, 0}; + fd_set fds; + GIOCondition condition = sock->condition; + +#if USE_SSL + if (sock->ssl) { + if (condition & G_IO_IN) { + if (SSL_pending(sock->ssl) > 0) + return TRUE; + if (SSL_want_write(sock->ssl)) + condition |= G_IO_OUT; + } + + if (condition & G_IO_OUT) { + if (SSL_want_read(sock->ssl)) + condition |= G_IO_IN; + } + } +#endif + + FD_ZERO(&fds); + FD_SET(sock->sock, &fds); + + select(sock->sock + 1, + (condition & G_IO_IN) ? &fds : NULL, + (condition & G_IO_OUT) ? &fds : NULL, + NULL, &timeout); + + return FD_ISSET(sock->sock, &fds) != 0; +} + +static gboolean sock_dispatch(GSource *source, GSourceFunc callback, + gpointer user_data) +{ + SockInfo *sock = ((SockSource *)source)->sock; + + return sock->callback(sock, sock->condition, sock->data); +} + +static gboolean sock_watch_cb(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + SockInfo *sock = (SockInfo *)data; + + if ((condition & sock->condition) == 0) + return TRUE; + + return sock->callback(sock, sock->condition, sock->data); +} + +guint sock_add_watch(SockInfo *sock, GIOCondition condition, SockFunc func, + gpointer data) +{ + sock->callback = func; + sock->condition = condition; + sock->data = data; + +#if USE_SSL + if (sock->ssl) { + GSource *source; + + source = g_source_new(&sock_watch_funcs, sizeof(SockSource)); + ((SockSource *)source)->sock = sock; + g_source_set_priority(source, G_PRIORITY_DEFAULT); + g_source_set_can_recurse(source, FALSE); + return g_source_attach(source, NULL); + } +#endif + + return g_io_add_watch(sock->sock_ch, condition, sock_watch_cb, sock); +} + +static gint fd_check_io(gint fd, GIOCondition cond) +{ + struct timeval timeout; + fd_set fds; + + if (is_nonblocking_mode(fd)) + return 0; + + timeout.tv_sec = io_timeout; + timeout.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (cond == G_IO_IN) { + select(fd + 1, &fds, NULL, NULL, + io_timeout > 0 ? &timeout : NULL); + } else { + select(fd + 1, NULL, &fds, NULL, + io_timeout > 0 ? &timeout : NULL); + } + + if (FD_ISSET(fd, &fds)) { + return 0; + } else { + g_warning("Socket IO timeout\n"); + return -1; + } +} + +#ifdef G_OS_UNIX +static sigjmp_buf jmpenv; + +static void timeout_handler(gint sig) +{ + siglongjmp(jmpenv, 1); +} +#endif + +static gint sock_connect_with_timeout(gint sock, + const struct sockaddr *serv_addr, + gint addrlen, + guint timeout_secs) +{ + gint ret; +#ifdef G_OS_UNIX + void (*prev_handler)(gint); + + alarm(0); + prev_handler = signal(SIGALRM, timeout_handler); + if (sigsetjmp(jmpenv, 1)) { + alarm(0); + signal(SIGALRM, prev_handler); + errno = ETIMEDOUT; + return -1; + } + alarm(timeout_secs); +#endif + + ret = connect(sock, serv_addr, addrlen); + +#ifdef G_OS_UNIX + alarm(0); + signal(SIGALRM, prev_handler); +#endif + + return ret; +} + +struct hostent *my_gethostbyname(const gchar *hostname) +{ + struct hostent *hp; +#ifdef G_OS_UNIX + void (*prev_handler)(gint); + + alarm(0); + prev_handler = signal(SIGALRM, timeout_handler); + if (sigsetjmp(jmpenv, 1)) { + alarm(0); + signal(SIGALRM, prev_handler); + fprintf(stderr, "%s: host lookup timed out.\n", hostname); + errno = 0; + return NULL; + } + alarm(io_timeout); +#endif + + if ((hp = gethostbyname(hostname)) == NULL) { +#ifdef G_OS_UNIX + alarm(0); + signal(SIGALRM, prev_handler); +#endif + fprintf(stderr, "%s: unknown host.\n", hostname); + errno = 0; + return NULL; + } + +#ifdef G_OS_UNIX + alarm(0); + signal(SIGALRM, prev_handler); +#endif + + return hp; +} + +#ifndef INET6 +static gint my_inet_aton(const gchar *hostname, struct in_addr *inp) +{ +#if HAVE_INET_ATON + return inet_aton(hostname, inp); +#else +#if HAVE_INET_ADDR + guint32 inaddr; + + inaddr = inet_addr(hostname); + if (inaddr != -1) { + memcpy(inp, &inaddr, sizeof(inaddr)); + return 1; + } else + return 0; +#else + return 0; +#endif +#endif /* HAVE_INET_ATON */ +} + +static gint sock_connect_by_hostname(gint sock, const gchar *hostname, + gushort port) +{ + struct hostent *hp; + struct sockaddr_in ad; + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + ad.sin_port = htons(port); + + if (!my_inet_aton(hostname, &ad.sin_addr)) { + if ((hp = my_gethostbyname(hostname)) == NULL) { + fprintf(stderr, "%s: unknown host.\n", hostname); + errno = 0; + return -1; + } + + if (hp->h_length != 4 && hp->h_length != 8) { + fprintf(stderr, "illegal address length received for host %s\n", hostname); + errno = 0; + return -1; + } + + memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); + } + + return sock_connect_with_timeout(sock, (struct sockaddr *)&ad, + sizeof(ad), io_timeout); +} + +#else /* INET6 */ +static gint sock_connect_by_getaddrinfo(const gchar *hostname, gushort port) +{ + gint sock = -1, gai_error; + struct addrinfo hints, *res, *ai; + gchar port_str[6]; + + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_CANONNAME; */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* convert port from integer to string. */ + g_snprintf(port_str, sizeof(port_str), "%d", port); + + if ((gai_error = getaddrinfo(hostname, port_str, &hints, &res)) != 0) { + fprintf(stderr, "getaddrinfo for %s:%s failed: %s\n", + hostname, port_str, gai_strerror(gai_error)); + return -1; + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) + continue; + + if (sock_connect_with_timeout + (sock, ai->ai_addr, ai->ai_addrlen, io_timeout) == 0) + break; + + fd_close(sock); + } + + if (res != NULL) + freeaddrinfo(res); + + if (ai == NULL) + return -1; + + return sock; +} +#endif /* !INET6 */ + +SockInfo *sock_connect(const gchar *hostname, gushort port) +{ +#ifdef G_OS_WIN32 + SOCKET sock; +#else + gint sock; +#endif + SockInfo *sockinfo; + +#ifdef INET6 + if ((sock = sock_connect_by_getaddrinfo(hostname, port)) < 0) + return NULL; +#else +#ifdef G_OS_WIN32 + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { + g_warning("socket() failed: %ld\n", WSAGetLastError()); +#else + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("socket"); +#endif /* G_OS_WIN32 */ + return NULL; + } + + if (sock_connect_by_hostname(sock, hostname, port) < 0) { + if (errno != 0) perror("connect"); + fd_close(sock); + return NULL; + } +#endif /* INET6 */ + + sockinfo = g_new0(SockInfo, 1); + sockinfo->sock = sock; + sockinfo->sock_ch = g_io_channel_unix_new(sock); + sockinfo->hostname = g_strdup(hostname); + sockinfo->port = port; + sockinfo->state = CONN_ESTABLISHED; + + g_usleep(100000); + + return sockinfo; +} + +#ifdef G_OS_UNIX +static void sock_address_list_free(GList *addr_list) +{ + GList *cur; + + for (cur = addr_list; cur != NULL; cur = cur->next) { + SockAddrData *addr_data = (SockAddrData *)cur->data; + g_free(addr_data->addr); + g_free(addr_data); + } + + g_list_free(addr_list); +} + +/* asynchronous TCP connection */ + +static gboolean sock_connect_async_cb(GIOChannel *source, + GIOCondition condition, gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + gint fd; + gint val; + guint len; + SockInfo *sockinfo; + + fd = g_io_channel_unix_get_fd(source); + + conn_data->io_tag = 0; + conn_data->channel = NULL; + g_io_channel_unref(source); + + len = sizeof(val); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len) < 0) { + perror("getsockopt"); + fd_close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + if (val != 0) { + fd_close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + sockinfo = g_new0(SockInfo, 1); + sockinfo->sock = fd; + sockinfo->sock_ch = g_io_channel_unix_new(fd); + sockinfo->hostname = g_strdup(conn_data->hostname); + sockinfo->port = conn_data->port; + sockinfo->state = CONN_ESTABLISHED; + + conn_data->func(sockinfo, conn_data->data); + + sock_connect_async_cancel(conn_data->id); + + return FALSE; +} + +static gint sock_connect_async_get_address_info_cb(GList *addr_list, + gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + + conn_data->addr_list = addr_list; + conn_data->cur_addr = addr_list; + conn_data->lookup_data = NULL; + + return sock_connect_address_list_async(conn_data); +} + +gint sock_connect_async(const gchar *hostname, gushort port, + SockConnectFunc func, gpointer data) +{ + static gint id = 1; + SockConnectData *conn_data; + + conn_data = g_new0(SockConnectData, 1); + conn_data->id = id++; + conn_data->hostname = g_strdup(hostname); + conn_data->port = port; + conn_data->addr_list = NULL; + conn_data->cur_addr = NULL; + conn_data->io_tag = 0; + conn_data->func = func; + conn_data->data = data; + + conn_data->lookup_data = sock_get_address_info_async + (hostname, port, sock_connect_async_get_address_info_cb, + conn_data); + + if (conn_data->lookup_data == NULL) { + g_free(conn_data->hostname); + g_free(conn_data); + return -1; + } + + sock_connect_data_list = g_list_append(sock_connect_data_list, + conn_data); + + return conn_data->id; +} + +gint sock_connect_async_cancel(gint id) +{ + SockConnectData *conn_data = NULL; + GList *cur; + + for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) { + if (((SockConnectData *)cur->data)->id == id) { + conn_data = (SockConnectData *)cur->data; + break; + } + } + + if (conn_data) { + sock_connect_data_list = g_list_remove(sock_connect_data_list, + conn_data); + + if (conn_data->lookup_data) + sock_get_address_info_async_cancel + (conn_data->lookup_data); + + if (conn_data->io_tag > 0) + g_source_remove(conn_data->io_tag); + if (conn_data->channel) { + g_io_channel_shutdown(conn_data->channel, FALSE, NULL); + g_io_channel_unref(conn_data->channel); + } + + sock_address_list_free(conn_data->addr_list); + g_free(conn_data->hostname); + g_free(conn_data); + } else { + g_warning("sock_connect_async_cancel: id %d not found.\n", id); + return -1; + } + + return 0; +} + +static gint sock_connect_address_list_async(SockConnectData *conn_data) +{ + SockAddrData *addr_data; + gint sock = -1; + + for (; conn_data->cur_addr != NULL; + conn_data->cur_addr = conn_data->cur_addr->next) { + addr_data = (SockAddrData *)conn_data->cur_addr->data; + + if ((sock = socket(addr_data->family, addr_data->socktype, + addr_data->protocol)) < 0) { + perror("socket"); + continue; + } + + set_nonblocking_mode(sock, TRUE); + + if (connect(sock, addr_data->addr, addr_data->addr_len) < 0) { + if (EINPROGRESS == errno) { + break; + } else { + perror("connect"); + fd_close(sock); + } + } else + break; + } + + if (conn_data->cur_addr == NULL) { + g_warning("sock_connect_address_list_async: " + "connection to %s:%d failed\n", + conn_data->hostname, conn_data->port); + conn_data->func(NULL, conn_data->data); + sock_connect_async_cancel(conn_data->id); + return -1; + } + + conn_data->cur_addr = conn_data->cur_addr->next; + + conn_data->channel = g_io_channel_unix_new(sock); + conn_data->io_tag = g_io_add_watch(conn_data->channel, G_IO_OUT, + sock_connect_async_cb, conn_data); + + return 0; +} + +/* asynchronous DNS lookup */ + +static gboolean sock_get_address_info_async_cb(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + SockLookupData *lookup_data = (SockLookupData *)data; + GList *addr_list = NULL; + SockAddrData *addr_data; + gsize bytes_read; + gint ai_member[4]; + struct sockaddr *addr; + + for (;;) { + if (g_io_channel_read(source, (gchar *)ai_member, + sizeof(ai_member), &bytes_read) + != G_IO_ERROR_NONE) { + g_warning("sock_get_address_info_async_cb: " + "address length read error\n"); + break; + } + + if (bytes_read == 0 || bytes_read != sizeof(ai_member)) + break; + + if (ai_member[0] == AF_UNSPEC) { + g_warning("DNS lookup failed\n"); + break; + } + + addr = g_malloc(ai_member[3]); + if (g_io_channel_read(source, (gchar *)addr, ai_member[3], + &bytes_read) + != G_IO_ERROR_NONE) { + g_warning("sock_get_address_info_async_cb: " + "address data read error\n"); + g_free(addr); + break; + } + + if (bytes_read != ai_member[3]) { + g_warning("sock_get_address_info_async_cb: " + "incomplete address data\n"); + g_free(addr); + break; + } + + addr_data = g_new0(SockAddrData, 1); + addr_data->family = ai_member[0]; + addr_data->socktype = ai_member[1]; + addr_data->protocol = ai_member[2]; + addr_data->addr_len = ai_member[3]; + addr_data->addr = addr; + + addr_list = g_list_append(addr_list, addr_data); + } + + g_io_channel_shutdown(source, FALSE, NULL); + g_io_channel_unref(source); + + kill(lookup_data->child_pid, SIGKILL); + waitpid(lookup_data->child_pid, NULL, 0); + + lookup_data->func(addr_list, lookup_data->data); + + g_free(lookup_data->hostname); + g_free(lookup_data); + + return FALSE; +} + +static SockLookupData *sock_get_address_info_async(const gchar *hostname, + gushort port, + SockAddrFunc func, + gpointer data) +{ + SockLookupData *lookup_data = NULL; + gint pipe_fds[2]; + pid_t pid; + + if (pipe(pipe_fds) < 0) { + perror("pipe"); + func(NULL, data); + return NULL; + } + + if ((pid = fork()) < 0) { + perror("fork"); + func(NULL, data); + return NULL; + } + + /* child process */ + if (pid == 0) { +#ifdef INET6 + gint gai_err; + struct addrinfo hints, *res, *ai; + gchar port_str[6]; +#else /* !INET6 */ + struct hostent *hp; + gchar **addr_list_p; + struct sockaddr_in ad; +#endif /* INET6 */ + gint ai_member[4] = {AF_UNSPEC, 0, 0, 0}; + + close(pipe_fds[0]); + +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_CANONNAME; */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + g_snprintf(port_str, sizeof(port_str), "%d", port); + + gai_err = getaddrinfo(hostname, port_str, &hints, &res); + if (gai_err != 0) { + g_warning("getaddrinfo for %s:%s failed: %s\n", + hostname, port_str, gai_strerror(gai_err)); + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + close(pipe_fds[1]); + _exit(1); + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + ai_member[0] = ai->ai_family; + ai_member[1] = ai->ai_socktype; + ai_member[2] = ai->ai_protocol; + ai_member[3] = ai->ai_addrlen; + + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + fd_write_all(pipe_fds[1], (gchar *)ai->ai_addr, + ai->ai_addrlen); + } + + if (res != NULL) + freeaddrinfo(res); +#else /* !INET6 */ + hp = my_gethostbyname(hostname); + if (hp == NULL || hp->h_addrtype != AF_INET) { + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + close(pipe_fds[1]); + _exit(1); + } + + ai_member[0] = AF_INET; + ai_member[1] = SOCK_STREAM; + ai_member[2] = IPPROTO_TCP; + ai_member[3] = sizeof(ad); + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + ad.sin_port = htons(port); + + for (addr_list_p = hp->h_addr_list; *addr_list_p != NULL; + addr_list_p++) { + memcpy(&ad.sin_addr, *addr_list_p, hp->h_length); + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + fd_write_all(pipe_fds[1], (gchar *)&ad, sizeof(ad)); + } +#endif /* INET6 */ + + close(pipe_fds[1]); + + _exit(0); + } else { + close(pipe_fds[1]); + + lookup_data = g_new0(SockLookupData, 1); + lookup_data->hostname = g_strdup(hostname); + lookup_data->child_pid = pid; + lookup_data->func = func; + lookup_data->data = data; + + lookup_data->channel = g_io_channel_unix_new(pipe_fds[0]); + lookup_data->io_tag = g_io_add_watch + (lookup_data->channel, G_IO_IN, + sock_get_address_info_async_cb, lookup_data); + } + + return lookup_data; +} + +static gint sock_get_address_info_async_cancel(SockLookupData *lookup_data) +{ + if (lookup_data->io_tag > 0) + g_source_remove(lookup_data->io_tag); + if (lookup_data->channel) { + g_io_channel_shutdown(lookup_data->channel, FALSE, NULL); + g_io_channel_unref(lookup_data->channel); + } + + if (lookup_data->child_pid > 0) { + kill(lookup_data->child_pid, SIGKILL); + waitpid(lookup_data->child_pid, NULL, 0); + } + + g_free(lookup_data->hostname); + g_free(lookup_data); + + return 0; +} +#endif /* G_OS_UNIX */ + + +gint sock_printf(SockInfo *sock, const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + return sock_write_all(sock, buf, strlen(buf)); +} + +gint sock_read(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_read(sock->ssl, buf, len); +#endif + return fd_read(sock->sock, buf, len); +} + +gint fd_read(gint fd, gchar *buf, gint len) +{ + if (fd_check_io(fd, G_IO_IN) < 0) + return -1; + +#ifdef G_OS_WIN32 + return recv(fd, buf, len, 0); +#else + return read(fd, buf, len); +#endif +} + +#if USE_SSL +gint ssl_read(SSL *ssl, gchar *buf, gint len) +{ + gint err, ret; + + if (SSL_pending(ssl) == 0) { + if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0) + return -1; + } + + ret = SSL_read(ssl, buf, len); + + switch ((err = SSL_get_error(ssl, ret))) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + case SSL_ERROR_ZERO_RETURN: + return 0; + default: + g_warning("SSL_read() returned error %d, ret = %d\n", err, ret); + if (ret == 0) + return 0; + return -1; + } +} +#endif + +gint sock_write(SockInfo *sock, const gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_write(sock->ssl, buf, len); +#endif + return fd_write(sock->sock, buf, len); +} + +gint fd_write(gint fd, const gchar *buf, gint len) +{ + if (fd_check_io(fd, G_IO_OUT) < 0) + return -1; + +#ifdef G_OS_WIN32 + return send(fd, buf, len, 0); +#else + return write(fd, buf, len); +#endif +} + +#if USE_SSL +gint ssl_write(SSL *ssl, const gchar *buf, gint len) +{ + gint ret; + + ret = SSL_write(ssl, buf, len); + + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + default: + return -1; + } +} +#endif + +gint sock_write_all(SockInfo *sock, const gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_write_all(sock->ssl, buf, len); +#endif + return fd_write_all(sock->sock, buf, len); +} + +gint fd_write_all(gint fd, const gchar *buf, gint len) +{ + gint n, wrlen = 0; + + while (len) { + n = fd_write(fd, buf, len); + if (n <= 0) + return -1; + len -= n; + wrlen += n; + buf += n; + } + + return wrlen; +} + +#if USE_SSL +gint ssl_write_all(SSL *ssl, const gchar *buf, gint len) +{ + gint n, wrlen = 0; + + while (len) { + n = ssl_write(ssl, buf, len); + if (n <= 0) + return -1; + len -= n; + wrlen += n; + buf += n; + } + + return wrlen; +} +#endif + +gint fd_recv(gint fd, gchar *buf, gint len, gint flags) +{ + if (fd_check_io(fd, G_IO_IN) < 0) + return -1; + + return recv(fd, buf, len, flags); +} + +gint fd_gets(gint fd, gchar *buf, gint len) +{ + gchar *newline, *bp = buf; + gint n; + + if (--len < 1) + return -1; + do { + if ((n = fd_recv(fd, bp, len, MSG_PEEK)) <= 0) + return -1; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = fd_read(fd, bp, n)) < 0) + return -1; + bp += n; + len -= n; + } while (!newline && len); + + *bp = '\0'; + return bp - buf; +} + +#if USE_SSL +gint ssl_gets(SSL *ssl, gchar *buf, gint len) +{ + gchar *newline, *bp = buf; + gint n; + + if (--len < 1) + return -1; + do { + if ((n = ssl_peek(ssl, bp, len)) <= 0) + return -1; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = ssl_read(ssl, bp, n)) < 0) + return -1; + bp += n; + len -= n; + } while (!newline && len); + + *bp = '\0'; + return bp - buf; +} +#endif + +gint sock_gets(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_gets(sock->ssl, buf, len); +#endif + return fd_gets(sock->sock, buf, len); +} + +gint fd_getline(gint fd, gchar **line) +{ + gchar buf[BUFFSIZE]; + gchar *str = NULL; + gint len; + gulong size = 0; + gulong cur_offset = 0; + + while ((len = fd_gets(fd, buf, sizeof(buf))) > 0) { + size += len; + str = g_realloc(str, size + 1); + memcpy(str + cur_offset, buf, len + 1); + cur_offset += len; + if (buf[len - 1] == '\n') + break; + } + + *line = str; + + if (!str) + return -1; + else + return (gint)size; +} + +#if USE_SSL +gint ssl_getline(SSL *ssl, gchar **line) +{ + gchar buf[BUFFSIZE]; + gchar *str = NULL; + gint len; + gulong size = 0; + gulong cur_offset = 0; + + while ((len = ssl_gets(ssl, buf, sizeof(buf))) > 0) { + size += len; + str = g_realloc(str, size + 1); + memcpy(str + cur_offset, buf, len + 1); + cur_offset += len; + if (buf[len - 1] == '\n') + break; + } + + *line = str; + + if (!str) + return -1; + else + return (gint)size; +} +#endif + +gint sock_getline(SockInfo *sock, gchar **line) +{ + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(line != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_getline(sock->ssl, line); +#endif + return fd_getline(sock->sock, line); +} + +gint sock_puts(SockInfo *sock, const gchar *buf) +{ + gint ret; + + if ((ret = sock_write_all(sock, buf, strlen(buf))) < 0) + return ret; + return sock_write_all(sock, "\r\n", 2); +} + +/* peek at the socket data without actually reading it */ +#if USE_SSL +gint ssl_peek(SSL *ssl, gchar *buf, gint len) +{ + gint err, ret; + + if (SSL_pending(ssl) == 0) { + if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0) + return -1; + } + + ret = SSL_peek(ssl, buf, len); + + switch ((err = SSL_get_error(ssl, ret))) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + case SSL_ERROR_ZERO_RETURN: + return 0; + default: + g_warning("SSL_peek() returned error %d, ret = %d\n", err, ret); + if (ret == 0) + return 0; + return -1; + } +} +#endif + +gint sock_peek(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_peek(sock->ssl, buf, len); +#endif + return fd_recv(sock->sock, buf, len, MSG_PEEK); +} + +gint sock_close(SockInfo *sock) +{ + if (!sock) + return 0; + +#if USE_SSL + if (sock->ssl) + ssl_done_socket(sock); +#endif + + if (sock->sock_ch) { + g_io_channel_shutdown(sock->sock_ch, FALSE, NULL); + g_io_channel_unref(sock->sock_ch); + } + + g_free(sock->hostname); + g_free(sock); + + return 0; +} + +gint fd_close(gint fd) +{ +#ifdef G_OS_WIN32 + return closesocket(fd); +#else + return close(fd); +#endif +} diff --git a/libsylph/socket.h b/libsylph/socket.h new file mode 100644 index 00000000..5721a304 --- /dev/null +++ b/libsylph/socket.h @@ -0,0 +1,124 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#if HAVE_NETDB_H +# include <netdb.h> +#endif + +typedef struct _SockInfo SockInfo; + +#if USE_SSL +# include "ssl.h" +#endif + +typedef enum +{ + CONN_READY, + CONN_LOOKUPSUCCESS, + CONN_ESTABLISHED, + CONN_LOOKUPFAILED, + CONN_FAILED +} ConnectionState; + +typedef gint (*SockConnectFunc) (SockInfo *sock, + gpointer data); +typedef gboolean (*SockFunc) (SockInfo *sock, + GIOCondition condition, + gpointer data); + +struct _SockInfo +{ + gint sock; +#if USE_SSL + SSL *ssl; +#endif + GIOChannel *sock_ch; + + gchar *hostname; + gushort port; + ConnectionState state; + gpointer data; + + SockFunc callback; + GIOCondition condition; +}; + +gint sock_init (void); +gint sock_cleanup (void); + +gint sock_set_io_timeout (guint sec); + +gint sock_set_nonblocking_mode (SockInfo *sock, gboolean nonblock); +gboolean sock_is_nonblocking_mode (SockInfo *sock); + +guint sock_add_watch (SockInfo *sock, GIOCondition condition, + SockFunc func, gpointer data); + +struct hostent *my_gethostbyname (const gchar *hostname); + +SockInfo *sock_connect (const gchar *hostname, gushort port); +#ifdef G_OS_UNIX +gint sock_connect_async (const gchar *hostname, gushort port, + SockConnectFunc func, gpointer data); +gint sock_connect_async_cancel (gint id); +#endif + +/* Basic I/O functions */ +gint sock_printf (SockInfo *sock, const gchar *format, ...) + G_GNUC_PRINTF(2, 3); +gint sock_read (SockInfo *sock, gchar *buf, gint len); +gint sock_write (SockInfo *sock, const gchar *buf, gint len); +gint sock_write_all (SockInfo *sock, const gchar *buf, gint len); +gint sock_gets (SockInfo *sock, gchar *buf, gint len); +gint sock_getline (SockInfo *sock, gchar **line); +gint sock_puts (SockInfo *sock, const gchar *buf); +gint sock_peek (SockInfo *sock, gchar *buf, gint len); +gint sock_close (SockInfo *sock); + +/* Functions to directly work on FD. They are needed for pipes */ +gint fd_connect_unix (const gchar *path); +gint fd_open_unix (const gchar *path); +gint fd_accept (gint sock); + +gint fd_read (gint sock, gchar *buf, gint len); +gint fd_write (gint sock, const gchar *buf, gint len); +gint fd_write_all (gint sock, const gchar *buf, gint len); +gint fd_gets (gint sock, gchar *buf, gint len); +gint fd_getline (gint sock, gchar **line); +gint fd_close (gint sock); + +/* Functions for SSL */ +#if USE_SSL +gint ssl_read (SSL *ssl, gchar *buf, gint len); +gint ssl_write (SSL *ssl, const gchar *buf, gint len); +gint ssl_write_all (SSL *ssl, const gchar *buf, gint len); +gint ssl_gets (SSL *ssl, gchar *buf, gint len); +gint ssl_getline (SSL *ssl, gchar **line); +gint ssl_peek (SSL *ssl, gchar *buf, gint len); +#endif + +#endif /* __SOCKET_H__ */ diff --git a/libsylph/ssl.c b/libsylph/ssl.c new file mode 100644 index 00000000..b57c60a9 --- /dev/null +++ b/libsylph/ssl.c @@ -0,0 +1,175 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include "defs.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include "utils.h" +#include "ssl.h" + +static SSL_CTX *ssl_ctx_SSLv23; +static SSL_CTX *ssl_ctx_TLSv1; + +void ssl_init(void) +{ + gchar *certs_dir; + + SSL_library_init(); + SSL_load_error_strings(); + + certs_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "certs", NULL); + if (!is_dir_exist(certs_dir)) { + debug_print("%s doesn't exist, or not a directory.\n", + certs_dir); + g_free(certs_dir); + certs_dir = NULL; + } + + ssl_ctx_SSLv23 = SSL_CTX_new(SSLv23_client_method()); + if (ssl_ctx_SSLv23 == NULL) { + debug_print(_("SSLv23 not available\n")); + } else { + debug_print(_("SSLv23 available\n")); + if (certs_dir && + !SSL_CTX_load_verify_locations(ssl_ctx_SSLv23, NULL, + certs_dir)) + g_warning("SSLv23 SSL_CTX_load_verify_locations failed.\n"); + } + + ssl_ctx_TLSv1 = SSL_CTX_new(TLSv1_client_method()); + if (ssl_ctx_TLSv1 == NULL) { + debug_print(_("TLSv1 not available\n")); + } else { + debug_print(_("TLSv1 available\n")); + if (certs_dir && + !SSL_CTX_load_verify_locations(ssl_ctx_TLSv1, NULL, + certs_dir)) + g_warning("TLSv1 SSL_CTX_load_verify_locations failed.\n"); + } + + g_free(certs_dir); +} + +void ssl_done(void) +{ + if (ssl_ctx_SSLv23) { + SSL_CTX_free(ssl_ctx_SSLv23); + } + + if (ssl_ctx_TLSv1) { + SSL_CTX_free(ssl_ctx_TLSv1); + } +} + +gboolean ssl_init_socket(SockInfo *sockinfo) +{ + return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23); +} + +gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) +{ + X509 *server_cert; + gint ret; + + switch (method) { + case SSL_METHOD_SSLv23: + if (!ssl_ctx_SSLv23) { + g_warning(_("SSL method not available\n")); + return FALSE; + } + sockinfo->ssl = SSL_new(ssl_ctx_SSLv23); + break; + case SSL_METHOD_TLSv1: + if (!ssl_ctx_TLSv1) { + g_warning(_("SSL method not available\n")); + return FALSE; + } + sockinfo->ssl = SSL_new(ssl_ctx_TLSv1); + break; + default: + g_warning(_("Unknown SSL method *PROGRAM BUG*\n")); + return FALSE; + break; + } + + if (sockinfo->ssl == NULL) { + g_warning(_("Error creating ssl context\n")); + return FALSE; + } + + SSL_set_fd(sockinfo->ssl, sockinfo->sock); + if ((ret = SSL_connect(sockinfo->ssl)) == -1) { + g_warning(_("SSL connect failed (%s)\n"), + ERR_error_string(ERR_get_error(), NULL)); + return FALSE; + } + + /* Get the cipher */ + + debug_print(_("SSL connection using %s\n"), + SSL_get_cipher(sockinfo->ssl)); + + /* Get server's certificate (note: beware of dynamic allocation) */ + + if ((server_cert = SSL_get_peer_certificate(sockinfo->ssl)) != NULL) { + gchar *str; + glong verify_result; + + debug_print(_("Server certificate:\n")); + + if ((str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0)) != NULL) { + debug_print(_(" Subject: %s\n"), str); + g_free(str); + } + + if ((str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0)) != NULL) { + debug_print(_(" Issuer: %s\n"), str); + g_free(str); + } + + verify_result = SSL_get_verify_result(sockinfo->ssl); + if (verify_result == X509_V_OK) + debug_print("SSL verify OK\n"); + else + g_warning("%s: SSL certificate verify failed (%ld: %s)\n", + sockinfo->hostname, verify_result, + X509_verify_cert_error_string(verify_result)); + + X509_free(server_cert); + } + + return TRUE; +} + +void ssl_done_socket(SockInfo *sockinfo) +{ + if (sockinfo->ssl) { + SSL_free(sockinfo->ssl); + } +} + +#endif /* USE_SSL */ diff --git a/libsylph/ssl.h b/libsylph/ssl.h new file mode 100644 index 00000000..6c13ddac --- /dev/null +++ b/libsylph/ssl.h @@ -0,0 +1,58 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SSL_H__ +#define __SSL_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include <glib.h> +#include <openssl/crypto.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> +#include <openssl/err.h> + +#include "socket.h" + +typedef enum { + SSL_METHOD_SSLv23, + SSL_METHOD_TLSv1 +} SSLMethod; + +typedef enum { + SSL_NONE, + SSL_TUNNEL, + SSL_STARTTLS +} SSLType; + +void ssl_init (void); +void ssl_done (void); +gboolean ssl_init_socket (SockInfo *sockinfo); +gboolean ssl_init_socket_with_method (SockInfo *sockinfo, + SSLMethod method); +void ssl_done_socket (SockInfo *sockinfo); + +#endif /* USE_SSL */ + +#endif /* __SSL_H__ */ diff --git a/libsylph/stringtable.c b/libsylph/stringtable.c new file mode 100644 index 00000000..da5e0ea4 --- /dev/null +++ b/libsylph/stringtable.c @@ -0,0 +1,163 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <glib.h> +#include <string.h> + +#include "stringtable.h" +#include "utils.h" + +/* alfons - hashed string table (I wasn't content with GStringChunk; + * can't recall why :-) */ + +#if 0 +#define XXX_DEBUG \ + debug_print +#else +#define XXX_DEBUG \ + if (0) debug_print +#endif + +typedef struct StringEntry_ { + gint ref_count; + gchar *string; +} StringEntry; + +static StringEntry *string_entry_new(const gchar *str) +{ + StringEntry *entry; + + entry = g_new0(StringEntry, 1); + entry->ref_count = 1; + entry->string = g_strdup(str); + return entry; +} + +static void string_entry_free(StringEntry *entry) +{ + g_return_if_fail(entry != NULL); + + g_free(entry->string); + g_free(entry); +} + +StringTable *string_table_new(void) +{ + StringTable *strtable; + + strtable = g_new0(StringTable, 1); + g_return_val_if_fail(strtable != NULL, NULL); + strtable->hash_table = g_hash_table_new(g_str_hash, g_str_equal); + g_return_val_if_fail(strtable->hash_table, NULL); + return strtable; +} + +gchar *string_table_lookup_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + return entry->string; + } else { + return NULL; + } +} + +gchar *string_table_insert_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + entry->ref_count++; + XXX_DEBUG ("ref++ for %s (%d)\n", entry->string, + entry->ref_count); + } else { + entry = string_entry_new(str); + XXX_DEBUG ("inserting %s\n", str); + /* insert entry->string instead of str, since it can be + * invalid pointer after this. */ + g_hash_table_insert(table->hash_table, entry->string, entry); + } + + return entry->string; +} + +void string_table_free_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + entry->ref_count--; + if (entry->ref_count <= 0) { + XXX_DEBUG ("refcount of string %s dropped to zero\n", + entry->string); + g_hash_table_remove(table->hash_table, str); + string_entry_free(entry); + } else { + XXX_DEBUG ("ref-- for %s (%d)\n", entry->string, + entry->ref_count); + } + } +} + +static gboolean string_table_remove_for_each_fn(gchar *key, StringEntry *entry, + gpointer user_data) +{ + g_return_val_if_fail(key != NULL, TRUE); + g_return_val_if_fail(entry != NULL, TRUE); + + string_entry_free(entry); + + return TRUE; +} + +void string_table_free(StringTable *table) +{ + g_return_if_fail(table != NULL); + g_return_if_fail(table->hash_table != NULL); + + g_hash_table_foreach_remove(table->hash_table, + (GHRFunc)string_table_remove_for_each_fn, + NULL); + g_hash_table_destroy(table->hash_table); + g_free(table); +} + +static void string_table_stats_for_each_fn(gchar *key, StringEntry *entry, + guint *totals) +{ + if (entry->ref_count > 1) { + *totals += strlen(key) * (entry->ref_count - 1); + } +} + +void string_table_get_stats(StringTable *table) +{ + guint totals = 0; + + g_hash_table_foreach(table->hash_table, + (GHFunc)string_table_stats_for_each_fn, &totals); + XXX_DEBUG ("TOTAL UNSPILLED %d (%dK)\n", totals, totals / 1024); +} diff --git a/libsylph/stringtable.h b/libsylph/stringtable.h new file mode 100644 index 00000000..337ef2c7 --- /dev/null +++ b/libsylph/stringtable.h @@ -0,0 +1,38 @@ +/* + * sylpheed -- a gtk+ based, lightweight, and fast e-mail client + * copyright (c) 1999-2001 hiroyuki yamamoto + * + * this program is free software; you can redistribute it and/or modify + * it under the terms of the gnu general public license as published by + * the free software foundation; either version 2 of the license, or + * (at your option) any later version. + * + * this program 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 general public license for more details. + * + * you should have received a copy of the gnu general public license + * along with this program; if not, write to the free software + * foundation, inc., 59 temple place - suite 330, boston, ma 02111-1307, usa. + */ + +#ifndef STRINGTABLE_H__ +#define STRINGTABLE_H__ + +#include <glib.h> + +typedef struct { + GHashTable *hash_table; +} StringTable; + +StringTable *string_table_new (void); +void string_table_free (StringTable *table); + +gchar *string_table_lookup_string (StringTable *table, const gchar *str); +gchar *string_table_insert_string (StringTable *table, const gchar *str); +void string_table_free_string (StringTable *table, const gchar *str); + +void string_table_get_stats (StringTable *table); + +#endif /* STRINGTABLE_H__ */ diff --git a/libsylph/unmime.c b/libsylph/unmime.c new file mode 100644 index 00000000..23d787a3 --- /dev/null +++ b/libsylph/unmime.c @@ -0,0 +1,134 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <string.h> +#include <ctype.h> + +#include "codeconv.h" +#include "base64.h" +#include "quoted-printable.h" + +#define ENCODED_WORD_BEGIN "=?" +#define ENCODED_WORD_END "?=" + +/* Decodes headers based on RFC2045 and RFC2047. */ + +gchar *unmime_header(const gchar *encoded_str) +{ + const gchar *p = encoded_str; + const gchar *eword_begin_p, *encoding_begin_p, *text_begin_p, + *eword_end_p; + gchar charset[32]; + gchar encoding; + gchar *conv_str; + GString *outbuf; + gchar *out_str; + gsize out_len; + + outbuf = g_string_sized_new(strlen(encoded_str) * 2); + + while (*p != '\0') { + gchar *decoded_text = NULL; + gint len; + + eword_begin_p = strstr(p, ENCODED_WORD_BEGIN); + if (!eword_begin_p) { + g_string_append(outbuf, p); + break; + } + encoding_begin_p = strchr(eword_begin_p + 2, '?'); + if (!encoding_begin_p) { + g_string_append(outbuf, p); + break; + } + text_begin_p = strchr(encoding_begin_p + 1, '?'); + if (!text_begin_p) { + g_string_append(outbuf, p); + break; + } + eword_end_p = strstr(text_begin_p + 1, ENCODED_WORD_END); + if (!eword_end_p) { + g_string_append(outbuf, p); + break; + } + + if (p == encoded_str) { + g_string_append_len(outbuf, p, eword_begin_p - p); + p = eword_begin_p; + } else { + /* ignore spaces between encoded words */ + const gchar *sp; + + for (sp = p; sp < eword_begin_p; sp++) { + if (!g_ascii_isspace(*sp)) { + g_string_append_len + (outbuf, p, eword_begin_p - p); + p = eword_begin_p; + break; + } + } + } + + len = MIN(sizeof(charset) - 1, + encoding_begin_p - (eword_begin_p + 2)); + memcpy(charset, eword_begin_p + 2, len); + charset[len] = '\0'; + encoding = g_ascii_toupper(*(encoding_begin_p + 1)); + + if (encoding == 'B') { + decoded_text = g_malloc + (eword_end_p - (text_begin_p + 1) + 1); + len = base64_decode(decoded_text, text_begin_p + 1, + eword_end_p - (text_begin_p + 1)); + decoded_text[len] = '\0'; + } else if (encoding == 'Q') { + decoded_text = g_malloc + (eword_end_p - (text_begin_p + 1) + 1); + len = qp_decode_q_encoding + (decoded_text, text_begin_p + 1, + eword_end_p - (text_begin_p + 1)); + } else { + g_string_append_len(outbuf, p, eword_end_p + 2 - p); + p = eword_end_p + 2; + continue; + } + + /* convert to UTF-8 */ + conv_str = conv_codeset_strdup(decoded_text, charset, NULL); + if (!conv_str) + conv_str = conv_utf8todisp(decoded_text, NULL); + g_string_append(outbuf, conv_str); + g_free(conv_str); + + g_free(decoded_text); + + p = eword_end_p + 2; + } + + out_str = outbuf->str; + out_len = outbuf->len; + g_string_free(outbuf, FALSE); + + return g_realloc(out_str, out_len + 1); +} diff --git a/libsylph/unmime.h b/libsylph/unmime.h new file mode 100644 index 00000000..be9f390e --- /dev/null +++ b/libsylph/unmime.h @@ -0,0 +1,27 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __UNMIME_H__ +#define __UNMIME_H__ + +#include <glib.h> + +gchar *unmime_header (const gchar *encoded_str); + +#endif /* __UNMIME_H__ */ diff --git a/libsylph/utils.c b/libsylph/utils.c new file mode 100644 index 00000000..dc72b814 --- /dev/null +++ b/libsylph/utils.c @@ -0,0 +1,3436 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#if (HAVE_WCTYPE_H && HAVE_WCHAR_H) +# include <wchar.h> +# include <wctype.h> +#endif +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdarg.h> +#include <sys/types.h> +#if HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#include <dirent.h> +#include <time.h> + +#ifdef G_OS_WIN32 +# include <direct.h> +# include <io.h> +#endif + +#include "utils.h" +#include "socket.h" + +#define BUFFSIZE 8192 + +static gboolean debug_mode = FALSE; + + +#if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX) +gint g_chdir(const gchar *path) +{ +#ifdef G_OS_WIN32 + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + gint retval; + gint save_errno; + + wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL); + if (wpath == NULL) { + errno = EINVAL; + return -1; + } + + retval = _wchdir(wpath); + save_errno = errno; + + g_free(wpath); + + errno = save_errno; + return retval; + } else { + gchar *cp_path; + gint retval; + gint save_errno; + + cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL); + if (cp_path == NULL) { + errno = EINVAL; + return -1; + } + + retval = chdir(cp_path); + save_errno = errno; + + g_free(cp_path); + + errno = save_errno; + return retval; + } +#else + return chdir(path); +#endif +} + +gint g_chmod(const gchar *path, gint mode) +{ +#ifdef G_OS_WIN32 + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + gint retval; + gint save_errno; + + wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL); + if (wpath == NULL) { + errno = EINVAL; + return -1; + } + + retval = _wchmod(wpath, mode); + save_errno = errno; + + g_free(wpath); + + errno = save_errno; + return retval; + } else { + gchar *cp_path; + gint retval; + gint save_errno; + + cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL); + if (cp_path == NULL) { + errno = EINVAL; + return -1; + } + + retval = chmod(cp_path, mode); + save_errno = errno; + + g_free(cp_path); + + errno = save_errno; + return retval; + } +#else + return chmod(path, mode); +#endif +} +#endif /* GLIB_CHECK_VERSION && G_OS_UNIX */ + +void list_free_strings(GList *list) +{ + list = g_list_first(list); + + while (list != NULL) { + g_free(list->data); + list = list->next; + } +} + +void slist_free_strings(GSList *list) +{ + while (list != NULL) { + g_free(list->data); + list = list->next; + } +} + +static void hash_free_strings_func(gpointer key, gpointer value, gpointer data) +{ + g_free(key); +} + +void hash_free_strings(GHashTable *table) +{ + g_hash_table_foreach(table, hash_free_strings_func, NULL); +} + +static void hash_free_value_mem_func(gpointer key, gpointer value, + gpointer data) +{ + g_free(value); +} + +void hash_free_value_mem(GHashTable *table) +{ + g_hash_table_foreach(table, hash_free_value_mem_func, NULL); +} + +gint str_case_equal(gconstpointer v, gconstpointer v2) +{ + return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0; +} + +guint str_case_hash(gconstpointer key) +{ + const gchar *p = key; + guint h = *p; + + if (h) { + h = g_ascii_tolower(h); + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + g_ascii_tolower(*p); + } + + return h; +} + +void ptr_array_free_strings(GPtrArray *array) +{ + gint i; + gchar *str; + + g_return_if_fail(array != NULL); + + for (i = 0; i < array->len; i++) { + str = g_ptr_array_index(array, i); + g_free(str); + } +} + +gboolean str_find(const gchar *haystack, const gchar *needle) +{ + return strstr(haystack, needle) != NULL ? TRUE : FALSE; +} + +gboolean str_case_find(const gchar *haystack, const gchar *needle) +{ + return strcasestr(haystack, needle) != NULL ? TRUE : FALSE; +} + +gboolean str_find_equal(const gchar *haystack, const gchar *needle) +{ + return strcmp(haystack, needle) == 0; +} + +gboolean str_case_find_equal(const gchar *haystack, const gchar *needle) +{ + return g_ascii_strcasecmp(haystack, needle) == 0; +} + +gint to_number(const gchar *nstr) +{ + register const gchar *p; + + if (*nstr == '\0') return -1; + + for (p = nstr; *p != '\0'; p++) + if (!g_ascii_isdigit(*p)) return -1; + + return atoi(nstr); +} + +/* convert integer into string, + nstr must be not lower than 11 characters length */ +gchar *itos_buf(gchar *nstr, gint n) +{ + g_snprintf(nstr, 11, "%d", n); + return nstr; +} + +/* convert integer into string */ +gchar *itos(gint n) +{ + static gchar nstr[11]; + + return itos_buf(nstr, n); +} + +gchar *to_human_readable(off_t size) +{ + static gchar str[10]; + + if (size < 1024) + g_snprintf(str, sizeof(str), _("%dB"), (gint)size); + else if (size >> 10 < 1024) + g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10)); + else if (size >> 20 < 1024) + g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20)); + else + g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30)); + + return str; +} + +/* strcmp with NULL-checking */ +gint strcmp2(const gchar *s1, const gchar *s2) +{ + if (s1 == NULL || s2 == NULL) + return -1; + else + return strcmp(s1, s2); +} + +/* compare paths */ +gint path_cmp(const gchar *s1, const gchar *s2) +{ + gint len1, len2; +#ifdef G_OS_WIN32 + gchar *s1_, *s2_; +#endif + + if (s1 == NULL || s2 == NULL) return -1; + if (*s1 == '\0' || *s2 == '\0') return -1; + + len1 = strlen(s1); + len2 = strlen(s2); + +#ifdef G_OS_WIN32 + Xstrdup_a(s1_, s1, return -1); + Xstrdup_a(s2_, s2, return -1); + subst_char(s1_, '/', G_DIR_SEPARATOR); + subst_char(s2_, '/', G_DIR_SEPARATOR); + if (s1_[len1 - 1] == G_DIR_SEPARATOR) len1--; + if (s2_[len2 - 1] == G_DIR_SEPARATOR) len2--; + + return strncmp(s1_, s2_, MAX(len1, len2)); +#else + if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--; + if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--; + + return strncmp(s1, s2, MAX(len1, len2)); +#endif +} + +/* remove trailing return code */ +gchar *strretchomp(gchar *str) +{ + register gchar *s; + + if (!*str) return str; + + for (s = str + strlen(str) - 1; + s >= str && (*s == '\n' || *s == '\r'); + s--) + *s = '\0'; + + return str; +} + +/* remove trailing character */ +gchar *strtailchomp(gchar *str, gchar tail_char) +{ + register gchar *s; + + if (!*str) return str; + if (tail_char == '\0') return str; + + for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--) + *s = '\0'; + + return str; +} + +/* remove CR (carriage return) */ +gchar *strcrchomp(gchar *str) +{ + register gchar *s; + + if (!*str) return str; + + s = str + strlen(str) - 1; + if (*s == '\n' && s > str && *(s - 1) == '\r') { + *(s - 1) = '\n'; + *s = '\0'; + } + + return str; +} + +/* Similar to `strstr' but this function ignores the case of both strings. */ +gchar *strcasestr(const gchar *haystack, const gchar *needle) +{ + register size_t haystack_len, needle_len; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!g_ascii_strncasecmp(haystack, needle, needle_len)) + return (gchar *)haystack; + else { + haystack++; + haystack_len--; + } + } + + return NULL; +} + +gpointer my_memmem(gconstpointer haystack, size_t haystacklen, + gconstpointer needle, size_t needlelen) +{ + const gchar *haystack_ = (const gchar *)haystack; + const gchar *needle_ = (const gchar *)needle; + const gchar *haystack_cur = (const gchar *)haystack; + + if (needlelen == 1) + return memchr(haystack_, *needle_, haystacklen); + + while ((haystack_cur = memchr(haystack_cur, *needle_, haystacklen)) + != NULL) { + if (haystacklen - (haystack_cur - haystack_) < needlelen) + break; + if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0) + return (gpointer)haystack_cur; + else + haystack_cur++; + } + + return NULL; +} + +/* Copy no more than N characters of SRC to DEST, with NULL terminating. */ +gchar *strncpy2(gchar *dest, const gchar *src, size_t n) +{ + register const gchar *s = src; + register gchar *d = dest; + + while (--n && *s) + *d++ = *s++; + *d = '\0'; + + return dest; +} + +#if !HAVE_ISWALNUM +int iswalnum(wint_t wc) +{ + return g_ascii_isalnum((int)wc); +} +#endif + +#if !HAVE_ISWSPACE +int iswspace(wint_t wc) +{ + return g_ascii_isspace((int)wc); +} +#endif + +#if !HAVE_TOWLOWER +wint_t towlower(wint_t wc) +{ + if (wc >= L'A' && wc <= L'Z') + return wc + L'a' - L'A'; + + return wc; +} +#endif + +#if !HAVE_WCSLEN +size_t wcslen(const wchar_t *s) +{ + size_t len = 0; + + while (*s != L'\0') + ++len, ++s; + + return len; +} +#endif + +#if !HAVE_WCSCPY +/* Copy SRC to DEST. */ +wchar_t *wcscpy(wchar_t *dest, const wchar_t *src) +{ + wint_t c; + wchar_t *s = dest; + + do { + c = *src++; + *dest++ = c; + } while (c != L'\0'); + + return s; +} +#endif + +#if !HAVE_WCSNCPY +/* Copy no more than N wide-characters of SRC to DEST. */ +wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n) +{ + wint_t c; + wchar_t *s = dest; + + do { + c = *src++; + *dest++ = c; + if (--n == 0) + return s; + } while (c != L'\0'); + + /* zero fill */ + do + *dest++ = L'\0'; + while (--n > 0); + + return s; +} +#endif + +/* Duplicate S, returning an identical malloc'd string. */ +wchar_t *wcsdup(const wchar_t *s) +{ + wchar_t *new_str; + + if (s) { + new_str = g_new(wchar_t, wcslen(s) + 1); + wcscpy(new_str, s); + } else + new_str = NULL; + + return new_str; +} + +/* Duplicate no more than N wide-characters of S, + returning an identical malloc'd string. */ +wchar_t *wcsndup(const wchar_t *s, size_t n) +{ + wchar_t *new_str; + + if (s) { + new_str = g_new(wchar_t, n + 1); + wcsncpy(new_str, s, n); + new_str[n] = (wchar_t)0; + } else + new_str = NULL; + + return new_str; +} + +wchar_t *strdup_mbstowcs(const gchar *s) +{ + wchar_t *new_str; + + if (s) { + new_str = g_new(wchar_t, strlen(s) + 1); + if (mbstowcs(new_str, s, strlen(s) + 1) < 0) { + g_free(new_str); + new_str = NULL; + } else + new_str = g_realloc(new_str, + sizeof(wchar_t) * (wcslen(new_str) + 1)); + } else + new_str = NULL; + + return new_str; +} + +gchar *strdup_wcstombs(const wchar_t *s) +{ + gchar *new_str; + size_t len; + + if (s) { + len = wcslen(s) * MB_CUR_MAX + 1; + new_str = g_new(gchar, len); + if (wcstombs(new_str, s, len) < 0) { + g_free(new_str); + new_str = NULL; + } else + new_str = g_realloc(new_str, strlen(new_str) + 1); + } else + new_str = NULL; + + return new_str; +} + +/* Compare S1 and S2, ignoring case. */ +gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n) +{ + wint_t c1; + wint_t c2; + + while (n--) { + c1 = towlower(*s1++); + c2 = towlower(*s2++); + if (c1 != c2) + return c1 - c2; + else if (c1 == 0 && c2 == 0) + break; + } + + return 0; +} + +/* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case. */ +wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle) +{ + register size_t haystack_len, needle_len; + + haystack_len = wcslen(haystack); + needle_len = wcslen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!wcsncasecmp(haystack, needle, needle_len)) + return (wchar_t *)haystack; + else { + haystack++; + haystack_len--; + } + } + + return NULL; +} + +gint get_mbs_len(const gchar *s) +{ + const gchar *p = s; + gint mb_len; + gint len = 0; + + if (!p) + return -1; + + while (*p != '\0') { + mb_len = g_utf8_skip[*(guchar *)p]; + if (mb_len == 0) + break; + else + len++; + + p += mb_len; + } + + return len; +} + +/* Examine if next block is non-ASCII string */ +gboolean is_next_nonascii(const gchar *s) +{ + const gchar *p; + + /* skip head space */ + for (p = s; *p != '\0' && g_ascii_isspace(*p); p++) + ; + for (; *p != '\0' && !g_ascii_isspace(*p); p++) { + if (*(guchar *)p > 127 || *(guchar *)p < 32) + return TRUE; + } + + return FALSE; +} + +gint get_next_word_len(const gchar *s) +{ + gint len = 0; + + for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++) + ; + + return len; +} + +/* compare subjects */ +gint subject_compare(const gchar *s1, const gchar *s2) +{ + gchar *str1, *str2; + + if (!s1 || !s2) return -1; + if (!*s1 || !*s2) return -1; + + Xstrdup_a(str1, s1, return -1); + Xstrdup_a(str2, s2, return -1); + + trim_subject_for_compare(str1); + trim_subject_for_compare(str2); + + if (!*str1 || !*str2) return -1; + + return strcmp(str1, str2); +} + +gint subject_compare_for_sort(const gchar *s1, const gchar *s2) +{ + gchar *str1, *str2; + + if (!s1 || !s2) return -1; + + Xstrdup_a(str1, s1, return -1); + Xstrdup_a(str2, s2, return -1); + + trim_subject_for_sort(str1); + trim_subject_for_sort(str2); + + return g_ascii_strcasecmp(str1, str2); +} + +void trim_subject_for_compare(gchar *str) +{ + gchar *srcp; + + eliminate_parenthesis(str, '[', ']'); + eliminate_parenthesis(str, '(', ')'); + g_strstrip(str); + + while (!g_ascii_strncasecmp(str, "Re:", 3)) { + srcp = str + 3; + while (g_ascii_isspace(*srcp)) srcp++; + memmove(str, srcp, strlen(srcp) + 1); + } +} + +void trim_subject_for_sort(gchar *str) +{ + gchar *srcp; + + g_strstrip(str); + + while (!g_ascii_strncasecmp(str, "Re:", 3)) { + srcp = str + 3; + while (g_ascii_isspace(*srcp)) srcp++; + memmove(str, srcp, strlen(srcp) + 1); + } +} + +void trim_subject(gchar *str) +{ + register gchar *srcp, *destp; + gchar op, cl; + gint in_brace; + + destp = str; + while (!g_ascii_strncasecmp(destp, "Re:", 3)) { + destp += 3; + while (g_ascii_isspace(*destp)) destp++; + } + + if (*destp == '[') { + op = '['; + cl = ']'; + } else if (*destp == '(') { + op = '('; + cl = ')'; + } else + return; + + srcp = destp + 1; + in_brace = 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); +} + +void eliminate_parenthesis(gchar *str, gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((destp = strchr(destp, op))) { + in_brace = 1; + srcp = destp + 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } +} + +void extract_parenthesis(gchar *str, gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((srcp = strchr(destp, op))) { + if (destp > str) + *destp++ = ' '; + memmove(destp, srcp + 1, strlen(srcp)); + in_brace = 1; + while(*destp) { + if (*destp == op) + in_brace++; + else if (*destp == cl) + in_brace--; + + if (in_brace == 0) + break; + + destp++; + } + } + *destp = '\0'; +} + +void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr, + gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + gboolean in_quote = FALSE; + + srcp = destp = str; + + while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) { + if (destp > str) + *destp++ = ' '; + memmove(destp, srcp + 1, strlen(srcp)); + in_brace = 1; + while(*destp) { + if (*destp == op && !in_quote) + in_brace++; + else if (*destp == cl && !in_quote) + in_brace--; + else if (*destp == quote_chr) + in_quote ^= TRUE; + + if (in_brace == 0) + break; + + destp++; + } + } + *destp = '\0'; +} + +void eliminate_quote(gchar *str, gchar quote_chr) +{ + register gchar *srcp, *destp; + + srcp = destp = str; + + while ((destp = strchr(destp, quote_chr))) { + if ((srcp = strchr(destp + 1, quote_chr))) { + srcp++; + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } else { + *destp = '\0'; + break; + } + } +} + +void extract_quote(gchar *str, gchar quote_chr) +{ + register gchar *p; + + if ((str = strchr(str, quote_chr))) { + if ((p = strchr(str + 1, quote_chr))) { + *p = '\0'; + memmove(str, str + 1, p - str); + } + } +} + +void eliminate_address_comment(gchar *str) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((destp = strchr(destp, '"'))) { + if ((srcp = strchr(destp + 1, '"'))) { + srcp++; + if (*srcp == '@') { + destp = srcp + 1; + } else { + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } + } else { + *destp = '\0'; + break; + } + } + + srcp = destp = str; + + while ((destp = strchr_with_skip_quote(destp, '"', '('))) { + in_brace = 1; + srcp = destp + 1; + while (*srcp) { + if (*srcp == '(') + in_brace++; + else if (*srcp == ')') + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } +} + +gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) +{ + gboolean in_quote = FALSE; + + while (*str) { + if (*str == c && !in_quote) + return (gchar *)str; + if (*str == quote_chr) + in_quote ^= TRUE; + str++; + } + + return NULL; +} + +gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) +{ + gboolean in_quote = FALSE; + const gchar *p; + + p = str + strlen(str) - 1; + while (p >= str) { + if (*p == c && !in_quote) + return (gchar *)p; + if (*p == quote_chr) + in_quote ^= TRUE; + p--; + } + + return NULL; +} + +void extract_address(gchar *str) +{ + eliminate_address_comment(str); + if (strchr_with_skip_quote(str, '"', '<')) + extract_parenthesis_with_skip_quote(str, '"', '<', '>'); + g_strstrip(str); +} + +void extract_list_id_str(gchar *str) +{ + if (strchr_with_skip_quote(str, '"', '<')) + extract_parenthesis_with_skip_quote(str, '"', '<', '>'); + g_strstrip(str); +} + +GSList *address_list_append(GSList *addr_list, const gchar *str) +{ + gchar *work; + gchar *workp; + + if (!str) return addr_list; + + Xstrdup_a(work, str, return addr_list); + + eliminate_address_comment(work); + workp = work; + + while (workp && *workp) { + gchar *p, *next; + + if ((p = strchr_with_skip_quote(workp, '"', ','))) { + *p = '\0'; + next = p + 1; + } else + next = NULL; + + if (strchr_with_skip_quote(workp, '"', '<')) + extract_parenthesis_with_skip_quote + (workp, '"', '<', '>'); + + g_strstrip(workp); + if (*workp) + addr_list = g_slist_append(addr_list, g_strdup(workp)); + + workp = next; + } + + return addr_list; +} + +GSList *references_list_prepend(GSList *msgid_list, const gchar *str) +{ + const gchar *strp; + + if (!str) return msgid_list; + strp = str; + + while (strp && *strp) { + const gchar *start, *end; + gchar *msgid; + + if ((start = strchr(strp, '<')) != NULL) { + end = strchr(start + 1, '>'); + if (!end) break; + } else + break; + + msgid = g_strndup(start + 1, end - start - 1); + g_strstrip(msgid); + if (*msgid) + msgid_list = g_slist_prepend(msgid_list, msgid); + else + g_free(msgid); + + strp = end + 1; + } + + return msgid_list; +} + +GSList *references_list_append(GSList *msgid_list, const gchar *str) +{ + GSList *list; + + list = references_list_prepend(NULL, str); + list = g_slist_reverse(list); + msgid_list = g_slist_concat(msgid_list, list); + + return msgid_list; +} + +GSList *newsgroup_list_append(GSList *group_list, const gchar *str) +{ + gchar *work; + gchar *workp; + + if (!str) return group_list; + + Xstrdup_a(work, str, return group_list); + + workp = work; + + while (workp && *workp) { + gchar *p, *next; + + if ((p = strchr_with_skip_quote(workp, '"', ','))) { + *p = '\0'; + next = p + 1; + } else + next = NULL; + + g_strstrip(workp); + if (*workp) + group_list = g_slist_append(group_list, + g_strdup(workp)); + + workp = next; + } + + return group_list; +} + +GList *add_history(GList *list, const gchar *str) +{ + GList *old; + + g_return_val_if_fail(str != NULL, list); + + old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2); + if (old) { + g_free(old->data); + list = g_list_remove(list, old->data); + } else if (g_list_length(list) >= MAX_HISTORY_SIZE) { + GList *last; + + last = g_list_last(list); + if (last) { + g_free(last->data); + g_list_remove(list, last->data); + } + } + + list = g_list_prepend(list, g_strdup(str)); + + return list; +} + +void remove_return(gchar *str) +{ + register gchar *p = str; + + while (*p) { + if (*p == '\n' || *p == '\r') + memmove(p, p + 1, strlen(p)); + else + p++; + } +} + +void remove_space(gchar *str) +{ + register gchar *p = str; + register gint spc; + + while (*p) { + spc = 0; + while (g_ascii_isspace(*(p + spc))) + spc++; + if (spc) + memmove(p, p + spc, strlen(p + spc) + 1); + else + p++; + } +} + +void unfold_line(gchar *str) +{ + register gchar *p = str; + register gint spc; + + while (*p) { + if (*p == '\n' || *p == '\r') { + *p++ = ' '; + spc = 0; + while (g_ascii_isspace(*(p + spc))) + spc++; + if (spc) + memmove(p, p + spc, strlen(p + spc) + 1); + } else + p++; + } +} + +void subst_char(gchar *str, gchar orig, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (*p == orig) + *p = subst; + p++; + } +} + +void subst_chars(gchar *str, gchar *orig, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (strchr(orig, *p) != NULL) + *p = subst; + p++; + } +} + +void subst_null(gchar *str, gint len, gchar subst) +{ + register gchar *p = str; + + while (len--) { + if (*p == '\0') + *p = subst; + p++; + } +} + +void subst_for_filename(gchar *str) +{ + subst_chars(str, " \t\r\n\"'/\\", '_'); +} + +gboolean is_header_line(const gchar *str) +{ + if (str[0] == ':') return FALSE; + + while (*str != '\0' && *str != ' ') { + if (*str == ':') + return TRUE; + str++; + } + + return FALSE; +} + +gboolean is_ascii_str(const gchar *str) +{ + const guchar *p = (const guchar *)str; + + while (*p != '\0') { + if (*p != '\t' && *p != ' ' && + *p != '\r' && *p != '\n' && + (*p < 32 || *p >= 127)) + return FALSE; + p++; + } + + return TRUE; +} + +gint get_quote_level(const gchar *str) +{ + const gchar *first_pos; + const gchar *last_pos; + const gchar *p = str; + gint quote_level = -1; + + /* speed up line processing by only searching to the last '>' */ + if ((first_pos = strchr(str, '>')) != NULL) { + /* skip a line if it contains a '<' before the initial '>' */ + if (memchr(str, '<', first_pos - str) != NULL) + return -1; + last_pos = strrchr(first_pos, '>'); + } else + return -1; + + while (p <= last_pos) { + while (p < last_pos) { + if (g_ascii_isspace(*p)) + p++; + else + break; + } + + if (*p == '>') + quote_level++; + else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) { + /* any characters are allowed except '-' and space */ + while (*p != '-' && *p != '>' && !g_ascii_isspace(*p) && + p < last_pos) + p++; + if (*p == '>') + quote_level++; + else + break; + } + + p++; + } + + return quote_level; +} + +gint check_line_length(const gchar *str, gint max_chars, gint *line) +{ + const gchar *p = str, *q; + gint cur_line = 0, len; + + while ((q = strchr(p, '\n')) != NULL) { + len = q - p + 1; + if (len > max_chars) { + if (line) + *line = cur_line; + return -1; + } + p = q + 1; + ++cur_line; + } + + len = strlen(p); + if (len > max_chars) { + if (line) + *line = cur_line; + return -1; + } + + return 0; +} + +gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle) +{ + register guint haystack_len, needle_len; + gboolean in_squote = FALSE, in_dquote = FALSE; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!in_squote && !in_dquote && + !strncmp(haystack, needle, needle_len)) + return (gchar *)haystack; + + /* 'foo"bar"' -> foo"bar" + "foo'bar'" -> foo'bar' */ + if (*haystack == '\'') { + if (in_squote) + in_squote = FALSE; + else if (!in_dquote) + in_squote = TRUE; + } else if (*haystack == '\"') { + if (in_dquote) + in_dquote = FALSE; + else if (!in_squote) + in_dquote = TRUE; + } + + haystack++; + haystack_len--; + } + + return NULL; +} + +gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl) +{ + const gchar *p; + gchar quote_chr = '"'; + gint in_brace; + gboolean in_quote = FALSE; + + p = str; + + if ((p = strchr_with_skip_quote(p, quote_chr, op))) { + p++; + in_brace = 1; + while (*p) { + if (*p == op && !in_quote) + in_brace++; + else if (*p == cl && !in_quote) + in_brace--; + else if (*p == quote_chr) + in_quote ^= TRUE; + + if (in_brace == 0) + return (gchar *)p; + + p++; + } + } + + return NULL; +} + +gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array; + const gchar *s_op, *s_cl; + guint i, n = 1; + + g_return_val_if_fail(str != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s_op = strchr_with_skip_quote(str, '"', op); + if (!s_op) return NULL; + str = s_op; + s_cl = strchr_parenthesis_close(str, op, cl); + if (s_cl) { + do { + guint len; + gchar *new_string; + + str++; + len = s_cl - str; + new_string = g_new(gchar, len + 1); + strncpy(new_string, str, len); + new_string[len] = 0; + string_list = g_slist_prepend(string_list, new_string); + n++; + str = s_cl + 1; + + while (*str && g_ascii_isspace(*str)) str++; + if (*str != op) { + string_list = g_slist_prepend(string_list, + g_strdup("")); + n++; + s_op = strchr_with_skip_quote(str, '"', op); + if (!--max_tokens || !s_op) break; + str = s_op; + } else + s_op = str; + s_cl = strchr_parenthesis_close(str, op, cl); + } while (--max_tokens && s_cl); + } + + str_array = g_new(gchar*, n); + + i = n - 1; + + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +gchar **strsplit_with_quote(const gchar *str, const gchar *delim, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array, *s, *new_str; + guint i, n = 1, len; + + g_return_val_if_fail(str != NULL, NULL); + g_return_val_if_fail(delim != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s = strstr_with_skip_quote(str, delim); + if (s) { + guint delimiter_len = strlen(delim); + + do { + len = s - str; + new_str = g_strndup(str, len); + + if (new_str[0] == '\'' || new_str[0] == '\"') { + if (new_str[len - 1] == new_str[0]) { + new_str[len - 1] = '\0'; + memmove(new_str, new_str + 1, len - 1); + } + } + string_list = g_slist_prepend(string_list, new_str); + n++; + str = s + delimiter_len; + s = strstr_with_skip_quote(str, delim); + } while (--max_tokens && s); + } + + if (*str) { + new_str = g_strdup(str); + if (new_str[0] == '\'' || new_str[0] == '\"') { + len = strlen(str); + if (new_str[len - 1] == new_str[0]) { + new_str[len - 1] = '\0'; + memmove(new_str, new_str + 1, len - 1); + } + } + string_list = g_slist_prepend(string_list, new_str); + n++; + } + + str_array = g_new(gchar*, n); + + i = n - 1; + + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +gchar *get_abbrev_newsgroup_name(const gchar *group, gint len) +{ + gchar *abbrev_group; + gchar *ap; + const gchar *p = group; + const gchar *last; + + last = group + strlen(group); + abbrev_group = ap = g_malloc(strlen(group) + 1); + + while (*p) { + while (*p == '.') + *ap++ = *p++; + if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) { + *ap++ = *p++; + while (*p != '.') p++; + } else { + strcpy(ap, p); + return abbrev_group; + } + } + + *ap = '\0'; + return abbrev_group; +} + +gchar *trim_string(const gchar *str, gint len) +{ + const gchar *p = str; + gint mb_len; + gchar *new_str; + gint new_len = 0; + + if (!str) return NULL; + if (strlen(str) <= len) + return g_strdup(str); + if (g_utf8_validate(str, -1, NULL) == FALSE) + return g_strdup(str); + + while (*p != '\0') { + mb_len = g_utf8_skip[*(guchar *)p]; + if (mb_len == 0) + break; + else if (new_len + mb_len > len) + break; + + new_len += mb_len; + p += mb_len; + } + + Xstrndup_a(new_str, str, new_len, return g_strdup(str)); + return g_strconcat(new_str, "...", NULL); +} + +gchar *trim_string_before(const gchar *str, gint len) +{ + const gchar *p = str; + gint mb_len; + gint new_len; + + if (!str) return NULL; + if ((new_len = strlen(str)) <= len) + return g_strdup(str); + if (g_utf8_validate(str, -1, NULL) == FALSE) + return g_strdup(str); + + while (*p != '\0') { + mb_len = g_utf8_skip[*(guchar *)p]; + if (mb_len == 0) + break; + + new_len -= mb_len; + p += mb_len; + + if (new_len <= len) + break; + } + + return g_strconcat("...", p, NULL); +} + +GList *uri_list_extract_filenames(const gchar *uri_list) +{ + GList *result = NULL; + const gchar *p, *q; + gchar *file; + + p = uri_list; + + while (p) { + if (*p != '#') { + while (g_ascii_isspace(*p)) p++; + if (!strncmp(p, "file:", 5)) { + p += 5; + while (*p == '/' && *(p + 1) == '/') p++; + q = p; + while (*q && *q != '\n' && *q != '\r') q++; + + if (q > p) { + q--; + while (q > p && g_ascii_isspace(*q)) + q--; + file = g_malloc(q - p + 2); + strncpy(file, p, q - p + 1); + file[q - p + 1] = '\0'; + decode_uri(file, file); + result = g_list_append(result,file); + } + } + } + p = strchr(p, '\n'); + if (p) p++; + } + + return result; +} + +#define HEX_TO_INT(val, hex) \ +{ \ + gchar c = hex; \ + \ + if ('0' <= c && c <= '9') { \ + val = c - '0'; \ + } else if ('a' <= c && c <= 'f') { \ + val = c - 'a' + 10; \ + } else if ('A' <= c && c <= 'F') { \ + val = c - 'A' + 10; \ + } else { \ + val = 0; \ + } \ +} + +/* Converts two-digit hexadecimal to decimal. Used for unescaping escaped + * characters. + */ +static gint axtoi(const gchar *hex_str) +{ + gint hi, lo; + + HEX_TO_INT(hi, hex_str[0]); + HEX_TO_INT(lo, hex_str[1]); + + return (hi << 4) + lo; +} + +gboolean is_uri_string(const gchar *str) +{ + return (g_ascii_strncasecmp(str, "http://", 7) == 0 || + g_ascii_strncasecmp(str, "https://", 8) == 0 || + g_ascii_strncasecmp(str, "ftp://", 6) == 0 || + g_ascii_strncasecmp(str, "www.", 4) == 0); +} + +gchar *get_uri_path(const gchar *uri) +{ + if (g_ascii_strncasecmp(uri, "http://", 7) == 0) + return (gchar *)(uri + 7); + else if (g_ascii_strncasecmp(uri, "https://", 8) == 0) + return (gchar *)(uri + 8); + else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0) + return (gchar *)(uri + 6); + else + return (gchar *)uri; +} + +gint get_uri_len(const gchar *str) +{ + const gchar *p; + + if (is_uri_string(str)) { + for (p = str; *p != '\0'; p++) { + if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p)) + break; + } + return p - str; + } + + return 0; +} + +/* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by + * plusses, and escape characters are used) + * Note: decoded_uri and encoded_uri can point the same location + */ +void decode_uri(gchar *decoded_uri, const gchar *encoded_uri) +{ + gchar *dec = decoded_uri; + const gchar *enc = encoded_uri; + + while (*enc) { + if (*enc == '%') { + enc++; + if (isxdigit((guchar)enc[0]) && + isxdigit((guchar)enc[1])) { + *dec = axtoi(enc); + dec++; + enc += 2; + } + } else { + if (*enc == '+') + *dec = ' '; + else + *dec = *enc; + dec++; + enc++; + } + } + + *dec = '\0'; +} + +gchar *encode_uri(const gchar *filename) +{ + gchar *uri; + + uri = g_filename_to_uri(filename, NULL, NULL); + if (!uri) + uri = g_strconcat("file://", filename, NULL); + + return uri; +} + +gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc, + gchar **subject, gchar **body) +{ + gchar *tmp_mailto; + gchar *p; + + Xstrdup_a(tmp_mailto, mailto, return -1); + + if (!strncmp(tmp_mailto, "mailto:", 7)) + tmp_mailto += 7; + + p = strchr(tmp_mailto, '?'); + if (p) { + *p = '\0'; + p++; + } + + if (to && !*to) + *to = g_strdup(tmp_mailto); + + while (p) { + gchar *field, *value; + + field = p; + + p = strchr(p, '='); + if (!p) break; + *p = '\0'; + p++; + + value = p; + + p = strchr(p, '&'); + if (p) { + *p = '\0'; + p++; + } + + if (*value == '\0') continue; + + if (cc && !*cc && !g_ascii_strcasecmp(field, "cc")) { + *cc = g_strdup(value); + } else if (bcc && !*bcc && !g_ascii_strcasecmp(field, "bcc")) { + *bcc = g_strdup(value); + } else if (subject && !*subject && + !g_ascii_strcasecmp(field, "subject")) { + *subject = g_malloc(strlen(value) + 1); + decode_uri(*subject, value); + } else if (body && !*body && + !g_ascii_strcasecmp(field, "body")) { + *body = g_malloc(strlen(value) + 1); + decode_uri(*body, value); + } + } + + return 0; +} + +const gchar *get_home_dir(void) +{ +#ifdef G_OS_WIN32 + static const gchar *home_dir = NULL; + + if (!home_dir) { + home_dir = g_get_home_dir(); + if (!home_dir) + home_dir = "C:\\Sylpheed"; + } + + return home_dir; +#else + return g_get_home_dir(); +#endif +} + +const gchar *get_rc_dir(void) +{ + static gchar *rc_dir = NULL; + + if (!rc_dir) +#ifdef G_OS_WIN32 + rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + "Application Data", G_DIR_SEPARATOR_S, + RC_DIR, NULL); +#else + rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + RC_DIR, NULL); +#endif + + return rc_dir; +} + +const gchar *get_old_rc_dir(void) +{ + static gchar *old_rc_dir = NULL; + + if (!old_rc_dir) + old_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + OLD_RC_DIR, NULL); + + return old_rc_dir; +} + +const gchar *get_mail_base_dir(void) +{ +#ifdef G_OS_WIN32 + static gchar *mail_base_dir = NULL; + + if (!mail_base_dir) + mail_base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "Mailboxes", NULL); + + return mail_base_dir; +#else + return get_home_dir(); +#endif +} + +const gchar *get_news_cache_dir(void) +{ + static gchar *news_cache_dir = NULL; + + if (!news_cache_dir) + news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + NEWS_CACHE_DIR, NULL); + + return news_cache_dir; +} + +const gchar *get_imap_cache_dir(void) +{ + static gchar *imap_cache_dir = NULL; + + if (!imap_cache_dir) + imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + IMAP_CACHE_DIR, NULL); + + return imap_cache_dir; +} + +const gchar *get_mime_tmp_dir(void) +{ + static gchar *mime_tmp_dir = NULL; + + if (!mime_tmp_dir) + mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + MIME_TMP_DIR, NULL); + + return mime_tmp_dir; +} + +const gchar *get_template_dir(void) +{ + static gchar *template_dir = NULL; + + if (!template_dir) + template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + TEMPLATE_DIR, NULL); + + return template_dir; +} + +const gchar *get_tmp_dir(void) +{ + static gchar *tmp_dir = NULL; + + if (!tmp_dir) + tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + TMP_DIR, NULL); + + return tmp_dir; +} + +gchar *get_tmp_file(void) +{ + gchar *tmp_file; + static guint32 id = 0; + + tmp_file = g_strdup_printf("%s%ctmpfile.%08x", + get_tmp_dir(), G_DIR_SEPARATOR, id++); + + return tmp_file; +} + +const gchar *get_domain_name(void) +{ +#ifdef G_OS_UNIX + static gchar *domain_name = NULL; + + if (!domain_name) { + gchar buf[128] = ""; + struct hostent *hp; + + if (gethostname(buf, sizeof(buf)) < 0) { + perror("gethostname"); + domain_name = "unknown"; + } else { + buf[sizeof(buf) - 1] = '\0'; + if ((hp = my_gethostbyname(buf)) == NULL) { + perror("gethostbyname"); + domain_name = g_strdup(buf); + } else { + domain_name = g_strdup(hp->h_name); + } + } + + debug_print("domain name = %s\n", domain_name); + } + + return domain_name; +#else + return "unknown"; +#endif +} + +off_t get_file_size(const gchar *file) +{ + struct stat s; + + if (g_stat(file, &s) < 0) { + FILE_OP_ERROR(file, "stat"); + return -1; + } + + return s.st_size; +} + +off_t get_file_size_as_crlf(const gchar *file) +{ + FILE *fp; + off_t size = 0; + gchar buf[BUFFSIZE]; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + size += strlen(buf) + 2; + } + + if (ferror(fp)) { + FILE_OP_ERROR(file, "fgets"); + size = -1; + } + + fclose(fp); + + return size; +} + +off_t get_left_file_size(FILE *fp) +{ + glong pos; + glong end; + off_t size; + + if ((pos = ftell(fp)) < 0) { + perror("ftell"); + return -1; + } + if (fseek(fp, 0L, SEEK_END) < 0) { + perror("fseek"); + return -1; + } + if ((end = ftell(fp)) < 0) { + perror("fseek"); + return -1; + } + size = end - pos; + if (fseek(fp, pos, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + return size; +} + +gboolean file_exist(const gchar *file, gboolean allow_fifo) +{ + struct stat s; + + if (file == NULL) + return FALSE; + + if (g_stat(file, &s) < 0) { + if (ENOENT != errno) FILE_OP_ERROR(file, "stat"); + return FALSE; + } + + if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode))) + return TRUE; + + return FALSE; +} + +gboolean is_dir_exist(const gchar *dir) +{ + if (dir == NULL) + return FALSE; + + return g_file_test(dir, G_FILE_TEST_IS_DIR); +} + +gboolean is_file_entry_exist(const gchar *file) +{ + if (file == NULL) + return FALSE; + + return g_file_test(file, G_FILE_TEST_EXISTS); +} + +gboolean dirent_is_regular_file(struct dirent *d) +{ +#ifdef HAVE_DIRENT_D_TYPE + if (d->d_type == DT_REG) + return TRUE; + else if (d->d_type != DT_UNKNOWN) + return FALSE; +#endif + + return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR); +} + +gboolean dirent_is_directory(struct dirent *d) +{ +#ifdef HAVE_DIRENT_D_TYPE + if (d->d_type == DT_DIR) + return TRUE; + else if (d->d_type != DT_UNKNOWN) + return FALSE; +#endif + + return g_file_test(d->d_name, G_FILE_TEST_IS_DIR); +} + +gint change_dir(const gchar *dir) +{ + gchar *prevdir = NULL; + + if (debug_mode) + prevdir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + if (debug_mode) g_free(prevdir); + return -1; + } else if (debug_mode) { + gchar *cwd; + + cwd = g_get_current_dir(); + if (strcmp(prevdir, cwd) != 0) + g_print("current dir: %s\n", cwd); + g_free(cwd); + g_free(prevdir); + } + + return 0; +} + +gint make_dir(const gchar *dir) +{ + if (g_mkdir(dir, S_IRWXU) < 0) { + FILE_OP_ERROR(dir, "mkdir"); + return -1; + } + if (g_chmod(dir, S_IRWXU) < 0) + FILE_OP_ERROR(dir, "chmod"); + + return 0; +} + +gint make_dir_hier(const gchar *dir) +{ + gchar *parent_dir; + const gchar *p; + + for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) { + parent_dir = g_strndup(dir, p - dir); + if (*parent_dir != '\0') { + if (!is_dir_exist(parent_dir)) { + if (make_dir(parent_dir) < 0) { + g_free(parent_dir); + return -1; + } + } + } + g_free(parent_dir); + } + + if (!is_dir_exist(dir)) { + if (make_dir(dir) < 0) + return -1; + } + + return 0; +} + +gint remove_all_files(const gchar *dir) +{ + GDir *dp; + const gchar *dir_name; + gchar *prev_dir; + + prev_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_free(prev_dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_numbered_files(const gchar *dir, guint first, guint last) +{ + GDir *dp; + const gchar *dir_name; + gchar *prev_dir; + gint file_no; + + prev_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_free(prev_dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + file_no = to_number(dir_name); + if (file_no > 0 && first <= file_no && file_no <= last) { + if (is_dir_exist(dir_name)) + continue; + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_all_numbered_files(const gchar *dir) +{ + return remove_numbered_files(dir, 0, UINT_MAX); +} + +gint remove_expired_files(const gchar *dir, guint hours) +{ + GDir *dp; + const gchar *dir_name; + struct stat s; + gchar *prev_dir; + gint file_no; + time_t mtime, now, expire_time; + + prev_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_free(prev_dir); + return -1; + } + + now = time(NULL); + expire_time = hours * 60 * 60; + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + file_no = to_number(dir_name); + if (file_no > 0) { + if (g_stat(dir_name, &s) < 0) { + FILE_OP_ERROR(dir_name, "stat"); + continue; + } + if (S_ISDIR(s.st_mode)) + continue; + mtime = MAX(s.st_mtime, s.st_atime); + if (now - mtime > expire_time) { + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + } + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +static gint remove_dir_recursive_real(const gchar *dir) +{ + struct stat s; + GDir *dp; + const gchar *dir_name; + gchar *prev_dir; + + if (g_stat(dir, &s) < 0) { + FILE_OP_ERROR(dir, "stat"); + if (ENOENT == errno) return 0; + return -1; + } + + if (!S_ISDIR(s.st_mode)) { + if (g_unlink(dir) < 0) { + FILE_OP_ERROR(dir, "unlink"); + return -1; + } + + return 0; + } + + prev_dir = g_get_current_dir(); + /* g_print("prev_dir = %s\n", prev_dir); */ + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_chdir(prev_dir); + g_free(prev_dir); + return -1; + } + + /* remove all files in the directory */ + while ((dir_name = g_dir_read_name(dp)) != NULL) { + /* g_print("removing %s\n", dir_name); */ + + if (is_dir_exist(dir_name)) { + if (remove_dir_recursive_real(dir_name) < 0) { + g_warning("can't remove directory\n"); + return -1; + } + } else { + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + if (g_rmdir(dir) < 0) { + FILE_OP_ERROR(dir, "rmdir"); + return -1; + } + + return 0; +} + +gint remove_dir_recursive(const gchar *dir) +{ + gchar *cur_dir; + gint ret; + + cur_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + ret = -1; + goto leave; + } + if (g_chdir("..") < 0) { + FILE_OP_ERROR(dir, "chdir"); + ret = -1; + goto leave; + } + + ret = remove_dir_recursive_real(dir); + +leave: + if (is_dir_exist(cur_dir)) { + if (g_chdir(cur_dir) < 0) { + FILE_OP_ERROR(cur_dir, "chdir"); + } + } + + g_free(cur_dir); + + return ret; +} + +gint rename_force(const gchar *oldpath, const gchar *newpath) +{ +#ifndef G_OS_UNIX + if (!is_file_entry_exist(oldpath)) { + errno = ENOENT; + return -1; + } + if (is_file_exist(newpath)) { + if (g_unlink(newpath) < 0) + FILE_OP_ERROR(newpath, "unlink"); + } +#endif + return g_rename(oldpath, newpath); +} + +gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup) +{ + FILE *src_fp, *dest_fp; + gint n_read; + gchar buf[BUFSIZ]; + gchar *dest_bak = NULL; + gboolean err = FALSE; + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + if (is_file_exist(dest)) { + dest_bak = g_strconcat(dest, ".bak", NULL); + if (rename_force(dest, dest_bak) < 0) { + FILE_OP_ERROR(dest, "rename"); + fclose(src_fp); + g_free(dest_bak); + return -1; + } + } + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + if (dest_bak) { + if (rename_force(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning(_("can't change file mode\n")); + } + + while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) { + if (n_read < sizeof(buf) && ferror(src_fp)) + break; + if (fwrite(buf, n_read, 1, dest_fp) < 1) { + g_warning(_("writing to %s failed.\n"), dest); + fclose(dest_fp); + fclose(src_fp); + g_unlink(dest); + if (dest_bak) { + if (rename_force(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fread"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + g_unlink(dest); + if (dest_bak) { + if (rename_force(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + + if (keep_backup == FALSE && dest_bak) + g_unlink(dest_bak); + + g_free(dest_bak); + + return 0; +} + +gint copy_dir(const gchar *src, const gchar *dest) +{ + GDir *dir; + const gchar *dir_name; + gchar *src_file; + gchar *dest_file; + + if ((dir = g_dir_open(src, 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", src); + return -1; + } + + if (make_dir_hier(dest) < 0) { + g_dir_close(dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dir)) != NULL) { + src_file = g_strconcat(src, G_DIR_SEPARATOR_S, dir_name, NULL); + dest_file = g_strconcat(dest, G_DIR_SEPARATOR_S, dir_name, + NULL); + if (is_file_exist(src_file)) + copy_file(src_file, dest_file, FALSE); + g_free(dest_file); + g_free(src_file); + } + + g_dir_close(dir); + + return 0; +} + +gint move_file(const gchar *src, const gchar *dest, gboolean overwrite) +{ + if (overwrite == FALSE && is_file_exist(dest)) { + g_warning("move_file(): file %s already exists.", dest); + return -1; + } + + if (rename_force(src, dest) == 0) return 0; + + if (EXDEV != errno) { + FILE_OP_ERROR(src, "rename"); + return -1; + } + + if (copy_file(src, dest, FALSE) < 0) return -1; + + g_unlink(src); + + return 0; +} + +gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest) +{ + FILE *dest_fp; + gint n_read; + gint bytes_left, to_read; + gchar buf[BUFSIZ]; + gboolean err = FALSE; + + if (fseek(fp, offset, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + bytes_left = length; + to_read = MIN(bytes_left, sizeof(buf)); + + while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) { + if (n_read < to_read && ferror(fp)) + break; + if (fwrite(buf, n_read, 1, dest_fp) < 1) { + g_warning(_("writing to %s failed.\n"), dest); + fclose(dest_fp); + g_unlink(dest); + return -1; + } + bytes_left -= n_read; + if (bytes_left == 0) + break; + to_read = MIN(bytes_left, sizeof(buf)); + } + + if (ferror(fp)) { + perror("fread"); + err = TRUE; + } + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +/* convert line endings into CRLF. If the last line doesn't end with + * linebreak, add it. + */ +gchar *canonicalize_str(const gchar *str) +{ + const gchar *p; + guint new_len = 0; + gchar *out, *outp; + + for (p = str; *p != '\0'; ++p) { + if (*p != '\r') { + ++new_len; + if (*p == '\n') + ++new_len; + } + } + if (p == str || *(p - 1) != '\n') + new_len += 2; + + out = outp = g_malloc(new_len + 1); + for (p = str; *p != '\0'; ++p) { + if (*p != '\r') { + if (*p == '\n') + *outp++ = '\r'; + *outp++ = *p; + } + } + if (p == str || *(p - 1) != '\n') { + *outp++ = '\r'; + *outp++ = '\n'; + } + *outp = '\0'; + + return out; +} + +gint canonicalize_file(const gchar *src, const gchar *dest) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + gint len; + gboolean err = FALSE; + gboolean last_linebreak = FALSE; + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gint r = 0; + + len = strlen(buf); + if (len == 0) break; + last_linebreak = FALSE; + + if (buf[len - 1] != '\n') { + last_linebreak = TRUE; + r = fputs(buf, dest_fp); + } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { + r = fputs(buf, dest_fp); + } else { + if (len > 1) { + r = fwrite(buf, len - 1, 1, dest_fp); + if (r != 1) + r = EOF; + } + if (r != EOF) + r = fputs("\r\n", dest_fp); + } + + if (r == EOF) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + fclose(src_fp); + g_unlink(dest); + return -1; + } + } + + if (last_linebreak == TRUE) { + if (fputs("\r\n", dest_fp) == EOF) + err = TRUE; + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +gint canonicalize_file_replace(const gchar *file) +{ + gchar *tmp_file; + + tmp_file = get_tmp_file(); + + if (canonicalize_file(file, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (move_file(tmp_file, file, TRUE) < 0) { + g_warning("can't replace %s .\n", file); + g_unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(tmp_file); + return 0; +} + +gint uncanonicalize_file(const gchar *src, const gchar *dest) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + gboolean err = FALSE; + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + strcrchomp(buf); + if (fputs(buf, dest_fp) == EOF) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + fclose(src_fp); + g_unlink(dest); + return -1; + } + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +gint uncanonicalize_file_replace(const gchar *file) +{ + gchar *tmp_file; + + tmp_file = get_tmp_file(); + + if (uncanonicalize_file(file, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (move_file(tmp_file, file, TRUE) < 0) { + g_warning("can't replace %s .\n", file); + g_unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(tmp_file); + return 0; +} + +gchar *normalize_newlines(const gchar *str) +{ + const gchar *p = str; + gchar *out, *outp; + + out = outp = g_malloc(strlen(str) + 1); + for (p = str; *p != '\0'; ++p) { + if (*p == '\r') { + if (*(p + 1) != '\n') + *outp++ = '\n'; + } else + *outp++ = *p; + } + + *outp = '\0'; + + return out; +} + +gchar *get_outgoing_rfc2822_str(FILE *fp) +{ + gchar buf[BUFFSIZE]; + GString *str; + gchar *ret; + + str = g_string_new(NULL); + + /* output header part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) { + gint next; + + for (;;) { + next = fgetc(fp); + if (next == EOF) + break; + else if (next != ' ' && next != '\t') { + ungetc(next, fp); + break; + } + if (fgets(buf, sizeof(buf), fp) == NULL) + break; + } +#if 0 + } else if (!g_ascii_strncasecmp(buf, "Date:", 5)) { + get_rfc822_date(buf, sizeof(buf)); + g_string_append_printf(str, "Date: %s\r\n", buf); +#endif + } else { + g_string_append(str, buf); + g_string_append(str, "\r\n"); + if (buf[0] == '\0') + break; + } + } + + /* output body part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (buf[0] == '.') + g_string_append_c(str, '.'); + g_string_append(str, buf); + g_string_append(str, "\r\n"); + } + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +/* + * Create a new boundary in a way that it is very unlikely that this + * will occur in the following text. It would be easy to ensure + * uniqueness if everything is either quoted-printable or base64 + * encoded (note that conversion is allowed), but because MIME bodies + * may be nested, it may happen that the same boundary has already + * been used. We avoid scanning the message for conflicts and hope the + * best. + * + * boundary := 0*69<bchars> bcharsnospace + * bchars := bcharsnospace / " " + * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + * "+" / "_" / "," / "-" / "." / + * "/" / ":" / "=" / "?" + * + * some special characters removed because of buggy MTAs + */ + +gchar *generate_mime_boundary(const gchar *prefix) +{ + static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "1234567890+_./="; + gchar buf_uniq[17]; + gchar buf_date[64]; + gint i; + + for (i = 0; i < sizeof(buf_uniq) - 1; i++) + buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)]; + buf_uniq[i] = '\0'; + + get_rfc822_date(buf_date, sizeof(buf_date)); + subst_char(buf_date, ' ', '_'); + subst_char(buf_date, ',', '_'); + subst_char(buf_date, ':', '_'); + + return g_strdup_printf("%s=_%s_%s", prefix ? prefix : "Multipart", + buf_date, buf_uniq); +} + +gint change_file_mode_rw(FILE *fp, const gchar *file) +{ +#if HAVE_FCHMOD + return fchmod(fileno(fp), S_IRUSR|S_IWUSR); +#else + return g_chmod(file, S_IRUSR|S_IWUSR); +#endif +} + +FILE *my_tmpfile(void) +{ +#if HAVE_MKSTEMP + const gchar suffix[] = ".XXXXXX"; + const gchar *tmpdir; + guint tmplen; + const gchar *progname; + guint proglen; + gchar *fname; + gint fd; + FILE *fp; + + tmpdir = get_tmp_dir(); + tmplen = strlen(tmpdir); + progname = g_get_prgname(); + proglen = strlen(progname); + Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix), + return tmpfile()); + + memcpy(fname, tmpdir, tmplen); + fname[tmplen] = G_DIR_SEPARATOR; + memcpy(fname + tmplen + 1, progname, proglen); + memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix)); + + fd = mkstemp(fname); + if (fd < 0) + return tmpfile(); + + g_unlink(fname); + + fp = fdopen(fd, "w+b"); + if (!fp) + close(fd); + else + return fp; +#endif /* HAVE_MKSTEMP */ + + return tmpfile(); +} + +FILE *str_open_as_stream(const gchar *str) +{ + FILE *fp; + size_t len; + + g_return_val_if_fail(str != NULL, NULL); + + fp = my_tmpfile(); + if (!fp) { + FILE_OP_ERROR("str_open_as_stream", "my_tmpfile"); + return NULL; + } + + len = strlen(str); + if (len == 0) return fp; + + if (fwrite(str, len, 1, fp) != 1) { + FILE_OP_ERROR("str_open_as_stream", "fwrite"); + fclose(fp); + return NULL; + } + + rewind(fp); + return fp; +} + +gint str_write_to_file(const gchar *str, const gchar *file) +{ + FILE *fp; + size_t len; + + g_return_val_if_fail(str != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + + if ((fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + len = strlen(str); + if (len == 0) { + fclose(fp); + return 0; + } + + if (fwrite(str, len, 1, fp) != 1) { + FILE_OP_ERROR(file, "fwrite"); + fclose(fp); + g_unlink(file); + return -1; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + g_unlink(file); + return -1; + } + + return 0; +} + +gchar *file_read_to_str(const gchar *file) +{ + FILE *fp; + gchar *str; + + g_return_val_if_fail(file != NULL, NULL); + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + str = file_read_stream_to_str(fp); + + fclose(fp); + + return str; +} + +gchar *file_read_stream_to_str(FILE *fp) +{ + GByteArray *array; + guchar buf[BUFSIZ]; + gint n_read; + gchar *str; + + g_return_val_if_fail(fp != NULL, NULL); + + array = g_byte_array_new(); + + while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) { + if (n_read < sizeof(buf) && ferror(fp)) + break; + g_byte_array_append(array, buf, n_read); + } + + if (ferror(fp)) { + FILE_OP_ERROR("file stream", "fread"); + g_byte_array_free(array, TRUE); + return NULL; + } + + buf[0] = '\0'; + g_byte_array_append(array, buf, 1); + str = (gchar *)array->data; + g_byte_array_free(array, FALSE); + + return str; +} + +gint execute_async(gchar *const argv[]) +{ + g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); + + if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, FALSE) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + return -1; + } + + return 0; +} + +gint execute_sync(gchar *const argv[]) +{ + gint status; + + g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); + + if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL, &status, NULL) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + return -1; + } + +#ifdef G_OS_UNIX + if (WIFEXITED(status)) + return WEXITSTATUS(status); + else + return -1; +#else + return status; +#endif +} + +gint execute_command_line(const gchar *cmdline, gboolean async) +{ + gchar **argv; + gint ret; + + debug_print("execute_command_line(): executing: %s\n", cmdline); + + argv = strsplit_with_quote(cmdline, " ", 0); + + if (async) + ret = execute_async(argv); + else + ret = execute_sync(argv); + + g_strfreev(argv); + + return ret; +} + +gchar *get_command_output(const gchar *cmdline) +{ + gchar *child_stdout; + gint status; + + g_return_val_if_fail(cmdline != NULL, NULL); + + debug_print("get_command_output(): executing: %s\n", cmdline); + + if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status, + NULL) == FALSE) { + g_warning("Can't execute command: %s\n", cmdline); + return NULL; + } + + return child_stdout; +} + +gint open_uri(const gchar *uri, const gchar *cmdline) +{ + gchar buf[BUFFSIZE]; + gchar *p; + + g_return_val_if_fail(uri != NULL, -1); + + if (cmdline && + (p = strchr(cmdline, '%')) && *(p + 1) == 's' && + !strchr(p + 2, '%')) + g_snprintf(buf, sizeof(buf), cmdline, uri); + else { + if (cmdline) + g_warning("Open URI command line is invalid " + "(there must be only one '%%s'): %s", + cmdline); + g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, uri); + } + + execute_command_line(buf, TRUE); + + return 0; +} + +time_t remote_tzoffset_sec(const gchar *zone) +{ + static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT"; + gchar zone3[4]; + gchar *p; + gchar c; + gint iustz; + gint offset; + time_t remoteoffset; + + strncpy(zone3, zone, 3); + zone3[3] = '\0'; + remoteoffset = 0; + + if (sscanf(zone, "%c%d", &c, &offset) == 2 && + (c == '+' || c == '-')) { + remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60; + if (c == '-') + remoteoffset = -remoteoffset; + } else if (!strncmp(zone, "UT" , 2) || + !strncmp(zone, "GMT", 2)) { + remoteoffset = 0; + } else if (strlen(zone3) == 3) { + for (p = ustzstr; *p != '\0'; p += 3) { + if (!g_ascii_strncasecmp(p, zone3, 3)) { + iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8; + remoteoffset = iustz * 3600; + break; + } + } + if (*p == '\0') + return -1; + } else if (strlen(zone3) == 1) { + switch (zone[0]) { + case 'Z': remoteoffset = 0; break; + case 'A': remoteoffset = -1; break; + case 'B': remoteoffset = -2; break; + case 'C': remoteoffset = -3; break; + case 'D': remoteoffset = -4; break; + case 'E': remoteoffset = -5; break; + case 'F': remoteoffset = -6; break; + case 'G': remoteoffset = -7; break; + case 'H': remoteoffset = -8; break; + case 'I': remoteoffset = -9; break; + case 'K': remoteoffset = -10; break; /* J is not used */ + case 'L': remoteoffset = -11; break; + case 'M': remoteoffset = -12; break; + case 'N': remoteoffset = 1; break; + case 'O': remoteoffset = 2; break; + case 'P': remoteoffset = 3; break; + case 'Q': remoteoffset = 4; break; + case 'R': remoteoffset = 5; break; + case 'S': remoteoffset = 6; break; + case 'T': remoteoffset = 7; break; + case 'U': remoteoffset = 8; break; + case 'V': remoteoffset = 9; break; + case 'W': remoteoffset = 10; break; + case 'X': remoteoffset = 11; break; + case 'Y': remoteoffset = 12; break; + default: remoteoffset = 0; break; + } + remoteoffset = remoteoffset * 3600; + } else + return -1; + + return remoteoffset; +} + +time_t tzoffset_sec(time_t *now) +{ + struct tm gmt, *lt; + gint off; + + gmt = *gmtime(now); + lt = localtime(now); + + off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; + + if (lt->tm_year < gmt.tm_year) + off -= 24 * 60; + else if (lt->tm_year > gmt.tm_year) + off += 24 * 60; + else if (lt->tm_yday < gmt.tm_yday) + off -= 24 * 60; + else if (lt->tm_yday > gmt.tm_yday) + off += 24 * 60; + + if (off >= 24 * 60) /* should be impossible */ + off = 23 * 60 + 59; /* if not, insert silly value */ + if (off <= -24 * 60) + off = -(23 * 60 + 59); + + return off * 60; +} + +/* calculate timezone offset */ +gchar *tzoffset(time_t *now) +{ + static gchar offset_string[6]; + struct tm gmt, *lt; + gint off; + gchar sign = '+'; + + gmt = *gmtime(now); + lt = localtime(now); + + off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; + + if (lt->tm_year < gmt.tm_year) + off -= 24 * 60; + else if (lt->tm_year > gmt.tm_year) + off += 24 * 60; + else if (lt->tm_yday < gmt.tm_yday) + off -= 24 * 60; + else if (lt->tm_yday > gmt.tm_yday) + off += 24 * 60; + + if (off < 0) { + sign = '-'; + off = -off; + } + + if (off >= 24 * 60) /* should be impossible */ + off = 23 * 60 + 59; /* if not, insert silly value */ + + sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60); + + return offset_string; +} + +void get_rfc822_date(gchar *buf, gint len) +{ + struct tm *lt; + time_t t; + gchar day[4], mon[4]; + gint dd, hh, mm, ss, yyyy; + + t = time(NULL); + lt = localtime(&t); + + sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n", + day, mon, &dd, &hh, &mm, &ss, &yyyy); + g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s", + day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t)); +} + +/* just a wrapper to suppress the warning of gcc about %c */ +size_t my_strftime(gchar *s, size_t max, const gchar *format, + const struct tm *tm) +{ + return strftime(s, max, format, tm); +} + +static FILE *log_fp = NULL; + +void set_log_file(const gchar *filename) +{ + if (log_fp) return; + log_fp = g_fopen(filename, "wb"); + if (!log_fp) + FILE_OP_ERROR(filename, "fopen"); +} + +void close_log_file(void) +{ + if (log_fp) { + fclose(log_fp); + log_fp = NULL; + } +} + +static guint log_verbosity_count = 0; + +void set_log_verbosity(gboolean verbose) +{ + if (verbose) + log_verbosity_count++; + else if (log_verbosity_count > 0) + log_verbosity_count--; +} + +gboolean get_debug_mode(void) +{ + return debug_mode; +} + +void set_debug_mode(gboolean enable) +{ + debug_mode = enable; +} + +static void log_dummy_func(const gchar *str) +{ +} + +static LogFunc log_print_ui_func = log_dummy_func; +static LogFunc log_message_ui_func = log_dummy_func; +static LogFunc log_warning_ui_func = log_dummy_func; +static LogFunc log_error_ui_func = log_dummy_func; + +static LogFunc log_show_status_func = log_dummy_func; + +void set_log_ui_func(LogFunc print_func, LogFunc message_func, + LogFunc warning_func, LogFunc error_func) +{ + log_print_ui_func = print_func; + log_message_ui_func = message_func; + log_warning_ui_func = warning_func; + log_error_ui_func = error_func; +} + +void set_log_show_status_func(LogFunc status_func) +{ + log_show_status_func = status_func; +} + +void debug_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + if (!debug_mode) return; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + g_print("%s", buf); +} + +#define TIME_LEN 11 + +void log_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + if (debug_mode) fputs(buf, stdout); + log_print_ui_func(buf); + if (log_fp) { + fputs(buf, log_fp); + fflush(log_fp); + } + if (log_verbosity_count) + log_show_status_func(buf + TIME_LEN); +} + +void log_message(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + if (debug_mode) g_message("%s", buf + TIME_LEN); + log_message_ui_func(buf + TIME_LEN); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("* message: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } + log_show_status_func(buf + TIME_LEN); +} + +void log_warning(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + g_warning("%s", buf); + log_warning_ui_func(buf + TIME_LEN); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("** warning: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } +} + +void log_error(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + g_warning("%s", buf); + log_error_ui_func(buf + TIME_LEN); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("*** error: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } +} diff --git a/libsylph/utils.h b/libsylph/utils.h new file mode 100644 index 00000000..fbda26a3 --- /dev/null +++ b/libsylph/utils.h @@ -0,0 +1,493 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <dirent.h> +#include <time.h> +#if HAVE_ALLOCA_H +# include <alloca.h> +#endif +#if HAVE_WCHAR_H +# include <wchar.h> +#endif + +/* Wrappers for C library function that take pathname arguments. */ +#if GLIB_CHECK_VERSION(2, 6, 0) +# include <glib/gstdio.h> +#else + +#define g_open open +#define g_rename rename +#define g_mkdir mkdir +#define g_stat stat +#define g_lstat lstat +#define g_unlink unlink +#define g_remove remove +#define g_rmdir rmdir +#define g_fopen fopen +#define g_freopen freopen + +#endif /* GLIB_CHECK_VERSION */ + +#if !GLIB_CHECK_VERSION(2, 7, 0) + +#ifdef G_OS_UNIX +#define g_chdir chdir +#define g_chmod chmod +#else +gint g_chdir (const gchar *path); +gint g_chmod (const gchar *path, + gint mode); +#endif /* G_OS_UNIX */ + +#endif /* !GLIB_CHECK_VERSION */ + +/* The AC_CHECK_SIZEOF() in configure fails for some machines. + * we provide some fallback values here */ +#if !SIZEOF_UNSIGNED_SHORT + #undef SIZEOF_UNSIGNED_SHORT + #define SIZEOF_UNSIGNED_SHORT 2 +#endif +#if !SIZEOF_UNSIGNED_INT + #undef SIZEOF_UNSIGNED_INT + #define SIZEOF_UNSIGNED_INT 4 +#endif +#if !SIZEOF_UNSIGNED_LONG + #undef SIZEOF_UNSIGNED_LONG + #define SIZEOF_UNSIGNED_LONG 4 +#endif + +#ifndef HAVE_U32_TYPEDEF + #undef u32 /* maybe there is a macro with this name */ + typedef guint32 u32; + #define HAVE_U32_TYPEDEF +#endif + +#ifndef BIG_ENDIAN_HOST + #if (G_BYTE_ORDER == G_BIG_ENDIAN) + #define BIG_ENDIAN_HOST 1 + #endif +#endif + +#define CHDIR_RETURN_IF_FAIL(dir) \ +{ \ + if (change_dir(dir) < 0) return; \ +} + +#define CHDIR_RETURN_VAL_IF_FAIL(dir, val) \ +{ \ + if (change_dir(dir) < 0) return val; \ +} + +#define Xalloca(ptr, size, iffail) \ +{ \ + if ((ptr = alloca(size)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } \ +} + +#define Xstrdup_a(ptr, str, iffail) \ +{ \ + gchar *__tmp; \ + \ + if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else \ + strcpy(__tmp, str); \ + \ + ptr = __tmp; \ +} + +#define Xstrndup_a(ptr, str, len, iffail) \ +{ \ + gchar *__tmp; \ + \ + if ((__tmp = alloca(len + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else { \ + strncpy(__tmp, str, len); \ + __tmp[len] = '\0'; \ + } \ + \ + ptr = __tmp; \ +} + +#define Xstrcat_a(ptr, str1, str2, iffail) \ +{ \ + gchar *__tmp; \ + gint len1, len2; \ + \ + len1 = strlen(str1); \ + len2 = strlen(str2); \ + if ((__tmp = alloca(len1 + len2 + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else { \ + memcpy(__tmp, str1, len1); \ + memcpy(__tmp + len1, str2, len2 + 1); \ + } \ + \ + ptr = __tmp; \ +} + +#define AUTORELEASE_STR(str, iffail) \ +{ \ + gchar *__str; \ + Xstrdup_a(__str, str, iffail); \ + g_free(str); \ + str = __str; \ +} + +#define FILE_OP_ERROR(file, func) \ +{ \ + fprintf(stderr, "%s: ", file); \ + fflush(stderr); \ + perror(func); \ +} + +typedef void (*LogFunc) (const gchar *str); + +/* for macro expansion */ +#define Str(x) #x +#define Xstr(x) Str(x) + +void list_free_strings (GList *list); +void slist_free_strings (GSList *list); + +void hash_free_strings (GHashTable *table); +void hash_free_value_mem (GHashTable *table); + +gint str_case_equal (gconstpointer v, + gconstpointer v2); +guint str_case_hash (gconstpointer key); + +void ptr_array_free_strings (GPtrArray *array); + +typedef gboolean (*StrFindFunc) (const gchar *haystack, + const gchar *needle); + +gboolean str_find (const gchar *haystack, + const gchar *needle); +gboolean str_case_find (const gchar *haystack, + const gchar *needle); +gboolean str_find_equal (const gchar *haystack, + const gchar *needle); +gboolean str_case_find_equal (const gchar *haystack, + const gchar *needle); + +/* number-string conversion */ +gint to_number (const gchar *nstr); +gchar *itos_buf (gchar *nstr, + gint n); +gchar *itos (gint n); +gchar *to_human_readable (off_t size); + +/* alternative string functions */ +gint strcmp2 (const gchar *s1, + const gchar *s2); +gint path_cmp (const gchar *s1, + const gchar *s2); +gchar *strretchomp (gchar *str); +gchar *strtailchomp (gchar *str, + gchar tail_char); +gchar *strcrchomp (gchar *str); +gchar *strcasestr (const gchar *haystack, + const gchar *needle); +gpointer my_memmem (gconstpointer haystack, + size_t haystacklen, + gconstpointer needle, + size_t needlelen); +gchar *strncpy2 (gchar *dest, + const gchar *src, + size_t n); + +/* wide-character functions */ +#if !HAVE_ISWALNUM +int iswalnum (wint_t wc); +#endif +#if !HAVE_ISWSPACE +int iswspace (wint_t wc); +#endif +#if !HAVE_TOWLOWER +wint_t towlower (wint_t wc); +#endif + +#if !HAVE_WCSLEN +size_t wcslen (const wchar_t *s); +#endif +#if !HAVE_WCSCPY +wchar_t *wcscpy (wchar_t *dest, + const wchar_t *src); +#endif +#if !HAVE_WCSNCPY +wchar_t *wcsncpy (wchar_t *dest, + const wchar_t *src, + size_t n); +#endif + +wchar_t *wcsdup (const wchar_t *s); +wchar_t *wcsndup (const wchar_t *s, + size_t n); +wchar_t *strdup_mbstowcs (const gchar *s); +gchar *strdup_wcstombs (const wchar_t *s); +gint wcsncasecmp (const wchar_t *s1, + const wchar_t *s2, + size_t n); +wchar_t *wcscasestr (const wchar_t *haystack, + const wchar_t *needle); +gint get_mbs_len (const gchar *s); + +gboolean is_next_nonascii (const gchar *s); +gint get_next_word_len (const gchar *s); + +/* functions for string parsing */ +gint subject_compare (const gchar *s1, + const gchar *s2); +gint subject_compare_for_sort (const gchar *s1, + const gchar *s2); +void trim_subject_for_compare (gchar *str); +void trim_subject_for_sort (gchar *str); +void trim_subject (gchar *str); +void eliminate_parenthesis (gchar *str, + gchar op, + gchar cl); +void extract_parenthesis (gchar *str, + gchar op, + gchar cl); + +void extract_parenthesis_with_skip_quote (gchar *str, + gchar quote_chr, + gchar op, + gchar cl); + +void eliminate_quote (gchar *str, + gchar quote_chr); +void extract_quote (gchar *str, + gchar quote_chr); +void eliminate_address_comment (gchar *str); +gchar *strchr_with_skip_quote (const gchar *str, + gint quote_chr, + gint c); +gchar *strrchr_with_skip_quote (const gchar *str, + gint quote_chr, + gint c); +void extract_address (gchar *str); +void extract_list_id_str (gchar *str); + +GSList *address_list_append (GSList *addr_list, + const gchar *str); +GSList *references_list_prepend (GSList *msgid_list, + const gchar *str); +GSList *references_list_append (GSList *msgid_list, + const gchar *str); +GSList *newsgroup_list_append (GSList *group_list, + const gchar *str); + +GList *add_history (GList *list, + const gchar *str); + +void remove_return (gchar *str); +void remove_space (gchar *str); +void unfold_line (gchar *str); +void subst_char (gchar *str, + gchar orig, + gchar subst); +void subst_chars (gchar *str, + gchar *orig, + gchar subst); +void subst_null (gchar *str, + gint len, + gchar subst); +void subst_for_filename (gchar *str); +gboolean is_header_line (const gchar *str); +gboolean is_ascii_str (const gchar *str); +gint get_quote_level (const gchar *str); +gint check_line_length (const gchar *str, + gint max_chars, + gint *line); + +gchar *strstr_with_skip_quote (const gchar *haystack, + const gchar *needle); +gchar *strchr_parenthesis_close (const gchar *str, + gchar op, + gchar cl); + +gchar **strsplit_parenthesis (const gchar *str, + gchar op, + gchar cl, + gint max_tokens); +gchar **strsplit_with_quote (const gchar *str, + const gchar *delim, + gint max_tokens); + +gchar *get_abbrev_newsgroup_name (const gchar *group, + gint len); +gchar *trim_string (const gchar *str, + gint len); +gchar *trim_string_before (const gchar *str, + gint len); + +GList *uri_list_extract_filenames (const gchar *uri_list); +gboolean is_uri_string (const gchar *str); +gchar *get_uri_path (const gchar *uri); +gint get_uri_len (const gchar *str); +void decode_uri (gchar *decoded_uri, + const gchar *encoded_uri); +gchar *encode_uri (const gchar *filename); +gint scan_mailto_url (const gchar *mailto, + gchar **to, + gchar **cc, + gchar **bcc, + gchar **subject, + gchar **body); + +/* return static strings */ +const gchar *get_home_dir (void); +const gchar *get_rc_dir (void); +const gchar *get_old_rc_dir (void); +const gchar *get_mail_base_dir (void); +const gchar *get_news_cache_dir (void); +const gchar *get_imap_cache_dir (void); +const gchar *get_mime_tmp_dir (void); +const gchar *get_template_dir (void); +const gchar *get_tmp_dir (void); +gchar *get_tmp_file (void); +const gchar *get_domain_name (void); + +/* file / directory handling */ +off_t get_file_size (const gchar *file); +off_t get_file_size_as_crlf (const gchar *file); +off_t get_left_file_size (FILE *fp); + +gboolean file_exist (const gchar *file, + gboolean allow_fifo); +gboolean is_dir_exist (const gchar *dir); +gboolean is_file_entry_exist (const gchar *file); +gboolean dirent_is_regular_file (struct dirent *d); +gboolean dirent_is_directory (struct dirent *d); + +#define is_file_exist(file) file_exist(file, FALSE) +#define is_file_or_fifo_exist(file) file_exist(file, TRUE) + +gint change_dir (const gchar *dir); +gint make_dir (const gchar *dir); +gint make_dir_hier (const gchar *dir); +gint remove_all_files (const gchar *dir); +gint remove_numbered_files (const gchar *dir, + guint first, + guint last); +gint remove_all_numbered_files (const gchar *dir); +gint remove_expired_files (const gchar *dir, + guint hours); +gint remove_dir_recursive (const gchar *dir); +gint rename_force (const gchar *oldpath, + const gchar *newpath); +gint copy_file (const gchar *src, + const gchar *dest, + gboolean keep_backup); +gint copy_dir (const gchar *src, + const gchar *dest); +gint move_file (const gchar *src, + const gchar *dest, + gboolean overwrite); +gint copy_file_part (FILE *fp, + off_t offset, + size_t length, + const gchar *dest); + +gchar *canonicalize_str (const gchar *str); +gint canonicalize_file (const gchar *src, + const gchar *dest); +gint canonicalize_file_replace (const gchar *file); +gint uncanonicalize_file (const gchar *src, + const gchar *dest); +gint uncanonicalize_file_replace(const gchar *file); + +gchar *normalize_newlines (const gchar *str); + +gchar *get_outgoing_rfc2822_str (FILE *fp); +gchar *generate_mime_boundary (const gchar *prefix); + +gint change_file_mode_rw (FILE *fp, + const gchar *file); +FILE *my_tmpfile (void); +FILE *str_open_as_stream (const gchar *str); +gint str_write_to_file (const gchar *str, + const gchar *file); +gchar *file_read_to_str (const gchar *file); +gchar *file_read_stream_to_str (FILE *fp); + +/* process execution */ +gint execute_async (gchar *const argv[]); +gint execute_sync (gchar *const argv[]); +gint execute_command_line (const gchar *cmdline, + gboolean async); +gchar *get_command_output (const gchar *cmdline); + +/* open URI with external browser */ +gint open_uri(const gchar *uri, const gchar *cmdline); + +/* time functions */ +time_t remote_tzoffset_sec (const gchar *zone); +time_t tzoffset_sec (time_t *now); +gchar *tzoffset (time_t *now); +void get_rfc822_date (gchar *buf, + gint len); + +size_t my_strftime (gchar *s, + size_t max, + const gchar *format, + const struct tm *tm); + +/* logging */ +void set_log_file (const gchar *filename); +void close_log_file (void); +void set_log_verbosity (gboolean verbose); +gboolean get_debug_mode (void); +void set_debug_mode (gboolean enable); + +void set_log_ui_func (LogFunc print_func, + LogFunc message_func, + LogFunc warning_func, + LogFunc error_func); + +void set_log_show_status_func (LogFunc status_func); + +void debug_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_message (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_warning (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_error (const gchar *format, ...) G_GNUC_PRINTF(1, 2); + +#endif /* __UTILS_H__ */ diff --git a/libsylph/uuencode.c b/libsylph/uuencode.c new file mode 100644 index 00000000..e0b2e79a --- /dev/null +++ b/libsylph/uuencode.c @@ -0,0 +1,101 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <ctype.h> + +#define UUDECODE(c) (c=='`' ? 0 : c - ' ') +#define N64(i) (i & ~63) + +const char uudigit[64] = +{ + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_' +}; + +int touufrombits(unsigned char *out, const unsigned char *in, int inlen) +{ + int len; + + if (inlen > 45) return -1; + len = (inlen * 4 + 2) / 3 + 1; + *out++ = uudigit[inlen]; + + for (; inlen >= 3; inlen -= 3) { + *out++ = uudigit[in[0] >> 2]; + *out++ = uudigit[((in[0] << 4) & 0x30) | (in[1] >> 4)]; + *out++ = uudigit[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; + *out++ = uudigit[in[2] & 0x3f]; + in += 3; + } + + if (inlen > 0) { + *out++ = uudigit[(in[0] >> 2)]; + if (inlen == 1) { + *out++ = uudigit[((in[0] << 4) & 0x30)]; + } else { + *out++ = uudigit[(((in[0] << 4) & 0x30) | (in[1] >> 4))] ; + *out++ = uudigit[((in[1] << 2) & 0x3c)]; + } + } + *out = '\0'; + + return len; +} + +int fromuutobits(char *out, const char *in) +{ + int len, outlen, inlen; + register unsigned char digit1, digit2; + + outlen = UUDECODE(in[0]); + in += 1; + if(outlen < 0 || outlen > 45) + return -2; + if(outlen == 0) + return 0; + inlen = (outlen * 4 + 2) / 3; + len = 0; + + for( ; inlen>0; inlen-=4) { + digit1 = UUDECODE(in[0]); + if (N64(digit1)) return -1; + digit2 = UUDECODE(in[1]); + if (N64(digit2)) return -1; + out[len++] = (digit1 << 2) | (digit2 >> 4); + if (inlen > 2) { + digit1 = UUDECODE(in[2]); + if (N64(digit1)) return -1; + out[len++] = (digit2 << 4) | (digit1 >> 2); + if (inlen > 3) { + digit2 = UUDECODE(in[3]); + if (N64(digit2)) return -1; + out[len++] = (digit1 << 6) | digit2; + } + } + in += 4; + } + + return len == outlen ? len : -3; +} diff --git a/libsylph/uuencode.h b/libsylph/uuencode.h new file mode 100644 index 00000000..3658ebc6 --- /dev/null +++ b/libsylph/uuencode.h @@ -0,0 +1,24 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +void touufrombits(unsigned char *, const unsigned char *, int); +int fromuutobits(char *, const char *); + +#define X_UUENCODE_END_LINE '`' +#define UUENCODE_END_LINE ' ' diff --git a/libsylph/xml.c b/libsylph/xml.c new file mode 100644 index 00000000..62a04829 --- /dev/null +++ b/libsylph/xml.c @@ -0,0 +1,655 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <glib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include "xml.h" +#include "utils.h" +#include "codeconv.h" + +#define SPARSE_MEMORY +/* if this is defined all attr.names and tag.names are stored + * in a hash table */ +#if defined(SPARSE_MEMORY) +#include "stringtable.h" + +static StringTable *xml_string_table; + +static void xml_string_table_create(void) +{ + if (xml_string_table == NULL) + xml_string_table = string_table_new(); +} +#define XML_STRING_ADD(str) \ + string_table_insert_string(xml_string_table, (str)) +#define XML_STRING_FREE(str) \ + string_table_free_string(xml_string_table, (str)) + +#define XML_STRING_TABLE_CREATE() \ + xml_string_table_create() + +#else /* !SPARSE_MEMORY */ + +#define XML_STRING_ADD(str) \ + g_strdup(str) +#define XML_STRING_FREE(str) \ + g_free(str) + +#define XML_STRING_TABLE_CREATE() + +#endif /* SPARSE_MEMORY */ + +static void xml_free_tag (XMLTag *tag); +static gint xml_get_parenthesis (XMLFile *file, + gchar *buf, + gint len); + +XMLFile *xml_open_file(const gchar *path) +{ + XMLFile *newfile; + + g_return_val_if_fail(path != NULL, NULL); + + XML_STRING_TABLE_CREATE(); + + newfile = g_new(XMLFile, 1); + + newfile->fp = g_fopen(path, "rb"); + if (!newfile->fp) { + g_free(newfile); + return NULL; + } + + newfile->buf = g_string_new(NULL); + newfile->bufp = newfile->buf->str; + + newfile->dtd = NULL; + newfile->encoding = NULL; + newfile->tag_stack = NULL; + newfile->level = 0; + newfile->is_empty_element = FALSE; + + return newfile; +} + +void xml_close_file(XMLFile *file) +{ + g_return_if_fail(file != NULL); + + if (file->fp) fclose(file->fp); + + g_string_free(file->buf, TRUE); + + g_free(file->dtd); + g_free(file->encoding); + + while (file->tag_stack != NULL) + xml_pop_tag(file); + + g_free(file); +} + +static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level) +{ + GNode *node = NULL; + XMLNode *xmlnode; + XMLTag *tag; + + while (xml_parse_next_tag(file) == 0) { + if (file->level < level) break; + if (file->level == level) { + g_warning("xml_build_tree(): Parse error\n"); + break; + } + + tag = xml_get_current_tag(file); + if (!tag) break; + xmlnode = xml_node_new(xml_copy_tag(tag), NULL); + xmlnode->element = xml_get_element(file); + if (!parent) + node = g_node_new(xmlnode); + else + node = g_node_append_data(parent, xmlnode); + + xml_build_tree(file, node, file->level); + if (file->level == 0) break; + } + + return node; +} + +GNode *xml_parse_file(const gchar *path) +{ + XMLFile *file; + GNode *node; + + file = xml_open_file(path); + g_return_val_if_fail(file != NULL, NULL); + + xml_get_dtd(file); + + node = xml_build_tree(file, NULL, file->level); + + xml_close_file(file); + +#if defined(SPARSE_MEMORY) + if (get_debug_mode()) + string_table_get_stats(xml_string_table); +#endif + + return node; +} + +gint xml_get_dtd(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gchar *bufp = buf; + + if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1; + + if ((*bufp++ == '?') && + (bufp = strcasestr(bufp, "xml")) && + (bufp = strcasestr(bufp + 3, "version")) && + (bufp = strchr(bufp + 7, '?'))) { + file->dtd = g_strdup(buf); + if ((bufp = strcasestr(buf, "encoding=\""))) { + bufp += 9; + extract_quote(bufp, '"'); + file->encoding = g_strdup(bufp); + } else + file->encoding = g_strdup(CS_INTERNAL); + } else { + g_warning("Can't get xml dtd\n"); + return -1; + } + + return 0; +} + +gint xml_parse_next_tag(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gchar *bufp = buf; + gchar *tag_str; + XMLTag *tag; + gint len; + + if (file->is_empty_element == TRUE) { + file->is_empty_element = FALSE; + xml_pop_tag(file); + return 0; + } + + if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) { + g_warning("xml_parse_next_tag(): Can't parse next tag\n"); + return -1; + } + + /* end-tag */ + if (buf[0] == '/') { + if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) { + g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf); + return -1; + } + xml_pop_tag(file); + return 0; + } + + tag = xml_tag_new(NULL); + xml_push_tag(file, tag); + + len = strlen(buf); + if (len > 0 && buf[len - 1] == '/') { + file->is_empty_element = TRUE; + buf[len - 1] = '\0'; + g_strchomp(buf); + } + if (strlen(buf) == 0) { + g_warning("xml_parse_next_tag(): Tag name is empty\n"); + return -1; + } + + while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++; + if (*bufp == '\0') { + tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL); + if (tag_str) { + tag->tag = XML_STRING_ADD(tag_str); + g_free(tag_str); + } else + tag->tag = XML_STRING_ADD(buf); + return 0; + } else { + *bufp++ = '\0'; + tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL); + if (tag_str) { + tag->tag = XML_STRING_ADD(tag_str); + g_free(tag_str); + } else + tag->tag = XML_STRING_ADD(buf); + } + + /* parse attributes ( name=value ) */ + while (*bufp) { + XMLAttr *attr; + gchar *attr_name; + gchar *attr_value; + gchar *utf8_attr_name; + gchar *utf8_attr_value; + gchar *p; + gchar quote; + + while (g_ascii_isspace(*bufp)) bufp++; + attr_name = bufp; + if ((p = strchr(attr_name, '=')) == NULL) { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + bufp = p; + *bufp++ = '\0'; + while (g_ascii_isspace(*bufp)) bufp++; + + if (*bufp != '"' && *bufp != '\'') { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + quote = *bufp; + bufp++; + attr_value = bufp; + if ((p = strchr(attr_value, quote)) == NULL) { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + bufp = p; + *bufp++ = '\0'; + + g_strchomp(attr_name); + xml_unescape_str(attr_value); + utf8_attr_name = conv_codeset_strdup + (attr_name, file->encoding, CS_INTERNAL); + utf8_attr_value = conv_codeset_strdup + (attr_value, file->encoding, CS_INTERNAL); + if (!utf8_attr_name) + utf8_attr_name = g_strdup(attr_name); + if (!utf8_attr_value) + utf8_attr_value = g_strdup(attr_value); + + attr = xml_attr_new(utf8_attr_name, utf8_attr_value); + xml_tag_add_attr(tag, attr); + + g_free(utf8_attr_value); + g_free(utf8_attr_name); + } + + return 0; +} + +void xml_push_tag(XMLFile *file, XMLTag *tag) +{ + g_return_if_fail(tag != NULL); + + file->tag_stack = g_list_prepend(file->tag_stack, tag); + file->level++; +} + +void xml_pop_tag(XMLFile *file) +{ + XMLTag *tag; + + if (!file->tag_stack) return; + + tag = (XMLTag *)file->tag_stack->data; + + xml_free_tag(tag); + file->tag_stack = g_list_remove(file->tag_stack, tag); + file->level--; +} + +XMLTag *xml_get_current_tag(XMLFile *file) +{ + if (file->tag_stack) + return (XMLTag *)file->tag_stack->data; + else + return NULL; +} + +GList *xml_get_current_tag_attr(XMLFile *file) +{ + XMLTag *tag; + + tag = xml_get_current_tag(file); + if (!tag) return NULL; + + return tag->attr; +} + +gchar *xml_get_element(XMLFile *file) +{ + gchar *str; + gchar *new_str; + gchar *end; + + while ((end = strchr(file->bufp, '<')) == NULL) + if (xml_read_line(file) < 0) return NULL; + + if (end == file->bufp) + return NULL; + + str = g_strndup(file->bufp, end - file->bufp); + /* this is not XML1.0 strict */ + g_strstrip(str); + xml_unescape_str(str); + + file->bufp = end; + xml_truncate_buf(file); + + if (str[0] == '\0') { + g_free(str); + return NULL; + } + + new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL); + if (!new_str) + new_str = g_strdup(str); + g_free(str); + + return new_str; +} + +gint xml_read_line(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gint index; + + if (fgets(buf, sizeof(buf), file->fp) == NULL) + return -1; + + index = file->bufp - file->buf->str; + + g_string_append(file->buf, buf); + + file->bufp = file->buf->str + index; + + return 0; +} + +void xml_truncate_buf(XMLFile *file) +{ + gint len; + + len = file->bufp - file->buf->str; + if (len > 0) { + g_string_erase(file->buf, 0, len); + file->bufp = file->buf->str; + } +} + +gboolean xml_compare_tag(XMLFile *file, const gchar *name) +{ + XMLTag *tag; + + tag = xml_get_current_tag(file); + + if (tag && strcmp(tag->tag, name) == 0) + return TRUE; + else + return FALSE; +} + +XMLNode *xml_node_new(XMLTag *tag, const gchar *text) +{ + XMLNode *node; + + node = g_new(XMLNode, 1); + node->tag = tag; + node->element = g_strdup(text); + + return node; +} + +XMLTag *xml_tag_new(const gchar *tag) +{ + XMLTag *new_tag; + + new_tag = g_new(XMLTag, 1); + if (tag) + new_tag->tag = XML_STRING_ADD(tag); + else + new_tag->tag = NULL; + new_tag->attr = NULL; + + return new_tag; +} + +XMLAttr *xml_attr_new(const gchar *name, const gchar *value) +{ + XMLAttr *new_attr; + + new_attr = g_new(XMLAttr, 1); + new_attr->name = XML_STRING_ADD(name); + new_attr->value = g_strdup(value); + + return new_attr; +} + +void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr) +{ + tag->attr = g_list_append(tag->attr, attr); +} + +XMLTag *xml_copy_tag(XMLTag *tag) +{ + XMLTag *new_tag; + XMLAttr *attr; + GList *list; + + new_tag = xml_tag_new(tag->tag); + for (list = tag->attr; list != NULL; list = list->next) { + attr = xml_copy_attr((XMLAttr *)list->data); + xml_tag_add_attr(new_tag, attr); + } + + return new_tag; +} + +XMLAttr *xml_copy_attr(XMLAttr *attr) +{ + return xml_attr_new(attr->name, attr->value); +} + +gint xml_unescape_str(gchar *str) +{ + gchar *start; + gchar *end; + gchar *p = str; + gchar *esc_str; + gchar ch; + gint len; + + while ((start = strchr(p, '&')) != NULL) { + if ((end = strchr(start + 1, ';')) == NULL) { + g_warning("Unescaped `&' appeared\n"); + p = start + 1; + continue; + } + len = end - start + 1; + if (len < 3) { + p = end + 1; + continue; + } + + Xstrndup_a(esc_str, start, len, return -1); + if (!strcmp(esc_str, "<")) + ch = '<'; + else if (!strcmp(esc_str, ">")) + ch = '>'; + else if (!strcmp(esc_str, "&")) + ch = '&'; + else if (!strcmp(esc_str, "'")) + ch = '\''; + else if (!strcmp(esc_str, """)) + ch = '\"'; + else { + p = end + 1; + continue; + } + + *start = ch; + memmove(start + 1, end + 1, strlen(end + 1) + 1); + p = start + 1; + } + + return 0; +} + +gint xml_file_put_escape_str(FILE *fp, const gchar *str) +{ + const gchar *p; + + g_return_val_if_fail(fp != NULL, -1); + + if (!str) return 0; + + for (p = str; *p != '\0'; p++) { + switch (*p) { + case '<': + fputs("<", fp); + break; + case '>': + fputs(">", fp); + break; + case '&': + fputs("&", fp); + break; + case '\'': + fputs("'", fp); + break; + case '\"': + fputs(""", fp); + break; + default: + fputc(*p, fp); + } + } + + return 0; +} + +gint xml_file_put_xml_decl(FILE *fp) +{ + g_return_val_if_fail(fp != NULL, -1); + + fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL); + return 0; +} + +gint xml_file_put_node(FILE *fp, XMLNode *node) +{ + GList *cur; + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(node != NULL, -1); + + fprintf(fp, "<%s", node->tag->tag); + + for (cur = node->tag->attr; cur != NULL; cur = cur->next) { + XMLAttr *attr = (XMLAttr *)cur->data; + fprintf(fp, " %s=\"", attr->name); + xml_file_put_escape_str(fp, attr->value); + fputs("\"", fp); + } + + if (node->element) { + fputs(">", fp); + xml_file_put_escape_str(fp, node->element); + fprintf(fp, "</%s>\n", node->tag->tag); + } else { + fputs(" />\n", fp); + } + + return 0; +} + +void xml_free_node(XMLNode *node) +{ + if (!node) return; + + xml_free_tag(node->tag); + g_free(node->element); + g_free(node); +} + +static gboolean xml_free_func(GNode *node, gpointer data) +{ + XMLNode *xmlnode = node->data; + + xml_free_node(xmlnode); + return FALSE; +} + +void xml_free_tree(GNode *node) +{ + g_return_if_fail(node != NULL); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func, + NULL); + + g_node_destroy(node); +} + +static void xml_free_tag(XMLTag *tag) +{ + if (!tag) return; + + XML_STRING_FREE(tag->tag); + while (tag->attr != NULL) { + XMLAttr *attr = (XMLAttr *)tag->attr->data; + XML_STRING_FREE(attr->name); + g_free(attr->value); + g_free(attr); + tag->attr = g_list_remove(tag->attr, tag->attr->data); + } + g_free(tag); +} + +static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len) +{ + gchar *start; + gchar *end; + + buf[0] = '\0'; + + while ((start = strchr(file->bufp, '<')) == NULL) + if (xml_read_line(file) < 0) return -1; + + start++; + file->bufp = start; + + while ((end = strchr(file->bufp, '>')) == NULL) + if (xml_read_line(file) < 0) return -1; + + strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len)); + g_strstrip(buf); + file->bufp = end + 1; + xml_truncate_buf(file); + + return 0; +} diff --git a/libsylph/xml.h b/libsylph/xml.h new file mode 100644 index 00000000..b41449a4 --- /dev/null +++ b/libsylph/xml.h @@ -0,0 +1,108 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __XML_H__ +#define __XML_H__ + +#include <glib.h> +#include <stdio.h> + +#define XMLBUFSIZE 8192 + +typedef struct _XMLAttr XMLAttr; +typedef struct _XMLTag XMLTag; +typedef struct _XMLNode XMLNode; +typedef struct _XMLFile XMLFile; + +struct _XMLAttr +{ + gchar *name; + gchar *value; +}; + +struct _XMLTag +{ + gchar *tag; + GList *attr; +}; + +struct _XMLNode +{ + XMLTag *tag; + gchar *element; +}; + +struct _XMLFile +{ + FILE *fp; + + GString *buf; + gchar *bufp; + + gchar *dtd; + gchar *encoding; + + GList *tag_stack; + guint level; + + gboolean is_empty_element; +}; + +XMLFile *xml_open_file (const gchar *path); +void xml_close_file (XMLFile *file); +GNode *xml_parse_file (const gchar *path); + +gint xml_get_dtd (XMLFile *file); +gint xml_parse_next_tag (XMLFile *file); +void xml_push_tag (XMLFile *file, + XMLTag *tag); +void xml_pop_tag (XMLFile *file); + +XMLTag *xml_get_current_tag (XMLFile *file); +GList *xml_get_current_tag_attr(XMLFile *file); +gchar *xml_get_element (XMLFile *file); + +gint xml_read_line (XMLFile *file); +void xml_truncate_buf (XMLFile *file); +gboolean xml_compare_tag (XMLFile *file, + const gchar *name); + +XMLNode *xml_node_new (XMLTag *tag, + const gchar *text); +XMLTag *xml_tag_new (const gchar *tag); +XMLAttr *xml_attr_new (const gchar *name, + const gchar *value); +void xml_tag_add_attr (XMLTag *tag, + XMLAttr *attr); + +XMLTag *xml_copy_tag (XMLTag *tag); +XMLAttr *xml_copy_attr (XMLAttr *attr); + +gint xml_unescape_str (gchar *str); +gint xml_file_put_escape_str (FILE *fp, + const gchar *str); + +gint xml_file_put_xml_decl (FILE *fp); +gint xml_file_put_node (FILE *fp, + XMLNode *node); + +void xml_free_node (XMLNode *node); +void xml_free_tree (GNode *node); + +#endif /* __XML_H__ */ |