2011-12-18 21 views
17

Trong ngữ cảnh của một ứng dụng phức tạp, tôi cần phải nhập khẩu các tập lệnh 'do người dùng cung cấp'. Lý tưởng nhất, một kịch bản sẽ phảiNhập khẩu một mô-đun python mà không thực sự thực thi nó

def init(): 
    blah 

def execute(): 
    more blah 

def cleanup(): 
    yadda 

vì vậy tôi muốn chỉ

import imp 
fname, path, desc = imp.find_module(userscript) 
foo = imp.load_module(userscript, fname, path, desc) 
foo.init() 

Tuy nhiên, như chúng ta đều biết, kịch bản của người dùng là thực hiện càng sớm càng load_module chạy. Điều đó có nghĩa, một kịch bản có thể được một cái gì đó như thế này:

def init(): 
    blah 

yadda 

nhượng cho những phần yadda được gọi ngay sau khi tôi import kịch bản.

Những gì tôi cần là một cách để:

  1. kiểm tra đầu tiên cho dù nó đã init(), thực hiện() và dọn dẹp()
  2. nếu chúng tồn tại, tất cả là tốt
  3. nếu họ don 't tồn tại, phàn nàn
  4. không chạy bất kỳ mã khác, hoặc ít nhất là không cho đến khi tôi biết không có init()

Thông thường tôi sẽ buộc việc sử dụng các cũ if __name__ == '__main__' lừa, nhưng tôi có ít quyền kiểm soát trên tập lệnh do người dùng cung cấp, vì vậy tôi đang tìm một giải pháp tương đối không đau. Tôi đã thấy tất cả các loại thủ thuật phức tạp, bao gồm phân tích cú pháp kịch bản, nhưng không có gì thực sự đơn giản. Tôi ngạc nhiên vì nó không tồn tại .. hoặc có lẽ tôi không nhận được một cái gì đó.

Cảm ơn.

+0

Tôi chỉ muốn nói với người dùng của bạn sử dụng 'if __name__' vv Nếu họ không làm điều đó, đó chỉ là lookout của họ. –

+0

@DavidHeffernan vâng, đó sẽ là cách thông thường. Nhưng tôi ngạc nhiên không có cách nào * thực tế * để làm điều này mà không cần phải nói chuyện với một con người – lorenzog

+1

Do tính chất của Python, bạn không thể nói những gì trong đó cho đến khi nó được thực hiện. Bạn có thể lấy nó bằng cách sử dụng 'find_module' và sau đó kiểm tra nó theo cách thủ công ... mà vẫn sẽ thiếu sót vì nó có thể lấy mã từ những nơi khác hoặc sử dụng mã lạ như rot13 hoặc những thứ thú vị khác. –

Trả lời

7

nỗ lực của tôi bằng cách sử dụng mô-đun ast:

import ast 

# which syntax elements are allowed at module level? 
whitelist = [ 
    # docstring 
    lambda x: isinstance(x, ast.Expr) \ 
      and isinstance(x.value, ast.Str), 
    # import 
    lambda x: isinstance(x, ast.Import), 
    # class 
    lambda x: isinstance(x, ast.ClassDef), 
    # function 
    lambda x: isinstance(x, ast.FunctionDef), 
] 

def validate(source, required_functions): 
    tree = ast.parse(source) 

    functions = set() 
    required_functions = set(required_functions) 

    for item in tree.body: 
    if isinstance(item, ast.FunctionDef): 
     functions.add(item.name) 
     continue 

    if all(not checker(item) for checker in whitelist): 
     return False 

    # at least the required functions must be there 
    return len(required_functions - functions) == 0 


if __name__ == "__main__": 
    required_funcs = [ "init", "execute", "cleanup" ] 
    with open("/tmp/test.py", "rb") as f: 
    print("yay!" if validate(f.read(), required_funcs) else "d'oh!") 
+0

Tôi thích điều này, nhưng tôi không chắc chắn nếu điều này sẽ làm việc tất cả các thời gian (có nghĩa là: Tôi chắc chắn rằng ai đó sẽ làm việc ra để phá vỡ này;)) –

+0

Gọn gàng, đây là một sử dụng tuyệt vời của AST. Là một bài tập học thuật, làm thế nào chúng ta có thể phá vỡ điều này (vì vậy nó xác nhận một tập tin chạy mã tùy ý khi nhập khẩu)? Cho đến nay, tôi thấy hai cách: các cuộc gọi hàm trong các đối số mặc định ('def f (x = dothings()):') và viết một metaclass để chạy mã khi một lớp được định nghĩa. –

+0

@Thomas K: Chúng tôi có thể khắc phục điều đó bằng cách cấm các lớp và chức năng với các tham số. Nhưng sau đó một lần nữa, có những khả năng khác như nhập khẩu mô-đun tùy chỉnh vv ... Tôi không nghĩ rằng bạn thực sự có thể nhận được tốt hơn nhiều. –

-1

Một giải pháp rất đơn giản có thể là để kiểm tra các ký tự đầu tiên của mỗi dòng mã: Các phép chỉ nên là:

  • def init():
  • def execute():
  • def cleanup():
  • dòng bắt đầu với 4 không gian
  • [tùy chọn]: các dòng bắt đầu bằng #

này rất thô sơ nhưng nó đáp ứng yêu cầu của bạn ...

Cập nhật: Sau một giây dù về nó tôi nhận ra rằng nó không phải là dễ dàng như vậy sau khi tất cả. Ví dụ: xem xét ví dụ về đoạn mã này:

def init(): 
    v = """abc 
def 
ghi""" 
    print(v) 

Điều này có nghĩa là bạn cần một thuật toán phân tích cú pháp mã phức tạp hơn ... nên quên giải pháp của tôi ...

+1

Khi tôi nói 'đơn giản' tôi có nghĩa là thực sự 'thanh lịch' .. của bạn là chức năng nhưng là quá nhiều của một hack :) – lorenzog

+0

Tôi biết :-) Tôi đánh dấu sao câu hỏi. Tôi hy vọng ai đó có một giải pháp tốt hơn ... – gecco

+0

@lorenzog: Bạn sẽ cần một trình phân tích cú pháp. –

1

Không chắc chắn nếu bạn sẽ xem xét thanh lịch này, nhưng nó là một chút thông minh theo nghĩa là nó nhận ra khi def init là thẻ và không chỉ là một phần của một chuỗi nhiều đường phức tạp:

''' 
def init does not define init... 
''' 

Nó sẽ không nhận ra khi init được định nghĩa theo nhiều cách khéo léo xen kẽ như

init = lambda ... 

hoặc

codestr='def i'+'nit ...' 
exec(codestr) 

Cách duy nhất để xử lý tất cả các trường hợp như vậy là chạy mã (ví dụ: trong hộp cát hoặc bằng cách nhập) và kiểm tra kết quả.


import tokenize 
import token 
import io 
import collections 

userscript = '''\ 
def init(): 
    blah 

""" 
def execute(): 
    more blah 
""" 

yadda 
''' 

class Token(object): 
    def __init__(self, tok): 
     toknum, tokval, (srow, scol), (erow, ecol), line = tok 
     self.toknum = toknum 
     self.tokname = token.tok_name[toknum] 
     self.tokval = tokval 
     self.srow = srow 
     self.scol = scol 
     self.erow = erow 
     self.ecol = ecol 
     self.line = line  

class Validator(object): 
    def __init__(self, codestr): 
     self.codestr = codestr 
     self.toks = collections.deque(maxlen = 2) 
     self.names = set() 
    def validate(self): 
     tokens = tokenize.generate_tokens(io.StringIO(self.codestr).readline) 
     self.toks.append(Token(next(tokens))) 
     for tok in tokens: 
      self.toks.append(Token(tok))    
      if (self.toks[0].tokname == 'NAME'  # First token is a name 
       and self.toks[0].scol == 0   # First token starts at col 0 
       and self.toks[0].tokval == 'def' # First token is 'def' 
       and self.toks[1].tokname == 'NAME' # Next token is a name 
       ): 
       self.names.add(self.toks[1].tokval) 
     delta = set(['init', 'cleanup', 'execute']) - self.names 
     if delta: 
      raise ValueError('{n} not defined'.format(n = ' and '.join(delta))) 

v = Validator(userscript) 
v.validate() 

mang

ValueError: execute and cleanup not defined 
+0

Đối với một số thanh lịch hơn giải pháp của tôi, nhưng tôi tự hỏi nếu nó cung cấp một hiệu suất tốt hơn ... – gecco

+0

Đây là chính xác những gì tôi xem xét "quá nhiều rắc rối", mặc dù cực kỳ thanh lịch và súc tích. Tại thời điểm này, tôi cũng có thể thực thi kịch bản người dùng để chứa init(), execute() và cleanup() và cảnh báo về hành vi không xác định khác. Tuy nhiên, bạn trả lời câu hỏi của tôi. Tôi sẽ xem có điều gì tốt hơn không và đánh dấu điều này là được chấp nhận. Cảm ơn. – lorenzog

-1

Một giải pháp cho 1-3, (không phải là phần yadda) là để tay ra "generic_class.py" với tất cả các phương pháp mà bạn cần. Vì vậy,

class Generic(object): 

    def __init__(self): 
     return 

    def execute(self): 
     return 

    # etc 

Sau đó, bạn có thể kiểm tra sự tồn tại của "chung" trong những gì bạn đã nhập. Nếu nó không tồn tại, bạn có thể bỏ qua nó và nếu nó không thì bạn biết chính xác những gì ở đó. Bất cứ điều gì thêm sẽ không bao giờ được gọi trừ khi nó được gọi từ bên trong một trong các phương pháp được xác định trước của bạn.

+0

Khi bạn 'nhập' một mô đun thì bạn thực thi nó ... mã độc đã được thực hiện trước khi bạn có thể kiểm tra nhập" chung "... – gecco

+0

@gecco Tôi đã nói rằng nó giải quyết 1-4 (ý tôi là 3) - đã chỉnh sửa - không phần thi hành. Đó là một cách tốt hơn để thực thi các phương pháp cụ thể trong kịch bản hơn bất cứ điều gì tôi có thể nghĩ đến nhưng bạn đúng nó không ngăn chặn mã độc. Nhưng tôi không cố trả lời câu hỏi đó. Trên đỉnh đầu của tôi, câu trả lời của bạn sẽ là cách đơn giản nhất để ngăn chặn việc thực hiện độc hại. – Ben

+0

Điều này không thực sự giúp giải quyết bất cứ điều gì. Nếu tôi phải thực thi sự tồn tại của một đối tượng phân lớp 'Generic', tôi cũng có thể thực thi sự hiện diện của 'init()', 'execute()' và 'cleanup()'. Ngoài ra, '__init() __' trong mã của bạn đề cập đến khởi tạo đối tượng, khác với khái niệm khởi tạo của tập lệnh: người ta có thể thiết lập các đối tượng cho mỗi đối tượng trong __init__, không liên quan đến toàn bộ tập lệnh. – lorenzog

3

tôi muốn trước hết là không đòi hỏi một số chức năng, nhưng một lớp học phù hợp với một giao diện cụ thể, sử dụng các mô-đun abc, hoặc zope.interface. Điều này buộc các nhà sản xuất của các mô-đun để cung cấp các chức năng mà bạn muốn.

Thứ hai, tôi sẽ không bận tâm tìm kiếm mã cấp mô-đun. Đó là vấn đề của người tạo mô-đun nếu anh ta làm điều này. Đó là quá nhiều công việc không có lợi ích thực tế.

Nếu bạn lo lắng về các vấn đề bảo mật, bạn cần phải sandbox mã bằng cách nào đó.

+0

Cảm ơn. Nỗi lo lắng của tôi không phải về bảo mật, nhiều hơn về công việc chính xác của ứng dụng. Điều cuối cùng tôi muốn là tìm bản thân mình giải thích cho người dùng cuối tại sao không thể có bất kỳ mã nào bên ngoài các hàm được yêu cầu ... – lorenzog

+0

@lorenzog: Người dùng cuối sẽ không lập trình các tập lệnh Python. Một lập trình viên có thể, nhưng anh ta sẽ hiểu tại sao. Xem thêm "Đồng ý người lớn". :-) –

+0

một người dùng cuối có thể làm điều đó mà không nhận ra nó (DSL ai?) .. – lorenzog

5

Dưới đây là một đơn giản hơn (và ngây thơ hơn) thay thế cho phương pháp AST:

import sys 
from imp import find_module, new_module, PY_SOURCE 


EXPECTED = ("init", "execute", "cleanup") 

def import_script(name): 
    fileobj, path, description = find_module(name) 

    if description[2] != PY_SOURCE: 
     raise ImportError("no source file found") 

    code = compile(fileobj.read(), path, "exec") 

    expected = list(EXPECTED) 
    for const in code.co_consts: 
     if isinstance(const, type(code)) and const.co_name in expected: 
      expected.remove(const.co_name) 
    if expected: 
     raise ImportError("missing expected function: {}".format(expected)) 

    module = new_module(name) 
    exec(code, module.__dict__) 
    sys.modules[name] = module 
    return module 

Hãy nhớ, đây là một cách rất trực tiếp làm việc đó và làm hỏng tính phần mở rộng cho máy móc thiết bị nhập khẩu của Python.

Các vấn đề liên quan