mhd_spa.c (8714B)
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 55 56 /** 57 * Resource from the WebUi. 58 */ 59 struct TALER_MHD_Spa 60 { 61 /** 62 * Resources of the WebUI, kept in a DLL. 63 */ 64 struct WebuiFile *webui_head; 65 66 /** 67 * Resources of the WebUI, kept in a DLL. 68 */ 69 struct WebuiFile *webui_tail; 70 }; 71 72 73 enum MHD_Result 74 TALER_MHD_spa_handler (const struct TALER_MHD_Spa *spa, 75 struct MHD_Connection *connection, 76 const char *path) 77 { 78 struct WebuiFile *w = NULL; 79 enum TALER_MHD_CompressionType comp; 80 81 if ( (NULL == path) || 82 (0 == strcmp (path, 83 "")) ) 84 path = "index.html"; 85 for (struct WebuiFile *pos = spa->webui_head; 86 NULL != pos; 87 pos = pos->next) 88 if (0 == strcmp (path, 89 pos->path)) 90 { 91 w = pos; 92 break; 93 } 94 if ( (NULL == w) || 95 (NULL == w->responses[TALER_MHD_CT_NONE]) ) 96 return TALER_MHD_reply_with_error (connection, 97 MHD_HTTP_NOT_FOUND, 98 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 99 path); 100 comp = TALER_MHD_can_compress (connection, 101 TALER_MHD_CT_MAX - 1); 102 GNUNET_assert (comp < TALER_MHD_CT_MAX); 103 GNUNET_assert (comp >= 0); 104 if (NULL != w->responses[comp]) 105 return MHD_queue_response (connection, 106 MHD_HTTP_OK, 107 w->responses[comp]); 108 return MHD_queue_response (connection, 109 MHD_HTTP_OK, 110 w->responses[TALER_MHD_CT_NONE]); 111 } 112 113 114 /** 115 * Function called on each file to load for the WebUI. 116 * 117 * @param cls the `struct TALER_MHD_Spa *` to build 118 * @param dn name of the file to load 119 */ 120 static enum GNUNET_GenericReturnValue 121 build_webui (void *cls, 122 const char *dn) 123 { 124 static const struct 125 { 126 const char *ext; 127 const char *mime; 128 } mime_map[] = { 129 { 130 .ext = "css", 131 .mime = "text/css" 132 }, 133 { 134 .ext = "html", 135 .mime = "text/html" 136 }, 137 { 138 .ext = "js", 139 .mime = "text/javascript" 140 }, 141 { 142 .ext = "jpg", 143 .mime = "image/jpeg" 144 }, 145 { 146 .ext = "jpeg", 147 .mime = "image/jpeg" 148 }, 149 { 150 .ext = "png", 151 .mime = "image/png" 152 }, 153 { 154 .ext = "svg", 155 .mime = "image/svg+xml" 156 }, 157 { 158 .ext = NULL, 159 .mime = NULL 160 }, 161 }; 162 struct TALER_MHD_Spa *spa = cls; 163 int fd; 164 struct stat sb; 165 struct MHD_Response *response; 166 char *ext; 167 const char *mime; 168 const char *slash; 169 char *fn; 170 const char *cts = NULL; 171 enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE; 172 173 slash = strrchr (dn, '/'); 174 if (NULL == slash) 175 { 176 GNUNET_break (0); 177 return GNUNET_SYSERR; 178 } 179 fd = open (dn, 180 O_RDONLY); 181 if (-1 == fd) 182 { 183 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 184 "open", 185 dn); 186 return GNUNET_SYSERR; 187 } 188 if (0 != 189 fstat (fd, 190 &sb)) 191 { 192 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 193 "open", 194 dn); 195 GNUNET_break (0 == close (fd)); 196 return GNUNET_SYSERR; 197 } 198 199 fn = GNUNET_strdup (slash + 1); 200 ext = strrchr (fn, 201 '.'); 202 if (NULL == ext) 203 goto skip; 204 if (0 == strcmp (ext, 205 ".gz")) 206 { 207 cts = "gzip"; 208 ct = TALER_MHD_CT_GZIP; 209 *ext = '\0'; 210 ext = strrchr (fn, '.'); 211 if (NULL == ext) 212 goto skip; 213 } 214 if (0 == strcmp (ext, 215 ".zstd")) 216 { 217 cts = "zstd"; 218 ct = TALER_MHD_CT_ZSTD; 219 *ext = '\0'; 220 ext = strrchr (fn, '.'); 221 if (NULL == ext) 222 goto skip; 223 } 224 ext++; 225 226 mime = NULL; 227 for (unsigned int i = 0; NULL != mime_map[i].ext; i++) 228 if (0 == strcasecmp (ext, 229 mime_map[i].ext)) 230 { 231 mime = mime_map[i].mime; 232 break; 233 } 234 235 response = MHD_create_response_from_fd ( 236 sb.st_size, 237 fd /* FD now owned by MHD! */); 238 if (NULL == response) 239 { 240 /* MHD did not take ownership of the FD after all */ 241 GNUNET_break (0 == close (fd)); 242 GNUNET_free (fn); 243 return GNUNET_SYSERR; 244 } 245 if ( (NULL != cts) && 246 (MHD_NO == 247 MHD_add_response_header (response, 248 MHD_HTTP_HEADER_CONTENT_ENCODING, 249 cts)) ) 250 { 251 GNUNET_break (0); 252 MHD_destroy_response (response); 253 GNUNET_free (fn); 254 return GNUNET_SYSERR; 255 } 256 if (NULL != mime) 257 { 258 GNUNET_break (MHD_YES == 259 MHD_add_response_header (response, 260 MHD_HTTP_HEADER_CONTENT_TYPE, 261 mime)); 262 } 263 /* Which representation we return depends on the Accept-Encoding 264 header of the client, so shared caches must not serve one 265 variant to a client that asked for another. */ 266 GNUNET_break (MHD_YES == 267 MHD_add_response_header (response, 268 MHD_HTTP_HEADER_VARY, 269 MHD_HTTP_HEADER_ACCEPT_ENCODING)); 270 271 { 272 struct WebuiFile *w; 273 274 for (w = spa->webui_head; 275 NULL != w; 276 w = w->next) 277 { 278 if (0 == strcmp (fn, 279 w->path)) 280 break; 281 } 282 if (NULL == w) 283 { 284 w = GNUNET_new (struct WebuiFile); 285 w->path = fn; 286 GNUNET_CONTAINER_DLL_insert (spa->webui_head, 287 spa->webui_tail, 288 w); 289 } 290 else 291 { 292 GNUNET_free (fn); 293 } 294 GNUNET_assert (NULL == w->responses[ct]); 295 w->responses[ct] = response; 296 } 297 return GNUNET_OK; 298 skip: 299 GNUNET_break (0 == close (fd)); 300 GNUNET_free (fn); 301 return GNUNET_OK; 302 } 303 304 305 struct TALER_MHD_Spa * 306 TALER_MHD_spa_load_dir (const char *dn) 307 { 308 struct TALER_MHD_Spa *spa; 309 310 spa = GNUNET_new (struct TALER_MHD_Spa); 311 if (-1 == 312 GNUNET_DISK_directory_scan (dn, 313 &build_webui, 314 spa)) 315 { 316 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 317 "Failed to load WebUI from `%s'\n", 318 dn); 319 TALER_MHD_spa_free (spa); 320 return NULL; 321 } 322 return spa; 323 } 324 325 326 struct TALER_MHD_Spa * 327 TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd, 328 const char *dir) 329 { 330 struct TALER_MHD_Spa *spa; 331 char *dn; 332 char *path; 333 334 path = GNUNET_OS_installation_get_path (pd, 335 GNUNET_OS_IPK_DATADIR); 336 if (NULL == path) 337 { 338 GNUNET_break (0); 339 return NULL; 340 } 341 GNUNET_asprintf (&dn, 342 "%s%s", 343 path, 344 dir); 345 GNUNET_free (path); 346 spa = TALER_MHD_spa_load_dir (dn); 347 GNUNET_free (dn); 348 return spa; 349 } 350 351 352 void 353 TALER_MHD_spa_free (struct TALER_MHD_Spa *spa) 354 { 355 struct WebuiFile *w; 356 357 while (NULL != (w = spa->webui_head)) 358 { 359 GNUNET_CONTAINER_DLL_remove (spa->webui_head, 360 spa->webui_tail, 361 w); 362 for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE; 363 ct < TALER_MHD_CT_MAX; 364 ct++) 365 { 366 if (NULL != w->responses[ct]) 367 { 368 MHD_destroy_response (w->responses[ct]); 369 w->responses[ct] = NULL; 370 } 371 } 372 GNUNET_free (w->path); 373 GNUNET_free (w); 374 } 375 GNUNET_free (spa); 376 }