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"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include "taler_util.h"
24 : #include "taler_mhd_lib.h"
25 : #include "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 const char *
76 0 : lookup_template (struct MHD_Connection *connection,
77 : const char *name)
78 : {
79 0 : struct TVE *best = NULL;
80 : const char *lang;
81 :
82 0 : lang = MHD_lookup_connection_value (connection,
83 : MHD_HEADER_KIND,
84 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
85 0 : if (NULL == lang)
86 0 : lang = "en";
87 : /* find best match by language */
88 0 : for (unsigned int i = 0; i<loaded_length; i++)
89 : {
90 0 : if (0 != strcmp (loaded[i].name,
91 : name))
92 0 : continue; /* does not match by name */
93 0 : if ( (NULL == best) ||
94 0 : (TALER_language_matches (lang,
95 0 : loaded[i].lang) >
96 0 : TALER_language_matches (lang,
97 0 : best->lang) ) )
98 0 : best = &loaded[i];
99 : }
100 0 : if (NULL == best)
101 : {
102 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
103 : "No templates found in `%s'\n",
104 : name);
105 0 : return NULL;
106 : }
107 0 : return best->value;
108 : }
109 :
110 :
111 : /**
112 : * Get the base URL for static resources.
113 : *
114 : * @param con the MHD connection
115 : * @param instance_id the instance ID
116 : * @returns the static files base URL, guaranteed
117 : * to have a trailing slash.
118 : */
119 : static char *
120 0 : make_static_url (struct MHD_Connection *con,
121 : const char *instance_id)
122 : {
123 : const char *host;
124 : const char *forwarded_host;
125 : const char *uri_path;
126 0 : struct GNUNET_Buffer buf = { 0 };
127 :
128 0 : host = MHD_lookup_connection_value (con,
129 : MHD_HEADER_KIND,
130 : "Host");
131 0 : forwarded_host = MHD_lookup_connection_value (con,
132 : MHD_HEADER_KIND,
133 : "X-Forwarded-Host");
134 :
135 0 : uri_path = MHD_lookup_connection_value (con,
136 : MHD_HEADER_KIND,
137 : "X-Forwarded-Prefix");
138 0 : if (NULL != forwarded_host)
139 0 : host = forwarded_host;
140 :
141 0 : if (NULL == host)
142 : {
143 0 : GNUNET_break (0);
144 0 : return NULL;
145 : }
146 :
147 0 : GNUNET_assert (NULL != instance_id);
148 :
149 0 : if (GNUNET_NO == TALER_mhd_is_https (con))
150 0 : GNUNET_buffer_write_str (&buf,
151 : "http://");
152 : else
153 0 : GNUNET_buffer_write_str (&buf,
154 : "https://");
155 0 : GNUNET_buffer_write_str (&buf,
156 : host);
157 0 : if (NULL != uri_path)
158 0 : GNUNET_buffer_write_path (&buf,
159 : uri_path);
160 0 : if (0 != strcmp ("default",
161 : instance_id))
162 : {
163 0 : GNUNET_buffer_write_path (&buf,
164 : "instances");
165 0 : GNUNET_buffer_write_path (&buf,
166 : instance_id);
167 : }
168 0 : GNUNET_buffer_write_path (&buf,
169 : "static/");
170 0 : return GNUNET_buffer_reap_str (&buf);
171 : }
172 :
173 :
174 : enum GNUNET_GenericReturnValue
175 0 : TALER_TEMPLATING_build (struct MHD_Connection *connection,
176 : unsigned int *http_status,
177 : const char *template,
178 : const char *instance_id,
179 : const char *taler_uri,
180 : const json_t *root,
181 : struct MHD_Response **reply)
182 : {
183 : char *body;
184 : size_t body_size;
185 :
186 : {
187 : const char *tmpl;
188 : int eno;
189 :
190 0 : tmpl = lookup_template (connection,
191 : template);
192 0 : if (NULL == tmpl)
193 : {
194 : /* FIXME: should this not be an
195 : internal failure? The language
196 : mismatch is not critical here! */
197 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
198 : "Failed to load template `%s'\n",
199 : template);
200 0 : *http_status = MHD_HTTP_NOT_ACCEPTABLE;
201 0 : *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
202 : template);
203 0 : return GNUNET_NO;
204 : }
205 : /* Add default values to the context */
206 0 : if (NULL != instance_id)
207 : {
208 0 : char *static_url = make_static_url (connection,
209 : instance_id);
210 :
211 0 : GNUNET_break (0 ==
212 : json_object_set_new ((json_t *) root,
213 : "static_url",
214 : json_string (static_url)));
215 0 : GNUNET_free (static_url);
216 : }
217 0 : if (0 !=
218 0 : (eno = mustach_jansson (tmpl,
219 : (json_t *) root,
220 : &body,
221 : &body_size)))
222 : {
223 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
224 : "mustach failed on template `%s' with error %d\n",
225 : template,
226 : eno);
227 0 : *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
228 0 : *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
229 : template);
230 0 : return GNUNET_NO;
231 : }
232 : }
233 :
234 : /* try to compress reply if client allows it */
235 : {
236 0 : bool compressed = false;
237 :
238 0 : if (MHD_YES ==
239 0 : TALER_MHD_can_compress (connection))
240 : {
241 0 : compressed = TALER_MHD_body_compress ((void **) &body,
242 : &body_size);
243 : }
244 0 : *reply = MHD_create_response_from_buffer (body_size,
245 : body,
246 : MHD_RESPMEM_MUST_FREE);
247 0 : if (NULL == *reply)
248 : {
249 0 : GNUNET_break (0);
250 0 : return GNUNET_SYSERR;
251 : }
252 0 : if (compressed)
253 : {
254 0 : if (MHD_NO ==
255 0 : MHD_add_response_header (*reply,
256 : MHD_HTTP_HEADER_CONTENT_ENCODING,
257 : "deflate"))
258 : {
259 0 : GNUNET_break (0);
260 0 : MHD_destroy_response (*reply);
261 0 : *reply = NULL;
262 0 : return GNUNET_SYSERR;
263 : }
264 : }
265 : }
266 :
267 : /* Add standard headers */
268 0 : if (NULL != taler_uri)
269 0 : GNUNET_break (MHD_NO !=
270 : MHD_add_response_header (*reply,
271 : "Taler",
272 : taler_uri));
273 0 : GNUNET_break (MHD_NO !=
274 : MHD_add_response_header (*reply,
275 : MHD_HTTP_HEADER_CONTENT_TYPE,
276 : "text/html"));
277 0 : return GNUNET_OK;
278 : }
279 :
280 :
281 : enum GNUNET_GenericReturnValue
282 0 : TALER_TEMPLATING_reply (struct MHD_Connection *connection,
283 : unsigned int http_status,
284 : const char *template,
285 : const char *instance_id,
286 : const char *taler_uri,
287 : const json_t *root)
288 : {
289 : enum GNUNET_GenericReturnValue res;
290 : struct MHD_Response *reply;
291 : MHD_RESULT ret;
292 :
293 0 : res = TALER_TEMPLATING_build (connection,
294 : &http_status,
295 : template,
296 : instance_id,
297 : taler_uri,
298 : root,
299 : &reply);
300 0 : if (GNUNET_SYSERR == res)
301 0 : return res;
302 0 : ret = MHD_queue_response (connection,
303 : http_status,
304 : reply);
305 0 : MHD_destroy_response (reply);
306 0 : if (MHD_NO == ret)
307 0 : return GNUNET_SYSERR;
308 : return (res == GNUNET_OK)
309 : ? GNUNET_OK
310 0 : : GNUNET_NO;
311 : }
312 :
313 :
314 : /**
315 : * Function called with a template's filename.
316 : *
317 : * @param cls closure, NULL
318 : * @param filename complete filename (absolute path)
319 : * @return #GNUNET_OK to continue to iterate,
320 : * #GNUNET_NO to stop iteration with no error,
321 : * #GNUNET_SYSERR to abort iteration with error!
322 : */
323 : static enum GNUNET_GenericReturnValue
324 0 : load_template (void *cls,
325 : const char *filename)
326 : {
327 : char *lang;
328 : char *end;
329 : int fd;
330 : struct stat sb;
331 : char *map;
332 : const char *name;
333 :
334 : (void) cls;
335 0 : if ('.' == filename[0])
336 0 : return GNUNET_OK;
337 :
338 0 : name = strrchr (filename,
339 : '/');
340 0 : if (NULL == name)
341 0 : name = filename;
342 : else
343 0 : name++;
344 0 : lang = strchr (name,
345 : '.');
346 0 : if (NULL == lang)
347 0 : return GNUNET_OK; /* name must include .$LANG */
348 0 : lang++;
349 0 : end = strchr (lang,
350 : '.');
351 0 : if ( (NULL == end) ||
352 0 : (0 != strcmp (end,
353 : ".must")) )
354 0 : return GNUNET_OK; /* name must end with '.must' */
355 :
356 : /* finally open template */
357 0 : fd = open (filename,
358 : O_RDONLY);
359 0 : if (-1 == fd)
360 : {
361 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
362 : "open",
363 : filename);
364 :
365 0 : return GNUNET_SYSERR;
366 : }
367 0 : if (0 !=
368 0 : fstat (fd,
369 : &sb))
370 : {
371 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
372 : "open",
373 : filename);
374 0 : GNUNET_break (0 == close (fd));
375 0 : return GNUNET_OK;
376 : }
377 0 : map = GNUNET_malloc_large (sb.st_size + 1);
378 0 : if (NULL == map)
379 : {
380 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
381 : "malloc");
382 0 : GNUNET_break (0 == close (fd));
383 0 : return GNUNET_SYSERR;
384 : }
385 0 : if (sb.st_size !=
386 0 : read (fd,
387 : map,
388 0 : sb.st_size))
389 : {
390 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
391 : "read",
392 : filename);
393 0 : GNUNET_break (0 == close (fd));
394 0 : return GNUNET_OK;
395 : }
396 0 : GNUNET_break (0 == close (fd));
397 0 : GNUNET_array_grow (loaded,
398 : loaded_length,
399 : loaded_length + 1);
400 0 : loaded[loaded_length - 1].name = GNUNET_strndup (name,
401 : (lang - 1) - name);
402 0 : loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
403 : end - lang);
404 0 : loaded[loaded_length - 1].value = map;
405 0 : return GNUNET_OK;
406 : }
407 :
408 :
409 : enum GNUNET_GenericReturnValue
410 0 : TALER_TEMPLATING_init (const char *subsystem)
411 : {
412 : char *dn;
413 : int ret;
414 :
415 : {
416 : char *path;
417 :
418 0 : path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
419 0 : if (NULL == path)
420 : {
421 0 : GNUNET_break (0);
422 0 : return GNUNET_SYSERR;
423 : }
424 0 : GNUNET_asprintf (&dn,
425 : "%s/%s/templates/",
426 : path,
427 : subsystem);
428 0 : GNUNET_free (path);
429 : }
430 0 : ret = GNUNET_DISK_directory_scan (dn,
431 : &load_template,
432 : NULL);
433 0 : GNUNET_free (dn);
434 0 : if (-1 == ret)
435 : {
436 0 : GNUNET_break (0);
437 0 : return GNUNET_SYSERR;
438 : }
439 0 : return GNUNET_OK;
440 : }
441 :
442 :
443 : void
444 0 : TALER_TEMPLATING_done (void)
445 : {
446 0 : for (unsigned int i = 0; i<loaded_length; i++)
447 : {
448 0 : GNUNET_free (loaded[i].name);
449 0 : GNUNET_free (loaded[i].lang);
450 0 : GNUNET_free (loaded[i].value);
451 : }
452 0 : GNUNET_array_grow (loaded,
453 : loaded_length,
454 : 0);
455 0 : }
456 :
457 :
458 : /* end of templating_api.c */
|