mhd_responses.c (14720B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2021 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 mhd_responses.c 18 * @brief API for generating HTTP replies 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <zlib.h> 25 #include "taler/taler_util.h" 26 #include "taler/taler_mhd_lib.h" 27 28 29 /** 30 * Global options for response generation. 31 */ 32 static enum TALER_MHD_GlobalOptions TM_go; 33 34 35 void 36 TALER_MHD_setup (enum TALER_MHD_GlobalOptions go) 37 { 38 TM_go = go; 39 } 40 41 42 void 43 TALER_MHD_add_global_headers (struct MHD_Response *response, 44 bool allow_store) 45 { 46 if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE)) 47 GNUNET_break (MHD_YES == 48 MHD_add_response_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_YES == 54 MHD_add_response_header (response, 55 MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, 56 "*")); 57 GNUNET_break (MHD_YES == 58 MHD_add_response_header (response, 59 /* Not available as MHD constant yet */ 60 "Access-Control-Expose-Headers", 61 "*")); 62 if (! allow_store) 63 GNUNET_break (MHD_YES == 64 MHD_add_response_header (response, 65 MHD_HTTP_HEADER_CACHE_CONTROL, 66 "no-store")); 67 } 68 69 70 enum TALER_MHD_CompressionType 71 TALER_MHD_can_compress (struct MHD_Connection *connection, 72 enum TALER_MHD_CompressionType max) 73 { 74 struct NameMap 75 { 76 const char *name; 77 enum TALER_MHD_CompressionType ct; 78 } map[] = { 79 /* sorted largest to smallest on purpose! */ 80 { "zstd", TALER_MHD_CT_ZSTD }, 81 { "gzip", TALER_MHD_CT_GZIP }, 82 { "deflate", TALER_MHD_CT_DEFLATE }, 83 { NULL, TALER_MHD_CT_NONE } 84 }; 85 const char *ae; 86 const char *de; 87 88 if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION)) 89 return TALER_MHD_CT_NONE; 90 ae = MHD_lookup_connection_value (connection, 91 MHD_HEADER_KIND, 92 MHD_HTTP_HEADER_ACCEPT_ENCODING); 93 if (NULL == ae) 94 return TALER_MHD_CT_NONE; 95 if (0 == strcmp (ae, 96 "*")) 97 return MHD_YES; 98 for (unsigned int i=0; NULL != map[i].name; i++) 99 { 100 const char *name = map[i].name; 101 102 if (map[i].ct > max) 103 continue; /* not allowed by client */ 104 de = strstr (ae, 105 name); 106 if (NULL == de) 107 continue; 108 if ( ( (de == ae) || 109 (de[-1] == ',') || 110 (de[-1] == ' ') ) && 111 ( (de[strlen (name)] == '\0') || 112 (de[strlen (name)] == ',') || 113 (de[strlen (name)] == ';') ) ) 114 return map[i].ct; 115 } 116 return TALER_MHD_CT_NONE; 117 } 118 119 120 MHD_RESULT 121 TALER_MHD_body_compress (void **buf, 122 size_t *buf_size) 123 { 124 Bytef *cbuf; 125 uLongf cbuf_size; 126 MHD_RESULT ret; 127 128 cbuf_size = compressBound (*buf_size); 129 cbuf = malloc (cbuf_size); 130 if (NULL == cbuf) 131 return MHD_NO; 132 ret = compress (cbuf, 133 &cbuf_size, 134 (const Bytef *) *buf, 135 *buf_size); 136 if ( (Z_OK != ret) || 137 (cbuf_size >= *buf_size) ) 138 { 139 /* compression failed */ 140 free (cbuf); 141 return MHD_NO; 142 } 143 free (*buf); 144 *buf = (void *) cbuf; 145 *buf_size = (size_t) cbuf_size; 146 return MHD_YES; 147 } 148 149 150 struct MHD_Response * 151 TALER_MHD_make_json (const json_t *json) 152 { 153 struct MHD_Response *response; 154 char *json_str; 155 156 json_str = json_dumps (json, 157 JSON_INDENT (2)); 158 if (NULL == json_str) 159 { 160 GNUNET_break (0); 161 return NULL; 162 } 163 response = MHD_create_response_from_buffer (strlen (json_str), 164 json_str, 165 MHD_RESPMEM_MUST_FREE); 166 if (NULL == response) 167 { 168 free (json_str); 169 GNUNET_break (0); 170 return NULL; 171 } 172 TALER_MHD_add_global_headers (response, 173 false); 174 GNUNET_break (MHD_YES == 175 MHD_add_response_header (response, 176 MHD_HTTP_HEADER_CONTENT_TYPE, 177 "application/json")); 178 return response; 179 } 180 181 182 struct MHD_Response * 183 TALER_MHD_make_json_steal (json_t *json) 184 { 185 struct MHD_Response *res; 186 187 res = TALER_MHD_make_json (json); 188 json_decref (json); 189 return res; 190 } 191 192 193 MHD_RESULT 194 TALER_MHD_reply_json (struct MHD_Connection *connection, 195 const json_t *json, 196 unsigned int response_code) 197 { 198 struct MHD_Response *response; 199 void *json_str; 200 size_t json_len; 201 MHD_RESULT is_compressed; 202 203 json_str = json_dumps (json, 204 JSON_INDENT (2)); 205 if (NULL == json_str) 206 { 207 /** 208 * This log helps to figure out which 209 * function called this one and assert-failed. 210 */ 211 TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n", 212 response_code); 213 214 GNUNET_assert (0); 215 return MHD_NO; 216 } 217 json_len = strlen (json_str); 218 /* try to compress the body */ 219 is_compressed = MHD_NO; 220 if (TALER_MHD_CT_DEFLATE == 221 TALER_MHD_can_compress (connection, 222 TALER_MHD_CT_DEFLATE)) 223 is_compressed = TALER_MHD_body_compress (&json_str, 224 &json_len); 225 response = MHD_create_response_from_buffer (json_len, 226 json_str, 227 MHD_RESPMEM_MUST_FREE); 228 if (NULL == response) 229 { 230 free (json_str); 231 GNUNET_break (0); 232 return MHD_NO; 233 } 234 TALER_MHD_add_global_headers (response, 235 false); 236 GNUNET_break (MHD_YES == 237 MHD_add_response_header (response, 238 MHD_HTTP_HEADER_CONTENT_TYPE, 239 "application/json")); 240 if (MHD_YES == is_compressed) 241 { 242 /* Need to indicate to client that body is compressed */ 243 if (MHD_NO == 244 MHD_add_response_header (response, 245 MHD_HTTP_HEADER_CONTENT_ENCODING, 246 "deflate")) 247 { 248 GNUNET_break (0); 249 MHD_destroy_response (response); 250 return MHD_NO; 251 } 252 } 253 254 { 255 MHD_RESULT ret; 256 257 ret = MHD_queue_response (connection, 258 response_code, 259 response); 260 MHD_destroy_response (response); 261 return ret; 262 } 263 } 264 265 266 MHD_RESULT 267 TALER_MHD_reply_json_steal (struct MHD_Connection *connection, 268 json_t *json, 269 unsigned int response_code) 270 { 271 MHD_RESULT ret; 272 273 ret = TALER_MHD_reply_json (connection, 274 json, 275 response_code); 276 json_decref (json); 277 return ret; 278 } 279 280 281 MHD_RESULT 282 TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection) 283 { 284 struct MHD_Response *response; 285 286 response = MHD_create_response_from_buffer (0, 287 NULL, 288 MHD_RESPMEM_PERSISTENT); 289 if (NULL == response) 290 return MHD_NO; 291 /* This adds the Access-Control-Allow-Origin header. 292 * All endpoints of the exchange allow CORS. */ 293 TALER_MHD_add_global_headers (response, 294 true); 295 GNUNET_break (MHD_YES == 296 MHD_add_response_header (response, 297 /* Not available as MHD constant yet */ 298 "Access-Control-Allow-Headers", 299 "*")); 300 GNUNET_break (MHD_YES == 301 MHD_add_response_header (response, 302 /* Not available as MHD constant yet */ 303 "Access-Control-Allow-Methods", 304 "*")); 305 { 306 MHD_RESULT ret; 307 308 ret = MHD_queue_response (connection, 309 MHD_HTTP_NO_CONTENT, 310 response); 311 MHD_destroy_response (response); 312 return ret; 313 } 314 } 315 316 317 struct MHD_Response * 318 TALER_MHD_make_error (enum TALER_ErrorCode ec, 319 const char *detail) 320 { 321 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 322 "Generating JSON response with code %d (%s)\n", 323 (int) ec, 324 detail); 325 return TALER_MHD_MAKE_JSON_PACK ( 326 TALER_MHD_PACK_EC (ec), 327 GNUNET_JSON_pack_allow_null ( 328 GNUNET_JSON_pack_string ("detail", detail))); 329 } 330 331 332 MHD_RESULT 333 TALER_MHD_reply_with_error (struct MHD_Connection *connection, 334 unsigned int http_status, 335 enum TALER_ErrorCode ec, 336 const char *detail) 337 { 338 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 339 "Generating HTTP response with status %u and code %d (%s)\n", 340 http_status, 341 (int) ec, 342 detail); 343 return TALER_MHD_REPLY_JSON_PACK ( 344 connection, 345 http_status, 346 TALER_MHD_PACK_EC (ec), 347 GNUNET_JSON_pack_allow_null ( 348 GNUNET_JSON_pack_string ("detail", detail))); 349 } 350 351 352 MHD_RESULT 353 TALER_MHD_reply_with_ec (struct MHD_Connection *connection, 354 enum TALER_ErrorCode ec, 355 const char *detail) 356 { 357 unsigned int hc = TALER_ErrorCode_get_http_status (ec); 358 359 if ( (0 == hc) || 360 (UINT_MAX == hc) ) 361 { 362 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 363 "Invalid Taler error code %d provided for response!\n", 364 (int) ec); 365 hc = MHD_HTTP_INTERNAL_SERVER_ERROR; 366 } 367 return TALER_MHD_reply_with_error (connection, 368 hc, 369 ec, 370 detail); 371 } 372 373 374 MHD_RESULT 375 TALER_MHD_reply_request_too_large (struct MHD_Connection *connection) 376 { 377 return TALER_MHD_reply_with_error (connection, 378 MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, 379 TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT, 380 NULL); 381 } 382 383 384 MHD_RESULT 385 TALER_MHD_reply_agpl (struct MHD_Connection *connection, 386 const char *url) 387 { 388 const char *agpl = 389 "This server is licensed under the Affero GPL. You will now be redirected to the source code."; 390 struct MHD_Response *response; 391 392 response = MHD_create_response_from_buffer (strlen (agpl), 393 (void *) agpl, 394 MHD_RESPMEM_PERSISTENT); 395 if (NULL == response) 396 { 397 GNUNET_break (0); 398 return MHD_NO; 399 } 400 TALER_MHD_add_global_headers (response, 401 true); 402 GNUNET_break (MHD_YES == 403 MHD_add_response_header (response, 404 MHD_HTTP_HEADER_CONTENT_TYPE, 405 "text/plain")); 406 if (MHD_NO == 407 MHD_add_response_header (response, 408 MHD_HTTP_HEADER_LOCATION, 409 url)) 410 { 411 GNUNET_break (0); 412 MHD_destroy_response (response); 413 return MHD_NO; 414 } 415 416 { 417 MHD_RESULT ret; 418 419 ret = MHD_queue_response (connection, 420 MHD_HTTP_FOUND, 421 response); 422 MHD_destroy_response (response); 423 return ret; 424 } 425 } 426 427 428 MHD_RESULT 429 TALER_MHD_reply_static (struct MHD_Connection *connection, 430 unsigned int http_status, 431 const char *mime_type, 432 const char *body, 433 size_t body_size) 434 { 435 struct MHD_Response *response; 436 437 response = MHD_create_response_from_buffer (body_size, 438 (void *) body, 439 MHD_RESPMEM_PERSISTENT); 440 if (NULL == response) 441 { 442 GNUNET_break (0); 443 return MHD_NO; 444 } 445 TALER_MHD_add_global_headers (response, 446 true); 447 if (NULL != mime_type) 448 GNUNET_break (MHD_YES == 449 MHD_add_response_header (response, 450 MHD_HTTP_HEADER_CONTENT_TYPE, 451 mime_type)); 452 { 453 MHD_RESULT ret; 454 455 ret = MHD_queue_response (connection, 456 http_status, 457 response); 458 MHD_destroy_response (response); 459 return ret; 460 } 461 } 462 463 464 void 465 TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at, 466 char date[128]) 467 { 468 static const char *const days[] = 469 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 470 static const char *const mons[] = 471 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", 472 "Nov", "Dec"}; 473 struct tm now; 474 time_t t; 475 #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ 476 ! defined(HAVE_GMTIME_R) 477 struct tm*pNow; 478 #endif 479 480 date[0] = 0; 481 t = (time_t) (at.abs_value_us / 1000LL / 1000LL); 482 #if defined(HAVE_C11_GMTIME_S) 483 if (NULL == gmtime_s (&t, &now)) 484 return; 485 #elif defined(HAVE_W32_GMTIME_S) 486 if (0 != gmtime_s (&now, &t)) 487 return; 488 #elif defined(HAVE_GMTIME_R) 489 if (NULL == gmtime_r (&t, &now)) 490 return; 491 #else 492 pNow = gmtime (&t); 493 if (NULL == pNow) 494 return; 495 now = *pNow; 496 #endif 497 sprintf (date, 498 "%3s, %02u %3s %04u %02u:%02u:%02u GMT", 499 days[now.tm_wday % 7], 500 (unsigned int) now.tm_mday, 501 mons[now.tm_mon % 12], 502 (unsigned int) (1900 + now.tm_year), 503 (unsigned int) now.tm_hour, 504 (unsigned int) now.tm_min, 505 (unsigned int) now.tm_sec); 506 } 507 508 509 /* end of mhd_responses.c */