diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd8ef64..dc27b9a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
## CHANGELOG
+### [25.6.0] - Mar 20, 2025
+- Added support for `-allowjs` which allows dynamic JavaScript in HTML outputs and JinjaFx Input modals - this should only be enabled in internal environments where you trust your users
+- Added support for a `script` option for JinjaFx Input modals to allow dynamic input modals - requires `-allowjs`
+
### [25.5.5] - Mar 17, 2025
- Update font look on MacOS to make it less heavy
- Added support for `-nocache` argument for internal development
@@ -423,6 +427,7 @@
### 21.11.0 - Nov 29, 2021
- Initial release
+[25.6.0]: https://github.com/cmason3/jinjafx_server/compare/25.5.5...25.6.0
[25.5.5]: https://github.com/cmason3/jinjafx_server/compare/25.5.4...25.5.5
[25.5.4]: https://github.com/cmason3/jinjafx_server/compare/25.5.3...25.5.4
[25.5.3]: https://github.com/cmason3/jinjafx_server/compare/25.5.2...25.5.3
diff --git a/README.md b/README.md
index 167c322..7e45621 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
jinjafx_server -s [-l
] [-p ]
[-r | -s3 | -github /[:]]
[-rl ] [-tl ] [-ml ]
- [-logfile ] [-weblog] [-pandoc] [-v]
+ [-logfile ] [-weblog] [-pandoc] [-allowjs] [-nocache] [-v]
-s - start the JinjaFx Server
-l - specify a listen address (default is '127.0.0.1')
@@ -34,6 +34,8 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
-logfile - specify a logfile for persistent logging
-weblog - enable web log interface (/logs)
-pandoc - enable support for DOCX using pandoc (requires pandoc)
+ -allowjs - allows javascript in `jinjafx_input` and html output
+ -nocache - disables versioned urls for internal development
-v - log all HTTP requests
Environment Variables:
@@ -79,6 +81,8 @@ The `-rl` argument is used to provide an optional rate limit of the source IP -
The `-weblog` argument in combination with the `JFX_WEBLOG_KEY` environment variable enables the Web Log interface to view the current application logs - this can be accessed from a web browser using the URL `/logs` - the user will be prompted for the key or they can provide it via a query string of `?key=`.
+If you specify `-allowjs` then this allows you to inject dynamic JavaScript into HTML outputs and JinjaFx Input modals, but this basically disables the Content Security Policy, which protects you from XSS attacks. This is only advisable if you are hosting this internally and you trust your users.
+
### Shortcut Keys
As well as supporting the standard CodeMirror shortcut keys for the "data.csv", "global.yml", "vars.yml" and "template.j2" panes, it also supports the following custom shortcut keys:
@@ -181,6 +185,16 @@ jinjafx_input:
```
+If you pass `-allowjs` on the command line then you can also specify a `script` section, which allows you to perform dynamic actions within your modal, e.g:
+
+```yaml
+jinjafx_input:
+ script: |2
+ document.getElementById('select_dropdown').addEventListener('change', function() {
+ // Do Something
+ });
+```
+
You can also specify an optional `size` attribute alongside the `body` attribute which sets the width of the modal using the pre-defined Bootstrap sizes (i.e. "sm", "lg" and "xl"). The input form supports full native HTML validation using `required` and `pattern` attributes. The values which are input are then mapped to Jinja2 variables using the `data-var` custom attribute (e.g. `data-var="name"` would map to `jinjafx_input['name']` or `jinjafx_input.name`):
```jinja2
diff --git a/jinjafx_server.py b/jinjafx_server.py
index 43b8104..ab833ea 100755
--- a/jinjafx_server.py
+++ b/jinjafx_server.py
@@ -27,7 +27,7 @@
import re, argparse, hashlib, traceback, glob, hmac, uuid, struct, binascii, gzip, requests, ctypes, subprocess
import cmarkgfm, emoji
-__version__ = '25.5.5'
+__version__ = '25.6.0'
llock = threading.RLock()
rlock = threading.RLock()
@@ -387,9 +387,14 @@ def sanitise_dt(dt):
else:
r = [ 'text/plain', 404, '404 Not Found\r\n'.encode('utf-8'), sys._getframe().f_lineno ]
+ script_src = "'self' https://cdnjs.cloudflare.com"
+
+ if allowjs:
+ script_src += " 'unsafe-inline'"
+
headers = {
'X-Content-Type-Options': 'nosniff',
- 'Content-Security-Policy': "default-src 'self'; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com; img-src data: *; frame-ancestors 'none'",
+ 'Content-Security-Policy': "default-src 'self'; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; script-src " + script_src + "; img-src data: *; frame-ancestors 'none'",
'Referrer-Policy': 'strict-origin-when-cross-origin'
}
etag = '"' + hashlib.sha224(repr(headers).encode('utf-8') + b'|' + r[0].encode('utf-8') + b'; ' + r[2]).hexdigest() + '"'
@@ -1122,6 +1127,7 @@ def main(rflag=[0]):
global rl_limit
global timelimit
global logfile
+ global allowjs
global nocache
global verbose
global pandoc
@@ -1145,9 +1151,11 @@ def main(rflag=[0]):
parser.add_argument('-logfile', metavar='', type=str)
parser.add_argument('-weblog', action='store_true', default=False)
parser.add_argument('-pandoc', action='store_true', default=False)
+ parser.add_argument('-allowjs', action='store_true', default=False)
parser.add_argument('-nocache', action='store_true', default=False)
parser.add_argument('-v', action='store_true', default=False)
args = parser.parse_args()
+ allowjs = args.allowjs
nocache = args.nocache
verbose = args.v
diff --git a/www/index.html b/www/index.html
index b224292..53cdcea 100644
--- a/www/index.html
+++ b/www/index.html
@@ -32,7 +32,7 @@
-
+
diff --git a/www/jinjafx_m.js b/www/jinjafx_m.js
index 2c93a76..000e035 100644
--- a/www/jinjafx_m.js
+++ b/www/jinjafx_m.js
@@ -119,6 +119,7 @@ function getStatusText(code) {
var dt_epassword = null;
var delete_pending = false;
var input_form = null;
+ var input_script = null;
var r_input_form = null;
var jinput = null;
var protect_action = 0;
@@ -596,6 +597,13 @@ function getStatusText(code) {
}
if (vars['jinjafx_input'].hasOwnProperty('body')) {
+ if (vars['jinjafx_input'].hasOwnProperty('script')) {
+ if (input_script !== vars['jinjafx_input']['script']) {
+ input_script = vars['jinjafx_input']['script'];
+ input_form = null;
+ }
+ }
+
if (input_form !== vars['jinjafx_input']['body']) {
var xHR = new XMLHttpRequest();
xHR.open("POST", '/jinjafx?dt=jinjafx_input', true);
@@ -610,6 +618,13 @@ function getStatusText(code) {
r_input_form = d(obj.outputs['Output:text']);
document.getElementById('jinjafx_input_form').innerHTML = r_input_form;
input_form = vars['jinjafx_input']['body'];
+
+ if (vars['jinjafx_input'].hasOwnProperty('script')) {
+ var script = document.createElement('script');
+ script.innerHTML = vars['jinjafx_input']['script'];
+ document.getElementById('jinjafx_input').appendChild(script);
+ }
+
jinput = new bootstrap.Modal(document.getElementById('jinjafx_input'), {
keyboard: false
});