1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright © 2011 Igalia S.L.
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-web-app-utils.h"
23
24 #include "ephy-debug.h"
25 #include "ephy-file-helpers.h"
26 #include "ephy-profile-utils.h"
27 #include "ephy-settings.h"
28
29 #include <errno.h>
30 #include <gio/gio.h>
31 #include <glib/gstdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <fcntl.h>
35
36 /* Web Apps are installed in the default config dir of the user.
37 * Every app has its own profile directory. To create a web app
38 * an id needs to be generated using the given name as
39 * <normalized-name>-checksum.
40 *
41 * The id is used to uniquely identify the app.
42 * - Program name: org.gnome.Epiphany.WebApp-<id>
43 * - Profile directory: <program-name>
44 * - Desktop file: <profile-dir>/<program-name>.desktop
45 *
46 * System web applications have a profile dir without a desktop file.
47 */
48
49 #define EPHY_WEB_APP_PROGRAM_NAME_PREFIX "org.gnome.Epiphany.WebApp-"
50
51 char *
ephy_web_application_get_app_id_from_name(const char * name)52 ephy_web_application_get_app_id_from_name (const char *name)
53 {
54 char *normal_id;
55 char *checksum;
56 char *id;
57
58 normal_id = g_utf8_strdown (name, -1);
59 g_strdelimit (normal_id, " ", '-');
60 g_strdelimit (normal_id, G_DIR_SEPARATOR_S, '-');
61
62 checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1, name, -1);
63 id = g_strdup_printf ("%s-%s", normal_id, checksum);
64
65 g_free (normal_id);
66 g_free (checksum);
67
68 return id;
69 }
70
71 static char *
get_encoded_path(const char * path)72 get_encoded_path (const char *path)
73 {
74 char *encoded;
75 GError *error = NULL;
76
77 encoded = g_filename_from_utf8 (path, -1, NULL, NULL, &error);
78 if (error) {
79 g_warning ("%s", error->message);
80 g_error_free (error);
81 return NULL;
82 }
83
84 return encoded;
85 }
86
87 static char *
get_app_profile_directory_name(const char * id)88 get_app_profile_directory_name (const char *id)
89 {
90 char *profile_dir;
91 char *encoded;
92
93 profile_dir = g_strconcat (EPHY_WEB_APP_PROGRAM_NAME_PREFIX, id, NULL);
94 encoded = get_encoded_path (profile_dir);
95 g_free (profile_dir);
96
97 return encoded;
98 }
99
100 static char *
get_app_desktop_filename(const char * id)101 get_app_desktop_filename (const char *id)
102 {
103 char *filename;
104 char *encoded;
105
106 filename = g_strconcat (EPHY_WEB_APP_PROGRAM_NAME_PREFIX, id, ".desktop", NULL);
107 encoded = get_encoded_path (filename);
108 g_free (filename);
109
110 return encoded;
111 }
112
113 const char *
ephy_web_application_get_program_name_from_profile_directory(const char * profile_dir)114 ephy_web_application_get_program_name_from_profile_directory (const char *profile_dir)
115 {
116 const char *name;
117
118 /* Just get the basename */
119 name = strrchr (profile_dir, G_DIR_SEPARATOR);
120 if (name == NULL) {
121 g_warning ("Profile directoroy %s is not a valid path", profile_dir);
122 return NULL;
123 }
124
125 name++; /* Strip '/' */
126
127 /* Legacy web app support */
128 if (g_str_has_prefix (name, "app-"))
129 name += strlen ("app-");
130
131 if (!g_str_has_prefix (name, EPHY_WEB_APP_PROGRAM_NAME_PREFIX)) {
132 g_warning ("Profile directory %s does not begin with required web app prefix %s", profile_dir, EPHY_WEB_APP_PROGRAM_NAME_PREFIX);
133 return NULL;
134 }
135
136 return name;
137 }
138
139 static const char *
get_app_id_from_program_name(const char * name)140 get_app_id_from_program_name (const char *name)
141 {
142 if (!g_str_has_prefix (name, EPHY_WEB_APP_PROGRAM_NAME_PREFIX)) {
143 g_warning ("Program name %s does not begin with required prefix %s", name, EPHY_WEB_APP_PROGRAM_NAME_PREFIX);
144 return NULL;
145 }
146
147 return name + strlen (EPHY_WEB_APP_PROGRAM_NAME_PREFIX);
148 }
149
150 static const char *
get_app_id_from_profile_directory(const char * profile_dir)151 get_app_id_from_profile_directory (const char *profile_dir)
152 {
153 const char *program_name;
154
155 program_name = ephy_web_application_get_program_name_from_profile_directory (profile_dir);
156 return program_name ? get_app_id_from_program_name (program_name) : NULL;
157 }
158
159 static char *
ephy_web_application_get_directory_under(const char * id,const char * path)160 ephy_web_application_get_directory_under (const char *id,
161 const char *path)
162 {
163 g_autofree char *app_dir = NULL;
164
165 app_dir = get_app_profile_directory_name (id);
166 if (!app_dir)
167 return NULL;
168
169 return g_build_filename (path, app_dir, NULL);
170 }
171
172 /**
173 * ephy_web_application_get_profile_directory:
174 * @id: the application identifier
175 *
176 * Gets the directory where the profile for @id is meant to be stored.
177 *
178 * Returns: (transfer full): A newly allocated string.
179 **/
180 char *
ephy_web_application_get_profile_directory(const char * id)181 ephy_web_application_get_profile_directory (const char *id)
182 {
183 return ephy_web_application_get_directory_under (id, g_get_user_data_dir ());
184 }
185
186 static char *
ephy_web_application_get_cache_directory(const char * id)187 ephy_web_application_get_cache_directory (const char *id)
188 {
189 return ephy_web_application_get_directory_under (id, g_get_user_cache_dir ());
190 }
191
192 static char *
ephy_web_application_get_config_directory(const char * id)193 ephy_web_application_get_config_directory (const char *id)
194 {
195 return ephy_web_application_get_directory_under (id, g_get_user_config_dir ());
196 }
197
198 /**
199 * ephy_web_application_delete:
200 * @id: the identifier of the web application do delete
201 *
202 * Deletes all the data associated with a Web Application created by
203 * Epiphany.
204 *
205 * Returns: %TRUE if the web app was succesfully deleted, %FALSE otherwise
206 **/
207 gboolean
ephy_web_application_delete(const char * id)208 ephy_web_application_delete (const char *id)
209 {
210 g_autofree char *profile_dir = NULL;
211 g_autofree char *cache_dir = NULL;
212 g_autofree char *config_dir = NULL;
213 g_autofree char *desktop_file = NULL;
214 g_autofree char *desktop_path = NULL;
215 g_autoptr (GFile) launcher = NULL;
216 g_autoptr (GError) error = NULL;
217
218 g_assert (id);
219
220 profile_dir = ephy_web_application_get_profile_directory (id);
221 if (!profile_dir)
222 return FALSE;
223
224 /* If there's no profile dir for this app, it means it does not
225 * exist. */
226 if (!g_file_test (profile_dir, G_FILE_TEST_IS_DIR)) {
227 g_warning ("No application with id '%s' is installed.\n", id);
228 return FALSE;
229 }
230
231 if (!ephy_file_delete_dir_recursively (profile_dir, &error)) {
232 g_warning ("Failed to recursively delete %s: %s", profile_dir, error->message);
233 return FALSE;
234 }
235 LOG ("Deleted application profile.\n");
236
237 cache_dir = ephy_web_application_get_cache_directory (id);
238 if (g_file_test (cache_dir, G_FILE_TEST_IS_DIR)) {
239 if (!ephy_file_delete_dir_recursively (cache_dir, &error)) {
240 g_warning ("Failed to recursively delete %s: %s", cache_dir, error->message);
241 return FALSE;
242 }
243 LOG ("Deleted application cache directory.\n");
244 }
245
246 config_dir = ephy_web_application_get_config_directory (id);
247 if (g_file_test (config_dir, G_FILE_TEST_IS_DIR)) {
248 if (!ephy_file_delete_dir_recursively (config_dir, &error)) {
249 g_warning ("Failed to recursively delete %s: %s", config_dir, error->message);
250 return FALSE;
251 }
252 LOG ("Deleted application config directory.\n");
253 }
254
255 desktop_file = get_app_desktop_filename (id);
256 if (!desktop_file) {
257 g_warning ("Failed to compute desktop filename for app %s", id);
258 return FALSE;
259 }
260
261 desktop_path = g_build_filename (g_get_user_data_dir (), "applications", desktop_file, NULL);
262 if (g_file_test (desktop_path, G_FILE_TEST_IS_SYMLINK)) {
263 launcher = g_file_new_for_path (desktop_path);
264 if (!g_file_delete (launcher, NULL, &error)) {
265 g_warning ("Failed to delete %s: %s", desktop_path, error->message);
266 return FALSE;
267 }
268 LOG ("Deleted application launcher.\n");
269 }
270
271 return TRUE;
272 }
273
274 static char *
create_desktop_file(const char * id,const char * name,const char * address,const char * profile_dir,GdkPixbuf * icon)275 create_desktop_file (const char *id,
276 const char *name,
277 const char *address,
278 const char *profile_dir,
279 GdkPixbuf *icon)
280 {
281 GKeyFile *file = NULL;
282 char *exec_string;
283 char *data = NULL;
284 char *filename, *apps_path, *desktop_file_path = NULL;
285 char *link_path;
286 char *wm_class;
287 GFile *link;
288 GError *error = NULL;
289
290 g_assert (profile_dir);
291
292 filename = get_app_desktop_filename (id);
293 if (!filename)
294 return NULL;
295
296 file = g_key_file_new ();
297 g_key_file_set_value (file, "Desktop Entry", "Name", name);
298 exec_string = g_strdup_printf ("epiphany --application-mode --profile=\"%s\" %s",
299 profile_dir,
300 address);
301 g_key_file_set_value (file, "Desktop Entry", "Exec", exec_string);
302 g_free (exec_string);
303 g_key_file_set_value (file, "Desktop Entry", "StartupNotify", "true");
304 g_key_file_set_value (file, "Desktop Entry", "Terminal", "false");
305 g_key_file_set_value (file, "Desktop Entry", "Type", "Application");
306 g_key_file_set_value (file, "Desktop Entry", "Categories", "GNOME;GTK;");
307
308 if (icon) {
309 GOutputStream *stream;
310 char *path;
311 GFile *image;
312
313 path = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL);
314 image = g_file_new_for_path (path);
315
316 stream = (GOutputStream *)g_file_create (image, 0, NULL, NULL);
317 gdk_pixbuf_save_to_stream (icon, stream, "png", NULL, NULL, NULL);
318 g_key_file_set_value (file, "Desktop Entry", "Icon", path);
319
320 g_object_unref (stream);
321 g_object_unref (image);
322 g_free (path);
323 }
324
325 wm_class = g_strconcat (EPHY_WEB_APP_PROGRAM_NAME_PREFIX, id, NULL);
326 g_key_file_set_value (file, "Desktop Entry", "StartupWMClass", wm_class);
327 g_free (wm_class);
328
329 g_key_file_set_value (file, "Desktop Entry", "X-Purism-FormFactor", "Workstation;Mobile;");
330
331 data = g_key_file_to_data (file, NULL, NULL);
332
333 desktop_file_path = g_build_filename (profile_dir, filename, NULL);
334
335 if (!g_file_set_contents (desktop_file_path, data, -1, NULL)) {
336 g_free (desktop_file_path);
337 desktop_file_path = NULL;
338 }
339
340 /* Create a symlink in XDG_DATA_DIR/applications for the Shell to
341 * pick up this application. */
342 apps_path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
343 if (ephy_ensure_dir_exists (apps_path, &error)) {
344 link_path = g_build_filename (apps_path, filename, NULL);
345 link = g_file_new_for_path (link_path);
346 g_free (link_path);
347 g_file_make_symbolic_link (link, desktop_file_path, NULL, NULL);
348 g_object_unref (link);
349 } else {
350 g_warning ("Error creating application symlink: %s", error->message);
351 g_error_free (error);
352 }
353
354 g_free (apps_path);
355 g_free (filename);
356 g_free (data);
357 g_key_file_free (file);
358
359 return desktop_file_path;
360 }
361
362 /**
363 * ephy_web_application_create:
364 * @id: the identifier for the new web application
365 * @address: the address of the new web application
366 * @name: the name for the new web application
367 * @icon: the icon for the new web application
368 * @options: the options for the new web application
369 *
370 * Creates a new Web Application for @address.
371 *
372 * Returns: (transfer-full): the path to the desktop file representing the new application
373 **/
374 char *
ephy_web_application_create(const char * id,const char * address,const char * name,GdkPixbuf * icon,EphyWebApplicationOptions options)375 ephy_web_application_create (const char *id,
376 const char *address,
377 const char *name,
378 GdkPixbuf *icon,
379 EphyWebApplicationOptions options)
380 {
381 g_autofree char *app_file = NULL;
382 g_autofree char *profile_dir = NULL;
383 g_autofree char *desktop_file_path = NULL;
384 int fd;
385
386 /* If there's already a WebApp profile for the contents of this
387 * view, do nothing. */
388 profile_dir = ephy_web_application_get_profile_directory (id);
389 if (g_file_test (profile_dir, G_FILE_TEST_IS_DIR)) {
390 g_warning ("Profile directory %s already exists", profile_dir);
391 return NULL;
392 }
393
394 /* Create the profile directory, populate it. */
395 if (g_mkdir_with_parents (profile_dir, 488) == -1) {
396 g_warning ("Failed to create directory %s", profile_dir);
397 return NULL;
398 }
399
400 /* Skip migration for new web apps. */
401 ephy_profile_utils_set_migration_version_for_profile_dir (EPHY_PROFILE_MIGRATION_VERSION, profile_dir);
402
403 /* Create an .app file. */
404 app_file = g_build_filename (profile_dir, ".app", NULL);
405 fd = g_open (app_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
406 if (fd < 0) {
407 g_warning ("Failed to create .app file: %s", g_strerror (errno));
408 return NULL;
409 }
410 close (fd);
411
412 /* Create the deskop file. */
413 desktop_file_path = create_desktop_file (id, name, address, profile_dir, icon);
414 if (desktop_file_path)
415 ephy_web_application_initialize_settings (profile_dir, options);
416
417 return g_steal_pointer (&desktop_file_path);
418 }
419
420 char *
ephy_web_application_ensure_for_app_info(GAppInfo * app_info)421 ephy_web_application_ensure_for_app_info (GAppInfo *app_info)
422 {
423 g_autofree char *id = NULL;
424 g_autofree char *profile_dir = NULL;
425 g_autofree char *app_file = NULL;
426 int fd;
427
428 id = ephy_web_application_get_app_id_from_name (g_app_info_get_name (app_info));
429 profile_dir = ephy_web_application_get_profile_directory (id);
430
431 /* Create the profile directory, populate it. */
432 if (g_mkdir (profile_dir, 488) == -1) {
433 if (errno == EEXIST)
434 return g_steal_pointer (&profile_dir);
435
436 return NULL;
437 }
438
439 /* Skip migration for new web apps. */
440 ephy_profile_utils_set_migration_version_for_profile_dir (EPHY_PROFILE_MIGRATION_VERSION, profile_dir);
441
442 /* Create an .app file. */
443 app_file = g_build_filename (profile_dir, ".app", NULL);
444 fd = g_open (app_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
445 if (fd < 0) {
446 g_warning ("Failed to create .app file: %s", g_strerror (errno));
447 return NULL;
448 }
449 close (fd);
450
451 /* Create the deskop file. */
452 if (G_IS_DESKTOP_APP_INFO (app_info)) {
453 const char *source_name = NULL;
454 g_autofree char *dest_name = NULL;
455 g_autofree char *desktop_basename = NULL;
456 g_autoptr (GFile) source = NULL;
457 g_autoptr (GFile) dest = NULL;
458 g_autoptr (GError) error = NULL;
459
460 source_name = g_desktop_app_info_get_filename (G_DESKTOP_APP_INFO (app_info));
461 source = g_file_new_for_path (source_name);
462
463 desktop_basename = get_app_desktop_filename (id);
464 dest_name = g_build_filename (profile_dir, desktop_basename, NULL);
465 dest = g_file_new_for_path (dest_name);
466
467 g_file_copy (source, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
468
469 if (error)
470 g_warning ("Couldn't copy desktop file: %s", error->message);
471
472 ephy_web_application_initialize_settings (profile_dir, EPHY_WEB_APPLICATION_SYSTEM);
473 }
474
475 return g_steal_pointer (&profile_dir);
476 }
477
478 void
ephy_web_application_setup_from_profile_directory(const char * profile_directory)479 ephy_web_application_setup_from_profile_directory (const char *profile_directory)
480 {
481 const char *program_name;
482 const char *id;
483 char *app_icon;
484 char *desktop_basename;
485 char *desktop_filename;
486 GDesktopAppInfo *desktop_info;
487
488 g_assert (profile_directory != NULL);
489
490 program_name = ephy_web_application_get_program_name_from_profile_directory (profile_directory);
491 if (!program_name)
492 exit (1);
493
494 g_set_prgname (program_name);
495
496 id = get_app_id_from_program_name (program_name);
497 if (!id)
498 exit (1);
499
500 /* Get display name from desktop file */
501 desktop_basename = get_app_desktop_filename (id);
502 desktop_filename = g_build_filename (profile_directory, desktop_basename, NULL);
503 desktop_info = g_desktop_app_info_new_from_filename (desktop_filename);
504 if (!desktop_info) {
505 g_warning ("Required desktop file not present at %s", desktop_filename);
506 exit (1);
507 }
508 g_set_application_name (g_app_info_get_name (G_APP_INFO (desktop_info)));
509
510 app_icon = g_build_filename (profile_directory, EPHY_WEB_APP_ICON_NAME, NULL);
511 gtk_window_set_default_icon_from_file (app_icon, NULL);
512
513 /* We need to re-set this because we have already parsed the
514 * options, which inits GTK+ and sets this as a side effect.
515 */
516 gdk_set_program_class (program_name);
517
518 g_free (app_icon);
519 g_free (desktop_basename);
520 g_free (desktop_filename);
521 g_object_unref (desktop_info);
522 }
523
524 void
ephy_web_application_setup_from_desktop_file(GDesktopAppInfo * desktop_info)525 ephy_web_application_setup_from_desktop_file (GDesktopAppInfo *desktop_info)
526 {
527 GAppInfo *app_info;
528 const char *wm_class;
529 GIcon *icon;
530
531 g_assert (G_IS_DESKTOP_APP_INFO (desktop_info));
532
533 app_info = G_APP_INFO (desktop_info);
534 g_set_prgname (g_app_info_get_name (app_info));
535 g_set_application_name (g_app_info_get_display_name (app_info));
536
537 icon = g_app_info_get_icon (app_info);
538 if (G_IS_FILE_ICON (icon)) {
539 GFile *file = g_file_icon_get_file (G_FILE_ICON (icon));
540 char *path = file ? g_file_get_path (file) : NULL;
541
542 if (path) {
543 gtk_window_set_default_icon_from_file (path, NULL);
544 g_free (path);
545 }
546 g_clear_object (&file);
547 } else if (G_IS_THEMED_ICON (icon)) {
548 const char * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon));
549 if (names)
550 gtk_window_set_default_icon_name (names[0]);
551 }
552
553 /* We need to re-set this because we have already parsed the
554 * options, which inits GTK+ and sets this as a side effect.
555 */
556 wm_class = g_desktop_app_info_get_startup_wm_class (desktop_info);
557 if (wm_class)
558 gdk_set_program_class (wm_class);
559 }
560
561 void
ephy_web_application_free(EphyWebApplication * app)562 ephy_web_application_free (EphyWebApplication *app)
563 {
564 g_free (app->id);
565 g_free (app->name);
566 g_free (app->icon_url);
567 g_free (app->url);
568 g_free (app->desktop_file);
569 g_free (app);
570 }
571
572
573 EphyWebApplication *
ephy_web_application_for_profile_directory(const char * profile_dir)574 ephy_web_application_for_profile_directory (const char *profile_dir)
575 {
576 EphyWebApplication *app;
577 char *desktop_file_path;
578 const char *id;
579 GDesktopAppInfo *desktop_info;
580 const char *exec;
581 int argc;
582 char **argv;
583 GFile *file;
584 GFileInfo *file_info;
585 guint64 created;
586 GDate *date;
587
588 id = get_app_id_from_profile_directory (profile_dir);
589 if (!id)
590 return NULL;
591
592 app = g_new0 (EphyWebApplication, 1);
593 app->id = g_strdup (id);
594
595 app->desktop_file = get_app_desktop_filename (id);
596 desktop_file_path = g_build_filename (profile_dir, app->desktop_file, NULL);
597 desktop_info = g_desktop_app_info_new_from_filename (desktop_file_path);
598 if (!desktop_info) {
599 ephy_web_application_free (app);
600 g_free (desktop_file_path);
601 return NULL;
602 }
603
604 app->name = g_strdup (g_app_info_get_name (G_APP_INFO (desktop_info)));
605 app->icon_url = g_desktop_app_info_get_string (desktop_info, "Icon");
606 exec = g_app_info_get_commandline (G_APP_INFO (desktop_info));
607 if (g_shell_parse_argv (exec, &argc, &argv, NULL)) {
608 app->url = g_strdup (argv[argc - 1]);
609 g_strfreev (argv);
610 }
611
612 g_object_unref (desktop_info);
613
614 file = g_file_new_for_path (desktop_file_path);
615
616 /* FIXME: this should use TIME_CREATED but it does not seem to be working. */
617 file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
618 created = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
619
620 date = g_date_new ();
621 g_date_set_time_t (date, (time_t)created);
622 g_date_strftime (app->install_date, 127, "%x", date);
623
624 g_date_free (date);
625 g_object_unref (file);
626 g_object_unref (file_info);
627 g_free (desktop_file_path);
628
629 return app;
630 }
631
632 static GList *
ephy_web_application_get_application_list_internal(gboolean only_legacy)633 ephy_web_application_get_application_list_internal (gboolean only_legacy)
634 {
635 GFileEnumerator *children = NULL;
636 GFileInfo *info;
637 GList *applications = NULL;
638 g_autofree char *parent_directory_path = NULL;
639 g_autoptr (GFile) parent_directory = NULL;
640
641 if (only_legacy)
642 parent_directory_path = g_build_filename (g_get_user_config_dir (), "epiphany", NULL);
643 else
644 parent_directory_path = g_strdup (g_get_user_data_dir ());
645
646 parent_directory = g_file_new_for_path (parent_directory_path);
647 children = g_file_enumerate_children (parent_directory,
648 "standard::name",
649 0, NULL, NULL);
650 if (!children)
651 return NULL;
652
653 info = g_file_enumerator_next_file (children, NULL, NULL);
654 while (info) {
655 const char *name;
656
657 name = g_file_info_get_name (info);
658 if ((only_legacy && g_str_has_prefix (name, "app-")) ||
659 (!only_legacy && g_str_has_prefix (name, EPHY_WEB_APP_PROGRAM_NAME_PREFIX))) {
660 EphyWebApplication *app;
661 char *profile_dir;
662
663 profile_dir = g_build_filename (parent_directory_path, name, NULL);
664 app = ephy_web_application_for_profile_directory (profile_dir);
665 if (app) {
666 if (!only_legacy) {
667 g_autofree char *app_file = g_build_filename (profile_dir, ".app", NULL);
668 if (g_file_test (app_file, G_FILE_TEST_EXISTS))
669 applications = g_list_prepend (applications, app);
670 else
671 g_free (app);
672 } else
673 applications = g_list_prepend (applications, app);
674 }
675
676 g_free (profile_dir);
677 }
678
679 g_object_unref (info);
680
681 info = g_file_enumerator_next_file (children, NULL, NULL);
682 }
683
684 g_object_unref (children);
685
686 return g_list_reverse (applications);
687 }
688
689 /**
690 * ephy_web_application_get_application_list:
691 *
692 * Gets a list of the currently installed web applications.
693 * Free the returned GList with
694 * ephy_web_application_free_application_list.
695 *
696 * Returns: (transfer-full): a #GList of #EphyWebApplication objects
697 **/
698 GList *
ephy_web_application_get_application_list(void)699 ephy_web_application_get_application_list (void)
700 {
701 return ephy_web_application_get_application_list_internal (FALSE);
702 }
703
704 /**
705 * ephy_web_application_get_legacy_application_list:
706 *
707 * Gets a list of the currently installed web applications.
708 * This is only used for the profile migrator as it gets
709 * applications in the legacy directory.
710 *
711 * Free the returned GList with
712 * ephy_web_application_free_application_list.
713 *
714 * Returns: (transfer-full): a #GList of #EphyWebApplication objects
715 **/
716 GList *
ephy_web_application_get_legacy_application_list(void)717 ephy_web_application_get_legacy_application_list (void)
718 {
719 return ephy_web_application_get_application_list_internal (TRUE);
720 }
721
722
723 /**
724 * ephy_web_application_free_application_list:
725 * @list: an #EphyWebApplication GList
726 *
727 * Frees a @list as given by ephy_web_application_get_application_list.
728 **/
729 void
ephy_web_application_free_application_list(GList * list)730 ephy_web_application_free_application_list (GList *list)
731 {
732 g_list_free_full (list, (GDestroyNotify)ephy_web_application_free);
733 }
734
735 /**
736 * ephy_web_application_exists:
737 * @id: the potential identifier of the web application
738 *
739 * Returns: whether an application with @id exists.
740 **/
741 gboolean
ephy_web_application_exists(const char * id)742 ephy_web_application_exists (const char *id)
743 {
744 char *profile_dir;
745 gboolean profile_exists;
746
747 profile_dir = ephy_web_application_get_profile_directory (id);
748 profile_exists = g_file_test (profile_dir, G_FILE_TEST_IS_DIR);
749 g_free (profile_dir);
750
751 return profile_exists;
752 }
753
754 void
ephy_web_application_initialize_settings(const char * profile_directory,EphyWebApplicationOptions options)755 ephy_web_application_initialize_settings (const char *profile_directory,
756 EphyWebApplicationOptions options)
757 {
758 GSettings *settings;
759 GSettings *web_app_settings;
760 char *name;
761 char *path;
762
763 name = g_path_get_basename (profile_directory);
764 settings = g_settings_new_with_path (EPHY_PREFS_WEB_SCHEMA, "/org/gnome/epiphany/web/");
765
766 path = g_build_path ("/", "/org/gnome/epiphany/web-apps/", name, "web/", NULL);
767 web_app_settings = g_settings_new_with_path (EPHY_PREFS_WEB_SCHEMA, path);
768 g_free (path);
769
770 for (guint i = 0; i < G_N_ELEMENTS (ephy_prefs_web_schema); i++) {
771 GVariant *value;
772
773 value = g_settings_get_value (settings, ephy_prefs_web_schema[i]);
774 g_settings_set_value (web_app_settings, ephy_prefs_web_schema[i], value);
775 g_variant_unref (value);
776 }
777
778 g_object_unref (settings);
779 g_object_unref (web_app_settings);
780
781 settings = g_settings_new_with_path (EPHY_PREFS_STATE_SCHEMA, "/org/gnome/epiphany/state/");
782
783 path = g_build_path ("/", "/org/gnome/epiphany/web-apps/", name, "state/", NULL);
784 web_app_settings = g_settings_new_with_path (EPHY_PREFS_STATE_SCHEMA, path);
785 g_free (path);
786
787 for (guint i = 0; i < G_N_ELEMENTS (ephy_prefs_state_schema); i++) {
788 GVariant *value;
789
790 value = g_settings_get_value (settings, ephy_prefs_state_schema[i]);
791 g_settings_set_value (web_app_settings, ephy_prefs_state_schema[i], value);
792 g_variant_unref (value);
793 }
794
795 g_object_unref (settings);
796 g_object_unref (web_app_settings);
797
798 if (options) {
799 path = g_build_path ("/", "/org/gnome/epiphany/web-apps/", name, "webapp/", NULL);
800 web_app_settings = g_settings_new_with_path (EPHY_PREFS_WEB_APP_SCHEMA, path);
801 g_free (path);
802
803 if (options & EPHY_WEB_APPLICATION_MOBILE_CAPABLE)
804 g_settings_set_boolean (web_app_settings, EPHY_PREFS_WEB_APP_SHOW_NAVIGATION_BUTTONS, TRUE);
805
806 if (options & EPHY_WEB_APPLICATION_SYSTEM)
807 g_settings_set_boolean (web_app_settings, EPHY_PREFS_WEB_APP_SYSTEM, TRUE);
808
809 g_object_unref (web_app_settings);
810 }
811
812 g_free (name);
813 }
814
815 static gboolean
urls_have_same_origin(const char * a_url,const char * b_url)816 urls_have_same_origin (const char *a_url,
817 const char *b_url)
818 {
819 g_autoptr (GUri) a_uri = NULL;
820 g_autoptr (GUri) b_uri = NULL;
821
822 a_uri = g_uri_parse (a_url, G_URI_FLAGS_NONE, NULL);
823 if (!a_uri || !g_uri_get_host (a_uri))
824 return FALSE;
825
826 b_uri = g_uri_parse (b_url, G_URI_FLAGS_NONE, NULL);
827 if (!b_uri || !g_uri_get_host (b_uri))
828 return FALSE;
829
830 if (strcmp (g_uri_get_scheme (a_uri), g_uri_get_scheme (b_uri)) != 0)
831 return FALSE;
832
833 if (g_uri_get_port (a_uri) != g_uri_get_port (b_uri))
834 return FALSE;
835
836 return g_ascii_strcasecmp (g_uri_get_host (a_uri), g_uri_get_host (b_uri)) == 0;
837 }
838
839 gboolean
ephy_web_application_is_uri_allowed(const char * uri)840 ephy_web_application_is_uri_allowed (const char *uri)
841 {
842 EphyWebApplication *webapp = ephy_web_application_for_profile_directory (ephy_profile_dir ());
843 const char *scheme;
844 char **urls;
845 guint i;
846 gboolean matched = FALSE;
847
848 g_assert (webapp);
849
850 if (g_str_has_prefix (uri, "blob:") || g_str_has_prefix (uri, "data:"))
851 return TRUE;
852
853 if (urls_have_same_origin (uri, webapp->url))
854 return TRUE;
855
856 if (g_strcmp0 (uri, "about:blank") == 0)
857 return TRUE;
858
859 scheme = g_uri_peek_scheme (uri);
860 if (!scheme)
861 return FALSE;
862
863 urls = g_settings_get_strv (EPHY_SETTINGS_WEB_APP, EPHY_PREFS_WEB_APP_ADDITIONAL_URLS);
864 for (i = 0; urls[i] && !matched; i++) {
865 if (!strstr (urls[i], "://")) {
866 g_autofree char *url = NULL;
867
868 url = g_strdup_printf ("%s://%s", scheme, urls[i]);
869
870 matched = g_str_has_prefix (uri, url);
871 } else {
872 matched = g_str_has_prefix (uri, urls[i]);
873 }
874 }
875 g_strfreev (urls);
876
877 return matched;
878 }
879
880 static void
ephy_web_icon_copy_cb(GFile * file,GAsyncResult * result,gpointer user_data)881 ephy_web_icon_copy_cb (GFile *file,
882 GAsyncResult *result,
883 gpointer user_data)
884 {
885 GError *error = NULL;
886 if (!g_file_copy_finish (file, result, &error)) {
887 g_warning ("Failed to update web app icon: %s", error->message);
888 g_error_free (error);
889 }
890 }
891
892 gboolean
ephy_web_application_save(EphyWebApplication * app)893 ephy_web_application_save (EphyWebApplication *app)
894 {
895 char *profile_dir;
896 char *desktop_file_path;
897 char *contents;
898 gboolean saved = FALSE;
899 GError *error = NULL;
900
901 profile_dir = ephy_web_application_get_profile_directory (app->id);
902 desktop_file_path = g_build_filename (profile_dir, app->desktop_file, NULL);
903 if (g_file_get_contents (desktop_file_path, &contents, NULL, &error)) {
904 GKeyFile *key;
905 char *name;
906 char *icon;
907 char *exec;
908 char **strings;
909 guint exec_length;
910 gboolean changed = FALSE;
911
912 key = g_key_file_new ();
913 g_key_file_load_from_data (key, contents, -1, 0, NULL);
914
915 name = g_key_file_get_string (key, "Desktop Entry", "Name", NULL);
916 if (g_strcmp0 (name, app->name) != 0) {
917 changed = TRUE;
918 g_key_file_set_string (key, "Desktop Entry", "Name", app->name);
919 }
920 g_free (name);
921
922 icon = g_key_file_get_string (key, "Desktop Entry", "Icon", NULL);
923 if (g_strcmp0 (icon, app->icon_url) != 0) {
924 GFile *new_icon;
925 GFile *old_icon;
926 changed = TRUE;
927 new_icon = g_file_new_for_path (app->icon_url);
928 old_icon = g_file_new_for_path (icon);
929 g_file_copy_async (new_icon, old_icon, G_FILE_COPY_OVERWRITE,
930 G_PRIORITY_DEFAULT, NULL, NULL, NULL,
931 (GAsyncReadyCallback)ephy_web_icon_copy_cb, NULL);
932 }
933 g_free (icon);
934
935 exec = g_key_file_get_string (key, "Desktop Entry", "Exec", NULL);
936 strings = g_strsplit (exec, " ", -1);
937 g_free (exec);
938
939 exec_length = g_strv_length (strings);
940 if (g_strcmp0 (strings[exec_length - 1], app->url) != 0) {
941 changed = TRUE;
942 g_free (strings[exec_length - 1]);
943 strings[exec_length - 1] = g_strdup (app->url);
944 exec = g_strjoinv (" ", strings);
945 g_key_file_set_string (key, "Desktop Entry", "Exec", exec);
946 g_free (exec);
947 }
948 g_strfreev (strings);
949
950 if (changed) {
951 saved = g_key_file_save_to_file (key, desktop_file_path, &error);
952 if (!saved) {
953 g_warning ("Failed to save desktop file of web application: %s\n", error->message);
954 g_error_free (error);
955 }
956 }
957 g_free (contents);
958 g_key_file_free (key);
959 } else {
960 g_warning ("Failed to load desktop file of web application: %s\n", error->message);
961 g_error_free (error);
962 }
963
964 g_free (desktop_file_path);
965 g_free (profile_dir);
966
967 return saved;
968 }
969
970 gboolean
ephy_web_application_is_system(EphyWebApplication * app)971 ephy_web_application_is_system (EphyWebApplication *app)
972 {
973 GSettings *web_app_settings;
974 g_autofree char *profile_directory = NULL;
975 g_autofree char *name = NULL;
976 g_autofree char *path = NULL;
977
978 profile_directory = ephy_web_application_get_profile_directory (app->id);
979 name = g_path_get_basename (profile_directory);
980
981 path = g_build_path ("/", "/org/gnome/epiphany/web-apps/", name, "webapp/", NULL);
982 web_app_settings = g_settings_new_with_path (EPHY_PREFS_WEB_APP_SCHEMA, path);
983
984 return g_settings_get_boolean (web_app_settings, EPHY_PREFS_WEB_APP_SYSTEM);
985 }
986