paivana-httpd_templates.c (12670B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2026 Taler Systems SA 4 5 Paivana is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 8 3, or (at your option) any later version. 9 10 Paivana is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty 12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 13 the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with Paivana; see the file COPYING. If not, 17 write to the Free Software Foundation, Inc., 51 Franklin 18 Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @author Christian Grothoff 23 * @file paivana-httpd_templates.c 24 * @brief template functions 25 */ 26 #include "platform.h" 27 #include <curl/curl.h> 28 #include <gnunet/gnunet_util_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include "paivana-httpd.h" 31 #include "paivana-httpd_daemon.h" 32 #include "paivana-httpd_templates.h" 33 #include <taler/taler_mhd_lib.h> 34 #include <taler/taler_templating_lib.h> 35 #include "paivana_pd.h" 36 #include <regex.h> 37 38 39 struct Template; 40 #define TALER_MERCHANT_GET_PRIVATE_TEMPLATE_RESULT_CLOSURE struct Template 41 #include <taler/merchant/get-private-templates-TEMPLATE_ID.h> 42 #include <taler/merchant/get-private-templates.h> 43 44 45 /** 46 * Entry in the cache of responses for a given template. 47 */ 48 struct ResponseCacheEntry 49 { 50 51 /** 52 * Kept in a DLL. 53 */ 54 struct ResponseCacheEntry *next; 55 56 /** 57 * Kept in a DLL. 58 */ 59 struct ResponseCacheEntry *prev; 60 61 /** 62 * Language of the response. 63 */ 64 char *lang; 65 66 /** 67 * Accept-Encoding of the response. 68 */ 69 char *ae; 70 71 /** 72 * Paywall response for these request parameters. 73 */ 74 struct MHD_Response *paywall; 75 76 /** 77 * HTTP status to return with @e paywall. 78 */ 79 unsigned int http_status; 80 81 }; 82 83 84 /** 85 * Information about a template in the merchant backend. 86 */ 87 struct Template 88 { 89 90 /** 91 * Kept in a DLL. 92 */ 93 struct Template *next; 94 95 /** 96 * Kept in a DLL. 97 */ 98 struct Template *prev; 99 100 /** 101 * ID of the template. 102 */ 103 char *template_id; 104 105 /** 106 * Maximum pickup delay for the pages. 107 */ 108 struct GNUNET_TIME_Relative max_pickup_delay; 109 110 /** 111 * Regular expression of websites the template is for. 112 */ 113 char *regex; 114 115 /** 116 * Pre-compiled regular expression @e regex. 117 */ 118 regex_t ex; 119 120 /** 121 * Handle used to request more information about the template. 122 */ 123 struct TALER_MERCHANT_GetPrivateTemplateHandle *gt; 124 125 /** 126 * Kept in a DLL. 127 */ 128 struct ResponseCacheEntry *rce_head; 129 130 /** 131 * Kept in a DLL. 132 */ 133 struct ResponseCacheEntry *rce_tail; 134 135 }; 136 137 138 /** 139 * Kept in a DLL. 140 */ 141 static struct Template *t_head; 142 143 /** 144 * Kept in a DLL. 145 */ 146 static struct Template *t_tail; 147 148 /** 149 * Handle to get all the templates. 150 */ 151 static struct TALER_MERCHANT_GetPrivateTemplatesHandle *gpt; 152 153 154 /** 155 * Check if two strings are equal, including both being NULL 156 * 157 * @param s1 a string, possibly NULL 158 * @param s2 a string. possibly NULL 159 * @return true if both are equal 160 */ 161 static bool 162 eq (const char *s1, 163 const char *s2) 164 { 165 if (s1 == s2) 166 return true; 167 if (NULL == s1) 168 return false; 169 if (NULL == s2) 170 return false; 171 return (0 == strcmp (s1, 172 s2)); 173 } 174 175 176 /** 177 * Try to initialize the paywall response. 178 * 179 * @param conn connection to create the response for 180 * @param t template template to create the response for 181 * @return MHD status code to return 182 */ 183 static enum MHD_Result 184 load_paywall (struct MHD_Connection *conn, 185 struct Template *t) 186 { 187 struct MHD_Response *reply; 188 const char *lang; 189 const char *ae; 190 unsigned int http_status = MHD_HTTP_PAYMENT_REQUIRED; 191 192 lang = MHD_lookup_connection_value (conn, 193 MHD_HEADER_KIND, 194 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 195 ae = MHD_lookup_connection_value (conn, 196 MHD_HEADER_KIND, 197 MHD_HTTP_HEADER_ACCEPT_ENCODING); 198 for (struct ResponseCacheEntry *pos = t->rce_head; 199 NULL != pos; 200 pos = pos->next) 201 { 202 if ( (eq (lang, 203 pos->lang)) && 204 (eq (ae, 205 pos->ae) ) ) 206 return MHD_queue_response (conn, 207 pos->http_status, 208 pos->paywall); 209 } 210 211 { 212 enum GNUNET_GenericReturnValue ret; 213 json_t *data; 214 215 data = GNUNET_JSON_PACK ( 216 GNUNET_JSON_pack_string ( 217 "template_id", 218 t->template_id), 219 GNUNET_JSON_pack_uint64 ( 220 "max_pickup_delay", 221 t->max_pickup_delay.rel_value_us / 1000LLU / 1000LLU), 222 GNUNET_JSON_pack_string ( 223 "merchant_backend", 224 PH_merchant_base_url)); 225 ret = TALER_TEMPLATING_build (conn, 226 &http_status, 227 "paywall", 228 NULL /* no instance */, 229 NULL, /* FIXME: no Taler URI!? */ 230 data, 231 &reply); 232 if (GNUNET_OK != ret) 233 { 234 GNUNET_break (0); 235 json_decref (data); 236 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 237 } 238 json_decref (data); 239 } 240 241 GNUNET_break (MHD_YES == 242 MHD_add_response_header (reply, 243 MHD_HTTP_HEADER_CONTENT_TYPE, 244 "text/html")); 245 // FIXME: set Vary and other cache control headers! 246 { 247 struct ResponseCacheEntry *rce; 248 249 rce = GNUNET_new (struct ResponseCacheEntry); 250 if (NULL != lang) 251 rce->lang = GNUNET_strdup (lang); 252 if (NULL != ae) 253 rce->ae = GNUNET_strdup (ae); 254 rce->paywall = reply; 255 rce->http_status = http_status; 256 GNUNET_CONTAINER_DLL_insert (t->rce_head, 257 t->rce_tail, 258 rce); 259 return MHD_queue_response (conn, 260 rce->http_status, 261 reply); 262 } 263 } 264 265 266 /** 267 * Parse template contract to (mostly) determine the 268 * regex specifying which websites the template applies to. 269 * 270 * @param[in,out] t template to update 271 * @param contract contract to parse 272 */ 273 static void 274 parse_template (struct Template *t, 275 const json_t *contract) 276 { 277 const char *regex = NULL; 278 struct GNUNET_JSON_Specification spec[] = { 279 GNUNET_JSON_spec_mark_optional ( 280 GNUNET_JSON_spec_string ("website_regex", 281 ®ex), 282 NULL), 283 GNUNET_JSON_spec_end () 284 }; 285 const char *en; 286 287 if (GNUNET_OK != 288 GNUNET_JSON_parse ((json_t *) contract, 289 spec, 290 &en, 291 NULL)) 292 { 293 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 294 "Invalid template %s at field %s\n", 295 t->template_id, 296 en); 297 return; 298 } 299 if (0 != regcomp (&t->ex, 300 regex, 301 REG_NOSUB | REG_EXTENDED)) 302 { 303 GNUNET_break_op (0); 304 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 305 "Invalid regex in template %s: %s\n", 306 t->template_id, 307 regex); 308 return; 309 } 310 t->regex = GNUNET_strdup (regex); 311 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 312 "Using payment template %s for `%s'\n", 313 t->template_id, 314 regex); 315 } 316 317 318 /** 319 * Callback for a GET /private/templates/$TEMPLATE_ID request. 320 * 321 * @param cls closure 322 * @param tgr response details 323 */ 324 static void 325 setup_template ( 326 struct Template *t, 327 const struct TALER_MERCHANT_GetPrivateTemplateResponse *tgr) 328 { 329 t->gt = NULL; 330 switch (tgr->hr.http_status) 331 { 332 case MHD_HTTP_OK: 333 parse_template (t, 334 tgr->details.ok.template_contract); 335 break; 336 default: 337 GNUNET_break (0); 338 break; 339 } 340 for (struct Template *p = t_head; NULL != p; p = p->next) 341 if (NULL != p->gt) 342 return; 343 /* all templates done, continue with main logic */ 344 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 345 "Templates loaded, starting to serve requests\n"); 346 PAIVANA_HTTPD_serve_requests (); 347 } 348 349 350 /** 351 * Callback for a GET /private/templates request. 352 * 353 * @param cls closure 354 * @param tgr response details 355 */ 356 static void 357 check_templates ( 358 void *cls, 359 const struct TALER_MERCHANT_GetPrivateTemplatesResponse *tgr) 360 { 361 gpt = NULL; 362 switch (tgr->hr.http_status) 363 { 364 case MHD_HTTP_OK: 365 break; 366 case MHD_HTTP_NO_CONTENT: 367 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 368 "No templates found, starting to serve requests\n"); 369 PAIVANA_HTTPD_serve_requests (); 370 return; 371 case MHD_HTTP_UNAUTHORIZED: 372 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 373 "Access to templates unauthorized: %s\n", 374 TALER_ErrorCode_get_hint (tgr->hr.ec)); 375 PH_global_ret = EXIT_FAILURE; 376 GNUNET_SCHEDULER_shutdown (); 377 return; 378 default: 379 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 380 "Unexpected HTTP status code %u on GET /private/templates (%d)\n", 381 tgr->hr.http_status, 382 (int) tgr->hr.ec); 383 PH_global_ret = EXIT_FAILURE; 384 GNUNET_SCHEDULER_shutdown (); 385 return; 386 } 387 if (0 == tgr->details.ok.templates_length) 388 { 389 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 390 "No templates found, starting to serve requests\n"); 391 PAIVANA_HTTPD_serve_requests (); 392 return; 393 } 394 395 for (unsigned int i = 0; i<tgr->details.ok.templates_length; i++) 396 { 397 const struct TALER_MERCHANT_GetPrivateTemplatesTemplateEntry *te 398 = &tgr->details.ok.templates[i]; 399 struct Template *t; 400 401 t = GNUNET_new (struct Template); 402 t->template_id = GNUNET_strdup (te->template_id); 403 t->gt = TALER_MERCHANT_get_private_template_create (PH_ctx, 404 PH_merchant_base_url, 405 t->template_id); 406 GNUNET_CONTAINER_DLL_insert (t_head, 407 t_tail, 408 t); 409 GNUNET_assert ( 410 TALER_EC_NONE == 411 TALER_MERCHANT_get_private_template_start (t->gt, 412 &setup_template, 413 t)); 414 } 415 } 416 417 418 void 419 PAIVANA_HTTPD_load_templates () 420 { 421 gpt = TALER_MERCHANT_get_private_templates_create (PH_ctx, 422 PH_merchant_base_url); 423 GNUNET_assert (NULL != gpt); 424 GNUNET_assert ( 425 TALER_EC_NONE == 426 TALER_MERCHANT_get_private_templates_start (gpt, 427 &check_templates, 428 NULL)); 429 } 430 431 432 enum GNUNET_GenericReturnValue 433 PAIVANA_HTTPD_search_templates (struct MHD_Connection *connection, 434 const char *website) 435 { 436 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 437 "Searching templates for `%s'\n", 438 website); 439 for (struct Template *t = t_head; NULL != t; t = t->next) 440 { 441 if (NULL == t->regex) 442 continue; 443 if (0 == regexec (&t->ex, 444 website, 445 0, NULL, 446 0)) 447 { 448 enum MHD_Result ret; 449 450 ret = load_paywall (connection, 451 t); 452 return (MHD_YES == ret) ? GNUNET_OK : GNUNET_NO; 453 } 454 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 455 "Request for %s did not match template %s\n", 456 website, 457 t->template_id); 458 } 459 return GNUNET_SYSERR; 460 } 461 462 463 /** 464 * Unload all of the template state. 465 */ 466 void 467 PAIVANA_HTTPD_unload_templates () 468 { 469 while (NULL != t_head) 470 { 471 struct Template *t = t_head; 472 473 while (NULL != t->rce_head) 474 { 475 struct ResponseCacheEntry *rce = t->rce_head; 476 477 GNUNET_CONTAINER_DLL_remove (t->rce_head, 478 t->rce_tail, 479 rce); 480 MHD_destroy_response (rce->paywall); 481 GNUNET_free (rce->ae); 482 GNUNET_free (rce->lang); 483 GNUNET_free (rce); 484 } 485 GNUNET_CONTAINER_DLL_remove (t_head, 486 t_tail, 487 t); 488 if (NULL != t->gt) 489 TALER_MERCHANT_get_private_template_cancel (t->gt); 490 if (NULL != t->regex) 491 { 492 regfree (&t->ex); 493 GNUNET_free (t->regex); 494 } 495 GNUNET_free (t->template_id); 496 GNUNET_free (t); 497 } 498 if (NULL != gpt) 499 { 500 TALER_MERCHANT_get_private_templates_cancel (gpt); 501 gpt = NULL; 502 } 503 }