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