Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
Use PYONDecoder instead of eval()
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoffland committed Sep 22, 2020
1 parent ec5852b commit 9b619ae
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 1 deletion.
4 changes: 3 additions & 1 deletion fah/Connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
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
Expand Down Expand Up @@ -204,7 +206,7 @@ def queue_command(self, command):

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()
Expand Down
216 changes: 216 additions & 0 deletions fah/util/PYONDecoder.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions fah/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from EntryValidator import *
from PasswordValidator import *
from OrderedDict import *
from PYONDecoder import *


def parse_bool(x):
Expand Down

0 comments on commit 9b619ae

Please sign in to comment.