2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include <sys/types.h>
33 #include <sys/signal.h>
42 static gint session_connect_cb (SockInfo *sock,
44 static gint session_close (Session *session);
46 static gboolean session_timeout_cb (gpointer data);
48 static gboolean session_recv_msg_idle_cb (gpointer data);
49 static gboolean session_recv_data_idle_cb (gpointer data);
51 static gboolean session_read_msg_cb (SockInfo *source,
52 GIOCondition condition,
54 static gboolean session_read_data_cb (SockInfo *source,
55 GIOCondition condition,
57 static gboolean session_write_msg_cb (SockInfo *source,
58 GIOCondition condition,
60 static gboolean session_write_data_cb (SockInfo *source,
61 GIOCondition condition,
65 void session_init(Session *session)
67 session->type = SESSION_UNKNOWN;
69 session->server = NULL;
72 session->ssl_type = SSL_NONE;
74 session->nonblocking = TRUE;
75 session->state = SESSION_READY;
76 session->last_access_time = time(NULL);
78 gettimeofday(&session->tv_prev, NULL);
84 session->read_buf_p = session->read_buf;
85 session->read_buf_len = 0;
87 session->read_msg_buf = g_string_sized_new(1024);
88 session->read_data_buf = g_byte_array_new();
90 session->write_buf = NULL;
91 session->write_buf_p = NULL;
92 session->write_buf_len = 0;
94 session->write_data = NULL;
95 session->write_data_p = NULL;
96 session->write_data_len = 0;
98 session->timeout_tag = 0;
99 session->timeout_interval = 0;
101 session->data = NULL;
105 *\brief Set up parent and child process
106 * Childloop: Read commands from parent,
107 * send to server, get answer, pass to parent
109 *\param session Contains session information
110 * server to connect to
114 * -1 : pipe / fork errors (parent)
115 * 1 : connection error (child)
117 gint session_connect(Session *session, const gchar *server, gushort port)
119 session->server = g_strdup(server);
120 session->port = port;
122 session->conn_id = sock_connect_async(server, port, session_connect_cb,
124 if (session->conn_id < 0) {
125 g_warning("can't connect to server.");
126 session_close(session);
133 static gint session_connect_cb(SockInfo *sock, gpointer data)
135 Session *session = SESSION(data);
137 session->conn_id = 0;
140 g_warning("can't connect to server.");
141 session->state = SESSION_ERROR;
145 session->sock = sock;
148 if (session->ssl_type == SSL_TUNNEL) {
149 sock_set_nonblocking_mode(sock, FALSE);
150 if (!ssl_init_socket(sock)) {
151 g_warning("can't initialize SSL.");
152 session->state = SESSION_ERROR;
158 sock_set_nonblocking_mode(sock, session->nonblocking);
160 debug_print("session (%p): connected\n", session);
162 session->state = SESSION_RECV;
163 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
171 *\brief child and parent: send DISCONNECT message to other process
173 *\param session Contains session information
177 gint session_disconnect(Session *session)
179 session_close(session);
186 *\param session Contains session information
188 void session_destroy(Session *session)
190 g_return_if_fail(session != NULL);
191 g_return_if_fail(session->destroy != NULL);
193 session_close(session);
194 session->destroy(session);
195 g_free(session->server);
196 g_string_free(session->read_msg_buf, TRUE);
197 g_byte_array_free(session->read_data_buf, TRUE);
198 g_free(session->read_data_terminator);
199 g_free(session->write_buf);
201 debug_print("session (%p): destroyed\n", session);
206 gboolean session_is_connected(Session *session)
208 return (session->state == SESSION_READY ||
209 session->state == SESSION_SEND ||
210 session->state == SESSION_RECV);
213 void session_set_access_time(Session *session)
215 session->last_access_time = time(NULL);
218 void session_set_timeout(Session *session, guint interval)
220 if (session->timeout_tag > 0)
221 g_source_remove(session->timeout_tag);
223 session->timeout_interval = interval;
225 session->timeout_tag =
226 g_timeout_add(interval, session_timeout_cb, session);
228 session->timeout_tag = 0;
231 static gboolean session_timeout_cb(gpointer data)
233 Session *session = SESSION(data);
235 g_warning("session timeout.\n");
237 if (session->io_tag > 0) {
238 g_source_remove(session->io_tag);
242 session->timeout_tag = 0;
243 session->state = SESSION_TIMEOUT;
248 void session_set_recv_message_notify(Session *session,
249 RecvMsgNotify notify_func, gpointer data)
251 session->recv_msg_notify = notify_func;
252 session->recv_msg_notify_data = data;
255 void session_set_recv_data_progressive_notify
257 RecvDataProgressiveNotify notify_func,
260 session->recv_data_progressive_notify = notify_func,
261 session->recv_data_progressive_notify_data = data;
264 void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func,
267 session->recv_data_notify = notify_func;
268 session->recv_data_notify_data = data;
271 void session_set_send_data_progressive_notify
273 SendDataProgressiveNotify notify_func,
276 session->send_data_progressive_notify = notify_func;
277 session->send_data_progressive_notify_data = data;
280 void session_set_send_data_notify(Session *session, SendDataNotify notify_func,
283 session->send_data_notify = notify_func;
284 session->send_data_notify_data = data;
288 *\brief child and parent cleanup (child closes first)
290 *\param session Contains session information
294 static gint session_close(Session *session)
296 g_return_val_if_fail(session != NULL, -1);
298 if (session->conn_id > 0) {
299 sock_connect_async_cancel(session->conn_id);
300 session->conn_id = 0;
301 debug_print("session (%p): connection cancelled\n", session);
304 session_set_timeout(session, 0);
306 if (session->io_tag > 0) {
307 g_source_remove(session->io_tag);
312 sock_close(session->sock);
313 session->sock = NULL;
314 session->state = SESSION_DISCONNECTED;
315 debug_print("session (%p): closed\n", session);
322 gint session_start_tls(Session *session)
326 nb_mode = sock_is_nonblocking_mode(session->sock);
329 sock_set_nonblocking_mode(session->sock, FALSE);
331 if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) {
332 g_warning("can't start TLS session.\n");
334 sock_set_nonblocking_mode(session->sock, TRUE);
339 sock_set_nonblocking_mode(session->sock, session->nonblocking);
345 gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
349 g_return_val_if_fail(session->write_buf == NULL, -1);
350 g_return_val_if_fail(msg != NULL, -1);
351 g_return_val_if_fail(msg[0] != '\0', -1);
353 session->state = SESSION_SEND;
354 session->write_buf = g_strconcat(msg, "\r\n", NULL);
355 session->write_buf_p = session->write_buf;
356 session->write_buf_len = strlen(msg) + 2;
358 ret = session_write_msg_cb(session->sock, G_IO_OUT, session);
361 session->io_tag = sock_add_watch(session->sock, G_IO_OUT,
362 session_write_msg_cb, session);
363 else if (session->state == SESSION_ERROR)
369 gint session_recv_msg(Session *session)
371 g_return_val_if_fail(session->read_msg_buf->len == 0, -1);
373 session->state = SESSION_RECV;
375 if (session->read_buf_len > 0)
376 g_idle_add(session_recv_msg_idle_cb, session);
378 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
379 session_read_msg_cb, session);
384 static gboolean session_recv_msg_idle_cb(gpointer data)
386 Session *session = SESSION(data);
389 ret = session_read_msg_cb(session->sock, G_IO_IN, session);
392 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
393 session_read_msg_cb, session);
399 *\brief parent (child?): send data to other process
401 *\param session Contains session information
408 gint session_send_data(Session *session, const guchar *data, guint size)
412 g_return_val_if_fail(session->write_data == NULL, -1);
413 g_return_val_if_fail(data != NULL, -1);
414 g_return_val_if_fail(size != 0, -1);
416 session->state = SESSION_SEND;
418 session->write_data = data;
419 session->write_data_p = session->write_data;
420 session->write_data_len = size;
421 gettimeofday(&session->tv_prev, NULL);
423 ret = session_write_data_cb(session->sock, G_IO_OUT, session);
426 session->io_tag = sock_add_watch(session->sock, G_IO_OUT,
427 session_write_data_cb,
429 else if (session->state == SESSION_ERROR)
435 gint session_recv_data(Session *session, guint size, const gchar *terminator)
437 g_return_val_if_fail(session->read_data_buf->len == 0, -1);
439 session->state = SESSION_RECV;
441 g_free(session->read_data_terminator);
442 session->read_data_terminator = g_strdup(terminator);
443 gettimeofday(&session->tv_prev, NULL);
445 if (session->read_buf_len > 0)
446 g_idle_add(session_recv_data_idle_cb, session);
448 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
449 session_read_data_cb, session);
454 static gboolean session_recv_data_idle_cb(gpointer data)
456 Session *session = SESSION(data);
459 ret = session_read_data_cb(session->sock, G_IO_IN, session);
462 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
463 session_read_data_cb, session);
468 static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition,
471 Session *session = SESSION(data);
472 gchar buf[SESSION_BUFFSIZE];
478 g_return_val_if_fail(condition == G_IO_IN, FALSE);
480 session_set_timeout(session, session->timeout_interval);
482 if (session->read_buf_len == 0) {
485 read_len = sock_read(session->sock, session->read_buf,
486 SESSION_BUFFSIZE - 1);
488 if (read_len == -1 && session->state == SESSION_DISCONNECTED) {
489 g_warning ("sock_read: session disconnected\n");
490 if (session->io_tag > 0) {
491 g_source_remove(session->io_tag);
498 g_warning("sock_read: received EOF\n");
499 session->state = SESSION_EOF;
508 g_warning("sock_read: %s\n", g_strerror(errno));
509 session->state = SESSION_ERROR;
514 session->read_buf_len = read_len;
517 if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len))
519 line_len = newline - session->read_buf_p + 1;
521 line_len = session->read_buf_len;
526 memcpy(buf, session->read_buf_p, line_len);
527 buf[line_len] = '\0';
529 g_string_append(session->read_msg_buf, buf);
531 session->read_buf_len -= line_len;
532 if (session->read_buf_len == 0)
533 session->read_buf_p = session->read_buf;
535 session->read_buf_p += line_len;
537 /* incomplete read */
538 if (buf[line_len - 1] != '\n')
542 if (session->io_tag > 0) {
543 g_source_remove(session->io_tag);
548 msg = g_strdup(session->read_msg_buf->str);
550 g_string_truncate(session->read_msg_buf, 0);
552 ret = session->recv_msg(session, msg);
553 session->recv_msg_notify(session, msg, session->recv_msg_notify_data);
558 session->state = SESSION_ERROR;
563 static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition,
566 Session *session = SESSION(data);
567 GByteArray *data_buf;
569 gboolean complete = FALSE;
573 g_return_val_if_fail(condition == G_IO_IN, FALSE);
575 session_set_timeout(session, session->timeout_interval);
577 if (session->read_buf_len == 0) {
580 read_len = sock_read(session->sock, session->read_buf,
584 g_warning("sock_read: received EOF\n");
585 session->state = SESSION_EOF;
594 g_warning("sock_read: %s\n", g_strerror(errno));
595 session->state = SESSION_ERROR;
600 session->read_buf_len = read_len;
603 data_buf = session->read_data_buf;
604 terminator_len = strlen(session->read_data_terminator);
606 if (session->read_buf_len == 0)
609 g_byte_array_append(data_buf, session->read_buf_p,
610 session->read_buf_len);
612 session->read_buf_len = 0;
613 session->read_buf_p = session->read_buf;
615 /* check if data is terminated */
616 if (data_buf->len >= terminator_len) {
617 if (memcmp(data_buf->data, session->read_data_terminator,
618 terminator_len) == 0)
620 else if (data_buf->len >= terminator_len + 2 &&
621 memcmp(data_buf->data + data_buf->len -
622 (terminator_len + 2), "\r\n", 2) == 0 &&
623 memcmp(data_buf->data + data_buf->len -
624 terminator_len, session->read_data_terminator,
625 terminator_len) == 0)
629 /* incomplete read */
631 struct timeval tv_cur;
633 gettimeofday(&tv_cur, NULL);
634 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
635 tv_cur.tv_usec - session->tv_prev.tv_usec >
636 UI_REFRESH_INTERVAL) {
637 session->recv_data_progressive_notify
638 (session, data_buf->len, 0,
639 session->recv_data_progressive_notify_data);
640 gettimeofday(&session->tv_prev, NULL);
646 if (session->io_tag > 0) {
647 g_source_remove(session->io_tag);
651 data_len = data_buf->len - terminator_len;
654 ret = session->recv_data_finished(session, (gchar *)data_buf->data,
657 g_byte_array_set_size(data_buf, 0);
659 session->recv_data_notify(session, data_len,
660 session->recv_data_notify_data);
663 session->state = SESSION_ERROR;
668 static gint session_write_buf(Session *session)
673 g_return_val_if_fail(session->write_buf != NULL, -1);
674 g_return_val_if_fail(session->write_buf_p != NULL, -1);
675 g_return_val_if_fail(session->write_buf_len > 0, -1);
677 to_write_len = session->write_buf_len -
678 (session->write_buf_p - session->write_buf);
679 to_write_len = MIN(to_write_len, SESSION_BUFFSIZE);
681 write_len = sock_write(session->sock, session->write_buf_p,
690 g_warning("sock_write: %s\n", g_strerror(errno));
691 session->state = SESSION_ERROR;
696 /* incomplete write */
697 if (session->write_buf_p - session->write_buf + write_len <
698 session->write_buf_len) {
699 session->write_buf_p += write_len;
703 g_free(session->write_buf);
704 session->write_buf = NULL;
705 session->write_buf_p = NULL;
706 session->write_buf_len = 0;
711 static gint session_write_data(Session *session)
716 g_return_val_if_fail(session->write_data != NULL, -1);
717 g_return_val_if_fail(session->write_data_p != NULL, -1);
718 g_return_val_if_fail(session->write_data_len > 0, -1);
720 to_write_len = session->write_data_len -
721 (session->write_data_p - session->write_data);
722 to_write_len = MIN(to_write_len, SESSION_BUFFSIZE);
724 write_len = sock_write(session->sock, session->write_data_p,
733 g_warning("sock_write: %s\n", g_strerror(errno));
734 session->state = SESSION_ERROR;
739 /* incomplete write */
740 if (session->write_data_p - session->write_data + write_len <
741 session->write_data_len) {
742 session->write_data_p += write_len;
746 session->write_data = NULL;
747 session->write_data_p = NULL;
748 session->write_data_len = 0;
753 static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition,
756 Session *session = SESSION(data);
759 g_return_val_if_fail(condition == G_IO_OUT, FALSE);
760 g_return_val_if_fail(session->write_buf != NULL, FALSE);
761 g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
762 g_return_val_if_fail(session->write_buf_len > 0, FALSE);
764 ret = session_write_buf(session);
767 session->state = SESSION_ERROR;
772 if (session->io_tag > 0) {
773 g_source_remove(session->io_tag);
777 session_recv_msg(session);
782 static gboolean session_write_data_cb(SockInfo *source,
783 GIOCondition condition, gpointer data)
785 Session *session = SESSION(data);
786 guint write_data_len;
789 g_return_val_if_fail(condition == G_IO_OUT, FALSE);
790 g_return_val_if_fail(session->write_data != NULL, FALSE);
791 g_return_val_if_fail(session->write_data_p != NULL, FALSE);
792 g_return_val_if_fail(session->write_data_len > 0, FALSE);
794 write_data_len = session->write_data_len;
796 ret = session_write_data(session);
799 session->state = SESSION_ERROR;
801 } else if (ret > 0) {
802 struct timeval tv_cur;
804 gettimeofday(&tv_cur, NULL);
805 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
806 tv_cur.tv_usec - session->tv_prev.tv_usec >
807 UI_REFRESH_INTERVAL) {
808 session_set_timeout(session, session->timeout_interval);
809 session->send_data_progressive_notify
811 session->write_data_p - session->write_data,
813 session->send_data_progressive_notify_data);
814 gettimeofday(&session->tv_prev, NULL);
819 if (session->io_tag > 0) {
820 g_source_remove(session->io_tag);
825 ret = session->send_data_finished(session, write_data_len);
826 session->send_data_notify(session, write_data_len,
827 session->send_data_notify_data);