From 75d9584e280ebfb3fcb400f46942695ac6e2958e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 12 May 2022 14:15:02 +0200 Subject: add Etag and 'expires' to /wire --- src/exchange/taler-exchange-httpd_keys.c | 60 +-------------- src/exchange/taler-exchange-httpd_wire.c | 123 ++++++++++++++++++++----------- src/include/taler_mhd_lib.h | 12 +++ src/mhd/mhd_legal.c | 4 +- src/mhd/mhd_responses.c | 45 +++++++++++ 5 files changed, 145 insertions(+), 99 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 56fe6412b..a84849099 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -1656,58 +1656,6 @@ add_denom_key_cb (void *cls, } -/** - * Produce HTTP "Date:" header. - * - * @param at time to write to @a date - * @param[out] date where to write the header, with - * at least 128 bytes available space. - */ -static void -get_date_string (struct GNUNET_TIME_Absolute at, - char date[128]) -{ - static const char *const days[] = - { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - static const char *const mons[] = - { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", - "Nov", "Dec"}; - struct tm now; - time_t t; -#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ - ! defined(HAVE_GMTIME_R) - struct tm*pNow; -#endif - - date[0] = 0; - t = (time_t) (at.abs_value_us / 1000LL / 1000LL); -#if defined(HAVE_C11_GMTIME_S) - if (NULL == gmtime_s (&t, &now)) - return; -#elif defined(HAVE_W32_GMTIME_S) - if (0 != gmtime_s (&now, &t)) - return; -#elif defined(HAVE_GMTIME_R) - if (NULL == gmtime_r (&t, &now)) - return; -#else - pNow = gmtime (&t); - if (NULL == pNow) - return; - now = *pNow; -#endif - sprintf (date, - "%3s, %02u %3s %04u %02u:%02u:%02u GMT", - days[now.tm_wday % 7], - (unsigned int) now.tm_mday, - mons[now.tm_mon % 12], - (unsigned int) (1900 + now.tm_year), - (unsigned int) now.tm_hour, - (unsigned int) now.tm_min, - (unsigned int) now.tm_sec); -} - - /** * Add the headers we want to set for every /keys response. * @@ -1726,8 +1674,8 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh, MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json")); - get_date_string (ksh->reload_time.abs_time, - dat); + TALER_MHD_get_date_string (ksh->reload_time.abs_time, + dat); GNUNET_break (MHD_YES == MHD_add_response_header (response, MHD_HTTP_HEADER_LAST_MODIFIED, @@ -1742,8 +1690,8 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh, ksh->rekey_frequency); a = GNUNET_TIME_relative_to_absolute (r); m = GNUNET_TIME_absolute_to_timestamp (a); - get_date_string (m.abs_time, - dat); + TALER_MHD_get_date_string (m.abs_time, + dat); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setting /keys 'Expires' header to '%s'\n", dat); diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c index 30c281b55..22b4e7cdb 100644 --- a/src/exchange/taler-exchange-httpd_wire.c +++ b/src/exchange/taler-exchange-httpd_wire.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA + Copyright (C) 2015-2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -72,9 +72,9 @@ struct WireFeeSet struct WireStateHandle { /** - * Cached JSON for /wire response. + * Cached reply for /wire response. */ - json_t *wire_reply; + struct MHD_Response *wire_reply; /** * head of DLL of wire fees. @@ -136,7 +136,7 @@ destroy_wire_state (struct WireStateHandle *wsh) GNUNET_free (wfs->method); GNUNET_free (wfs); } - json_decref (wsh->wire_reply); + MHD_destroy_response (wsh->wire_reply); GNUNET_free (wsh); } @@ -203,28 +203,6 @@ TEH_wire_done () } -/** - * Create standard JSON response format using @a ec and @a detail. - * - * @param ec error code to return - * @param detail optional detail text to return, can be NULL - * @return JSON response - */ -static json_t * -make_ec_reply (enum TALER_ErrorCode ec, - const char *detail) -{ - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - ec), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint (ec)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("detail", - detail))); -} - - /** * Add information about a wire account to @a cls. * @@ -274,6 +252,18 @@ struct AddContext * Array to append the fee to. */ json_t *a; + + /** + * Context we hash "everything" we add into. This is used + * to compute the etag. Technically, we only hash the + * master_sigs, as they imply the rest. + */ + struct GNUNET_HashContext *hc; + + /** + * Set to the maximum end-date seen. + */ + struct GNUNET_TIME_Absolute max_seen; }; @@ -297,6 +287,11 @@ add_wire_fee (void *cls, struct AddContext *ac = cls; struct WireFeeSet *wfs; + GNUNET_CRYPTO_hash_context_read (ac->hc, + master_sig, + sizeof (*master_sig)); + ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen, + end_date.abs_time); wfs = GNUNET_new (struct WireFeeSet); wfs->start_date = start_date; wfs->end_date = end_date; @@ -341,6 +336,8 @@ build_wire_state (void) uint64_t wg = wire_generation; /* must be obtained FIRST */ enum GNUNET_DB_QueryStatus qs; struct WireStateHandle *wsh; + struct GNUNET_TIME_Absolute cache_expiration; + struct GNUNET_HashContext *hc; wsh = GNUNET_new (struct WireStateHandle); wsh->wire_generation = wg; @@ -355,8 +352,8 @@ build_wire_state (void) json_decref (wire_accounts_array); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply - = make_ec_reply (TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_wire_accounts"); + = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_wire_accounts"); return wsh; } if (0 == json_array_size (wire_accounts_array)) @@ -364,12 +361,14 @@ build_wire_state (void) json_decref (wire_accounts_array); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply - = make_ec_reply (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED, - NULL); + = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED, + NULL); return wsh; } wire_fee_object = json_object (); GNUNET_assert (NULL != wire_fee_object); + cache_expiration = GNUNET_TIME_UNIT_ZERO_ABS; + hc = GNUNET_CRYPTO_hash_context_start (); { json_t *account; size_t index; @@ -385,10 +384,12 @@ build_wire_state (void) { wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply - = make_ec_reply (TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED, - payto_uri); + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED, + payto_uri); json_decref (wire_accounts_array); json_decref (wire_fee_object); + GNUNET_CRYPTO_hash_context_abort (hc); return wsh; } if (NULL == json_object_get (wire_fee_object, @@ -397,7 +398,8 @@ build_wire_state (void) struct AddContext ac = { .wire_method = wire_method, .wsh = wsh, - .a = json_array () + .a = json_array (), + .hc = hc }; GNUNET_assert (NULL != ac.a); @@ -414,8 +416,9 @@ build_wire_state (void) GNUNET_free (wire_method); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply - = make_ec_reply (TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_wire_fees"); + = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_wire_fees"); + GNUNET_CRYPTO_hash_context_abort (hc); return wsh; } if (0 == json_array_size (ac.a)) @@ -425,11 +428,14 @@ build_wire_state (void) json_decref (wire_fee_object); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply - = make_ec_reply (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, - wire_method); + = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, + wire_method); GNUNET_free (wire_method); + GNUNET_CRYPTO_hash_context_abort (hc); return wsh; } + cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen, + cache_expiration); GNUNET_assert (0 == json_object_set_new (wire_fee_object, wire_method, @@ -438,13 +444,48 @@ build_wire_state (void) GNUNET_free (wire_method); } } - wsh->wire_reply = GNUNET_JSON_PACK ( + + + wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_array_steal ("accounts", wire_accounts_array), GNUNET_JSON_pack_object_steal ("fees", wire_fee_object), GNUNET_JSON_pack_data_auto ("master_public_key", &TEH_master_public_key)); + { + char dat[128]; + struct GNUNET_TIME_Timestamp m; + + m = GNUNET_TIME_absolute_to_timestamp (cache_expiration); + TALER_MHD_get_date_string (m.abs_time, + dat); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setting 'Expires' header for '/wire' to '%s'\n", + dat); + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_EXPIRES, + dat)); + } + TALER_MHD_add_global_headers (wsh->wire_reply); + { + struct GNUNET_HashCode h; + char etag[sizeof (h) * 2]; + char *end; + + GNUNET_CRYPTO_hash_context_finish (hc, + &h); + end = GNUNET_STRINGS_data_to_string (&h, + sizeof (h), + etag, + sizeof (etag)); + *end = '\0'; + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_ETAG, + etag)); + } wsh->http_status = MHD_HTTP_OK; return wsh; } @@ -509,9 +550,9 @@ TEH_handler_wire (struct TEH_RequestContext *rc, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, NULL); - return TALER_MHD_reply_json (rc->connection, - wsh->wire_reply, - wsh->http_status); + return MHD_queue_response (rc->connection, + wsh->http_status, + wsh->wire_reply); } diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h index dc68df06b..b64231352 100644 --- a/src/include/taler_mhd_lib.h +++ b/src/include/taler_mhd_lib.h @@ -207,6 +207,18 @@ TALER_MHD_reply_with_ec (struct MHD_Connection *connection, const char *detail); +/** + * Produce HTTP "Date:" header. + * + * @param at time to write to @a date + * @param[out] date where to write the header, with + * at least 128 bytes available space. + */ +void +TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at, + char date[128]); + + /** * Make JSON response object. * diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c index 64c176a93..bd596862c 100644 --- a/src/mhd/mhd_legal.c +++ b/src/mhd/mhd_legal.c @@ -178,8 +178,8 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING); m = GNUNET_TIME_absolute_to_timestamp (a); - get_date_string (m.abs_time, - dat); + TALER_MHD_get_date_string (m.abs_time, + dat); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setting 'Expires' header to '%s'\n", dat); diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c index a639f4052..7dd6824e2 100644 --- a/src/mhd/mhd_responses.c +++ b/src/mhd/mhd_responses.c @@ -502,4 +502,49 @@ TALER_MHD_reply_static (struct MHD_Connection *connection, } +void +TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at, + char date[128]) +{ + static const char *const days[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char *const mons[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec"}; + struct tm now; + time_t t; +#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ + ! defined(HAVE_GMTIME_R) + struct tm*pNow; +#endif + + date[0] = 0; + t = (time_t) (at.abs_value_us / 1000LL / 1000LL); +#if defined(HAVE_C11_GMTIME_S) + if (NULL == gmtime_s (&t, &now)) + return; +#elif defined(HAVE_W32_GMTIME_S) + if (0 != gmtime_s (&now, &t)) + return; +#elif defined(HAVE_GMTIME_R) + if (NULL == gmtime_r (&t, &now)) + return; +#else + pNow = gmtime (&t); + if (NULL == pNow) + return; + now = *pNow; +#endif + sprintf (date, + "%3s, %02u %3s %04u %02u:%02u:%02u GMT", + days[now.tm_wday % 7], + (unsigned int) now.tm_mday, + mons[now.tm_mon % 12], + (unsigned int) (1900 + now.tm_year), + (unsigned int) now.tm_hour, + (unsigned int) now.tm_min, + (unsigned int) now.tm_sec); +} + + /* end of mhd_responses.c */ -- cgit v1.2.3