62
58
self.remainder = ''
64
def _trim_incomplete_final(self, stuff):
65
'''Trim an incomplete UTF-8 character from the end of a string.
66
Returns (trimmed_string, count_of_trimmed_bytes).
68
tokill = incomplete_utf8_sequence(stuff)
70
return (stuff, tokill)
72
return (stuff[:-tokill], tokill)
74
60
def write(self, stuff):
75
# print will only give a non-file a unicode or str. There's no way
76
# to convince it to encode unicodes, so we have to do it ourselves.
77
# Yay for file special-cases (fileobject.c, PyFile_WriteObject).
78
# If somebody wants to write some other object to here, they do it
80
if isinstance(stuff, unicode):
81
stuff = stuff.encode('utf-8')
82
61
self.remainder = self.remainder + stuff
84
63
# if there's less than 128 bytes, buffer
88
67
# if there's lots, then send it in 1/2K blocks
89
68
while len(self.remainder) > 512:
90
# We send things as Unicode inside JSON, so we must only send
91
# complete UTF-8 characters.
92
(blk, count) = self._trim_incomplete_final(self.remainder[:512])
93
self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
69
blk = self.remainder[0:512]
70
self.cmdQ.put({"output":blk})
95
72
ln = self.lineQ.get()
96
self.remainder = self.remainder[512 - count:]
73
self.remainder = self.remainder[512:]
98
75
# Finally, split the remainder up into lines, and ship all the
99
76
# completed lines off to the server.
114
91
if len(self.remainder) > 0:
115
(out, count) = self._trim_incomplete_final(self.remainder)
116
self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
92
self.cmdQ.put({"output":self.remainder})
118
94
ln = self.lineQ.get()
119
# Leave incomplete characters in the buffer.
120
# Yes, this does mean that an incomplete character will be left
121
# off the end, but we discussed this and it was deemed best.
122
self.remainder = self.remainder[len(self.remainder)-count:]
123
96
if 'interrupt' in ln:
156
129
sys.stdin = self.webio
157
130
sys.stdout = self.webio
158
131
sys.stderr = self.webio
159
# We don't expect a return value - 'single' symbol prints it.
160
eval(cmd, self.globs)
132
res = eval(cmd, self.globs)
161
133
self.webio.flush()
162
self.cmdQ.put({"okay": None})
134
self.cmdQ.put({"okay":res})
163
135
self.curr_cmd = ''
165
tb = format_exc_start(start=1)
136
except Exception, exc:
166
137
self.webio.flush()
167
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
138
exc_classname = exc.__class__.__name__
139
self.cmdQ.put({"exc": exc_classname + ": " + str(exc)})
168
140
self.curr_cmd = ''
172
144
self.globs['__builtins__'] = globals()['__builtins__']
173
145
self.curr_cmd = ''
146
compiler = codeop.CommandCompiler()
176
149
ln = self.lineQ.get()
181
154
self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
183
cmd = codeop.compile_command(self.curr_cmd, '<web session>')
156
cmd = compiler(self.curr_cmd)
185
158
# The command was incomplete,
186
159
# so send back a None, so the
188
161
self.cmdQ.put({"more":None})
190
163
self.execCmd(cmd)
192
tb = format_exc_start(start=3)
193
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
164
except Exception, exc:
194
165
self.webio.flush()
166
self.cmdQ.put({"exc":str(exc)})
195
167
self.curr_cmd = ''
196
168
if 'block' in ln:
197
169
# throw away a partial command.
199
171
cmd = compile(ln['block'], "<web session>", 'exec');
200
172
self.execCmd(cmd)
202
tb = format_exc_start(start=1)
173
except Exception, exc:
203
174
self.webio.flush()
204
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
175
self.cmdQ.put({"exc":str(exc)})
205
176
self.curr_cmd = ''
234
205
lineQ.put({msg['cmd']:msg['text']})
235
206
return cmdQ.get()
237
def format_exc_start(start=0):
238
etype, value, tb = sys.exc_info()
239
tbbits = traceback.extract_tb(tb)[start:]
240
list = ['Traceback (most recent call last):\n']
241
list = list + traceback.format_list(tbbits)
242
list = list + traceback.format_exception_only(etype, value)
245
def incomplete_utf8_sequence(byteseq):
248
Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
249
the end of the string which comprise an incomplete UTF-8 character
252
If the string is empty or ends with a complete character OR INVALID
254
Otherwise, returns 1-3 indicating the number of bytes in the final
255
incomplete (but valid) character sequence.
257
Does not check any bytes before the final sequence for correctness.
259
>>> incomplete_utf8_sequence("")
261
>>> incomplete_utf8_sequence("xy")
263
>>> incomplete_utf8_sequence("xy\xc3\xbc")
265
>>> incomplete_utf8_sequence("\xc3")
267
>>> incomplete_utf8_sequence("\xbc\xc3")
269
>>> incomplete_utf8_sequence("xy\xbc\xc3")
271
>>> incomplete_utf8_sequence("xy\xe0\xa0")
273
>>> incomplete_utf8_sequence("xy\xf4")
275
>>> incomplete_utf8_sequence("xy\xf4\x8f")
277
>>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
282
for b in byteseq[::-1]:
286
# 0xxxxxxx (single-byte character)
289
elif b & 0xc0 == 0x80:
290
# 10xxxxxx (subsequent byte)
292
elif b & 0xe0 == 0xc0:
293
# 110xxxxx (start of 2-byte sequence)
296
elif b & 0xf0 == 0xe0:
297
# 1110xxxx (start of 3-byte sequence)
300
elif b & 0xf8 == 0xf0:
301
# 11110xxx (start of 4-byte sequence)
309
# Seen too many "subsequent bytes", invalid
313
# We never saw a "first byte", invalid
316
# We now know expect and count
318
# Complete, or we saw an invalid sequence
324
208
if __name__ == "__main__":
325
209
port = int(sys.argv[1])
326
210
magic = sys.argv[2]