1 /*
2  * Copyright © 2016 Aidan Holm <aidanholm@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18 
19 #include "globalconf.h"
20 #include "ipc.h"
21 
22 #include <assert.h>
23 #include <webkit2/webkit2.h>
24 #include <sys/socket.h>
25 #include <sys/types.h>
26 #include <sys/un.h>
27 #include <glib.h>
28 #include <glib/gstdio.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 
32 #include "clib/web_module.h"
33 #include "clib/luakit.h"
34 #include "clib/widget.h"
35 #include "common/luaserialize.h"
36 #include "common/clib/ipc.h"
37 #include "web_context.h"
38 #include "widgets/webview.h"
39 
40 void webview_scroll_recv(void *d, const ipc_scroll_t *ipc);
41 void run_javascript_finished(const guint8 *msg, guint length);
42 
43 static char *socket_path;
44 GMutex socket_path_lock;
45 GCond socket_path_cond;
46 
47 IPC_NO_HANDLER(lua_require_module)
IPC_NO_HANDLER(web_extension_loaded)48 IPC_NO_HANDLER(web_extension_loaded)
49 IPC_NO_HANDLER(crash)
50 
51 void
52 ipc_recv_extension_init(ipc_endpoint_t *ipc, const gpointer UNUSED(msg), guint UNUSED(length))
53 {
54     web_module_load_modules_on_endpoint(ipc);
55 
56     /* Notify web extension that pending signals can be released */
57     ipc_header_t header = { .type = IPC_TYPE_extension_init, .length = 0 };
58     ipc_send(ipc, &header, NULL);
59 }
60 
61 void
ipc_recv_lua_ipc(ipc_endpoint_t * UNUSED (ipc),const ipc_lua_ipc_t * msg,guint length)62 ipc_recv_lua_ipc(ipc_endpoint_t *UNUSED(ipc), const ipc_lua_ipc_t *msg, guint length)
63 {
64     ipc_channel_recv(common.L, msg->arg, length);
65 }
66 
67 void
ipc_recv_scroll(ipc_endpoint_t * UNUSED (ipc),ipc_scroll_t * msg,guint UNUSED (length))68 ipc_recv_scroll(ipc_endpoint_t *UNUSED(ipc), ipc_scroll_t *msg, guint UNUSED(length))
69 {
70     g_ptr_array_foreach(globalconf.webviews, (GFunc)webview_scroll_recv, msg);
71 }
72 
73 void
ipc_recv_eval_js(ipc_endpoint_t * UNUSED (ipc),const guint8 * msg,guint length)74 ipc_recv_eval_js(ipc_endpoint_t *UNUSED(ipc), const guint8 *msg, guint length)
75 {
76     run_javascript_finished(msg, length);
77 }
78 
79 void
ipc_recv_page_created(ipc_endpoint_t * ipc,const ipc_page_created_t * msg,guint UNUSED (length))80 ipc_recv_page_created(ipc_endpoint_t *ipc, const ipc_page_created_t *msg, guint UNUSED(length))
81 {
82     widget_t *w = webview_get_by_id(msg->page_id);
83 
84     /* Page may already have been closed */
85     if (!w) return;
86 
87     webview_connect_to_endpoint(w, ipc);
88     webview_set_web_process_id(w, msg->pid);
89 }
90 
91 static gchar *
build_socket_path(void)92 build_socket_path(void)
93 {
94     char suffix[11] = {0};
95 retry:
96     for (unsigned i=0; i < sizeof(suffix)-1; i++) {
97         int c = g_random_int_range(0, 10+26+26), base = '0';
98         if (c >= 10) { base = 'A'; c -= 10; }
99         if (c >= 26) { base = 'a'; c -= 26; }
100         suffix[i] = base + c;
101     }
102     gchar *socket_name = g_strdup_printf("luakit-ipc-%d-%s", getpid(), suffix);
103     gchar *socket_path = g_build_filename(g_get_tmp_dir(), socket_name, NULL);
104     g_free(socket_name);
105 
106     if (g_file_test(socket_path, G_FILE_TEST_EXISTS)) {
107         g_free(socket_path);
108         goto retry;
109     }
110     return socket_path;
111 }
112 
113 static gpointer
web_extension_connect_thread(gpointer UNUSED (data))114 web_extension_connect_thread(gpointer UNUSED(data))
115 {
116     gchar *path = build_socket_path();
117 
118     int sock;
119     if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
120         fatal("Error calling socket(): %s", strerror(errno));
121 
122     struct sockaddr_un local;
123     memset(&local, 0, sizeof(local));
124     local.sun_family = AF_UNIX;
125     strcpy(local.sun_path, path);
126     int len = offsetof(struct sockaddr_un, sun_path) + strlen(local.sun_path);
127 
128     /* Remove any pre-existing socket, before opening */
129     unlink(local.sun_path);
130 
131     if (bind(sock, (struct sockaddr *)&local, len) == -1)
132         fatal("Error calling bind() on socket %s: %s", path, strerror(errno));
133 
134     if (listen(sock, 5) == -1)
135         fatal("Error calling listen() on socket %s: %s", path, strerror(errno));
136 
137     g_mutex_lock(&socket_path_lock);
138     socket_path = path;
139     g_cond_signal(&socket_path_cond);
140     g_mutex_unlock(&socket_path_lock);
141 
142     while (TRUE) {
143         debug("Waiting for a connection...");
144 
145         int web_socket;
146         struct sockaddr_un remote;
147         socklen_t size = sizeof(remote);
148         if ((web_socket = accept(sock, (struct sockaddr *)&remote, &size)) == -1)
149             fatal("Error calling accept(): %s", strerror(errno));
150 
151         ipc_endpoint_t *ipc = ipc_endpoint_new("UI");
152         ipc_endpoint_connect_to_socket(ipc, web_socket);
153     }
154 
155     return NULL;
156 }
157 
158 static void
initialize_web_extensions_cb(WebKitWebContext * context,gpointer UNUSED (data))159 initialize_web_extensions_cb(WebKitWebContext *context, gpointer UNUSED(data))
160 {
161     char *dirs[] = { g_get_current_dir(), LUAKIT_LIB_PATH }, *dir = NULL;
162 
163     for (unsigned i = 0; !dir && i < LENGTH(dirs); ++i) {
164         char *extension_file = g_build_filename(dirs[i],  "luakit.so", NULL);
165         verbose("checking for luakit extension at '%s'", dirs[i]);
166         if (!access(extension_file, R_OK))
167             dir = dirs[i];
168         g_free(extension_file);
169     }
170 
171     if (dir)
172         verbose("found luakit extension at '%s'", dir);
173     else
174         fatal("cannot find luakit extension 'luakit.so'");
175 
176     const char *path;
177     g_mutex_lock (&socket_path_lock);
178     while (!socket_path)
179         g_cond_wait (&socket_path_cond, &socket_path_lock);
180     path = socket_path;
181     g_mutex_unlock (&socket_path_lock);
182 
183     lua_getglobal(common.L, "package");
184     lua_getfield(common.L, -1, "path");
185     const char *package_path = lua_tostring(common.L, -1);
186     lua_getfield(common.L, -2, "cpath");
187     const char *package_cpath = lua_tostring(common.L, -1);
188     lua_pop(common.L, 3);
189 
190     GVariant *payload = g_variant_new("(sss)", path, package_path, package_cpath);
191     webkit_web_context_set_web_extensions_initialization_user_data(context, payload);
192     webkit_web_context_set_web_extensions_directory(context, dir);
193 
194     g_free(dirs[0]);
195 }
196 
197 void
ipc_remove_socket_file(void)198 ipc_remove_socket_file(void)
199 {
200     g_mutex_lock(&socket_path_lock);
201     g_unlink(socket_path);
202     g_free(socket_path);
203     socket_path = NULL;
204     g_mutex_unlock(&socket_path_lock);
205 }
206 
207 void
ipc_init(void)208 ipc_init(void)
209 {
210     /* Start web extension connection accept thread */
211     g_thread_new("accept_thread", web_extension_connect_thread, NULL);
212     g_signal_connect(web_context_get(), "initialize-web-extensions",
213             G_CALLBACK (initialize_web_extensions_cb), NULL);
214     atexit(ipc_remove_socket_file);
215 }
216 
217 // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80
218