/wxgeometrie/pylib/securite.py
Python | 184 lines | 110 code | 25 blank | 49 comment | 8 complexity | 6463db16c46bef56cafe5383e9ac8cf4 MD5 | raw file
- # -*- coding: utf-8 -*-
- ##########################################################################
- #
- # Gestion de la securite
- #
- ##########################################################################
- # WxGeometrie
- # Dynamic geometry, graph plotter, and more for french mathematic teachers.
- # Copyright (C) 2005-2013 Nicolas Pourcelot
- #
- # 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 2 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, write to the Free Software
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- import keyword, re, types
- import math # pour avoir droit aux fonctions mathematiques dans eval_restricted par defaut.
- import random as module_random# idem
- from ..pylib import advanced_split
- from .. import param
- def _avertissement(*arg, **kw):
- print("Instruction interdite en mode securisé.")
- #fonctions_interdites = ["eval", "compile", "execfile", "file", "open", "write", "getattr", "setattr"]
- liste_blanche = set(('False', 'None', 'True', 'abs', 'all', 'any', 'bool', 'callable', 'chr', 'close', \
- 'coerce', 'complex', 'dict', 'divmod', 'enumerate', 'filter', 'float', 'frozenset', 'globals', 'hash', 'hex', \
- 'id', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max', 'min', 'object', 'oct', \
- 'ord', 'pow', 'range', 'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'str', 'sum', \
- 'tuple', 'type', 'zip'\
- 'IndexError', 'SyntaxError', 'NameError', 'StandardError', 'UnicodeDecodeError', 'RuntimeWarning', \
- 'Warning', 'FloatingPointError', 'FutureWarning', 'ImportWarning', 'TypeError', 'KeyboardInterrupt', \
- 'UserWarning', 'SystemError', 'BaseException', 'RuntimeError', 'GeneratorExit', 'StopIteration', \
- 'LookupError', 'UnicodeError', 'IndentationError', 'Exception', 'UnicodeTranslateError', 'UnicodeEncodeError', \
- 'PendingDeprecationWarning', 'ArithmeticError', 'MemoryError', 'ImportError', 'KeyError', 'SyntaxWarning', \
- 'EnvironmentError', 'OSError', 'DeprecationWarning', 'UnicodeWarning', 'ValueError', 'NotImplemented', \
- 'TabError', 'ZeroDivisionError', 'ReferenceError', 'AssertionError', 'UnboundLocalError', 'NotImplementedError', \
- 'AttributeError', 'OverflowError', 'WindowsError'))
- liste_noire = set(__builtins__.keys())
- liste_noire.difference_update(liste_blanche)
- dictionnaire_builtins = {}.fromkeys(list(liste_noire), _avertissement)
- dictionnaire_modules = {}.fromkeys([key for key, objet in globals().items() if type(objet) == types.ModuleType])
- ajouts_math = {"pi": math.pi, "e": math.e, "i": 1j}
- for key, obj in math.__dict__.items():
- if type(obj) == types.BuiltinFunctionType:
- ajouts_math[key] = obj
- for key, obj in module_random.__dict__.items():
- if type(obj) == types.BuiltinFunctionType:
- ajouts_math[key] = obj
- ##keywords = ('and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
- ### mots clés qui peuvent être utilisés
- ##keywords_autorises = set('and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'if', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
- keywords = set(keyword.kwlist)
- # mots clés dont il faut s'assurer qu'ils ne puissent PAS être utilisés
- keywords_interdits = set(('exec', 'global', 'import')) # attention à global !
- # mots clés qui peuvent être utilisés
- keywords_autorises = keywords.difference(keywords_interdits)
- ### ici, il s'agit de mots clés qui doivent rester en début de ligne
- ### (plus précisément, il est impossible de rajouter devant une affectation, style 'variable = ').
- ##keywords_debut_ligne = ('break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'pass', 'for', 'if', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
- # Mots clés devant lesquels il est possible de rajouter une affectation, style 'variable = ...'.
- keywords_affectables = set(('lambda', 'not'))
- # Mots clés qui ne sont jamais en début de ligne.
- keywords_milieu = set(('or', 'and', 'as', 'in'))
- # Les autres, qui doivent rester en tête de ligne.
- keywords_non_affectables = keywords.difference(keywords_affectables).difference(keywords_milieu)
- def keywords_interdits_presents(chaine):
- return bool(re.search(r'(?<!\w)(' + '|'.join(keywords_interdits) + ')(?!\w)', chaine))
- def expression_affectable(chaine):
- return not bool(re.match('[ ]*(' + '|'.join(keywords_non_affectables) + ')(?!\w)', chaine))
- # Exemple d'usage :
- # dico = {"pi":3.1415926535897931, "e":2.7182818284590451} # dictionnaire local
- # dico.update(securite.dictionnaire_builtins)
- # exec(string, dico)
- def eval_restricted(s, dico_perso = None):
- """eval_restricted(s) évalue s dans un contexte vierge et légèrement sécurisé.
- Toutes les fonctions disponibles par défaut sont filtrées.
- Note: eval_restricted est le plus securisée possible, mais contrairement
- à eval_safe, il utilise la fonction eval ; il existe donc forcément des
- failles de sécurité.
- """
- dico = {"rand": module_random.random}
- dico.update(dictionnaire_builtins) # supprime certaines fonctions par defaut (en les redefinissant)
- dico.update(ajouts_math) # ajoute les fonctions mathematiques usuelles
- if dico_perso is not None:
- dico.update(dico_perso)
- for module in ("os", "sys", "securite"):
- dico.pop(module, None)
- return eval(s, dico)
- def eval_safe(s):
- """eval_safe(repr(x)) retourne x pour les types les plus usuels
- (int, str, float, bool, None, list, tuple, dict.)
- Mais aucune évaluation n'est faite, ce qui évite d'éxécuter un code dangereux.
- Le type de s est detecté, et la transformation appropriée appliquée.
- NB1: eval_safe n'est pas destiné à remplacer eval :
- - il est relativement lent,
- - le nombre de types supportés est volontairement réduit,
- - il n'est pas souple (eval_safe("2-3") renvoie une erreur).
- La fonction eval_safe est orientée uniquement securité.
- NB2: eval_safe est récursif (il peut lire des listes de tuples de ...).
- NB3: eval_safe est parfaitement securisé, car il ne fait (presque) jamais appel à une instruction eval.
- Contrairement à la fonction eval_restricted, qui est 'légèrement' securisée."""
- s = s.strip()
- def filtre(chaine, caracteres):
- return re.match("^[%s]*$" %caracteres, chaine) is not None
- if filtre(s, "-0-9") or s[-1] == "L": # entier
- return int(s)
- if filtre(s, "-+.e0-9"): # flottant
- return float(s)
- if len(s) > 1 and s[0] == s[-1] and s[0] in ("'", '"'): # chaine
- # s est évalué dans un contexte parfaitement vierge.
- return eval(s, {"__builtins__": {}}, {"__builtins__": {}})
- if len(s) > 2 and s[0] == "u" and s[1] == s[-1] and s[1] in ("'", '"'): #unicode
- return eval(s, {"__builtins__": {}}, {"__builtins__": {}}) # idem
- if s == "None":
- return None
- if s == "True":
- # Eviter return eval(s), car True peut etre redefini. Exemple : True = False (!)
- return True
- if s == "False":
- return False
- if s[0] in ("(", "["): # tuple ou liste
- liste = [eval_safe(t) for t in advanced_split(s[1:-1], ",") if t]
- if s[0] == "(":
- liste = tuple(liste)
- return liste
- if s[0] == "{": # dictionnaire
- dict = {}
- liste = [advanced_split(t, ":") for t in advanced_split(s[1:-1], ",") if t]
- for key, val in liste:
- dict[eval_safe(key)] = eval_safe(val)
- return dict
- raise TypeError("%s: types int, str, float, bool, ou None requis (ou liste ou tuple de ces types)" % repr(s))