mhd2_spa.c (9095B)
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 "taler/platform.h" 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 if (NULL != mime) 251 GNUNET_break (MHD_SC_OK == 252 MHD_response_add_header (zresponse, 253 MHD_HTTP_HEADER_CONTENT_TYPE, 254 mime)); 255 } 256 } 257 else 258 { 259 GNUNET_free (in); 260 } 261 } 262 263 response = MHD_response_from_fd (MHD_HTTP_STATUS_OK, 264 fd, 265 0LLU, 266 sb.st_size); 267 if (NULL == response) 268 { 269 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 270 "open", 271 dn); 272 GNUNET_break (0 == close (fd)); 273 if (NULL != zresponse) 274 { 275 MHD_response_destroy (zresponse); 276 zresponse = NULL; 277 } 278 return GNUNET_SYSERR; 279 } 280 if (NULL != mime) 281 GNUNET_break (MHD_SC_OK == 282 MHD_response_add_header (response, 283 MHD_HTTP_HEADER_CONTENT_TYPE, 284 mime)); 285 286 { 287 struct WebuiFile *w; 288 const char *fn; 289 290 fn = strrchr (dn, '/'); 291 GNUNET_assert (NULL != fn); 292 w = GNUNET_new (struct WebuiFile); 293 w->path = GNUNET_strdup (fn + 1); 294 w->response = response; 295 w->zresponse = zresponse; 296 GNUNET_assert (MHD_SC_OK == 297 MHD_RESPONSE_SET_OPTIONS (response, 298 MHD_R_OPTION_REUSABLE (MHD_YES))); 299 if (NULL != zresponse) 300 GNUNET_assert (MHD_SC_OK == 301 MHD_RESPONSE_SET_OPTIONS (zresponse, 302 MHD_R_OPTION_REUSABLE (MHD_YES))) 303 ; 304 GNUNET_CONTAINER_DLL_insert (spa->webui_head, 305 spa->webui_tail, 306 w); 307 } 308 return GNUNET_OK; 309 } 310 311 312 struct TALER_MHD2_Spa * 313 TALER_MHD2_spa_load (const struct GNUNET_OS_ProjectData *pd, 314 const char *dir) 315 { 316 struct TALER_MHD2_Spa *spa; 317 char *dn; 318 319 { 320 char *path; 321 322 path = GNUNET_OS_installation_get_path (pd, 323 GNUNET_OS_IPK_DATADIR); 324 if (NULL == path) 325 { 326 GNUNET_break (0); 327 return NULL; 328 } 329 GNUNET_asprintf (&dn, 330 "%s%s", 331 path, 332 dir); 333 GNUNET_free (path); 334 } 335 spa = GNUNET_new (struct TALER_MHD2_Spa); 336 if (-1 == 337 GNUNET_DISK_directory_scan (dn, 338 &build_webui, 339 spa)) 340 { 341 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 342 "Failed to load WebUI from `%s'\n", 343 dn); 344 GNUNET_free (dn); 345 TALER_MHD2_spa_free (spa); 346 return NULL; 347 } 348 GNUNET_free (dn); 349 return spa; 350 } 351 352 353 void 354 TALER_MHD2_spa_free (struct TALER_MHD2_Spa *spa) 355 { 356 struct WebuiFile *w; 357 358 while (NULL != (w = spa->webui_head)) 359 { 360 GNUNET_CONTAINER_DLL_remove (spa->webui_head, 361 spa->webui_tail, 362 w); 363 if (NULL != w->response) 364 { 365 MHD_response_destroy (w->response); 366 w->response = NULL; 367 } 368 if (NULL != w->zresponse) 369 { 370 MHD_response_destroy (w->zresponse); 371 w->zresponse = NULL; 372 } 373 GNUNET_free (w->path); 374 GNUNET_free (w); 375 } 376 GNUNET_free (spa); 377 }