mhd2_responses.c (13731B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-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 Affero 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 MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file mhd2_responses.c 18 * @brief API for generating HTTP replies 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "platform.h" 24 #include <zlib.h> 25 #include "taler/taler_util.h" 26 #include "taler/taler_mhd2_lib.h" 27 28 29 /** 30 * Global options for response generation. 31 */ 32 static enum TALER_MHD2_GlobalOptions TM_go; 33 34 35 void 36 TALER_MHD2_setup (enum TALER_MHD2_GlobalOptions go) 37 { 38 TM_go = go; 39 } 40 41 42 void 43 TALER_MHD2_add_global_headers (struct MHD_Response *response, 44 bool allow_store) 45 { 46 if (0 != (TM_go & TALER_MHD2_GO_FORCE_CONNECTION_CLOSE)) 47 GNUNET_break (MHD_SC_OK == 48 MHD_response_add_header (response, 49 MHD_HTTP_HEADER_CONNECTION, 50 "close")); 51 /* The wallet, operating from a background page, needs CORS to 52 be disabled otherwise browsers block access. */ 53 GNUNET_break (MHD_SC_OK == 54 MHD_response_add_header (response, 55 MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, 56 "*")); 57 GNUNET_break (MHD_SC_OK == 58 MHD_response_add_header (response, 59 /* Not available as MHD constant yet */ 60 "Access-Control-Expose-Headers", 61 "*")); 62 if (! allow_store) 63 GNUNET_break (MHD_SC_OK == 64 MHD_response_add_header (response, 65 MHD_HTTP_HEADER_CACHE_CONTROL, 66 "no-store")); 67 } 68 69 70 bool 71 TALER_MHD2_can_compress (struct MHD_Request *request) 72 { 73 struct MHD_StringNullable aeb; 74 const char *ae; 75 const char *de; 76 77 if (0 != (TM_go & TALER_MHD2_GO_DISABLE_COMPRESSION)) 78 return MHD_NO; 79 if ( (MHD_NO == 80 MHD_request_get_value (request, 81 MHD_VK_HEADER, 82 MHD_HTTP_HEADER_ACCEPT_ENCODING, 83 &aeb)) || 84 (NULL == aeb.cstr) ) 85 return false; 86 ae = aeb.cstr; 87 if (0 == strcmp (ae, 88 "*")) 89 return true; 90 de = strstr (ae, 91 "deflate"); 92 if (NULL == de) 93 return false; 94 if ( ( (de == ae) || 95 (de[-1] == ',') || 96 (de[-1] == ' ') ) && 97 ( (de[strlen ("deflate")] == '\0') || 98 (de[strlen ("deflate")] == ',') || 99 (de[strlen ("deflate")] == ';') ) ) 100 return true; 101 return false; 102 } 103 104 105 bool 106 TALER_MHD2_body_compress (void **buf, 107 size_t *buf_size) 108 { 109 Bytef *cbuf; 110 uLongf cbuf_size; 111 int ret; 112 113 cbuf_size = compressBound (*buf_size); 114 cbuf = malloc (cbuf_size); 115 if (NULL == cbuf) 116 return false; 117 ret = compress (cbuf, 118 &cbuf_size, 119 (const Bytef *) *buf, 120 *buf_size); 121 if ( (Z_OK != ret) || 122 (cbuf_size >= *buf_size) ) 123 { 124 /* compression failed */ 125 free (cbuf); 126 return false; 127 } 128 free (*buf); 129 *buf = (void *) cbuf; 130 *buf_size = (size_t) cbuf_size; 131 return true; 132 } 133 134 135 struct MHD_Response * 136 TALER_MHD2_make_json (enum MHD_HTTP_StatusCode sc, 137 const json_t *json) 138 { 139 struct MHD_Response *response; 140 char *json_str; 141 142 json_str = json_dumps (json, 143 JSON_INDENT (2)); 144 if (NULL == json_str) 145 { 146 GNUNET_break (0); 147 return NULL; 148 } 149 response = MHD_response_from_buffer (sc, 150 strlen (json_str), 151 json_str, 152 &free, 153 json_str); 154 if (NULL == response) 155 { 156 free (json_str); 157 GNUNET_break (0); 158 return NULL; 159 } 160 TALER_MHD2_add_global_headers (response, 161 false); 162 GNUNET_break (MHD_SC_OK == 163 MHD_response_add_header (response, 164 MHD_HTTP_HEADER_CONTENT_TYPE, 165 "application/json")); 166 return response; 167 } 168 169 170 struct MHD_Response * 171 TALER_MHD2_make_json_steal (enum MHD_HTTP_StatusCode sc, 172 json_t *json) 173 { 174 struct MHD_Response *res; 175 176 res = TALER_MHD2_make_json (sc, 177 json); 178 json_decref (json); 179 return res; 180 } 181 182 183 const struct MHD_Action * 184 TALER_MHD2_reply_json (struct MHD_Request *request, 185 const json_t *json, 186 enum MHD_HTTP_StatusCode sc) 187 { 188 struct MHD_Response *response; 189 void *json_str; 190 size_t json_len; 191 bool is_compressed; 192 193 json_str = json_dumps (json, 194 JSON_INDENT (2)); 195 if (NULL == json_str) 196 { 197 /** 198 * This log helps to figure out which 199 * function called this one and assert-failed. 200 */ 201 TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n", 202 sc); 203 204 GNUNET_assert (0); 205 return MHD_action_abort_request (request); 206 } 207 json_len = strlen (json_str); 208 /* try to compress the body */ 209 is_compressed = MHD_NO; 210 if (TALER_MHD2_can_compress (request)) 211 is_compressed = TALER_MHD2_body_compress (&json_str, 212 &json_len); 213 response = MHD_response_from_buffer (sc, 214 json_len, 215 json_str, 216 &free, 217 json_str); 218 if (NULL == response) 219 { 220 free (json_str); 221 GNUNET_break (0); 222 return MHD_action_abort_request (request); 223 } 224 TALER_MHD2_add_global_headers (response, 225 false); 226 GNUNET_break (MHD_SC_OK == 227 MHD_response_add_header (response, 228 MHD_HTTP_HEADER_CONTENT_TYPE, 229 "application/json")); 230 if (is_compressed) 231 { 232 /* Need to indicate to client that body is compressed */ 233 if (MHD_SC_OK != 234 MHD_response_add_header (response, 235 MHD_HTTP_HEADER_CONTENT_ENCODING, 236 "deflate")) 237 { 238 GNUNET_break (0); 239 MHD_response_destroy (response); 240 return MHD_action_abort_request (request); 241 } 242 } 243 244 return MHD_action_from_response (request, 245 response); 246 } 247 248 249 const struct MHD_Action * 250 TALER_MHD2_reply_json_steal (struct MHD_Request *request, 251 json_t *json, 252 enum MHD_HTTP_StatusCode sc) 253 { 254 const struct MHD_Action *ret; 255 256 ret = TALER_MHD2_reply_json (request, 257 json, 258 sc); 259 json_decref (json); 260 return ret; 261 } 262 263 264 const struct MHD_Action * 265 TALER_MHD2_reply_cors_preflight (struct MHD_Request *request) 266 { 267 struct MHD_Response *response; 268 269 response = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT); 270 if (NULL == response) 271 return MHD_action_abort_request (request); 272 /* This adds the Access-Control-Allow-Origin header. 273 * All endpoints of the exchange allow CORS. */ 274 TALER_MHD2_add_global_headers (response, 275 true); 276 GNUNET_break (MHD_SC_OK == 277 MHD_response_add_header (response, 278 /* Not available as MHD constant yet */ 279 "Access-Control-Allow-Headers", 280 "*")); 281 GNUNET_break (MHD_SC_OK == 282 MHD_response_add_header (response, 283 /* Not available as MHD constant yet */ 284 "Access-Control-Allow-Methods", 285 "*")); 286 return MHD_action_from_response (request, 287 response); 288 } 289 290 291 struct MHD_Response * 292 TALER_MHD2_make_error (enum TALER_ErrorCode ec, 293 const char *detail) 294 { 295 return TALER_MHD2_MAKE_JSON_PACK ( 296 TALER_ErrorCode_get_http_status (ec), 297 TALER_MHD2_PACK_EC (ec), 298 GNUNET_JSON_pack_allow_null ( 299 GNUNET_JSON_pack_string ("detail", detail))); 300 } 301 302 303 const struct MHD_Action * 304 TALER_MHD2_reply_with_error (struct MHD_Request *request, 305 enum MHD_HTTP_StatusCode sc, 306 enum TALER_ErrorCode ec, 307 const char *detail) 308 { 309 return TALER_MHD2_REPLY_JSON_PACK ( 310 request, 311 sc, 312 TALER_MHD2_PACK_EC (ec), 313 GNUNET_JSON_pack_allow_null ( 314 GNUNET_JSON_pack_string ("detail", detail))); 315 } 316 317 318 const struct MHD_Action * 319 TALER_MHD2_reply_with_ec (struct MHD_Request *request, 320 enum TALER_ErrorCode ec, 321 const char *detail) 322 { 323 unsigned int hc 324 = TALER_ErrorCode_get_http_status (ec); 325 326 if ( (0 == hc) || 327 (UINT_MAX == hc) ) 328 { 329 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 330 "Invalid Taler error code %u provided for response!\n", 331 (unsigned int) ec); 332 hc = MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR; 333 } 334 return TALER_MHD2_reply_with_error (request, 335 (enum MHD_HTTP_StatusCode) hc, 336 ec, 337 detail); 338 } 339 340 341 const struct MHD_Action * 342 TALER_MHD2_reply_request_too_large (struct MHD_Request *request) 343 { 344 return TALER_MHD2_reply_with_error ( 345 request, 346 MHD_HTTP_STATUS_CONTENT_TOO_LARGE, 347 TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT, 348 NULL); 349 } 350 351 352 const struct MHD_Action * 353 TALER_MHD2_reply_agpl (struct MHD_Request *request, 354 const char *url) 355 { 356 const char *agpl = 357 "This server is licensed under the Affero GPL. You will now be redirected to the source code."; 358 struct MHD_Response *response; 359 360 response = MHD_response_from_buffer_static (MHD_HTTP_STATUS_FOUND, 361 strlen (agpl), 362 (void *) agpl); 363 if (NULL == response) 364 { 365 GNUNET_break (0); 366 return MHD_action_abort_request (request); 367 } 368 TALER_MHD2_add_global_headers (response, 369 true); 370 GNUNET_break (MHD_SC_OK == 371 MHD_response_add_header (response, 372 MHD_HTTP_HEADER_CONTENT_TYPE, 373 "text/plain")); 374 if (MHD_SC_OK == 375 MHD_response_add_header (response, 376 MHD_HTTP_HEADER_LOCATION, 377 url)) 378 { 379 GNUNET_break (0); 380 MHD_response_destroy (response); 381 return MHD_action_abort_request (request); 382 } 383 return MHD_action_from_response (request, 384 response); 385 } 386 387 388 const struct MHD_Action * 389 TALER_MHD2_reply_static (struct MHD_Request *request, 390 enum MHD_HTTP_StatusCode sc, 391 const char *mime_type, 392 const char *body, 393 size_t body_size) 394 { 395 struct MHD_Response *response; 396 397 response = MHD_response_from_buffer_static (sc, 398 body_size, 399 body); 400 if (NULL == response) 401 { 402 GNUNET_break (0); 403 return MHD_action_abort_request (request); 404 } 405 TALER_MHD2_add_global_headers (response, 406 true); 407 if (NULL != mime_type) 408 GNUNET_break (MHD_SC_OK == 409 MHD_response_add_header (response, 410 MHD_HTTP_HEADER_CONTENT_TYPE, 411 mime_type)); 412 return MHD_action_from_response (request, 413 response); 414 } 415 416 417 void 418 TALER_MHD2_get_date_string (struct GNUNET_TIME_Absolute at, 419 char date[128]) 420 { 421 static const char *const days[] = 422 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 423 static const char *const mons[] = 424 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", 425 "Nov", "Dec"}; 426 struct tm now; 427 time_t t; 428 #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ 429 ! defined(HAVE_GMTIME_R) 430 struct tm*pNow; 431 #endif 432 433 date[0] = 0; 434 t = (time_t) (at.abs_value_us / 1000LL / 1000LL); 435 #if defined(HAVE_C11_GMTIME_S) 436 if (NULL == gmtime_s (&t, &now)) 437 return; 438 #elif defined(HAVE_W32_GMTIME_S) 439 if (0 != gmtime_s (&now, &t)) 440 return; 441 #elif defined(HAVE_GMTIME_R) 442 if (NULL == gmtime_r (&t, &now)) 443 return; 444 #else 445 pNow = gmtime (&t); 446 if (NULL == pNow) 447 return; 448 now = *pNow; 449 #endif 450 sprintf (date, 451 "%3s, %02u %3s %04u %02u:%02u:%02u GMT", 452 days[now.tm_wday % 7], 453 (unsigned int) now.tm_mday, 454 mons[now.tm_mon % 12], 455 (unsigned int) (1900 + now.tm_year), 456 (unsigned int) now.tm_hour, 457 (unsigned int) now.tm_min, 458 (unsigned int) now.tm_sec); 459 } 460 461 462 /* end of mhd_responses.c */