aboutsummaryrefslogtreecommitdiff
path: root/libsylph
diff options
context:
space:
mode:
Diffstat (limited to 'libsylph')
-rw-r--r--libsylph/Makefile.am25
-rw-r--r--libsylph/base64.c168
-rw-r--r--libsylph/base64.h46
-rw-r--r--libsylph/codeconv.c1982
-rw-r--r--libsylph/codeconv.h239
-rw-r--r--libsylph/defs.h122
-rw-r--r--libsylph/prefs.c465
-rw-r--r--libsylph/prefs.h80
-rw-r--r--libsylph/quoted-printable.c232
-rw-r--r--libsylph/quoted-printable.h36
-rw-r--r--libsylph/session.c793
-rw-r--r--libsylph/session.h205
-rw-r--r--libsylph/socket.c1397
-rw-r--r--libsylph/socket.h124
-rw-r--r--libsylph/ssl.c175
-rw-r--r--libsylph/ssl.h58
-rw-r--r--libsylph/stringtable.c163
-rw-r--r--libsylph/stringtable.h38
-rw-r--r--libsylph/unmime.c134
-rw-r--r--libsylph/unmime.h27
-rw-r--r--libsylph/utils.c3436
-rw-r--r--libsylph/utils.h493
-rw-r--r--libsylph/uuencode.c101
-rw-r--r--libsylph/uuencode.h24
-rw-r--r--libsylph/xml.c655
-rw-r--r--libsylph/xml.h108
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, "&lt;"))
+ ch = '<';
+ else if (!strcmp(esc_str, "&gt;"))
+ ch = '>';
+ else if (!strcmp(esc_str, "&amp;"))
+ ch = '&';
+ else if (!strcmp(esc_str, "&apos;"))
+ ch = '\'';
+ else if (!strcmp(esc_str, "&quot;"))
+ 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("&lt;", fp);
+ break;
+ case '>':
+ fputs("&gt;", fp);
+ break;
+ case '&':
+ fputs("&amp;", fp);
+ break;
+ case '\'':
+ fputs("&apos;", fp);
+ break;
+ case '\"':
+ fputs("&quot;", 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__ */