summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-08-19 19:27:28 +0200
committerChristian Grothoff <christian@grothoff.org>2020-08-19 19:27:28 +0200
commitd6f28270094db1ffa4e5526ce556e7fc23e8e9b9 (patch)
treec4c7d3e7bf633535fd6db3241917caafc1533cfb
parentc4ffdfa3f2c736e4324f11df7a35340f0e64e9a9 (diff)
downloadmerchant-d6f28270094db1ffa4e5526ce556e7fc23e8e9b9.tar.gz
merchant-d6f28270094db1ffa4e5526ce556e7fc23e8e9b9.tar.bz2
merchant-d6f28270094db1ffa4e5526ce556e7fc23e8e9b9.zip
implement #6460: serving of static files
-rw-r--r--contrib/Makefile.am9
-rw-r--r--contrib/depleted_tip.en.must3
-rw-r--r--contrib/offer_refund.en.must3
-rw-r--r--contrib/offer_tip.en.must3
-rw-r--r--contrib/pure-min.css11
-rw-r--r--contrib/request_payment.en.must3
-rw-r--r--contrib/show_order_details.en.must3
-rw-r--r--src/backend/Makefile.am2
-rw-r--r--src/backend/taler-merchant-httpd.c9
-rw-r--r--src/backend/taler-merchant-httpd_statics.c336
-rw-r--r--src/backend/taler-merchant-httpd_statics.h49
-rw-r--r--src/backend/taler-merchant-httpd_templating.c2
12 files changed, 420 insertions, 13 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index c362ddd5..14826f9e 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -1,8 +1,13 @@
-pkgdatadir = $(prefix)/share/taler/merchant/templates/
+tmplpkgdatadir = $(prefix)/share/taler/merchant/templates/
+staticpkgdatadir = $(prefix)/share/taler/merchant/static/
-dist_pkgdata_DATA = \
+dist_tmplpkgdata_DATA = \
depleted_tip.en.must \
offer_refund.en.must \
offer_tip.en.must \
request_payment.en.must \
show_order_details.en.must
+
+# Note: if you update pure-min.css, you must also update the MUST templates above!
+dist_staticpkgdata_DATA = \
+ pure-min.css
diff --git a/contrib/depleted_tip.en.must b/contrib/depleted_tip.en.must
index 694bb726..795f4f17 100644
--- a/contrib/depleted_tip.en.must
+++ b/contrib/depleted_tip.en.must
@@ -23,9 +23,8 @@
<meta http-equiv="refresh" content="1">
</noscript>
<title>Status of your tip</title>
- <!-- FIXME: inline this? How to best serve this without using 3rd party? -->
<link rel="stylesheet"
- href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ href="/static/pure-min.css"
integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
crossorigin="anonymous">
<style>
diff --git a/contrib/offer_refund.en.must b/contrib/offer_refund.en.must
index 62766ad7..c6cc9aec 100644
--- a/contrib/offer_refund.en.must
+++ b/contrib/offer_refund.en.must
@@ -23,9 +23,8 @@
<meta http-equiv="refresh" content="1">
</noscript>
<title>Refund available for {{order_summary}}</title>
- <!-- FIXME-6460: allow taler-merchant-httpd to serve this, so we do not use 3rd party? -->
<link rel="stylesheet"
- href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ href="/static/pure-min.css"
integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
crossorigin="anonymous">
<style>
diff --git a/contrib/offer_tip.en.must b/contrib/offer_tip.en.must
index abc71690..b44f30c7 100644
--- a/contrib/offer_tip.en.must
+++ b/contrib/offer_tip.en.must
@@ -23,9 +23,8 @@
<meta http-equiv="refresh" content="1">
</noscript>
<title>Tip available</title>
- <!-- FIXME-6460: allow taler-merchant-httpd to serve this, so we do not use 3rd party? -->
<link rel="stylesheet"
- href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ href="/static/pure-min.css"
integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
crossorigin="anonymous">
<style>
diff --git a/contrib/pure-min.css b/contrib/pure-min.css
new file mode 100644
index 00000000..7b6e7b90
--- /dev/null
+++ b/contrib/pure-min.css
@@ -0,0 +1,11 @@
+/*!
+Pure v2.0.3
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/pure-css/pure/blob/master/LICENSE.md
+*/
+/*!
+normalize.css v | MIT License | git.io/normalize
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-line-pack:start;align-content:flex-start}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:rgba(0,0,0,.8);border:none transparent;background-color:#e6e6e6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{background-image:-webkit-gradient(linear,left top,left bottom,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{-webkit-box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;opacity:.4;cursor:not-allowed;-webkit-box-shadow:none;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{margin:0;border-radius:0;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 3px #ddd;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 3px #ddd;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input:not([type]):focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129fea;outline:1px auto #129fea}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0 0}.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;padding:.5em 0}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-disabled,.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file
diff --git a/contrib/request_payment.en.must b/contrib/request_payment.en.must
index d7819059..1d666fef 100644
--- a/contrib/request_payment.en.must
+++ b/contrib/request_payment.en.must
@@ -23,9 +23,8 @@
<meta http-equiv="refresh" content="1">
</noscript>
<title>Payment required for {{order_summary}}</title>
- <!-- FIXME-6460: allow taler-merchant-httpd to serve this, so we do not use 3rd party? -->
<link rel="stylesheet"
- href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ href="/static/pure-min.css"
integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
crossorigin="anonymous">
<style>
diff --git a/contrib/show_order_details.en.must b/contrib/show_order_details.en.must
index 71e6a564..36885850 100644
--- a/contrib/show_order_details.en.must
+++ b/contrib/show_order_details.en.must
@@ -23,9 +23,8 @@
<meta http-equiv="refresh" content="1">
</noscript>
<title>Status of your order for {{order_summary}}</title>
- <!-- FIXME: inline this? How to best serve this without using 3rd party? -->
<link rel="stylesheet"
- href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ href="/static/pure-min.css"
integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
crossorigin="anonymous">
<style>
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index af86034b..60e8125e 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -95,6 +95,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_qr.h \
taler-merchant-httpd_reserves.c \
taler-merchant-httpd_reserves.h \
+ taler-merchant-httpd_statics.c \
+ taler-merchant-httpd_statics.h \
taler-merchant-httpd_templating.c \
taler-merchant-httpd_templating.h
taler_merchant_httpd_LDADD = \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 54b8d163..5c6924c2 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -63,6 +63,7 @@
#include "taler-merchant-httpd_post-orders-ID-refund.h"
#include "taler-merchant-httpd_post-tips-ID-pickup.h"
#include "taler-merchant-httpd_reserves.h"
+#include "taler-merchant-httpd_statics.h"
#include "taler-merchant-httpd_templating.h"
/**
@@ -1147,6 +1148,13 @@ url_handler (void *cls,
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
+ /* GET /static/ *: */
+ {
+ .url_prefix = "/static/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_return_static
+ },
{
NULL
}
@@ -1579,6 +1587,7 @@ run (void *cls,
"FORCE_AUDIT"))
TMH_force_audit = GNUNET_YES;
TMH_templating_init ();
+ TMH_statics_init ();
if (GNUNET_SYSERR ==
TMH_EXCHANGES_init (config))
{
diff --git a/src/backend/taler-merchant-httpd_statics.c b/src/backend/taler-merchant-httpd_statics.c
new file mode 100644
index 00000000..e8be2cba
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_statics.c
@@ -0,0 +1,336 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_statics.c
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_util.h>
+#include <taler/taler_mhd_lib.h>
+#include "taler-merchant-httpd_statics.h"
+#include "../mustach/mustach.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Entry in a key-value array we use to cache templates.
+ */
+struct TVE
+{
+ /**
+ * A name, used as the key. NULL for the last entry.
+ */
+ char *name;
+
+ /**
+ * Language the template is in.
+ */
+ char *lang;
+
+ /**
+ * Pre-built reply.
+ */
+ struct MHD_Response *reply;
+
+};
+
+
+/**
+ * Array of templates loaded into RAM.
+ */
+static struct TVE *loaded;
+
+/**
+ * Length of the #loaded array.
+ */
+static unsigned int loaded_length;
+
+
+/**
+ * Load Mustach template into memory. Note that we intentionally cache
+ * failures, that is if we ever failed to load a template, we will never try
+ * again.
+ *
+ * @param connection the connection we act upon
+ * @param name name of the template file to load
+ * (MUST be a 'static' string in memory!)
+ * @return NULL on error, otherwise the template
+ */
+static const struct TVE *
+lookup_file (struct MHD_Connection *connection,
+ const char *name)
+{
+ struct TVE *best = NULL;
+ const char *lang;
+
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ /* find best match by language */
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ if (0 != strcmp (loaded[i].name,
+ name))
+ continue; /* does not match by name */
+ if (NULL == loaded[i].lang) /* no language == always best match */
+ return &loaded[i];
+ if ( (NULL == best) ||
+ (TALER_language_matches (lang,
+ loaded[i].lang) >
+ TALER_language_matches (lang,
+ best->lang) ) )
+ best = &loaded[i];
+ }
+ if (NULL == best)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No static file found for `%s'\n",
+ name);
+ return NULL;
+ }
+ return best;
+}
+
+
+/**
+ * Check for the existence of a static file and return the result if it
+ * matches. Otherwise returns a "not found" page.
+ *
+ * @param connection the connection we act upon
+ * @param template basename of the template to load
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param kvc key value pairs to fill in
+ * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
+ */
+MHD_RESULT
+TMH_return_static (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const struct TVE *tmpl;
+
+ tmpl = lookup_file (connection,
+ hc->infix);
+ if (NULL == tmpl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load static file `%s'\n",
+ hc->infix);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ENDPOINT_UNKNOWN,
+ hc->infix);
+ }
+
+ return MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ tmpl->reply);
+}
+
+
+/**
+ * Function called with a static file's filename.
+ *
+ * @param cls closure
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static int
+load_static_file (void *cls,
+ const char *filename)
+{
+ char *lang;
+ char *end;
+ int fd;
+ struct stat sb;
+ const char *name;
+ struct MHD_Response *reply;
+
+ if ('.' == filename[0])
+ return GNUNET_OK;
+ name = strrchr (filename,
+ '/');
+ if (NULL == name)
+ name = filename;
+ else
+ name++;
+ lang = strchr (name,
+ '.');
+ if (NULL == lang)
+ return GNUNET_OK; /* name must include _some_ extension */
+ lang++;
+ end = strchr (lang,
+ '.');
+ if (NULL == end)
+ {
+ /* language was not present, we ONLY have the extension */
+ end = lang;
+ lang = NULL;
+ }
+ /* finally open template */
+ fd = open (filename,
+ O_RDONLY);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ fstat (fd,
+ &sb))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+
+ reply = MHD_create_response_from_fd (sb.st_size,
+ fd);
+ if (NULL == reply)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+
+ {
+ static struct MimeMap
+ {
+ const char *ext;
+ const char *mime;
+ } mm[] = {
+ { .ext = ".css", .mime = "text/css" },
+ { .ext = ".js", .mime = "text/javascript" },
+ { .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 = ".apng", .mime = "image/apng" },
+ { .ext = ".gif", .mime = "image/gif" },
+ { .ext = ".svg", .mime = "image/svg+xml" },
+ { .ext = ".tiff", .mime = "image/tiff" },
+ { .ext = ".ico", .mime = "image/x-icon" },
+ { .ext = ".bmp", .mime = "image/bmp" },
+ { .ext = ".epub", .mime = "application/epub+zip" },
+ { .ext = ".xml", .mime = "text/xml" },
+ { .ext = NULL, .mime = NULL }
+ };
+ const char *mime;
+
+ mime = NULL;
+ for (unsigned int i = 0; NULL != mm[i].ext; i++)
+ if (0 == strcasecmp (mm[i].ext,
+ end))
+ {
+ mime = mm[i].mime;
+ break;
+ }
+ if (NULL != mime)
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
+ }
+
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ loaded_length + 1);
+ if (NULL != lang)
+ {
+ GNUNET_asprintf (&loaded[loaded_length - 1].name,
+ "%.*s%s",
+ (int) (lang - name) - 1,
+ name,
+ end);
+ loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
+ end - lang);
+ }
+ else
+ {
+ loaded[loaded_length - 1].name = GNUNET_strdup (name);
+ }
+ loaded[loaded_length - 1].reply = reply;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Preload static files.
+ */
+int
+TMH_statics_init ()
+{
+ char *dn;
+ int ret;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/merchant/static/",
+ path);
+ GNUNET_free (path);
+ }
+ ret = GNUNET_DISK_directory_scan (dn,
+ &load_static_file,
+ NULL);
+ GNUNET_free (dn);
+ if (-1 == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Nicely shut down.
+ */
+void __attribute__ ((destructor))
+get_statics_fini ()
+{
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ GNUNET_free (loaded[i].name);
+ GNUNET_free (loaded[i].lang);
+ MHD_destroy_response (loaded[i].reply);
+ }
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ 0);
+}
diff --git a/src/backend/taler-merchant-httpd_statics.h b/src/backend/taler-merchant-httpd_statics.h
new file mode 100644
index 00000000..dac06a0d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_statics.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_statics.h
+ * @brief logic to preload and serve static files
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_STATICS_H
+#define TALER_MERCHANT_HTTPD_STATICS_H
+
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+/**
+ * Check for the existence of a static file and return the result if it
+ * matches. Otherwise returns a "not found" page.
+ *
+ * @param connection the connection we act upon
+ * @param template basename of the template to load
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param kvc key value pairs to fill in
+ * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
+ */
+MHD_RESULT
+TMH_return_static (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/**
+ * Preload static files.
+ */
+int
+TMH_statics_init (void);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_templating.c b/src/backend/taler-merchant-httpd_templating.c
index 3fcdcebe..1b480a67 100644
--- a/src/backend/taler-merchant-httpd_templating.c
+++ b/src/backend/taler-merchant-httpd_templating.c
@@ -470,7 +470,7 @@ TMH_templating_init ()
* Nicely shut down.
*/
void __attribute__ ((destructor))
-get_orders_fini ()
+templating_fini ()
{
for (unsigned int i = 0; i<loaded_length; i++)
{