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 "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 : void
36 2 : TALER_MHD_setup (enum TALER_MHD_GlobalOptions go)
37 : {
38 2 : TM_go = go;
39 2 : }
40 :
41 :
42 : void
43 14 : TALER_MHD_add_global_headers (struct MHD_Response *response)
44 : {
45 14 : if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE))
46 0 : GNUNET_break (MHD_YES ==
47 : MHD_add_response_header (response,
48 : MHD_HTTP_HEADER_CONNECTION,
49 : "close"));
50 : /* The wallet, operating from a background page, needs CORS to
51 : be disabled otherwise browsers block access. */
52 14 : GNUNET_break (MHD_YES ==
53 : MHD_add_response_header (response,
54 : MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
55 : "*"));
56 14 : GNUNET_break (MHD_YES ==
57 : MHD_add_response_header (response,
58 : /* Not available as MHD constant yet */
59 : "Access-Control-Expose-Headers",
60 : "*"));
61 14 : }
62 :
63 :
64 : MHD_RESULT
65 14 : TALER_MHD_can_compress (struct MHD_Connection *connection)
66 : {
67 : const char *ae;
68 : const char *de;
69 :
70 14 : if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION))
71 0 : return MHD_NO;
72 14 : ae = MHD_lookup_connection_value (connection,
73 : MHD_HEADER_KIND,
74 : MHD_HTTP_HEADER_ACCEPT_ENCODING);
75 14 : if (NULL == ae)
76 14 : return MHD_NO;
77 0 : if (0 == strcmp (ae,
78 : "*"))
79 0 : return MHD_YES;
80 0 : de = strstr (ae,
81 : "deflate");
82 0 : if (NULL == de)
83 0 : return MHD_NO;
84 0 : if ( ( (de == ae) ||
85 0 : (de[-1] == ',') ||
86 0 : (de[-1] == ' ') ) &&
87 0 : ( (de[strlen ("deflate")] == '\0') ||
88 0 : (de[strlen ("deflate")] == ',') ||
89 0 : (de[strlen ("deflate")] == ';') ) )
90 0 : return MHD_YES;
91 0 : return MHD_NO;
92 : }
93 :
94 :
95 : MHD_RESULT
96 0 : TALER_MHD_body_compress (void **buf,
97 : size_t *buf_size)
98 : {
99 : Bytef *cbuf;
100 : uLongf cbuf_size;
101 : MHD_RESULT ret;
102 :
103 0 : cbuf_size = compressBound (*buf_size);
104 0 : cbuf = malloc (cbuf_size);
105 0 : if (NULL == cbuf)
106 0 : return MHD_NO;
107 0 : ret = compress (cbuf,
108 : &cbuf_size,
109 : (const Bytef *) *buf,
110 : *buf_size);
111 0 : if ( (Z_OK != ret) ||
112 0 : (cbuf_size >= *buf_size) )
113 : {
114 : /* compression failed */
115 0 : free (cbuf);
116 0 : return MHD_NO;
117 : }
118 0 : free (*buf);
119 0 : *buf = (void *) cbuf;
120 0 : *buf_size = (size_t) cbuf_size;
121 0 : return MHD_YES;
122 : }
123 :
124 :
125 : struct MHD_Response *
126 0 : TALER_MHD_make_json (const json_t *json)
127 : {
128 : struct MHD_Response *response;
129 : char *json_str;
130 :
131 0 : json_str = json_dumps (json,
132 : JSON_INDENT (2));
133 0 : if (NULL == json_str)
134 : {
135 0 : GNUNET_break (0);
136 0 : return NULL;
137 : }
138 0 : response = MHD_create_response_from_buffer (strlen (json_str),
139 : json_str,
140 : MHD_RESPMEM_MUST_FREE);
141 0 : if (NULL == response)
142 : {
143 0 : free (json_str);
144 0 : GNUNET_break (0);
145 0 : return NULL;
146 : }
147 0 : TALER_MHD_add_global_headers (response);
148 0 : GNUNET_break (MHD_YES ==
149 : MHD_add_response_header (response,
150 : MHD_HTTP_HEADER_CONTENT_TYPE,
151 : "application/json"));
152 0 : return response;
153 : }
154 :
155 :
156 : struct MHD_Response *
157 0 : TALER_MHD_make_json_steal (json_t *json)
158 : {
159 : struct MHD_Response *res;
160 :
161 0 : res = TALER_MHD_make_json (json);
162 0 : json_decref (json);
163 0 : return res;
164 : }
165 :
166 :
167 : MHD_RESULT
168 14 : TALER_MHD_reply_json (struct MHD_Connection *connection,
169 : const json_t *json,
170 : unsigned int response_code)
171 : {
172 : struct MHD_Response *response;
173 : void *json_str;
174 : size_t json_len;
175 : MHD_RESULT is_compressed;
176 :
177 14 : json_str = json_dumps (json,
178 : JSON_INDENT (2));
179 14 : if (NULL == json_str)
180 : {
181 : /**
182 : * This log helps to figure out which
183 : * function called this one and assert-failed.
184 : */
185 0 : TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n",
186 : response_code);
187 :
188 0 : GNUNET_assert (0);
189 : return MHD_NO;
190 : }
191 14 : json_len = strlen (json_str);
192 : /* try to compress the body */
193 14 : is_compressed = MHD_NO;
194 14 : if (MHD_YES ==
195 14 : TALER_MHD_can_compress (connection))
196 0 : is_compressed = TALER_MHD_body_compress (&json_str,
197 : &json_len);
198 14 : response = MHD_create_response_from_buffer (json_len,
199 : json_str,
200 : MHD_RESPMEM_MUST_FREE);
201 14 : if (NULL == response)
202 : {
203 0 : free (json_str);
204 0 : GNUNET_break (0);
205 0 : return MHD_NO;
206 : }
207 14 : TALER_MHD_add_global_headers (response);
208 14 : GNUNET_break (MHD_YES ==
209 : MHD_add_response_header (response,
210 : MHD_HTTP_HEADER_CONTENT_TYPE,
211 : "application/json"));
212 14 : if (MHD_YES == is_compressed)
213 : {
214 : /* Need to indicate to client that body is compressed */
215 0 : if (MHD_NO ==
216 0 : MHD_add_response_header (response,
217 : MHD_HTTP_HEADER_CONTENT_ENCODING,
218 : "deflate"))
219 : {
220 0 : GNUNET_break (0);
221 0 : MHD_destroy_response (response);
222 0 : return MHD_NO;
223 : }
224 : }
225 :
226 : {
227 : MHD_RESULT ret;
228 :
229 14 : ret = MHD_queue_response (connection,
230 : response_code,
231 : response);
232 14 : MHD_destroy_response (response);
233 14 : return ret;
234 : }
235 : }
236 :
237 :
238 : MHD_RESULT
239 14 : TALER_MHD_reply_json_steal (struct MHD_Connection *connection,
240 : json_t *json,
241 : unsigned int response_code)
242 : {
243 : MHD_RESULT ret;
244 :
245 14 : ret = TALER_MHD_reply_json (connection,
246 : json,
247 : response_code);
248 14 : json_decref (json);
249 14 : return ret;
250 : }
251 :
252 :
253 : MHD_RESULT
254 0 : TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
255 : {
256 : struct MHD_Response *response;
257 :
258 0 : response = MHD_create_response_from_buffer (0,
259 : NULL,
260 : MHD_RESPMEM_PERSISTENT);
261 0 : if (NULL == response)
262 0 : return MHD_NO;
263 : /* This adds the Access-Control-Allow-Origin header.
264 : * All endpoints of the exchange allow CORS. */
265 0 : TALER_MHD_add_global_headers (response);
266 0 : GNUNET_break (MHD_YES ==
267 : MHD_add_response_header (response,
268 : /* Not available as MHD constant yet */
269 : "Access-Control-Allow-Headers",
270 : "*"));
271 0 : GNUNET_break (MHD_YES ==
272 : MHD_add_response_header (response,
273 : /* Not available as MHD constant yet */
274 : "Access-Control-Allow-Methods",
275 : "*"));
276 : {
277 : MHD_RESULT ret;
278 :
279 0 : ret = MHD_queue_response (connection,
280 : MHD_HTTP_NO_CONTENT,
281 : response);
282 0 : MHD_destroy_response (response);
283 0 : return ret;
284 : }
285 : }
286 :
287 :
288 : MHD_RESULT
289 0 : TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
290 : unsigned int response_code,
291 : const char *fmt,
292 : ...)
293 : {
294 : json_t *json;
295 : json_error_t jerror;
296 :
297 : {
298 : va_list argp;
299 :
300 0 : va_start (argp,
301 : fmt);
302 0 : json = json_vpack_ex (&jerror,
303 : 0,
304 : fmt,
305 : argp);
306 0 : va_end (argp);
307 : }
308 :
309 0 : if (NULL == json)
310 : {
311 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
312 : "Failed to pack JSON with format `%s': %s\n",
313 : fmt,
314 : jerror.text);
315 0 : GNUNET_break (0);
316 0 : return MHD_NO;
317 : }
318 :
319 : {
320 : MHD_RESULT ret;
321 :
322 0 : ret = TALER_MHD_reply_json (connection,
323 : json,
324 : response_code);
325 0 : json_decref (json);
326 0 : return ret;
327 : }
328 : }
329 :
330 :
331 : struct MHD_Response *
332 0 : TALER_MHD_make_json_pack (const char *fmt,
333 : ...)
334 : {
335 : json_t *json;
336 : json_error_t jerror;
337 :
338 : {
339 : va_list argp;
340 :
341 0 : va_start (argp, fmt);
342 0 : json = json_vpack_ex (&jerror,
343 : 0,
344 : fmt,
345 : argp);
346 0 : va_end (argp);
347 : }
348 :
349 0 : if (NULL == json)
350 : {
351 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
352 : "Failed to pack JSON with format `%s': %s\n",
353 : fmt,
354 : jerror.text);
355 0 : GNUNET_break (0);
356 0 : return NULL;
357 : }
358 :
359 : {
360 : struct MHD_Response *response;
361 :
362 0 : response = TALER_MHD_make_json (json);
363 0 : json_decref (json);
364 0 : return response;
365 : }
366 : }
367 :
368 :
369 : struct MHD_Response *
370 0 : TALER_MHD_make_error (enum TALER_ErrorCode ec,
371 : const char *detail)
372 : {
373 0 : return TALER_MHD_MAKE_JSON_PACK (
374 : TALER_MHD_PACK_EC (ec),
375 : GNUNET_JSON_pack_allow_null (
376 : GNUNET_JSON_pack_string ("detail", detail)));
377 : }
378 :
379 :
380 : MHD_RESULT
381 2 : TALER_MHD_reply_with_error (struct MHD_Connection *connection,
382 : unsigned int http_status,
383 : enum TALER_ErrorCode ec,
384 : const char *detail)
385 : {
386 2 : return TALER_MHD_REPLY_JSON_PACK (
387 : connection,
388 : http_status,
389 : TALER_MHD_PACK_EC (ec),
390 : GNUNET_JSON_pack_allow_null (
391 : GNUNET_JSON_pack_string ("detail", detail)));
392 : }
393 :
394 :
395 : MHD_RESULT
396 0 : TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
397 : enum TALER_ErrorCode ec,
398 : const char *detail)
399 : {
400 0 : unsigned int hc = TALER_ErrorCode_get_http_status (ec);
401 :
402 0 : if ( (0 == hc) ||
403 : (UINT_MAX == hc) )
404 : {
405 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
406 : "Invalid Taler error code %d provided for response!\n",
407 : (int) ec);
408 0 : hc = MHD_HTTP_INTERNAL_SERVER_ERROR;
409 : }
410 0 : return TALER_MHD_reply_with_error (connection,
411 : hc,
412 : ec,
413 : detail);
414 : }
415 :
416 :
417 : MHD_RESULT
418 0 : TALER_MHD_reply_request_too_large (struct MHD_Connection *connection)
419 : {
420 0 : return TALER_MHD_reply_with_error (connection,
421 : MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
422 : TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
423 : NULL);
424 : }
425 :
426 :
427 : MHD_RESULT
428 0 : TALER_MHD_reply_agpl (struct MHD_Connection *connection,
429 : const char *url)
430 : {
431 0 : const char *agpl =
432 : "This server is licensed under the Affero GPL. You will now be redirected to the source code.";
433 : struct MHD_Response *response;
434 :
435 0 : response = MHD_create_response_from_buffer (strlen (agpl),
436 : (void *) agpl,
437 : MHD_RESPMEM_PERSISTENT);
438 0 : if (NULL == response)
439 : {
440 0 : GNUNET_break (0);
441 0 : return MHD_NO;
442 : }
443 0 : TALER_MHD_add_global_headers (response);
444 0 : GNUNET_break (MHD_YES ==
445 : MHD_add_response_header (response,
446 : MHD_HTTP_HEADER_CONTENT_TYPE,
447 : "text/plain"));
448 0 : if (MHD_NO ==
449 0 : MHD_add_response_header (response,
450 : MHD_HTTP_HEADER_LOCATION,
451 : url))
452 : {
453 0 : GNUNET_break (0);
454 0 : MHD_destroy_response (response);
455 0 : return MHD_NO;
456 : }
457 :
458 : {
459 : MHD_RESULT ret;
460 :
461 0 : ret = MHD_queue_response (connection,
462 : MHD_HTTP_FOUND,
463 : response);
464 0 : MHD_destroy_response (response);
465 0 : return ret;
466 : }
467 : }
468 :
469 :
470 : MHD_RESULT
471 0 : TALER_MHD_reply_static (struct MHD_Connection *connection,
472 : unsigned int http_status,
473 : const char *mime_type,
474 : const char *body,
475 : size_t body_size)
476 : {
477 : struct MHD_Response *response;
478 :
479 0 : response = MHD_create_response_from_buffer (body_size,
480 : (void *) body,
481 : MHD_RESPMEM_PERSISTENT);
482 0 : if (NULL == response)
483 : {
484 0 : GNUNET_break (0);
485 0 : return MHD_NO;
486 : }
487 0 : TALER_MHD_add_global_headers (response);
488 0 : if (NULL != mime_type)
489 0 : GNUNET_break (MHD_YES ==
490 : MHD_add_response_header (response,
491 : MHD_HTTP_HEADER_CONTENT_TYPE,
492 : mime_type));
493 : {
494 : MHD_RESULT ret;
495 :
496 0 : ret = MHD_queue_response (connection,
497 : http_status,
498 : response);
499 0 : MHD_destroy_response (response);
500 0 : return ret;
501 : }
502 : }
503 :
504 :
505 : void
506 0 : TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at,
507 : char date[128])
508 : {
509 : static const char *const days[] =
510 : { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
511 : static const char *const mons[] =
512 : { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
513 : "Nov", "Dec"};
514 : struct tm now;
515 : time_t t;
516 : #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \
517 : ! defined(HAVE_GMTIME_R)
518 : struct tm*pNow;
519 : #endif
520 :
521 0 : date[0] = 0;
522 0 : t = (time_t) (at.abs_value_us / 1000LL / 1000LL);
523 : #if defined(HAVE_C11_GMTIME_S)
524 : if (NULL == gmtime_s (&t, &now))
525 : return;
526 : #elif defined(HAVE_W32_GMTIME_S)
527 : if (0 != gmtime_s (&now, &t))
528 : return;
529 : #elif defined(HAVE_GMTIME_R)
530 : if (NULL == gmtime_r (&t, &now))
531 : return;
532 : #else
533 0 : pNow = gmtime (&t);
534 0 : if (NULL == pNow)
535 0 : return;
536 0 : now = *pNow;
537 : #endif
538 0 : sprintf (date,
539 : "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
540 0 : days[now.tm_wday % 7],
541 0 : (unsigned int) now.tm_mday,
542 0 : mons[now.tm_mon % 12],
543 0 : (unsigned int) (1900 + now.tm_year),
544 0 : (unsigned int) now.tm_hour,
545 0 : (unsigned int) now.tm_min,
546 0 : (unsigned int) now.tm_sec);
547 : }
548 :
549 :
550 : /* end of mhd_responses.c */
|