taler-merchant-httpd_statics.c (8911B)
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 lookup_file (struct MHD_Connection *connection, 77 const char *name) 78 { 79 double best_q = 0.0; 80 struct TVE *best = NULL; 81 const char *lang; 82 83 lang = MHD_lookup_connection_value (connection, 84 MHD_HEADER_KIND, 85 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 86 if (NULL == lang) 87 lang = "en"; 88 /* find best match by language */ 89 for (unsigned int i = 0; i<loaded_length; i++) 90 { 91 double q; 92 93 if (0 != strcmp (loaded[i].name, 94 name)) 95 continue; /* does not match by name */ 96 if (NULL == loaded[i].lang) /* no language == always best match */ 97 return &loaded[i]; 98 q = TALER_pattern_matches (lang, 99 loaded[i].lang); 100 if (q < best_q) 101 continue; 102 best_q = q; 103 best = &loaded[i]; 104 } 105 if (NULL == best) 106 { 107 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 108 "No static file found for `%s'\n", 109 name); 110 return NULL; 111 } 112 return best; 113 } 114 115 116 MHD_RESULT 117 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 tmpl = lookup_file (connection, 124 hc->infix); 125 if (NULL == tmpl) 126 { 127 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 128 "Failed to load static file `%s'\n", 129 hc->infix); 130 return TALER_MHD_reply_with_error (connection, 131 MHD_HTTP_NOT_FOUND, 132 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 133 hc->infix); 134 } 135 136 return MHD_queue_response (connection, 137 MHD_HTTP_OK, 138 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 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 if ('.' == filename[0]) 163 return GNUNET_OK; 164 name = strrchr (filename, 165 '/'); 166 if (NULL == name) 167 name = filename; 168 else 169 name++; 170 lang = strchr (name, 171 '.'); 172 if (NULL == lang) 173 return GNUNET_OK; /* name must include _some_ extension */ 174 lang++; 175 end = strchr (lang, 176 '.'); 177 if (NULL == end) 178 { 179 /* language was not present, we ONLY have the extension */ 180 end = lang - 1; 181 lang = NULL; 182 } 183 /* finally open template */ 184 fd = open (filename, 185 O_RDONLY); 186 if (-1 == fd) 187 { 188 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 189 "open", 190 filename); 191 return GNUNET_SYSERR; 192 } 193 if (0 != 194 fstat (fd, 195 &sb)) 196 { 197 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 198 "open", 199 filename); 200 GNUNET_break (0 == close (fd)); 201 return GNUNET_OK; 202 } 203 204 reply = MHD_create_response_from_fd (sb.st_size, 205 fd); 206 if (NULL == reply) 207 { 208 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 209 "open", 210 filename); 211 GNUNET_break (0 == close (fd)); 212 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 mime = NULL; 243 for (unsigned int i = 0; NULL != mm[i].ext; i++) 244 if (0 == strcasecmp (mm[i].ext, 245 end)) 246 { 247 mime = mm[i].mime; 248 break; 249 } 250 if (NULL != mime) 251 GNUNET_break (MHD_NO != 252 MHD_add_response_header (reply, 253 MHD_HTTP_HEADER_CONTENT_TYPE, 254 mime)); 255 } 256 257 GNUNET_array_grow (loaded, 258 loaded_length, 259 loaded_length + 1); 260 if (NULL != lang) 261 { 262 GNUNET_asprintf (&loaded[loaded_length - 1].name, 263 "%.*s%s", 264 (int) (lang - name) - 1, 265 name, 266 end); 267 loaded[loaded_length - 1].lang = GNUNET_strndup (lang, 268 end - lang); 269 } 270 else 271 { 272 loaded[loaded_length - 1].name = GNUNET_strdup (name); 273 } 274 loaded[loaded_length - 1].reply = reply; 275 return GNUNET_OK; 276 } 277 278 279 /** 280 * Preload static files. 281 */ 282 enum GNUNET_GenericReturnValue 283 TMH_statics_init () 284 { 285 char *dn; 286 int ret; 287 288 { 289 char *path; 290 291 path = GNUNET_OS_installation_get_path (TALER_MERCHANT_project_data (), 292 GNUNET_OS_IPK_DATADIR); 293 if (NULL == path) 294 { 295 GNUNET_break (0); 296 return GNUNET_SYSERR; 297 } 298 GNUNET_asprintf (&dn, 299 "%smerchant/static/", 300 path); 301 GNUNET_free (path); 302 } 303 ret = GNUNET_DISK_directory_scan (dn, 304 &load_static_file, 305 NULL); 306 if (-1 == ret) 307 { 308 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 309 "Could not load static resources from `%s': %s\n", 310 dn, 311 strerror (errno)); 312 GNUNET_free (dn); 313 return GNUNET_SYSERR; 314 } 315 GNUNET_free (dn); 316 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 get_statics_fini () 329 { 330 for (unsigned int i = 0; i<loaded_length; i++) 331 { 332 GNUNET_free (loaded[i].name); 333 GNUNET_free (loaded[i].lang); 334 MHD_destroy_response (loaded[i].reply); 335 } 336 GNUNET_array_grow (loaded, 337 loaded_length, 338 0); 339 }