mhd_responses.c (14385B)
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 return TALER_MHD_MAKE_JSON_PACK ( 322 TALER_MHD_PACK_EC (ec), 323 GNUNET_JSON_pack_allow_null ( 324 GNUNET_JSON_pack_string ("detail", detail))); 325 } 326 327 328 MHD_RESULT 329 TALER_MHD_reply_with_error (struct MHD_Connection *connection, 330 unsigned int http_status, 331 enum TALER_ErrorCode ec, 332 const char *detail) 333 { 334 return TALER_MHD_REPLY_JSON_PACK ( 335 connection, 336 http_status, 337 TALER_MHD_PACK_EC (ec), 338 GNUNET_JSON_pack_allow_null ( 339 GNUNET_JSON_pack_string ("detail", detail))); 340 } 341 342 343 MHD_RESULT 344 TALER_MHD_reply_with_ec (struct MHD_Connection *connection, 345 enum TALER_ErrorCode ec, 346 const char *detail) 347 { 348 unsigned int hc = TALER_ErrorCode_get_http_status (ec); 349 350 if ( (0 == hc) || 351 (UINT_MAX == hc) ) 352 { 353 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 354 "Invalid Taler error code %d provided for response!\n", 355 (int) ec); 356 hc = MHD_HTTP_INTERNAL_SERVER_ERROR; 357 } 358 return TALER_MHD_reply_with_error (connection, 359 hc, 360 ec, 361 detail); 362 } 363 364 365 MHD_RESULT 366 TALER_MHD_reply_request_too_large (struct MHD_Connection *connection) 367 { 368 return TALER_MHD_reply_with_error (connection, 369 MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, 370 TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT, 371 NULL); 372 } 373 374 375 MHD_RESULT 376 TALER_MHD_reply_agpl (struct MHD_Connection *connection, 377 const char *url) 378 { 379 const char *agpl = 380 "This server is licensed under the Affero GPL. You will now be redirected to the source code."; 381 struct MHD_Response *response; 382 383 response = MHD_create_response_from_buffer (strlen (agpl), 384 (void *) agpl, 385 MHD_RESPMEM_PERSISTENT); 386 if (NULL == response) 387 { 388 GNUNET_break (0); 389 return MHD_NO; 390 } 391 TALER_MHD_add_global_headers (response, 392 true); 393 GNUNET_break (MHD_YES == 394 MHD_add_response_header (response, 395 MHD_HTTP_HEADER_CONTENT_TYPE, 396 "text/plain")); 397 if (MHD_NO == 398 MHD_add_response_header (response, 399 MHD_HTTP_HEADER_LOCATION, 400 url)) 401 { 402 GNUNET_break (0); 403 MHD_destroy_response (response); 404 return MHD_NO; 405 } 406 407 { 408 MHD_RESULT ret; 409 410 ret = MHD_queue_response (connection, 411 MHD_HTTP_FOUND, 412 response); 413 MHD_destroy_response (response); 414 return ret; 415 } 416 } 417 418 419 MHD_RESULT 420 TALER_MHD_reply_static (struct MHD_Connection *connection, 421 unsigned int http_status, 422 const char *mime_type, 423 const char *body, 424 size_t body_size) 425 { 426 struct MHD_Response *response; 427 428 response = MHD_create_response_from_buffer (body_size, 429 (void *) body, 430 MHD_RESPMEM_PERSISTENT); 431 if (NULL == response) 432 { 433 GNUNET_break (0); 434 return MHD_NO; 435 } 436 TALER_MHD_add_global_headers (response, 437 true); 438 if (NULL != mime_type) 439 GNUNET_break (MHD_YES == 440 MHD_add_response_header (response, 441 MHD_HTTP_HEADER_CONTENT_TYPE, 442 mime_type)); 443 { 444 MHD_RESULT ret; 445 446 ret = MHD_queue_response (connection, 447 http_status, 448 response); 449 MHD_destroy_response (response); 450 return ret; 451 } 452 } 453 454 455 void 456 TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at, 457 char date[128]) 458 { 459 static const char *const days[] = 460 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 461 static const char *const mons[] = 462 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", 463 "Nov", "Dec"}; 464 struct tm now; 465 time_t t; 466 #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ 467 ! defined(HAVE_GMTIME_R) 468 struct tm*pNow; 469 #endif 470 471 date[0] = 0; 472 t = (time_t) (at.abs_value_us / 1000LL / 1000LL); 473 #if defined(HAVE_C11_GMTIME_S) 474 if (NULL == gmtime_s (&t, &now)) 475 return; 476 #elif defined(HAVE_W32_GMTIME_S) 477 if (0 != gmtime_s (&now, &t)) 478 return; 479 #elif defined(HAVE_GMTIME_R) 480 if (NULL == gmtime_r (&t, &now)) 481 return; 482 #else 483 pNow = gmtime (&t); 484 if (NULL == pNow) 485 return; 486 now = *pNow; 487 #endif 488 sprintf (date, 489 "%3s, %02u %3s %04u %02u:%02u:%02u GMT", 490 days[now.tm_wday % 7], 491 (unsigned int) now.tm_mday, 492 mons[now.tm_mon % 12], 493 (unsigned int) (1900 + now.tm_year), 494 (unsigned int) now.tm_hour, 495 (unsigned int) now.tm_min, 496 (unsigned int) now.tm_sec); 497 } 498 499 500 /* end of mhd_responses.c */