mhd2_spa.c (9827B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020--2025 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 mhd2_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_mhd2_lib.h" 25 26 27 /** 28 * Resource from the WebUi. 29 */ 30 struct WebuiFile 31 { 32 /** 33 * Kept in a DLL. 34 */ 35 struct WebuiFile *next; 36 37 /** 38 * Kept in a DLL. 39 */ 40 struct WebuiFile *prev; 41 42 /** 43 * Path this resource matches. 44 */ 45 char *path; 46 47 /** 48 * SPA resource, compressed. 49 */ 50 struct MHD_Response *zresponse; 51 52 /** 53 * SPA resource, vanilla. 54 */ 55 struct MHD_Response *response; 56 57 }; 58 59 60 /** 61 * Resource from the WebUi. 62 */ 63 struct TALER_MHD2_Spa 64 { 65 /** 66 * Resources of the WebUI, kept in a DLL. 67 */ 68 struct WebuiFile *webui_head; 69 70 /** 71 * Resources of the WebUI, kept in a DLL. 72 */ 73 struct WebuiFile *webui_tail; 74 }; 75 76 77 const struct MHD_Action * 78 TALER_MHD2_spa_handler (const struct TALER_MHD2_Spa *spa, 79 struct MHD_Request *request, 80 const char *path) 81 { 82 struct WebuiFile *w = NULL; 83 84 if ( (NULL == path) || 85 (0 == strcmp (path, 86 "")) ) 87 path = "index.html"; 88 for (struct WebuiFile *pos = spa->webui_head; 89 NULL != pos; 90 pos = pos->next) 91 if (0 == strcmp (path, 92 pos->path)) 93 { 94 w = pos; 95 break; 96 } 97 if (NULL == w) 98 return TALER_MHD2_reply_with_error (request, 99 MHD_HTTP_STATUS_NOT_FOUND, 100 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 101 path); 102 if ( (MHD_YES == 103 TALER_MHD2_can_compress (request)) && 104 (NULL != w->zresponse) ) 105 return MHD_action_from_response (request, 106 w->zresponse); 107 return MHD_action_from_response (request, 108 w->response); 109 } 110 111 112 /** 113 * Function called on each file to load for the WebUI. 114 * 115 * @param cls the `struct TALER_MHD2_Spa *` to build 116 * @param dn name of the file to load 117 */ 118 static enum GNUNET_GenericReturnValue 119 build_webui (void *cls, 120 const char *dn) 121 { 122 static const struct 123 { 124 const char *ext; 125 const char *mime; 126 } mime_map[] = { 127 { 128 .ext = "css", 129 .mime = "text/css" 130 }, 131 { 132 .ext = "html", 133 .mime = "text/html" 134 }, 135 { 136 .ext = "js", 137 .mime = "text/javascript" 138 }, 139 { 140 .ext = "jpg", 141 .mime = "image/jpeg" 142 }, 143 { 144 .ext = "jpeg", 145 .mime = "image/jpeg" 146 }, 147 { 148 .ext = "png", 149 .mime = "image/png" 150 }, 151 { 152 .ext = "svg", 153 .mime = "image/svg+xml" 154 }, 155 { 156 .ext = NULL, 157 .mime = NULL 158 }, 159 }; 160 struct TALER_MHD2_Spa *spa = cls; 161 int fd; 162 struct stat sb; 163 struct MHD_Response *zresponse = NULL; 164 struct MHD_Response *response; 165 const char *ext; 166 const char *mime; 167 168 fd = open (dn, 169 O_RDONLY); 170 if (-1 == fd) 171 { 172 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 173 "open", 174 dn); 175 return GNUNET_SYSERR; 176 } 177 if (0 != 178 fstat (fd, 179 &sb)) 180 { 181 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 182 "open", 183 dn); 184 GNUNET_break (0 == close (fd)); 185 return GNUNET_SYSERR; 186 } 187 188 mime = NULL; 189 ext = strrchr (dn, '.'); 190 if (NULL == ext) 191 { 192 GNUNET_break (0 == close (fd)); 193 return GNUNET_OK; 194 } 195 ext++; 196 for (unsigned int i = 0; NULL != mime_map[i].ext; i++) 197 if (0 == strcasecmp (ext, 198 mime_map[i].ext)) 199 { 200 mime = mime_map[i].mime; 201 break; 202 } 203 204 { 205 void *in; 206 ssize_t r; 207 size_t csize; 208 209 in = GNUNET_malloc_large (sb.st_size); 210 if (NULL == in) 211 { 212 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 213 "malloc"); 214 GNUNET_break (0 == close (fd)); 215 return GNUNET_SYSERR; 216 } 217 r = read (fd, 218 in, 219 sb.st_size); 220 if ( (-1 == r) || 221 (sb.st_size != (size_t) r) ) 222 { 223 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 224 "read", 225 dn); 226 GNUNET_free (in); 227 GNUNET_break (0 == close (fd)); 228 return GNUNET_SYSERR; 229 } 230 csize = (size_t) r; 231 if (TALER_MHD2_body_compress (&in, 232 &csize)) 233 { 234 zresponse = MHD_response_from_buffer (MHD_HTTP_STATUS_OK, 235 csize, 236 in, 237 &free, 238 in); 239 if (NULL != zresponse) 240 { 241 if (MHD_SC_OK != 242 MHD_response_add_header (zresponse, 243 MHD_HTTP_HEADER_CONTENT_ENCODING, 244 "deflate")) 245 { 246 GNUNET_break (0); 247 MHD_response_destroy (zresponse); 248 zresponse = NULL; 249 } 250 else if (NULL != mime) 251 GNUNET_break (MHD_SC_OK == 252 MHD_response_add_header (zresponse, 253 MHD_HTTP_HEADER_CONTENT_TYPE, 254 mime)); 255 /* Which representation we return depends on the 256 Accept-Encoding header of the client, so shared caches 257 must not serve one variant to a client that asked for 258 another. */ 259 if (NULL != zresponse) 260 GNUNET_break (MHD_SC_OK == 261 MHD_response_add_header (zresponse, 262 MHD_HTTP_HEADER_VARY, 263 MHD_HTTP_HEADER_ACCEPT_ENCODING)); 264 } 265 } 266 else 267 { 268 GNUNET_free (in); 269 } 270 } 271 272 response = MHD_response_from_fd (MHD_HTTP_STATUS_OK, 273 fd, 274 0LLU, 275 sb.st_size); 276 if (NULL == response) 277 { 278 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 279 "open", 280 dn); 281 GNUNET_break (0 == close (fd)); 282 if (NULL != zresponse) 283 { 284 MHD_response_destroy (zresponse); 285 zresponse = NULL; 286 } 287 return GNUNET_SYSERR; 288 } 289 if (NULL != mime) 290 GNUNET_break (MHD_SC_OK == 291 MHD_response_add_header (response, 292 MHD_HTTP_HEADER_CONTENT_TYPE, 293 mime)); 294 GNUNET_break (MHD_SC_OK == 295 MHD_response_add_header (response, 296 MHD_HTTP_HEADER_VARY, 297 MHD_HTTP_HEADER_ACCEPT_ENCODING)); 298 299 { 300 struct WebuiFile *w; 301 const char *fn; 302 303 fn = strrchr (dn, '/'); 304 GNUNET_assert (NULL != fn); 305 w = GNUNET_new (struct WebuiFile); 306 w->path = GNUNET_strdup (fn + 1); 307 w->response = response; 308 w->zresponse = zresponse; 309 GNUNET_assert (MHD_SC_OK == 310 MHD_RESPONSE_SET_OPTIONS (response, 311 MHD_R_OPTION_REUSABLE (MHD_YES))); 312 if (NULL != zresponse) 313 GNUNET_assert (MHD_SC_OK == 314 MHD_RESPONSE_SET_OPTIONS (zresponse, 315 MHD_R_OPTION_REUSABLE (MHD_YES))) 316 ; 317 GNUNET_CONTAINER_DLL_insert (spa->webui_head, 318 spa->webui_tail, 319 w); 320 } 321 return GNUNET_OK; 322 } 323 324 325 struct TALER_MHD2_Spa * 326 TALER_MHD2_spa_load (const struct GNUNET_OS_ProjectData *pd, 327 const char *dir) 328 { 329 struct TALER_MHD2_Spa *spa; 330 char *dn; 331 332 { 333 char *path; 334 335 path = GNUNET_OS_installation_get_path (pd, 336 GNUNET_OS_IPK_DATADIR); 337 if (NULL == path) 338 { 339 GNUNET_break (0); 340 return NULL; 341 } 342 GNUNET_asprintf (&dn, 343 "%s%s", 344 path, 345 dir); 346 GNUNET_free (path); 347 } 348 spa = GNUNET_new (struct TALER_MHD2_Spa); 349 if (-1 == 350 GNUNET_DISK_directory_scan (dn, 351 &build_webui, 352 spa)) 353 { 354 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 355 "Failed to load WebUI from `%s'\n", 356 dn); 357 GNUNET_free (dn); 358 TALER_MHD2_spa_free (spa); 359 return NULL; 360 } 361 GNUNET_free (dn); 362 return spa; 363 } 364 365 366 void 367 TALER_MHD2_spa_free (struct TALER_MHD2_Spa *spa) 368 { 369 struct WebuiFile *w; 370 371 while (NULL != (w = spa->webui_head)) 372 { 373 GNUNET_CONTAINER_DLL_remove (spa->webui_head, 374 spa->webui_tail, 375 w); 376 if (NULL != w->response) 377 { 378 MHD_response_destroy (w->response); 379 w->response = NULL; 380 } 381 if (NULL != w->zresponse) 382 { 383 MHD_response_destroy (w->zresponse); 384 w->zresponse = NULL; 385 } 386 GNUNET_free (w->path); 387 GNUNET_free (w); 388 } 389 GNUNET_free (spa); 390 }