-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilesystem_endpoints.py
154 lines (130 loc) · 4.68 KB
/
filesystem_endpoints.py
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""HTTP endpoints definitions for filesystem operations.
"""
from os import path
import os
from filesystem_views import (
FilesystemDirectoryListing,
TextFileEditor,
)
from server import (
APPLICATION_JSON,
APPLICATION_PYTHON,
DELETE,
GET,
POST,
PUT,
TEXT_HTML,
TEXT_PLAIN,
_200,
_303,
_404,
_503,
as_json,
get_file_path_content_type,
route,
send,
)
###############################################################################
# Filesystem
###############################################################################
EDITABLE_CONTENT_TYPES = (
APPLICATION_JSON,
APPLICATION_PYTHON,
TEXT_HTML,
TEXT_PLAIN,
)
# Set the default public filesystem root to "<this-directory>/public".
DEFAULT_PUBLIC_ROOT = path.join(path.dirname(__file__), 'public')
###############################################################################
# Endpoint helpers
###############################################################################
def _fs_GET(public_root, req_path):
"""Handle a filesystem GET request.
"""
fs_path = path.join(public_root, req_path)
if not path.exists(fs_path):
return _404()
# The request path is a directory, return an HTML directory listing.
if path.isdir(fs_path):
return _200(body=FilesystemDirectoryListing(fs_path, req_path))
# The requested path is a file, so return it.
content_type = get_file_path_content_type(fs_path)
return _200(
headers={'content-type': content_type},
body=open(fs_path, 'rb')
)
def _fs_GET_edit(public_root, req_path, create):
fs_path = path.join(public_root, req_path)
if path.exists(fs_path):
text = open(fs_path, 'rb').read().decode('utf-8')
elif not create:
return _404()
else:
text = ''
body = TextFileEditor(req_path, text)
return _200(body=body)
async def _fs_PUT(public_root, req_path, request):
"""Handle a filesystem PUT request.
"""
# TODO - validate the request (e.g. check for avail drive space, whether
# directory already exists with same name, etc.))
if request.headers.get('expect') == '100-continue':
request.writer.write('HTTP/1.1 100 Continue\r\n')
request.writer.write('\r\n')
await request.writer.drain()
# TODO - write to a temporary file and rename to target on success.
MAX_CHUNK_BYTES = 1024
with open(path.join(public_root, req_path), 'wb') as fh:
bytes_remaining = int(request.headers['content-length'])
# First yield from the body if non-empty.
body = request.body
while body:
chunk = body[:MAX_CHUNK_BYTES]
body = body[MAX_CHUNK_BYTES:]
fh.write(chunk)
bytes_remaining -= len(chunk)
# If we need more bytes, receive them from the socket.
while bytes_remaining:
chunk = await request.reader.read(
min(bytes_remaining, MAX_CHUNK_BYTES)
)
fh.write(chunk)
bytes_remaining -= len(chunk)
return _303(location='/_fs/{}'.format(req_path))
def _fs_DELETE(public_root, req_path):
"""Handle a filesystem DELETE request.
"""
fs_path = path.join(public_root, req_path)
if not path.exists(fs_path):
return _404()
os.remove(fs_path)
return _200()
###############################################################################
# Filesystem operation dispatcher
###############################################################################
async def filesystem(request, public_root):
"""Handle filesystem operations.
"""
# Strip any leading slash to prevent path.join() from resolving relative to
# the filesystem root.
req_path = request.path[4:].lstrip('/')
if request.method == 'GET':
if (request.query.get('edit') == '1' and
get_file_path_content_type(req_path) in EDITABLE_CONTENT_TYPES):
create = request.query.get('create') == '1'
return _fs_GET_edit(public_root, req_path, create)
else:
return _fs_GET(public_root, req_path)
elif request.method == 'PUT':
return await _fs_PUT(public_root, req_path, request)
elif request.method == 'DELETE':
return _fs_DELETE(public_root, req_path)
###############################################################################
# Route attacher
###############################################################################
def attach(public_root=DEFAULT_PUBLIC_ROOT):
"""Add a route for the filesystem operation endpoints.
"""
@route('^((/_fs/?)|(/_fs/.+))$', methods=(GET, PUT, DELETE))
async def _filesystem(request):
return await filesystem(request, public_root)