diff --git a/.gitignore b/.gitignore index 55be276..75d38aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +config.py + # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 7e4ad83..3f698ff 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # landoc -Server for sharing documents over a LAN \ No newline at end of file +Server for sharing documents over a LAN + +Not well-tested; please do not use this over the Internet diff --git a/config.py b/config.py new file mode 100644 index 0000000..9bd74e4 --- /dev/null +++ b/config.py @@ -0,0 +1,15 @@ +root = "/path/to/documents/" +# The path to the direvtory containing the documents + +title = "User-Visible Title" +# A user-visible title for the server (e.g. "School Documents" or "My Notes") + +allowed_file_types = ["txt", "odt", "pdf", "md"] +# File types that LANdoc will serve; all other file types will result in 403 +# Forbidden + +# To disallow one of these file types, remove it; to allow another type, add +# its extension without the `.` + +# `md` and `txt` files will be displayed in the LANdoc viewer; all other files +# will be sent as-is to the client diff --git a/landoc.py b/landoc.py new file mode 100644 index 0000000..99bfd6f --- /dev/null +++ b/landoc.py @@ -0,0 +1,270 @@ +import flask +import jinja2 +import os +import mimetypes +import bleach +from markdown_it import MarkdownIt + +app = flask.Flask(__name__) +md = MarkdownIt("commonmark", {"typographer": True}).enable( + ["replacements", "smartquotes"] +) + + +def split_url(url): + segments = [""] + for character in url: + segments[-1] += character + if character == "/": + segments.append("") + + if not segments[-1]: + del segments[-1] + + paths = [ + [segment, "".join(segments[: i + 1])] + for i, segment in enumerate(segments) + ] + + paths[0][0] = f"{title}: {paths[0][0]}" + + return paths + + +def render(path, main, show_print=True, status_code=200): + path = jinja2.filters.escape(path) + path_segments = split_url(path) + path_html = ( + "".join( + [ + f'
{bleach.clean(text)}") + + +def render_md(text, path): + text = bleach.clean( + md.render(text.decode("utf-8")), + tags=[ + "a", + "abbr", + "acronym", + "b", + "blockquote", + "code", + "em", + "i", + "li", + "ol", + "strong", + "ul", + "p", + "pre", + "img", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + ], + attributes={ + "a": ["href", "title"], + "abbr": ["title"], + "acronym": ["title"], + "ol": ["start"], + "li": ["value"], + }, + ) + return render(path, text) + + +def render_directory(contents, path): + # TODO: strikethrough forbidden files + if path.endswith("/"): + contents.sort() + listing = "".join( + [ + f'
{path}
Disallowed file type: {file_type}
",
+ show_print=False,
+ status_code=403,
+ )
+ elif file_type in renderers:
+ with open(full_path, "rb") as f:
+ return renderers[file_type](f.read(), path)
+ else:
+ if path.endswith("/"):
+ return flask.redirect(path[:-1])
+ else:
+ with open(full_path, "rb") as f:
+ return flask.Response(
+ f.read(),
+ headers={
+ "Content-Type": mimetypes.guess_type(full_path)[0]
+ },
+ )
+ except FileNotFoundError:
+ return render(
+ path,
+ f"
{path}
", + show_print=False, + status_code=404, + )