LCOV - code coverage report
Current view: top level - mhd - mhd_responses.c (source / functions) Hit Total Coverage
Test: GNU Taler coverage report Lines: 72 157 45.9 %
Date: 2021-04-12 06:08:44 Functions: 9 15 60.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2020 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 "platform.h"
      24             : #include <zlib.h>
      25             : #include "taler_util.h"
      26             : #include "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             : /**
      36             :  * Set global options for response generation
      37             :  * within libtalermhd.
      38             :  *
      39             :  * @param go global options to use
      40             :  */
      41             : void
      42          13 : TALER_MHD_setup (enum TALER_MHD_GlobalOptions go)
      43             : {
      44          13 :   TM_go = go;
      45          13 : }
      46             : 
      47             : 
      48             : /**
      49             :  * Add headers we want to return in every response.
      50             :  * Useful for testing, like if we want to always close
      51             :  * connections.
      52             :  *
      53             :  * @param response response to modify
      54             :  */
      55             : void
      56         110 : TALER_MHD_add_global_headers (struct MHD_Response *response)
      57             : {
      58         110 :   if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE))
      59           0 :     GNUNET_break (MHD_YES ==
      60             :                   MHD_add_response_header (response,
      61             :                                            MHD_HTTP_HEADER_CONNECTION,
      62             :                                            "close"));
      63             :   /* The wallet, operating from a background page, needs CORS to
      64             :      be disabled otherwise browsers block access. */
      65         110 :   GNUNET_break (MHD_YES ==
      66             :                 MHD_add_response_header (response,
      67             :                                          MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
      68             :                                          "*"));
      69         110 : }
      70             : 
      71             : 
      72             : /**
      73             :  * Is HTTP body deflate compression supported by the client?
      74             :  *
      75             :  * @param connection connection to check
      76             :  * @return #MHD_YES if 'deflate' compression is allowed
      77             :  *
      78             :  * Note that right now we're ignoring q-values, which is technically
      79             :  * not correct, and also do not support "*" anywhere but in a line by
      80             :  * itself.  This should eventually be fixed, see also
      81             :  * https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
      82             :  */
      83             : MHD_RESULT
      84          56 : TALER_MHD_can_compress (struct MHD_Connection *connection)
      85             : {
      86             :   const char *ae;
      87             :   const char *de;
      88             : 
      89          56 :   if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION))
      90           0 :     return MHD_NO;
      91          56 :   ae = MHD_lookup_connection_value (connection,
      92             :                                     MHD_HEADER_KIND,
      93             :                                     MHD_HTTP_HEADER_ACCEPT_ENCODING);
      94          56 :   if (NULL == ae)
      95          54 :     return MHD_NO;
      96           2 :   if (0 == strcmp (ae,
      97             :                    "*"))
      98           0 :     return MHD_YES;
      99           2 :   de = strstr (ae,
     100             :                "deflate");
     101           2 :   if (NULL == de)
     102           2 :     return MHD_NO;
     103           0 :   if ( ( (de == ae) ||
     104           0 :          (de[-1] == ',') ||
     105           0 :          (de[-1] == ' ') ) &&
     106           0 :        ( (de[strlen ("deflate")] == '\0') ||
     107           0 :          (de[strlen ("deflate")] == ',') ||
     108           0 :          (de[strlen ("deflate")] == ';') ) )
     109           0 :     return MHD_YES;
     110           0 :   return MHD_NO;
     111             : }
     112             : 
     113             : 
     114             : /**
     115             :  * Try to compress a response body.  Updates @a buf and @a buf_size.
     116             :  *
     117             :  * @param[in,out] buf pointer to body to compress
     118             :  * @param[in,out] buf_size pointer to initial size of @a buf
     119             :  * @return #MHD_YES if @a buf was compressed
     120             :  */
     121             : MHD_RESULT
     122          20 : TALER_MHD_body_compress (void **buf,
     123             :                          size_t *buf_size)
     124             : {
     125             :   Bytef *cbuf;
     126             :   uLongf cbuf_size;
     127             :   MHD_RESULT ret;
     128             : 
     129          20 :   cbuf_size = compressBound (*buf_size);
     130          20 :   cbuf = malloc (cbuf_size);
     131          20 :   if (NULL == cbuf)
     132           0 :     return MHD_NO;
     133          20 :   ret = compress (cbuf,
     134             :                   &cbuf_size,
     135             :                   (const Bytef *) *buf,
     136             :                   *buf_size);
     137          20 :   if ( (Z_OK != ret) ||
     138          20 :        (cbuf_size >= *buf_size) )
     139             :   {
     140             :     /* compression failed */
     141           0 :     free (cbuf);
     142           0 :     return MHD_NO;
     143             :   }
     144          20 :   free (*buf);
     145          20 :   *buf = (void *) cbuf;
     146          20 :   *buf_size = (size_t) cbuf_size;
     147          20 :   return MHD_YES;
     148             : }
     149             : 
     150             : 
     151             : /**
     152             :  * Make JSON response object.
     153             :  *
     154             :  * @param json the json object
     155             :  * @return MHD response object
     156             :  */
     157             : struct MHD_Response *
     158           0 : TALER_MHD_make_json (const json_t *json)
     159             : {
     160             :   struct MHD_Response *response;
     161             :   char *json_str;
     162             : 
     163           0 :   json_str = json_dumps (json,
     164             :                          JSON_INDENT (2));
     165           0 :   if (NULL == json_str)
     166             :   {
     167           0 :     GNUNET_break (0);
     168           0 :     return NULL;
     169             :   }
     170           0 :   response = MHD_create_response_from_buffer (strlen (json_str),
     171             :                                               json_str,
     172             :                                               MHD_RESPMEM_MUST_FREE);
     173           0 :   if (NULL == response)
     174             :   {
     175           0 :     free (json_str);
     176           0 :     GNUNET_break (0);
     177           0 :     return NULL;
     178             :   }
     179           0 :   TALER_MHD_add_global_headers (response);
     180           0 :   GNUNET_break (MHD_YES ==
     181             :                 MHD_add_response_header (response,
     182             :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     183             :                                          "application/json"));
     184           0 :   return response;
     185             : }
     186             : 
     187             : 
     188             : /**
     189             :  * Send JSON object as response.
     190             :  *
     191             :  * @param connection the MHD connection
     192             :  * @param json the json object
     193             :  * @param response_code the http response code
     194             :  * @return MHD result code
     195             :  */
     196             : MHD_RESULT
     197          48 : TALER_MHD_reply_json (struct MHD_Connection *connection,
     198             :                       const json_t *json,
     199             :                       unsigned int response_code)
     200             : {
     201             :   struct MHD_Response *response;
     202             :   void *json_str;
     203             :   size_t json_len;
     204             :   MHD_RESULT is_compressed;
     205             : 
     206          48 :   json_str = json_dumps (json,
     207             :                          JSON_INDENT (2));
     208          48 :   if (NULL == json_str)
     209             :   {
     210             :     /**
     211             :      * This log helps to figure out which
     212             :      * function called this one and assert-failed.
     213             :      */
     214           0 :     TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n",
     215             :                      response_code);
     216             : 
     217           0 :     GNUNET_assert (0);
     218             :     return MHD_NO;
     219             :   }
     220          48 :   json_len = strlen (json_str);
     221             :   /* try to compress the body */
     222          48 :   is_compressed = MHD_NO;
     223          48 :   if (MHD_YES ==
     224          48 :       TALER_MHD_can_compress (connection))
     225           0 :     is_compressed = TALER_MHD_body_compress (&json_str,
     226             :                                              &json_len);
     227          48 :   response = MHD_create_response_from_buffer (json_len,
     228             :                                               json_str,
     229             :                                               MHD_RESPMEM_MUST_FREE);
     230          48 :   if (NULL == response)
     231             :   {
     232           0 :     free (json_str);
     233           0 :     GNUNET_break (0);
     234           0 :     return MHD_NO;
     235             :   }
     236          48 :   TALER_MHD_add_global_headers (response);
     237          48 :   GNUNET_break (MHD_YES ==
     238             :                 MHD_add_response_header (response,
     239             :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     240             :                                          "application/json"));
     241          48 :   if (MHD_YES == is_compressed)
     242             :   {
     243             :     /* Need to indicate to client that body is compressed */
     244           0 :     if (MHD_NO ==
     245           0 :         MHD_add_response_header (response,
     246             :                                  MHD_HTTP_HEADER_CONTENT_ENCODING,
     247             :                                  "deflate"))
     248             :     {
     249           0 :       GNUNET_break (0);
     250           0 :       MHD_destroy_response (response);
     251           0 :       return MHD_NO;
     252             :     }
     253             :   }
     254             : 
     255             :   {
     256             :     MHD_RESULT ret;
     257             : 
     258          48 :     ret = MHD_queue_response (connection,
     259             :                               response_code,
     260             :                               response);
     261          48 :     MHD_destroy_response (response);
     262          48 :     return ret;
     263             :   }
     264             : }
     265             : 
     266             : 
     267             : /**
     268             :  * Send back a "204 No Content" response with headers
     269             :  * for the CORS pre-flight request.
     270             :  *
     271             :  * @param connection the MHD connection
     272             :  * @return MHD result code
     273             :  */
     274             : MHD_RESULT
     275           0 : TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
     276             : {
     277             :   struct MHD_Response *response;
     278             : 
     279           0 :   response = MHD_create_response_from_buffer (0,
     280             :                                               NULL,
     281             :                                               MHD_RESPMEM_PERSISTENT);
     282           0 :   if (NULL == response)
     283           0 :     return MHD_NO;
     284             :   /* This adds the Access-Control-Allow-Origin header.
     285             :    * All endpoints of the exchange allow CORS. */
     286           0 :   TALER_MHD_add_global_headers (response);
     287           0 :   GNUNET_break (MHD_YES ==
     288             :                 MHD_add_response_header (response,
     289             :                                          /* Not available as MHD constant yet */
     290             :                                          "Access-Control-Allow-Headers",
     291             :                                          "*"));
     292           0 :   GNUNET_break (MHD_YES ==
     293             :                 MHD_add_response_header (response,
     294             :                                          /* Not available as MHD constant yet */
     295             :                                          "Access-Control-Allow-Methods",
     296             :                                          "*"));
     297             : 
     298             :   {
     299             :     MHD_RESULT ret;
     300             : 
     301           0 :     ret = MHD_queue_response (connection,
     302             :                               MHD_HTTP_NO_CONTENT,
     303             :                               response);
     304           0 :     MHD_destroy_response (response);
     305           0 :     return ret;
     306             :   }
     307             : }
     308             : 
     309             : 
     310             : /**
     311             :  * Function to call to handle the request by building a JSON
     312             :  * reply from a format string and varargs.
     313             :  *
     314             :  * @param connection the MHD connection to handle
     315             :  * @param response_code HTTP response code to use
     316             :  * @param fmt format string for pack
     317             :  * @param ... varargs
     318             :  * @return MHD result code
     319             :  */
     320             : MHD_RESULT
     321          44 : TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
     322             :                            unsigned int response_code,
     323             :                            const char *fmt,
     324             :                            ...)
     325             : {
     326             :   json_t *json;
     327             :   json_error_t jerror;
     328             : 
     329             :   {
     330             :     va_list argp;
     331             : 
     332          44 :     va_start (argp,
     333             :               fmt);
     334          44 :     json = json_vpack_ex (&jerror,
     335             :                           0,
     336             :                           fmt,
     337             :                           argp);
     338          44 :     va_end (argp);
     339             :   }
     340             : 
     341          44 :   if (NULL == json)
     342             :   {
     343           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     344             :                 "Failed to pack JSON with format `%s': %s\n",
     345             :                 fmt,
     346             :                 jerror.text);
     347           0 :     GNUNET_break (0);
     348           0 :     return MHD_NO;
     349             :   }
     350             : 
     351             :   {
     352             :     MHD_RESULT ret;
     353             : 
     354          44 :     ret = TALER_MHD_reply_json (connection,
     355             :                                 json,
     356             :                                 response_code);
     357          44 :     json_decref (json);
     358          44 :     return ret;
     359             :   }
     360             : }
     361             : 
     362             : 
     363             : /**
     364             :  * Make JSON response object.
     365             :  *
     366             :  * @param fmt format string for pack
     367             :  * @param ... varargs
     368             :  * @return MHD response object
     369             :  */
     370             : struct MHD_Response *
     371           0 : TALER_MHD_make_json_pack (const char *fmt,
     372             :                           ...)
     373             : {
     374             :   json_t *json;
     375             :   json_error_t jerror;
     376             : 
     377             :   {
     378             :     va_list argp;
     379             : 
     380           0 :     va_start (argp, fmt);
     381           0 :     json = json_vpack_ex (&jerror,
     382             :                           0,
     383             :                           fmt,
     384             :                           argp);
     385           0 :     va_end (argp);
     386             :   }
     387             : 
     388           0 :   if (NULL == json)
     389             :   {
     390           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     391             :                 "Failed to pack JSON with format `%s': %s\n",
     392             :                 fmt,
     393             :                 jerror.text);
     394           0 :     GNUNET_break (0);
     395           0 :     return MHD_NO;
     396             :   }
     397             : 
     398             :   {
     399             :     struct MHD_Response *response;
     400             : 
     401           0 :     response = TALER_MHD_make_json (json);
     402           0 :     json_decref (json);
     403           0 :     return response;
     404             :   }
     405             : }
     406             : 
     407             : 
     408             : /**
     409             :  * Create a response indicating an internal error.
     410             :  *
     411             :  * @param ec error code to return
     412             :  * @param detail additional optional detail about the error, can be NULL
     413             :  * @return a MHD response object
     414             :  */
     415             : struct MHD_Response *
     416           0 : TALER_MHD_make_error (enum TALER_ErrorCode ec,
     417             :                       const char *detail)
     418             : {
     419           0 :   return TALER_MHD_make_json_pack ("{s:I, s:s, s:s?}",
     420             :                                    "code", (json_int_t) ec,
     421             :                                    "hint", TALER_ErrorCode_get_hint (ec),
     422             :                                    "detail", detail);
     423             : }
     424             : 
     425             : 
     426             : /**
     427             :  * Send a response indicating an error.
     428             :  *
     429             :  * @param connection the MHD connection to use
     430             :  * @param ec error code uniquely identifying the error
     431             :  * @param http_status HTTP status code to use
     432             :  * @param detail additional optional detail about the error, can be NULL
     433             :  * @return a MHD result code
     434             :  */
     435             : MHD_RESULT
     436          23 : TALER_MHD_reply_with_error (struct MHD_Connection *connection,
     437             :                             unsigned int http_status,
     438             :                             enum TALER_ErrorCode ec,
     439             :                             const char *detail)
     440             : {
     441          23 :   return TALER_MHD_reply_json_pack (connection,
     442             :                                     http_status,
     443             :                                     "{s:I, s:s, s:s?}",
     444             :                                     "code", (json_int_t) ec,
     445             :                                     "hint", TALER_ErrorCode_get_hint (ec),
     446             :                                     "detail", detail);
     447             : }
     448             : 
     449             : 
     450             : /**
     451             :  * Send a response indicating an error. The HTTP status code is
     452             :  * to be derived from the @a ec.
     453             :  *
     454             :  * @param connection the MHD connection to use
     455             :  * @param ec error code uniquely identifying the error
     456             :  * @param detail additional optional detail about the error
     457             :  * @return a MHD result code
     458             :  */
     459             : MHD_RESULT
     460           0 : TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
     461             :                          enum TALER_ErrorCode ec,
     462             :                          const char *detail)
     463             : {
     464           0 :   unsigned int hc = TALER_ErrorCode_get_http_status (ec);
     465             : 
     466           0 :   if ( (0 == hc) ||
     467             :        (UINT_MAX == hc) )
     468             :   {
     469           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     470             :                 "Invalid Taler error code %d provided for response!\n",
     471             :                 (int) ec);
     472           0 :     hc = MHD_HTTP_INTERNAL_SERVER_ERROR;
     473             :   }
     474           0 :   return TALER_MHD_reply_with_error (connection,
     475             :                                      hc,
     476             :                                      ec,
     477             :                                      detail);
     478             : }
     479             : 
     480             : 
     481             : /**
     482             :  * Send a response indicating that the request was too big.
     483             :  *
     484             :  * @param connection the MHD connection to use
     485             :  * @return a MHD result code
     486             :  */
     487             : MHD_RESULT
     488           0 : TALER_MHD_reply_request_too_large (struct MHD_Connection *connection)
     489             : {
     490             :   struct MHD_Response *response;
     491             : 
     492           0 :   response = MHD_create_response_from_buffer (0,
     493             :                                               NULL,
     494             :                                               MHD_RESPMEM_PERSISTENT);
     495           0 :   if (NULL == response)
     496           0 :     return MHD_NO;
     497           0 :   TALER_MHD_add_global_headers (response);
     498             : 
     499             :   {
     500             :     MHD_RESULT ret;
     501             : 
     502           0 :     ret = MHD_queue_response (connection,
     503             :                               MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
     504             :                               response);
     505           0 :     MHD_destroy_response (response);
     506           0 :     return ret;
     507             :   }
     508             : }
     509             : 
     510             : 
     511             : /**
     512             :  * Function to call to handle the request by sending
     513             :  * back a redirect to the AGPL source code.
     514             :  *
     515             :  * @param connection the MHD connection to handle
     516             :  * @param url where to redirect for the sources
     517             :  * @return MHD result code
     518             :  */
     519             : MHD_RESULT
     520           4 : TALER_MHD_reply_agpl (struct MHD_Connection *connection,
     521             :                       const char *url)
     522             : {
     523           4 :   const char *agpl =
     524             :     "This server is licensed under the Affero GPL. You will now be redirected to the source code.";
     525             :   struct MHD_Response *response;
     526             : 
     527           4 :   response = MHD_create_response_from_buffer (strlen (agpl),
     528             :                                               (void *) agpl,
     529             :                                               MHD_RESPMEM_PERSISTENT);
     530           4 :   if (NULL == response)
     531             :   {
     532           0 :     GNUNET_break (0);
     533           0 :     return MHD_NO;
     534             :   }
     535           4 :   TALER_MHD_add_global_headers (response);
     536           4 :   GNUNET_break (MHD_YES ==
     537             :                 MHD_add_response_header (response,
     538             :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     539             :                                          "text/plain"));
     540           4 :   if (MHD_NO ==
     541           4 :       MHD_add_response_header (response,
     542             :                                MHD_HTTP_HEADER_LOCATION,
     543             :                                url))
     544             :   {
     545           0 :     GNUNET_break (0);
     546           0 :     MHD_destroy_response (response);
     547           0 :     return MHD_NO;
     548             :   }
     549             : 
     550             :   {
     551             :     MHD_RESULT ret;
     552             : 
     553           4 :     ret = MHD_queue_response (connection,
     554             :                               MHD_HTTP_FOUND,
     555             :                               response);
     556           4 :     MHD_destroy_response (response);
     557           4 :     return ret;
     558             :   }
     559             : }
     560             : 
     561             : 
     562             : /**
     563             :  * Function to call to handle the request by sending
     564             :  * back static data.
     565             :  *
     566             :  * @param connection the MHD connection to handle
     567             :  * @param http_status status code to return
     568             :  * @param mime_type content-type to use
     569             :  * @param body response payload
     570             :  * @param body_size number of bytes in @a body
     571             :  * @return MHD result code
     572             :  */
     573             : MHD_RESULT
     574          36 : TALER_MHD_reply_static (struct MHD_Connection *connection,
     575             :                         unsigned int http_status,
     576             :                         const char *mime_type,
     577             :                         const char *body,
     578             :                         size_t body_size)
     579             : {
     580             :   struct MHD_Response *response;
     581             : 
     582          36 :   response = MHD_create_response_from_buffer (body_size,
     583             :                                               (void *) body,
     584             :                                               MHD_RESPMEM_PERSISTENT);
     585          36 :   if (NULL == response)
     586             :   {
     587           0 :     GNUNET_break (0);
     588           0 :     return MHD_NO;
     589             :   }
     590          36 :   TALER_MHD_add_global_headers (response);
     591          36 :   if (NULL != mime_type)
     592           8 :     GNUNET_break (MHD_YES ==
     593             :                   MHD_add_response_header (response,
     594             :                                            MHD_HTTP_HEADER_CONTENT_TYPE,
     595             :                                            mime_type));
     596             :   {
     597             :     MHD_RESULT ret;
     598             : 
     599          36 :     ret = MHD_queue_response (connection,
     600             :                               http_status,
     601             :                               response);
     602          36 :     MHD_destroy_response (response);
     603          36 :     return ret;
     604             :   }
     605             : }
     606             : 
     607             : 
     608             : /* end of mhd_responses.c */

Generated by: LCOV version 1.14