summaryrefslogtreecommitdiff
path: root/src/mhd/mhd_legal.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mhd/mhd_legal.c')
-rw-r--r--src/mhd/mhd_legal.c396
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);
}