Fix two warnings.
[jelmer/ctrlproxy.git] / lib / transport.c
1
2 /*
3         ctrlproxy: A modular IRC proxy
4         (c) 2002-2008 Jelmer Vernooij <jelmer@nl.linux.org>
5
6         This program is free software; you can redistribute it and/or modify
7         it under the terms of the GNU General Public License as published by
8         the Free Software Foundation; either version 3 of the License, or
9         (at your option) any later version.
10
11         This program is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14         GNU General Public License for more details.
15
16         You should have received a copy of the GNU General Public License
17         along with this program; if not, write to the Free Software
18         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "transport.h"
22 #include "line.h"
23 #include "util.h"
24 #include <glib.h>
25
26 static gboolean transport_send_queue(GIOChannel *c, GIOCondition cond, 
27                                                                                  void *_client);
28
29 static gboolean handle_transport_receive(GIOChannel *c, GIOCondition cond, 
30                                                                           void *_transport)
31 {
32         struct irc_transport *transport = _transport;
33         struct irc_line *l;
34
35         g_assert(transport);
36
37         if (cond & G_IO_ERR) {
38                 char *tmp = g_strdup_printf("Error reading from client: %s", 
39                                                   g_io_channel_unix_get_sock_error(c));
40                 transport->callbacks->error(transport, tmp);
41                 g_free(tmp);
42                 return FALSE;
43         }
44
45         if (cond & G_IO_HUP) {
46                 transport->callbacks->hangup(transport);
47                 return FALSE;
48         }
49
50         if (cond & G_IO_IN) {
51                 GError *error = NULL;
52                 GIOStatus status;
53                 gboolean ret = TRUE;
54                 
55                 while ((status = irc_recv_line(c, transport->incoming_iconv, &error, 
56                                                                            &l)) == G_IO_STATUS_NORMAL) {
57
58                         ret &= transport->callbacks->recv(transport, l);
59                         free_line(l);
60
61                         if (!ret)
62                                 return FALSE;
63                 }
64
65                 if (status == G_IO_STATUS_EOF) {
66                         transport->callbacks->hangup(transport);
67                         return FALSE;
68                 }
69
70                 if (status != G_IO_STATUS_AGAIN) {
71                         if (error->domain == G_CONVERT_ERROR &&
72                                 error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) {
73                                 transport->callbacks->charset_error(transport, 
74                                                                                                         error->message);
75                         } else {
76                                 return transport->callbacks->error(transport, error?error->message:NULL);
77                         }
78                 }
79                 return ret;
80         }
81
82         return TRUE;
83 }
84
85 void irc_transport_set_callbacks(struct irc_transport *transport, 
86                                                                  const struct irc_transport_callbacks *callbacks, void *userdata)
87 {
88         transport->userdata = userdata;
89         transport->callbacks = callbacks;
90
91         transport->incoming_id = g_io_add_watch(
92                                                         transport->incoming, G_IO_IN | G_IO_HUP, 
93                                                         handle_transport_receive, transport);
94 }
95
96 /* GIOChannels passed into this function 
97  * should preferably:
98  *  - have no encoding set
99  *  - work asynchronously
100  *
101  * @param iochannel Channel to talk over 
102  */
103 struct irc_transport *irc_transport_new_iochannel(GIOChannel *iochannel)
104 {
105         struct irc_transport *ret = g_new0(struct irc_transport, 1);
106
107         ret->incoming = iochannel;
108         ret->pending_lines = g_queue_new();
109         ret->outgoing_iconv = ret->incoming_iconv = (GIConv)-1;
110         g_io_channel_ref(ret->incoming);
111
112         return ret;
113 }
114
115 void irc_transport_disconnect(struct irc_transport *transport)
116 {
117         if (transport->incoming == NULL)
118                 return; /* We're already disconnected */
119
120         g_io_channel_unref(transport->incoming);
121
122         g_source_remove(transport->incoming_id);
123         if (transport->outgoing_id)
124                 g_source_remove(transport->outgoing_id);
125
126         transport->incoming = NULL;
127
128         transport->callbacks->disconnect(transport);
129 }
130
131 static void free_pending_line(void *_line, void *userdata)
132 {
133         free_line((struct irc_line *)_line);
134 }
135
136 void free_irc_transport(struct irc_transport *transport)
137 {
138         /* Should already be disconnected */
139         g_assert(transport->incoming == NULL);
140         g_free(transport->charset);
141
142         if (transport->outgoing_iconv != (GIConv)-1)
143                 g_iconv_close(transport->outgoing_iconv);
144         if (transport->incoming_iconv != (GIConv)-1)
145                 g_iconv_close(transport->incoming_iconv);
146
147         g_assert(transport->pending_lines != NULL);
148         g_queue_foreach(transport->pending_lines, free_pending_line, NULL);
149         g_queue_free(transport->pending_lines);
150
151         g_free(transport);
152 }
153
154 /**
155  * Change the character set used to send data to a client
156  * @param c client to change the character set for
157  * @param name name of the character set to change to
158  * @return whether changing the character set succeeded
159  */
160 gboolean transport_set_charset(struct irc_transport *transport, const char *name)
161 {
162         GIConv tmp;
163
164         if (name != NULL) {
165                 tmp = g_iconv_open(name, "UTF-8");
166
167                 if (tmp == (GIConv)-1) {
168                         return FALSE;
169                 }
170         } else {
171                 tmp = (GIConv)-1;
172         }
173         
174         if (transport->outgoing_iconv != (GIConv)-1)
175                 g_iconv_close(transport->outgoing_iconv);
176
177         transport->outgoing_iconv = tmp;
178
179         if (name != NULL) {
180                 tmp = g_iconv_open("UTF-8", name);
181
182                 if (tmp == (GIConv)-1) {
183                         return FALSE;
184                 }
185         } else {
186                 tmp = (GIConv)-1;
187         }
188
189         if (transport->incoming_iconv != (GIConv)-1)
190                 g_iconv_close(transport->incoming_iconv);
191
192         transport->incoming_iconv = tmp;
193
194         g_free(transport->charset);
195         transport->charset = g_strdup(name);
196
197         return TRUE;
198 }
199
200 static gboolean transport_send_queue(GIOChannel *ioc, GIOCondition cond, 
201                                                                           void *_transport)
202 {
203         gboolean ret = FALSE;
204         struct irc_transport *transport = _transport;
205         GIOStatus status;
206
207         g_assert(ioc == transport->incoming);
208
209         status = g_io_channel_flush(transport->incoming, NULL);
210         if (status == G_IO_STATUS_AGAIN)
211                 ret = TRUE;
212
213         g_assert(transport->pending_lines != NULL);
214
215         while (!g_queue_is_empty(transport->pending_lines)) {
216                 GError *error = NULL;
217                 struct irc_line *l = g_queue_pop_head(transport->pending_lines);
218
219                 g_assert(transport->incoming != NULL);
220                 status = irc_send_line(transport->incoming, 
221                                                            transport->outgoing_iconv, l, &error);
222
223                 switch (status) {
224                 case G_IO_STATUS_AGAIN:
225                         g_queue_push_head(transport->pending_lines, l);
226                         return TRUE;
227                 case G_IO_STATUS_ERROR:
228                         transport->callbacks->log(transport, l, error);
229                         break;
230                 case G_IO_STATUS_EOF:
231                         transport->outgoing_id = 0;
232
233                         transport->callbacks->hangup(transport);
234
235                         free_line(l);
236
237                         return FALSE;
238                 case G_IO_STATUS_NORMAL:
239                         transport->last_line_sent = time(NULL);
240                         break;
241                 }
242
243                 status = g_io_channel_flush(transport->incoming, &error);
244                 switch (status) {
245                 case G_IO_STATUS_EOF:
246                         g_assert_not_reached();
247                 case G_IO_STATUS_AGAIN:
248                         free_line(l);
249                         return TRUE;
250                 case G_IO_STATUS_NORMAL:
251                         break;
252                 case G_IO_STATUS_ERROR:
253                         transport->callbacks->log(transport, l, error);
254                         break;
255                 }
256                 free_line(l);
257         }
258
259         if (!ret)
260                 transport->outgoing_id = 0;
261         return ret;
262 }
263
264 gboolean transport_send_line(struct irc_transport *transport, 
265                                                          const struct irc_line *l)
266 {
267         GError *error = NULL;
268         GIOStatus status;
269
270         if (transport->incoming == NULL)
271                 return FALSE;
272
273         if (transport->outgoing_id != 0) {
274                 g_queue_push_tail(transport->pending_lines, linedup(l));
275                 return TRUE;
276         }
277
278         status = irc_send_line(transport->incoming, transport->outgoing_iconv, l, &error);
279
280         switch (status) {
281         case G_IO_STATUS_AGAIN:
282                 transport->outgoing_id = g_io_add_watch(transport->incoming, G_IO_OUT, 
283                                                                                 transport_send_queue, transport);
284                 g_queue_push_tail(transport->pending_lines, linedup(l));
285                 break;
286         case G_IO_STATUS_EOF:
287                 transport->callbacks->hangup(transport);
288                 return FALSE;
289         case G_IO_STATUS_ERROR:
290                 transport->callbacks->log(transport, l, error);
291                 return FALSE;
292         case G_IO_STATUS_NORMAL:
293                 transport->last_line_sent = time(NULL);
294                 break;
295         }
296
297         status = g_io_channel_flush(transport->incoming, &error);
298
299         switch (status) {
300         case G_IO_STATUS_EOF:
301                 g_assert_not_reached();
302         case G_IO_STATUS_NORMAL:
303                 break;
304         case G_IO_STATUS_AGAIN:
305                 transport->outgoing_id = g_io_add_watch(transport->incoming, G_IO_OUT, 
306                                                                                 transport_send_queue, transport);
307                 break;
308         case G_IO_STATUS_ERROR:
309                 transport->callbacks->log(transport, l, error);
310                 return FALSE;
311         }
312
313         return TRUE;
314 }
315
316 gboolean transport_send_args(struct irc_transport *transport, ...) 
317 {
318         struct irc_line *l;
319         gboolean ret;
320         va_list ap;
321
322         va_start(ap, transport);
323         l = virc_parse_line(NULL, ap);
324         va_end(ap);
325
326         ret = transport_send_line(transport, l);
327
328         free_line(l); 
329
330         return ret;
331 }
332
333 void transport_parse_buffer(struct irc_transport *transport)
334 {
335         handle_transport_receive(transport->incoming, 
336                           g_io_channel_get_buffer_condition(transport->incoming),
337                           transport);
338 }