add logic for privacy policy

This commit is contained in:
Christian Grothoff 2019-12-11 14:30:55 +01:00
parent 75240345d0
commit 631bc65253
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
7 changed files with 81 additions and 473 deletions

View File

@ -67,3 +67,18 @@ LOOKAHEAD_SIGN = 32 weeks 1 day
# how long do we provide to clients denomination and signing keys
# ahead of time?
LOOKAHEAD_PROVIDE = 4 weeks 1 day
# Directory with our terms of service.
# TERMS_DIR =
# Etag / filename for the terms of service.
# TERMS_ETAG =
# Directory with our privacy policy.
# PRIVACY_DIR =
# Etag / filename for the privacy policy.
# PRIVACY_ETAG =

View File

@ -256,6 +256,10 @@ handle_mhd_request (void *cls,
{ "/terms", MHD_HTTP_METHOD_GET, NULL,
NULL, 0,
&TEH_handler_terms, MHD_HTTP_OK },
/* Privacy policy */
{ "/privacy", MHD_HTTP_METHOD_GET, NULL,
NULL, 0,
&TEH_handler_privacy, MHD_HTTP_OK },
/* Return key material and fundamental properties for this exchange */
{ "/keys", MHD_HTTP_METHOD_GET, "application/json",
NULL, 0,

View File

@ -26,118 +26,16 @@
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_responses.h"
/**
* Entry in the terms-of-service array.
* Our terms of service.
*/
struct Terms
{
/**
* Mime type of the terms.
*/
const char *mime_type;
/**
* The terms (NOT 0-terminated!).
*/
const void *terms;
/**
* The desired language.
*/
char *language;
/**
* Number of bytes in @e terms.
*/
size_t terms_size;
};
static struct TALER_MHD_Legal *tos;
/**
* Array of terms of service, terminated by NULL/0 value.
* Our privacy policy.
*/
static struct Terms *terms;
/**
* Length of the #terms array.
*/
static unsigned int terms_len;
/**
* Etag to use for the terms of service (= version).
*/
static char *terms_etag;
/**
* Check if @a mime matches the @a accept_pattern.
*
* @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
*/
static bool
mime_matches (const char *accept_pattern,
const char *mime)
{
const char *da = strchr (accept_pattern, '/');
const char *dm = strchr (mime, '/');
if ( (NULL == da) ||
(NULL == dm) )
return (0 == strcmp ("*", accept_pattern));
return
( ( (1 == da - accept_pattern) &&
('*' == *accept_pattern) ) ||
( (da - accept_pattern == dm - mime) &&
(0 == strncasecmp (accept_pattern,
mime,
da - accept_pattern)) ) ) &&
( (0 == strcmp (da, "/*")) ||
(0 == strcasecmp (da,
dm)) );
}
/**
* 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)
{
char *p = GNUNET_strdup (language_pattern);
char *sptr;
double r = 0.0;
for (char *tok = strtok_r (p, ", ", &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;
GNUNET_break_op ( (NULL == qp) ||
(1 == sscanf (qp,
"q=%lf",
&q)) );
if (0 == strcasecmp (lang,
lp))
r = GNUNET_MAX (r, q);
}
GNUNET_free (p);
return r;
}
static struct TALER_MHD_Legal *pp;
/**
@ -157,340 +55,38 @@ TEH_handler_terms (struct TEH_RequestHandler *rh,
const char *upload_data,
size_t *upload_data_size)
{
struct MHD_Response *resp;
struct Terms *t;
(void) rh;
(void) upload_data;
(void) upload_data_size;
(void) connection_cls;
{
const char *etag;
etag = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
if ( (NULL != etag) &&
(NULL != terms_etag) &&
(0 == strcasecmp (etag,
terms_etag)) )
{
int ret;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
ret = MHD_queue_response (connection,
MHD_HTTP_NOT_MODIFIED,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
}
t = NULL;
{
const char *mime;
const char *lang;
mime = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT);
if (NULL == mime)
mime = "text/html";
lang = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
if (NULL == lang)
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 < terms_len; i++)
{
struct Terms *p = &terms[i];
if ( (NULL == t) ||
(mime_matches (mime,
p->mime_type)) )
{
if ( (NULL == t) ||
(! mime_matches (mime,
t->mime_type)) ||
(language_matches (lang,
p->language) >
language_matches (lang,
t->language) ) )
t = p;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Best match for %s/%s: %s / %s\n",
lang,
mime,
(NULL != t) ? t->mime_type : "<none>",
(NULL != t) ? t->language : "<none>");
}
if (NULL == t)
{
/* Default terms of service if none are configured */
static struct Terms none = {
.mime_type = "text/plain",
.terms = "Terms of service not configured",
.language = "en",
.terms_size = strlen ("Terms of service not configured")
};
t = &none;
}
/* try to compress the response */
resp = NULL;
if (MHD_YES ==
TALER_MHD_can_compress (connection))
{
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
{
GNUNET_free (buf);
}
}
if (NULL == resp)
{
/* could not generate compressed response, return uncompressed */
resp = MHD_create_response_from_buffer (t->terms_size,
(void *) t->terms,
MHD_RESPMEM_PERSISTENT);
}
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_ETAG,
terms_etag));
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_CONTENT_TYPE,
t->mime_type));
{
int ret;
ret = MHD_queue_response (connection,
MHD_HTTP_OK,
resp);
MHD_destroy_response (resp);
return ret;
}
return TALER_MHD_reply_legal (connection,
tos);
}
/**
* Load all the terms of service from @a path under language @a lang
* from file @a name
* Handle a "/privacy" request.
*
* @param path where the terms are found
* @param lang which language directory to crawl
* @param name specific file to access
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
static void
load_terms (const char *path,
const char *lang,
const char *name)
int
TEH_handler_privacy (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
static struct MimeMap
{
const char *ext;
const char *mime;
} mm[] = {
{ .ext = ".html", .mime = "text/html" },
{ .ext = ".htm", .mime = "text/html" },
{ .ext = ".txt", .mime = "text/plain" },
{ .ext = ".pdf", .mime = "application/pdf" },
{ .ext = ".jpg", .mime = "image/jpeg" },
{ .ext = ".jpeg", .mime = "image/jpeg" },
{ .ext = ".png", .mime = "image/png" },
{ .ext = ".gif", .mime = "image/gif" },
{ .ext = NULL, .mime = NULL }
};
const char *ext = strrchr (name, '.');
const char *mime;
if (NULL == ext)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unsupported file `%s' in directory `%s/%s': lacks extension\n",
name,
path,
lang);
return;
}
if ( (NULL == terms_etag) ||
(0 != strncmp (terms_etag,
name,
ext - name - 1)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Filename `%s' does not match Etag `%s' in directory `%s/%s'. Ignoring it.\n",
name,
terms_etag,
path,
lang);
return;
}
mime = NULL;
for (unsigned int i = 0; NULL != mm[i].ext; i++)
if (0 == strcasecmp (mm[i].ext,
ext))
{
mime = mm[i].mime;
break;
}
if (NULL == mime)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unsupported file extension `%s' of file `%s' in directory `%s/%s'\n",
ext,
name,
path,
lang);
return;
}
/* try to read the file with the terms of service */
{
struct stat st;
char *fn;
int fd;
GNUNET_asprintf (&fn,
"%s/%s/%s",
path,
lang,
name);
fd = open (fn, O_RDONLY);
if (-1 == fd)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"open",
fn);
GNUNET_free (fn);
return;
}
GNUNET_free (fn);
if (0 != fstat (fd, &st))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"fstat",
fn);
(void) close (fd);
GNUNET_free (fn);
return;
}
if (SIZE_MAX < st.st_size)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"fstat-size",
fn);
(void) close (fd);
GNUNET_free (fn);
return;
}
{
char *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");
(void) close (fd);
GNUNET_free (fn);
return;
}
ret = read (fd,
buf,
bsize);
if ( (ret < 0) ||
(bsize != ((size_t) ret)) )
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"read",
fn);
(void) close (fd);
GNUNET_free (buf);
GNUNET_free (fn);
return;
}
(void) close (fd);
GNUNET_free (fn);
/* append to global list of terms of service */
{
struct Terms t = {
.mime_type = mime,
.terms = buf,
.language = GNUNET_strdup (lang),
.terms_size = bsize
};
GNUNET_array_append (terms,
terms_len,
t);
}
}
}
}
/**
* Load all the terms of service from @a path under language @a lang.
*
* @param path where the terms are found
* @param lang which language directory to crawl
*/
static void
load_language (const char *path,
const char *lang)
{
char *dname;
DIR *d;
GNUNET_asprintf (&dname,
"%s/%s",
path,
lang);
d = opendir (dname);
for (struct dirent *de = readdir (d);
NULL != de;
de = readdir (d))
{
const char *fn = de->d_name;
if (fn[0] == '.')
continue;
load_terms (path, lang, fn);
}
closedir (d);
free (dname);
(void) rh;
(void) upload_data;
(void) upload_data_size;
(void) connection_cls;
return TALER_MHD_reply_legal (connection,
pp);
}
@ -502,45 +98,20 @@ load_language (const char *path,
void
TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
char *path;
DIR *d;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"exchange",
"TERMS_ETAG",
&terms_etag))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
"exchange",
"TERMS_ETAG");
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
tos = TALER_MHD_legal_load (cfg,
"exchange",
"TERMS_DIR",
&path))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
"TERMS_ETAG");
if (NULL == tos)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Terms of service not configured\n");
pp = TALER_MHD_legal_load (cfg,
"exchange",
"TERMS_DIR");
return;
}
d = opendir (path);
for (struct dirent *de = readdir (d);
NULL != de;
de = readdir (d))
{
const char *lang = de->d_name;
if (lang[0] == '.')
continue;
load_language (path, lang);
}
closedir (d);
free (path);
"PRIVACY_DIR",
"PRIVACY_ETAG");
if (NULL == pp)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Privacy policy not configured\n");
}

View File

@ -46,6 +46,24 @@ TEH_handler_terms (struct TEH_RequestHandler *rh,
const char *upload_data,
size_t *upload_data_size);
/**
* Handle a "/privacy" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_handler_privacy (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
/**
* Load our terms of service as per configuration.
*