From 6f09426b6dacf501d675c49e672675259cb17836 Mon Sep 17 00:00:00 2001 From: Jasper den Ouden Date: Sun, 15 May 2022 16:57:54 +0200 Subject: [PATCH] Filename mangling fix for when confusion between directory names and those in urls --- live_doc/file_handler.py | 11 +++++----- live_doc/http_main.py | 46 ++++++++++++++++++++++++---------------- live_doc/main.py | 20 ++++++++++------- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/live_doc/file_handler.py b/live_doc/file_handler.py index dc009cd..1a72dd8 100644 --- a/live_doc/file_handler.py +++ b/live_doc/file_handler.py @@ -23,7 +23,7 @@ class FileHandler(dict): self.touched = set() # Which formats were touched and need closing. self.implied = dict() # Which formats were implied by which. - @property + @property # TODO particular interpreters depend.. def depends_files(self): """Returns files it depends on. In the `file` config, `depends` sets this. `:`-separated.""" @@ -39,10 +39,11 @@ In the `file` config, `depends` sets this. `:`-separated.""" def use_obj(self): return self.ensure_interpreter(self.use_lang) - def hard_reset(self, for_name): - """Harshly deletes `for_name` and where it was implied from.""" - if for_name in self: del self[for_name] - for name in self.implied.get(for_name, []): + def hard_reset(self, interpreter_name): + """Harshly deletes interpreter and where it was implied from.""" + if interpreter_name in self: del self[interpreter_name] + + for name in self.implied.get(interpreter_name, []): if name in self: del self[name] def reset_state(self): diff --git a/live_doc/http_main.py b/live_doc/http_main.py index e27e578..5286d3d 100644 --- a/live_doc/http_main.py +++ b/live_doc/http_main.py @@ -76,16 +76,23 @@ class RH(hs.BaseHTTPRequestHandler): def do_GET_file(self, file): """Access a file. TODO only files this thing made..""" - while file[0] == '/': file = file[1:] # May not start with slash. + # Strip slash and translate special filenames. + file = main.mangle_filename(file.lstrip('/')) + if not file: + self.send_response(403) + self.end_headers() + self.write("Not in permitted directory") + return + # Root directory permitted for module directory. + if ('/' + file).startswith(main.module_dir): + file = '/' + file - # TODO permanently remember what .d/ directories are ok? - if (file.find("..")!=-1 - or (file.find(".d/")==-1 and file not in main.file_ok)): + if file.find("..")!=-1: # Dont permit escaping. print("DENIED", main.output_dir, file, main.file_ok) self.send_response(403) self.end_headers() - self.wfile.write(b"access denied") - else: + self.write("stuff that may try to escape directories.") + else: # Ok, provide the file. print("ACCESS", main.output_dir, file) self.send_response(200) self.end_headers() @@ -116,6 +123,7 @@ class RH(hs.BaseHTTPRequestHandler): result_time = update(self.write, float(after_t), 5) else: # Nothing there to respond, maybe when file changes. # TODO need to be able to interrupt this? +# TODO won't do much if remembered state not appropriately invalidated.. subprocess.call(['inotifywait', '-e', 'modify'] + file.depends_files) result_time = update(self.write, float(after_t), 0.5) @@ -129,17 +137,16 @@ class RH(hs.BaseHTTPRequestHandler): def do_GET_close_nb(self, file): """Close a notebook.""" - return 'closed' if main.close_file() else 'not found' + return ('closed' if main.close_file(main.mangle_filename(file)) + else 'not found') def do_POST_reset_nb(self, file): """Reset a notebook.""" - if main.reset_file(file): + real_file = main.mangle_filename(file) + if main.reset_file(real_file): self.redirect("Resetting notebook, returning to it.", '/' + file) - return 'not found' - - def do_GET_reset_nb(self, filename): - return main.reset_file(filename) + return f"not found {real_file}" def report(self, say, *rest): print(say, self.path, self.client_address, @@ -154,16 +161,19 @@ class RH(hs.BaseHTTPRequestHandler): self.write(f"invalid {meth} ({rest})") def do_POST_save_nb(self, file): - saved = main.get_file(file).save_nb() - main.file_ok.add(saved) + main.file_ok.add(main.get_file(file).save_nb()) + + saved = f"{file}.d/index.html" self.redirect( # TODO report success/not, link to it. f"""Tried to save. {file}

Returning to notebook.

{saved} contains the saved file.

""", f"/{file}?save_attempt=1") def do_POST_save_nb_zip(self, file): - zip_file = main_get_file(file).save_nb_zip() + zip_file = main.get_file(file).save_nb_zip() main.file_ok.add(zip_file) + + zip_file = f"{file}.d/" + zip_file.split('/')[-1] self.redirect( # TODO report success/not, link to it. f"""Tried to save. {file}

Returning to notebook.

{zip_file} contains the saved file.

""", @@ -305,12 +315,12 @@ Will probably redirect to a picture after.\n""" filename = file.filename self.wfile.write(assets.bformat("top.htm", - file=filename, + file=path[1:], **{ nom : (f"{say}" if Path(filename).exists() else "") for nom, f, say in - [('if_saved', f"{filename}.d/index.html", "(earlier)"), - ('if_saved_zip', f"{filename}.d/" + filename.split('/')[-1] + ".zip", "(earlier)")] + [('if_saved', f"{path[1:]}.d/index.html", "(earlier)"), + ('if_saved_zip', f"{path[1:]}.d/" + path[1:].split('/')[-1] + ".zip", "(earlier)")] }, request_interval=1000*args.request_interval)) diff --git a/live_doc/main.py b/live_doc/main.py index 2e7f113..94a243e 100644 --- a/live_doc/main.py +++ b/live_doc/main.py @@ -31,9 +31,6 @@ class MainModuleDict(ModuleDict): from live_doc.file_handler import FileHandlerDict, FileHandler -import live_doc.module_dict as module_dict -module_dir = '/'.join(module_dict.__file__.split('/')[:-2]) - import os, time from live_doc.util.stream import Stream @@ -78,7 +75,7 @@ TODO option to insert them with base64, tho inefficient.""" saved = self.out_local("index_for_zip.html") import zipfile # (want dependency to be optional) zipname = file.split('/')[-1] - to_file = (self.out_local("{zipname}.zip") + to_file = (self.out_local(f"{zipname}.zip") if to_file is None else to_file) zf = zipfile.ZipFile(to_file, 'w') @@ -86,7 +83,7 @@ TODO option to insert them with base64, tho inefficient.""" zf.write(self.out_local(file_inside), f"{zipname}/{file_inside}") - self.save_nb(file, saved, add_inside_file) # Write the .html + self.save_nb(saved, add_inside_file) # Write the .html zf.write(saved, f"{zipname}/index.html") zf.close() return to_file @@ -145,9 +142,13 @@ highlighter=highlighters.bat_aha:highlighters.vimcat_aha:highlighters.script_bat colorer=highlighters.aha:highlighters.plain """) # NOTE: wrap is used straight-up right now. + # TODO deny list? def __init__(self, cfg_dirs, args, file_ok=None, permit_dirs=None): + import live_doc.module_dict as module_dict + self.module_dir = '/'.join(module_dict.__file__.split('/')[:-2]) + self.assets = AssetsMem([d + "/assets/" for d in cfg_dirs] - + [f"{module_dir}/assets/"]) + + [f"{self.module_dir}/assets/"]) self.configs = Assets([d + "/cfg/" for d in cfg_dirs]) # Figure where the files are put. @@ -175,12 +176,15 @@ colorer=highlighters.aha:highlighters.plain self.permit_dirs = permit_dirs if len(permit_dirs)>0 else [''] + def permitted_file(self, filename): + return any(map(filename.startswith, self.permit_dirs)) + def mangle_filename(self, filename): if filename.startswith(":live_doc:"): - return module_dir + filename[10:] + return self.module_dir + filename[10:] elif not filename.startswith('/') and filename.find("..") == -1: # Must be in one of the permitted directories. (defaultly all) - if any(map(filename.startswith, self.permit_dirs)): + if self.permitted_file(filename) return filename # else Otherwise not permitted. -- 2.45.2