exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */