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 "platform.h" /* UNNECESSARY? */
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 : * mmap()'ed file contents served by the respective @e responses,
55 : * NULL for empty files. Use munmap() to release.
56 : */
57 : void *maps[TALER_MHD_CT_MAX];
58 :
59 : /**
60 : * Sizes of the respective @e maps.
61 : */
62 : size_t map_sizes[TALER_MHD_CT_MAX];
63 :
64 : };
65 :
66 :
67 : /**
68 : * Resource from the WebUi.
69 : */
70 : struct TALER_MHD_Spa
71 : {
72 : /**
73 : * Resources of the WebUI, kept in a DLL.
74 : */
75 : struct WebuiFile *webui_head;
76 :
77 : /**
78 : * Resources of the WebUI, kept in a DLL.
79 : */
80 : struct WebuiFile *webui_tail;
81 : };
82 :
83 :
84 : enum MHD_Result
85 0 : TALER_MHD_spa_handler (const struct TALER_MHD_Spa *spa,
86 : struct MHD_Connection *connection,
87 : const char *path)
88 : {
89 0 : struct WebuiFile *w = NULL;
90 : enum TALER_MHD_CompressionType comp;
91 :
92 0 : if ( (NULL == path) ||
93 0 : (0 == strcmp (path,
94 : "")) )
95 0 : path = "index.html";
96 0 : for (struct WebuiFile *pos = spa->webui_head;
97 0 : NULL != pos;
98 0 : pos = pos->next)
99 0 : if (0 == strcmp (path,
100 0 : pos->path))
101 : {
102 0 : w = pos;
103 0 : break;
104 : }
105 0 : if ( (NULL == w) ||
106 0 : (NULL == w->responses[TALER_MHD_CT_NONE]) )
107 0 : return TALER_MHD_reply_with_error (connection,
108 : MHD_HTTP_NOT_FOUND,
109 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
110 : path);
111 0 : comp = TALER_MHD_can_compress (connection,
112 : TALER_MHD_CT_MAX - 1);
113 0 : GNUNET_assert (comp < TALER_MHD_CT_MAX);
114 : GNUNET_assert (comp >= 0);
115 0 : if (NULL != w->responses[comp])
116 0 : return MHD_queue_response (connection,
117 : MHD_HTTP_OK,
118 : w->responses[comp]);
119 0 : return MHD_queue_response (connection,
120 : MHD_HTTP_OK,
121 : w->responses[TALER_MHD_CT_NONE]);
122 : }
123 :
124 :
125 : /**
126 : * Function called on each file to load for the WebUI.
127 : *
128 : * @param cls the `struct TALER_MHD_Spa *` to build
129 : * @param dn name of the file to load
130 : */
131 : static enum GNUNET_GenericReturnValue
132 45 : build_webui (void *cls,
133 : const char *dn)
134 : {
135 : static const struct
136 : {
137 : const char *ext;
138 : const char *mime;
139 : } mime_map[] = {
140 : {
141 : .ext = "css",
142 : .mime = "text/css"
143 : },
144 : {
145 : .ext = "html",
146 : .mime = "text/html"
147 : },
148 : {
149 : .ext = "js",
150 : .mime = "text/javascript"
151 : },
152 : {
153 : .ext = "jpg",
154 : .mime = "image/jpeg"
155 : },
156 : {
157 : .ext = "jpeg",
158 : .mime = "image/jpeg"
159 : },
160 : {
161 : .ext = "png",
162 : .mime = "image/png"
163 : },
164 : {
165 : .ext = "svg",
166 : .mime = "image/svg+xml"
167 : },
168 : {
169 : .ext = NULL,
170 : .mime = NULL
171 : },
172 : };
173 45 : struct TALER_MHD_Spa *spa = cls;
174 : int fd;
175 : struct stat sb;
176 : size_t size;
177 : void *buf;
178 : struct MHD_Response *response;
179 : char *ext;
180 : const char *mime;
181 : const char *slash;
182 : char *fn;
183 45 : const char *cts = NULL;
184 45 : enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE;
185 :
186 45 : slash = strrchr (dn, '/');
187 45 : if (NULL == slash)
188 : {
189 0 : GNUNET_break (0);
190 0 : return GNUNET_SYSERR;
191 : }
192 45 : fd = open (dn,
193 : O_RDONLY);
194 45 : if (-1 == fd)
195 : {
196 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
197 : "open",
198 : dn);
199 0 : return GNUNET_SYSERR;
200 : }
201 45 : if (0 !=
202 45 : fstat (fd,
203 : &sb))
204 : {
205 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
206 : "open",
207 : dn);
208 0 : GNUNET_break (0 == close (fd));
209 0 : return GNUNET_SYSERR;
210 : }
211 : if (SIZE_MAX < ((unsigned long long) sb.st_size))
212 : {
213 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
214 : "fstat-size",
215 : dn);
216 : GNUNET_break (0 == close (fd));
217 : return GNUNET_SYSERR;
218 : }
219 45 : size = (size_t) sb.st_size;
220 45 : buf = NULL;
221 45 : if (0 != size)
222 : {
223 45 : buf = mmap (NULL,
224 : size,
225 : PROT_READ,
226 : MAP_SHARED,
227 : fd,
228 : 0);
229 45 : if (MAP_FAILED == buf)
230 : {
231 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
232 : "mmap",
233 : dn);
234 0 : GNUNET_break (0 == close (fd));
235 0 : return GNUNET_SYSERR;
236 : }
237 : }
238 : /* The mapping stays valid after the descriptor is closed; do not
239 : hold one descriptor per file for the lifetime of the process. */
240 45 : GNUNET_break (0 == close (fd));
241 :
242 45 : fn = GNUNET_strdup (slash + 1);
243 45 : ext = strrchr (fn,
244 : '.');
245 45 : if (NULL == ext)
246 0 : goto skip;
247 45 : if (0 == strcmp (ext,
248 : ".gz"))
249 : {
250 0 : cts = "gzip";
251 0 : ct = TALER_MHD_CT_GZIP;
252 0 : *ext = '\0';
253 0 : ext = strrchr (fn, '.');
254 0 : if (NULL == ext)
255 0 : goto skip;
256 : }
257 45 : if (0 == strcmp (ext,
258 : ".zstd"))
259 : {
260 0 : cts = "zstd";
261 0 : ct = TALER_MHD_CT_ZSTD;
262 0 : *ext = '\0';
263 0 : ext = strrchr (fn, '.');
264 0 : if (NULL == ext)
265 0 : goto skip;
266 : }
267 45 : ext++;
268 :
269 45 : mime = NULL;
270 90 : for (unsigned int i = 0; NULL != mime_map[i].ext; i++)
271 90 : if (0 == strcasecmp (ext,
272 90 : mime_map[i].ext))
273 : {
274 45 : mime = mime_map[i].mime;
275 45 : break;
276 : }
277 :
278 45 : response = MHD_create_response_from_buffer_static (size,
279 : buf);
280 45 : if (NULL == response)
281 : {
282 0 : GNUNET_break (0);
283 0 : if (NULL != buf)
284 0 : GNUNET_break (0 == munmap (buf,
285 : size));
286 0 : GNUNET_free (fn);
287 0 : return GNUNET_SYSERR;
288 : }
289 45 : if ( (NULL != cts) &&
290 : (MHD_NO ==
291 0 : MHD_add_response_header (response,
292 : MHD_HTTP_HEADER_CONTENT_ENCODING,
293 : cts)) )
294 : {
295 0 : GNUNET_break (0);
296 0 : MHD_destroy_response (response);
297 0 : if (NULL != buf)
298 0 : GNUNET_break (0 == munmap (buf,
299 : size));
300 0 : GNUNET_free (fn);
301 0 : return GNUNET_SYSERR;
302 : }
303 45 : if (NULL != mime)
304 : {
305 45 : GNUNET_break (MHD_YES ==
306 : MHD_add_response_header (response,
307 : MHD_HTTP_HEADER_CONTENT_TYPE,
308 : mime));
309 : }
310 : /* Which representation we return depends on the Accept-Encoding
311 : header of the client, so shared caches must not serve one
312 : variant to a client that asked for another. */
313 45 : GNUNET_break (MHD_YES ==
314 : MHD_add_response_header (response,
315 : MHD_HTTP_HEADER_VARY,
316 : MHD_HTTP_HEADER_ACCEPT_ENCODING));
317 :
318 : {
319 : struct WebuiFile *w;
320 :
321 45 : for (w = spa->webui_head;
322 45 : NULL != w;
323 0 : w = w->next)
324 : {
325 0 : if (0 == strcmp (fn,
326 0 : w->path))
327 0 : break;
328 : }
329 45 : if (NULL == w)
330 : {
331 45 : w = GNUNET_new (struct WebuiFile);
332 45 : w->path = fn;
333 45 : GNUNET_CONTAINER_DLL_insert (spa->webui_head,
334 : spa->webui_tail,
335 : w);
336 : }
337 : else
338 : {
339 0 : GNUNET_free (fn);
340 : }
341 45 : GNUNET_assert (NULL == w->responses[ct]);
342 45 : w->responses[ct] = response;
343 45 : w->maps[ct] = buf;
344 45 : w->map_sizes[ct] = size;
345 : }
346 45 : return GNUNET_OK;
347 0 : skip:
348 0 : if (NULL != buf)
349 0 : GNUNET_break (0 == munmap (buf,
350 : size));
351 0 : GNUNET_free (fn);
352 0 : return GNUNET_OK;
353 : }
354 :
355 :
356 : struct TALER_MHD_Spa *
357 45 : TALER_MHD_spa_load_dir (const char *dn)
358 : {
359 : struct TALER_MHD_Spa *spa;
360 :
361 45 : spa = GNUNET_new (struct TALER_MHD_Spa);
362 45 : if (-1 ==
363 45 : GNUNET_DISK_directory_scan (dn,
364 : &build_webui,
365 : spa))
366 : {
367 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
368 : "Failed to load WebUI from `%s'\n",
369 : dn);
370 0 : TALER_MHD_spa_free (spa);
371 0 : return NULL;
372 : }
373 45 : return spa;
374 : }
375 :
376 :
377 : struct TALER_MHD_Spa *
378 45 : TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd,
379 : const char *dir)
380 : {
381 : struct TALER_MHD_Spa *spa;
382 : char *dn;
383 : char *path;
384 :
385 45 : path = GNUNET_OS_installation_get_path (pd,
386 : GNUNET_OS_IPK_DATADIR);
387 45 : if (NULL == path)
388 : {
389 0 : GNUNET_break (0);
390 0 : return NULL;
391 : }
392 45 : GNUNET_asprintf (&dn,
393 : "%s%s",
394 : path,
395 : dir);
396 45 : GNUNET_free (path);
397 45 : spa = TALER_MHD_spa_load_dir (dn);
398 45 : GNUNET_free (dn);
399 45 : return spa;
400 : }
401 :
402 :
403 : void
404 45 : TALER_MHD_spa_free (struct TALER_MHD_Spa *spa)
405 : {
406 : struct WebuiFile *w;
407 :
408 90 : while (NULL != (w = spa->webui_head))
409 : {
410 45 : GNUNET_CONTAINER_DLL_remove (spa->webui_head,
411 : spa->webui_tail,
412 : w);
413 45 : for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE;
414 225 : ct < TALER_MHD_CT_MAX;
415 180 : ct++)
416 : {
417 180 : if (NULL != w->responses[ct])
418 : {
419 45 : MHD_destroy_response (w->responses[ct]);
420 45 : w->responses[ct] = NULL;
421 : }
422 180 : if (NULL != w->maps[ct])
423 : {
424 45 : GNUNET_break (0 == munmap (w->maps[ct],
425 : w->map_sizes[ct]));
426 45 : w->maps[ct] = NULL;
427 : }
428 : }
429 45 : GNUNET_free (w->path);
430 45 : GNUNET_free (w);
431 : }
432 45 : GNUNET_free (spa);
433 45 : }
|