1 /* gdict-client-context.c -
2 *
3 * Copyright (C) 2005 Emmanuele Bassi <ebassi@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library 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 GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * SECTION:gdict-client-context
21 * @short_description: DICT client transport
22 *
23 * #GdictClientContext is an implementation of the #GdictContext interface.
24 * It implements the Dictionary Protocol as defined by the RFC 2229 in order
25 * to connect to a dictionary server.
26 *
27 * You should rarely instantiate this object directely: use an appropriate
28 * #GdictSource instead.
29 */
30
31 #include "config.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <netinet/in.h>
39 #include <arpa/inet.h>
40 #include <netdb.h>
41 #include <fcntl.h>
42 #include <errno.h>
43 #ifdef HAVE_UNISTD_H
44 #include <unistd.h>
45 #endif
46
47 #include <glib.h>
48 #include <glib/gi18n-lib.h>
49
50 #include "gdict-context-private.h"
51 #include "gdict-context.h"
52 #include "gdict-client-context.h"
53 #include "gdict-enum-types.h"
54 #include "gdict-marshal.h"
55 #include "gdict-debug.h"
56 #include "gdict-utils.h"
57 #include "gdict-private.h"
58
59 typedef enum {
60 CMD_CLIENT,
61 CMD_SHOW_DB,
62 CMD_SHOW_STRAT,
63 CMD_SHOW_INFO, /* not implemented */
64 CMD_SHOW_SERVER, /* not implemented */
65 CMD_MATCH,
66 CMD_DEFINE,
67 CMD_STATUS, /* not implemented */
68 CMD_OPTION_MIME, /* not implemented */
69 CMD_AUTH, /* not implemented */
70 CMD_HELP, /* not implemented */
71 CMD_QUIT,
72
73 CMD_INVALID
74 } GdictCommandType;
75 #define IS_VALID_CMD(cmd) (((cmd) >= CMD_CLIENT) || ((cmd) < CMD_INVALID))
76
77 /* command strings: keep synced with the enum above! */
78 static const gchar *dict_command_strings[] = {
79 "CLIENT",
80 "SHOW DB",
81 "SHOW STRAT",
82 "SHOW INFO",
83 "SHOW SERVER",
84 "MATCH",
85 "DEFINE",
86 "STATUS",
87 "OPTION MIME",
88 "AUTH",
89 "HELP",
90 "QUIT",
91
92 NULL
93 };
94
95 /* command stata */
96 enum
97 {
98 S_START,
99
100 S_STATUS,
101 S_DATA,
102
103 S_FINISH
104 };
105
106 typedef struct
107 {
108 GdictCommandType cmd_type;
109
110 gchar *cmd_string;
111 guint state;
112
113 /* optional parameters passed to the command */
114 gchar *database;
115 gchar *strategy;
116 gchar *word;
117
118 /* buffer used to hold the reply from the server */
119 GString *buffer;
120
121 gpointer data;
122 GDestroyNotify data_destroy;
123 } GdictCommand;
124
125 /* The default string to be passed to the CLIENT command */
126 #define GDICT_DEFAULT_CLIENT "GNOME Dictionary (" VERSION ")"
127
128 /* Default server:port couple */
129 #define GDICT_DEFAULT_HOSTNAME "dict.org"
130 #define GDICT_DEFAULT_PORT 2628
131
132 /* make the hostname lookup expire every five minutes */
133 #define HOSTNAME_LOOKUP_EXPIRE 300
134
135 /* wait 30 seconds between connection and receiving data on the line */
136 #define CONNECTION_TIMEOUT_SEC 30
137
138 enum
139 {
140 PROP_0,
141
142 PROP_HOSTNAME,
143 PROP_PORT,
144 PROP_STATUS,
145 PROP_CLIENT_NAME
146 };
147
148 enum
149 {
150 CONNECTED,
151 DISCONNECTED,
152
153 LAST_SIGNAL
154 };
155
156 static guint gdict_client_context_signals[LAST_SIGNAL] = { 0 };
157
158 struct _GdictClientContextPrivate
159 {
160 #ifdef ENABLE_IPV6
161 struct sockaddr_storage sockaddr;
162 struct addrinfo *host6info;
163 #else
164 struct sockaddr_in sockaddr;
165 #endif
166 struct hostent *hostinfo;
167
168 time_t last_lookup;
169
170 gchar *hostname;
171 gint port;
172
173 GIOChannel *channel;
174 guint source_id;
175 guint timeout_id;
176
177 GdictCommand *command;
178 GQueue *commands_queue;
179
180 gchar *client_name;
181
182 GdictStatusCode status_code;
183
184 guint local_only : 1;
185 guint is_connecting : 1;
186 };
187
188 static void gdict_client_context_iface_init (GdictContextIface *iface);
189
190 G_DEFINE_TYPE_WITH_CODE (GdictClientContext,
191 gdict_client_context,
192 G_TYPE_OBJECT,
193 G_ADD_PRIVATE (GdictClientContext)
194 G_IMPLEMENT_INTERFACE (GDICT_TYPE_CONTEXT,
195 gdict_client_context_iface_init))
196
197 /* GObject methods */
198 static void gdict_client_context_set_property (GObject *object,
199 guint prop_id,
200 const GValue *value,
201 GParamSpec *pspec);
202 static void gdict_client_context_get_property (GObject *object,
203 guint prop_id,
204 GValue *value,
205 GParamSpec *pspec);
206 static void gdict_client_context_finalize (GObject *object);
207
208 /* GdictContext methods */
209 static gboolean gdict_client_context_get_databases (GdictContext *context,
210 GError **error);
211 static gboolean gdict_client_context_get_strategies (GdictContext *context,
212 GError **error);
213 static gboolean gdict_client_context_define_word (GdictContext *context,
214 const gchar *database,
215 const gchar *word,
216 GError **error);
217 static gboolean gdict_client_context_match_word (GdictContext *context,
218 const gchar *database,
219 const gchar *strategy,
220 const gchar *word,
221 GError **error);
222
223 static void gdict_client_context_clear_hostinfo (GdictClientContext *context);
224 static gboolean gdict_client_context_lookup_server (GdictClientContext *context,
225 GError **error);
226 static gboolean gdict_client_context_io_watch_cb (GIOChannel *source,
227 GIOCondition condition,
228 GdictClientContext *context);
229 static gboolean gdict_client_context_parse_line (GdictClientContext *context,
230 const gchar *buffer);
231 static void gdict_client_context_disconnect (GdictClientContext *context);
232 static void gdict_client_context_force_disconnect (GdictClientContext *context);
233 static void gdict_client_context_real_connected (GdictClientContext *context);
234 static void gdict_client_context_real_disconnected (GdictClientContext *context);
235
236 static GdictCommand *gdict_command_new (GdictCommandType cmd_type);
237 static void gdict_command_free (GdictCommand *cmd);
238
239
240
241 GQuark
gdict_client_context_error_quark(void)242 gdict_client_context_error_quark (void)
243 {
244 return g_quark_from_static_string ("gdict-client-context-error-quark");
245 }
246
247 static void
gdict_client_context_iface_init(GdictContextIface * iface)248 gdict_client_context_iface_init (GdictContextIface *iface)
249 {
250 iface->get_databases = gdict_client_context_get_databases;
251 iface->get_strategies = gdict_client_context_get_strategies;
252 iface->match_word = gdict_client_context_match_word;
253 iface->define_word = gdict_client_context_define_word;
254 }
255
256 static void
gdict_client_context_class_init(GdictClientContextClass * klass)257 gdict_client_context_class_init (GdictClientContextClass *klass)
258 {
259 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
260
261 gobject_class->set_property = gdict_client_context_set_property;
262 gobject_class->get_property = gdict_client_context_get_property;
263 gobject_class->finalize = gdict_client_context_finalize;
264
265 g_object_class_override_property (gobject_class,
266 GDICT_CONTEXT_PROP_LOCAL_ONLY,
267 "local-only");
268
269 /**
270 * GdictClientContext:client-name
271 *
272 * The name of the client using this context; it will be advertised when
273 * connecting to the dictionary server.
274 *
275 * Since: 1.0
276 */
277 g_object_class_install_property (gobject_class,
278 PROP_CLIENT_NAME,
279 g_param_spec_string ("client-name",
280 "Client Name",
281 "The name of the client of the context object",
282 NULL,
283 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
284 /**
285 * GdictClientContext:hostname
286 *
287 * The hostname of the dictionary server to connect to.
288 *
289 * Since: 1.0
290 */
291 g_object_class_install_property (gobject_class,
292 PROP_HOSTNAME,
293 g_param_spec_string ("hostname",
294 "Hostname",
295 "The hostname of the dictionary server to connect to",
296 NULL,
297 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
298 /**
299 * GdictClientContext:port
300 *
301 * The port of the dictionary server to connect to.
302 *
303 * Since: 1.0
304 */
305 g_object_class_install_property (gobject_class,
306 PROP_PORT,
307 g_param_spec_uint ("port",
308 "Port",
309 "The port of the dictionary server to connect to",
310 0,
311 65535,
312 GDICT_DEFAULT_PORT,
313 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
314 /**
315 * GdictClientContext:status
316 *
317 * The status code as returned by the dictionary server.
318 *
319 * Since: 1.0
320 */
321 g_object_class_install_property (gobject_class,
322 PROP_STATUS,
323 g_param_spec_enum ("status",
324 "Status",
325 "The status code as returned by the dictionary server",
326 GDICT_TYPE_STATUS_CODE,
327 GDICT_STATUS_INVALID,
328 G_PARAM_READABLE));
329
330 /**
331 * GdictClientContext::connected
332 * @client: the object which received the signal
333 *
334 * Emitted when a #GdictClientContext has successfully established a
335 * connection with a dictionary server.
336 *
337 * Since: 1.0
338 */
339 gdict_client_context_signals[CONNECTED] =
340 g_signal_new ("connected",
341 G_OBJECT_CLASS_TYPE (gobject_class),
342 G_SIGNAL_RUN_LAST,
343 G_STRUCT_OFFSET (GdictClientContextClass, connected),
344 NULL, NULL,
345 gdict_marshal_VOID__VOID,
346 G_TYPE_NONE, 0);
347 /**
348 * GdictClientContext::disconnected
349 * @client: the object which received the signal
350 *
351 * Emitted when a #GdictClientContext has disconnected from a dictionary
352 * server.
353 *
354 * Since: 1.0
355 */
356 gdict_client_context_signals[DISCONNECTED] =
357 g_signal_new ("disconnected",
358 G_OBJECT_CLASS_TYPE (gobject_class),
359 G_SIGNAL_RUN_LAST,
360 G_STRUCT_OFFSET (GdictClientContextClass, disconnected),
361 NULL, NULL,
362 gdict_marshal_VOID__VOID,
363 G_TYPE_NONE, 0);
364
365 klass->connected = gdict_client_context_real_connected;
366 klass->disconnected = gdict_client_context_real_disconnected;
367 }
368
369 static void
gdict_client_context_init(GdictClientContext * context)370 gdict_client_context_init (GdictClientContext *context)
371 {
372 GdictClientContextPrivate *priv;
373
374 priv = gdict_client_context_get_instance_private (context);
375 context->priv = priv;
376
377 priv->hostname = NULL;
378 priv->port = 0;
379
380 priv->hostinfo = NULL;
381 #ifdef ENABLE_IPV6
382 priv->host6info = NULL;
383 #endif
384
385 priv->last_lookup = (time_t) -1;
386
387 priv->is_connecting = FALSE;
388 priv->local_only = FALSE;
389
390 priv->status_code = GDICT_STATUS_INVALID;
391
392 priv->client_name = NULL;
393
394 priv->command = NULL;
395 priv->commands_queue = g_queue_new ();
396 }
397
398 static void
gdict_client_context_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)399 gdict_client_context_set_property (GObject *object,
400 guint prop_id,
401 const GValue *value,
402 GParamSpec *pspec)
403 {
404 GdictClientContext *self = GDICT_CLIENT_CONTEXT (object);
405 GdictClientContextPrivate *priv = gdict_client_context_get_instance_private (self);
406
407 switch (prop_id)
408 {
409 case PROP_HOSTNAME:
410 if (priv->hostname)
411 g_free (priv->hostname);
412 priv->hostname = g_strdup (g_value_get_string (value));
413 gdict_client_context_clear_hostinfo (GDICT_CLIENT_CONTEXT (object));
414 break;
415 case PROP_PORT:
416 priv->port = g_value_get_uint (value);
417 break;
418 case PROP_CLIENT_NAME:
419 if (priv->client_name)
420 g_free (priv->client_name);
421 priv->client_name = g_strdup (g_value_get_string (value));
422 break;
423 case GDICT_CONTEXT_PROP_LOCAL_ONLY:
424 priv->local_only = g_value_get_boolean (value);
425 break;
426 default:
427 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
428 break;
429 }
430 }
431
432 static void
gdict_client_context_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)433 gdict_client_context_get_property (GObject *object,
434 guint prop_id,
435 GValue *value,
436 GParamSpec *pspec)
437 {
438 GdictClientContext *self = GDICT_CLIENT_CONTEXT (object);
439 GdictClientContextPrivate *priv = gdict_client_context_get_instance_private (self);
440
441 switch (prop_id)
442 {
443 case PROP_STATUS:
444 g_value_set_enum (value, priv->status_code);
445 break;
446 case PROP_HOSTNAME:
447 g_value_set_string (value, priv->hostname);
448 break;
449 case PROP_PORT:
450 g_value_set_uint (value, priv->port);
451 break;
452 case PROP_CLIENT_NAME:
453 g_value_set_string (value, priv->client_name);
454 break;
455 case GDICT_CONTEXT_PROP_LOCAL_ONLY:
456 g_value_set_boolean (value, priv->local_only);
457 break;
458 default:
459 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460 break;
461 }
462 }
463
464 static void
gdict_client_context_finalize(GObject * object)465 gdict_client_context_finalize (GObject *object)
466 {
467 GdictClientContext *context = GDICT_CLIENT_CONTEXT (object);
468 GdictClientContextPrivate *priv = context->priv;
469
470 /* force disconnection */
471 gdict_client_context_force_disconnect (context);
472
473 if (priv->command)
474 gdict_command_free (priv->command);
475
476 if (priv->commands_queue)
477 {
478 g_queue_foreach (priv->commands_queue,
479 (GFunc) gdict_command_free,
480 NULL);
481 g_queue_free (priv->commands_queue);
482
483 priv->commands_queue = NULL;
484 }
485
486 if (priv->client_name)
487 g_free (priv->client_name);
488
489 if (priv->hostname)
490 g_free (priv->hostname);
491
492 #ifdef ENABLE_IPV6
493 if (priv->host6info)
494 freeaddrinfo (priv->host6info);
495 #endif
496
497 /* chain up parent's finalize method */
498 G_OBJECT_CLASS (gdict_client_context_parent_class)->finalize (object);
499 }
500
501 /**
502 * gdict_client_context_new:
503 * @hostname: (nullable): the hostname of a dictionary server,
504 * or %NULL for the default server
505 * @port: port to be used when connecting to the dictionary server,
506 * or -1 for the default port
507 *
508 * Creates a new #GdictClientContext object for @hostname. Use this
509 * object to connect and query the dictionary server using the Dictionary
510 * Protocol as defined by RFC 2229.
511 *
512 * Return value: (transfer full): the newly created #GdictClientContext object.
513 */
514 GdictContext *
gdict_client_context_new(const gchar * hostname,gint port)515 gdict_client_context_new (const gchar *hostname,
516 gint port)
517 {
518 return g_object_new (GDICT_TYPE_CLIENT_CONTEXT,
519 "hostname", (hostname != NULL ? hostname : GDICT_DEFAULT_HOSTNAME),
520 "port", (port != -1 ? port : GDICT_DEFAULT_PORT),
521 "client-name", GDICT_DEFAULT_CLIENT,
522 NULL);
523 }
524
525 /**
526 * gdict_client_context_set_hostname:
527 * @context: a #GdictClientContext
528 * @hostname: (nullable): the hostname of a Dictionary server, or %NULL
529 *
530 * Sets @hostname as the hostname of the dictionary server to be used.
531 * If @hostname is %NULL, the default dictionary server will be used.
532 */
533 void
gdict_client_context_set_hostname(GdictClientContext * context,const gchar * hostname)534 gdict_client_context_set_hostname (GdictClientContext *context,
535 const gchar *hostname)
536 {
537 g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
538
539 g_object_set (G_OBJECT (context),
540 "hostname", (hostname != NULL ? hostname : GDICT_DEFAULT_HOSTNAME),
541 NULL);
542 }
543
544 /**
545 * gdict_client_context_get_hostname:
546 * @context: a #GdictClientContext
547 *
548 * Gets the hostname of the dictionary server used by @context.
549 *
550 * Return value: the hostname of a dictionary server. The returned string is
551 * owned by the #GdictClientContext object and should never be modified or
552 * freed.
553 */
554 const gchar *
gdict_client_context_get_hostname(GdictClientContext * context)555 gdict_client_context_get_hostname (GdictClientContext *context)
556 {
557 gchar *hostname;
558
559 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), NULL);
560
561 g_object_get (G_OBJECT (context), "hostname", &hostname, NULL);
562
563 return hostname;
564 }
565
566 /**
567 * gdict_client_context_set_port:
568 * @context: a #GdictClientContext
569 * @port: port of the dictionary server to be used, or -1
570 *
571 * Sets the port of the dictionary server to be used when connecting.
572 *
573 * If @port is -1, the default port will be used.
574 */
575 void
gdict_client_context_set_port(GdictClientContext * context,gint port)576 gdict_client_context_set_port (GdictClientContext *context,
577 gint port)
578 {
579 g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
580
581 g_object_set (G_OBJECT (context),
582 "port", (port != -1 ? port : GDICT_DEFAULT_PORT),
583 NULL);
584 }
585
586 /**
587 * gdict_client_context_get_port:
588 * @context: a #GdictClientContext
589 *
590 * Gets the port of the dictionary server used by @context.
591 *
592 * Return value: the number of the port.
593 */
594 guint
gdict_client_context_get_port(GdictClientContext * context)595 gdict_client_context_get_port (GdictClientContext *context)
596 {
597 guint port;
598
599 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), -1);
600
601 g_object_get (G_OBJECT (context), "port", &port, NULL);
602
603 return port;
604 }
605
606 /**
607 * gdict_client_context_set_client:
608 * @context: a #GdictClientContext
609 * @client: (nullable): the client name to use, or %NULL
610 *
611 * Sets @client as the client name to be used when advertising ourselves when
612 * a connection the the dictionary server has been established.
613 * If @client is %NULL, the default client name will be used.
614 */
615 void
gdict_client_context_set_client(GdictClientContext * context,const gchar * client)616 gdict_client_context_set_client (GdictClientContext *context,
617 const gchar *client)
618 {
619 g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
620
621 g_object_set (G_OBJECT (context),
622 "client-name", (client != NULL ? client : GDICT_DEFAULT_CLIENT),
623 NULL);
624 }
625
626 /**
627 * gdict_client_context_get_client:
628 * @context: a #GdictClientContext
629 *
630 * Gets the client name used by @context. See gdict_client_context_set_client().
631 *
632 * Return value: the client name. The returned string is owned by the
633 * #GdictClientContext object and should never be modified or freed.
634 */
635 const gchar *
gdict_client_context_get_client(GdictClientContext * context)636 gdict_client_context_get_client (GdictClientContext *context)
637 {
638 gchar *client_name;
639
640 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), NULL);
641
642 g_object_get (G_OBJECT (context), "client-name", &client_name, NULL);
643
644 return client_name;
645 }
646
647 /* creates a new command to be sent to the dictionary server */
648 static GdictCommand *
gdict_command_new(GdictCommandType cmd_type)649 gdict_command_new (GdictCommandType cmd_type)
650 {
651 GdictCommand *retval;
652
653 g_assert (IS_VALID_CMD (cmd_type));
654
655 retval = g_slice_new0 (GdictCommand);
656
657 retval->cmd_type = cmd_type;
658 retval->state = S_START;
659
660 return retval;
661 }
662
663 static void
gdict_command_free(GdictCommand * cmd)664 gdict_command_free (GdictCommand *cmd)
665 {
666 if (!cmd)
667 return;
668
669 g_free (cmd->cmd_string);
670
671 switch (cmd->cmd_type)
672 {
673 case CMD_CLIENT:
674 case CMD_QUIT:
675 break;
676 case CMD_SHOW_DB:
677 case CMD_SHOW_STRAT:
678 break;
679 case CMD_MATCH:
680 g_free (cmd->database);
681 g_free (cmd->strategy);
682 g_free (cmd->word);
683 break;
684 case CMD_DEFINE:
685 g_free (cmd->database);
686 g_free (cmd->word);
687 break;
688 default:
689 break;
690 }
691
692 if (cmd->buffer)
693 g_string_free (cmd->buffer, TRUE);
694
695 if (cmd->data_destroy)
696 cmd->data_destroy (cmd->data);
697
698 g_slice_free (GdictCommand, cmd);
699 }
700
701 /* push @command into the head of the commands queue; the command queue is
702 * a FIFO-like pipe: commands go into the head and are retrieved from the
703 * tail.
704 */
705 static gboolean
gdict_client_context_push_command(GdictClientContext * context,GdictCommand * command)706 gdict_client_context_push_command (GdictClientContext *context,
707 GdictCommand *command)
708 {
709 GdictClientContextPrivate *priv;
710
711 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
712 g_assert (command != NULL);
713
714 priv = context->priv;
715
716 /* avoid pushing a command twice */
717 if (g_queue_find (priv->commands_queue, command))
718 {
719 g_warning ("gdict_client_context_push_command() called on a command already in queue\n");
720 return FALSE;
721 }
722
723 GDICT_NOTE (DICT, "Pushing command ('%s') into the queue...",
724 dict_command_strings[command->cmd_type]);
725
726 g_queue_push_head (priv->commands_queue, command);
727
728 return TRUE;
729 }
730
731 static GdictCommand *
gdict_client_context_pop_command(GdictClientContext * context)732 gdict_client_context_pop_command (GdictClientContext *context)
733 {
734 GdictClientContextPrivate *priv;
735 GdictCommand *retval;
736
737 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
738
739 priv = context->priv;
740
741 retval = (GdictCommand *) g_queue_pop_tail (priv->commands_queue);
742 if (!retval)
743 return NULL;
744
745 GDICT_NOTE (DICT, "Getting command ('%s') from the queue...",
746 dict_command_strings[retval->cmd_type]);
747
748 return retval;
749 }
750
751 /* send @command on the wire */
752 static gboolean
gdict_client_context_send_command(GdictClientContext * context,GdictCommand * command,GError ** error)753 gdict_client_context_send_command (GdictClientContext *context,
754 GdictCommand *command,
755 GError **error)
756 {
757 GdictClientContextPrivate *priv;
758 GError *write_error;
759 gsize written_bytes;
760 GIOStatus res;
761
762 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
763 g_assert (command != NULL && command->cmd_string != NULL);
764
765 priv = context->priv;
766
767 if (!priv->channel)
768 {
769 GDICT_NOTE (DICT, "No connection established");
770
771 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
772 GDICT_CLIENT_CONTEXT_ERROR_NO_CONNECTION,
773 _("No connection to the dictionary server at “%s:%d”"),
774 priv->hostname,
775 priv->port);
776
777 return FALSE;
778 }
779
780 write_error = NULL;
781 res = g_io_channel_write_chars (priv->channel,
782 command->cmd_string,
783 -1,
784 &written_bytes,
785 &write_error);
786 if (res != G_IO_STATUS_NORMAL)
787 {
788 g_propagate_error (error, write_error);
789
790 return FALSE;
791 }
792
793 /* force flushing of the write buffer */
794 g_io_channel_flush (priv->channel, NULL);
795
796 GDICT_NOTE (DICT, "Wrote %"G_GSIZE_FORMAT" bytes to the channel", written_bytes);
797
798 return TRUE;
799 }
800
801 /* gdict_client_context_run_command: runs @command inside @context; this
802 * function builds the command string and then passes it to the dictionary
803 * server.
804 */
805 static gboolean
gdict_client_context_run_command(GdictClientContext * context,GdictCommand * command,GError ** error)806 gdict_client_context_run_command (GdictClientContext *context,
807 GdictCommand *command,
808 GError **error)
809 {
810 GdictClientContextPrivate *priv;
811 gchar *payload;
812 GError *send_error;
813 gboolean res;
814
815 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
816 g_assert (command != NULL);
817 g_assert (IS_VALID_CMD (command->cmd_type));
818
819 GDICT_NOTE (DICT, "GdictCommand command =\n"
820 "{\n"
821 " .cmd_type = '%02d' ('%s');\n"
822 " .database = '%s';\n"
823 " .strategy = '%s';\n"
824 " .word = '%s';\n"
825 "}\n",
826 command->cmd_type, dict_command_strings[command->cmd_type],
827 command->database ? command->database : "<none>",
828 command->strategy ? command->strategy : "<none>",
829 command->word ? command->word : "<none>");
830
831 priv = context->priv;
832
833 g_assert (priv->command == NULL);
834
835 priv->command = command;
836
837 /* build the command string to be sent to the server */
838 switch (command->cmd_type)
839 {
840 case CMD_CLIENT:
841 payload = g_shell_quote (priv->client_name);
842 command->cmd_string = g_strdup_printf ("%s %s\r\n",
843 dict_command_strings[CMD_CLIENT],
844 payload);
845 g_free (payload);
846 break;
847 case CMD_QUIT:
848 command->cmd_string = g_strdup_printf ("%s\r\n",
849 dict_command_strings[CMD_QUIT]);
850 break;
851 case CMD_SHOW_DB:
852 command->cmd_string = g_strdup_printf ("%s\r\n",
853 dict_command_strings[CMD_SHOW_DB]);
854 break;
855 case CMD_SHOW_STRAT:
856 command->cmd_string = g_strdup_printf ("%s\r\n",
857 dict_command_strings[CMD_SHOW_STRAT]);
858 break;
859 case CMD_MATCH:
860 g_assert (command->word);
861 payload = g_shell_quote (command->word);
862 command->cmd_string = g_strdup_printf ("%s %s %s %s\r\n",
863 dict_command_strings[CMD_MATCH],
864 (command->database != NULL ? command->database : "!"),
865 (command->strategy != NULL ? command->strategy : "*"),
866 payload);
867 g_free (payload);
868 break;
869 case CMD_DEFINE:
870 g_assert (command->word);
871 payload = g_shell_quote (command->word);
872 command->cmd_string = g_strdup_printf ("%s %s %s\r\n",
873 dict_command_strings[CMD_DEFINE],
874 (command->database != NULL ? command->database : "!"),
875 payload);
876 g_free (payload);
877 break;
878 default:
879 g_assert_not_reached ();
880 break;
881 }
882
883 g_assert (command->cmd_string);
884
885 GDICT_NOTE (DICT, "Sending command ('%s') to the server",
886 dict_command_strings[command->cmd_type]);
887
888 send_error = NULL;
889 res = gdict_client_context_send_command (context, command, &send_error);
890 if (!res)
891 {
892 g_propagate_error (error, send_error);
893
894 return FALSE;
895 }
896
897 return TRUE;
898 }
899
900 /* we use this signal to advertise ourselves to the dictionary server */
901 static void
gdict_client_context_real_connected(GdictClientContext * context)902 gdict_client_context_real_connected (GdictClientContext *context)
903 {
904 GdictCommand *cmd;
905
906 cmd = gdict_command_new (CMD_CLIENT);
907 cmd->state = S_FINISH;
908
909 /* the CLIENT command should be the first one in our queue, so we place
910 * it above all other commands the user might have issued between the
911 * first and the emission of the "connected" signal, by calling it
912 * directely.
913 */
914 gdict_client_context_run_command (context, cmd, NULL);
915 }
916
917 static void
clear_command_queue(GdictClientContext * context)918 clear_command_queue (GdictClientContext *context)
919 {
920 GdictClientContextPrivate *priv = context->priv;
921
922 if (priv->commands_queue)
923 {
924 g_queue_foreach (priv->commands_queue,
925 (GFunc) gdict_command_free,
926 NULL);
927
928 g_queue_free (priv->commands_queue);
929 }
930
931 /* renew */
932 priv->commands_queue = g_queue_new ();
933 }
934
935 /* force a disconnection from the server */
936 static void
gdict_client_context_force_disconnect(GdictClientContext * context)937 gdict_client_context_force_disconnect (GdictClientContext *context)
938 {
939 GdictClientContextPrivate *priv = context->priv;
940
941 if (priv->timeout_id)
942 {
943 g_source_remove (priv->timeout_id);
944 priv->timeout_id = 0;
945 }
946
947 if (priv->source_id)
948 {
949 g_source_remove (priv->source_id);
950 priv->source_id = 0;
951 }
952
953 if (priv->channel)
954 {
955 g_io_channel_shutdown (priv->channel, TRUE, NULL);
956 g_io_channel_unref (priv->channel);
957
958 priv->channel = NULL;
959 }
960
961 if (priv->command)
962 {
963 gdict_command_free (priv->command);
964 priv->command = NULL;
965 }
966
967 clear_command_queue (context);
968 }
969
970 static void
gdict_client_context_real_disconnected(GdictClientContext * context)971 gdict_client_context_real_disconnected (GdictClientContext *context)
972 {
973 gdict_client_context_force_disconnect (context);
974 }
975
976 /* clear the lookup data */
977 static void
gdict_client_context_clear_hostinfo(GdictClientContext * context)978 gdict_client_context_clear_hostinfo (GdictClientContext *context)
979 {
980 GdictClientContextPrivate *priv;
981
982 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
983
984 priv = context->priv;
985
986 #ifdef ENABLE_IPV6
987 if (!priv->host6info)
988 return;
989 #endif
990
991 if (!priv->hostinfo)
992 return;
993
994 #ifdef ENABLE_IPV6
995 freeaddrinfo (priv->host6info);
996 #endif
997 priv->hostinfo = NULL;
998 }
999
1000 /* gdict_client_context_lookup_server: perform an hostname lookup in order to
1001 * connect to the dictionary server
1002 */
1003 static gboolean
gdict_client_context_lookup_server(GdictClientContext * context,GError ** error)1004 gdict_client_context_lookup_server (GdictClientContext *context,
1005 GError **error)
1006 {
1007 GdictClientContextPrivate *priv;
1008 gboolean is_expired = FALSE;
1009 time_t now;
1010
1011 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
1012
1013 priv = context->priv;
1014
1015 /* we need the hostname, at this point */
1016 g_assert (priv->hostname != NULL);
1017
1018 time (&now);
1019 if (now >= (priv->last_lookup + HOSTNAME_LOOKUP_EXPIRE))
1020 is_expired = TRUE;
1021
1022 /* we already have resolved the hostname */
1023 #ifdef ENABLE_IPV6
1024 if (priv->host6info && !is_expired)
1025 return TRUE;
1026 #endif
1027
1028 if (priv->hostinfo && !is_expired)
1029 return TRUE;
1030
1031 /* clear any previously acquired lookup data */
1032 gdict_client_context_clear_hostinfo (context);
1033
1034 GDICT_NOTE (DICT, "Looking up hostname '%s'", priv->hostname);
1035
1036 #ifdef ENABLE_IPV6
1037 if (_gdict_has_ipv6 ())
1038 {
1039 struct addrinfo hints, *res;
1040
1041 GDICT_NOTE (DICT, "Hostname '%s' look-up (using IPv6)", priv->hostname);
1042
1043 memset (&hints, 0, sizeof (hints));
1044 hints.ai_socktype = SOCK_STREAM;
1045
1046 if (getaddrinfo (priv->hostname, NULL, &hints, &(priv->host6info)) == 0)
1047 {
1048 for (res = priv->host6info; res; res = res->ai_next)
1049 if (res->ai_family == AF_INET6 || res->ai_family == AF_INET)
1050 break;
1051
1052 if (!res)
1053 {
1054 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1055 GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
1056 _("Lookup failed for hostname “%s”: no suitable resources found"),
1057 priv->hostname);
1058
1059 return FALSE;
1060 }
1061 else
1062 {
1063 if (res->ai_family == AF_INET6)
1064 memcpy (&((struct sockaddr_in6 *) &priv->sockaddr)->sin6_addr,
1065 &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr,
1066 sizeof (struct in6_addr));
1067
1068 if (res->ai_family == AF_INET)
1069 memcpy (&((struct sockaddr_in *) &priv->sockaddr)->sin_addr,
1070 &((struct sockaddr_in *) res->ai_addr)->sin_addr,
1071 sizeof (struct in_addr));
1072
1073 priv->sockaddr.ss_family = res->ai_family;
1074
1075 GDICT_NOTE (DICT, "Hostname '%s' found (using IPv6)",
1076 priv->hostname);
1077
1078 priv->last_lookup = time (NULL);
1079
1080 return TRUE;
1081 }
1082 }
1083 else
1084 {
1085 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1086 GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
1087 _("Lookup failed for host “%s”: %s"),
1088 priv->hostname,
1089 g_strerror (errno));
1090
1091 return FALSE;
1092 }
1093 }
1094 else
1095 {
1096 #endif /* ENABLE_IPV6 */
1097 /* if we don't support IPv6, fallback to usual IPv4 lookup */
1098
1099 GDICT_NOTE (DICT, "Hostname '%s' look-up (using IPv4)", priv->hostname);
1100
1101 ((struct sockaddr_in *) &priv->sockaddr)->sin_family = AF_INET;
1102
1103 priv->hostinfo = gethostbyname (priv->hostname);
1104 if (priv->hostinfo)
1105 {
1106 memcpy (&((struct sockaddr_in *) &(priv->sockaddr))->sin_addr,
1107 priv->hostinfo->h_addr,
1108 priv->hostinfo->h_length);
1109
1110 GDICT_NOTE (DICT, "Hostname '%s' found (using IPv4)",
1111 priv->hostname);
1112
1113 priv->last_lookup = time (NULL);
1114
1115 return TRUE;
1116 }
1117 else
1118 {
1119 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1120 GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
1121 _("Lookup failed for host “%s”: host not found"),
1122 priv->hostname);
1123
1124 return FALSE;
1125 }
1126 #ifdef ENABLE_IPV6
1127 }
1128 #endif
1129
1130 g_assert_not_reached ();
1131
1132 return FALSE;
1133 }
1134
1135 /* gdict_client_context_parse_line: parses a line from the dictionary server
1136 * this is the core of the RFC2229 protocol implementation, here's where
1137 * the magic happens.
1138 */
1139 static gboolean
gdict_client_context_parse_line(GdictClientContext * context,const gchar * buffer)1140 gdict_client_context_parse_line (GdictClientContext *context,
1141 const gchar *buffer)
1142 {
1143 GdictClientContextPrivate *priv;
1144 GError *server_error = NULL;
1145
1146 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
1147 g_assert (buffer != NULL);
1148
1149 priv = context->priv;
1150
1151 GDICT_NOTE (DICT, "parse buffer: '%s'", buffer);
1152
1153 /* connection is a special case: we don't have a command, so we just
1154 * make sure that the server replied with the correct code. WARNING:
1155 * the server might be shutting down or not available, so we must
1156 * take into account those responses too!
1157 */
1158 if (!priv->command)
1159 {
1160 if (priv->status_code == GDICT_STATUS_CONNECT)
1161 {
1162 /* the server accepts our connection */
1163 g_signal_emit (context, gdict_client_context_signals[CONNECTED], 0);
1164
1165 return TRUE;
1166 }
1167 else if ((priv->status_code == GDICT_STATUS_SERVER_DOWN) ||
1168 (priv->status_code == GDICT_STATUS_SHUTDOWN))
1169 {
1170 /* the server is shutting down or is not available */
1171 g_set_error (&server_error, GDICT_CLIENT_CONTEXT_ERROR,
1172 GDICT_CLIENT_CONTEXT_ERROR_SERVER_DOWN,
1173 _("Unable to connect to the dictionary server "
1174 "at “%s:%d”. The server replied with "
1175 "code %d (server down)"),
1176 priv->hostname,
1177 priv->port,
1178 priv->status_code);
1179
1180 g_signal_emit_by_name (context, "error", server_error);
1181
1182 g_error_free (server_error);
1183
1184 return TRUE;
1185 }
1186 else
1187 {
1188 GError *parse_error = NULL;
1189
1190 g_set_error (&parse_error, GDICT_CONTEXT_ERROR,
1191 GDICT_CONTEXT_ERROR_PARSE,
1192 _("Unable to parse the dictionary server reply\n: “%s”"),
1193 buffer);
1194
1195 g_signal_emit_by_name (context, "error", parse_error);
1196
1197 g_error_free (parse_error);
1198
1199 return FALSE;
1200 }
1201 }
1202
1203 /* disconnection is another special case: the server replies with code
1204 * 221, and closes the connection; we emit the "disconnected" signal
1205 * and close the connection on our side.
1206 */
1207 if (priv->status_code == GDICT_STATUS_QUIT)
1208 {
1209 g_signal_emit (context, gdict_client_context_signals[DISCONNECTED], 0);
1210
1211 return TRUE;
1212 }
1213
1214 /* here we catch all the errors codes that the server might give us */
1215 server_error = NULL;
1216 switch (priv->status_code)
1217 {
1218 case GDICT_STATUS_NO_MATCH:
1219 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1220 GDICT_CONTEXT_ERROR_NO_MATCH,
1221 _("No definitions found for “%s”"),
1222 priv->command->word);
1223
1224 GDICT_NOTE (DICT, "No match: %s", server_error->message);
1225
1226 g_signal_emit_by_name (context, "error", server_error);
1227
1228 g_error_free (server_error);
1229 server_error = NULL;
1230
1231 priv->command->state = S_FINISH;
1232 break;
1233 case GDICT_STATUS_BAD_DATABASE:
1234 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1235 GDICT_CONTEXT_ERROR_INVALID_DATABASE,
1236 _("Invalid database “%s”"),
1237 priv->command->database);
1238
1239 GDICT_NOTE (DICT, "Bad DB: %s", server_error->message);
1240
1241 g_signal_emit_by_name (context, "error", server_error);
1242
1243 g_error_free (server_error);
1244 server_error = NULL;
1245
1246 priv->command->state = S_FINISH;
1247 break;
1248 case GDICT_STATUS_BAD_STRATEGY:
1249 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1250 GDICT_CONTEXT_ERROR_INVALID_STRATEGY,
1251 _("Invalid strategy “%s”"),
1252 priv->command->strategy);
1253
1254 GDICT_NOTE (DICT, "Bad strategy: %s", server_error->message);
1255
1256 g_signal_emit_by_name (context, "error", server_error);
1257
1258 g_error_free (server_error);
1259 server_error = NULL;
1260
1261 priv->command->state = S_FINISH;
1262 break;
1263 case GDICT_STATUS_BAD_COMMAND:
1264 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1265 GDICT_CONTEXT_ERROR_INVALID_COMMAND,
1266 _("Bad command “%s”"),
1267 dict_command_strings[priv->command->cmd_type]);
1268
1269 GDICT_NOTE (DICT, "Bad command: %s", server_error->message);
1270
1271 g_signal_emit_by_name (context, "error", server_error);
1272
1273 g_error_free (server_error);
1274 server_error = NULL;
1275
1276 priv->command->state = S_FINISH;
1277 break;
1278 case GDICT_STATUS_BAD_PARAMETERS:
1279 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1280 GDICT_CONTEXT_ERROR_INVALID_COMMAND,
1281 _("Bad parameters for command “%s”"),
1282 dict_command_strings[priv->command->cmd_type]);
1283
1284 GDICT_NOTE (DICT, "Bad params: %s", server_error->message);
1285
1286 g_signal_emit_by_name (context, "error", server_error);
1287
1288 g_error_free (server_error);
1289 server_error = NULL;
1290
1291 priv->command->state = S_FINISH;
1292 break;
1293 case GDICT_STATUS_NO_DATABASES_PRESENT:
1294 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1295 GDICT_CONTEXT_ERROR_NO_DATABASES,
1296 _("No databases found on dictionary server at “%s”"),
1297 priv->hostname);
1298
1299 GDICT_NOTE (DICT, "No DB: %s", server_error->message);
1300
1301 g_signal_emit_by_name (context, "error", server_error);
1302
1303 g_error_free (server_error);
1304 server_error = NULL;
1305
1306 priv->command->state = S_FINISH;
1307 break;
1308 case GDICT_STATUS_NO_STRATEGIES_PRESENT:
1309 g_set_error (&server_error, GDICT_CONTEXT_ERROR,
1310 GDICT_CONTEXT_ERROR_NO_STRATEGIES,
1311 _("No strategies found on dictionary server at “%s”"),
1312 priv->hostname);
1313
1314 GDICT_NOTE (DICT, "No strategies: %s", server_error->message);
1315
1316 g_signal_emit_by_name (context, "error", server_error);
1317
1318 g_error_free (server_error);
1319 server_error = NULL;
1320
1321 priv->command->state = S_FINISH;
1322 break;
1323 default:
1324 GDICT_NOTE (DICT, "non-error code: %d", priv->status_code);
1325 break;
1326 }
1327
1328 /* server replied with 'ok' or the command has reached its FINISH state,
1329 * so now we are clear for destroying the current command and check if
1330 * there are other commands on the queue, and run them.
1331 */
1332 if ((priv->status_code == GDICT_STATUS_OK) ||
1333 (priv->command->state == S_FINISH))
1334 {
1335 GdictCommand *new_command;
1336 GError *run_error;
1337 GdictCommandType last_cmd;
1338
1339 last_cmd = priv->command->cmd_type;
1340
1341 gdict_command_free (priv->command);
1342 priv->command = NULL;
1343
1344 /* notify the end of a command - ignore CLIENT and QUIT commands, as
1345 * we issue them ourselves
1346 */
1347 if ((last_cmd != CMD_CLIENT) && (last_cmd != CMD_QUIT))
1348 {
1349 if (last_cmd == CMD_SHOW_DB)
1350 g_signal_emit_by_name (context, "database-lookup-end");
1351 else if (last_cmd == CMD_DEFINE)
1352 g_signal_emit_by_name (context, "definition-lookup-end");
1353 else
1354 g_signal_emit_by_name (context, "lookup-end");
1355 }
1356
1357 /* pop the next command from the queue */
1358 new_command = gdict_client_context_pop_command (context);
1359 if (!new_command)
1360 {
1361 /* if the queue is empty, quit */
1362 gdict_client_context_disconnect (context);
1363 new_command = gdict_client_context_pop_command (context);
1364 }
1365
1366 run_error = NULL;
1367 gdict_client_context_run_command (context, new_command, &run_error);
1368 if (run_error)
1369 {
1370 g_signal_emit_by_name (context, "error", run_error);
1371
1372 g_error_free (run_error);
1373 }
1374
1375 return TRUE;
1376 }
1377
1378 GDICT_NOTE (DICT, "check command %d ('%s')[state:%d]",
1379 priv->command->cmd_type,
1380 dict_command_strings[priv->command->cmd_type],
1381 priv->command->state);
1382
1383 /* check command type */
1384 switch (priv->command->cmd_type)
1385 {
1386 case CMD_CLIENT:
1387 case CMD_QUIT:
1388 break;
1389 case CMD_SHOW_DB:
1390 if (priv->status_code == GDICT_STATUS_N_DATABASES_PRESENT)
1391 {
1392 gchar *p;
1393
1394 priv->command->state = S_DATA;
1395
1396 p = g_utf8_strchr (buffer, -1, ' ');
1397 if (p)
1398 p = g_utf8_next_char (p);
1399
1400 GDICT_NOTE (DICT, "server replied: %d databases found", atoi (p));
1401 }
1402 else if (0 == strcmp (buffer, "."))
1403 priv->command->state = S_FINISH;
1404 else
1405 {
1406 GdictDatabase *db;
1407 gchar *name, *full, *p;
1408
1409 g_assert (priv->command->state == S_DATA);
1410
1411 /* first token: database name;
1412 * second token: database description;
1413 */
1414 name = (gchar *) buffer;
1415 if (!name)
1416 break;
1417
1418 p = g_utf8_strchr (name, -1, ' ');
1419 if (p)
1420 *p = '\0';
1421
1422 full = g_utf8_next_char (p);
1423
1424 if (full[0] == '\"')
1425 full = g_utf8_next_char (full);
1426
1427 p = g_utf8_strchr (full, -1, '\"');
1428 if (p)
1429 *p = '\0';
1430
1431 db = _gdict_database_new (name);
1432 db->full_name = g_strdup (full);
1433
1434 g_signal_emit_by_name (context, "database-found", db);
1435
1436 gdict_database_unref (db);
1437 }
1438 break;
1439 case CMD_SHOW_STRAT:
1440 if (priv->status_code == GDICT_STATUS_N_STRATEGIES_PRESENT)
1441 {
1442 gchar *p;
1443
1444 priv->command->state = S_DATA;
1445
1446 p = g_utf8_strchr (buffer, -1, ' ');
1447 if (p)
1448 p = g_utf8_next_char (p);
1449
1450 GDICT_NOTE (DICT, "server replied: %d strategies found", atoi (p));
1451 }
1452 else if (0 == strcmp (buffer, "."))
1453 priv->command->state = S_FINISH;
1454 else
1455 {
1456 GdictStrategy *strat;
1457 gchar *name, *desc, *p;
1458
1459 g_assert (priv->command->state == S_DATA);
1460
1461 name = (gchar *) buffer;
1462 if (!name)
1463 break;
1464
1465 p = g_utf8_strchr (name, -1, ' ');
1466 if (p)
1467 *p = '\0';
1468
1469 desc = g_utf8_next_char (p);
1470
1471 if (desc[0] == '\"')
1472 desc = g_utf8_next_char (desc);
1473
1474 p = g_utf8_strchr (desc, -1, '\"');
1475 if (p)
1476 *p = '\0';
1477
1478 strat = _gdict_strategy_new (name);
1479 strat->description = g_strdup (desc);
1480
1481 g_signal_emit_by_name (context, "strategy-found", strat);
1482
1483 gdict_strategy_unref (strat);
1484 }
1485 break;
1486 case CMD_DEFINE:
1487 if (priv->status_code == GDICT_STATUS_N_DEFINITIONS_RETRIEVED)
1488 {
1489 GdictDefinition *def;
1490 gchar *p;
1491
1492 priv->command->state = S_STATUS;
1493
1494 p = g_utf8_strchr (buffer, -1, ' ');
1495 if (p)
1496 p = g_utf8_next_char (p);
1497
1498 GDICT_NOTE (DICT, "server replied: %d definitions found", atoi (p));
1499
1500 def = _gdict_definition_new (atoi (p));
1501
1502 priv->command->data = def;
1503 priv->command->data_destroy = (GDestroyNotify) gdict_definition_unref;
1504 }
1505 else if (priv->status_code == GDICT_STATUS_WORD_DB_NAME)
1506 {
1507 GdictDefinition *def;
1508 gchar *word, *db_name, *db_full, *p;
1509
1510 word = (gchar *) buffer;
1511
1512 /* skip the status code */
1513 word = g_utf8_strchr (word, -1, ' ');
1514 word = g_utf8_next_char (word);
1515
1516 if (word[0] == '\"')
1517 word = g_utf8_next_char (word);
1518
1519 p = g_utf8_strchr (word, -1, '\"');
1520 if (p)
1521 *p = '\0';
1522
1523 p = g_utf8_next_char (p);
1524
1525 /* the database name is not protected by "" */
1526 db_name = g_utf8_next_char (p);
1527 if (!db_name)
1528 break;
1529
1530 p = g_utf8_strchr (db_name, -1, ' ');
1531 if (p)
1532 *p = '\0';
1533
1534 p = g_utf8_next_char (p);
1535
1536 db_full = g_utf8_next_char (p);
1537 if (!db_full)
1538 break;
1539
1540 if (db_full[0] == '\"')
1541 db_full = g_utf8_next_char (db_full);
1542
1543 p = g_utf8_strchr (db_full, -1, '\"');
1544 if (p)
1545 *p = '\0';
1546
1547 def = (GdictDefinition *) priv->command->data;
1548
1549 GDICT_NOTE (DICT, "{ word = '%s', db_name = '%s', db_full = '%s' }",
1550 word,
1551 db_name,
1552 db_full);
1553
1554 def->word = g_strdup (word);
1555 def->database_name = g_strdup (db_name);
1556 def->database_full = g_strdup (db_full);
1557 def->definition = NULL;
1558
1559 priv->command->state = S_DATA;
1560 }
1561 else if (strcmp (buffer, ".") == 0)
1562 {
1563 GdictDefinition *def;
1564 gint num;
1565
1566 g_assert (priv->command->state == S_DATA);
1567
1568 def = (GdictDefinition *) priv->command->data;
1569 if (!def)
1570 break;
1571
1572 def->definition = g_string_free (priv->command->buffer, FALSE);
1573
1574 /* store the numer of definitions */
1575 num = def->total;
1576
1577 g_signal_emit_by_name (context, "definition-found", def);
1578
1579 gdict_definition_unref (def);
1580
1581 priv->command->buffer = NULL;
1582 priv->command->data = _gdict_definition_new (num);
1583
1584 priv->command->state = S_STATUS;
1585 }
1586 else
1587 {
1588 g_assert (priv->command->state == S_DATA);
1589
1590 if (!priv->command->buffer)
1591 priv->command->buffer = g_string_new (NULL);
1592
1593 GDICT_NOTE (DICT, "appending to buffer:\n %s", buffer);
1594
1595 /* TODO - collapse '..' to '.' */
1596 g_string_append_printf (priv->command->buffer, "%s\n", buffer);
1597 }
1598 break;
1599 case CMD_MATCH:
1600 if (priv->status_code == GDICT_STATUS_N_MATCHES_FOUND)
1601 {
1602 gchar *p;
1603
1604 priv->command->state = S_DATA;
1605
1606 p = g_utf8_strchr (buffer, -1, ' ');
1607 if (p)
1608 p = g_utf8_next_char (p);
1609
1610 GDICT_NOTE (DICT, "server replied: %d matches found", atoi (p));
1611 }
1612 else if (0 == strcmp (buffer, "."))
1613 priv->command->state = S_FINISH;
1614 else
1615 {
1616 GdictMatch *match;
1617 gchar *word, *db_name, *p;
1618
1619 g_assert (priv->command->state == S_DATA);
1620
1621 db_name = (gchar *) buffer;
1622 if (!db_name)
1623 break;
1624
1625 p = g_utf8_strchr (db_name, -1, ' ');
1626 if (p)
1627 *p = '\0';
1628
1629 word = g_utf8_next_char (p);
1630
1631 if (word[0] == '\"')
1632 word = g_utf8_next_char (word);
1633
1634 p = g_utf8_strchr (word, -1, '\"');
1635 if (p)
1636 *p = '\0';
1637
1638 match = _gdict_match_new (word);
1639 match->database = g_strdup (db_name);
1640
1641 g_signal_emit_by_name (context, "match-found", match);
1642
1643 gdict_match_unref (match);
1644 }
1645 break;
1646 default:
1647 g_assert_not_reached ();
1648 break;
1649 }
1650
1651 return TRUE;
1652 }
1653
1654 /* retrieve the status code from the server response line */
1655 static gint
get_status_code(const gchar * line,gint old_status)1656 get_status_code (const gchar *line,
1657 gint old_status)
1658 {
1659 gchar *status;
1660 gint possible_status, retval;
1661
1662 if (strlen (line) < 3)
1663 return 0;
1664
1665 if (!g_unichar_isdigit (line[0]) ||
1666 !g_unichar_isdigit (line[1]) ||
1667 !g_unichar_isdigit (line[2]))
1668 return 0;
1669
1670 if (!g_unichar_isspace (line[3]))
1671 return 0;
1672
1673 status = g_strndup (line, 3);
1674 possible_status = atoi (status);
1675 g_free (status);
1676
1677 /* status whitelisting: sometimes, a database *cough* moby-thes *cough*
1678 * might return a number as first word; we do a small check here for
1679 * invalid status codes based on the previously set status; we don't check
1680 * the whole line, as we need only to be sure that the status code is
1681 * consistent with what we expect.
1682 */
1683 switch (old_status)
1684 {
1685 case GDICT_STATUS_WORD_DB_NAME:
1686 case GDICT_STATUS_N_MATCHES_FOUND:
1687 if (possible_status == GDICT_STATUS_OK)
1688 retval = possible_status;
1689 else
1690 retval = 0;
1691 break;
1692 case GDICT_STATUS_N_DEFINITIONS_RETRIEVED:
1693 if (possible_status == GDICT_STATUS_WORD_DB_NAME)
1694 retval = possible_status;
1695 else
1696 retval = 0;
1697 break;
1698 default:
1699 retval = possible_status;
1700 break;
1701 }
1702
1703 return retval;
1704 }
1705
1706 static gboolean
gdict_client_context_io_watch_cb(GIOChannel * channel,GIOCondition condition,GdictClientContext * context)1707 gdict_client_context_io_watch_cb (GIOChannel *channel,
1708 GIOCondition condition,
1709 GdictClientContext *context)
1710 {
1711 GdictClientContextPrivate *priv;
1712
1713 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
1714 priv = context->priv;
1715
1716 /* since this is an asynchronous channel, we might end up here
1717 * even though the channel has been shut down.
1718 */
1719 if (!priv->channel)
1720 {
1721 g_warning ("No channel available\n");
1722
1723 return FALSE;
1724 }
1725
1726 if (priv->is_connecting)
1727 {
1728 priv->is_connecting = FALSE;
1729
1730 if (priv->timeout_id)
1731 {
1732 g_source_remove (priv->timeout_id);
1733 priv->timeout_id = 0;
1734 }
1735 }
1736
1737 if (condition & G_IO_ERR)
1738 {
1739 GError *err = NULL;
1740
1741 g_set_error (&err, GDICT_CLIENT_CONTEXT_ERROR,
1742 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1743 _("Connection failed to the dictionary server at %s:%d"),
1744 priv->hostname,
1745 priv->port);
1746
1747 g_signal_emit_by_name (context, "error", err);
1748
1749 g_error_free (err);
1750
1751 return FALSE;
1752 }
1753
1754 while (1)
1755 {
1756 GIOStatus res;
1757 guint status_code;
1758 GError *read_err;
1759 gsize len, term;
1760 gchar *line;
1761 gboolean parse_res;
1762
1763 /* we might sever the connection while still inside the read loop,
1764 * so we must check the state of the channel before actually doing
1765 * the line reading, otherwise we'll end up with death, destruction
1766 * and chaos on all earth. oh, and an assertion failed inside
1767 * g_io_channel_read_line().
1768 */
1769 if (!priv->channel)
1770 break;
1771
1772 read_err = NULL;
1773 res = g_io_channel_read_line (priv->channel, &line, &len, &term, &read_err);
1774 if (res == G_IO_STATUS_ERROR)
1775 {
1776 if (read_err)
1777 {
1778 GError *err = NULL;
1779
1780 g_set_error (&err, GDICT_CLIENT_CONTEXT_ERROR,
1781 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1782 _("Error while reading reply from server:\n%s"),
1783 read_err->message);
1784
1785 g_signal_emit_by_name (context, "error", err);
1786
1787 g_error_free (err);
1788 g_error_free (read_err);
1789 }
1790
1791 gdict_client_context_force_disconnect (context);
1792
1793 return FALSE;
1794 }
1795
1796 if (len == 0)
1797 break;
1798
1799 /* truncate the line terminator before parsing */
1800 line[term] = '\0';
1801
1802 status_code = get_status_code (line, priv->status_code);
1803 if ((status_code == 0) || (GDICT_IS_VALID_STATUS_CODE (status_code)))
1804 {
1805 priv->status_code = status_code;
1806
1807 GDICT_NOTE (DICT, "new status = '%d'", priv->status_code);
1808 }
1809 else
1810 priv->status_code = GDICT_STATUS_INVALID;
1811
1812 /* notify changes only for valid status codes */
1813 if (priv->status_code != GDICT_STATUS_INVALID)
1814 g_object_notify (G_OBJECT (context), "status");
1815
1816 parse_res = gdict_client_context_parse_line (context, line);
1817 if (!parse_res)
1818 {
1819 g_free (line);
1820
1821 g_warning ("Parsing failed");
1822
1823 gdict_client_context_force_disconnect (context);
1824
1825 return FALSE;
1826 }
1827
1828 g_free (line);
1829 }
1830
1831 return TRUE;
1832 }
1833
1834 static gboolean
check_for_connection(gpointer data)1835 check_for_connection (gpointer data)
1836 {
1837 GdictClientContext *context = data;
1838
1839 #if 0
1840 g_debug (G_STRLOC ": checking for connection (is connecting:%s)",
1841 context->priv->is_connecting ? "true" : "false");
1842 #endif
1843
1844 if (context == NULL)
1845 return FALSE;
1846
1847 if (context->priv->is_connecting)
1848 {
1849 GError *err = NULL;
1850
1851 GDICT_NOTE (DICT, "Forcing a disconnection due to timeout");
1852
1853 g_set_error (&err, GDICT_CLIENT_CONTEXT_ERROR,
1854 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1855 _("Connection timeout for the dictionary server at “%s:%d”"),
1856 context->priv->hostname,
1857 context->priv->port);
1858
1859 g_signal_emit_by_name (context, "error", err);
1860
1861 g_error_free (err);
1862
1863 gdict_client_context_force_disconnect (context);
1864 }
1865
1866 /* this is a one-off operation */
1867 return FALSE;
1868 }
1869
1870 static gboolean
gdict_client_context_connect(GdictClientContext * context,GError ** error)1871 gdict_client_context_connect (GdictClientContext *context,
1872 GError **error)
1873 {
1874 GdictClientContextPrivate *priv;
1875 GError *lookup_error, *flags_error;
1876 gboolean res;
1877 gint sock_fd, sock_res;
1878 gsize addrlen;
1879 GIOFlags flags;
1880
1881 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
1882
1883 priv = context->priv;
1884
1885 if (!priv->hostname)
1886 {
1887 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1888 GDICT_CLIENT_CONTEXT_ERROR_LOOKUP,
1889 _("No hostname defined for the dictionary server"));
1890
1891 return FALSE;
1892 }
1893
1894 /* forgive the absence of a port */
1895 if (!priv->port)
1896 priv->port = GDICT_DEFAULT_PORT;
1897
1898 priv->is_connecting = TRUE;
1899
1900 lookup_error = NULL;
1901 res = gdict_client_context_lookup_server (context, &lookup_error);
1902 if (!res)
1903 {
1904 g_propagate_error (error, lookup_error);
1905
1906 return FALSE;
1907 }
1908
1909 #ifdef ENABLE_IPV6
1910 if (priv->sockaddr.ss_family == AF_INET6)
1911 ((struct sockaddr_in6 *) &priv->sockaddr)->sin6_port = g_htons (priv->port);
1912 else
1913 #endif
1914 ((struct sockaddr_in *) &priv->sockaddr)->sin_port = g_htons (priv->port);
1915
1916
1917 #ifdef ENABLE_IPV6
1918 if (priv->sockaddr.ss_family == AF_INET6)
1919 {
1920 sock_fd = socket (AF_INET6, SOCK_STREAM, 0);
1921 if (sock_fd < 0)
1922 {
1923 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1924 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1925 _("Unable to create socket"));
1926
1927 return FALSE;
1928 }
1929
1930 addrlen = sizeof (struct sockaddr_in6);
1931 }
1932 else
1933 {
1934 #endif /* ENABLE_IPV6 */
1935 sock_fd = socket (AF_INET, SOCK_STREAM, 0);
1936 if (sock_fd < 0)
1937 {
1938 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1939 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1940 _("Unable to create socket"));
1941
1942 return FALSE;
1943 }
1944
1945 addrlen = sizeof (struct sockaddr_in);
1946 #ifdef ENABLE_IPV6
1947 }
1948 #endif
1949
1950 priv->channel = g_io_channel_unix_new (sock_fd);
1951
1952 /* RFC2229 mandates the usage of UTF-8, so we force this encoding */
1953 g_io_channel_set_encoding (priv->channel, "UTF-8", NULL);
1954
1955 g_io_channel_set_line_term (priv->channel, "\r\n", 2);
1956
1957 /* make sure that the channel is non-blocking */
1958 flags = g_io_channel_get_flags (priv->channel);
1959 flags |= G_IO_FLAG_NONBLOCK;
1960 flags_error = NULL;
1961 g_io_channel_set_flags (priv->channel, flags, &flags_error);
1962 if (flags_error)
1963 {
1964 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1965 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1966 _("Unable to set the channel as non-blocking: %s"),
1967 flags_error->message);
1968
1969 g_error_free (flags_error);
1970 g_io_channel_unref (priv->channel);
1971
1972 return FALSE;
1973 }
1974
1975 /* let the magic begin */
1976 sock_res = connect (sock_fd, (struct sockaddr *) &priv->sockaddr, addrlen);
1977 if ((sock_res != 0) && (errno != EINPROGRESS))
1978 {
1979 g_set_error (error, GDICT_CLIENT_CONTEXT_ERROR,
1980 GDICT_CLIENT_CONTEXT_ERROR_SOCKET,
1981 _("Unable to connect to the dictionary server at “%s:%d”"),
1982 priv->hostname,
1983 priv->port);
1984
1985 return FALSE;
1986 }
1987
1988 priv->timeout_id = g_timeout_add_seconds (CONNECTION_TIMEOUT_SEC,
1989 check_for_connection,
1990 context);
1991
1992 /* XXX - remember that g_io_add_watch() increases the reference count
1993 * of the GIOChannel we are using.
1994 */
1995 priv->source_id = g_io_add_watch (priv->channel,
1996 (G_IO_IN | G_IO_ERR),
1997 (GIOFunc) gdict_client_context_io_watch_cb,
1998 context);
1999
2000 return TRUE;
2001 }
2002
2003 static void
gdict_client_context_disconnect(GdictClientContext * context)2004 gdict_client_context_disconnect (GdictClientContext *context)
2005 {
2006 GdictCommand *cmd;
2007
2008 g_return_if_fail (GDICT_IS_CLIENT_CONTEXT (context));
2009
2010 /* instead of just breaking the connection to the server, we push
2011 * a QUIT command on the queue, and wait for every other scheduled
2012 * command to perform; this allows the creation of a batch of
2013 * commands.
2014 */
2015 cmd = gdict_command_new (CMD_QUIT);
2016 cmd->state = S_FINISH;
2017
2018 gdict_client_context_push_command (context, cmd);
2019 }
2020
2021 static gboolean
gdict_client_context_is_connected(GdictClientContext * context)2022 gdict_client_context_is_connected (GdictClientContext *context)
2023 {
2024 g_assert (GDICT_IS_CLIENT_CONTEXT (context));
2025
2026 /* we are in the middle of a connection attempt */
2027 if (context->priv->is_connecting)
2028 return TRUE;
2029
2030 return (context->priv->channel != NULL && context->priv->source_id != 0);
2031 }
2032
2033 static gboolean
gdict_client_context_get_databases(GdictContext * context,GError ** error)2034 gdict_client_context_get_databases (GdictContext *context,
2035 GError **error)
2036 {
2037 GdictClientContext *client_ctx;
2038 GdictCommand *cmd;
2039
2040 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
2041
2042 client_ctx = GDICT_CLIENT_CONTEXT (context);
2043
2044 g_signal_emit_by_name (context, "database-lookup-start");
2045
2046 if (!gdict_client_context_is_connected (client_ctx))
2047 {
2048 GError *connect_error = NULL;
2049
2050 gdict_client_context_connect (client_ctx, &connect_error);
2051 if (connect_error)
2052 {
2053 g_signal_emit_by_name (context, "lookup-end");
2054
2055 g_propagate_error (error, connect_error);
2056
2057 return FALSE;
2058 }
2059 }
2060
2061 cmd = gdict_command_new (CMD_SHOW_DB);
2062
2063 return gdict_client_context_push_command (client_ctx, cmd);
2064 }
2065
2066 static gboolean
gdict_client_context_get_strategies(GdictContext * context,GError ** error)2067 gdict_client_context_get_strategies (GdictContext *context,
2068 GError **error)
2069 {
2070 GdictClientContext *client_ctx;
2071 GdictCommand *cmd;
2072
2073 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
2074
2075 client_ctx = GDICT_CLIENT_CONTEXT (context);
2076
2077 g_signal_emit_by_name (context, "lookup-start");
2078
2079 if (!gdict_client_context_is_connected (client_ctx))
2080 {
2081 GError *connect_error = NULL;
2082
2083 gdict_client_context_connect (client_ctx, &connect_error);
2084 if (connect_error)
2085 {
2086 g_signal_emit_by_name (context, "lookup-end");
2087
2088 g_propagate_error (error, connect_error);
2089
2090 return FALSE;
2091 }
2092 }
2093
2094 cmd = gdict_command_new (CMD_SHOW_STRAT);
2095
2096 return gdict_client_context_push_command (client_ctx, cmd);
2097 }
2098
2099 static gboolean
gdict_client_context_define_word(GdictContext * context,const gchar * database,const gchar * word,GError ** error)2100 gdict_client_context_define_word (GdictContext *context,
2101 const gchar *database,
2102 const gchar *word,
2103 GError **error)
2104 {
2105 GdictClientContext *client_ctx;
2106 GdictCommand *cmd;
2107
2108 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
2109
2110 client_ctx = GDICT_CLIENT_CONTEXT (context);
2111
2112 g_signal_emit_by_name (context, "definition-lookup-start");
2113
2114 if (!gdict_client_context_is_connected (client_ctx))
2115 {
2116 GError *connect_error = NULL;
2117
2118 gdict_client_context_connect (client_ctx, &connect_error);
2119 if (connect_error)
2120 {
2121 g_signal_emit_by_name (context, "definition-lookup-end");
2122
2123 g_propagate_error (error, connect_error);
2124
2125 return FALSE;
2126 }
2127 }
2128
2129 cmd = gdict_command_new (CMD_DEFINE);
2130 cmd->database = g_strdup ((database != NULL ? database : GDICT_DEFAULT_DATABASE));
2131 cmd->word = g_utf8_normalize (word, -1, G_NORMALIZE_NFC);
2132
2133 return gdict_client_context_push_command (client_ctx, cmd);
2134 }
2135
2136 static gboolean
gdict_client_context_match_word(GdictContext * context,const gchar * database,const gchar * strategy,const gchar * word,GError ** error)2137 gdict_client_context_match_word (GdictContext *context,
2138 const gchar *database,
2139 const gchar *strategy,
2140 const gchar *word,
2141 GError **error)
2142 {
2143 GdictClientContext *client_ctx;
2144 GdictCommand *cmd;
2145
2146 g_return_val_if_fail (GDICT_IS_CLIENT_CONTEXT (context), FALSE);
2147
2148 client_ctx = GDICT_CLIENT_CONTEXT (context);
2149
2150 g_signal_emit_by_name (context, "lookup-start");
2151
2152 if (!gdict_client_context_is_connected (client_ctx))
2153 {
2154 GError *connect_error = NULL;
2155
2156 gdict_client_context_connect (client_ctx, &connect_error);
2157 if (connect_error)
2158 {
2159 g_signal_emit_by_name (context, "lookup-end");
2160
2161 g_propagate_error (error, connect_error);
2162
2163 return FALSE;
2164 }
2165 }
2166
2167 cmd = gdict_command_new (CMD_MATCH);
2168 cmd->database = g_strdup ((database != NULL ? database : GDICT_DEFAULT_DATABASE));
2169 cmd->strategy = g_strdup ((strategy != NULL ? strategy : GDICT_DEFAULT_STRATEGY));
2170 cmd->word = g_utf8_normalize (word, -1, G_NORMALIZE_NFC);
2171
2172 return gdict_client_context_push_command (client_ctx, cmd);
2173 }
2174