From ced67bec8bbb2f8deb4992f3432da7241c90edac Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Wed, 14 Apr 2021 20:45:50 -0500 Subject: [PATCH] allow for serving files directly inside pages/ --- incorporealcms/pages.py | 17 ++++++++----- tests/functional_tests.py | 7 ++++++ tests/instance/pages/bss-square-no-bg.png | Bin 0 -> 49940 bytes tests/instance/pages/foo.txt | 1 + tests/test_pages.py | 28 +++++++++++++++------- 5 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 tests/instance/pages/bss-square-no-bg.png create mode 100644 tests/instance/pages/foo.txt diff --git a/incorporealcms/pages.py b/incorporealcms/pages.py index 911df60..6191852 100644 --- a/incorporealcms/pages.py +++ b/incorporealcms/pages.py @@ -6,7 +6,7 @@ import re from flask import Blueprint, Markup, abort from flask import current_app as app -from flask import redirect, request +from flask import redirect, request, send_from_directory from tzlocal import get_localzone from incorporealcms.lib import get_meta_str, init_md, render @@ -21,7 +21,7 @@ bp = Blueprint('pages', __name__, url_prefix='/') def display_page(path): """Get the file contents of the requested path and render the file.""" try: - resolved_path = request_path_to_instance_resource_path(path) + resolved_path, render_md = request_path_to_instance_resource_path(path) logger.debug("received request for path '%s', resolved to '%s'", path, resolved_path) except PermissionError: abort(400) @@ -30,6 +30,9 @@ def display_page(path): except FileNotFoundError: abort(404) + if not render_md: + return send_from_directory(app.instance_path, resolved_path) + try: with app.open_instance_resource(resolved_path, 'r') as entry_file: mtime = datetime.datetime.fromtimestamp(os.path.getmtime(entry_file.name), get_localzone()) @@ -76,21 +79,23 @@ def request_path_to_instance_resource_path(path): logger.info("client requested a path '%s' that is actually a directory", path) raise IsADirectoryError - # derive the proper markdown file depending on if this is a dir or file + # derive the proper markdown or actual file depending on if this is a dir or file if os.path.isdir(resolved_path): absolute_resource = os.path.join(resolved_path, 'index.md') + elif os.path.exists(resolved_path): + logger.info("final DIRECT path = '%s' for request '%s'", resolved_path, path) + return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), False else: absolute_resource = f'{resolved_path}.md' - logger.info("final path = '%s' for request '%s'", absolute_resource, path) - # does the final file actually exist? if not os.path.exists(absolute_resource): logger.warning("requested final path '%s' does not exist!", absolute_resource) raise FileNotFoundError + logger.info("final path = '%s' for request '%s'", absolute_resource, path) # we checked that the file exists via absolute path, but now we need to give the path relative to instance dir - return absolute_resource.replace(f'{app.instance_path}{os.path.sep}', '') + return absolute_resource.replace(f'{app.instance_path}{os.path.sep}', ''), True def instance_resource_path_to_request_path(path): diff --git a/tests/functional_tests.py b/tests/functional_tests.py index bd3a892..e83ca73 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -9,6 +9,13 @@ def test_page_that_exists(client): assert b'

test index

' in response.data +def test_direct_file_that_exists(client): + """Test that the app can serve a basic file at the index.""" + response = client.get('/foo.txt') + assert response.status_code == 200 + assert b'test file' in response.data + + def test_page_that_doesnt_exist(client): """Test that the app returns 404 for nonsense requests and they use my error page.""" response = client.get('/ohuesthaoeusth') diff --git a/tests/instance/pages/bss-square-no-bg.png b/tests/instance/pages/bss-square-no-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..c592d258e2b8393c4435b4a83c2ddbd0020c3931 GIT binary patch literal 49940 zcmeHQ2~ZSQ8txtj7(k&x6cvSrAmSPYb%`JxGIsF(_V$W7ZYIn#EnOM2QSKSpyzJ5Ol)1kHfL2r(aTAwW;;^shX;;?%_?p z_x|tu|Nr~n>rR!02X(dVZAlPB*N|Xo1VNZFZ6AplI^)u1b1p%Uu?qqNWeeuaAqbE3 zKc<%r6o1^a*#CUWR9~4}vhVx8vb~y;5s#xYdPK*4``KsTeUzVn{pzAm^Wx*aiKiSc zTDO=SFWnZgX{$!?V)!}zs7Lw$ebc=cd86XU?e#~dJXzbiIrC^`H5Fs$baQcWHz$YiqpJ$; zkE^u4p!kQS*2=~wo~g7tFi(^?X_t-eqZ7ljcWwB*`tbKjasPIBWSZ+y?%8M1&gkZc zBdgtQ|GG0f{m!6#^DMWx+4JXm*LnBx z%PsuQU+dp^H?xR1mp89Eqb@59&49(y;NLGJ2asjrY-xriNRe1V!2t{cSOTB` zPyi?Z6aWeU1%LuT0iXa-04M+y015yFfC4}Ppa4+t+M+;L`~5(%NZc}0{cwx+;!jA@ zRgb51y85ctI{!*p;(I)uSJq9JTQD~TJE1Z=iV3~;2b?r4-^~*L$}f=O^y95!vBK_T zFPyf#r$5cs9UX)fp!<>Je(~asxV!toLPC#P7>2_m6T%P!0UM%i6kK-4jyEFu zZ$sB%){P7o3?8s ze>uD<+P30qTT(UPhT_O9Sk-VE5g>^#Wl}J86x%b1`iCg&`Sr#A9vHP4D)R3tUOH%eg;NVX{33mo(Bxu`vv zdvWF$*BM;q{PZdrL#(E*=W{Dm!S;te+KN#7mvh$4C#F9hp)!5f|pG{E+s( zviy?Lpj|G{JE6jf)8)DWxHLrF7pe1pPt{X|GiuZ|8&o~8ncZN|ZI#=j8f#RY=)1CJ zAonC!x{;fsOvrr5+D+JTN@j`L&3?M>*lw;zH@SZM=nrw@R2Yaxy0~wXCAOOZ=q3Zk z=D2cijN7AoE;L*Cz&g>Cdy?Cunmy4W3337eLpz#J$V%%KB|U=HnAXrwk&MnU;J1+b!X1z|b$V?G97LUDHNl)h94*_`i z%Tz>sN)?_3yOJg)j-!IHMBK-+Cu2Qi6rO-wqKMV|%m;88gG)8?K=cDH2l1|8xVQ@) zTSsH*XQ@XN;d{9b&%zs7Qj_MiAL|ILO}o&^+jrv$kn&;+|GC~C%fWv^KR{i_yCO$4 zN5c~oSa0ZK@5T0+oq+cOfhwZ*cQz4NM+8OyMj!$s03!e+puq^h2*3zPFaj_FFaipU z0E_^P0LhDx9YA3Ct{6d`qkeB>32n)w~sN`WuuF zh$41Kt+8t4PL|w%YkMJ9>qAoASXXo0WoDd;h*&r_5?5@ALSm_Vx(V*`9``8Xu(!LX z{%&~(UP!6TC?R=g^%EiM=EdIVososBCT&L}3)3!S_HvkZK)W$)RDgCNw6yULkUWsQ zfENVc4!+$uwt|ojLOKClKqL>5ys-~~1P&57V_ZPqj*G-Wt^v8mm_t5Dj;u% zyj{R9Aa57QPuj-`Ge6ay7Fec?@i02R2`t?)W0*xB#pT7b^15{j?_eAZ#Y zAk>0TD-Z~QP-`qmb({ghf87WlNL~!doB)SGG6(DGa4ZhV93*r498P}#z8!qK04`t^ zt?>e%jx(@|7S_xQRHA`u1-SF zi@VF%2L{OGRx(ZE%(FM;k?|G|8A+02m;67YXxs}uSTaO|-6^ zXs+gmD|(`y(T!q`FTVN$zey9l+*sv6K#HZ5_%W>C9@^V*hHw@gpFM7Gh z=?D|-vt&gnj@*$k*s~dr-deKcfEo5dj+4TV|M+q(fS4MNWj76Cr}EIQQiB`gjD&<< zg1bbOSV-uz+j^%M+!$vhD5e~DiB=$1jUVVu;bdx>J%hUhlg+mZnBZ47`c|SDCKx6d zD7w?RzmfhJ5DW+g1OtNkp_>GvXimn^K05!elnE#rC>kglC>khQ{<^AKK+!