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