[Zope-Perl] Perl External Methods

Gisle Aas gisle@ActiveState.com
11 Jun 2000 14:12:58 -0000


I have now implemented Perl External Methods as a separate product
instead of patching up the existing (Python) External Method product
and App.Extentions.  This is probably a better idea.  The guts of this
product is included below.  General comments to the code are welcomed?

Anyway, the big question is what security requirements there should
be.  This implementation allows a webuser to set up calls to any
function from any module that perl can load.  This is probably too
much :-)

The python external method stuff only allow you to set up calls to
functions living in modules found in the ./Extensions directory of the
Zope installation.  In addition each one of these modules are executed
in a clean namespace environment (via 'exec module_src in {}').

To achieve something similar for perl we would have to set up an
environment with 'Safe' and then evaluate perl module code within it.
I don't really like this.

Another approach that I like better could be that all perl function
and module names are always (automatically) prefixed by something like
"ZopeExt::" in order to limit what can be done.  Perl External Method
modules would then need to go into the ./Extensions/ZopeExt directory
(or anywhere else perl looks for modules).

A third approach could be to do stuff similar to what mod_perl's
Apache::Registry does, i.e. wrap up all code in a package declaration
deduced from the file names involved and eval the stuff.

Any other thoughts on this?

--Gisle


-----lib/python/Products/PerlExternalMethod__init__.py-------------->8---
# Copyright 2000 Digital Creations
# Copyright 2000 ActiveState Tool Corp.
#
# This module can be redistributed and/or modified
# under the terms of the Zope Public Licence v1.0.
#
# This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment (http://www.zope.org/).

from Globals import Persistent, HTMLFile, MessageDialog
import OFS.SimpleItem
import AccessControl.Role
import Acquisition

from string import split, strip, join, find


def get_perl_func(name, module):
    import perl
    if module:
        # XXX unshift(@INC, qw(./Extensions/perl ./Extensions)
        perl.require(module)  # XXX do we need some kind of untaint here??
    f = perl.get_ref(name)
    return f


# A class emulating internal func-code objects enough to fool
# ZPublisher's mapply.  Need to emulate co_varnames and co_argcount.
# For Python External methods you find a similar class in
# App.Extentions.

class FuncCode:
    def __init__(self, args = ""):
        self.co_varnames = map(strip, split(args, ","))
        self.co_argcount = len(self.co_varnames)

    def args(self):
        return join(self.co_varnames, ", ")


# This is the actual class that we will store instances of, in the
# ZODB as this product is instantiated through manage_addPerlExtMethod
# below.  It stores enough information to obtain a reference to a single
# perl function loaded from the file system on demand and the information
# needed to fool mapply into thinking that this object is a function
# by itself.

class PerlExtMethod(OFS.SimpleItem.Item, Persistent, Acquisition.Explicit,
                    AccessControl.Role.RoleManager):
    """Web-callable functions that encapsulate external perl functions."""

    # Emulated attributes of functions.  The func_code class attribute
    # is not really used as instances override it with their own version.
    func_defaults=()
    func_code=FuncCode()

    # Set up various stuff that the Zope frameworks wants
    meta_type='Perl External Method'
    manage_options=(
        (
        {'label':'Properties', 'action':'manage_main',},
        {'label':'Try It', 'action':''},
        )
        +OFS.SimpleItem.Item.manage_options
        +AccessControl.Role.RoleManager.manage_options
        )

    __ac_permissions__=(
        ('View management screens', ('manage_main',)),
        ('Change External Methods', ('manage_edit',)),
        ('View', ('__call__','')),
        )

    # I am not really sure why :-(
    ZopeTime=Acquisition.Acquired
    HelpSys=Acquisition.Acquired

    def __init__(self, id, title, module, function, args):
        self.id=id
        self.manage_edit(title, module, function, args)

    manage_main=HTMLFile('methodEdit', globals())
    
    def manage_edit(self, title, module, function, args, REQUEST=None):
        """Change the perl external method"""

        self.title = str(title)
        
        module = strip(str(module))
        if module[-3:]=='.pm': module=module[:-3]
        self.module = module

        # XXX In order to improve security we might here require
        # that the function name is prefixed with some specific
        # package name (like "ZopeExt::").  This really allows
        # access to any function to be set up.
        function = strip(str(function))
        if find(function, "::") < 0:
            function = module + "::" + function
        self.function = function
        
        self.func_code = FuncCode(str(args))
        
        #self.getFunction(1,1)
        if REQUEST: return MessageDialog(
            title  ='Changed %s' % self.id,
            message='%s has been updated' % self.id,
            action =REQUEST['URL2']+'/manage_main',
            target ='manage_main')

    def __call__(self, *args, **kw):
        """Invoke a PerlExternalMethod"""

        # The "_v_" prefix tells ZODB not try to store this thing
        if not hasattr(self, "_v_f"):
            self._v_f = get_perl_func(self.function, self.module)
        return apply(self._v_f, args, kw)

    def args(self):
        return self.func_code.args()



manage_addPerlExtMethodForm=HTMLFile('methodAdd', globals())

def manage_addPerlExtMethod(self, id, title, module, function, args,
                            REQUEST=None):
    """Add a perl external method to a folder"""
    self._setObject(id, PerlExtMethod(id, title, module, function, args))
    if REQUEST:
        return self.manage_main(self, REQUEST)


def initialize(context):
    context.registerClass(
        PerlExtMethod,
        constructors=(manage_addPerlExtMethodForm,
                      manage_addPerlExtMethod),
        icon='extmethod.gif',
    )