Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020 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 taler-merchant-httpd_statics.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/taler_util.h>
24 : #include <taler/taler_mhd_lib.h>
25 : #include <taler/taler_templating_lib.h>
26 : #include "taler-merchant-httpd_statics.h"
27 : #include <gnunet/gnunet_mhd_compat.h>
28 :
29 :
30 : /**
31 : * Entry in a key-value array we use to cache templates.
32 : */
33 : struct TVE
34 : {
35 : /**
36 : * A name, used as the key. NULL for the last entry.
37 : */
38 : char *name;
39 :
40 : /**
41 : * Language the template is in.
42 : */
43 : char *lang;
44 :
45 : /**
46 : * Pre-built reply.
47 : */
48 : struct MHD_Response *reply;
49 :
50 : };
51 :
52 :
53 : /**
54 : * Array of templates loaded into RAM.
55 : */
56 : static struct TVE *loaded;
57 :
58 : /**
59 : * Length of the #loaded array.
60 : */
61 : static unsigned int loaded_length;
62 :
63 :
64 : /**
65 : * Load Mustach template into memory. Note that we intentionally cache
66 : * failures, that is if we ever failed to load a template, we will never try
67 : * again.
68 : *
69 : * @param connection the connection we act upon
70 : * @param name name of the template file to load
71 : * (MUST be a 'static' string in memory!)
72 : * @return NULL on error, otherwise the template
73 : */
74 : static const struct TVE *
75 0 : lookup_file (struct MHD_Connection *connection,
76 : const char *name)
77 : {
78 0 : struct TVE *best = NULL;
79 : const char *lang;
80 :
81 0 : lang = MHD_lookup_connection_value (connection,
82 : MHD_HEADER_KIND,
83 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
84 0 : if (NULL == lang)
85 0 : lang = "en";
86 : /* find best match by language */
87 0 : for (unsigned int i = 0; i<loaded_length; i++)
88 : {
89 0 : if (0 != strcmp (loaded[i].name,
90 : name))
91 0 : continue; /* does not match by name */
92 0 : if (NULL == loaded[i].lang) /* no language == always best match */
93 0 : return &loaded[i];
94 0 : if ( (NULL == best) ||
95 0 : (TALER_language_matches (lang,
96 0 : loaded[i].lang) >
97 0 : TALER_language_matches (lang,
98 0 : best->lang) ) )
99 0 : best = &loaded[i];
100 : }
101 0 : if (NULL == best)
102 : {
103 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
104 : "No static file found for `%s'\n",
105 : name);
106 0 : return NULL;
107 : }
108 0 : return best;
109 : }
110 :
111 :
112 : MHD_RESULT
113 0 : TMH_return_static (const struct TMH_RequestHandler *rh,
114 : struct MHD_Connection *connection,
115 : struct TMH_HandlerContext *hc)
116 : {
117 : const struct TVE *tmpl;
118 :
119 0 : tmpl = lookup_file (connection,
120 0 : hc->infix);
121 0 : if (NULL == tmpl)
122 : {
123 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
124 : "Failed to load static file `%s'\n",
125 : hc->infix);
126 0 : return TALER_MHD_reply_with_error (connection,
127 : MHD_HTTP_NOT_FOUND,
128 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
129 0 : hc->infix);
130 : }
131 :
132 0 : return MHD_queue_response (connection,
133 : MHD_HTTP_OK,
134 : tmpl->reply);
135 : }
136 :
137 :
138 : /**
139 : * Function called with a static file's filename.
140 : *
141 : * @param cls closure
142 : * @param filename complete filename (absolute path)
143 : * @return #GNUNET_OK to continue to iterate,
144 : * #GNUNET_NO to stop iteration with no error,
145 : * #GNUNET_SYSERR to abort iteration with error!
146 : */
147 : static int
148 0 : load_static_file (void *cls,
149 : const char *filename)
150 : {
151 : char *lang;
152 : char *end;
153 : int fd;
154 : struct stat sb;
155 : const char *name;
156 : struct MHD_Response *reply;
157 :
158 0 : if ('.' == filename[0])
159 0 : return GNUNET_OK;
160 0 : name = strrchr (filename,
161 : '/');
162 0 : if (NULL == name)
163 0 : name = filename;
164 : else
165 0 : name++;
166 0 : lang = strchr (name,
167 : '.');
168 0 : if (NULL == lang)
169 0 : return GNUNET_OK; /* name must include _some_ extension */
170 0 : lang++;
171 0 : end = strchr (lang,
172 : '.');
173 0 : if (NULL == end)
174 : {
175 : /* language was not present, we ONLY have the extension */
176 0 : end = lang - 1;
177 0 : lang = NULL;
178 : }
179 : /* finally open template */
180 0 : fd = open (filename,
181 : O_RDONLY);
182 0 : if (-1 == fd)
183 : {
184 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
185 : "open",
186 : filename);
187 0 : return GNUNET_SYSERR;
188 : }
189 0 : if (0 !=
190 0 : fstat (fd,
191 : &sb))
192 : {
193 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
194 : "open",
195 : filename);
196 0 : GNUNET_break (0 == close (fd));
197 0 : return GNUNET_OK;
198 : }
199 :
200 0 : reply = MHD_create_response_from_fd (sb.st_size,
201 : fd);
202 0 : if (NULL == reply)
203 : {
204 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
205 : "open",
206 : filename);
207 0 : GNUNET_break (0 == close (fd));
208 0 : return GNUNET_OK;
209 : }
210 :
211 : {
212 : static struct MimeMap
213 : {
214 : const char *ext;
215 : const char *mime;
216 : } mm[] = {
217 : { .ext = ".css", .mime = "text/css" },
218 : { .ext = ".js", .mime = "text/javascript" },
219 : { .ext = ".html", .mime = "text/html" },
220 : { .ext = ".htm", .mime = "text/html" },
221 : { .ext = ".txt", .mime = "text/plain" },
222 : { .ext = ".pdf", .mime = "application/pdf" },
223 : { .ext = ".jpg", .mime = "image/jpeg" },
224 : { .ext = ".jpeg", .mime = "image/jpeg" },
225 : { .ext = ".png", .mime = "image/png" },
226 : { .ext = ".apng", .mime = "image/apng" },
227 : { .ext = ".gif", .mime = "image/gif" },
228 : { .ext = ".svg", .mime = "image/svg+xml" },
229 : { .ext = ".tiff", .mime = "image/tiff" },
230 : { .ext = ".ico", .mime = "image/x-icon" },
231 : { .ext = ".bmp", .mime = "image/bmp" },
232 : { .ext = ".epub", .mime = "application/epub+zip" },
233 : { .ext = ".xml", .mime = "text/xml" },
234 : { .ext = NULL, .mime = NULL }
235 : };
236 : const char *mime;
237 :
238 0 : mime = NULL;
239 0 : for (unsigned int i = 0; NULL != mm[i].ext; i++)
240 0 : if (0 == strcasecmp (mm[i].ext,
241 : end))
242 : {
243 0 : mime = mm[i].mime;
244 0 : break;
245 : }
246 0 : if (NULL != mime)
247 0 : GNUNET_break (MHD_NO !=
248 : MHD_add_response_header (reply,
249 : MHD_HTTP_HEADER_CONTENT_TYPE,
250 : mime));
251 : }
252 :
253 0 : GNUNET_array_grow (loaded,
254 : loaded_length,
255 : loaded_length + 1);
256 0 : if (NULL != lang)
257 : {
258 0 : GNUNET_asprintf (&loaded[loaded_length - 1].name,
259 : "%.*s%s",
260 0 : (int) (lang - name) - 1,
261 : name,
262 : end);
263 0 : loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
264 : end - lang);
265 : }
266 : else
267 : {
268 0 : loaded[loaded_length - 1].name = GNUNET_strdup (name);
269 : }
270 0 : loaded[loaded_length - 1].reply = reply;
271 0 : return GNUNET_OK;
272 : }
273 :
274 :
275 : /**
276 : * Preload static files.
277 : */
278 : int
279 0 : TMH_statics_init ()
280 : {
281 : char *dn;
282 : int ret;
283 :
284 : {
285 : char *path;
286 :
287 0 : path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
288 0 : if (NULL == path)
289 : {
290 0 : GNUNET_break (0);
291 0 : return GNUNET_SYSERR;
292 : }
293 0 : GNUNET_asprintf (&dn,
294 : "%s/merchant/static/",
295 : path);
296 0 : GNUNET_free (path);
297 : }
298 0 : ret = GNUNET_DISK_directory_scan (dn,
299 : &load_static_file,
300 : NULL);
301 0 : GNUNET_free (dn);
302 0 : if (-1 == ret)
303 : {
304 0 : GNUNET_break (0);
305 0 : return GNUNET_SYSERR;
306 : }
307 0 : return GNUNET_OK;
308 : }
309 :
310 :
311 : /**
312 : * Nicely shut down.
313 : */
314 : void __attribute__ ((destructor))
315 14 : get_statics_fini ()
316 : {
317 14 : for (unsigned int i = 0; i<loaded_length; i++)
318 : {
319 0 : GNUNET_free (loaded[i].name);
320 0 : GNUNET_free (loaded[i].lang);
321 0 : MHD_destroy_response (loaded[i].reply);
322 : }
323 14 : GNUNET_array_grow (loaded,
324 : loaded_length,
325 : 0);
326 14 : }
|