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 358 : 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 358 : 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 358 : const char *cts = NULL;
171 358 : enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE;
172 :
173 358 : slash = strrchr (dn, '/');
174 358 : if (NULL == slash)
175 : {
176 0 : GNUNET_break (0);
177 0 : return GNUNET_SYSERR;
178 : }
179 358 : fd = open (dn,
180 : O_RDONLY);
181 358 : 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 358 : if (0 !=
189 358 : 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 358 : fn = GNUNET_strdup (slash + 1);
200 358 : ext = strrchr (fn,
201 : '.');
202 358 : if (NULL == ext)
203 : {
204 41 : GNUNET_break (0 == close (fd));
205 41 : GNUNET_free (fn);
206 41 : return GNUNET_OK;
207 : }
208 317 : if (0 == strcmp (ext,
209 : ".gz"))
210 : {
211 0 : cts = "gzip";
212 0 : ct = TALER_MHD_CT_GZIP;
213 0 : *ext = '\0';
214 0 : ext = strrchr (fn, '.');
215 : }
216 317 : if (0 == strcmp (ext,
217 : ".zstd"))
218 : {
219 0 : cts = "zstd";
220 0 : ct = TALER_MHD_CT_ZSTD;
221 0 : *ext = '\0';
222 0 : ext = strrchr (fn, '.');
223 : }
224 317 : if (NULL == ext)
225 : {
226 0 : GNUNET_break (0 == close (fd));
227 0 : GNUNET_free (fn);
228 0 : return GNUNET_OK;
229 : }
230 317 : ext++;
231 :
232 317 : mime = NULL;
233 1788 : for (unsigned int i = 0; NULL != mime_map[i].ext; i++)
234 1604 : if (0 == strcasecmp (ext,
235 1604 : mime_map[i].ext))
236 : {
237 133 : mime = mime_map[i].mime;
238 133 : break;
239 : }
240 :
241 317 : response = MHD_create_response_from_fd (
242 317 : sb.st_size,
243 : fd /* FD now owned by MHD! */);
244 317 : if (NULL == response)
245 : {
246 0 : GNUNET_free (fn);
247 0 : return GNUNET_SYSERR;
248 : }
249 317 : if ( (NULL != cts) &&
250 : (MHD_NO ==
251 0 : 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 317 : if (NULL != mime)
261 : {
262 133 : 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 317 : for (w = spa->webui_head;
272 1463 : NULL != w;
273 1146 : w = w->next)
274 : {
275 1146 : if (0 == strcmp (fn,
276 1146 : w->path))
277 0 : break;
278 : }
279 317 : if (NULL == w)
280 : {
281 317 : w = GNUNET_new (struct WebuiFile);
282 317 : w->path = fn;
283 317 : GNUNET_CONTAINER_DLL_insert (spa->webui_head,
284 : spa->webui_tail,
285 : w);
286 : }
287 : else
288 : {
289 0 : GNUNET_free (fn);
290 : }
291 317 : GNUNET_assert (NULL == w->responses[ct]);
292 317 : w->responses[ct] = response;
293 : }
294 317 : return GNUNET_OK;
295 : }
296 :
297 :
298 : struct TALER_MHD_Spa *
299 41 : TALER_MHD_spa_load_dir (const char *dn)
300 : {
301 : struct TALER_MHD_Spa *spa;
302 :
303 41 : spa = GNUNET_new (struct TALER_MHD_Spa);
304 41 : if (-1 ==
305 41 : GNUNET_DISK_directory_scan (dn,
306 : &build_webui,
307 : spa))
308 : {
309 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
310 : "Failed to load WebUI from `%s'\n",
311 : dn);
312 0 : TALER_MHD_spa_free (spa);
313 0 : return NULL;
314 : }
315 41 : return spa;
316 : }
317 :
318 :
319 : struct TALER_MHD_Spa *
320 41 : TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd,
321 : const char *dir)
322 : {
323 : struct TALER_MHD_Spa *spa;
324 : char *dn;
325 : char *path;
326 :
327 41 : path = GNUNET_OS_installation_get_path (pd,
328 : GNUNET_OS_IPK_DATADIR);
329 41 : if (NULL == path)
330 : {
331 0 : GNUNET_break (0);
332 0 : return NULL;
333 : }
334 41 : GNUNET_asprintf (&dn,
335 : "%s%s",
336 : path,
337 : dir);
338 41 : GNUNET_free (path);
339 41 : spa = TALER_MHD_spa_load_dir (dn);
340 41 : GNUNET_free (dn);
341 41 : return spa;
342 : }
343 :
344 :
345 : void
346 41 : TALER_MHD_spa_free (struct TALER_MHD_Spa *spa)
347 : {
348 : struct WebuiFile *w;
349 :
350 358 : while (NULL != (w = spa->webui_head))
351 : {
352 317 : GNUNET_CONTAINER_DLL_remove (spa->webui_head,
353 : spa->webui_tail,
354 : w);
355 317 : for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE;
356 1585 : ct < TALER_MHD_CT_MAX;
357 1268 : ct++)
358 : {
359 1268 : if (NULL != w->responses[ct])
360 : {
361 317 : MHD_destroy_response (w->responses[ct]);
362 317 : w->responses[ct] = NULL;
363 : }
364 : }
365 317 : GNUNET_free (w->path);
366 317 : GNUNET_free (w);
367 : }
368 41 : GNUNET_free (spa);
369 41 : }
|