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