58
58
self.remainder = ''
60
def _trim_incomplete_final(self, stuff):
61
'''Trim an incomplete UTF-8 character from the end of a string.
62
Returns (trimmed_string, count_of_trimmed_bytes).
64
tokill = incomplete_utf8_sequence(stuff)
66
return (stuff, tokill)
68
return (stuff[:-tokill], tokill)
60
70
def write(self, stuff):
71
# print will only give a non-file a unicode or str. There's no way
72
# to convince it to encode unicodes, so we have to do it ourselves.
73
# Yay for file special-cases (fileobject.c, PyFile_WriteObject).
74
# If somebody wants to write some other object to here, they do it
76
if isinstance(stuff, unicode):
77
stuff = stuff.encode('utf-8')
61
78
self.remainder = self.remainder + stuff
63
80
# if there's less than 128 bytes, buffer
67
84
# if there's lots, then send it in 1/2K blocks
68
85
while len(self.remainder) > 512:
69
blk = self.remainder[0:512]
70
self.cmdQ.put({"output":blk})
86
# We send things as Unicode inside JSON, so we must only send
87
# complete UTF-8 characters.
88
(blk, count) = self._trim_incomplete_final(self.remainder[:512])
89
self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
72
91
ln = self.lineQ.get()
73
self.remainder = self.remainder[512:]
92
self.remainder = self.remainder[512 - count:]
75
94
# Finally, split the remainder up into lines, and ship all the
76
95
# completed lines off to the server.
91
110
if len(self.remainder) > 0:
92
self.cmdQ.put({"output":self.remainder})
111
(out, count) = self._trim_incomplete_final(self.remainder)
112
self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
94
114
ln = self.lineQ.get()
115
# Leave incomplete characters in the buffer.
116
# Yes, this does mean that an incomplete character will be left
117
# off the end, but we discussed this and it was deemed best.
118
self.remainder = self.remainder[len(self.remainder)-count:]
96
119
if 'interrupt' in ln:
129
152
sys.stdin = self.webio
130
153
sys.stdout = self.webio
131
154
sys.stderr = self.webio
132
res = eval(cmd, self.globs)
155
# We don't expect a return value - 'single' symbol prints it.
156
eval(cmd, self.globs)
133
157
self.webio.flush()
134
self.cmdQ.put({"okay":res})
158
self.cmdQ.put({"okay": None})
135
159
self.curr_cmd = ''
136
except Exception, exc:
161
tb = format_exc_start(start=1)
137
162
self.webio.flush()
138
exc_classname = exc.__class__.__name__
139
self.cmdQ.put({"exc": exc_classname + ": " + str(exc)})
163
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
140
164
self.curr_cmd = ''
144
168
self.globs['__builtins__'] = globals()['__builtins__']
145
169
self.curr_cmd = ''
146
compiler = codeop.CommandCompiler()
149
172
ln = self.lineQ.get()
161
184
self.cmdQ.put({"more":None})
163
186
self.execCmd(cmd)
164
except Exception, exc:
188
tb = format_exc_start(start=3)
189
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
165
190
self.webio.flush()
166
self.cmdQ.put({"exc":str(exc)})
167
191
self.curr_cmd = ''
168
192
if 'block' in ln:
169
193
# throw away a partial command.
171
195
cmd = compile(ln['block'], "<web session>", 'exec');
172
196
self.execCmd(cmd)
173
except Exception, exc:
198
tb = format_exc_start(start=1)
174
199
self.webio.flush()
175
self.cmdQ.put({"exc":str(exc)})
200
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
176
201
self.curr_cmd = ''
198
224
def initializer():
199
225
interpThread.setDaemon(True)
200
226
interpThread.start()
227
signal.signal(signal.SIGXCPU, sig_handler)
230
def sig_handler(signum, frame):
231
"""Handles response from signals"""
233
if signum == signal.SIGXCPU:
234
terminate = "CPU Time Limit Exceeded"
203
236
def dispatch_msg(msg):
238
if msg['cmd'] == 'restart':
239
terminate = "User requested console be reset"
241
raise common.chat.Terminate({"restart":terminate})
205
243
lineQ.put({msg['cmd']:msg['text']})
245
raise common.chat.Terminate({"restart":terminate})
206
246
return cmdQ.get()
248
def format_exc_start(start=0):
249
etype, value, tb = sys.exc_info()
250
tbbits = traceback.extract_tb(tb)[start:]
251
list = ['Traceback (most recent call last):\n']
252
list = list + traceback.format_list(tbbits)
253
list = list + traceback.format_exception_only(etype, value)
256
def incomplete_utf8_sequence(byteseq):
259
Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
260
the end of the string which comprise an incomplete UTF-8 character
263
If the string is empty or ends with a complete character OR INVALID
265
Otherwise, returns 1-3 indicating the number of bytes in the final
266
incomplete (but valid) character sequence.
268
Does not check any bytes before the final sequence for correctness.
270
>>> incomplete_utf8_sequence("")
272
>>> incomplete_utf8_sequence("xy")
274
>>> incomplete_utf8_sequence("xy\xc3\xbc")
276
>>> incomplete_utf8_sequence("\xc3")
278
>>> incomplete_utf8_sequence("\xbc\xc3")
280
>>> incomplete_utf8_sequence("xy\xbc\xc3")
282
>>> incomplete_utf8_sequence("xy\xe0\xa0")
284
>>> incomplete_utf8_sequence("xy\xf4")
286
>>> incomplete_utf8_sequence("xy\xf4\x8f")
288
>>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
293
for b in byteseq[::-1]:
297
# 0xxxxxxx (single-byte character)
300
elif b & 0xc0 == 0x80:
301
# 10xxxxxx (subsequent byte)
303
elif b & 0xe0 == 0xc0:
304
# 110xxxxx (start of 2-byte sequence)
307
elif b & 0xf0 == 0xe0:
308
# 1110xxxx (start of 3-byte sequence)
311
elif b & 0xf8 == 0xf0:
312
# 11110xxx (start of 4-byte sequence)
320
# Seen too many "subsequent bytes", invalid
324
# We never saw a "first byte", invalid
327
# We now know expect and count
329
# Complete, or we saw an invalid sequence
208
335
if __name__ == "__main__":
209
336
port = int(sys.argv[1])
210
337
magic = sys.argv[2]
339
# Sanitise the Enviroment
341
os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
211
343
if len(sys.argv) >= 4:
213
345
os.chdir(sys.argv[3])
214
# Make python's search path follow the cwd
216
346
os.environ['HOME'] = sys.argv[3]
348
# Make python's search path follow the cwd
218
351
common.chat.start_server(port, magic, True, dispatch_msg, initializer)