Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a simple scripting language in Python

I'm creating a GUI application that can monitor and manipulate a stream of messages. I'm trying to create a simple means to let the user script some of this functionality and I'm looking for possible candidates. Initially I wanted to use XML since it can naturally take care of embedded code:

<if>
   <condition>
      <recv>
         <MesgTypeA/>
      </recv>
   </condition>
   <loop count=10>
      <send>
         <MesgTypeB>
            <param1>12</param1>
            <param2>52</param2>
         </MesgTypeB>
      </send>
   </loop>
</if>

For parsing I was planning on using ElementTree and just build states out of the code. Writing and reading XML isn't the easiest thing to do, especially since I can't assume that the writers of the script will have any sort of experience. I was wondering if anyone had any alternatives that is easier to read/write and process in Python. I looked into JSON but because it's a script, order matters.

Can anyone suggest any possible alternatives?

Thanks.

like image 807
Manny D Avatar asked Jun 30 '11 13:06

Manny D


3 Answers

How about Python itself?

For example:

>>> import code
>>> def host_func():
...     print("Hello old chap!")
...
>>> c = code.compile_command("print(\"Script says hello!\"); host_func()")
>>> exec(c)
Script says hello!
Hello old chap!

exec let's you be explicit about what from the host environment you want to expose through the two optional parameters locals and globals.

In this example I am being explicit about what globals the script will have access to. Note that I can "create" variables here, or give existing functions another name. It's a dictionary pointing to functions and data.

>>> import code
>>> def secret():
...     print("What?! I don't even... get out of here.")
...
>>> def public():
...     print("Hello stranger.")
...
>>> c = code.compile_command("secret(); public()")

Calling this with globals containing two functions, pointing back to the already existing ones gives:

>>> exec(c, {"secret": secret, "public": public})
What?! I don't even... get out of here.
Hello stranger.

Now when I omit secret, the script can no longer find it.

>>> exec(c, {"public": public})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<input>", line 1, in <module>
NameError: name 'secret' is not defined

Here I redefine secret all together:

>>> exec(c, {"public": public, "secret":lambda: print("Haha! Doppelganger.")})
Haha! Doppelganger.
Hello stranger.

As lazyr mentions in the comments there are security concerns. The above examples let the script pretty much do what it want. In some cases this is not acceptable.

There is some stuff one can do to discourage it:

  • Neuter __builtins__, only allow "white-listed" built-in functions.
  • Make it hard to import modules.

For example, here's how you bork the import statement (in Py2.* builtins is __builtins__):

>>> import builtins
>>> def no_import(*args, **kwargs):
...     raise ImportError("I cannot let you do that, Dave.")
...
>>> builtins.__import__ = no_import
>>> import os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in no_import
ImportError: I cannot let you do that, Dave.

From this follows that we can pass our own builtins in the globals parameter:

>>> import code
>>> evil_code = "import os; import stat; os.chmod(\"passwords.txt\", stat.S_IROT
H);"
>>> compiled = code.compile_command(evil_code)
>>> def no_import(*args, **kwargs):
...    raise ImportError("I cannot let you do that, Dave.")
...
>>> exec(compiled, {"__builtins__": {"__import__": no_import}})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<input>", line 1, in <module>
  File "<stdin>", line 2, in no_import
ImportError: I cannot let you do that, Dave.

A caveat, though, this will bork all imports that happen after it. It may be better to replace it with a version that lets you import whitelisted modules.

And finally, I'm not sure this completely guards you. Some crafty person may well circumvent it. But the most blatant infractions should at least be discouraged.

like image 165
Skurmedel Avatar answered Sep 27 '22 15:09

Skurmedel


Python or maybe Lisp because the syntax is easy to parse.

like image 39
luxcem Avatar answered Sep 27 '22 17:09

luxcem


You could define your own scripting language syntax with pyparsing.

like image 43
Mark Tolonen Avatar answered Sep 27 '22 16:09

Mark Tolonen