Skip to content

Instantly share code, notes, and snippets.

@FND
Last active January 23, 2021 09:23
Show Gist options
  • Save FND/e765a0da6be27fb8b9e1779ae7e3ef4b to your computer and use it in GitHub Desktop.
Save FND/e765a0da6be27fb8b9e1779ae7e3ef4b to your computer and use it in GitHub Desktop.
minimal WebDAV implementation for TiddlyWiki
#!/usr/bin/env python3
import os
from hashlib import md5
ROOT_DIR = os.path.dirname(__file__)
STATUSES = {
200: "OK",
204: "No Content",
404: "Not Found",
405: "Method Not Allowed",
412: "Precondition Failed",
415: "Unsupported Media Type"
}
def handler(environ, start_response):
method = environ["REQUEST_METHOD"]
respond = lambda *args, **kwargs: make_response(start_response, *args, **kwargs)
if method == "OPTIONS":
return respond(200, [("DAV", "1")]) # XXX: TiddlyWiki expects 200 rather than 204
if method == "HEAD":
filepath = _determine_filepath(environ)
return respond(204, headers=[
("ETag", _determine_etag(filepath))
])
elif method == "GET":
uri = environ.get("PATH_INFO", "")
if uri == "/favicon.ico": # TODO: read from disk
return respond(404)
else:
return serve_file(environ, start_response)
if method == "PUT":
return store_file(environ, start_response)
else:
return respond(405, body="invalid request")
def serve_file(environ, start_response):
filepath = _determine_filepath(environ)
with open(filepath) as fh:
return make_response(start_response, 200,
headers=[("Content-Type", "text/html")], # NB: TiddlyWiki-specific
body=fh.read()) # XXX: inefficient
def store_file(environ, start_response):
respond = lambda *args, **kwargs: make_response(start_response, *args, **kwargs)
if environ["CONTENT_TYPE"] != "text/html;charset=UTF-8": # NB: TiddlyWiki-specific heuristic
return respond(415)
filepath = _determine_filepath(environ)
etag = environ.get("HTTP_IF_MATCH")
if etag is not None:
_hash = _determine_etag(filepath)
if etag != _hash:
return respond(412, body=("expected ETag %s, received %s" % (_hash, etag)))
try:
size = int(environ["CONTENT_LENGTH"])
content = environ["wsgi.input"].read(size)
except (KeyError, ValueError):
return make_response(400, body="missing `Content-Length` request header")
with open(filepath, "w") as fh:
fh.write(content.decode("utf-8"))
return respond(204)
def make_response(start_response, status, headers=[], body=[]):
status_line = "%s %s" % (status, STATUSES[status])
start_response(status_line, headers)
if isinstance(body, str): # XXX: simplistic
return [body.encode("utf-8")]
return body
def _determine_etag(filepath):
with open(filepath) as fh:
content = fh.read()
return md5(content.encode("utf-8")).hexdigest()
def _determine_filepath(environ):
uri = environ.get("PATH_INFO", "")
filename = uri[1:] if uri.startswith("/") else uri
if len(filename) == 0:
filename = "index.html" # NB: TiddlyWki-specific
return os.path.abspath(os.path.join(ROOT_DIR, filename))
if __name__ == "__main__":
from wsgiref.simple_server import make_server
host = "localhost"
port = 8080
srv = make_server(host, port, handler)
print("→ http://%s:%s" % (host, port))
srv.serve_forever()
@Dialga
Copy link

Dialga commented Jan 23, 2021

I searched for it on gist.
I was looking for a tiddlywiki server that behaves like the nodejs version, which saves individual tiddlers, rather than saving the whole file each time.
If this script is in the cwd then it would be accessible at localhost:8080/webdav.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment