Skip to content

Commit 725e8e8

Browse files
authored
Merge pull request #120 from cmason3/dev
Dev
2 parents 148ad59 + 05b24ee commit 725e8e8

File tree

3 files changed

+67
-32
lines changed

3 files changed

+67
-32
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
## CHANGELOG
22

3+
### [26.3.0] - January 27, 2026
4+
- [BREAKING CHANGE] Removed `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` in favour of `S3_ACCESS_KEY` and `S3_SECRET_KEY`
5+
- [BREAKING CHANGE] Added `S3_REGION_NAME`, which is now mandatory for S3
6+
- Added support for Generic S3, so we can use providers like MinIO alongside AWS S3
7+
- Fixed an issue with POST requests when the query string was malformed
8+
- Fixed an issue with ANSI colours appearing in the logfile
9+
310
### [26.2.4] - January 20, 2026
411
- Implemented a workaround for https://github.com/twbs/bootstrap/issues/38779
512

613
### [26.2.3] - January 19, 2026
7-
- Fixed real root cause of read-only editor panes
14+
- Fixed the real root cause of read-only editor panes
815
- Fixed an issue where the DataSet dropdown wasn't built if a non-existent DataTemplate was specified
916

1017
### [26.2.2] - January 17, 2026
@@ -526,6 +533,7 @@
526533
- Initial release
527534

528535

536+
[26.3.0]: https://github.com/cmason3/jinjafx_server/compare/26.2.4...26.3.0
529537
[26.2.4]: https://github.com/cmason3/jinjafx_server/compare/26.2.3...26.2.4
530538
[26.2.3]: https://github.com/cmason3/jinjafx_server/compare/26.2.2...26.2.3
531539
[26.2.2]: https://github.com/cmason3/jinjafx_server/compare/26.2.1...26.2.2

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,35 +38,42 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
3838

3939
```
4040
jinjafx_server -s [-l <address>] [-p <port>]
41-
[-r <directory> | -s3 <aws s3 url> | -github <owner>/<repo>[:<branch>]]
41+
[-r <directory> | -s3 <s3 url> | -github <owner>/<repo>[:<branch>]] [-insecure]
4242
[-rl <rate/limit>] [-tl <time limit>] [-ml <memory limit>]
4343
[-logfile <logfile>] [-weblog] [-pandoc] [-allowjs | -nocsp] [-nocache] [-v]
4444
4545
-s - start the JinjaFx Server
4646
-l <address> - specify a listen address (default is '127.0.0.1')
4747
-p <port> - specify a listen port (default is 8080)
4848
-r <directory> - specify a local repository directory (allows 'Get Link')
49-
-s3 <aws s3 url> - specify a repository using aws s3 buckets (allows 'Get Link')
49+
-s3 <s3 url> - specify a repository using s3 buckets (allows 'Get Link')
5050
-github <owner>/<repo>[:<branch>] - specify a repository using github (allows 'Get Link')
5151
-rl <rate/limit> - specify a rate limit (i.e. '5/30s' for 5 requests in 30 seconds)
5252
-tl <time limit> - specify a time limit per request (seconds)
5353
-ml <memory limit> - specify a global memory limit (megabytes < total)
5454
-logfile <logfile> - specify a logfile for persistent logging
5555
-weblog - enable web log interface (/logs)
56+
-insecure - skip TLS certificate verification for '-s3'
5657
-pandoc - enable support for DOCX using pandoc (requires pandoc)
5758
-allowjs - allows javascript in `jinjafx_input` and html output
5859
-nocsp - disables 'content-security-policy' (implies '-allowjs')
5960
-nocache - disables versioned urls for internal development
6061
-v - log all http requests
6162
6263
Environment Variables:
63-
AWS_ACCESS_KEY - specify an aws access key to authenticate for '-s3'
64-
AWS_SECRET_KEY - specify an aws secret key to authenticate for '-s3'
65-
GITHUB_TOKEN - specify a github personal access token for '-github'
66-
JFX_WEBLOG_KEY - specify a key to allow access to web log interface
64+
JFX_WEBLOG_KEY - specify a key to allow access to web log interface
65+
S3_REGION_NAME - specify a region name for '-s3' (i.e. 'eu-west-2')
66+
S3_ACCESS_KEY - specify an access key to authenticate for '-s3'
67+
S3_SECRET_KEY - specify a secret key to authenticate for '-s3'
68+
GITHUB_TOKEN - specify a github personal access token for '-github'
6769
```
6870

69-
The `-r`, `-s3` or `-github` arguments (mutually exclusive) allow you to specify a repository (`-r` is a local directory, `-s3` is an AWS S3 URL and `-github` is a GitHub repository) that will be used to store DataTemplates on the server via the "Get Link" and "Update Link" buttons. The generated link is guaranteed to be unique and a different link will be created every time. If you use an AWS S3 bucket then you will also need to provide some credentials via the two environment variables which has read and write permissions to the S3 URL.
71+
The `-r`, `-s3` or `-github` arguments (mutually exclusive) allow you to specify a repository (`-r` is a local directory, `-s3` is an S3 URL and `-github` is a GitHub repository) that will be used to store DataTemplates on the server via the "Get Link" and "Update Link" buttons. The generated link is guaranteed to be unique and a different link will be created every time.
72+
73+
If you use an S3 bucket then you will also need to provide some credentials (and the region) via the "S3_" environment variables, which has read and write permissions to the S3 bucket. It supports both AWS S3 as well as Generic S3 via the following URL formats (https is assumed):
74+
75+
- AWS S3 - `<bucket>.s3.<region>.amazonaws.com`
76+
- Generic S3 - `<fqdn>[:<port>]/<bucket>`
7077

7178
The `-rl` argument is used to provide an optional rate limit of the source IP - the "rate" is how many requests are permitted and the "limit" is the interval in which those requests are permitted - it can be specified in "s", "m" or "h" (e.g. "5/30s", "10/1m" or "30/1h"). This is currently only applied to "Get Link" and Web Log authentication.
7279

jinjafx_server/jinjafx_server.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424
from jinja2 import __version__ as jinja2_version
2525
from jinja2 import TemplateError
2626

27-
import jinjafx, os, io, socket, signal, threading, yaml, json, base64, time, datetime, resource
27+
import jinjafx, os, io, socket, signal, threading, yaml, json, base64, time, datetime, resource, urllib3
2828
import re, argparse, hashlib, traceback, glob, hmac, uuid, struct, binascii, gzip, requests, ctypes, subprocess
2929
import cmarkgfm, emoji, jsonschema
3030

31-
__version__ = '26.2.4'
31+
__version__ = '26.3.0'
3232

3333
llock = threading.RLock()
3434
rlock = threading.RLock()
@@ -37,10 +37,12 @@
3737
aws_s3_url = None
3838
aws_access_key = None
3939
aws_secret_key = None
40+
aws_region_name = None
4041
github_url = None
4142
github_token = None
4243
jfx_weblog_key = None
4344
repository = None
45+
verify = True
4446
verbose = False
4547
nocache = False
4648
pandoc = None
@@ -465,9 +467,15 @@ def do_HEAD(self):
465467
def do_POST(self):
466468
try:
467469
cheaders = {}
470+
params = {}
468471

469472
uc = self.path.split('?', 1)
470-
params = { x[0]: x[1] for x in [x.split('=') for x in uc[1].split('&') ] } if len(uc) > 1 else { }
473+
if len(uc) > 1 and len(uc[1]) > 0:
474+
for x in uc[1].split('&'):
475+
e = x.split('=')
476+
if len(e[0]) > 0:
477+
params.update({ e[0]: e[1] if len(e) > 1 else '' })
478+
471479
fpath = uc[0]
472480

473481
if hasattr(self, 'headers') and 'X-Forwarded-For' in self.headers:
@@ -1143,6 +1151,7 @@ def main(rflag=[0]):
11431151
global aws_s3_url
11441152
global aws_access_key
11451153
global aws_secret_key
1154+
global aws_region_name
11461155
global github_url
11471156
global github_token
11481157
global jfx_weblog_key
@@ -1151,6 +1160,7 @@ def main(rflag=[0]):
11511160
global rl_limit
11521161
global timelimit
11531162
global logfile
1163+
global verify
11541164
global allowjs
11551165
global nocsp
11561166
global nocache
@@ -1168,13 +1178,14 @@ def main(rflag=[0]):
11681178
parser.add_argument('-p', metavar='<port>', default=8080, type=int)
11691179
group_ex = parser.add_mutually_exclusive_group()
11701180
group_ex.add_argument('-r', metavar='<directory>', type=w_directory)
1171-
group_ex.add_argument('-s3', metavar='<aws s3 url>', type=str)
1181+
group_ex.add_argument('-s3', metavar='<s3 url>', type=str)
11721182
group_ex.add_argument('-github', metavar='<owner>/<repo>[:<branch>]', type=str)
11731183
parser.add_argument('-rl', metavar='<rate/limit>', type=rlimit)
11741184
parser.add_argument('-tl', metavar='<time limit>', type=int, default=0)
11751185
parser.add_argument('-ml', metavar='<memory limit>', type=int, default=0)
11761186
parser.add_argument('-logfile', metavar='<logfile>', type=str)
11771187
parser.add_argument('-weblog', action='store_true', default=False)
1188+
parser.add_argument('-insecure', action='store_true', default=False)
11781189
parser.add_argument('-pandoc', action='store_true', default=False)
11791190
group2_ex = parser.add_mutually_exclusive_group()
11801191
group2_ex.add_argument('-allowjs', action='store_true', default=False)
@@ -1185,8 +1196,12 @@ def main(rflag=[0]):
11851196
allowjs = args.allowjs
11861197
nocsp = args.nocsp
11871198
nocache = args.nocache
1199+
verify = not args.insecure
11881200
verbose = args.v
11891201

1202+
if not verify:
1203+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
1204+
11901205
if args.pandoc:
11911206
from shutil import which
11921207
pandoc = which('pandoc')
@@ -1200,13 +1215,17 @@ def main(rflag=[0]):
12001215
if jfx_weblog_key is None:
12011216
parser.error("argument -weblog: environment variable 'JFX_WEBLOG_KEY' is mandatory")
12021217

1218+
if not args.s3 and args.insecure:
1219+
parser.error("argument -insecure: requires argument '-s3'")
1220+
12031221
if args.s3 is not None:
1204-
aws_s3_url = args.s3
1205-
aws_access_key = os.getenv('AWS_ACCESS_KEY')
1206-
aws_secret_key = os.getenv('AWS_SECRET_KEY')
1222+
aws_s3_url = args.s3.rstrip('/')
1223+
aws_region_name = os.getenv('S3_REGION_NAME')
1224+
aws_access_key = os.getenv('S3_ACCESS_KEY')
1225+
aws_secret_key = os.getenv('S3_SECRET_KEY')
12071226

1208-
if aws_access_key == None or aws_secret_key == None:
1209-
parser.error("argument -s3: environment variables 'AWS_ACCESS_KEY' and 'AWS_SECRET_KEY' are mandatory")
1227+
if aws_access_key is None or aws_secret_key is None or aws_region_name is None:
1228+
parser.error("argument -s3: environment variables 'S3_REGION_NAME', 'S3_ACCESS_KEY' and 'S3_SECRET_KEY' are mandatory")
12101229

12111230
if args.github is not None:
12121231
github_url = args.github
@@ -1282,7 +1301,7 @@ def log(t, ae=''):
12821301
timestamp = datetime.datetime.now().strftime('%b %d %H:%M:%S.%f')[:19]
12831302

12841303
if os.getenv('JOURNAL_STREAM'):
1285-
print(re.sub(r'\033\[(?:1;[0-9][0-9]|0)m', '', t + ae))
1304+
print(re.sub(r'\033\[(?:(?:[01];)?[0-9][0-9]|0)m', '', t + ae))
12861305

12871306
else:
12881307
print('[' + timestamp + '] ' + t + ae)
@@ -1293,7 +1312,7 @@ def log(t, ae=''):
12931312
if logfile is not None:
12941313
try:
12951314
with open(logfile, 'at') as f:
1296-
f.write('[' + timestamp + '] ' + re.sub(r'\033\[(?:1;[0-9][0-9]|0)m', '', t + ae) + '\n')
1315+
f.write('[' + timestamp + '] ' + re.sub(r'\033\[(?:(?:[01];)?[0-9][0-9]|0)m', '', t + ae) + '\n')
12971316

12981317
except Exception as e:
12991318
traceback.print_exc()
@@ -1343,14 +1362,15 @@ def rlimit(rl):
13431362
return rl
13441363

13451364

1346-
def aws_s3_authorization(method, fname, region, headers):
1365+
def aws_s3_authorization(method, s3_url, fname, headers):
1366+
prefix = s3_url.split('/', 1)[-1] + '/' if '/' in s3_url else ''
13471367
sheaders = ';'.join(map(lambda k: k.lower(), sorted(headers.keys())))
1348-
srequest = headers['x-amz-date'][:8] + '/' + region + '/s3/aws4_request'
1349-
cr = method.upper() + '\n/' + fname + '\n\n' + '\n'.join([ k.lower() + ':' + v for k, v in sorted(headers.items()) ]) + '\n\n' + sheaders + '\n' + headers['x-amz-content-sha256']
1368+
srequest = headers['x-amz-date'][:8] + '/' + aws_region_name + '/s3/aws4_request'
1369+
cr = method.upper() + '\n/' + prefix + fname + '\n\n' + '\n'.join([ k.lower() + ':' + v for k, v in sorted(headers.items()) ]) + '\n\n' + sheaders + '\n' + headers['x-amz-content-sha256']
13501370
s2s = 'AWS4-HMAC-SHA256\n' + headers['x-amz-date'] + '\n' + srequest + '\n' + hashlib.sha256(cr.encode('utf-8')).hexdigest()
13511371

13521372
dkey = hmac.new(('AWS4' + aws_secret_key).encode('utf-8'), headers['x-amz-date'][:8].encode('utf-8'), hashlib.sha256).digest()
1353-
drkey = hmac.new(dkey, region.encode('utf-8'), hashlib.sha256).digest()
1373+
drkey = hmac.new(dkey, aws_region_name.encode('utf-8'), hashlib.sha256).digest()
13541374
drskey = hmac.new(drkey, b's3', hashlib.sha256).digest()
13551375
skey = hmac.new(drskey, b'aws4_request', hashlib.sha256).digest()
13561376

@@ -1361,38 +1381,38 @@ def aws_s3_authorization(method, fname, region, headers):
13611381

13621382
def aws_s3_delete(s3_url, fname):
13631383
headers = {
1364-
'Host': s3_url,
1384+
'Host': s3_url.split('/')[0],
13651385
'Content-Type': 'text/plain',
13661386
'x-amz-content-sha256': hashlib.sha256(b'').hexdigest(),
13671387
'x-amz-date': datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
13681388
}
1369-
headers = aws_s3_authorization('DELETE', fname, s3_url.split('.')[2], headers)
1370-
return requests.delete('https://' + s3_url + '/' + fname, headers=headers)
1389+
headers = aws_s3_authorization('DELETE', s3_url, fname, headers)
1390+
return requests.delete('https://' + s3_url + '/' + fname, headers=headers, verify=verify)
13711391

13721392

13731393
def aws_s3_put(s3_url, fname, content, ctype):
13741394
content = gzip.compress(content.encode('utf-8'))
13751395
headers = {
1376-
'Host': s3_url,
1396+
'Host': s3_url.split('/')[0],
13771397
'Content-Length': str(len(content)),
13781398
'Content-Type': ctype,
13791399
'Content-Encoding': 'gzip',
13801400
'x-amz-content-sha256': hashlib.sha256(content).hexdigest(),
13811401
'x-amz-date': datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
13821402
}
1383-
headers = aws_s3_authorization('PUT', fname, s3_url.split('.')[2], headers)
1384-
return requests.put('https://' + s3_url + '/' + fname, headers=headers, data=content)
1403+
headers = aws_s3_authorization('PUT', s3_url, fname, headers)
1404+
return requests.put('https://' + s3_url + '/' + fname, headers=headers, data=content, verify=verify)
13851405

13861406

13871407
def aws_s3_get(s3_url, fname):
13881408
headers = {
1389-
'Host': s3_url,
1409+
'Host': s3_url.split('/')[0],
13901410
'Accept-Encoding': 'gzip',
13911411
'x-amz-content-sha256': hashlib.sha256(b'').hexdigest(),
13921412
'x-amz-date': datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
13931413
}
1394-
headers = aws_s3_authorization('GET', fname, s3_url.split('.')[2], headers)
1395-
return requests.get('https://' + s3_url + '/' + fname, headers=headers)
1414+
headers = aws_s3_authorization('GET', s3_url, fname, headers)
1415+
return requests.get('https://' + s3_url + '/' + fname, headers=headers, verify=verify)
13961416

13971417

13981418
def github_delete(github_url, fname, sha=None):

0 commit comments

Comments
 (0)