mhd_spa.c (8222B)
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 GNUNET_free (fn); 241 return GNUNET_SYSERR; 242 } 243 if ( (NULL != cts) && 244 (MHD_NO == 245 MHD_add_response_header (response, 246 MHD_HTTP_HEADER_CONTENT_ENCODING, 247 cts)) ) 248 { 249 GNUNET_break (0); 250 MHD_destroy_response (response); 251 GNUNET_free (fn); 252 return GNUNET_SYSERR; 253 } 254 if (NULL != mime) 255 { 256 GNUNET_break (MHD_YES == 257 MHD_add_response_header (response, 258 MHD_HTTP_HEADER_CONTENT_TYPE, 259 mime)); 260 } 261 262 { 263 struct WebuiFile *w; 264 265 for (w = spa->webui_head; 266 NULL != w; 267 w = w->next) 268 { 269 if (0 == strcmp (fn, 270 w->path)) 271 break; 272 } 273 if (NULL == w) 274 { 275 w = GNUNET_new (struct WebuiFile); 276 w->path = fn; 277 GNUNET_CONTAINER_DLL_insert (spa->webui_head, 278 spa->webui_tail, 279 w); 280 } 281 else 282 { 283 GNUNET_free (fn); 284 } 285 GNUNET_assert (NULL == w->responses[ct]); 286 w->responses[ct] = response; 287 } 288 return GNUNET_OK; 289 skip: 290 GNUNET_break (0 == close (fd)); 291 GNUNET_free (fn); 292 return GNUNET_OK; 293 } 294 295 296 struct TALER_MHD_Spa * 297 TALER_MHD_spa_load_dir (const char *dn) 298 { 299 struct TALER_MHD_Spa *spa; 300 301 spa = GNUNET_new (struct TALER_MHD_Spa); 302 if (-1 == 303 GNUNET_DISK_directory_scan (dn, 304 &build_webui, 305 spa)) 306 { 307 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 308 "Failed to load WebUI from `%s'\n", 309 dn); 310 TALER_MHD_spa_free (spa); 311 return NULL; 312 } 313 return spa; 314 } 315 316 317 struct TALER_MHD_Spa * 318 TALER_MHD_spa_load (const struct GNUNET_OS_ProjectData *pd, 319 const char *dir) 320 { 321 struct TALER_MHD_Spa *spa; 322 char *dn; 323 char *path; 324 325 path = GNUNET_OS_installation_get_path (pd, 326 GNUNET_OS_IPK_DATADIR); 327 if (NULL == path) 328 { 329 GNUNET_break (0); 330 return NULL; 331 } 332 GNUNET_asprintf (&dn, 333 "%s%s", 334 path, 335 dir); 336 GNUNET_free (path); 337 spa = TALER_MHD_spa_load_dir (dn); 338 GNUNET_free (dn); 339 return spa; 340 } 341 342 343 void 344 TALER_MHD_spa_free (struct TALER_MHD_Spa *spa) 345 { 346 struct WebuiFile *w; 347 348 while (NULL != (w = spa->webui_head)) 349 { 350 GNUNET_CONTAINER_DLL_remove (spa->webui_head, 351 spa->webui_tail, 352 w); 353 for (enum TALER_MHD_CompressionType ct = TALER_MHD_CT_NONE; 354 ct < TALER_MHD_CT_MAX; 355 ct++) 356 { 357 if (NULL != w->responses[ct]) 358 { 359 MHD_destroy_response (w->responses[ct]); 360 w->responses[ct] = NULL; 361 } 362 } 363 GNUNET_free (w->path); 364 GNUNET_free (w); 365 } 366 GNUNET_free (spa); 367 }