Line data Source code
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 26 : TALER_MHD_setup (enum TALER_MHD_GlobalOptions go)
37 : {
38 26 : TM_go = go;
39 26 : }
40 :
41 :
42 : void
43 1476 : TALER_MHD_add_global_headers (struct MHD_Response *response,
44 : bool allow_store)
45 : {
46 1476 : if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE))
47 0 : 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 1476 : GNUNET_break (MHD_YES ==
54 : MHD_add_response_header (response,
55 : MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
56 : "*"));
57 1476 : GNUNET_break (MHD_YES ==
58 : MHD_add_response_header (response,
59 : /* Not available as MHD constant yet */
60 : "Access-Control-Expose-Headers",
61 : "*"));
62 1476 : if (! allow_store)
63 829 : GNUNET_break (MHD_YES ==
64 : MHD_add_response_header (response,
65 : MHD_HTTP_HEADER_CACHE_CONTROL,
66 : "no-store"));
67 1476 : }
68 :
69 :
70 : enum TALER_MHD_CompressionType
71 846 : 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 846 : } 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 846 : if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION))
89 0 : return TALER_MHD_CT_NONE;
90 846 : ae = MHD_lookup_connection_value (connection,
91 : MHD_HEADER_KIND,
92 : MHD_HTTP_HEADER_ACCEPT_ENCODING);
93 846 : if (NULL == ae)
94 279 : return TALER_MHD_CT_NONE;
95 567 : if (0 == strcmp (ae,
96 : "*"))
97 0 : return MHD_YES;
98 1753 : for (unsigned int i=0; NULL != map[i].name; i++)
99 : {
100 1701 : const char *name = map[i].name;
101 :
102 1701 : if (map[i].ct > max)
103 1134 : continue; /* not allowed by client */
104 567 : de = strstr (ae,
105 : name);
106 567 : if (NULL == de)
107 52 : continue;
108 515 : if ( ( (de == ae) ||
109 0 : (de[-1] == ',') ||
110 0 : (de[-1] == ' ') ) &&
111 515 : ( (de[strlen (name)] == '\0') ||
112 515 : (de[strlen (name)] == ',') ||
113 0 : (de[strlen (name)] == ';') ) )
114 515 : return map[i].ct;
115 : }
116 52 : return TALER_MHD_CT_NONE;
117 : }
118 :
119 :
120 : MHD_RESULT
121 571 : 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 571 : cbuf_size = compressBound (*buf_size);
129 571 : cbuf = malloc (cbuf_size);
130 571 : if (NULL == cbuf)
131 0 : return MHD_NO;
132 571 : ret = compress (cbuf,
133 : &cbuf_size,
134 : (const Bytef *) *buf,
135 : *buf_size);
136 571 : if ( (Z_OK != ret) ||
137 571 : (cbuf_size >= *buf_size) )
138 : {
139 : /* compression failed */
140 49 : free (cbuf);
141 49 : return MHD_NO;
142 : }
143 522 : free (*buf);
144 522 : *buf = (void *) cbuf;
145 522 : *buf_size = (size_t) cbuf_size;
146 522 : return MHD_YES;
147 : }
148 :
149 :
150 : struct MHD_Response *
151 40 : TALER_MHD_make_json (const json_t *json)
152 : {
153 : struct MHD_Response *response;
154 : char *json_str;
155 :
156 40 : json_str = json_dumps (json,
157 : JSON_INDENT (2));
158 40 : if (NULL == json_str)
159 : {
160 0 : GNUNET_break (0);
161 0 : return NULL;
162 : }
163 40 : response = MHD_create_response_from_buffer (strlen (json_str),
164 : json_str,
165 : MHD_RESPMEM_MUST_FREE);
166 40 : if (NULL == response)
167 : {
168 0 : free (json_str);
169 0 : GNUNET_break (0);
170 0 : return NULL;
171 : }
172 40 : TALER_MHD_add_global_headers (response,
173 : false);
174 40 : GNUNET_break (MHD_YES ==
175 : MHD_add_response_header (response,
176 : MHD_HTTP_HEADER_CONTENT_TYPE,
177 : "application/json"));
178 40 : return response;
179 : }
180 :
181 :
182 : struct MHD_Response *
183 40 : TALER_MHD_make_json_steal (json_t *json)
184 : {
185 : struct MHD_Response *res;
186 :
187 40 : res = TALER_MHD_make_json (json);
188 40 : json_decref (json);
189 40 : return res;
190 : }
191 :
192 :
193 : MHD_RESULT
194 784 : 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 784 : json_str = json_dumps (json,
204 : JSON_INDENT (2));
205 784 : if (NULL == json_str)
206 : {
207 : /**
208 : * This log helps to figure out which
209 : * function called this one and assert-failed.
210 : */
211 0 : TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n",
212 : response_code);
213 :
214 0 : GNUNET_assert (0);
215 : return MHD_NO;
216 : }
217 784 : json_len = strlen (json_str);
218 : /* try to compress the body */
219 784 : is_compressed = MHD_NO;
220 784 : if (TALER_MHD_CT_DEFLATE ==
221 784 : TALER_MHD_can_compress (connection,
222 : TALER_MHD_CT_DEFLATE))
223 478 : is_compressed = TALER_MHD_body_compress (&json_str,
224 : &json_len);
225 784 : response = MHD_create_response_from_buffer (json_len,
226 : json_str,
227 : MHD_RESPMEM_MUST_FREE);
228 784 : if (NULL == response)
229 : {
230 0 : free (json_str);
231 0 : GNUNET_break (0);
232 0 : return MHD_NO;
233 : }
234 784 : TALER_MHD_add_global_headers (response,
235 : false);
236 784 : GNUNET_break (MHD_YES ==
237 : MHD_add_response_header (response,
238 : MHD_HTTP_HEADER_CONTENT_TYPE,
239 : "application/json"));
240 784 : if (MHD_YES == is_compressed)
241 : {
242 : /* Need to indicate to client that body is compressed */
243 429 : if (MHD_NO ==
244 429 : MHD_add_response_header (response,
245 : MHD_HTTP_HEADER_CONTENT_ENCODING,
246 : "deflate"))
247 : {
248 0 : GNUNET_break (0);
249 0 : MHD_destroy_response (response);
250 0 : return MHD_NO;
251 : }
252 : }
253 :
254 : {
255 : MHD_RESULT ret;
256 :
257 784 : ret = MHD_queue_response (connection,
258 : response_code,
259 : response);
260 784 : MHD_destroy_response (response);
261 784 : return ret;
262 : }
263 : }
264 :
265 :
266 : MHD_RESULT
267 739 : 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 739 : ret = TALER_MHD_reply_json (connection,
274 : json,
275 : response_code);
276 739 : json_decref (json);
277 739 : return ret;
278 : }
279 :
280 :
281 : MHD_RESULT
282 0 : TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
283 : {
284 : struct MHD_Response *response;
285 :
286 0 : response = MHD_create_response_from_buffer (0,
287 : NULL,
288 : MHD_RESPMEM_PERSISTENT);
289 0 : if (NULL == response)
290 0 : return MHD_NO;
291 : /* This adds the Access-Control-Allow-Origin header.
292 : * All endpoints of the exchange allow CORS. */
293 0 : TALER_MHD_add_global_headers (response,
294 : true);
295 0 : GNUNET_break (MHD_YES ==
296 : MHD_add_response_header (response,
297 : /* Not available as MHD constant yet */
298 : "Access-Control-Allow-Headers",
299 : "*"));
300 0 : 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 0 : ret = MHD_queue_response (connection,
309 : MHD_HTTP_NO_CONTENT,
310 : response);
311 0 : MHD_destroy_response (response);
312 0 : return ret;
313 : }
314 : }
315 :
316 :
317 : struct MHD_Response *
318 0 : TALER_MHD_make_error (enum TALER_ErrorCode ec,
319 : const char *detail)
320 : {
321 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
322 : "Generating JSON response with code %d (%s)\n",
323 : (int) ec,
324 : detail);
325 0 : 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 42 : 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 42 : 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 42 : 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 0 : TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
354 : enum TALER_ErrorCode ec,
355 : const char *detail)
356 : {
357 0 : unsigned int hc = TALER_ErrorCode_get_http_status (ec);
358 :
359 0 : if ( (0 == hc) ||
360 : (UINT_MAX == hc) )
361 : {
362 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
363 : "Invalid Taler error code %d provided for response!\n",
364 : (int) ec);
365 0 : hc = MHD_HTTP_INTERNAL_SERVER_ERROR;
366 : }
367 0 : return TALER_MHD_reply_with_error (connection,
368 : hc,
369 : ec,
370 : detail);
371 : }
372 :
373 :
374 : MHD_RESULT
375 0 : TALER_MHD_reply_request_too_large (struct MHD_Connection *connection)
376 : {
377 0 : 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 4 : TALER_MHD_reply_agpl (struct MHD_Connection *connection,
386 : const char *url)
387 : {
388 4 : 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 4 : response = MHD_create_response_from_buffer (strlen (agpl),
393 : (void *) agpl,
394 : MHD_RESPMEM_PERSISTENT);
395 4 : if (NULL == response)
396 : {
397 0 : GNUNET_break (0);
398 0 : return MHD_NO;
399 : }
400 4 : TALER_MHD_add_global_headers (response,
401 : true);
402 4 : GNUNET_break (MHD_YES ==
403 : MHD_add_response_header (response,
404 : MHD_HTTP_HEADER_CONTENT_TYPE,
405 : "text/plain"));
406 4 : if (MHD_NO ==
407 4 : MHD_add_response_header (response,
408 : MHD_HTTP_HEADER_LOCATION,
409 : url))
410 : {
411 0 : GNUNET_break (0);
412 0 : MHD_destroy_response (response);
413 0 : return MHD_NO;
414 : }
415 :
416 : {
417 : MHD_RESULT ret;
418 :
419 4 : ret = MHD_queue_response (connection,
420 : MHD_HTTP_FOUND,
421 : response);
422 4 : MHD_destroy_response (response);
423 4 : return ret;
424 : }
425 : }
426 :
427 :
428 : MHD_RESULT
429 450 : 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 450 : response = MHD_create_response_from_buffer (body_size,
438 : (void *) body,
439 : MHD_RESPMEM_PERSISTENT);
440 450 : if (NULL == response)
441 : {
442 0 : GNUNET_break (0);
443 0 : return MHD_NO;
444 : }
445 450 : TALER_MHD_add_global_headers (response,
446 : true);
447 450 : if (NULL != mime_type)
448 5 : 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 450 : ret = MHD_queue_response (connection,
456 : http_status,
457 : response);
458 450 : MHD_destroy_response (response);
459 450 : return ret;
460 : }
461 : }
462 :
463 :
464 : void
465 188 : 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 188 : date[0] = 0;
481 188 : 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 188 : pNow = gmtime (&t);
493 188 : if (NULL == pNow)
494 0 : return;
495 188 : now = *pNow;
496 : #endif
497 188 : sprintf (date,
498 : "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
499 188 : days[now.tm_wday % 7],
500 188 : (unsigned int) now.tm_mday,
501 188 : mons[now.tm_mon % 12],
502 188 : (unsigned int) (1900 + now.tm_year),
503 188 : (unsigned int) now.tm_hour,
504 188 : (unsigned int) now.tm_min,
505 188 : (unsigned int) now.tm_sec);
506 : }
507 :
508 :
509 : /* end of mhd_responses.c */
|