mhd_spa.c (10314B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020, 2023, 2024 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 EXCHANGEABILITY 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 mhd_spa.c 18 * @brief logic to load single page apps 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 <gnunet/gnunet_mhd_compat.h> 26 27 28 /** 29 * Resource from the WebUi. 30 */ 31 struct WebuiFile 32 { 33 /** 34 * Kept in a DLL. 35 */ 36 struct WebuiFile *next; 37 38 /** 39 * Kept in a DLL. 40 */ 41 struct WebuiFile *prev; 42 43 /** 44 * Path this resource matches. 45 */ 46 char *path; 47 48 /** 49 * SPA resource, deflate compressed. 50 */ 51 struct MHD_Response *responses[TALER_MHD_CT_MAX]; 52 53 /** 54 * mmap()'ed file contents served by the respective @e responses, 55 * NULL for empty files. Use munmap() to release. 56 */ 57 void *maps[TALER_MHD_CT_MAX]; 58 59 /** 60 * Sizes of the respective @e maps. 61 */ 62 size_t map_sizes[TALER_MHD_CT_MAX]; 63 64 }; 65 66 67 /** 68 * Resource from the WebUi. 69 */ 70 struct TALER_MHD_Spa 71 { 72 /** 73 * Resources of the WebUI, kept in a DLL. 74 */ 75 struct WebuiFile *webui_head; 76 77 /** 78 * Resources of the WebUI, kept in a DLL. 79 */ 80 struct WebuiFile *webui_tail; 81 }; 82 83 84 enum MHD_Result 85 TALER_MHD_spa_handler (const struct TALER_MHD_Spa *spa, 86 struct MHD_Connection *connection, 87 const char *path) 88 { 89 struct WebuiFile *w = NULL; 90 enum TALER_MHD_CompressionType comp; 91 92 if ( (NULL == path) || 93 (0 == strcmp (path, 94 "")) ) 95 path = "index.html"; 96 for (struct WebuiFile *pos = spa->webui_head; 97 NULL != pos; 98 pos = pos->next) 99 if (0 == strcmp (path, 100 pos->path)) 101 { 102 w = pos; 103 break; 104 } 105 if ( (NULL == w) || 106 (NULL == w->responses[TALER_MHD_CT_NONE]) ) 107 return TALER_MHD_reply_with_error (connection, 108 MHD_HTTP_NOT_FOUND, 109 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 110 path); 111 comp = TALER_MHD_can_compress (connection, 112 TALER_MHD_CT_MAX - 1); 113 GNUNET_assert (comp < TALER_MHD_CT_MAX); 114 GNUNET_assert (comp >= 0); 115 if (NULL != w->responses[comp]) 116 return MHD_queue_response (connection, 117 MHD_HTTP_OK, 118 w->responses[comp]); 119 return MHD_queue_response (connection, 120 MHD_HTTP_OK, 121 w->responses[TALER_MHD_CT_NONE]); 122 } 123 124 125 /** 126 * Function called on each file to load for the WebUI. 127 * 128 * @param cls the `struct TALER_MHD_Spa *` to build 129 * @param dn name of the file to load 130 */ 131 static enum GNUNET_GenericReturnValue 132 build_webui (void *cls, 133 const char *dn) 134 { 135 static const struct 136 { 137 const char *ext; 138 const char *mime; 139 } mime_map[] = { 140 { 141 .ext = "css", 142 .mime = "text/css" 143 }, 144 { 145 .ext = "html", 146 .mime = "text/html" 147 }, 148 { 149 .ext = "js", 150 .mime = "text/javascript" 151 }, 152 { 153 .ext = "jpg", 154 .mime = "image/jpeg" 155 }, 156 { 157 .ext = "jpeg", 158 .mime = "image/jpeg" 159 }, 160 { 161 .ext = "png", 162 .mime = "image/png" 163 }, 164 { 165 .ext = "svg", 166 .mime = "image/svg+xml" 167 }, 168 { 169 .ext = NULL, 170 .mime = NULL 171 }, 172 }; 173 struct TALER_MHD_Spa *spa = cls; 174 int fd; 175 struct stat sb; 176 size_t size; 177 void *buf; 178 struct MHD_Response *response; 179 char *ext; 180 const char *mime; 181 const char *slash; 182 char *fn; 183 const char *cts = NULL; 184 enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE; 185 186 slash = strrchr (dn, '/'); 187 if (NULL == slash) 188 { 189 GNUNET_break (0); 190 return GNUNET_SYSERR; 191 } 192 fd = open (dn, 193 O_RDONLY); 194 if (-1 == fd) 195 { 196 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 197 "open", 198 dn); 199 return GNUNET_SYSERR; 200 } 201 if (0 != 202 fstat (fd, 203 &sb)) 204 { 205 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 206 "open", 207 dn); 208 GNUNET_break (0 == close (fd)); 209 return GNUNET_SYSERR; 210 } 211 if (SIZE_MAX < ((unsigned long long) sb.st_size)) 212 { 213 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 214 "fstat-size", 215 dn); 216 GNUNET_break (0 == close (fd)); 217 return GNUNET_SYSERR; 218 } 219 size = (size_t) sb.st_size; 220 buf = NULL; 221 if (0 != size) 222 { 223 buf = mmap (NULL, 224 size, 225 PROT_READ, 226 MAP_SHARED, 227 fd, 228 0); 229 if (MAP_FAILED == buf) 230 { 231 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 232 "mmap", 233 dn); 234 GNUNET_break (0 == close (fd)); 235 return GNUNET_SYSERR; 236 } 237 } 238 /* The mapping stays valid after the descriptor is closed; do not 239 hold one descriptor per file for the lifetime of the process. */ 240 GNUNET_break (0 == close (fd)); 241 242 fn = GNUNET_strdup (slash + 1); 243 ext = strrchr (fn, 244 '.'); 245 if (NULL == ext) 246 goto skip; 247 if (0 == strcmp (ext, 248 ".gz")) 249 { 250 cts = "gzip"; 251 ct = TALER_MHD_CT_GZIP; 252 *ext = '\0'; 253 ext = strrchr (fn, '.'); 254 if (NULL == ext) 255 goto skip; 256 } 257 if (0 == strcmp (ext, 258 ".zstd")) 259 { 260 cts = "zstd"; 261 ct = TALER_MHD_CT_ZSTD; 262 *ext = '\0'; 263 ext = strrchr (fn, '.'); 264 if (NULL == ext) 265 goto skip; 266 } 267 ext++; 268 269 mime = NULL; 270 for (unsigned int i = 0; NULL != mime_map[i].ext; i++) 271 if (0 == strcasecmp (ext, 272 mime_map[i].ext)) 273 { 274 mime = mime_map[i].mime; 275 break; 276 } 277 278 response = MHD_create_response_from_buffer_static (size, 279 buf); 280 if (NULL == response) 281 { 282 GNUNET_break (0); 283 if (NULL != buf) 284 GNUNET_break (0 == munmap (buf, 285 size)); 286 GNUNET_free (fn); 287 return GNUNET_SYSERR; 288 } 289 if ( (NULL != cts) && 290 (MHD_NO == 291 MHD_add_response_header (response, 292 MHD_HTTP_HEADER_CONTENT_ENCODING, 293 cts)) ) 294 { 295 GNUNET_break (0); 296 MHD_destroy_response (response); 297 if (NULL != buf) 298 GNUNET_break (0 == munmap (buf, 299 size)); 300 GNUNET_free (fn); 301 return GNUNET_SYSERR; 302 } 303 if (NULL != mime) 304 { 305 GNUNET_break (MHD_YES == 306 MHD_add_response_header (response, 307 MHD_HTTP_HEADER_CONTENT_TYPE, 308 mime)); 309 } 310 /* Which representation we return depends on the Accept-Encoding 311 header of the client, so shared caches must not serve one 312 variant to a client that asked for another. */ 313 GNUNET_break (MHD_YES == 314 MHD_add_response_header (response, 315 MHD_HTTP_HEADER_VARY, 316 MHD_HTTP_HEADER_ACCEPT_ENCODING)); 317 318 { 319 struct WebuiFile *w; 320 321 for (w = spa->webui_head; 322 NULL != w; 323 w = w->next) 324 { 325 if (0 == strcmp (fn, 326 w->path)) 327 break; 328 } 329 if (NULL == w) 330 { 331 w = GNUNET_new (struct WebuiFile); 332 w->path = fn; 333 GNUNET_CONTAINER_DLL_insert (spa->webui_head, 334 spa->webui_tail, 335 w); 336 } 337 else 338 { 339 GNUNET_free (fn); 340 } 341 GNUNET_assert (NULL == w->responses[ct]); 342 w->responses[ct] = response; 343 w->maps[ct] = buf; 344 w->map_sizes[ct] = size; 345 } 346 return GNUNET_OK; 347 skip: 348 if (NULL != buf) 349 GNUNET_break (0 == munmap (buf, 350 size)); 351 GNUNET_free (fn); 352 return GNUNET_OK; 353 } 354 355 356 struct TALER_MHD_Spa * 357 TALER_MHD_spa_load_dir (const char *dn) 358 { 359 struct TALER_MHD_Spa *spa; 360 361 spa = GNUNET_new (struct TALER_MHD_Spa); 362 if (-1 == 363 GNUNET_DISK_directory_scan (dn, 364 &build_webui, 365 spa)) 366 { 367 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 368 "Failed to load WebUI from `%s'\n", 369 dn); 370 TALER_MHD_spa_free (spa); 371 return NULL; 372 } 373 return spa; 374 } 375 376 377 struct TALER_MHD_Spa * 378 TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd, 379 const char *dir) 380 { 381 struct TALER_MHD_Spa *spa; 382 char *dn; 383 char *path; 384 385 path = GNUNET_OS_installation_get_path (pd, 386 GNUNET_OS_IPK_DATADIR); 387 if (NULL == path) 388 { 389 GNUNET_break (0); 390 return NULL; 391 } 392 GNUNET_asprintf (&dn, 393 "%s%s", 394 path, 395 dir); 396 GNUNET_free (path); 397 spa = TALER_MHD_spa_load_dir (dn); 398 GNUNET_free (dn); 399 return spa; 400 } 401 402 403 void 404 TALER_MHD_spa_free (struct TALER_MHD_Spa *spa) 405 { 406 struct WebuiFile *w; 407 408 while (NULL != (w = spa->webui_head)) 409 { 410 GNUNET_CONTAINER_DLL_remove (spa->webui_head, 411 spa->webui_tail, 412 w); 413 for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE; 414 ct < TALER_MHD_CT_MAX; 415 ct++) 416 { 417 if (NULL != w->responses[ct]) 418 { 419 MHD_destroy_response (w->responses[ct]); 420 w->responses[ct] = NULL; 421 } 422 if (NULL != w->maps[ct]) 423 { 424 GNUNET_break (0 == munmap (w->maps[ct], 425 w->map_sizes[ct])); 426 w->maps[ct] = NULL; 427 } 428 } 429 GNUNET_free (w->path); 430 GNUNET_free (w); 431 } 432 GNUNET_free (spa); 433 }