diff options
Diffstat (limited to 'src/mhd')
-rw-r--r-- | src/mhd/Makefile.am | 12 | ||||
-rw-r--r-- | src/mhd/mhd_config.c | 75 | ||||
-rw-r--r-- | src/mhd/mhd_legal.c | 392 | ||||
-rw-r--r-- | src/mhd/mhd_parsing.c | 524 | ||||
-rw-r--r-- | src/mhd/mhd_responses.c | 258 | ||||
-rw-r--r-- | src/mhd/mhd_run.c | 175 |
6 files changed, 1014 insertions, 422 deletions
diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am index ec0103f4b..cd3fe7701 100644 --- a/src/mhd/Makefile.am +++ b/src/mhd/Makefile.am @@ -13,15 +13,17 @@ libtalermhd_la_SOURCES = \ mhd_config.c \ mhd_legal.c \ mhd_parsing.c \ - mhd_responses.c + mhd_responses.c \ + mhd_run.c libtalermhd_la_LDFLAGS = \ - -version-info 0:0:0 \ - -export-dynamic -no-undefined + -version-info 3:0:3 \ + -no-undefined libtalermhd_la_LIBADD = \ - -lgnunetjson \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ -lgnunetutil \ + -lmicrohttpd \ -ljansson \ + -lz \ $(XLIB) - diff --git a/src/mhd/mhd_config.c b/src/mhd/mhd_config.c index e2471e9b5..31ec3e476 100644 --- a/src/mhd/mhd_config.c +++ b/src/mhd/mhd_config.c @@ -78,7 +78,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg, section, - "port", + "PORT", &port)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, @@ -124,6 +124,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unixpath `%s' is too long\n", *unix_path); + GNUNET_free (*unix_path); return GNUNET_SYSERR; } @@ -136,6 +137,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "UNIXPATH_MODE"); + GNUNET_free (*unix_path); return GNUNET_SYSERR; } errno = 0; @@ -147,6 +149,7 @@ TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, "UNIXPATH_MODE", "must be octal number"); GNUNET_free (modestring); + GNUNET_free (*unix_path); return GNUNET_SYSERR; } GNUNET_free (modestring); @@ -252,6 +255,7 @@ TALER_MHD_open_unix_path (const char *unix_path, GNUNET_free (un); return -1; } + if (GNUNET_OK != GNUNET_NETWORK_socket_bind (nh, (void *) un, @@ -319,6 +323,53 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg, char *bind_to; struct GNUNET_NETWORK_Handle *nh; + /* try systemd passing first */ + { + const char *listen_pid; + const char *listen_fds; + + /* check for systemd-style FD passing */ + listen_pid = getenv ("LISTEN_PID"); + listen_fds = getenv ("LISTEN_FDS"); + if ( (NULL != listen_pid) && + (NULL != listen_fds) && + (getpid () == strtol (listen_pid, + NULL, + 10)) && + (1 == strtoul (listen_fds, + NULL, + 10)) ) + { + int fh; + int flags; + + fh = 3; + flags = fcntl (fh, + F_GETFD); + if ( (-1 == flags) && + (EBADF == errno) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Bad listen socket passed, ignored\n"); + fh = -1; + } + flags |= FD_CLOEXEC; + if ( (-1 != fh) && + (0 != fcntl (fh, + F_SETFD, + flags)) ) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "fcntl"); + if (-1 != fh) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Successfully obtained listen socket from hypervisor\n"); + return fh; + } + } + } + + /* now try configuration file */ *port = 0; { char *serve_unixpath; @@ -332,8 +383,14 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg, &unixpath_mode)) return -1; if (NULL != serve_unixpath) - return TALER_MHD_open_unix_path (serve_unixpath, - unixpath_mode); + { + int ret; + + ret = TALER_MHD_open_unix_path (serve_unixpath, + unixpath_mode); + GNUNET_free (serve_unixpath); + return ret; + } } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, @@ -390,6 +447,18 @@ TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg, freeaddrinfo (res); return -1; } + { + const int on = 1; + + if (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (nh, + SOL_SOCKET, + SO_REUSEPORT, + &on, + sizeof(on))) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "setsockopt"); + } if (GNUNET_OK != GNUNET_NETWORK_socket_bind (nh, res->ai_addr, diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c index 19a21475f..8353a6901 100644 --- a/src/mhd/mhd_legal.c +++ b/src/mhd/mhd_legal.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2019 Taler Systems SA + Copyright (C) 2019, 2020, 2022, 2024 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 @@ -24,8 +24,14 @@ #include <gnunet/gnunet_json_lib.h> #include <jansson.h> #include <microhttpd.h> +#include "taler_util.h" #include "taler_mhd_lib.h" +/** + * How long should browsers/proxies cache the "legal" replies? + */ +#define MAX_TERMS_CACHING GNUNET_TIME_UNIT_DAYS + /** * Entry in the terms-of-service array. @@ -33,12 +39,23 @@ struct Terms { /** + * Kept in a DLL. + */ + struct Terms *prev; + + /** + * Kept in a DLL. + */ + struct Terms *next; + + /** * Mime type of the terms. */ const char *mime_type; /** - * The terms (NOT 0-terminated!). + * The terms (NOT 0-terminated!), mmap()'ed. Do not free, + * use munmap() instead. */ void *terms; @@ -48,9 +65,27 @@ struct Terms char *language; /** + * deflated @e terms, to return if client supports deflate compression. + * malloc()'ed. NULL if @e terms does not compress. + */ + void *compressed_terms; + + /** * Number of bytes in @e terms. */ size_t terms_size; + + /** + * Number of bytes in @e compressed_terms. + */ + size_t compressed_terms_size; + + /** + * Sorting key by format preference in case + * everything else is equal. Higher is preferred. + */ + unsigned int priority; + }; @@ -61,14 +96,14 @@ struct Terms struct TALER_MHD_Legal { /** - * Array of terms of service, terminated by NULL/0 value. + * DLL of terms of service. */ - struct Terms *terms; + struct Terms *terms_head; /** - * Length of the #terms array. + * DLL of terms of service. */ - unsigned int terms_len; + struct Terms *terms_tail; /** * Etag to use for the terms of service (= version). @@ -80,7 +115,8 @@ struct TALER_MHD_Legal /** * Check if @a mime matches the @a accept_pattern. * - * @param accept_pattern a mime pattern like text/plain or image/STAR + * @param accept_pattern a mime pattern like "text/plain" + * or "image/STAR" * @param mime the mime type to match * @return true if @a mime matches the @a accept_pattern */ @@ -90,10 +126,18 @@ mime_matches (const char *accept_pattern, { const char *da = strchr (accept_pattern, '/'); const char *dm = strchr (mime, '/'); + const char *end; if ( (NULL == da) || (NULL == dm) ) return (0 == strcmp ("*", accept_pattern)); + // FIXME: use TALER_MHD_check_accept() here! + /* FIXME: eventually, we might want to parse the "q=$FLOAT" + part after the ';' and figure out which one is the + best/preferred match instead of returning a boolean... */ + end = strchr (da, ';'); + if (NULL == end) + end = &da[strlen (da)]; return ( ( (1 == da - accept_pattern) && ('*' == *accept_pattern) ) || @@ -102,72 +146,60 @@ mime_matches (const char *accept_pattern, mime, da - accept_pattern)) ) ) && ( (0 == strcmp (da, "/*")) || - (0 == strcasecmp (da, - dm)) ); + (0 == strncasecmp (da, + dm, + end - da)) ); } -/** - * Check if @a lang matches the @a language_pattern, and if so with - * which preference. - * See also: https://tools.ietf.org/html/rfc7231#section-5.3.1 - * - * @param language_pattern a language preferences string - * like "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.1" - * @param lang the 2-digit language to match - * @return q-weight given for @a lang in @a language_pattern, 1.0 if no weights are given; - * 0 if @a lang is not in @a language_pattern - */ -static double -language_matches (const char *language_pattern, - const char *lang) +bool +TALER_MHD_xmime_matches (const char *accept_pattern, + const char *mime) { - char *p = GNUNET_strdup (language_pattern); + char *ap = GNUNET_strdup (accept_pattern); char *sptr; - double r = 0.0; - for (char *tok = strtok_r (p, ",", &sptr); + for (const char *tok = strtok_r (ap, ",", &sptr); NULL != tok; tok = strtok_r (NULL, ",", &sptr)) { - char *sptr2; - char *lp = strtok_r (tok, ";", &sptr2); - char *qp = strtok_r (NULL, ";", &sptr2); - double q = 1.0; - - while (isspace ((int) *lp)) - lp++; - if (NULL != qp) - while (isspace ((int) *qp)) - qp++; - GNUNET_break_op ( (NULL == qp) || - (1 == sscanf (qp, - "q=%lf", - &q)) ); - if (0 == strcasecmp (lang, - lp)) - r = GNUNET_MAX (r, q); + if (mime_matches (tok, + mime)) + { + GNUNET_free (ap); + return true; + } } - GNUNET_free (p); - return r; + GNUNET_free (ap); + return false; } -/** - * Generate a response with a legal document in the format and language of the - * user's choosing. - * - * @param conn HTTP connection to handle - * @param legal legal document to serve - * @return MHD result code - */ MHD_RESULT TALER_MHD_reply_legal (struct MHD_Connection *conn, struct TALER_MHD_Legal *legal) { struct MHD_Response *resp; struct Terms *t; - + struct GNUNET_TIME_Absolute a; + struct GNUNET_TIME_Timestamp m; + char dat[128]; + char *langs; + + a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING); + m = GNUNET_TIME_absolute_to_timestamp (a); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + MAX_TERMS_CACHING); + a = GNUNET_TIME_absolute_add (a, + MAX_TERMS_CACHING); + TALER_MHD_get_date_string (m.abs_time, + dat); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setting '%s' header to '%s'\n", + MHD_HTTP_HEADER_EXPIRES, + dat); if (NULL != legal) { const char *etag; @@ -186,6 +218,15 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, NULL, MHD_RESPMEM_PERSISTENT); TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_EXPIRES, + dat)); + + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_ETAG, + legal->terms_etag)); ret = MHD_queue_response (conn, MHD_HTTP_NOT_MODIFIED, resp); @@ -196,6 +237,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, } t = NULL; + langs = NULL; if (NULL != legal) { const char *mime; @@ -205,7 +247,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT); if (NULL == mime) - mime = "text/html"; + mime = "text/plain"; lang = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT_LANGUAGE); @@ -213,21 +255,36 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, lang = "en"; /* Find best match: must match mime type (if possible), and if mime type matches, ideally also language */ - for (unsigned int i = 0; i < legal->terms_len; i++) + for (struct Terms *p = legal->terms_head; + NULL != p; + p = p->next) { - struct Terms *p = &legal->terms[i]; - if ( (NULL == t) || - (mime_matches (mime, - p->mime_type)) ) + (TALER_MHD_xmime_matches (mime, + p->mime_type)) ) { + if (NULL == langs) + { + langs = GNUNET_strdup (p->language); + } + else if (NULL == strstr (langs, + p->language)) + { + char *tmp = langs; + + GNUNET_asprintf (&langs, + "%s,%s", + tmp, + p->language); + GNUNET_free (tmp); + } if ( (NULL == t) || - (! mime_matches (mime, - t->mime_type)) || - (language_matches (lang, - p->language) > - language_matches (lang, - t->language) ) ) + (! TALER_MHD_xmime_matches (mime, + t->mime_type)) || + (TALER_language_matches (lang, + p->language) > + TALER_language_matches (lang, + t->language) ) ) t = p; } } @@ -257,29 +314,17 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, if (MHD_YES == TALER_MHD_can_compress (conn)) { - void *buf = GNUNET_memdup (t->terms, - t->terms_size); - size_t buf_size = t->terms_size; - - if (TALER_MHD_body_compress (&buf, - &buf_size)) - { - resp = MHD_create_response_from_buffer (buf_size, - buf, - MHD_RESPMEM_MUST_FREE); - if (MHD_NO == - MHD_add_response_header (resp, - MHD_HTTP_HEADER_CONTENT_ENCODING, - "deflate")) - { - GNUNET_break (0); - MHD_destroy_response (resp); - resp = NULL; - } - } - else + resp = MHD_create_response_from_buffer (t->compressed_terms_size, + t->compressed_terms, + MHD_RESPMEM_PERSISTENT); + if (MHD_NO == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) { - GNUNET_free (buf); + GNUNET_break (0); + MHD_destroy_response (resp); + resp = NULL; } } if (NULL == resp) @@ -290,6 +335,30 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, MHD_RESPMEM_PERSISTENT); } TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_EXPIRES, + dat)); + if (NULL != langs) + { + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + "Avail-Languages", + langs)); + GNUNET_free (langs); + } + /* Set cache control headers: our response varies depending on these headers */ + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_VARY, + MHD_HTTP_HEADER_ACCEPT_LANGUAGE "," + MHD_HTTP_HEADER_ACCEPT "," + MHD_HTTP_HEADER_ACCEPT_ENCODING)); + /* Information is always public, revalidate after 10 days */ + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,max-age=864000")); if (NULL != legal) GNUNET_break (MHD_YES == MHD_add_response_header (resp, @@ -299,6 +368,10 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, t->mime_type)); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_LANGUAGE, + t->language)); { MHD_RESULT ret; @@ -330,21 +403,24 @@ load_terms (struct TALER_MHD_Legal *legal, { const char *ext; const char *mime; + unsigned int priority; } mm[] = { - { .ext = ".html", .mime = "text/html" }, - { .ext = ".htm", .mime = "text/html" }, - { .ext = ".txt", .mime = "text/plain" }, - { .ext = ".pdf", .mime = "application/pdf" }, + { .ext = ".txt", .mime = "text/plain", .priority = 150 }, + { .ext = ".html", .mime = "text/html", .priority = 100 }, + { .ext = ".htm", .mime = "text/html", .priority = 99 }, + { .ext = ".md", .mime = "text/markdown", .priority = 50 }, + { .ext = ".pdf", .mime = "application/pdf", .priority = 25 }, { .ext = ".jpg", .mime = "image/jpeg" }, { .ext = ".jpeg", .mime = "image/jpeg" }, { .ext = ".png", .mime = "image/png" }, { .ext = ".gif", .mime = "image/gif" }, - { .ext = ".epub", .mime = "application/epub+zip" }, - { .ext = ".xml", .mime = "text/xml" }, + { .ext = ".epub", .mime = "application/epub+zip", .priority = 10 }, + { .ext = ".xml", .mime = "text/xml", .priority = 10 }, { .ext = NULL, .mime = NULL } }; const char *ext = strrchr (name, '.'); const char *mime; + unsigned int priority; if (NULL == ext) { @@ -360,7 +436,7 @@ load_terms (struct TALER_MHD_Legal *legal, name, ext - name - 1)) ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Filename `%s' does not match Etag `%s' in directory `%s/%s'. Ignoring it.\n", name, legal->terms_etag, @@ -374,6 +450,7 @@ load_terms (struct TALER_MHD_Legal *legal, ext)) { mime = mm[i].mime; + priority = mm[i].priority; break; } if (NULL == mime) @@ -424,50 +501,70 @@ load_terms (struct TALER_MHD_Legal *legal, GNUNET_free (fn); return; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Loading legal information from file `%s'\n", + fn); { - char *buf; + void *buf; size_t bsize; - ssize_t ret; bsize = (size_t) st.st_size; - buf = GNUNET_malloc_large (bsize); - if (NULL == buf) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "malloc"); - GNUNET_break (0 == close (fd)); - GNUNET_free (fn); - return; - } - ret = read (fd, - buf, - bsize); - if ( (ret < 0) || - (bsize != ((size_t) ret)) ) + buf = mmap (NULL, + bsize, + PROT_READ, + MAP_SHARED, + fd, + 0); + if (MAP_FAILED == buf) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, - "read", + "mmap", fn); GNUNET_break (0 == close (fd)); - GNUNET_free (buf); GNUNET_free (fn); return; } GNUNET_break (0 == close (fd)); GNUNET_free (fn); - /* append to global list of terms of service */ + /* insert into global list of terms of service */ { - struct Terms t = { - .mime_type = mime, - .terms = buf, - .language = GNUNET_strdup (lang), - .terms_size = bsize - }; - - GNUNET_array_append (legal->terms, - legal->terms_len, - t); + struct Terms *t; + + t = GNUNET_new (struct Terms); + t->mime_type = mime; + t->terms = buf; + t->language = GNUNET_strdup (lang); + t->terms_size = bsize; + t->priority = priority; + buf = GNUNET_memdup (t->terms, + t->terms_size); + if (TALER_MHD_body_compress (&buf, + &bsize)) + { + t->compressed_terms = buf; + t->compressed_terms_size = bsize; + } + else + { + GNUNET_free (buf); + } + { + struct Terms *prev = NULL; + + for (struct Terms *pos = legal->terms_head; + NULL != pos; + pos = pos->next) + { + if (pos->priority < priority) + break; + prev = pos; + } + GNUNET_CONTAINER_DLL_insert_after (legal->terms_head, + legal->terms_tail, + prev, + t); + } } } } @@ -507,26 +604,16 @@ load_language (struct TALER_MHD_Legal *legal, if (fn[0] == '.') continue; - load_terms (legal, path, lang, fn); + load_terms (legal, + path, + lang, + fn); } GNUNET_break (0 == closedir (d)); GNUNET_free (dname); } -/** - * Load set of legal documents as specified in @a cfg in section @a section - * where the Etag is given under the @param tagoption and the directory under - * the @a diroption. - * - * @param cfg configuration to use - * @param section section to load values from - * @param diroption name of the option with the - * path to the legal documents - * @param tagoption name of the files to use - * for the legal documents and the Etag - * @return NULL on error - */ struct TALER_MHD_Legal * TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section, @@ -583,7 +670,12 @@ TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg, if (lang[0] == '.') continue; - load_language (legal, path, lang); + if (0 == strcmp (lang, + "locale")) + continue; + load_language (legal, + path, + lang); } GNUNET_break (0 == closedir (d)); GNUNET_free (path); @@ -591,26 +683,24 @@ TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg, } -/** - * Free set of legal documents - * - * @param legal legal documents to free - */ void TALER_MHD_legal_free (struct TALER_MHD_Legal *legal) { + struct Terms *t; if (NULL == legal) return; - for (unsigned int i = 0; i<legal->terms_len; i++) + while (NULL != (t = legal->terms_head)) { - struct Terms *t = &legal->terms[i]; - GNUNET_free (t->language); - GNUNET_free (t->terms); + GNUNET_free (t->compressed_terms); + if (0 != munmap (t->terms, t->terms_size)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "munmap"); + GNUNET_CONTAINER_DLL_remove (legal->terms_head, + legal->terms_tail, + t); + GNUNET_free (t); } - GNUNET_array_grow (legal->terms, - legal->terms_len, - 0); GNUNET_free (legal->terms_etag); GNUNET_free (legal); } diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index fca54f3ff..771319bd5 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014--2019 Taler Systems SA + Copyright (C) 2014--2024 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 @@ -27,36 +27,7 @@ #include "taler_mhd_lib.h" -/** - * Maximum POST request size. - */ -#define REQUEST_BUFFER_MAX (1024 * 1024) - - -/** - * Process a POST request containing a JSON object. This function - * realizes an MHD POST processor that will (incrementally) process - * JSON data uploaded to the HTTP server. It will store the required - * state in the @a con_cls, which must be cleaned up using - * #TALER_MHD_parse_post_cleanup_callback(). - * - * @param connection the MHD connection - * @param con_cls the closure (points to a `struct Buffer *`) - * @param upload_data the POST data - * @param upload_data_size number of bytes in @a upload_data - * @param json the JSON object for a completed request - * @return - * #GNUNET_YES if json object was parsed or at least - * may be parsed in the future (call again); - * `*json` will be NULL if we need to be called again, - * and non-NULL if we are done. - * #GNUNET_NO is request incomplete or invalid - * (error message was generated) - * #GNUNET_SYSERR on internal error - * (we could not even queue an error message, - * close HTTP session with MHD_NO) - */ -int +enum GNUNET_GenericReturnValue TALER_MHD_parse_post_json (struct MHD_Connection *connection, void **con_cls, const char *upload_data, @@ -65,7 +36,7 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, { enum GNUNET_JSON_PostResult pr; - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + pr = GNUNET_JSON_post_parser (TALER_MHD_REQUEST_BUFFER_MAX, connection, con_cls, upload_data, @@ -76,27 +47,27 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, case GNUNET_JSON_PR_OUT_OF_MEMORY: GNUNET_break (NULL == *json); return (MHD_NO == - TALER_MHD_reply_with_error - (connection, + TALER_MHD_reply_with_error ( + connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_PARSER_OUT_OF_MEMORY, - "out of memory")) ? GNUNET_SYSERR : GNUNET_NO; + TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY, + NULL)) ? GNUNET_SYSERR : GNUNET_NO; case GNUNET_JSON_PR_CONTINUE: GNUNET_break (NULL == *json); return GNUNET_YES; case GNUNET_JSON_PR_REQUEST_TOO_LARGE: GNUNET_break (NULL == *json); - return (MHD_NO == - TALER_MHD_reply_request_too_large - (connection)) ? GNUNET_SYSERR : GNUNET_NO; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Closing connection, upload too large\n"); + return GNUNET_SYSERR; case GNUNET_JSON_PR_JSON_INVALID: GNUNET_break (NULL == *json); return (MHD_YES == TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_JSON_INVALID, - "invalid JSON uploaded")) + TALER_EC_GENERIC_JSON_INVALID, + NULL)) ? GNUNET_NO : GNUNET_SYSERR; case GNUNET_JSON_PR_SUCCESS: GNUNET_break (NULL != *json); @@ -108,13 +79,6 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, } -/** - * Function called whenever we are done with a request - * to clean up our state. - * - * @param con_cls value as it was left by - * #TALER_MHD_parse_post_json(), to be cleaned up - */ void TALER_MHD_parse_post_cleanup_callback (void *con_cls) { @@ -123,39 +87,39 @@ TALER_MHD_parse_post_cleanup_callback (void *con_cls) /** - * Extract base32crockford encoded data from request. + * Extract fixed-size base32crockford encoded data from request. * - * Queues an error response to the connection if the parameter is - * missing or invalid. + * Queues an error response to the connection if the parameter is missing or + * invalid. * * @param connection the MHD connection - * @param param_name the name of the parameter with the key + * @param param_name the name of the HTTP key with the value + * @param kind whether to extract from header, argument or footer * @param[out] out_data pointer to store the result - * @param out_size expected size of data + * @param out_size expected size of @a out_data + * @param[out] present set to true if argument was found * @return * #GNUNET_YES if the the argument is present * #GNUNET_NO if the argument is absent or malformed * #GNUNET_SYSERR on internal error (error response could not be sent) */ -int -TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, - const char *param_name, - void *out_data, - size_t out_size) +static enum GNUNET_GenericReturnValue +parse_request_data (struct MHD_Connection *connection, + const char *param_name, + enum MHD_ValueKind kind, + void *out_data, + size_t out_size, + bool *present) { const char *str; str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, + kind, param_name); if (NULL == str) { - return (MHD_NO == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MISSING, - param_name)) - ? GNUNET_SYSERR : GNUNET_NO; + *present = false; + return GNUNET_OK; } if (GNUNET_OK != GNUNET_STRINGS_string_to_data (str, @@ -165,33 +129,195 @@ TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, return (MHD_NO == TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MALFORMED, + TALER_EC_GENERIC_PARAMETER_MALFORMED, param_name)) ? GNUNET_SYSERR : GNUNET_NO; + *present = true; return GNUNET_OK; } -/** - * Parse JSON object into components based on the given field - * specification. Generates error response on parse errors. - * - * @param connection the connection to send an error response to - * @param root the JSON node to start the navigation at. - * @param[in,out] spec field specification for the parser - * @return - * #GNUNET_YES if navigation was successful (caller is responsible - * for freeing allocated variable-size data using - * GNUNET_JSON_parse_free() when done) - * #GNUNET_NO if json is malformed, error response was generated - * #GNUNET_SYSERR on internal error - */ -int +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size, + bool *present) +{ + return parse_request_data (connection, + param_name, + MHD_GET_ARGUMENT_KIND, + out_data, + out_size, + present); +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, + const char *header_name, + void *out_data, + size_t out_size, + bool *present) +{ + return parse_request_data (connection, + header_name, + MHD_HEADER_KIND, + out_data, + out_size, + present); +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection, + struct GNUNET_TIME_Absolute *expiration) +{ + const char *ts; + char dummy; + unsigned long long tms; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "timeout_ms"); + if (NULL == ts) + { + *expiration = GNUNET_TIME_UNIT_ZERO_ABS; + return GNUNET_OK; + } + if (1 != + sscanf (ts, + "%llu%c", + &tms, + &dummy)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "timeout_ms"); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + *expiration = GNUNET_TIME_relative_to_absolute ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + tms)); + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection, + const char *name, + uint64_t *off) +{ + const char *ts; + char dummy; + unsigned long long num; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + name); + if (NULL == ts) + return GNUNET_OK; + if (1 != + sscanf (ts, + "%llu%c", + &num, + &dummy)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + name); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + *off = (uint64_t) num; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_snumber (struct MHD_Connection *connection, + const char *name, + int64_t *val) +{ + const char *ts; + char dummy; + long long num; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + name); + if (NULL == ts) + return GNUNET_OK; + if (1 != + sscanf (ts, + "%lld%c", + &num, + &dummy)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + name); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + *val = (int64_t) num; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_amount (struct MHD_Connection *connection, + const char *name, + struct TALER_Amount *val) +{ + const char *ts; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + name); + if (NULL == ts) + return GNUNET_OK; + if (GNUNET_OK != + TALER_string_to_amount (ts, + val)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + name); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue TALER_MHD_parse_json_data (struct MHD_Connection *connection, const json_t *root, struct GNUNET_JSON_Specification *spec) { - int ret; + enum GNUNET_GenericReturnValue ret; const char *error_json_name; unsigned int error_line; @@ -204,15 +330,18 @@ TALER_MHD_parse_json_data (struct MHD_Connection *connection, if (NULL == error_json_name) error_json_name = "<no field>"; ret = (MHD_YES == - TALER_MHD_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:I, s:s, s:I}", - "hint", "JSON parse error", - "code", - (json_int_t) - TALER_EC_JSON_INVALID_WITH_DETAILS, - "field", error_json_name, - "line", (json_int_t) error_line)) + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_JSON_INVALID)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_JSON_INVALID), + GNUNET_JSON_pack_string ("field", + error_json_name), + GNUNET_JSON_pack_uint64 ("line", + error_line))) ? GNUNET_NO : GNUNET_SYSERR; return ret; } @@ -220,28 +349,50 @@ TALER_MHD_parse_json_data (struct MHD_Connection *connection, } -/** - * Parse JSON array into components based on the given field - * specification. Generates error response on parse errors. - * - * @param connection the connection to send an error response to - * @param root the JSON node to start the navigation at. - * @param[in,out] spec field specification for the parser - * @param ... -1-terminated list of array offsets of type 'int' - * @return - * #GNUNET_YES if navigation was successful (caller is responsible - * for freeing allocated variable-size data using - * GNUNET_JSON_parse_free() when done) - * #GNUNET_NO if json is malformed, error response was generated - * #GNUNET_SYSERR on internal error - */ -int +enum GNUNET_GenericReturnValue +TALER_MHD_parse_internal_json_data (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + enum GNUNET_GenericReturnValue ret; + const char *error_json_name; + unsigned int error_line; + + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + ret = (MHD_YES == + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE), + GNUNET_JSON_pack_string ("field", + error_json_name), + GNUNET_JSON_pack_uint64 ("line", + error_line))) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + +enum GNUNET_GenericReturnValue TALER_MHD_parse_json_array (struct MHD_Connection *connection, const json_t *root, struct GNUNET_JSON_Specification *spec, ...) { - int ret; + enum GNUNET_GenericReturnValue ret; const char *error_json_name; unsigned int error_line; va_list ap; @@ -259,14 +410,18 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection, if (NULL == root) { ret = (MHD_YES == - TALER_MHD_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:I, s:I}", - "hint", "expected array", - "code", - (json_int_t) - TALER_EC_JSON_INVALID_WITH_DETAILS, - "dimension", dim)) + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_JSON_INVALID)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_JSON_INVALID), + GNUNET_JSON_pack_string ("detail", + "expected array"), + GNUNET_JSON_pack_uint64 ("dimension", + dim))) ? GNUNET_NO : GNUNET_SYSERR; return ret; } @@ -279,14 +434,18 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection, if (NULL == error_json_name) error_json_name = "<no field>"; ret = (MHD_YES == - TALER_MHD_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:I, s:I}", - "hint", error_json_name, - "code", - (json_int_t) - TALER_EC_JSON_INVALID_WITH_DETAILS, - "line", (json_int_t) error_line)) + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + GNUNET_JSON_pack_string ("detail", + error_json_name), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_JSON_INVALID)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_JSON_INVALID), + GNUNET_JSON_pack_uint64 ("line", + error_line))) ? GNUNET_NO : GNUNET_SYSERR; return ret; } @@ -294,4 +453,121 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection, } +enum GNUNET_GenericReturnValue +TALER_MHD_check_content_length_ (struct MHD_Connection *connection, + unsigned long long max_len) +{ + const char *cl; + unsigned long long cv; + char dummy; + + /* Maybe check for maximum upload size + and refuse requests if they are just too big. */ + cl = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_LENGTH); + if (NULL == cl) + { + return GNUNET_OK; +#if 0 + /* wallet currently doesn't always send content-length! */ + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + MHD_HTTP_HEADER_CONTENT_LENGTH)) + ? GNUNET_NO + : GNUNET_SYSERR; +#endif + } + if (1 != sscanf (cl, + "%llu%c", + &cv, + &dummy)) + { + /* Not valid HTTP request, just close connection. */ + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + MHD_HTTP_HEADER_CONTENT_LENGTH)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if (cv > TALER_MHD_REQUEST_BUFFER_MAX) + { + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_request_too_large (connection)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +int +TALER_MHD_check_accept (struct MHD_Connection *connection, + const char *header, + ...) +{ + bool ret = false; + const char *accept; + char *a; + char *saveptr; + + accept = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + header); + if (NULL == accept) + return -2; /* no Accept header set */ + + a = GNUNET_strdup (accept); + for (char *t = strtok_r (a, ",", &saveptr); + NULL != t; + t = strtok_r (NULL, ",", &saveptr)) + { + char *end; + + /* skip leading whitespace */ + while (isspace ((unsigned char) t[0])) + t++; + /* trim of ';q=' parameter and everything after space */ + /* FIXME: eventually, we might want to parse the "q=$FLOAT" + part after the ';' and figure out which one is the + best/preferred match instead of returning a boolean... */ + end = strchr (t, ';'); + if (NULL != end) + *end = '\0'; + end = strchr (t, ' '); + if (NULL != end) + *end = '\0'; + { + va_list ap; + int off = 0; + const char *val; + + va_start (ap, + header); + while (NULL != (val = va_arg (ap, + const char *))) + { + if (0 == strcasecmp (val, + t)) + { + ret = off; + break; + } + off++; + } + va_end (ap); + } + } + GNUNET_free (a); + return ret; +} + + /* end of mhd_parsing.c */ diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c index 45a9932d3..7dd6824e2 100644 --- a/src/mhd/mhd_responses.c +++ b/src/mhd/mhd_responses.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2021 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 @@ -32,12 +32,6 @@ static enum TALER_MHD_GlobalOptions TM_go; -/** - * Set global options for response generation - * within libtalermhd. - * - * @param go global options to use - */ void TALER_MHD_setup (enum TALER_MHD_GlobalOptions go) { @@ -45,13 +39,6 @@ TALER_MHD_setup (enum TALER_MHD_GlobalOptions go) } -/** - * Add headers we want to return in every response. - * Useful for testing, like if we want to always close - * connections. - * - * @param response response to modify - */ void TALER_MHD_add_global_headers (struct MHD_Response *response) { @@ -66,20 +53,14 @@ TALER_MHD_add_global_headers (struct MHD_Response *response) MHD_add_response_header (response, MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*")); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Expose-Headers", + "*")); } -/** - * Is HTTP body deflate compression supported by the client? - * - * @param connection connection to check - * @return #MHD_YES if 'deflate' compression is allowed - * - * Note that right now we're ignoring q-values, which is technically - * not correct, and also do not support "*" anywhere but in a line by - * itself. This should eventually be fixed, see also - * https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - */ MHD_RESULT TALER_MHD_can_compress (struct MHD_Connection *connection) { @@ -111,13 +92,6 @@ TALER_MHD_can_compress (struct MHD_Connection *connection) } -/** - * Try to compress a response body. Updates @a buf and @a buf_size. - * - * @param[in,out] buf pointer to body to compress - * @param[in,out] buf_size pointer to initial size of @a buf - * @return #MHD_YES if @a buf was compressed - */ MHD_RESULT TALER_MHD_body_compress (void **buf, size_t *buf_size) @@ -148,12 +122,6 @@ TALER_MHD_body_compress (void **buf, } -/** - * Make JSON response object. - * - * @param json the json object - * @return MHD response object - */ struct MHD_Response * TALER_MHD_make_json (const json_t *json) { @@ -185,14 +153,17 @@ TALER_MHD_make_json (const json_t *json) } -/** - * Send JSON object as response. - * - * @param connection the MHD connection - * @param json the json object - * @param response_code the http response code - * @return MHD result code - */ +struct MHD_Response * +TALER_MHD_make_json_steal (json_t *json) +{ + struct MHD_Response *res; + + res = TALER_MHD_make_json (json); + json_decref (json); + return res; +} + + MHD_RESULT TALER_MHD_reply_json (struct MHD_Connection *connection, const json_t *json, @@ -264,13 +235,21 @@ TALER_MHD_reply_json (struct MHD_Connection *connection, } -/** - * Send back a "204 No Content" response with headers - * for the CORS pre-flight request. - * - * @param connection the MHD connection - * @return MHD result code - */ +MHD_RESULT +TALER_MHD_reply_json_steal (struct MHD_Connection *connection, + json_t *json, + unsigned int response_code) +{ + MHD_RESULT ret; + + ret = TALER_MHD_reply_json (connection, + json, + response_code); + json_decref (json); + return ret; +} + + MHD_RESULT TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection) { @@ -289,7 +268,11 @@ TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection) /* Not available as MHD constant yet */ "Access-Control-Allow-Headers", "*")); - + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Allow-Methods", + "*")); { MHD_RESULT ret; @@ -302,16 +285,6 @@ TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection) } -/** - * Function to call to handle the request by building a JSON - * reply from a format string and varargs. - * - * @param connection the MHD connection to handle - * @param response_code HTTP response code to use - * @param fmt format string for pack - * @param ... varargs - * @return MHD result code - */ MHD_RESULT TALER_MHD_reply_json_pack (struct MHD_Connection *connection, unsigned int response_code, @@ -355,13 +328,6 @@ TALER_MHD_reply_json_pack (struct MHD_Connection *connection, } -/** - * Make JSON response object. - * - * @param fmt format string for pack - * @param ... varargs - * @return MHD response object - */ struct MHD_Response * TALER_MHD_make_json_pack (const char *fmt, ...) @@ -387,7 +353,7 @@ TALER_MHD_make_json_pack (const char *fmt, fmt, jerror.text); GNUNET_break (0); - return MHD_NO; + return NULL; } { @@ -400,84 +366,64 @@ TALER_MHD_make_json_pack (const char *fmt, } -/** - * Create a response indicating an internal error. - * - * @param ec error code to return - * @param hint hint about the internal error's nature - * @return a MHD response object - */ struct MHD_Response * TALER_MHD_make_error (enum TALER_ErrorCode ec, - const char *hint) + const char *detail) { - return TALER_MHD_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) ec, - "hint", hint); + return TALER_MHD_MAKE_JSON_PACK ( + TALER_MHD_PACK_EC (ec), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", detail))); } -/** - * Send a response indicating an error. - * - * @param connection the MHD connection to use - * @param ec error code uniquely identifying the error - * @param http_status HTTP status code to use - * @param hint human readable hint about the error - * @return a MHD result code - */ MHD_RESULT TALER_MHD_reply_with_error (struct MHD_Connection *connection, unsigned int http_status, enum TALER_ErrorCode ec, - const char *hint) + const char *detail) { - return TALER_MHD_reply_json_pack (connection, - http_status, - "{s:I, s:s}", - "code", (json_int_t) ec, - "hint", hint); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + http_status, + TALER_MHD_PACK_EC (ec), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", detail))); } -/** - * Send a response indicating that the request was too big. - * - * @param connection the MHD connection to use - * @return a MHD result code - */ MHD_RESULT -TALER_MHD_reply_request_too_large (struct MHD_Connection *connection) +TALER_MHD_reply_with_ec (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *detail) { - struct MHD_Response *response; - - response = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - if (NULL == response) - return MHD_NO; - TALER_MHD_add_global_headers (response); + unsigned int hc = TALER_ErrorCode_get_http_status (ec); + if ( (0 == hc) || + (UINT_MAX == hc) ) { - MHD_RESULT ret; - - ret = MHD_queue_response (connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, - response); - MHD_destroy_response (response); - return ret; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid Taler error code %d provided for response!\n", + (int) ec); + hc = MHD_HTTP_INTERNAL_SERVER_ERROR; } + return TALER_MHD_reply_with_error (connection, + hc, + ec, + detail); +} + + +MHD_RESULT +TALER_MHD_reply_request_too_large (struct MHD_Connection *connection) +{ + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT, + NULL); } -/** - * Function to call to handle the request by sending - * back a redirect to the AGPL source code. - * - * @param connection the MHD connection to handle - * @param url where to redirect for the sources - * @return MHD result code - */ MHD_RESULT TALER_MHD_reply_agpl (struct MHD_Connection *connection, const char *url) @@ -521,17 +467,6 @@ TALER_MHD_reply_agpl (struct MHD_Connection *connection, } -/** - * Function to call to handle the request by sending - * back static data. - * - * @param connection the MHD connection to handle - * @param http_status status code to return - * @param mime_type content-type to use - * @param body response payload - * @param body_size number of bytes in @a body - * @return MHD result code - */ MHD_RESULT TALER_MHD_reply_static (struct MHD_Connection *connection, unsigned int http_status, @@ -567,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 */ diff --git a/src/mhd/mhd_run.c b/src/mhd/mhd_run.c new file mode 100644 index 000000000..8388fbff6 --- /dev/null +++ b/src/mhd/mhd_run.c @@ -0,0 +1,175 @@ +/* + This file is part of TALER + Copyright (C) 2019-2021 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_run.c + * @brief API for running an MHD daemon with the + * GNUnet scheduler + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include "taler_util.h" +#include "taler_mhd_lib.h" + + +/** + * Set to true if we should immediately MHD_run() again. + */ +static bool triggered; + +/** + * Task running the HTTP server. + */ +static struct GNUNET_SCHEDULER_Task *mhd_task; + +/** + * The MHD daemon we are running. + */ +static struct MHD_Daemon *mhd; + + +/** + * Function that queries MHD's select sets and + * starts the task waiting for them. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void); + + +/** + * Call MHD to process pending requests and then go back + * and schedule the next run. + * + * @param cls NULL + */ +static void +run_daemon (void *cls) +{ + (void) cls; + mhd_task = NULL; + do { + triggered = false; + GNUNET_assert (MHD_YES == + MHD_run (mhd)); + } while (triggered); + mhd_task = prepare_daemon (); +} + + +/** + * Function that queries MHD's select sets and starts the task waiting for + * them. + * + * @return task handle for the MHD task. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void) +{ + struct GNUNET_SCHEDULER_Task *ret; + fd_set rs; + fd_set ws; + fd_set es; + struct GNUNET_NETWORK_FDSet *wrs; + struct GNUNET_NETWORK_FDSet *wws; + int max; + MHD_UNSIGNED_LONG_LONG timeout; + int haveto; + struct GNUNET_TIME_Relative tv; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + wrs = GNUNET_NETWORK_fdset_create (); + wws = GNUNET_NETWORK_fdset_create (); + max = -1; + GNUNET_assert (MHD_YES == + MHD_get_fdset (mhd, + &rs, + &ws, + &es, + &max)); + haveto = MHD_get_timeout (mhd, + &timeout); + if (haveto == MHD_YES) + tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout); + else + tv = GNUNET_TIME_UNIT_FOREVER_REL; + GNUNET_NETWORK_fdset_copy_native (wrs, + &rs, + max + 1); + GNUNET_NETWORK_fdset_copy_native (wws, + &ws, + max + 1); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding run_daemon select task\n"); + ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH, + tv, + wrs, + wws, + &run_daemon, + NULL); + GNUNET_NETWORK_fdset_destroy (wrs); + GNUNET_NETWORK_fdset_destroy (wws); + return ret; +} + + +void +TALER_MHD_daemon_start (struct MHD_Daemon *daemon) +{ + GNUNET_assert (NULL == mhd); + mhd = daemon; + mhd_task = prepare_daemon (); +} + + +struct MHD_Daemon * +TALER_MHD_daemon_stop (void) +{ + struct MHD_Daemon *ret; + + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = NULL; + } + ret = mhd; + mhd = NULL; + return ret; +} + + +void +TALER_MHD_daemon_trigger (void) +{ + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon, + NULL); + } + else + { + triggered = true; + } +} + + +/* end of mhd_run.c */ |