-
Notifications
You must be signed in to change notification settings - Fork 262
Expand file tree
/
Copy pathserve.py
More file actions
75 lines (64 loc) · 2.66 KB
/
Copy pathserve.py
File metadata and controls
75 lines (64 loc) · 2.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import os
from http.server import SimpleHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, unquote
import urllib
from http import HTTPStatus
BOOK_SITE = os.path.abspath('./.lake/build/literate-html')
DOCS_SITE = os.path.abspath('./.lake/build/doc')
class CustomHTTPRequestHandler(SimpleHTTPRequestHandler):
# Avoid spurious error messages from /favicon.ico
def do_GET(self):
if self.path == '/favicon.ico':
self.send_response(204)
self.end_headers()
return
elif self.path in ('/', '/analysis'):
self.send_response(301)
self.send_header('Location', '/analysis/')
self.end_headers()
return
super().do_GET()
def translate_path(self, path):
# Prevent query strings from being treated as file paths
parsed = urlparse(path)
path = parsed.path
path = unquote(path)
# Serve /analysis/docs and /analysis/docs/* from DOCS_SITE
if path == '/analysis/docs' or path.startswith('/analysis/docs/'):
# HACK: double-slashes are being generated by JS
path = path.replace('//', '/')
rel_path = path[len('/analysis/docs'):].lstrip('/')
return self._join_under(DOCS_SITE, rel_path)
# Serve /analysis/* from BOOK_SITE
elif path.startswith('/analysis/'):
rel_path = path[len('/analysis/'):]
return self._join_under(BOOK_SITE, rel_path)
# Otherwise, serve nothing (return a non-existent path → 404)
else:
return os.path.join(BOOK_SITE, '.missing')
@staticmethod
def _join_under(root, rel_path):
"""Join rel_path under root, rejecting path traversal via .. components."""
root = os.path.abspath(root)
candidate = os.path.abspath(os.path.join(root, rel_path))
try:
if os.path.commonpath([root, candidate]) != root:
return os.path.join(root, '.path-rejected')
except ValueError:
return os.path.join(root, '.path-rejected')
return candidate
if __name__ == '__main__':
import argparse
import contextlib
parser = argparse.ArgumentParser()
parser.add_argument('port', default=8000, type=int, nargs='?',
help='bind to this port '
'(default: %(default)s)')
args = parser.parse_args()
PORT = args.port
handler = CustomHTTPRequestHandler
with HTTPServer(("", PORT), handler) as httpd:
print(f"Serving at http://localhost:{PORT}/analysis/")
print(f"/analysis: {BOOK_SITE}")
print(f"/analysis/docs: {DOCS_SITE}")
httpd.serve_forever()