Use PYONDecoder instead of eval()

This commit is contained in:
Joseph Coffland 2020-09-21 22:37:40 -07:00
parent ec5852b176
commit 9b619ae644
3 changed files with 220 additions and 1 deletions

View File

@ -27,8 +27,10 @@ import errno
import time
import sys
import traceback
import json
from fah.util import OrderedDict
from fah.util import PYONDecoder
if sys.platform == 'win32':
from ctypes import windll
@ -204,7 +206,7 @@ class Connection:
def parse_message(self, version, type, data):
try:
msg = eval(data, {}, {})
msg = json.loads(data, cls = PYONDecoder)
#if debug: print 'MSG:', type, msg
self.messages.append((version, type, msg))
self.last_message = time.time()

216
fah/util/PYONDecoder.py Normal file
View File

@ -0,0 +1,216 @@
################################################################################
# #
# Folding@Home Client Control (FAHControl) #
# Copyright (C) 2016-2020 foldingathome.org #
# Copyright (C) 2010-2016 Stanford University #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
################################################################################
import re
import json
import sys
NUMBER_RE = re.compile(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
(re.VERBOSE | re.MULTILINE | re.DOTALL))
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
BACKSLASH = {
'"': u'"', '\\': u'\\', '/': u'/',
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
}
DEFAULT_ENCODING = "utf-8"
def linecol(doc, pos):
lineno = doc.count('\n', 0, pos) + 1
if lineno == 1:
colno = pos + 1
else:
colno = pos - doc.rindex('\n', 0, pos)
return lineno, colno
def errmsg(msg, doc, pos, end=None):
# Note that this function is called from _json
lineno, colno = linecol(doc, pos)
if end is None:
fmt = '{0}: line {1} column {2} (char {3})'
return fmt.format(msg, lineno, colno, pos)
endlineno, endcolno = linecol(doc, end)
fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
def _decode_uXXXX(s, pos):
esc = s[pos + 1:pos + 5]
if len(esc) == 4 and esc[1] not in 'xX':
try:
return int(esc, 16)
except ValueError: pass
msg = "Invalid \\uXXXX escape"
raise ValueError(errmsg(msg, s, pos))
def pyon_scanstring(s, end, encoding = None, strict = True,
_b = BACKSLASH, _m = STRINGCHUNK.match):
"""Scan the string s for a JSON string. End is the index of the
character in s after the quote that started the JSON string.
Unescapes all valid JSON string escape sequences and raises ValueError
on attempt to decode an invalid string. If strict is False then literal
control characters are allowed in the string.
Returns a tuple of the decoded string and the index of the character in s
after the end quote."""
if encoding is None: encoding = DEFAULT_ENCODING
chunks = []
_append = chunks.append
begin = end - 1
while True:
chunk = _m(s, end)
if chunk is None:
raise ValueError(
errmsg("Unterminated string starting at", s, begin))
end = chunk.end()
content, terminator = chunk.groups()
# Content is contains zero or more unescaped string characters
if content:
if not isinstance(content, unicode):
content = unicode(content, encoding)
_append(content)
# Terminator is the end of string, a literal control character,
# or a backslash denoting that an escape sequence follows
if terminator == '"': break
elif terminator != '\\':
if strict:
msg = "Invalid control character {0!r} at".format(terminator)
raise ValueError(errmsg(msg, s, end))
else:
_append(terminator)
continue
try:
esc = s[end]
except IndexError:
raise ValueError(errmsg("Unterminated string starting at", s, begin))
# If not a unicode escape sequence, must be in the lookup table
if esc != 'u' and esc != 'x':
try:
char = _b[esc]
except KeyError:
msg = "Invalid \\escape: " + repr(esc)
raise ValueError(errmsg(msg, s, end))
end += 1
elif esc == 'x':
# Hex escape sequence
try:
code = s[end + 1: end + 3]
char = code.decode('hex')
except:
raise ValueError(errmsg('Invalid \\escape: ' + repr(code), s, end))
end += 3
else:
# Unicode escape sequence
uni = _decode_uXXXX(s, end)
end += 5
# Check for surrogate pair on UCS-4 systems
if sys.maxunicode > 65535 and \
0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u':
uni2 = _decode_uXXXX(s, end + 1)
if 0xdc00 <= uni2 <= 0xdfff:
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
end += 6
char = unichr(uni)
# Append the unescaped character
_append(char)
return u''.join(chunks), end
def make_pyon_scanner(context):
parse_object = context.parse_object
parse_array = context.parse_array
parse_string = context.parse_string
match_number = NUMBER_RE.match
strict = context.strict
parse_float = context.parse_float
parse_int = context.parse_int
parse_constant = context.parse_constant
object_hook = context.object_hook
object_pairs_hook = context.object_pairs_hook
def scan_once(string, idx):
try:
nextchar = string[idx]
except IndexError:
raise StopIteration(idx)
if nextchar == '"': return parse_string(string, idx + 1, 'utf-8', strict)
elif nextchar == '{':
return parse_object((string, idx + 1), 'utf-8', strict, scan_once,
object_hook, object_pairs_hook)
elif nextchar == '[':
return parse_array((string, idx + 1), scan_once)
elif nextchar == 'N' and string[idx:idx + 4] == 'None':
return None, idx + 4
elif nextchar == 'T' and string[idx:idx + 4] == 'True':
return True, idx + 4
elif nextchar == 'F' and string[idx:idx + 5] == 'False':
return False, idx + 5
m = match_number(string, idx)
if m is not None:
integer, frac, exp = m.groups()
if frac or exp:
res = parse_float(integer + (frac or '') + (exp or ''))
else: res = parse_int(integer)
return res, m.end()
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
return parse_constant('NaN'), idx + 3
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
return parse_constant('Infinity'), idx + 8
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
return parse_constant('-Infinity'), idx + 9
else: raise StopIteration(idx)
return scan_once
class PYONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, *args, **kwargs)
self.parse_string = pyon_scanstring
self.scan_once = make_pyon_scanner(self)

View File

@ -29,6 +29,7 @@ from SingleApp import *
from EntryValidator import *
from PasswordValidator import *
from OrderedDict import *
from PYONDecoder import *
def parse_bool(x):