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