diff -r 4000de2dcd24 Lib/gettext.py --- a/Lib/gettext.py Thu Nov 03 16:21:11 2016 -0700 +++ b/Lib/gettext.py Fri Nov 04 14:43:27 2016 +0800 @@ -59,56 +59,65 @@ _default_localedir = os.path.join(sys.base_prefix, 'share', 'locale') - def c2py(plural): """Gets a C expression as used in PO files for plural forms and returns a Python lambda function that implements an equivalent expression. """ - # Security check, allow only the "n" identifier - import token, tokenize - tokens = tokenize.generate_tokens(io.StringIO(plural).readline) - try: - danger = [x for x in tokens if x[0] == token.NAME and x[1] != 'n'] - except tokenize.TokenError: - raise ValueError('plural forms expression error, maybe unbalanced parenthesis') - else: - if danger: - raise ValueError('plural forms expression could be dangerous') - # Replace some C operators by their Python equivalents - plural = plural.replace('&&', ' and ') - plural = plural.replace('||', ' or ') - - expr = re.compile(r'\!([^=])') - plural = expr.sub(' not \\1', plural) + _TOKEN_PATTERN = re.compile(r""" + (?P[1-9][0-9]*|0) | # decimal number + (?P[ \t]) | # space and horizontal tab + (?Pn) | # only n is allowed + (?P[()]) | + (?P[-*/%+?:]|[>.) # invalid token + """, re.VERBOSE) # Regular expression and replacement function used to transform # "a?b:c" to "b if a else c". expr = re.compile(r'(.*?)\?(.*?):(.*)') def repl(x): - return "(%s if %s else %s)" % (x.group(2), x.group(1), - expr.sub(repl, x.group(3))) + return "({} if {} else {})".format(x.group(2), x.group(1), + expr.sub(repl, x.group(3))) - # Code to transform the plural expression, taking care of parentheses stack = [''] - for c in plural: - if c == '(': - stack.append('') - elif c == ')': - if len(stack) == 1: - # Actually, we never reach this code, because unbalanced - # parentheses get caught in the security check at the - # beginning. - raise ValueError('unbalanced parenthesis in plural form') - s = expr.sub(repl, stack.pop()) - stack[-1] += '(%s)' % s + for mo in re.finditer(_TOKEN_PATTERN, plural): + kind = mo.lastgroup + value = mo.group(kind) + if kind == 'NUMBER' or kind == 'IDENTIFIER': + stack[-1] += value + elif kind == 'WHITESPACE': + continue + elif kind == 'PARENTHESIS': + if value == '(': + stack.append('') + else: + if len(stack) == 1: + raise ValueError('unbalanced parenthesis in plural form') + s = expr.sub(repl, stack.pop()) + stack[-1] += '({})'.format(s) + elif kind == 'OPERATOR': + if value == '&&': + stack[-1] += ' and ' + elif value == '||': + stack[-1] += ' or ' + elif value == '!': + stack[-1] += ' not ' + else: + stack[-1] += value else: - stack[-1] += c - plural = expr.sub(repl, stack.pop()) + raise ValueError('invalid token in plural form: {}'.format(value)) - return eval('lambda n: int(%s)' % plural) + if len(stack) > 1: + raise ValueError('unbalanced parenthesis in plural form') + plural = expr.sub(repl, stack.pop()) + try: + func = eval('lambda n: int({})'.format(plural)) + except SyntaxError: + raise ValueError('invalid plural form') + return func def _expand_lang(loc): loc = locale.normalize(loc)