mhd_spa.c (8227B)
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 "taler/platform.h" 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 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 { 204 GNUNET_break (0 == close (fd)); 205 GNUNET_free (fn); 206 return GNUNET_OK; 207 } 208 if (0 == strcmp (ext, 209 ".gz")) 210 { 211 cts = "gzip"; 212 ct = TALER_MHD_CT_GZIP; 213 *ext = '\0'; 214 ext = strrchr (fn, '.'); 215 } 216 if (0 == strcmp (ext, 217 ".zstd")) 218 { 219 cts = "zstd"; 220 ct = TALER_MHD_CT_ZSTD; 221 *ext = '\0'; 222 ext = strrchr (fn, '.'); 223 } 224 if (NULL == ext) 225 { 226 GNUNET_break (0 == close (fd)); 227 GNUNET_free (fn); 228 return GNUNET_OK; 229 } 230 ext++; 231 232 mime = NULL; 233 for (unsigned int i = 0; NULL != mime_map[i].ext; i++) 234 if (0 == strcasecmp (ext, 235 mime_map[i].ext)) 236 { 237 mime = mime_map[i].mime; 238 break; 239 } 240 241 response = MHD_create_response_from_fd ( 242 sb.st_size, 243 fd /* FD now owned by MHD! */); 244 if (NULL == response) 245 { 246 GNUNET_free (fn); 247 return GNUNET_SYSERR; 248 } 249 if ( (NULL != cts) && 250 (MHD_NO == 251 MHD_add_response_header (response, 252 MHD_HTTP_HEADER_CONTENT_ENCODING, 253 cts)) ) 254 { 255 GNUNET_break (0); 256 MHD_destroy_response (response); 257 GNUNET_free (fn); 258 return GNUNET_SYSERR; 259 } 260 if (NULL != mime) 261 { 262 GNUNET_break (MHD_YES == 263 MHD_add_response_header (response, 264 MHD_HTTP_HEADER_CONTENT_TYPE, 265 mime)); 266 } 267 268 { 269 struct WebuiFile *w; 270 271 for (w = spa->webui_head; 272 NULL != w; 273 w = w->next) 274 { 275 if (0 == strcmp (fn, 276 w->path)) 277 break; 278 } 279 if (NULL == w) 280 { 281 w = GNUNET_new (struct WebuiFile); 282 w->path = fn; 283 GNUNET_CONTAINER_DLL_insert (spa->webui_head, 284 spa->webui_tail, 285 w); 286 } 287 else 288 { 289 GNUNET_free (fn); 290 } 291 GNUNET_assert (NULL == w->responses[ct]); 292 w->responses[ct] = response; 293 } 294 return GNUNET_OK; 295 } 296 297 298 struct TALER_MHD_Spa * 299 TALER_MHD_spa_load_dir (const char *dn) 300 { 301 struct TALER_MHD_Spa *spa; 302 303 spa = GNUNET_new (struct TALER_MHD_Spa); 304 if (-1 == 305 GNUNET_DISK_directory_scan (dn, 306 &build_webui, 307 spa)) 308 { 309 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 310 "Failed to load WebUI from `%s'\n", 311 dn); 312 TALER_MHD_spa_free (spa); 313 return NULL; 314 } 315 return spa; 316 } 317 318 319 struct TALER_MHD_Spa * 320 TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd, 321 const char *dir) 322 { 323 struct TALER_MHD_Spa *spa; 324 char *dn; 325 char *path; 326 327 path = GNUNET_OS_installation_get_path (pd, 328 GNUNET_OS_IPK_DATADIR); 329 if (NULL == path) 330 { 331 GNUNET_break (0); 332 return NULL; 333 } 334 GNUNET_asprintf (&dn, 335 "%s%s", 336 path, 337 dir); 338 GNUNET_free (path); 339 spa = TALER_MHD_spa_load_dir (dn); 340 GNUNET_free (dn); 341 return spa; 342 } 343 344 345 void 346 TALER_MHD_spa_free (struct TALER_MHD_Spa *spa) 347 { 348 struct WebuiFile *w; 349 350 while (NULL != (w = spa->webui_head)) 351 { 352 GNUNET_CONTAINER_DLL_remove (spa->webui_head, 353 spa->webui_tail, 354 w); 355 for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE; 356 ct < TALER_MHD_CT_MAX; 357 ct++) 358 { 359 if (NULL != w->responses[ct]) 360 { 361 MHD_destroy_response (w->responses[ct]); 362 w->responses[ct] = NULL; 363 } 364 } 365 GNUNET_free (w->path); 366 GNUNET_free (w); 367 } 368 GNUNET_free (spa); 369 }