Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020, 2022 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 MERCHANTABILITY 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 templating_api.c
18 : * @brief logic to load and complete HTML templates
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 "taler/taler_templating_lib.h"
26 : #include "mustach.h"
27 : #include "mustach-jansson.h"
28 : #include <gnunet/gnunet_mhd_compat.h>
29 :
30 :
31 : /**
32 : * Entry in a key-value array we use to cache templates.
33 : */
34 : struct TVE
35 : {
36 : /**
37 : * A name, used as the key. NULL for the last entry.
38 : */
39 : char *name;
40 :
41 : /**
42 : * Language the template is in.
43 : */
44 : char *lang;
45 :
46 : /**
47 : * 0-terminated (!) file data to return for @e name and @e lang.
48 : */
49 : char *value;
50 :
51 : };
52 :
53 :
54 : /**
55 : * Array of templates loaded into RAM.
56 : */
57 : static struct TVE *loaded;
58 :
59 : /**
60 : * Length of the #loaded array.
61 : */
62 : static unsigned int loaded_length;
63 :
64 :
65 : /**
66 : * Load Mustach template into memory. Note that we intentionally cache
67 : * failures, that is if we ever failed to load a template, we will never try
68 : * again.
69 : *
70 : * @param connection the connection we act upon
71 : * @param name name of the template file to load
72 : * (MUST be a 'static' string in memory!)
73 : * @return NULL on error, otherwise the template
74 : */
75 : static struct TVE *
76 3 : lookup_template (struct MHD_Connection *connection,
77 : const char *name)
78 : {
79 3 : struct TVE *best = NULL;
80 3 : double best_q = 0.0;
81 : const char *lang;
82 :
83 3 : lang = MHD_lookup_connection_value (connection,
84 : MHD_HEADER_KIND,
85 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
86 3 : if (NULL == lang)
87 3 : lang = "en";
88 : /* find best match by language */
89 63 : for (unsigned int i = 0; i<loaded_length; i++)
90 : {
91 : double q;
92 :
93 60 : if (0 != strcmp (loaded[i].name,
94 : name))
95 57 : continue; /* does not match by name */
96 3 : if (NULL == loaded[i].lang) /* no language == always best match */
97 0 : return &loaded[i];
98 3 : q = TALER_pattern_matches (lang,
99 3 : loaded[i].lang);
100 3 : if (q < best_q)
101 0 : continue;
102 3 : best_q = q;
103 3 : best = &loaded[i];
104 : }
105 3 : if (NULL == best)
106 : {
107 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
108 : "No templates found for `%s'\n",
109 : name);
110 0 : return NULL;
111 : }
112 3 : return best;
113 : }
114 :
115 :
116 : /**
117 : * Get the base URL for static resources.
118 : *
119 : * @param con the MHD connection
120 : * @param instance_id the instance ID
121 : * @returns the static files base URL, guaranteed
122 : * to have a trailing slash.
123 : */
124 : static char *
125 0 : make_static_url (struct MHD_Connection *con,
126 : const char *instance_id)
127 : {
128 : const char *host;
129 : const char *forwarded_host;
130 : const char *uri_path;
131 0 : struct GNUNET_Buffer buf = { 0 };
132 :
133 0 : host = MHD_lookup_connection_value (con,
134 : MHD_HEADER_KIND,
135 : "Host");
136 0 : forwarded_host = MHD_lookup_connection_value (con,
137 : MHD_HEADER_KIND,
138 : "X-Forwarded-Host");
139 :
140 0 : uri_path = MHD_lookup_connection_value (con,
141 : MHD_HEADER_KIND,
142 : "X-Forwarded-Prefix");
143 0 : if (NULL != forwarded_host)
144 0 : host = forwarded_host;
145 :
146 0 : if (NULL == host)
147 : {
148 0 : GNUNET_break (0);
149 0 : return NULL;
150 : }
151 :
152 0 : GNUNET_assert (NULL != instance_id);
153 :
154 0 : if (GNUNET_NO == TALER_mhd_is_https (con))
155 0 : GNUNET_buffer_write_str (&buf,
156 : "http://");
157 : else
158 0 : GNUNET_buffer_write_str (&buf,
159 : "https://");
160 0 : GNUNET_buffer_write_str (&buf,
161 : host);
162 0 : if (NULL != uri_path)
163 0 : GNUNET_buffer_write_path (&buf,
164 : uri_path);
165 0 : if (0 != strcmp ("default",
166 : instance_id))
167 : {
168 0 : GNUNET_buffer_write_path (&buf,
169 : "instances");
170 0 : GNUNET_buffer_write_path (&buf,
171 : instance_id);
172 : }
173 0 : GNUNET_buffer_write_path (&buf,
174 : "static/");
175 0 : return GNUNET_buffer_reap_str (&buf);
176 : }
177 :
178 :
179 : int
180 0 : TALER_TEMPLATING_fill (const char *tmpl,
181 : const json_t *root,
182 : void **result,
183 : size_t *result_size)
184 : {
185 : int eno;
186 :
187 0 : if (0 !=
188 0 : (eno = mustach_jansson_mem (tmpl,
189 : 0, /* length of tmpl */
190 : (json_t *) root,
191 : Mustach_With_AllExtensions,
192 : (char **) result,
193 : result_size)))
194 : {
195 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
196 : "mustach failed on template with error %d\n",
197 : eno);
198 0 : *result = NULL;
199 0 : *result_size = 0;
200 0 : return eno;
201 : }
202 0 : return eno;
203 : }
204 :
205 :
206 : int
207 0 : TALER_TEMPLATING_fill2 (const void *tmpl,
208 : size_t tmpl_len,
209 : const json_t *root,
210 : void **result,
211 : size_t *result_size)
212 : {
213 : int eno;
214 :
215 0 : if (0 !=
216 0 : (eno = mustach_jansson_mem (tmpl,
217 : tmpl_len,
218 : (json_t *) root,
219 : Mustach_With_AllExtensions,
220 : (char **) result,
221 : result_size)))
222 : {
223 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
224 : "mustach failed on template with error %d\n",
225 : eno);
226 0 : *result = NULL;
227 0 : *result_size = 0;
228 0 : return eno;
229 : }
230 0 : return eno;
231 : }
232 :
233 :
234 : enum GNUNET_GenericReturnValue
235 3 : TALER_TEMPLATING_build (struct MHD_Connection *connection,
236 : unsigned int *http_status,
237 : const char *template,
238 : const char *instance_id,
239 : const char *taler_uri,
240 : const json_t *root,
241 : struct MHD_Response **reply)
242 : {
243 : char *body;
244 : size_t body_size;
245 : struct TVE *tve;
246 :
247 : {
248 : const char *tmpl;
249 : int eno;
250 :
251 3 : tve = lookup_template (connection,
252 : template);
253 3 : if (NULL == tve)
254 : {
255 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
256 : "Failed to load template `%s'\n",
257 : template);
258 0 : *http_status = MHD_HTTP_NOT_ACCEPTABLE;
259 0 : *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
260 : template);
261 0 : return GNUNET_NO;
262 : }
263 3 : tmpl = tve->value;
264 : /* Add default values to the context */
265 3 : if (NULL != instance_id)
266 : {
267 0 : char *static_url = make_static_url (connection,
268 : instance_id);
269 :
270 0 : GNUNET_break (0 ==
271 : json_object_set_new ((json_t *) root,
272 : "static_url",
273 : json_string (static_url)));
274 0 : GNUNET_free (static_url);
275 : }
276 3 : if (0 !=
277 3 : (eno = mustach_jansson_mem (tmpl,
278 : 0,
279 : (json_t *) root,
280 : Mustach_With_NoExtensions,
281 : &body,
282 : &body_size)))
283 : {
284 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285 : "mustach failed on template `%s' with error %d\n",
286 : template,
287 : eno);
288 0 : *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
289 0 : *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
290 : template);
291 0 : return GNUNET_NO;
292 : }
293 : }
294 :
295 : /* try to compress reply if client allows it */
296 : {
297 3 : bool compressed = false;
298 :
299 3 : if (TALER_MHD_CT_DEFLATE ==
300 3 : TALER_MHD_can_compress (connection,
301 : TALER_MHD_CT_DEFLATE))
302 : {
303 3 : compressed = TALER_MHD_body_compress ((void **) &body,
304 : &body_size);
305 : }
306 3 : *reply = MHD_create_response_from_buffer (body_size,
307 : body,
308 : MHD_RESPMEM_MUST_FREE);
309 3 : if (NULL == *reply)
310 : {
311 0 : GNUNET_break (0);
312 0 : return GNUNET_SYSERR;
313 : }
314 3 : if (compressed)
315 : {
316 3 : if (MHD_NO ==
317 3 : MHD_add_response_header (*reply,
318 : MHD_HTTP_HEADER_CONTENT_ENCODING,
319 : "deflate"))
320 : {
321 0 : GNUNET_break (0);
322 0 : MHD_destroy_response (*reply);
323 0 : *reply = NULL;
324 0 : return GNUNET_SYSERR;
325 : }
326 : }
327 : }
328 3 : if (NULL != tve->lang)
329 3 : GNUNET_break (MHD_YES ==
330 : MHD_add_response_header (*reply,
331 : MHD_HTTP_HEADER_CONTENT_LANGUAGE,
332 : tve->lang));
333 :
334 : /* Add standard headers */
335 3 : if (NULL != taler_uri)
336 0 : GNUNET_break (MHD_NO !=
337 : MHD_add_response_header (*reply,
338 : "Taler",
339 : taler_uri));
340 3 : return GNUNET_OK;
341 : }
342 :
343 :
344 : enum GNUNET_GenericReturnValue
345 0 : TALER_TEMPLATING_reply (struct MHD_Connection *connection,
346 : unsigned int http_status,
347 : const char *template,
348 : const char *instance_id,
349 : const char *taler_uri,
350 : const json_t *root)
351 : {
352 : enum GNUNET_GenericReturnValue res;
353 : struct MHD_Response *reply;
354 : enum MHD_Result ret;
355 :
356 0 : res = TALER_TEMPLATING_build (connection,
357 : &http_status,
358 : template,
359 : instance_id,
360 : taler_uri,
361 : root,
362 : &reply);
363 0 : if (GNUNET_SYSERR == res)
364 0 : return res;
365 0 : GNUNET_break (MHD_NO !=
366 : MHD_add_response_header (reply,
367 : MHD_HTTP_HEADER_CONTENT_TYPE,
368 : "text/html"));
369 : // FIXME: set Vary header!
370 0 : ret = MHD_queue_response (connection,
371 : http_status,
372 : reply);
373 0 : MHD_destroy_response (reply);
374 0 : if (MHD_NO == ret)
375 0 : return GNUNET_SYSERR;
376 : return (res == GNUNET_OK)
377 : ? GNUNET_OK
378 0 : : GNUNET_NO;
379 : }
380 :
381 :
382 : /**
383 : * Function called with a template's filename.
384 : *
385 : * @param cls closure, NULL
386 : * @param filename complete filename (absolute path)
387 : * @return #GNUNET_OK to continue to iterate,
388 : * #GNUNET_NO to stop iteration with no error,
389 : * #GNUNET_SYSERR to abort iteration with error!
390 : */
391 : static enum GNUNET_GenericReturnValue
392 360 : load_template (void *cls,
393 : const char *filename)
394 : {
395 : char *lang;
396 : char *end;
397 : int fd;
398 : struct stat sb;
399 : char *map;
400 : const char *name;
401 :
402 : (void) cls;
403 360 : if ('.' == filename[0])
404 0 : return GNUNET_OK;
405 360 : name = strrchr (filename,
406 : '/');
407 360 : if (NULL == name)
408 0 : name = filename;
409 : else
410 360 : name++;
411 360 : lang = strchr (name,
412 : '.');
413 360 : if (NULL == lang)
414 0 : return GNUNET_OK; /* name must include .$LANG */
415 360 : lang++;
416 360 : end = strchr (lang,
417 : '.');
418 360 : if ( (NULL == end) ||
419 360 : (0 != strcmp (end,
420 : ".must")) )
421 0 : return GNUNET_OK; /* name must end with '.must' */
422 :
423 : /* finally open template */
424 360 : fd = open (filename,
425 : O_RDONLY);
426 360 : if (-1 == fd)
427 : {
428 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
429 : "open",
430 : filename);
431 :
432 0 : return GNUNET_SYSERR;
433 : }
434 360 : if (0 !=
435 360 : fstat (fd,
436 : &sb))
437 : {
438 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
439 : "fstat",
440 : filename);
441 0 : GNUNET_break (0 == close (fd));
442 0 : return GNUNET_OK;
443 : }
444 360 : map = GNUNET_malloc_large (sb.st_size + 1);
445 360 : if (NULL == map)
446 : {
447 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
448 : "malloc");
449 0 : GNUNET_break (0 == close (fd));
450 0 : return GNUNET_SYSERR;
451 : }
452 360 : if (sb.st_size !=
453 360 : read (fd,
454 : map,
455 360 : sb.st_size))
456 : {
457 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
458 : "read",
459 : filename);
460 0 : GNUNET_break (0 == close (fd));
461 0 : return GNUNET_OK;
462 : }
463 360 : GNUNET_break (0 == close (fd));
464 360 : GNUNET_array_grow (loaded,
465 : loaded_length,
466 : loaded_length + 1);
467 360 : loaded[loaded_length - 1].name = GNUNET_strndup (name,
468 : (lang - 1) - name);
469 360 : loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
470 : end - lang);
471 360 : loaded[loaded_length - 1].value = map;
472 360 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
473 : "Loading template `%s' (%s)\n",
474 : filename,
475 : loaded[loaded_length - 1].name);
476 360 : return GNUNET_OK;
477 : }
478 :
479 :
480 : enum MHD_Result
481 0 : TALER_TEMPLATING_reply_error (
482 : struct MHD_Connection *connection,
483 : const char *template_basename,
484 : unsigned int http_status,
485 : enum TALER_ErrorCode ec,
486 : const char *detail)
487 : {
488 : json_t *data;
489 : enum GNUNET_GenericReturnValue ret;
490 :
491 0 : data = GNUNET_JSON_PACK (
492 : GNUNET_JSON_pack_uint64 ("ec",
493 : ec),
494 : GNUNET_JSON_pack_string ("hint",
495 : TALER_ErrorCode_get_hint (ec)),
496 : GNUNET_JSON_pack_allow_null (
497 : GNUNET_JSON_pack_string ("detail",
498 : detail))
499 : );
500 0 : ret = TALER_TEMPLATING_reply (connection,
501 : http_status,
502 : template_basename,
503 : NULL,
504 : NULL,
505 : data);
506 0 : json_decref (data);
507 0 : switch (ret)
508 : {
509 0 : case GNUNET_OK:
510 0 : return MHD_YES;
511 0 : case GNUNET_NO:
512 0 : return MHD_YES;
513 0 : case GNUNET_SYSERR:
514 0 : return MHD_NO;
515 : }
516 0 : GNUNET_assert (0);
517 : return MHD_NO;
518 : }
519 :
520 :
521 : enum GNUNET_GenericReturnValue
522 18 : TALER_TEMPLATING_init (const struct GNUNET_OS_ProjectData *pd)
523 : {
524 : char *dn;
525 : int ret;
526 :
527 : {
528 : char *path;
529 :
530 18 : path = GNUNET_OS_installation_get_path (pd,
531 : GNUNET_OS_IPK_DATADIR);
532 18 : if (NULL == path)
533 : {
534 0 : GNUNET_break (0);
535 0 : return GNUNET_SYSERR;
536 : }
537 18 : GNUNET_asprintf (&dn,
538 : "%s/templates/",
539 : path);
540 18 : GNUNET_free (path);
541 : }
542 18 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
543 : "Loading templates from `%s'\n",
544 : dn);
545 18 : ret = GNUNET_DISK_directory_scan (dn,
546 : &load_template,
547 : NULL);
548 18 : GNUNET_free (dn);
549 18 : if (-1 == ret)
550 : {
551 0 : GNUNET_break (0);
552 0 : return GNUNET_SYSERR;
553 : }
554 18 : return GNUNET_OK;
555 : }
556 :
557 :
558 : void
559 18 : TALER_TEMPLATING_done (void)
560 : {
561 378 : for (unsigned int i = 0; i<loaded_length; i++)
562 : {
563 360 : GNUNET_free (loaded[i].name);
564 360 : GNUNET_free (loaded[i].lang);
565 360 : GNUNET_free (loaded[i].value);
566 : }
567 18 : GNUNET_array_grow (loaded,
568 : loaded_length,
569 : 0);
570 18 : }
571 :
572 :
573 : /* end of templating_api.c */
|