1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  *  Copyright © 2017 Cedric Le Moigne <cedlemo@gmx.com>
4  *
5  *  This file is part of Epiphany.
6  *
7  *  Epiphany is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  Epiphany is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 #include "ephy-search-engine-manager.h"
23 
24 #include "ephy-file-helpers.h"
25 #include "ephy-string.h"
26 
27 #include "ephy-settings.h"
28 #include "ephy-prefs.h"
29 
30 #include <libsoup/soup.h>
31 
32 #define FALLBACK_ADDRESS "https://duckduckgo.com/?q=%s&t=epiphany"
33 
34 enum {
35   SEARCH_ENGINES_CHANGED,
36   LAST_SIGNAL
37 };
38 
39 static guint signals[LAST_SIGNAL];
40 
41 struct _EphySearchEngineManager {
42   GObject parent_instance;
43   GHashTable *search_engines;
44 };
45 
46 typedef struct {
47   char *address;
48   char *bang;
49 } EphySearchEngineInfo;
50 
G_DEFINE_TYPE(EphySearchEngineManager,ephy_search_engine_manager,G_TYPE_OBJECT)51 G_DEFINE_TYPE (EphySearchEngineManager, ephy_search_engine_manager, G_TYPE_OBJECT)
52 
53 static void
54 ephy_search_engine_info_free (EphySearchEngineInfo *info)
55 {
56   g_free (info->address);
57   g_free (info->bang);
58   g_free (info);
59 }
60 
61 static EphySearchEngineInfo *
ephy_search_engine_info_new(const char * address,const char * bang)62 ephy_search_engine_info_new (const char *address,
63                              const char *bang)
64 {
65   EphySearchEngineInfo *info;
66   info = g_malloc (sizeof (EphySearchEngineInfo));
67   info->address = g_strdup (address);
68   info->bang = g_strdup (bang);
69   return info;
70 }
71 
72 static void
search_engines_changed_cb(GSettings * settings,char * key,gpointer user_data)73 search_engines_changed_cb (GSettings *settings,
74                            char      *key,
75                            gpointer   user_data)
76 {
77   g_signal_emit (EPHY_SEARCH_ENGINE_MANAGER (user_data),
78                  signals[SEARCH_ENGINES_CHANGED], 0);
79 }
80 
81 static void
ephy_search_engine_manager_init(EphySearchEngineManager * manager)82 ephy_search_engine_manager_init (EphySearchEngineManager *manager)
83 {
84   const char *address;
85   const char *bang;
86   char *name;
87   g_autoptr (GVariantIter) iter = NULL;
88 
89   manager->search_engines = g_hash_table_new_full (g_str_hash,
90                                                    g_str_equal,
91                                                    g_free,
92                                                    (GDestroyNotify)ephy_search_engine_info_free);
93 
94   g_settings_get (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES, "a(sss)", &iter);
95 
96   while (g_variant_iter_next (iter, "(s&s&s)", &name, &address, &bang)) {
97     g_hash_table_insert (manager->search_engines,
98                          name,
99                          ephy_search_engine_info_new (address,
100                                                       bang));
101   }
102 
103   g_signal_connect (EPHY_SETTINGS_MAIN,
104                     "changed::search-engines",
105                     G_CALLBACK (search_engines_changed_cb), manager);
106 }
107 
108 static void
ephy_search_engine_manager_dispose(GObject * object)109 ephy_search_engine_manager_dispose (GObject *object)
110 {
111   EphySearchEngineManager *manager = EPHY_SEARCH_ENGINE_MANAGER (object);
112 
113   g_clear_pointer (&manager->search_engines, g_hash_table_destroy);
114 
115   G_OBJECT_CLASS (ephy_search_engine_manager_parent_class)->dispose (object);
116 }
117 
118 static void
ephy_search_engine_manager_class_init(EphySearchEngineManagerClass * klass)119 ephy_search_engine_manager_class_init (EphySearchEngineManagerClass *klass)
120 {
121   GObjectClass *object_class = G_OBJECT_CLASS (klass);
122 
123   object_class->dispose = ephy_search_engine_manager_dispose;
124 
125   signals[SEARCH_ENGINES_CHANGED] = g_signal_new ("changed",
126                                                   EPHY_TYPE_SEARCH_ENGINE_MANAGER,
127                                                   G_SIGNAL_RUN_LAST,
128                                                   0,
129                                                   NULL, NULL, NULL,
130                                                   G_TYPE_NONE, 0);
131 }
132 
133 EphySearchEngineManager *
ephy_search_engine_manager_new(void)134 ephy_search_engine_manager_new (void)
135 {
136   return EPHY_SEARCH_ENGINE_MANAGER (g_object_new (EPHY_TYPE_SEARCH_ENGINE_MANAGER, NULL));
137 }
138 
139 const char *
ephy_search_engine_manager_get_address(EphySearchEngineManager * manager,const char * name)140 ephy_search_engine_manager_get_address (EphySearchEngineManager *manager,
141                                         const char              *name)
142 {
143   EphySearchEngineInfo *info;
144 
145   info = (EphySearchEngineInfo *)g_hash_table_lookup (manager->search_engines, name);
146 
147   if (info)
148     return info->address;
149 
150   return NULL;
151 }
152 
153 const char *
ephy_search_engine_manager_get_default_search_address(EphySearchEngineManager * manager)154 ephy_search_engine_manager_get_default_search_address (EphySearchEngineManager *manager)
155 {
156   char *name;
157   const char *address;
158 
159   name = ephy_search_engine_manager_get_default_engine (manager);
160   address = ephy_search_engine_manager_get_address (manager, name);
161   g_free (name);
162 
163   return address ? address : FALLBACK_ADDRESS;
164 }
165 
166 const char *
ephy_search_engine_manager_get_bang(EphySearchEngineManager * manager,const char * name)167 ephy_search_engine_manager_get_bang (EphySearchEngineManager *manager,
168                                      const char              *name)
169 {
170   EphySearchEngineInfo *info;
171 
172   info = (EphySearchEngineInfo *)g_hash_table_lookup (manager->search_engines, name);
173 
174   if (info)
175     return info->bang;
176 
177   return NULL;
178 }
179 
180 char *
ephy_search_engine_manager_get_default_engine(EphySearchEngineManager * manager)181 ephy_search_engine_manager_get_default_engine (EphySearchEngineManager *manager)
182 {
183   return g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_DEFAULT_SEARCH_ENGINE);
184 }
185 
186 gboolean
ephy_search_engine_manager_set_default_engine(EphySearchEngineManager * manager,const char * name)187 ephy_search_engine_manager_set_default_engine (EphySearchEngineManager *manager,
188                                                const char              *name)
189 {
190   if (!g_hash_table_contains (manager->search_engines, name))
191     return FALSE;
192 
193   return g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_DEFAULT_SEARCH_ENGINE, name);
194 }
195 
196 char **
ephy_search_engine_manager_get_names(EphySearchEngineManager * manager)197 ephy_search_engine_manager_get_names (EphySearchEngineManager *manager)
198 {
199   GHashTableIter iter;
200   gpointer key;
201   char **search_engine_names;
202   guint size;
203   guint i = 0;
204 
205   size = g_hash_table_size (manager->search_engines);
206   search_engine_names = g_new0 (char *, size + 1);
207 
208   g_hash_table_iter_init (&iter, manager->search_engines);
209 
210   while (g_hash_table_iter_next (&iter, &key, NULL))
211     search_engine_names[i++] = g_strdup ((char *)key);
212 
213   return search_engine_names;
214 }
215 
216 /**
217  * ephy_search_engine_manager_engine_exists:
218  *
219  * Checks if search engine @name exists in @manager.
220  *
221  * @manager: the #EphySearchEngineManager
222  * @name:    the name of the search engine
223  *
224  * Returns: %TRUE if the search engine was found, %FALSE otherwise.
225  */
226 gboolean
ephy_search_engine_manager_engine_exists(EphySearchEngineManager * manager,const char * name)227 ephy_search_engine_manager_engine_exists (EphySearchEngineManager *manager,
228                                           const char              *name)
229 {
230   return !!g_hash_table_lookup (manager->search_engines, name);
231 }
232 
233 char **
ephy_search_engine_manager_get_bangs(EphySearchEngineManager * manager)234 ephy_search_engine_manager_get_bangs (EphySearchEngineManager *manager)
235 {
236   GHashTableIter iter;
237   gpointer value;
238   char **search_engine_bangs;
239   guint size;
240   guint i = 0;
241 
242   size = g_hash_table_size (manager->search_engines);
243   search_engine_bangs = g_new0 (char *, size + 1);
244 
245   g_hash_table_iter_init (&iter, manager->search_engines);
246 
247   while (g_hash_table_iter_next (&iter, NULL, &value))
248     search_engine_bangs[i++] = ((EphySearchEngineInfo *)value)->bang;
249 
250   return search_engine_bangs;
251 }
252 
253 static void
ephy_search_engine_manager_apply_settings(EphySearchEngineManager * manager)254 ephy_search_engine_manager_apply_settings (EphySearchEngineManager *manager)
255 {
256   GHashTableIter iter;
257   EphySearchEngineInfo *info;
258   gpointer key;
259   gpointer value;
260   GVariantBuilder builder;
261   GVariant *variant;
262 
263   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sss)"));
264   g_hash_table_iter_init (&iter, manager->search_engines);
265 
266   while (g_hash_table_iter_next (&iter, &key, &value)) {
267     info = (EphySearchEngineInfo *)value;
268     g_variant_builder_add (&builder, "(sss)", key, info->address, info->bang);
269   }
270   variant = g_variant_builder_end (&builder);
271   g_settings_set_value (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES, variant);
272 }
273 
274 void
ephy_search_engine_manager_add_engine(EphySearchEngineManager * manager,const char * name,const char * address,const char * bang)275 ephy_search_engine_manager_add_engine (EphySearchEngineManager *manager,
276                                        const char              *name,
277                                        const char              *address,
278                                        const char              *bang)
279 {
280   EphySearchEngineInfo *info;
281 
282   info = ephy_search_engine_info_new (address, bang);
283   g_hash_table_insert (manager->search_engines, g_strdup (name), info);
284   ephy_search_engine_manager_apply_settings (manager);
285 }
286 
287 void
ephy_search_engine_manager_delete_engine(EphySearchEngineManager * manager,const char * name)288 ephy_search_engine_manager_delete_engine (EphySearchEngineManager *manager,
289                                           const char              *name)
290 {
291   g_hash_table_remove (manager->search_engines, name);
292   ephy_search_engine_manager_apply_settings (manager);
293 }
294 
295 /**
296  * ephy_search_engine_manager_rename:
297  *
298  * Renames search engine @old_name to @new_name, taking care of setting it back
299  * as default search engine if needed.
300  *
301  * @manager: a #EphySearchEngineManager
302  * @old_name: the current name of the search engine
303  * @new_name: the new name for search engine @old_name
304  *
305  * Returns: %FALSE if there wasn't any renaming to do (if both old and new names
306  * were the same), %TRUE if the search engine was renamed.
307  */
308 gboolean
ephy_search_engine_manager_rename(EphySearchEngineManager * manager,const char * old_name,const char * new_name)309 ephy_search_engine_manager_rename (EphySearchEngineManager *manager,
310                                    const char              *old_name,
311                                    const char              *new_name)
312 {
313   EphySearchEngineInfo *info, *info_copy;
314 
315   if (g_strcmp0 (old_name, new_name) == 0)
316     return FALSE;
317 
318   info = g_hash_table_lookup (manager->search_engines, old_name);
319   g_assert_nonnull (info);
320 
321   info_copy = ephy_search_engine_info_new (info->address, info->bang);
322   g_hash_table_remove (manager->search_engines, old_name);
323   g_hash_table_insert (manager->search_engines, g_strdup (new_name), info_copy);
324   /* Set the search engine back as default engine if it was the default one. */
325   if (g_strcmp0 (ephy_search_engine_manager_get_default_engine (manager), old_name) == 0)
326     ephy_search_engine_manager_set_default_engine (manager, new_name);
327   ephy_search_engine_manager_apply_settings (manager);
328 
329   return TRUE;
330 }
331 
332 void
ephy_search_engine_manager_modify_engine(EphySearchEngineManager * manager,const char * name,const char * address,const char * bang)333 ephy_search_engine_manager_modify_engine (EphySearchEngineManager *manager,
334                                           const char              *name,
335                                           const char              *address,
336                                           const char              *bang)
337 {
338   EphySearchEngineInfo *info;
339 
340   /* You can't modify a non-existant search engine. */
341   g_assert (g_hash_table_contains (manager->search_engines, name));
342 
343   info = ephy_search_engine_info_new (address, bang);
344   g_hash_table_replace (manager->search_engines,
345                         g_strdup (name),
346                         info);
347   ephy_search_engine_manager_apply_settings (manager);
348 }
349 
350 const char *
ephy_search_engine_manager_engine_from_bang(EphySearchEngineManager * manager,const char * bang)351 ephy_search_engine_manager_engine_from_bang (EphySearchEngineManager *manager,
352                                              const char              *bang)
353 {
354   GHashTableIter iter;
355   EphySearchEngineInfo *info;
356   gpointer key;
357   gpointer value;
358 
359   g_hash_table_iter_init (&iter, manager->search_engines);
360 
361   while (g_hash_table_iter_next (&iter, &key, &value)) {
362     info = (EphySearchEngineInfo *)value;
363     if (g_strcmp0 (bang, info->bang) == 0)
364       return (const char *)key;
365   }
366 
367   return NULL;
368 }
369 
370 static char *
ephy_search_engine_manager_replace_pattern(const char * string,const char * pattern,const char * replace)371 ephy_search_engine_manager_replace_pattern (const char *string,
372                                             const char *pattern,
373                                             const char *replace)
374 {
375   gchar **strings;
376   gchar *query_param;
377   const gchar *escaped_replace;
378   GString *buffer;
379 
380   strings = g_strsplit (string, pattern, -1);
381   query_param = soup_form_encode ("q", replace, NULL);
382   escaped_replace = query_param + 2;
383 
384   buffer = g_string_new (NULL);
385 
386   for (guint i = 0; strings[i] != NULL; i++) {
387     if (i > 0)
388       g_string_append (buffer, escaped_replace);
389 
390     g_string_append (buffer, strings[i]);
391   }
392 
393   g_strfreev (strings);
394   g_free (query_param);
395 
396   return g_string_free (buffer, FALSE);
397 }
398 
399 char *
ephy_search_engine_manager_build_search_address(EphySearchEngineManager * manager,const char * name,const char * search)400 ephy_search_engine_manager_build_search_address (EphySearchEngineManager *manager,
401                                                  const char              *name,
402                                                  const char              *search)
403 {
404   EphySearchEngineInfo *info;
405 
406   info = (EphySearchEngineInfo *)g_hash_table_lookup (manager->search_engines, name);
407 
408   if (info == NULL)
409     return NULL;
410 
411   return ephy_search_engine_manager_replace_pattern (info->address, "%s", search);
412 }
413 
414 char *
ephy_search_engine_manager_parse_bang_search(EphySearchEngineManager * manager,const char * search)415 ephy_search_engine_manager_parse_bang_search (EphySearchEngineManager *manager,
416                                               const char              *search)
417 {
418   GHashTableIter iter;
419   EphySearchEngineInfo *info;
420   gpointer value;
421   GString *buffer;
422   char *search_address = NULL;
423 
424   g_hash_table_iter_init (&iter, manager->search_engines);
425 
426   while (g_hash_table_iter_next (&iter, NULL, &value)) {
427     info = (EphySearchEngineInfo *)value;
428     buffer = g_string_new (info->bang);
429     g_string_append (buffer, " ");
430     if (strstr (search, buffer->str) == search) {
431       search_address = ephy_search_engine_manager_replace_pattern (info->address,
432                                                                    "%s",
433                                                                    (search + buffer->len));
434       g_string_free (buffer, TRUE);
435       return search_address;
436     }
437     g_string_free (buffer, TRUE);
438   }
439 
440   return search_address;
441 }
442