forked from aittalam/PicoGopher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpicogopher.py
124 lines (104 loc) · 4.53 KB
/
picogopher.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
from picopath import PicoPath, invalid_path
import micropython
import gc
import time
class PicoGopher:
def __init__(self, ip, gopher_port):
self._ip = ip
self._gopher_port = gopher_port
async def listener(self, reader, writer):
print("Gopher client connected")
request_line = await reader.readline()
print("Request:", request_line)
selector = request_line.decode().strip()
if '\t' in selector:
writer.write('3Gopher+ selectors unsupported {}.\t\terror.host\t1'.format(selector).encode('US-ASCII', 'replace'))
writer.write('\r\n.\r\n'.encode('US-ASCII', 'replace'))
await writer.drain()
await writer.wait_closed()
print("Client disconnected")
return
if selector:
if not selector.startswith('/'):
selector = '/' + selector
relative = selector[1:]
else:
relative = ""
absolute_path = "/gopher/" + relative
print("Absolute path: {}".format(absolute_path))
if invalid_path(absolute_path):
writer.write('3Error accessing {}.\t\terror.host\t1'.format(selector).encode('US-ASCII', 'replace'))
writer.write('\r\n.\r\n'.encode('US-ASCII', 'replace'))
await writer.drain()
await writer.wait_closed()
print("Client disconnected")
return
elif PicoPath(absolute_path).is_file():
print('Success: file: {}'.format(absolute_path))
# I got some memory errors with 64KB blocks,
# so I am keeping them tiny for now
block_size = 1 * 1024
# preallocate a buffer of block_size to load data into
buf = bytearray(block_size)
with open(absolute_path, 'rb') as inf:
bytes_read = inf.readinto(buf)
while bytes_read == block_size :
try:
writer.write(buf)
except MemoryError as e:
# happens (also) when out_buf gets too
# large, so let us drain and retry
print(f"[w] {e} => will drain and retry")
await writer.drain()
writer.write(buf)
# read next chunk into buf
bytes_read = inf.readinto(buf)
# finally, write the remaining bytes
# (note that there's a chance we'll get
# a MemoryError here too...)
writer.write(buf[:bytes_read])
await writer.drain()
await writer.wait_closed()
else:
absolute_path = absolute_path + "/gophermap"
f = open(absolute_path)
gmap = f.read()
f.close()
txt = gmap.splitlines()
outln = []
for line in txt:
if line:
cols = line.split('\t')
else:
cols = ['i', '/', '-', '0']
if len(cols) == 1:
# translate plain text rows
cols = ['i'+line, '/', '-', '0']
elif len(cols) == 2:
# these are regular tab-separated rows missing fqdn and port
cols.append(self._ip)
cols.append(str(self._gopher_port))
elif len(cols) == 3:
# these are regular tab-separated rows just missing port
cols.append(self._gopher_port)
elif len(cols) > 4:
# these rows should not have so many columns!
cols = cols[:4]
# fix relative paths (if not HTTP URLs)
if cols[0][0] != 'h' and cols[1] and cols[1][0] != '/':
if self._ip == cols[2]:
# if paths are within this domain
# they must be relative
cols[1] = '{}/{}'.format(selector, cols[1])
else:
# if paths point to an external domain
# they must be absolute
cols[1] = '/' + cols[1]
outln.append('\t'.join(cols))
response = '\r\n'.join(outln)
# print(response)
writer.write(response)
writer.write('\r\n.\r\n')
await writer.drain()
await writer.wait_closed()
print("Client disconnected")