safePython.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # File : safePython.py
  4. # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
  5. # Date : 2022/8/28
  6. import io
  7. import tokenize
  8. from func_timeout import func_set_timeout
  9. from func_timeout.exceptions import FunctionTimedOut
  10. from urllib.parse import urljoin, quote, unquote
  11. import requests
  12. import time
  13. import json
  14. import re
  15. from lxml import etree
  16. import datetime
  17. import base64
  18. from utils.log import logger
  19. time_out_sec = 8 # 安全执行python代码超时
  20. class my_exception(Exception):
  21. def __init__(self, message):
  22. self.message = message
  23. def __str__(self):
  24. message = f'函数执行超时: "{self.message}"'
  25. return message
  26. @func_set_timeout(time_out_sec)
  27. def excute(*args):
  28. exec(*args)
  29. def check_unsafe_attributes(string):
  30. """
  31. 安全检测需要exec执行的python代码
  32. :param string:
  33. :return:
  34. """
  35. g = tokenize.tokenize(io.BytesIO(string.encode('utf-8')).readline)
  36. pre_op = ''
  37. for toktype, tokval, _, _, _ in g:
  38. if toktype == tokenize.NAME and pre_op == '.' and tokval.startswith('_'):
  39. attr = tokval
  40. msg = "access to attribute '{0}' is unsafe.".format(attr)
  41. raise AttributeError(msg)
  42. elif toktype == tokenize.OP:
  43. pre_op = tokval
  44. DEFAULT_PYTHON_CODE = """# 可用内置环境变量:
  45. # - log: log(message): 打印日志功能
  46. # - error: 弹出用户错误的弹窗
  47. # 返回变量值: result = {...}\n\n
  48. zyw_lists = env['hikerule.zyw.list'].with_context(active_test=True).sudo().search(
  49. [('option', '=', 'zy'), ('cate_id.name', '!=', '18+'),('cate_id.is_bad', '!=', True)])
  50. result = env['hikerule.zyw.list2data.wizard'].sudo().get_publish_value(zyw_lists)
  51. """
  52. def safe_eval(code: str = '', localdict: dict = None):
  53. code = code.strip()
  54. logger.info('code:' + code)
  55. if not code:
  56. return {}
  57. if localdict is None:
  58. localdict = {}
  59. builtins = __builtins__
  60. builtins = dict(builtins).copy()
  61. for key in ['__import__', 'eval', 'exec', 'globals', 'dir', 'copyright', 'open', 'quit']:
  62. del builtins[key] # 删除不安全的关键字
  63. # print(builtins)
  64. global_dict = {'__builtins__': builtins,
  65. 'requests': requests, 'urljoin': urljoin, 'quote': quote, 'unquote': unquote,
  66. 'log': logger.info, 'json': json, 'print': print,
  67. 're': re, 'etree': etree, 'time': time, 'datetime': datetime, 'base64': base64
  68. } # 禁用内置函数,不允许导入包
  69. try:
  70. check_unsafe_attributes(code)
  71. # 待解决windows下运行超时的问题
  72. try:
  73. # excute(to_run_code, global_dict, localdict)
  74. excute(code, global_dict, localdict)
  75. return localdict
  76. except FunctionTimedOut:
  77. raise my_exception(f'safe_eval运行时间超过{time_out_sec}秒,疑似死循环,已被系统切断')
  78. except Exception as e:
  79. ret = f'执行报错:{e}'
  80. logger.info(ret)
  81. return ret
  82. class safePython:
  83. def __init__(self, name, code):
  84. self.name = name or '未定义'
  85. self.code = code
  86. def action_task_exec(self, call=None, params=None):
  87. """
  88. 接口调用执行函数
  89. :return:
  90. """
  91. if not params:
  92. params = []
  93. builtins = __builtins__
  94. builtins = dict(builtins).copy()
  95. for key in ['__import__', 'eval', 'exec', 'globals', 'dir', 'copyright', 'open', 'quit']:
  96. del builtins[key] # 删除不安全的关键字
  97. # print(builtins)
  98. global_dict = {'__builtins__': builtins,
  99. 'requests': requests, 'urljoin': urljoin, 'quote': quote, 'unquote': unquote,
  100. 'log': logger.info, 'json': json, 'print': print,
  101. 're': re, 'etree': etree, 'time': time, 'datetime': datetime, 'base64': base64
  102. } # 禁用内置函数,不允许导入包
  103. try:
  104. check_unsafe_attributes(self.code)
  105. localdict = {'result': None}
  106. # 待解决windows下运行超时的问题
  107. base_code = self.code.strip()
  108. if call:
  109. logger.info(f'开始执行:{call}')
  110. try:
  111. # excute(to_run_code, global_dict, localdict)
  112. excute(base_code, global_dict, localdict)
  113. run = localdict.get(call)
  114. if run:
  115. localdict['result'] = run(*params)
  116. except FunctionTimedOut:
  117. raise my_exception(f'函数[{self.name}]运行时间超过{time_out_sec}秒,疑似死循环,已被系统切断')
  118. except Exception as e:
  119. ret = f'执行报错:{e}'
  120. logger.info(ret)
  121. return ret
  122. else:
  123. # print(global_dict)
  124. # print(localdict)
  125. ret = localdict['result']
  126. return ret