Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
http_server.c
Go to the documentation of this file.
1/*
2 * This file is part of darktable,
3 * Copyright (C) 2015-2016 Roman Lebedev.
4 * Copyright (C) 2015-2016 Tobias Ellinghaus.
5 * Copyright (C) 2020 Pascal Obry.
6 * Copyright (C) 2022 Aurélien PIERRE.
7 * Copyright (C) 2022 Martin Bařinka.
8 * Copyright (C) 2023 Luca Zulberti.
9 * Copyright (C) 2023 Maurizio Paglia.
10 * Copyright (C) 2025 starapo7348.
11 *
12 * darktable is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * darktable is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26#include <glib/gi18n.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <libsoup/soup.h>
30#include "common/darktable.h"
31#include "common/http_server.h"
32#ifndef LIBSOUP_VERSION_MAJOR
33#error "LIBSOUP_VERSION_MAJOR not defined by CMake"
34#endif
35#if LIBSOUP_VERSION_MAJOR >= 3
36#define LIBSOUP3
37#else
38#define LIBSOUP2
39#endif
40
48
49static const char reply[]
50 = "<!DOCTYPE html>\n"
51 "<html>\n"
52 "<head>\n"
53 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
54 "<title>%s</title>\n"
55 "<style>\n"
56 "html {\n"
57 " background-color: #575656;\n"
58 " font-family: \"Lucida Grande\",Verdana,\"Bitstream Vera Sans\",Arial,sans-serif;\n"
59 " font-size: 12px;\n"
60 " padding: 50px 100px 50px 100px;\n"
61 "}\n"
62 "#content {\n"
63 " background-color: #cfcece;\n"
64 " border: 1px solid #000;\n"
65 " padding: 0px 40px 40px 40px;\n"
66 "}\n"
67 "</style>\n"
68 "<script>\n"
69 " if(window.location.hash && %d) {\n"
70 " var hash = window.location.hash.substring(1);\n"
71 " window.location.search = hash;\n"
72 " }\n"
73 "</script>\n"
74 "</head>\n"
75 "<body><div id=\"content\">\n"
76 "<div style=\"font-size: 42pt; font-weight: bold; color: white; text-align: right;\">%s</div>\n"
77 "%s\n"
78 "</div>\n"
79 "</body>\n"
80 "</html>";
81
82// Libsoup3: Delayed kill helper
83#ifdef LIBSOUP3
84static gboolean _delayed_kill(gpointer user_data)
85{
87 return G_SOURCE_REMOVE;
88}
89#endif
90
91#ifdef LIBSOUP2
92// Libsoup2 callbacks
93static void _request_finished_callback(SoupServer *server, SoupMessage *message, SoupClientContext *client,
94 gpointer user_data)
95{
97}
98
99static void _new_connection(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query,
100 SoupClientContext *client, gpointer user_data)
101{
102 _connection_t *params = (_connection_t *)user_data;
103 gboolean res = TRUE;
104
105 if(msg->method != SOUP_METHOD_GET)
106 {
107 soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
108 goto end;
109 }
110
111 char *page_title = g_strdup_printf(_("ansel >> %s"), params->id);
112 const char *title = _(params->id);
113 const char *body = _("<h1>Sorry,</h1><p>something went wrong. Please try again.</p>");
114
115 res = params->callback(query, params->user_data);
116
117 if(res)
118 body = _("<h1>Thank you,</h1><p>everything should have worked, you can <b>close</b> your browser now and "
119 "<b>go back</b> to Ansel.</p>");
120
121 char *resp_body = g_strdup_printf(reply, page_title, res ? 0 : 1, title, body);
122 size_t resp_length = strlen(resp_body);
123 dt_free(page_title);
124
125 soup_message_set_status(msg, SOUP_STATUS_OK);
126 soup_message_set_response(msg, "text/html", SOUP_MEMORY_TAKE, resp_body, resp_length);
127
128end:
129 if(res)
130 {
131 dt_http_server_t *http_server = params->server;
132 soup_server_remove_handler(server, path);
133 g_signal_connect(G_OBJECT(server), "request-finished", G_CALLBACK(_request_finished_callback), http_server);
134 }
135}
136#endif // LIBSOUP2
137
138#ifdef LIBSOUP3
139// Libsoup3 callbacks
140static void _new_connection(SoupServer *server, SoupServerMessage *msg, gpointer user_data)
141{
142 _connection_t *params = (_connection_t *)user_data;
143
144 if(g_strcmp0(soup_server_message_get_method(msg), SOUP_METHOD_GET) != 0)
145 {
146 soup_server_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED, "Not Implemented");
147 return;
148 }
149
150 GHashTable *query = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, dt_free_gpointer);
151
152 gboolean res = params->callback(query, params->user_data);
153 g_hash_table_unref(query);
154
155 const char *body = res ?
156 _("<h1>Thank you,</h1><p>everything should have worked, you can <b>close</b> your browser now and "
157 "<b>go back</b> to Ansel.</p>") :
158 _("<h1>Sorry,</h1><p>something went wrong. Please try again.</p>");
159
160 char *page_title = g_strdup_printf(_("ansel >> %s"), params->id);
161 char *resp_body = g_strdup_printf(reply, page_title, res ? 0 : 1, _(params->id), body);
162 dt_free(page_title);
163
164 soup_server_message_set_status(msg, SOUP_STATUS_OK, "OK");
165 soup_server_message_set_response(msg, "text/html; charset=utf-8",
166 SOUP_MEMORY_TAKE, resp_body, strlen(resp_body));
167
168 if(res)
169 g_timeout_add(100, _delayed_kill, params->server);
170}
171#endif // LIBSOUP3
172
173dt_http_server_t *dt_http_server_create(const int *ports, const int n_ports, const char *id,
174 const dt_http_server_callback callback, gpointer user_data)
175{
176 SoupServer *httpserver = NULL;
177 int port = 0;
178
179#ifdef LIBSOUP2
180
181 dt_print(DT_DEBUG_CONTROL, "[http server] using libsoup2\n");
182
183 httpserver = soup_server_new(SOUP_SERVER_SERVER_HEADER, "ansel internal server", NULL);
184 if(IS_NULL_PTR(httpserver))
185 {
186 fprintf(stderr, "error: couldn't create libsoup httpserver\n");
187 return NULL;
188 }
189
190 for(int i = 0; i < n_ports; i++)
191 {
192 port = ports[i];
193 if(soup_server_listen_local(httpserver, port, 0, NULL)) break;
194 port = 0;
195 }
196 if(port == 0)
197 {
198 g_object_unref(httpserver);
199 fprintf(stderr, "error: can't bind to any port from our pool\n");
200 return NULL;
201 }
202
203#ifdef LIBSOUP2
204
205#elif defined(LIBSOUP3)
206
207SoupServerListener *listener = soup_server_get_listener(httpserver);
208if (listener) {
209 GMainContext *ctx = g_main_context_default();
210 soup_server_listener_set_context(listener, ctx);
211}
212soup_server_run(httpserver);
213#endif
214
215#elif defined(LIBSOUP3)
216 // Libsoup3
217 dt_print(DT_DEBUG_CONTROL, "[http server] using libsoup3\n");
218
219 httpserver = soup_server_new("server-header", "ansel internal server", NULL);
220 if(IS_NULL_PTR(httpserver))
221 {
222 fprintf(stderr, "error: couldn't create libsoup httpserver\n");
223 return NULL;
224 }
225
226 for(int i = 0; i < n_ports; i++)
227 {
228 port = ports[i];
229 if(soup_server_listen_local(httpserver, port, SOUP_SERVER_LISTEN_IPV4_ONLY, NULL)) break;
230 port = 0;
231 }
232 if(port == 0)
233 {
234 g_object_unref(httpserver);
235 fprintf(stderr, "error: can't bind to any port from our pool\n");
236 return NULL;
237 }
238#endif
239
240 dt_http_server_t *server = g_new0(dt_http_server_t, 1);
241 server->server = httpserver;
242
243 _connection_t *params = g_new0(_connection_t, 1);
244 params->id = id;
245 params->server = server;
246 params->callback = callback;
247 params->user_data = user_data;
248
249 char *path = g_strdup_printf("/%s", id);
250 server->url = g_strdup_printf("http://localhost:%d%s", port, path);
251
252#ifdef LIBSOUP2
253 soup_server_add_handler(httpserver, path, _new_connection, params, (GDestroyNotify)free);
254#else
255 soup_server_add_early_handler(httpserver, path, (SoupServerCallback)_new_connection, params, (GDestroyNotify)g_free);
256#endif
257
258 dt_free(path);
259
260 dt_print(DT_DEBUG_CONTROL, "[http server] listening on %s\n", server->url);
261 return server;
262}
263
265{
266 if(server && server->server)
267 {
268 soup_server_disconnect(server->server);
269 g_object_unref(server->server);
270 server->server = NULL;
271 }
272 dt_free(server->url);
273 dt_free(server);
274}
275
276// clang-format off
277// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
278// vim: shiftwidth=2 expandtab tabstop=2 cindent
279// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
280// clang-format on
#define TRUE
Definition ashift_lsd.c:162
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_CONTROL
Definition darktable.h:716
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
void dt_http_server_kill(dt_http_server_t *server)
static void _request_finished_callback(SoupServer *server, SoupMessage *message, SoupClientContext *client, gpointer user_data)
Definition http_server.c:93
static void _new_connection(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *client, gpointer user_data)
Definition http_server.c:99
static const char reply[]
Definition http_server.c:50
dt_http_server_t * dt_http_server_create(const int *ports, const int n_ports, const char *id, const dt_http_server_callback callback, gpointer user_data)
gboolean(* dt_http_server_callback)(GHashTable *query, gpointer user_data)
Definition http_server.h:28
const char * id
Definition http_server.c:43
gpointer user_data
Definition http_server.c:46
dt_http_server_callback callback
Definition http_server.c:45
dt_http_server_t * server
Definition http_server.c:44
SoupServer * server
Definition http_server.h:32