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 return static resource files by client language preference
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include "taler_merchant_util.h"
24 : #include <taler/taler_util.h>
25 : #include <taler/taler_mhd_lib.h>
26 : #include <taler/taler_templating_lib.h>
27 : #include "taler-merchant-httpd_statics.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 : * Pre-built reply.
48 : */
49 : struct MHD_Response *reply;
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 struct TVE *
76 0 : lookup_file (struct MHD_Connection *connection,
77 : const char *name)
78 : {
79 0 : double best_q = 0.0;
80 0 : struct TVE *best = NULL;
81 : const char *lang;
82 :
83 0 : lang = MHD_lookup_connection_value (connection,
84 : MHD_HEADER_KIND,
85 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
86 0 : if (NULL == lang)
87 0 : lang = "en";
88 : /* find best match by language */
89 0 : for (unsigned int i = 0; i<loaded_length; i++)
90 : {
91 : double q;
92 :
93 0 : if (0 != strcmp (loaded[i].name,
94 : name))
95 0 : continue; /* does not match by name */
96 0 : if (NULL == loaded[i].lang) /* no language == always best match */
97 0 : return &loaded[i];
98 0 : q = TALER_pattern_matches (lang,
99 0 : loaded[i].lang);
100 0 : if (q < best_q)
101 0 : continue;
102 0 : best_q = q;
103 0 : best = &loaded[i];
104 : }
105 0 : if (NULL == best)
106 : {
107 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
108 : "No static file found for `%s'\n",
109 : name);
110 0 : return NULL;
111 : }
112 0 : return best;
113 : }
114 :
115 :
116 : MHD_RESULT
117 0 : TMH_return_static (const struct TMH_RequestHandler *rh,
118 : struct MHD_Connection *connection,
119 : struct TMH_HandlerContext *hc)
120 : {
121 : const struct TVE *tmpl;
122 :
123 0 : tmpl = lookup_file (connection,
124 0 : hc->infix);
125 0 : if (NULL == tmpl)
126 : {
127 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
128 : "Failed to load static file `%s'\n",
129 : hc->infix);
130 0 : return TALER_MHD_reply_with_error (connection,
131 : MHD_HTTP_NOT_FOUND,
132 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
133 0 : hc->infix);
134 : }
135 :
136 0 : return MHD_queue_response (connection,
137 : MHD_HTTP_OK,
138 0 : tmpl->reply);
139 : }
140 :
141 :
142 : /**
143 : * Function called with a static file's filename.
144 : *
145 : * @param cls closure
146 : * @param filename complete filename (absolute path)
147 : * @return #GNUNET_OK to continue to iterate,
148 : * #GNUNET_NO to stop iteration with no error,
149 : * #GNUNET_SYSERR to abort iteration with error!
150 : */
151 : static enum GNUNET_GenericReturnValue
152 0 : load_static_file (void *cls,
153 : const char *filename)
154 : {
155 : char *lang;
156 : char *end;
157 : int fd;
158 : struct stat sb;
159 : const char *name;
160 : struct MHD_Response *reply;
161 :
162 0 : if ('.' == filename[0])
163 0 : return GNUNET_OK;
164 0 : name = strrchr (filename,
165 : '/');
166 0 : if (NULL == name)
167 0 : name = filename;
168 : else
169 0 : name++;
170 0 : lang = strchr (name,
171 : '.');
172 0 : if (NULL == lang)
173 0 : return GNUNET_OK; /* name must include _some_ extension */
174 0 : lang++;
175 0 : end = strchr (lang,
176 : '.');
177 0 : if (NULL == end)
178 : {
179 : /* language was not present, we ONLY have the extension */
180 0 : end = lang - 1;
181 0 : lang = NULL;
182 : }
183 : /* finally open template */
184 0 : fd = open (filename,
185 : O_RDONLY);
186 0 : if (-1 == fd)
187 : {
188 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
189 : "open",
190 : filename);
191 0 : return GNUNET_SYSERR;
192 : }
193 0 : if (0 !=
194 0 : fstat (fd,
195 : &sb))
196 : {
197 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
198 : "open",
199 : filename);
200 0 : GNUNET_break (0 == close (fd));
201 0 : return GNUNET_OK;
202 : }
203 :
204 0 : reply = MHD_create_response_from_fd (sb.st_size,
205 : fd);
206 0 : if (NULL == reply)
207 : {
208 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
209 : "open",
210 : filename);
211 0 : GNUNET_break (0 == close (fd));
212 0 : return GNUNET_OK;
213 : }
214 :
215 : {
216 : static struct MimeMap
217 : {
218 : const char *ext;
219 : const char *mime;
220 : } mm[] = {
221 : { .ext = ".css", .mime = "text/css" },
222 : { .ext = ".js", .mime = "text/javascript" },
223 : { .ext = ".html", .mime = "text/html" },
224 : { .ext = ".htm", .mime = "text/html" },
225 : { .ext = ".txt", .mime = "text/plain" },
226 : { .ext = ".pdf", .mime = "application/pdf" },
227 : { .ext = ".jpg", .mime = "image/jpeg" },
228 : { .ext = ".jpeg", .mime = "image/jpeg" },
229 : { .ext = ".png", .mime = "image/png" },
230 : { .ext = ".apng", .mime = "image/apng" },
231 : { .ext = ".gif", .mime = "image/gif" },
232 : { .ext = ".svg", .mime = "image/svg+xml" },
233 : { .ext = ".tiff", .mime = "image/tiff" },
234 : { .ext = ".ico", .mime = "image/x-icon" },
235 : { .ext = ".bmp", .mime = "image/bmp" },
236 : { .ext = ".epub", .mime = "application/epub+zip" },
237 : { .ext = ".xml", .mime = "text/xml" },
238 : { .ext = NULL, .mime = NULL }
239 : };
240 : const char *mime;
241 :
242 0 : mime = NULL;
243 0 : for (unsigned int i = 0; NULL != mm[i].ext; i++)
244 0 : if (0 == strcasecmp (mm[i].ext,
245 : end))
246 : {
247 0 : mime = mm[i].mime;
248 0 : break;
249 : }
250 0 : if (NULL != mime)
251 0 : GNUNET_break (MHD_NO !=
252 : MHD_add_response_header (reply,
253 : MHD_HTTP_HEADER_CONTENT_TYPE,
254 : mime));
255 : }
256 :
257 0 : GNUNET_array_grow (loaded,
258 : loaded_length,
259 : loaded_length + 1);
260 0 : if (NULL != lang)
261 : {
262 0 : GNUNET_asprintf (&loaded[loaded_length - 1].name,
263 : "%.*s%s",
264 0 : (int) (lang - name) - 1,
265 : name,
266 : end);
267 0 : loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
268 : end - lang);
269 : }
270 : else
271 : {
272 0 : loaded[loaded_length - 1].name = GNUNET_strdup (name);
273 : }
274 0 : loaded[loaded_length - 1].reply = reply;
275 0 : return GNUNET_OK;
276 : }
277 :
278 :
279 : /**
280 : * Preload static files.
281 : */
282 : enum GNUNET_GenericReturnValue
283 0 : TMH_statics_init ()
284 : {
285 : char *dn;
286 : int ret;
287 :
288 : {
289 : char *path;
290 :
291 0 : path = GNUNET_OS_installation_get_path (TALER_MERCHANT_project_data (),
292 : GNUNET_OS_IPK_DATADIR);
293 0 : if (NULL == path)
294 : {
295 0 : GNUNET_break (0);
296 0 : return GNUNET_SYSERR;
297 : }
298 0 : GNUNET_asprintf (&dn,
299 : "%smerchant/static/",
300 : path);
301 0 : GNUNET_free (path);
302 : }
303 0 : ret = GNUNET_DISK_directory_scan (dn,
304 : &load_static_file,
305 : NULL);
306 0 : if (-1 == ret)
307 : {
308 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
309 : "Could not load static resources from `%s': %s\n",
310 : dn,
311 : strerror (errno));
312 0 : GNUNET_free (dn);
313 0 : return GNUNET_SYSERR;
314 : }
315 0 : GNUNET_free (dn);
316 0 : return GNUNET_OK;
317 : }
318 :
319 :
320 : /**
321 : * Nicely shut down.
322 : */
323 : void __attribute__ ((destructor))
324 : get_statics_fini (void);
325 :
326 : /* Declaration avoids compiler warning */
327 : void __attribute__ ((destructor))
328 27 : get_statics_fini ()
329 : {
330 27 : for (unsigned int i = 0; i<loaded_length; i++)
331 : {
332 0 : GNUNET_free (loaded[i].name);
333 0 : GNUNET_free (loaded[i].lang);
334 0 : MHD_destroy_response (loaded[i].reply);
335 : }
336 27 : GNUNET_array_grow (loaded,
337 : loaded_length,
338 : 0);
339 27 : }
|