diff options
Diffstat (limited to 'src/mhd/mhd_legal.c')
-rw-r--r-- | src/mhd/mhd_legal.c | 396 |
1 files changed, 247 insertions, 149 deletions
diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c index 7b1ba1c11..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,66 +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. - * - * @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)) + tok = strtok_r (NULL, ",", &sptr)) { - char *sptr2; - char *lp = strtok_r (tok, ";", &sptr2); - char *qp = strtok_r (NULL, ";", &sptr2); - double q = 1.0; - - 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 - */ -int +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; @@ -174,11 +212,21 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, (0 == strcasecmp (etag, legal->terms_etag)) ) { - int ret; + MHD_RESULT ret; resp = MHD_create_response_from_buffer (0, 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); @@ -189,6 +237,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, } t = NULL; + langs = NULL; if (NULL != legal) { const char *mime; @@ -198,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); @@ -206,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; } } @@ -250,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) @@ -282,6 +334,31 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, (void *) t->terms, 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, @@ -291,8 +368,12 @@ 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)); { - int ret; + MHD_RESULT ret; ret = MHD_queue_response (conn, MHD_HTTP_OK, @@ -322,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) { @@ -352,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, @@ -366,6 +450,7 @@ load_terms (struct TALER_MHD_Legal *legal, ext)) { mime = mm[i].mime; + priority = mm[i].priority; break; } if (NULL == mime) @@ -416,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); + } } } } @@ -499,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, @@ -575,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); @@ -583,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); } |