Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020, 2023, 2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of EXCHANGEABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file mhd_spa.c
18 : * @brief logic to load single page apps
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include "taler/taler_util.h"
24 : #include "taler/taler_mhd_lib.h"
25 : #include <gnunet/gnunet_mhd_compat.h>
26 :
27 :
28 : /**
29 : * Resource from the WebUi.
30 : */
31 : struct WebuiFile
32 : {
33 : /**
34 : * Kept in a DLL.
35 : */
36 : struct WebuiFile *next;
37 :
38 : /**
39 : * Kept in a DLL.
40 : */
41 : struct WebuiFile *prev;
42 :
43 : /**
44 : * Path this resource matches.
45 : */
46 : char *path;
47 :
48 : /**
49 : * SPA resource, deflate compressed.
50 : */
51 : struct MHD_Response *responses[TALER_MHD_CT_MAX];
52 :
53 : };
54 :
55 :
56 : /**
57 : * Resource from the WebUi.
58 : */
59 : struct TALER_MHD_Spa
60 : {
61 : /**
62 : * Resources of the WebUI, kept in a DLL.
63 : */
64 : struct WebuiFile *webui_head;
65 :
66 : /**
67 : * Resources of the WebUI, kept in a DLL.
68 : */
69 : struct WebuiFile *webui_tail;
70 : };
71 :
72 :
73 : MHD_RESULT
74 0 : TALER_MHD_spa_handler (const struct TALER_MHD_Spa *spa,
75 : struct MHD_Connection *connection,
76 : const char *path)
77 : {
78 0 : struct WebuiFile *w = NULL;
79 : enum TALER_MHD_CompressionType comp;
80 :
81 0 : if ( (NULL == path) ||
82 0 : (0 == strcmp (path,
83 : "")) )
84 0 : path = "index.html";
85 0 : for (struct WebuiFile *pos = spa->webui_head;
86 0 : NULL != pos;
87 0 : pos = pos->next)
88 0 : if (0 == strcmp (path,
89 0 : pos->path))
90 : {
91 0 : w = pos;
92 0 : break;
93 : }
94 0 : if ( (NULL == w) ||
95 0 : (NULL == w->responses[TALER_MHD_CT_NONE]) )
96 0 : return TALER_MHD_reply_with_error (connection,
97 : MHD_HTTP_NOT_FOUND,
98 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
99 : path);
100 0 : comp = TALER_MHD_can_compress (connection,
101 : TALER_MHD_CT_MAX - 1);
102 0 : GNUNET_assert (comp < TALER_MHD_CT_MAX);
103 : GNUNET_assert (comp >= 0);
104 0 : if (NULL != w->responses[comp])
105 0 : return MHD_queue_response (connection,
106 : MHD_HTTP_OK,
107 : w->responses[comp]);
108 0 : return MHD_queue_response (connection,
109 : MHD_HTTP_OK,
110 : w->responses[TALER_MHD_CT_NONE]);
111 : }
112 :
113 :
114 : /**
115 : * Function called on each file to load for the WebUI.
116 : *
117 : * @param cls the `struct TALER_MHD_Spa *` to build
118 : * @param dn name of the file to load
119 : */
120 : static enum GNUNET_GenericReturnValue
121 888 : build_webui (void *cls,
122 : const char *dn)
123 : {
124 : static const struct
125 : {
126 : const char *ext;
127 : const char *mime;
128 : } mime_map[] = {
129 : {
130 : .ext = "css",
131 : .mime = "text/css"
132 : },
133 : {
134 : .ext = "html",
135 : .mime = "text/html"
136 : },
137 : {
138 : .ext = "js",
139 : .mime = "text/javascript"
140 : },
141 : {
142 : .ext = "jpg",
143 : .mime = "image/jpeg"
144 : },
145 : {
146 : .ext = "jpeg",
147 : .mime = "image/jpeg"
148 : },
149 : {
150 : .ext = "png",
151 : .mime = "image/png"
152 : },
153 : {
154 : .ext = "svg",
155 : .mime = "image/svg+xml"
156 : },
157 : {
158 : .ext = NULL,
159 : .mime = NULL
160 : },
161 : };
162 888 : struct TALER_MHD_Spa *spa = cls;
163 : int fd;
164 : struct stat sb;
165 : struct MHD_Response *response;
166 : char *ext;
167 : const char *mime;
168 : const char *slash;
169 : char *fn;
170 888 : const char *cts = NULL;
171 888 : enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE;
172 :
173 888 : slash = strrchr (dn, '/');
174 888 : if (NULL == slash)
175 : {
176 0 : GNUNET_break (0);
177 0 : return GNUNET_SYSERR;
178 : }
179 888 : fd = open (dn,
180 : O_RDONLY);
181 888 : if (-1 == fd)
182 : {
183 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
184 : "open",
185 : dn);
186 0 : return GNUNET_SYSERR;
187 : }
188 888 : if (0 !=
189 888 : fstat (fd,
190 : &sb))
191 : {
192 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
193 : "open",
194 : dn);
195 0 : GNUNET_break (0 == close (fd));
196 0 : return GNUNET_SYSERR;
197 : }
198 :
199 888 : fn = GNUNET_strdup (slash + 1);
200 888 : ext = strrchr (fn,
201 : '.');
202 888 : if (NULL == ext)
203 : {
204 80 : GNUNET_break (0 == close (fd));
205 80 : GNUNET_free (fn);
206 80 : return GNUNET_OK;
207 : }
208 808 : if (0 == strcmp (ext,
209 : ".gz"))
210 : {
211 8 : cts = "gzip";
212 8 : ct = TALER_MHD_CT_GZIP;
213 8 : *ext = '\0';
214 8 : ext = strrchr (fn, '.');
215 : }
216 808 : if (0 == strcmp (ext,
217 : ".zstd"))
218 : {
219 8 : cts = "zstd";
220 8 : ct = TALER_MHD_CT_ZSTD;
221 8 : *ext = '\0';
222 8 : ext = strrchr (fn, '.');
223 : }
224 808 : if (NULL == ext)
225 : {
226 0 : GNUNET_break (0 == close (fd));
227 0 : GNUNET_free (fn);
228 0 : return GNUNET_OK;
229 : }
230 808 : ext++;
231 :
232 808 : mime = NULL;
233 4846 : for (unsigned int i = 0; NULL != mime_map[i].ext; i++)
234 4372 : if (0 == strcasecmp (ext,
235 4372 : mime_map[i].ext))
236 : {
237 334 : mime = mime_map[i].mime;
238 334 : break;
239 : }
240 :
241 808 : response = MHD_create_response_from_fd (
242 808 : sb.st_size,
243 : fd /* FD now owned by MHD! */);
244 808 : if (NULL == response)
245 : {
246 0 : GNUNET_free (fn);
247 0 : return GNUNET_SYSERR;
248 : }
249 824 : if ( (NULL != cts) &&
250 : (MHD_NO ==
251 16 : MHD_add_response_header (response,
252 : MHD_HTTP_HEADER_CONTENT_ENCODING,
253 : cts)) )
254 : {
255 0 : GNUNET_break (0);
256 0 : MHD_destroy_response (response);
257 0 : GNUNET_free (fn);
258 0 : return GNUNET_SYSERR;
259 : }
260 808 : if (NULL != mime)
261 : {
262 334 : GNUNET_break (MHD_YES ==
263 : MHD_add_response_header (response,
264 : MHD_HTTP_HEADER_CONTENT_TYPE,
265 : mime));
266 : }
267 :
268 : {
269 : struct WebuiFile *w;
270 :
271 808 : for (w = spa->webui_head;
272 4812 : NULL != w;
273 4004 : w = w->next)
274 : {
275 4020 : if (0 == strcmp (fn,
276 4020 : w->path))
277 16 : break;
278 : }
279 808 : if (NULL == w)
280 : {
281 792 : w = GNUNET_new (struct WebuiFile);
282 792 : w->path = fn;
283 792 : GNUNET_CONTAINER_DLL_insert (spa->webui_head,
284 : spa->webui_tail,
285 : w);
286 : }
287 : else
288 : {
289 16 : GNUNET_free (fn);
290 : }
291 808 : GNUNET_assert (NULL == w->responses[ct]);
292 808 : w->responses[ct] = response;
293 : }
294 808 : return GNUNET_OK;
295 : }
296 :
297 :
298 : struct TALER_MHD_Spa *
299 80 : TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd,
300 : const char *dir)
301 : {
302 : struct TALER_MHD_Spa *spa;
303 : char *dn;
304 :
305 : {
306 : char *path;
307 :
308 80 : path = GNUNET_OS_installation_get_path (pd,
309 : GNUNET_OS_IPK_DATADIR);
310 80 : if (NULL == path)
311 : {
312 0 : GNUNET_break (0);
313 0 : return NULL;
314 : }
315 80 : GNUNET_asprintf (&dn,
316 : "%s%s",
317 : path,
318 : dir);
319 80 : GNUNET_free (path);
320 : }
321 80 : spa = GNUNET_new (struct TALER_MHD_Spa);
322 80 : if (-1 ==
323 80 : GNUNET_DISK_directory_scan (dn,
324 : &build_webui,
325 : spa))
326 : {
327 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
328 : "Failed to load WebUI from `%s'\n",
329 : dn);
330 0 : GNUNET_free (dn);
331 0 : TALER_MHD_spa_free (spa);
332 0 : return NULL;
333 : }
334 80 : GNUNET_free (dn);
335 80 : return spa;
336 : }
337 :
338 :
339 : void
340 80 : TALER_MHD_spa_free (struct TALER_MHD_Spa *spa)
341 : {
342 : struct WebuiFile *w;
343 :
344 872 : while (NULL != (w = spa->webui_head))
345 : {
346 792 : GNUNET_CONTAINER_DLL_remove (spa->webui_head,
347 : spa->webui_tail,
348 : w);
349 792 : for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE;
350 3960 : ct < TALER_MHD_CT_MAX;
351 3168 : ct++)
352 : {
353 3168 : if (NULL != w->responses[ct])
354 : {
355 808 : MHD_destroy_response (w->responses[ct]);
356 808 : w->responses[ct] = NULL;
357 : }
358 : }
359 792 : GNUNET_free (w->path);
360 792 : GNUNET_free (w);
361 : }
362 80 : GNUNET_free (spa);
363 80 : }
|