1 /*
2 * This file is part of darktable,
3 * Copyright (C) 2015-2020 darktable developers.
4 *
5 * darktable is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * darktable is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <glib/gi18n.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22
23 #include "common/darktable.h"
24 #include "common/http_server.h"
25
26 #ifndef SOUP_CHECK_VERSION
27 // SOUP_CHECK_VERSION was introduced only in 2.42
28 #define SOUP_CHECK_VERSION(x, y, z) false
29 #endif
30
31 #if !SOUP_CHECK_VERSION(2, 48, 0)
32 #define OLD_API
33 #endif
34
35 typedef struct _connection_t
36 {
37 const char *id;
38 dt_http_server_t *server;
39 dt_http_server_callback callback;
40 gpointer user_data;
41 } _connection_t;
42
43 static const char reply[]
44 = "<!DOCTYPE html>\n"
45 "<html>\n"
46 "<head>\n"
47 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
48 "<title>%s</title>\n"
49 "<style>\n"
50 "html {\n"
51 " background-color: #575656;\n"
52 " font-family: \"Lucida Grande\",Verdana,\"Bitstream Vera Sans\",Arial,sans-serif;\n"
53 " font-size: 12px;\n"
54 " padding: 50px 100px 50px 100px;\n"
55 "}\n"
56 "#content {\n"
57 " background-color: #cfcece;\n"
58 " border: 1px solid #000;\n"
59 " padding: 0px 40px 40px 40px;\n"
60 "}\n"
61 "</style>\n"
62 "<script>\n"
63 " if(window.location.hash && %d) {\n"
64 " var hash = window.location.hash.substring(1);\n"
65 " window.location.search = hash;\n"
66 " }\n"
67 "</script>\n"
68 "</head>\n"
69 "<body><div id=\"content\">\n"
70 "<div style=\"font-size: 42pt; font-weight: bold; color: white; text-align: right;\">%s</div>\n"
71 "%s\n"
72 "</div>\n"
73 "</body>\n"
74 "</html>";
75
_request_finished_callback(SoupServer * server,SoupMessage * message,SoupClientContext * client,gpointer user_data)76 static void _request_finished_callback(SoupServer *server, SoupMessage *message, SoupClientContext *client,
77 gpointer user_data)
78 {
79 dt_http_server_kill((dt_http_server_t *)user_data);
80 }
81
82 // this is always in the gui thread
_new_connection(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * client,gpointer user_data)83 static void _new_connection(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query,
84 SoupClientContext *client, gpointer user_data)
85 {
86 _connection_t *params = (_connection_t *)user_data;
87 gboolean res = TRUE;
88
89 if(msg->method != SOUP_METHOD_GET)
90 {
91 soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
92 goto end;
93 }
94
95 char *page_title = g_strdup_printf(_("darktable » %s"), params->id);
96 const char *title = _(params->id);
97 const char *body = _("<h1>Sorry,</h1><p>something went wrong. Please try again.</p>");
98
99 res = params->callback(query, params->user_data);
100
101 if(res)
102 body = _("<h1>Thank you,</h1><p>everything should have worked, you can <b>close</b> your browser now and "
103 "<b>go back</b> to darktable.</p>");
104
105
106 char *resp_body = g_strdup_printf(reply, page_title, res ? 0 : 1, title, body);
107 size_t resp_length = strlen(resp_body);
108 g_free(page_title);
109
110 soup_message_set_status(msg, SOUP_STATUS_OK);
111 soup_message_set_response(msg, "text/html", SOUP_MEMORY_TAKE, resp_body, resp_length);
112
113 end:
114 if(res)
115 {
116 dt_http_server_t *http_server = params->server;
117 soup_server_remove_handler(server, path);
118 g_signal_connect(G_OBJECT(server), "request-finished", G_CALLBACK(_request_finished_callback), http_server);
119 }
120 }
121
dt_http_server_create(const int * ports,const int n_ports,const char * id,const dt_http_server_callback callback,gpointer user_data)122 dt_http_server_t *dt_http_server_create(const int *ports, const int n_ports, const char *id,
123 const dt_http_server_callback callback, gpointer user_data)
124 {
125 SoupServer *httpserver = NULL;
126 int port = 0;
127
128 #ifdef OLD_API
129 dt_print(DT_DEBUG_CONTROL, "[http server] using the old libsoup api\n");
130
131 for(int i = 0; i < n_ports; i++)
132 {
133 port = ports[i];
134
135 SoupAddress *httpaddress = soup_address_new("127.0.0.1", port);
136
137 if(!httpaddress)
138 {
139 fprintf(stderr, "couldn't create libsoup httpaddress on port %d\n", port);
140 return NULL;
141 }
142
143 if(soup_address_resolve_sync(httpaddress, NULL) != SOUP_STATUS_OK)
144 {
145 fprintf(stderr, "error: can't resolve 127.0.0.1:%d\n", port);
146 return NULL;
147 }
148
149 httpserver = soup_server_new(SOUP_SERVER_SERVER_HEADER, "darktable internal server", "interface",
150 httpaddress, NULL);
151
152 if(httpserver) break;
153
154 g_object_unref(httpaddress);
155 }
156
157 if(httpserver == NULL)
158 {
159 fprintf(stderr, "error: couldn't create libsoup httpserver\n");
160 return NULL;
161 }
162
163 #else
164 dt_print(DT_DEBUG_CONTROL, "[http server] using the new libsoup api\n");
165
166 httpserver = soup_server_new(SOUP_SERVER_SERVER_HEADER, "darktable internal server", NULL);
167 if(httpserver == NULL)
168 {
169 fprintf(stderr, "error: couldn't create libsoup httpserver\n");
170 return NULL;
171 }
172
173 for(int i = 0; i < n_ports; i++)
174 {
175 port = ports[i];
176
177 if(soup_server_listen_local(httpserver, port, 0, NULL)) break;
178
179 port = 0;
180 }
181 if(port == 0)
182 {
183 fprintf(stderr, "error: can't bind to any port from our pool\n");
184 return NULL;
185 }
186
187 #endif
188
189 dt_http_server_t *server = (dt_http_server_t *)malloc(sizeof(dt_http_server_t));
190 server->server = httpserver;
191
192 _connection_t *params = (_connection_t *)malloc(sizeof(_connection_t));
193 params->id = id;
194 params->server = server;
195 params->callback = callback;
196 params->user_data = user_data;
197
198 char *path = g_strdup_printf("/%s", id);
199 server->url = g_strdup_printf("http://localhost:%d/%s", port, id);
200
201 soup_server_add_handler(httpserver, path, _new_connection, params, free);
202
203 g_free(path);
204
205 #ifdef OLD_API
206 soup_server_run_async(httpserver);
207 #endif
208
209 dt_print(DT_DEBUG_CONTROL, "[http server] listening on %s\n", server->url);
210
211 return server;
212 }
213
dt_http_server_kill(dt_http_server_t * server)214 void dt_http_server_kill(dt_http_server_t *server)
215 {
216 if(server->server)
217 {
218 soup_server_disconnect(server->server);
219 g_object_unref(server->server);
220 server->server = NULL;
221 }
222 g_free(server->url);
223 server->url = NULL;
224 free(server);
225 }
226
227 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
228 // vim: shiftwidth=2 expandtab tabstop=2 cindent
229 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
230