Commit 437d5131 authored by Hanno Schlichting's avatar Hanno Schlichting

Factored out PythonScripts

parent f44f4ddd
...@@ -50,6 +50,7 @@ eggs = ...@@ -50,6 +50,7 @@ eggs =
MultiMapping MultiMapping
Persistence Persistence
Products.ExternalMethod Products.ExternalMethod
Products.PythonScripts
Products.ZCTextIndex Products.ZCTextIndex
Record Record
RestrictedPython RestrictedPython
......
...@@ -25,8 +25,10 @@ Restructuring ...@@ -25,8 +25,10 @@ Restructuring
- Avoid using the ``Products.PythonScripts.standard`` module inside the - Avoid using the ``Products.PythonScripts.standard`` module inside the
database manager ZMI. database manager ZMI.
- Factored out the `Products.MIMETools` and `Products.ExternalMethod` packages - Factored out the `Products.MIMETools`, `Products.ExternalMethod` and
into their own distributions. `Products.PythonScripts` packages into their own distributions. They will
no longer be included by default in Zope 2.14 but live on as independent
add-ons.
- Factored out the `Products.ZSQLMethods` into its own distribution. The - Factored out the `Products.ZSQLMethods` into its own distribution. The
distribution also includes the `Shared.DC.ZRDB` code. The Zope2 distribution distribution also includes the `Shared.DC.ZRDB` code. The Zope2 distribution
......
...@@ -47,8 +47,6 @@ setup(name='Zope2', ...@@ -47,8 +47,6 @@ setup(name='Zope2',
'Missing', 'Missing',
'MultiMapping', 'MultiMapping',
'Persistence', 'Persistence',
'Products.ExternalMethod',
'Products.MIMETools',
'Products.ZCTextIndex', 'Products.ZCTextIndex',
'Record', 'Record',
'RestrictedPython', 'RestrictedPython',
...@@ -99,6 +97,10 @@ setup(name='Zope2', ...@@ -99,6 +97,10 @@ setup(name='Zope2',
'zope.testing', 'zope.testing',
'zope.traversing', 'zope.traversing',
'zope.viewlet', 'zope.viewlet',
# BBB optional dependencies to be removed in Zope 2.14
'Products.ExternalMethod',
'Products.MIMETools',
'Products.PythonScripts',
], ],
include_package_data=True, include_package_data=True,
......
...@@ -9,7 +9,9 @@ Missing = svn svn://svn.zope.org/repos/main/Missing/trunk ...@@ -9,7 +9,9 @@ Missing = svn svn://svn.zope.org/repos/main/Missing/trunk
MultiMapping = svn svn://svn.zope.org/repos/main/MultiMapping/trunk MultiMapping = svn svn://svn.zope.org/repos/main/MultiMapping/trunk
nt_svcutils = svn svn://svn.zope.org/repos/main/nt_svcutils/trunk nt_svcutils = svn svn://svn.zope.org/repos/main/nt_svcutils/trunk
Persistence = svn svn://svn.zope.org/repos/main/Persistence/trunk Persistence = svn svn://svn.zope.org/repos/main/Persistence/trunk
Products.ExternalMethod = svn svn://svn.zope.org/repos/main/Products.ExternalMethod/trunk
Products.MIMETools = svn svn://svn.zope.org/repos/main/Products.MIMETools/trunk Products.MIMETools = svn svn://svn.zope.org/repos/main/Products.MIMETools/trunk
Products.PythonScripts = svn svn://svn.zope.org/repos/main/Products.PythonScripts/trunk
Products.ZCTextIndex = svn svn://svn.zope.org/repos/main/Products.ZCTextIndex/trunk Products.ZCTextIndex = svn svn://svn.zope.org/repos/main/Products.ZCTextIndex/trunk
Record = svn svn://svn.zope.org/repos/main/Record/trunk Record = svn svn://svn.zope.org/repos/main/Record/trunk
tempstorage = svn svn://svn.zope.org/repos/main/tempstorage/trunk tempstorage = svn svn://svn.zope.org/repos/main/tempstorage/trunk
......
2001-04-26 Evan Simpson <evan@digicool.com>
* Version 2.0.0
* Totally replaced zbytecodhacks engine with Zope's new
RestrictedPython package, which it shares with DTML.
1999-12-13 Evan Simpson <evan@4-am.com>
* Version 0.1.7
* Nested functions and lambdas are now supported, with full safety.
* You can access all of the dtml-var format functions through a builtin
dictionary called special_formats (eg: special_formats['html-quote']).
* Handing off to Digital Creations for inclusion in CVS.
* Packaged with packProduct script, which excludes parent directories
and .pyc files. Makes for a smaller package, and doesn't step on
ownership/permissions of lib/python/Products path elements.
1999-12-01 Evan Simpson <evan@4-am.com>
* Added COPYRIGHT.txt, making Wide Open Source licence (BSD-style)
explicit. (Mike Goldman provided the text, I provided the silly name).
* Jeff Rush donated a PrincipiaSearchSource method, so that
PythonMethod objects can be zcataloged to the same degree
as DTML Methods.
* Also from Jeff Rush, a document_src method, so that the source of
PythonMethods can be viewed via a "View Source" link if desired.
* If a PM has a 'traverse_subpath' parameter, you can now directly
traverse it. The elements of the subpath will then be put into a list
in 'traverse_subpath'. (thanks to Anthony Baxter)
1999-11-11 Evan Simpson <evan@4-am.com>
* Version 0.1.6
* Fix to builtins messed up DTML Methods, so I re-fixed it.
1999-11-05 Evan Simpson <evan@4-am.com>
* Version 0.1.5
* Killed *%#&$@ weird bug in which having 'add' documents in 'www'
subdirectory prevented rename, paste, or import of existing
PythonMethods! See use of '_www'.
* Range, test, and several other Zope 'builtins' had an unbound 'self'
argument unless called on _, but that's fixed.
* Safe multiplication was utterly broken (thanks to the guard); now
it works. Is anyone using the safe version??
1999-10-18 Evan Simpson <evan@4-am.com>
* Eliminated bug which delayed stringification of printed values.
1999-10-08 Evan Simpson <evan@4-am.com>
* Version 0.1.4
* Fixed mis-design noticed by Michel Pelletier, and refactored
MakeFunction. Now both kinds of Python Method have the bugfix
from 0.1.3, and shouldn't provoke a transaction when called.
1999-10-07 Evan Simpson <evan@4-am.com>
* Version 0.1.3
* Fixed parameter bug with 'self' and no defaults
1999-09-24 Evan Simpson <evan@4-am.com>
* Version 0.1.2
* Added WebDAV/FTP access code donated by Michel Pelletier
* Made parameters part of WebDAV/FTP text
* Eliminated initialization of globals to None
* Added 'global_exists' global function instead
* Killed bug with unused parameters
* Put switch in Guarded.py to allow both regular and
dangerous (XXX) PythonMethods to live side-by-side.
This means that people who patched version 0.1.1
will have to re-create any unsafe PMs they use (Sorry).
1999-09-10 Evan Simpson <evan@4-am.com>
* Version 0.1.1
* Incorporated DT_Util builtins and guards
* Fixed direct access via URL
* Fixed methodAdd.dtml
* rstrip function body
* Major changes to zbytecodehacks
''' RemotePS.py
External Method that allows you to remotely (via XML-RPC, for instance)
execute restricted Python code.
For example, create an External Method 'restricted_exec' in your Zope
root, and you can remotely call:
foobarsize = s.foo.bar.restricted_exec('len(context.objectIds())')
'''
from Products.PythonScripts.PythonScript import PythonScript
from string import join
def restricted_exec(self, body, varmap=None):
ps = PythonScript('temp')
if varmap is None:
varmap = {}
ps.ZPythonScript_edit(join(varmap.keys(), ','), body)
return apply(ps.__of__(self), varmap.values())
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Python Scripts Product
This product provides support for Script objects containing restricted
Python code.
$Id$
"""
from logging import getLogger
import marshal
import new
import os
import re
import sys
import traceback
from urllib import quote
from AccessControl.class_init import InitializeClass
from AccessControl.requestmethod import requestmethod
from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from AccessControl.ZopeGuards import get_safe_globals, guarded_getattr
from AccessControl.ZopeGuards import get_safe_globals, guarded_getattr
from Acquisition import aq_parent
from App.Common import package_home
from App.Dialogs import MessageDialog
from App.special_dtml import DTMLFile
from DateTime.DateTime import DateTime
from OFS.Cache import Cacheable
from OFS.History import Historical
from OFS.History import html_diff
from OFS.SimpleItem import SimpleItem
from RestrictedPython import compile_restricted_function
from Shared.DC.Scripts.Script import BindingsUI
from Shared.DC.Scripts.Script import defaultBindings
from Shared.DC.Scripts.Script import Script
from webdav.Lockable import ResourceLockedError
from zExceptions import Forbidden
LOG = getLogger('PythonScripts')
# Track the Python bytecode version
import imp
Python_magic = imp.get_magic()
del imp
# This should only be incremented to force recompilation.
Script_magic = 3
_log_complaint = (
'Some of your Scripts have stale code cached. Since Zope cannot'
' use this code, startup will be slightly slower until these Scripts'
' are edited. You can automatically recompile all Scripts that have'
' this problem by visiting /manage_addProduct/PythonScripts/recompile'
' of your server in a browser.')
manage_addPythonScriptForm = DTMLFile('www/pyScriptAdd', globals())
_default_file = os.path.join(package_home(globals()),
'www', 'default_py')
_marker = [] # Create a new marker object
def manage_addPythonScript(self, id, REQUEST=None, submit=None):
"""Add a Python script to a folder.
"""
id = str(id)
id = self._setObject(id, PythonScript(id))
if REQUEST is not None:
file = REQUEST.form.get('file', '')
if type(file) is not type(''): file = file.read()
if not file:
file = open(_default_file).read()
self._getOb(id).write(file)
try: u = self.DestinationURL()
except: u = REQUEST['URL1']
if submit==" Add and Edit ": u="%s/%s" % (u,quote(id))
REQUEST.RESPONSE.redirect(u+'/manage_main')
return ''
class PythonScript(Script, Historical, Cacheable):
"""Web-callable scripts written in a safe subset of Python.
The function may include standard python code, so long as it does
not attempt to use the "exec" statement or certain restricted builtins.
"""
meta_type='Script (Python)'
_proxy_roles = ()
_params = _body = ''
errors = warnings = ()
_v_change = 0
manage_options = (
{'label':'Edit',
'action':'ZPythonScriptHTML_editForm',
'help': ('PythonScripts', 'PythonScript_edit.stx')},
) + BindingsUI.manage_options + (
{'label':'Test',
'action':'ZScriptHTML_tryForm',
'help': ('PythonScripts', 'PythonScript_test.stx')},
{'label':'Proxy',
'action':'manage_proxyForm',
'help': ('OFSP','DTML-DocumentOrMethod_Proxy.stx')},
) + Historical.manage_options + SimpleItem.manage_options + \
Cacheable.manage_options
def __init__(self, id):
self.id = id
self.ZBindings_edit(defaultBindings)
self._makeFunction()
security = ClassSecurityInfo()
security.declareObjectProtected('View')
security.declareProtected('View', '__call__')
security.declareProtected('View management screens',
'ZPythonScriptHTML_editForm', 'manage_main', 'read',
'ZScriptHTML_tryForm', 'PrincipiaSearchSource',
'document_src', 'params', 'body', 'get_filepath')
ZPythonScriptHTML_editForm = DTMLFile('www/pyScriptEdit', globals())
manage = manage_main = ZPythonScriptHTML_editForm
ZPythonScriptHTML_editForm._setName('ZPythonScriptHTML_editForm')
security.declareProtected('Change Python Scripts',
'ZPythonScriptHTML_editAction',
'ZPythonScript_setTitle', 'ZPythonScript_edit',
'ZPythonScriptHTML_upload', 'ZPythonScriptHTML_changePrefs')
def ZPythonScriptHTML_editAction(self, REQUEST, title, params, body):
"""Change the script's main parameters."""
self.ZPythonScript_setTitle(title)
self.ZPythonScript_edit(params, body)
message = "Saved changes."
return self.ZPythonScriptHTML_editForm(self, REQUEST,
manage_tabs_message=message)
def ZPythonScript_setTitle(self, title):
title = str(title)
if self.title != title:
self.title = title
self.ZCacheable_invalidate()
def ZPythonScript_edit(self, params, body):
self._validateProxy()
if self.wl_isLocked():
raise ResourceLockedError, "The script is locked via WebDAV."
if type(body) is not type(''):
body = body.read()
if self._params <> params or self._body <> body or self._v_change:
self._params = str(params)
self.write(body)
def ZPythonScriptHTML_upload(self, REQUEST, file=''):
"""Replace the body of the script with the text in file."""
if self.wl_isLocked():
raise ResourceLockedError, "The script is locked via WebDAV."
if type(file) is not type(''):
if not file: raise ValueError, 'File not specified'
file = file.read()
self.write(file)
message = 'Saved changes.'
return self.ZPythonScriptHTML_editForm(self, REQUEST,
manage_tabs_message=message)
def ZPythonScriptHTML_changePrefs(self, REQUEST, height=None, width=None,
dtpref_cols="100%", dtpref_rows="20"):
"""Change editing preferences."""
dr = {"Taller":5, "Shorter":-5}.get(height, 0)
dc = {"Wider":5, "Narrower":-5}.get(width, 0)
if isinstance(height, int): dtpref_rows = height
if isinstance(width, int) or \
isinstance(width, str) and width.endswith('%'):
dtpref_cols = width
rows = str(max(1, int(dtpref_rows) + dr))
cols = str(dtpref_cols)
if cols.endswith('%'):
cols = str(min(100, max(25, int(cols[:-1]) + dc))) + '%'
else:
cols = str(max(35, int(cols) + dc))
e = (DateTime("GMT") + 365).rfc822()
setCookie = REQUEST["RESPONSE"].setCookie
setCookie("dtpref_rows", rows, path='/', expires=e)
setCookie("dtpref_cols", cols, path='/', expires=e)
REQUEST.other.update({"dtpref_cols":cols, "dtpref_rows":rows})
return self.manage_main(self, REQUEST)
def ZScriptHTML_tryParams(self):
"""Parameters to test the script with."""
param_names = []
for name in self._params.split(','):
name = name.strip()
if name and name[0] != '*' and re.match('\w',name):
param_names.append(name.split('=', 1)[0].strip())
return param_names
def manage_historyCompare(self, rev1, rev2, REQUEST,
historyComparisonResults=''):
return PythonScript.inheritedAttribute('manage_historyCompare')(
self, rev1, rev2, REQUEST,
historyComparisonResults=html_diff(rev1.read(), rev2.read()) )
def __setstate__(self, state):
Script.__setstate__(self, state)
if (getattr(self, 'Python_magic', None) != Python_magic or
getattr(self, 'Script_magic', None) != Script_magic):
global _log_complaint
if _log_complaint:
LOG.info(_log_complaint)
_log_complaint = 0
# Changes here won't get saved, unless this Script is edited.
body = self._body.rstrip()
if body:
self._body = body + '\n'
self._compile()
self._v_change = 1
elif self._code is None:
self._v_ft = None
else:
self._newfun(marshal.loads(self._code))
def _compiler(self, *args, **kw):
return compile_restricted_function(*args, **kw)
def _compile(self):
bind_names = self.getBindingAssignments().getAssignedNamesInOrder()
r = self._compiler(self._params, self._body or 'pass',
self.id, self.meta_type,
globalize=bind_names)
code = r[0]
errors = r[1]
self.warnings = tuple(r[2])
if errors:
self._code = None
self._v_ft = None
self._setFuncSignature((), (), 0)
# Fix up syntax errors.
filestring = ' File "<string>",'
for i in range(len(errors)):
line = errors[i]
if line.startswith(filestring):
errors[i] = line.replace(filestring, ' Script', 1)
self.errors = errors
return
self._code = marshal.dumps(code)
self.errors = ()
f = self._newfun(code)
fc = f.func_code
self._setFuncSignature(f.func_defaults, fc.co_varnames,
fc.co_argcount)
self.Python_magic = Python_magic
self.Script_magic = Script_magic
self._v_change = 0
def _newfun(self, code):
g = get_safe_globals()
g['_getattr_'] = guarded_getattr
g['__debug__'] = __debug__
# it doesn't really matter what __name__ is, *but*
# - we need a __name__
# (see testPythonScript.TestPythonScriptGlobals.test__name__)
# - it should not contain a period, so we can't use the id
# (see https://bugs.launchpad.net/zope2/+bug/142731/comments/4)
# - with Python 2.6 it should not be None
# (see testPythonScript.TestPythonScriptGlobals.test_filepath)
g['__name__'] = 'script'
l = {}
exec code in g, l
f = l.values()[0]
self._v_ft = (f.func_code, g, f.func_defaults or ())
return f
def _makeFunction(self):
self.ZCacheable_invalidate()
self._compile()
if not (aq_parent(self) is None or hasattr(self, '_filepath')):
# It needs a _filepath, and has an acquisition wrapper.
self._filepath = self.get_filepath()
def _editedBindings(self):
if getattr(self, '_v_ft', None) is not None:
self._makeFunction()
def _exec(self, bound_names, args, kw):
"""Call a Python Script
Calling a Python Script is an actual function invocation.
"""
# Retrieve the value from the cache.
keyset = None
if self.ZCacheable_isCachingEnabled():
# Prepare a cache key.
keyset = kw.copy()
asgns = self.getBindingAssignments()
name_context = asgns.getAssignedName('name_context', None)
if name_context:
keyset[name_context] = aq_parent(self).getPhysicalPath()
name_subpath = asgns.getAssignedName('name_subpath', None)
if name_subpath:
keyset[name_subpath] = self._getTraverseSubpath()
# Note: perhaps we should cache based on name_ns also.
keyset['*'] = args
result = self.ZCacheable_get(keywords=keyset, default=_marker)
if result is not _marker:
# Got a cached value.
return result
#__traceback_info__ = bound_names, args, kw, self.func_defaults
ft = self._v_ft
if ft is None:
__traceback_supplement__ = (
PythonScriptTracebackSupplement, self)
raise RuntimeError, '%s %s has errors.' % (self.meta_type, self.id)
fcode, g, fadefs = ft
g = g.copy()
if bound_names is not None:
g.update(bound_names)
g['__traceback_supplement__'] = (
PythonScriptTracebackSupplement, self, -1)
g['__file__'] = getattr(self, '_filepath', None) or self.get_filepath()
f = new.function(fcode, g, None, fadefs)
try:
result = f(*args, **kw)
except SystemExit:
raise ValueError('SystemExit cannot be raised within a PythonScript')
if keyset is not None:
# Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset)
return result
def manage_afterAdd(self, item, container):
if item is self:
self._filepath = self.get_filepath()
def manage_beforeDelete(self, item, container):
# shut up deprecation warnings
pass
def manage_afterClone(self, item):
# shut up deprecation warnings
pass
def get_filepath(self):
return self.meta_type + ':' + '/'.join(self.getPhysicalPath())
def manage_haveProxy(self,r): return r in self._proxy_roles
def _validateProxy(self, roles=None):
if roles is None: roles = self._proxy_roles
if not roles: return
user = getSecurityManager().getUser()
if user is not None and user.allowed(self, roles):
return
raise Forbidden, ('You are not authorized to change <em>%s</em> '
'because you do not have proxy roles.\n<!--%s, %s-->'
% (self.id, user, roles))
security.declareProtected('Change proxy roles',
'manage_proxyForm', 'manage_proxy')
manage_proxyForm = DTMLFile('www/pyScriptProxy', globals())
@requestmethod('POST')
def manage_proxy(self, roles=(), REQUEST=None):
"Change Proxy Roles"
self._validateProxy(roles)
self._validateProxy()
self.ZCacheable_invalidate()
self._proxy_roles=tuple(roles)
if REQUEST: return MessageDialog(
title ='Success!',
message='Your changes have been saved',
action ='manage_main')
security.declareProtected('Change Python Scripts',
'PUT', 'manage_FTPput', 'write',
'manage_historyCopy',
'manage_beforeHistoryCopy', 'manage_afterHistoryCopy')
def PUT(self, REQUEST, RESPONSE):
""" Handle HTTP PUT requests """
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
self.write(REQUEST.get('BODY', ''))
RESPONSE.setStatus(204)
return RESPONSE
manage_FTPput = PUT
def write(self, text):
""" Change the Script by parsing a read()-style source text. """
self._validateProxy()
mdata = self._metadata_map()
bindmap = self.getBindingAssignments().getAssignedNames()
bup = 0
st = 0
try:
while 1:
# Find the next non-empty line
m = _nonempty_line.search(text, st)
if not m:
# There were no non-empty body lines
body = ''
break
line = m.group(0).strip()
if line[:2] != '##':
# We have found the first line of the body
body = text[m.start(0):]
break
st = m.end(0)
# Parse this header line
if len(line) == 2 or line[2] == ' ' or '=' not in line:
# Null header line
continue
k, v = line[2:].split('=', 1)
k = k.strip().lower()
v = v.strip()
if not mdata.has_key(k):
raise SyntaxError, 'Unrecognized header line "%s"' % line
if v == mdata[k]:
# Unchanged value
continue
# Set metadata value
if k == 'title':
self.title = v
elif k == 'parameters':
self._params = v
elif k[:5] == 'bind ':
bindmap[_nice_bind_names[k[5:]]] = v
bup = 1
body = body.rstrip()
if body:
body = body + '\n'
if body != self._body:
self._body = body
if bup:
self.ZBindings_edit(bindmap)
else:
self._makeFunction()
except:
LOG.error('write failed', exc_info=sys.exc_info())
raise
def manage_FTPget(self):
"Get source for FTP download"
self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read()
def _metadata_map(self):
m = {
'title': self.title,
'parameters': self._params,
}
bindmap = self.getBindingAssignments().getAssignedNames()
for k, v in _nice_bind_names.items():
m['bind '+k] = bindmap.get(v, '')
return m
def read(self):
""" Generate a text representation of the Script source.
Includes specially formatted comment lines for parameters,
bindings, and the title.
"""
# Construct metadata header lines, indented the same as the body.
m = _first_indent.search(self._body)
if m: prefix = m.group(0) + '##'
else: prefix = '##'
hlines = ['%s %s "%s"' % (prefix, self.meta_type, self.id)]
mm = self._metadata_map().items()
mm.sort()
for kv in mm:
hlines.append('%s=%s' % kv)
if self.errors:
hlines.append('')
hlines.append(' Errors:')
for line in self.errors:
hlines.append(' ' + line)
if self.warnings:
hlines.append('')
hlines.append(' Warnings:')
for line in self.warnings:
hlines.append(' ' + line)
hlines.append('')
return ('\n' + prefix).join(hlines) + '\n' + self._body
def params(self): return self._params
def body(self): return self._body
def get_size(self): return len(self.read())
getSize = get_size
def PrincipiaSearchSource(self):
"Support for searching - the document's contents are searched."
return "%s\n%s" % (self._params, self._body)
def document_src(self, REQUEST=None, RESPONSE=None):
"""Return unprocessed document source."""
if RESPONSE is not None:
RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read()
InitializeClass(PythonScript)
class PythonScriptTracebackSupplement:
"""Implementation of ITracebackSupplement"""
def __init__(self, script, line=0):
self.object = script
# If line is set to -1, it means to use tb_lineno.
self.line = line
_first_indent = re.compile('(?m)^ *(?! |$)')
_nonempty_line = re.compile('(?m)^(.*\S.*)$')
_nice_bind_names = {'context': 'name_context', 'container': 'name_container',
'script': 'name_m_self', 'namespace': 'name_ns',
'subpath': 'name_subpath'}
Python Scripts
The Python Scripts product provides support for restricted execution of
Python scripts, exposing them as callable objects within the Zope
environment.
Providing access to extra modules
Python script objects have a limited number of "safe" modules
available to them by default. In the course of working with Zope,
you will probably wish to make other modules available to script
objects.
The Utility.py module in the PythonScripts products provides a
simple way to make modules available for use by script objects
on a site-wide basis. Before making a module available to Python
scripts, you should carefully consider the potential for abuse
or misuse of the module, since all users with permission to
create and edit Python scripts will be able to use any functions
and classes defined in the module. In some cases, you may want to
create a custom module that just imports a subset of names from
another module and make that custom module available to reduce
the risk of abuse.
The easiest way to make modules available to Python scripts on
your site is to create a new directory in your Products directory
containing an "__init__.py" file. At Zope startup time, this
"product" will be imported, and any module assertions you make
in the __init__.py will take effect. Here's how to do it:
o In your Products directory (either in lib/python of your
Zope installation or in the root of your Zope install,
depending on your deployment model), create a new directory
with a name like "GlobalModules".
o In the new directory, create a file named "__init__.py".
o Edit the __init__.py file, and add calls to the 'allow_module'
function (located in the Products.PythonScripts.Utility module),
passing the names of modules to be enabled for use by scripts.
For example:
# Global module assertions for Python scripts
from Products.PythonScripts.Utility import allow_module
allow_module('base64')
allow_module('re')
allow_module('DateTime.DateTime')
This example adds the modules 'base64', 're' and the 'DateTime'
module in the 'DateTime' package for use by Python scripts. Note
that for packages (dotted names), each module in the package path
will become available to script objects.
o Restart your Zope server. After restarting, the modules you enabled
in your custom product will be available to Python scripts.
NB -- Placing security assestions within the package/module you are trying
to import will not work unless that package/module is located in
your Products directory.
This is because that package/module would have to be imported for its
included security assertions to take effect, but to do
that would require importing a module without any security
declarations, which defeats the point of the restricted
python environment.
Products work differently as they are imported at Zope startup.
By placing a package/module in your Products directory, you are
asserting, among other things, that it is safe for Zope to check
that package/module for security assertions. As a result, please
be careful when place packages or modules that are not Zope Products
in the Products directory.
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Utility module for making simple security assertions for
Python scripts."""
__version__='$Revision: 1.6 $'[11:-2]
# These have been relocated, and should be imported from AccessControl
from AccessControl import allow_module, allow_class
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
__doc__='''Python Scripts Product Initialization
$Id$'''
import PythonScript
import standard
# Temporary
from Shared.DC import Scripts
__module_aliases__ = (
('Products.PythonScripts.Script', Scripts.Script),
('Products.PythonScripts.Bindings', Scripts.Bindings),
('Products.PythonScripts.BindingsUI', Scripts.BindingsUI),)
__roles__ = None
__allow_access_to_unprotected_subobjects__ = 1
def initialize(context):
context.registerClass(
PythonScript.PythonScript,
permission='Add Python Scripts',
constructors=(PythonScript.manage_addPythonScriptForm,
PythonScript.manage_addPythonScript),
icon='www/pyscript.gif'
)
context.registerHelp()
context.registerHelpTitle('Script (Python)')
global _m
_m['recompile'] = recompile
_m['recompile__roles__'] = ('Manager',)
# utility stuff
def recompile(self):
'''Recompile all Python Scripts'''
base = self.this()
scripts = base.ZopeFind(base, obj_metatypes=('Script (Python)',),
search_sub=1)
names = []
for name, ob in scripts:
if ob._v_change:
names.append(name)
ob._compile()
ob._p_changed = 1
if names:
return 'The following Scripts were recompiled:\n' + '\n'.join(names)
return 'No Scripts were found that required recompilation.'
import patches
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<five:deprecatedManageAddDelete
class="Products.PythonScripts.PythonScript.PythonScript"/>
</configure>
Allowing Import of Modules
Scripts are able to import a small number of Python modules for
which there are security declarations. These include 'string',
'math', and 'random'. The only way to make other Python modules
available for import is to add security declarations to them in the
filesystem.
MyScriptModules
The simplest way to allow import of a module is to create your own
simple custom Product. To make this Product:
1. Create a subdirectory of your Zope installation's "Products"
directory. The name of the directory doesn't really matter; Let's
call it 'MyScriptModules'.
2. Create a file in this subdirectory called '__init__.py'.
3. Add the following lines to your '__init__.py'::
from Products.PythonScripts.Utility import allow_module, allow_class
from AccessControl import ModuleSecurityInfo, ClassSecurityInfo
from AccessControl.class_init import InitializeClass
4. For each module to which you want to allow access, add
security declarations in '__init__.py'.
Security Declarations
You will need to write different security declarations depending
on how much of a module you want to expose. You should import the
module at the Python command line, and use 'dir(<module_name>)' to
examine its contents. Names starting with underscore ('_') may be
safely ignored. Be wary of dangerous modules, such as 'sys' and
'os', which may be exposed by the module.
You can handle a module, such as 'base64', that contains only safe
functions by writing 'allow_module("module_name")'.
To allow access to only some names, in a module with dangerous
contents, you can write::
ModuleSecurityInfo('module_name').declarePublic('name1',
'name2', ...)
If the module contains a class that you want to use, you will need
to add the following::
from <module_name> import <class>
allow_class(<class>)
Certain modules, such as 'sha', provide extension types instead of
classes. Security declarations typically cannot be added to
extension types, so the only way to use this sort of module is to
write a Python wrapper class, or use External Methods.
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
def manage_addPythonScript(id, REQUEST=None):
"""Add a Python script to a folder.
"""
class PythonScript:
"""
Python Scripts contain python code that gets executed when you call the
script by:
o Calling the script through the web by going to its location with a
web browser.
o Calling the script from another script object.
o Calling the script from a method object, such as a DTML Method.
Python Scripts can contain a "safe" subset of the python language.
Python Scripts must be safe because they can be potentially edited by
many different users through an insecure medium like the web. The
following safety issues drive the need for secure Python Scripts:
o Because many users can use Zope, a Python Script must make sure it
does not allow a user to do something they are not allowed to do,
like deleting an object they do not have permission to delete.
Because of this requirement, Python Scripts do many security checks
in the course of their execution.
o Because Python Scripts can be edited through the insecure medium of
the web, they are not allowed access to the Zope server's
file-system. Normal Python builtins like 'open' are, therefore,
not allowed.
o Because many standard Python modules break the above two security
restrictions, only a small subset of Python modules may be imported
into a Python Scripts with the "import" statement unless they have
been validated by Zope's security policy. Currently, the following
standard python modules have been validated:
o string
o math
o random
o Products.PythonScripts.standard
o Because it allows you to execute arbitrary python code, the python
"exec" statement is not allowed in Python methods.
o Because they may represent or cause security violations, some
Python builtin functions are not allowed. The following
Python builtins are not allowed:
o open
o input
o raw_input
o eval
o execfile
o compile
o type
o coerce
o intern
o dir
o globals
o locals
o vars
o buffer
o reduce
o Other builtins are restricted in nature. The following builtins
are restricted:
range -- Due to possible memory denial of service attacks, the
range builtin is restricted to creating ranges less than 10,000
elements long.
filter, map, tuple, list -- For the same reason, builtins
that construct lists from sequences do not operate on strings.
getattr, setattr, delattr -- Because these may enable Python
code to circumvent Zope's security system, they are replaced with
custom, security constrained versions.
o In order to be consistent with the Python expressions
available to DTML, the builtin functions are augmented with a
small number of functions and a class:
o test
o namespace
o render
o same_type
o DateTime
o Because the "print" statement cannot operate normally in Zope,
its effect has been changed. Rather than sending text to
stdout, "print" appends to an internal variable. The special
builtin name "printed" evaluates to the concatenation of all
text printed so far during the current execution of the
script.
"""
__constructor__ = manage_addPythonScript
__extends__=(
'PythonScripts.Script.Script',
)
def ZPythonScriptHTML_editAction(REQUEST, title, params, body):
"""
Change the script's main parameters. This method accepts the
following arguments:
REQUEST -- The current request.
title -- The new value of the Python Script's title. This must
be a string.
params -- The new value of the Python Script's parameters. Must
be a comma seperated list of values in valid python function
signature syntax. If it does not contain a valid signature
string, a SyntaxError is raised.
body -- The new value of the Python Script's body. Must contain
valid Python syntax. If it does not contain valid Python syntax,
a SyntaxError is raised.
"""
def ZPythonScript_setTitle(title):
"""
Change the script's title. This method accepts one argument,
'title' which is the new value for the script's title and must be a
string.
"""
def ZPythonScript_edit(params, body):
"""
Change the parameters and body of the script. This method accepts
two arguments:
params -- The new value of the Python Script's parameters. Must
be a comma seperated list of values in valid python function
signature syntax. If it does not contain a valid signature
string, a SyntaxError is raised.
body -- The new value of the Python Script's body. Must contain
valid Python syntax. If it does not contain valid Python syntax,
a SyntaxError is raised.
"""
def ZPythonScriptHTML_upload(REQUEST, file=''):
"""
Pass the text in file to the 'write' method.
"""
def ZScriptHTML_tryParams():
"""
Return a list of the required parameters with which to
test the script.
"""
def read():
"""
Return the body of the Python Script, with a special comment
block prepended. This block contains meta-data in the form of
comment lines as expected by the 'write' method.
"""
def write(text):
"""
Change the script by parsing the text argument into parts.
Leading lines that begin with '##' are stripped off, and if
they are of the form '##name=value', they are used to set
meta-data such as the title and parameters. The remainder of
the text is set as the body of the Python Script.
"""
def document_src(REQUEST=None, RESPONSE=None):
"""
Return the text of the 'read' method, with content type
'text/plain' set on the RESPONSE.
"""
Edit View: Edit A Script (Python)
Description
This view allows you to edit the logic which composes a script
in Python. Script instances execute in a restricted
context, bounded by your user's privilege level in Zope, and
certain global restrictions of all through-the-web code. For
information about what you "can" and "cannot" do in a Script
instance as opposed to non-through-the-web Python,
see the API Reference documentation for "Script (Python)" in
this help system.
Controls
'Title' -- Allows you to specify the Zope title of the script.
'Id' -- Allows you to specify the id of the script.
'Parameter List' -- Enter function parameters for this script
separated by commas. For example: *foo, bar, baz*
Status Elements
'Bound Names' -- the names used by this script for bindings.
You may use these names in the body of the script to refer to
bound elements. The defaults are::
context -- the script's "parent" respective to acquisition.
container -- the script's "parent" respective to containment.
script -- the script object itself.
traverse_subpath -- if the script is called directly from a URL,
this is the portion of the URL path after the script's name,
split at slash separators, into a list of strings. If the script
was not called directly from a URL, this will be an empty list.
Another possible name binding, to the "namespace" object, is
not set by default. If this was set, if the Script was
called from DTML, it would represent the namespace of the
calling DTML object.
More information about bindings can be found by visiting the
help screens of the "Bindings" tab of a Script (Python)
instance.
Buttons and Other Form Elements
'Save Changes' -- saves changes you make to the body, title, or
parameter list.
'Taller'/'Shorter'/'Wider'/'Narrower' -- make the body textarea
taller, shorter, wider, or narrower.
'File' -- upload a file into this Script (Python) instance.
File Upload Details
Files uploaded into a Script (Python) instance may either
consist only of the actual body of the function, or the file
containing the function body may contain at its head a set of
lines starting with "##" which describe bindings, parameters,
and the title. For example, a file uploaded into a Script
(Python) instance might be simply::
return "Hello"
If you upload this file into a Script (Python) instance, the
existing settings (or default settings) for bindings,
parameters, and title will remain.
However, if you wished to, you could develop a Script (Python)
on disk which looked like::
## Script (Python) "foo"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=goop, fudge
##title=
##
return "Fudge was %s, goop was %s" % (fudge, goop)
The lines preceded by "##" are metadata about the Script
(Python) instance which can survive a round trip via FTP or
through the web interface. When these lines are encountered
by the parser after an upload (or webform save), they serve to
*modify* the settings of the Script (Python) instance with the
metadata contained within the blocked area.
Lines beginning with "##" without any spaces after the "##"
are contextually meaningful to the file upload parser. There
are three keywords which can directly follow a "##": "bind",
"parameters", and "title".
The "bind" keyword following a "##" binds a name to a object
in the context this Script (Python) instance's body. For
example, the line "##bind container=goober" binds the name
"goober" to the acquisition parent of the script, allowing you
to refer to "goober" in the script body. Legal objects to
which to bind are: container, context, namespace, script, and
subpath. See the help available from the "bindings" tab of
Script (Python) instances for more details about what bindings
mean.
The "title" keyword following a "##" provides a title to the
script. E.g. "title=A Really Neat Script"
The "parameters" keyword following a "##" provides parameters
to the Script (Python) instance. E.g. "parameters=foo,bar,baz".
Test View: Test a Script (Python)
Description
This view allows you to test a Script (Python) instance.
Controls
If a Script has no parameters, when the "Test" tab is
visited the return value of the script will be presented in
the manage_main frame.
However, if a Script instance has parameters, a form
will be presented with fields for "Parameter" and "Value",
changeable on a per-parameter basis. These accept string
values. The 'Run Script' button runs the script after the
'Parameter' and 'Value' fields have been filled in, and
returns the results in the manage_main frame.
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
class Script:
"""
Web-callable script base interface.
"""
def ZScriptHTML_tryAction(REQUEST, argvars):
"""
Apply the test parameters provided by the dictionary 'argvars'.
This will call the current script with the given arguments and
return the result.
"""
"""
Products.PythonScripts.standard: Utility functions and classes
The functions and classes in this module are available from
Python-based scripts, DTML, and Page Templates.
"""
def whole_dollars(number):
"""
Show a numeric value with a dollar symbol.
"""
def dollars_and_cents(number):
"""
Show a numeric value with a dollar symbol and two decimal places.
"""
def structured_text(s):
"""
Convert a string in structured-text format to HTML.
See Also
"Structured-Text Rules":http://dev.zope.org/Members/jim/StructuredTextWiki/StructuredTextNGRules
"""
def sql_quote(s):
"""
Convert single quotes to pairs of single quotes. This is needed to
safely include values in Standard Query Language (SQL) strings.
"""
def html_quote(s):
"""
Convert characters that have special meaning in HTML to HTML
character entities.
See Also
"Python 'cgi' module":http://www.python.org/doc/current/lib/Functions_in_cgi_module.html
'escape' function.
"""
def url_quote(s):
"""
Convert characters that have special meaning in URLS to HTML
character entities using decimal values.
See Also
"Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html
'quote' function.
"""
def url_quote_plus(s):
"""
Like url_quote but also replace blank space characters with
'+'. This is needed for building query strings in some cases.
See Also
"Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html
'quote_plus' function.
"""
def url_unquote(s):
"""
Convert HTML %xx character entities into the characters they
represent. (Undoes the affects of url_quote).
See Also
"Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html
'unquote' function.
"""
def url_unquote_plus(s):
"""
Like url_unquote but also replace '+' characters with blank spaces.
See Also
"Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html
'unquote_plus' function.
"""
def urlencode(query, doseq=0):
"""
Convert a mapping object (such as a dictionary) or a sequence of
two-element tuples to a URL encoded query string. Useful for generating
query strings programmatically.
See Also
"Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html
'urlencode' function.
"""
def newline_to_br(s):
"""
Convert newlines and carriage-return and newline combinations to
break tags.
"""
def thousand_commas(number):
"""
Insert commas every three digits to the left of a decimal point in
values containing numbers. For example, the value, "12000
widgets" becomes "12,000 widgets".
"""
class DTML:
"""
DTML - temporary, security-restricted DTML objects
"""
def __init__(source, **kw):
"""
Create a DTML object with source text and keyword
variables. The source text defines the DTML source
content. The optinal keyword arguments define variables.
"""
def call(client=None, REQUEST={}, **kw):
"""
Render the DTML.
To accomplish its task, DTML often needs to resolve various
names into objects. For example, when the code &lt;dtml-var
spam&gt; is executed, the DTML engine tries to resolve the
name 'spam'.
In order to resolve names, you must be pass a namespace to the
DTML. This can be done several ways:
* By passing a 'client' object - If the argument 'client' is
passed, then names are looked up as attributes on the
argument.
* By passing a 'REQUEST' mapping - If the argument 'REQUEST'
is passed, then names are looked up as items on the
argument. If the object is not a mapping, an TypeError
will be raised when a name lookup is attempted.
* By passing keyword arguments -- names and their values can
be passed as keyword arguments to the Method.
The namespace given to a DTML object is the composite of these
three methods. You can pass any number of them or none at
all. Names will be looked up first in the keyword argument,
next in the client and finally in the mapping.
"""
'''Examples for enabling Script import
This file contains example code that can be used to make various
standard Python modules available to Scripts.
In order to use the example code, create a directory called
"MyScriptModules", or something equally descriptive, in your
Zope's "Products" directory. Copy this file to a file called
"__init__.py" in the new directory. Edit the new file,
uncommenting the block of code for each module that you want to
make available for import by Scripts.
You can, of course, add your own code to your "__init__.py" for
modules that are not listed below. The list is not comprehensive,
but is provided as a decent cross-section of modules.
NB: Placing security assestions within the package/module you are trying
to import will not work unless that package/module is located in
your Products directory.
This is because that package/module would have to be imported for its
included security assertions to take effect, but to do
that would require importing a module without any security
declarations, which defeats the point of the restricted
python environment.
Products work differently as they are imported at Zope startup.
By placing a package/module in your Products directory, you are
asserting, among other things, that it is safe for Zope to check
that package/module for security assertions. As a result, please
be careful when place packages or modules that are not Zope Products
in the Products directory.
'''
from AccessControl import allow_module, allow_class, allow_type
from AccessControl import ModuleSecurityInfo
# These modules are pretty safe
# allow_module('base64')
# allow_module('binascii')
# allow_module('bisect')
# allow_module('colorsys')
# allow_module('crypt')
# Only parts of these modules should be exposed
# ModuleSecurityInfo('fnmatch').declarePublic('fnmatch', 'fnmatchcase')
# ModuleSecurityInfo('re').declarePublic('compile', 'findall',
# 'match', 'search', 'split', 'sub', 'subn', 'error',
# 'I', 'L', 'M', 'S', 'X')
# import re
# allow_type(type(re.compile('')))
# allow_type(type(re.match('x','x')))
# ModuleSecurityInfo('StringIO').declarePublic('StringIO')
# These modules allow access to other servers
# ModuleSecurityInfo('ftplib').declarePublic('FTP', 'all_errors',
# 'error_reply', 'error_temp', 'error_perm', 'error_proto')
# from ftplib import FTP
# allow_class(FTP)
# ModuleSecurityInfo('httplib').declarePublic('HTTP')
# from httplib import HTTP
# allow_class(HTTP)
# ModuleSecurityInfo('nntplib').declarePublic('NNTP',
# 'error_reply', 'error_temp', 'error_perm', 'error_proto')
# from httplib import NNTP
# allow_class(NNTP)
################################################################
# Monkey patch for LP #257276 (Hotfix-2008-08-12)
#
# This code is taken from the encodings module of Python 2.4.
# Note that this code is originally (C) CNRI and it is possibly not compatible
# with the ZPL and therefore should not live within svn.zope.org. However this
# checkin is blessed by Jim Fulton for now. The fix is no longer required with
# Python 2.5 and hopefully fixed in Python 2.4.6 release.
################################################################
# Written by Marc-Andre Lemburg (mal@lemburg.com).
# (c) Copyright CNRI, All Rights Reserved. NO WARRANTY.
import sys
def search_function(encoding):
# Cache lookup
entry = _cache.get(encoding, _unknown)
if entry is not _unknown:
return entry
# Import the module:
#
# First try to find an alias for the normalized encoding
# name and lookup the module using the aliased name, then try to
# lookup the module using the standard import scheme, i.e. first
# try in the encodings package, then at top-level.
#
norm_encoding = normalize_encoding(encoding)
aliased_encoding = _aliases.get(norm_encoding) or \
_aliases.get(norm_encoding.replace('.', '_'))
if aliased_encoding is not None:
modnames = [aliased_encoding,
norm_encoding]
else:
modnames = [norm_encoding]
for modname in modnames:
if not modname or '.' in modname:
continue
try:
mod = __import__(modname,
globals(), locals(), _import_tail)
if not mod.__name__.startswith('encodings.'):
continue
except ImportError:
pass
else:
break
else:
mod = None
try:
getregentry = mod.getregentry
except AttributeError:
# Not a codec module
mod = None
if mod is None:
# Cache misses
_cache[encoding] = None
return None
# Now ask the module for the registry entry
entry = tuple(getregentry())
if len(entry) != 4:
raise CodecRegistryError,\
'module "%s" (%s) failed to register' % \
(mod.__name__, mod.__file__)
for obj in entry:
if not callable(obj):
raise CodecRegistryError,\
'incompatible codecs in module "%s" (%s)' % \
(mod.__name__, mod.__file__)
# Cache the codec registry entry
_cache[encoding] = entry
# Register its aliases (without overwriting previously registered
# aliases)
try:
codecaliases = mod.getaliases()
except AttributeError:
pass
else:
for alias in codecaliases:
if not _aliases.has_key(alias):
_aliases[alias] = modname
# Return the registry entry
return entry
if sys.version_info[:2] < (2, 5):
import encodings
encodings.search_function.func_code = search_function.func_code
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Python Scripts standard utility module
This module provides helpful functions and classes for use in Python
Scripts. It can be accessed from Python with the statement
"import Products.PythonScripts.standard"
"""
__version__='$Revision: 1.14 $'[11:-2]
from urllib import urlencode
from AccessControl.SecurityInfo import ModuleSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from App.special_dtml import HTML
from DocumentTemplate.DT_Var import special_formats
from DocumentTemplate.DT_Var import whole_dollars
from DocumentTemplate.DT_Var import dollars_and_cents
from DocumentTemplate.DT_Var import structured_text
from DocumentTemplate.DT_Var import sql_quote
from DocumentTemplate.DT_Var import html_quote
from DocumentTemplate.DT_Var import url_quote
from DocumentTemplate.DT_Var import url_quote_plus
from DocumentTemplate.DT_Var import newline_to_br
from DocumentTemplate.DT_Var import thousands_commas
from DocumentTemplate.DT_Var import url_unquote
from DocumentTemplate.DT_Var import url_unquote_plus
from DocumentTemplate.DT_Var import restructured_text
from DocumentTemplate.security import RestrictedDTML
from ZPublisher.HTTPRequest import record
security = ModuleSecurityInfo()
security.declarePublic('special_formats',
'whole_dollars',
'dollars_and_cents',
'structured_text',
'restructured_text',
'sql_quote',
'html_quote',
'url_quote',
'url_quote_plus',
'newline_to_br',
'thousands_commas',
'url_unquote',
'url_unquote_plus',
'urlencode',
)
security.declarePublic('DTML')
class DTML(RestrictedDTML, HTML):
"""DTML objects are DocumentTemplate.HTML objects that allow
dynamic, temporary creation of restricted DTML."""
def __call__(self, client=None, REQUEST={}, RESPONSE=None, **kw):
"""Render the DTML given a client object, REQUEST mapping,
Response, and key word arguments."""
security=getSecurityManager()
security.addContext(self)
try:
return HTML.__call__(self, client, REQUEST, **kw)
finally: security.removeContext(self)
# We don't expose classes directly to restricted code
class _Object(record):
_guarded_writes = 1
def __init__(self, **kw):
self.update(kw)
def __setitem__(self, key, value):
key = str(key)
if key.startswith('_'):
raise ValueError, ('Object key %s is invalid. '
'Keys may not begin with an underscore.' % `key`)
self.__dict__[key] = value
def update(self, d):
for key in d.keys():
# Ignore invalid keys, rather than raising an exception.
try:
skey = str(key)
except:
continue
if skey==key and not skey.startswith('_'):
self.__dict__[skey] = d[key]
def __hash__(self):
return id(self)
security.declarePublic('Object')
def Object(**kw):
return _Object(**kw)
security.apply(globals())
##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test Bindings
$Id$
"""
import unittest
import ZODB
import transaction
from Acquisition import Implicit
from AccessControl import ClassSecurityInfo
from AccessControl.class_init import InitializeClass
from OFS.ObjectManager import ObjectManager
from OFS.Folder import Folder
class SecurityManager:
def __init__(self, reject=0):
self.calls = []
self.reject = reject
def validate(self, *args):
from AccessControl import Unauthorized
self.calls.append(('validate', args))
if self.reject:
raise Unauthorized
return 1
def validateValue(self, *args):
from AccessControl import Unauthorized
self.calls.append(('validateValue', args))
if self.reject:
raise Unauthorized
return 1
def checkPermission(self, *args):
self.calls.append(('checkPermission', args))
return not self.reject
def addContext(self, *args):
self.calls.append(('addContext', args))
return 1
def removeContext(self, *args):
self.calls.append(('removeContext', args))
return 1
class UnderprivilegedUser:
def getId(self):
return 'underprivileged'
def allowed(self, object, object_roles=None):
return 0
class RivilegedUser:
def getId(self):
return 'privileged'
def allowed(self, object, object_roles=None):
return 1
class FauxRoot(ObjectManager):
def getPhysicalPath(self):
return ('',)
def __repr__(self):
return '<FauxRoot>'
class FauxFolder(Folder):
security = ClassSecurityInfo()
security.declareObjectPrivate()
security.declarePrivate('__repr__')
def __repr__(self):
return '<FauxFolder: %s>' % self.getId()
security.declarePublic('methodWithRoles')
def methodWithRoles(self):
return 'method called'
InitializeClass(FauxFolder)
class TestBindings(unittest.TestCase):
def setUp(self):
from Testing.ZODButil import makeDB
transaction.begin()
self.db = makeDB()
self.connection = self.db.open()
def tearDown(self):
from Testing.ZODButil import cleanDB
from AccessControl.SecurityManagement import noSecurityManager
noSecurityManager()
transaction.abort()
self.connection.close()
self.db.close()
cleanDB()
def _getRoot(self):
from Testing.makerequest import makerequest
#true_root = self.connection.root()[ 'Application' ]
#true_root = self.connection.root()
#return makerequest(true_root)
return makerequest(FauxRoot())
def _makeTree(self):
root = self._getRoot()
guarded = FauxFolder()
guarded._setId('guarded')
guarded.__roles__ = ( 'Manager', )
root._setOb('guarded', guarded)
guarded = root._getOb('guarded')
open = FauxFolder()
open._setId('open')
open.__roles__ = ( 'Anonymous', )
guarded._setOb('open', open)
bound_unused_container_ps = self._newPS('return 1')
guarded._setOb('bound_unused_container_ps', bound_unused_container_ps)
bound_used_container_ps = self._newPS('return container.id')
guarded._setOb('bound_used_container_ps', bound_used_container_ps)
bound_used_container_ok_ps = self._newPS('return container.id')
open._setOb('bound_used_container_ok_ps', bound_used_container_ok_ps)
bound_unused_context_ps = self._newPS('return 1')
guarded._setOb('bound_unused_context_ps', bound_unused_context_ps)
bound_used_context_ps = self._newPS('return context.id')
guarded._setOb('bound_used_context_ps', bound_used_context_ps)
bound_used_context_methodWithRoles_ps = self._newPS(
'return context.methodWithRoles()')
guarded._setOb('bound_used_context_methodWithRoles_ps',
bound_used_context_methodWithRoles_ps)
container_ps = self._newPS('return container')
guarded._setOb('container_ps', container_ps)
container_str_ps = self._newPS('return str(container)')
guarded._setOb('container_str_ps', container_str_ps)
context_ps = self._newPS('return context')
guarded._setOb('context_ps', context_ps)
context_str_ps = self._newPS('return str(context)')
guarded._setOb('context_str_ps', context_str_ps)
return root
def _newPS(self, txt, bind=None):
from Products.PythonScripts.PythonScript import PythonScript
ps = PythonScript('ps')
#ps.ZBindings_edit(bind or {})
ps.write(txt)
ps._makeFunction()
return ps
# These test that the mere binding of context or container, when the
# user doesn't have access to them, doesn't raise an unauthorized. An
# exception *will* be raised if the script attempts to use them. This
# is a b/w compatibility hack: see Bindings.py for details.
def test_bound_unused_container(self):
from AccessControl.SecurityManagement import newSecurityManager
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
ps = guarded._getOb('bound_unused_container_ps')
self.assertEqual(ps(), 1)
def test_bound_used_container(self):
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl import Unauthorized
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
ps = guarded._getOb('bound_used_container_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('container_str_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('container_ps')
container = ps()
self.assertRaises(Unauthorized, container)
self.assertRaises(Unauthorized, container.index_html)
try:
str(container)
except Unauthorized:
pass
else:
self.fail("str(container) didn't raise Unauthorized!")
ps = guarded._getOb('bound_used_container_ps')
ps._proxy_roles = ( 'Manager', )
ps()
ps = guarded._getOb('container_str_ps')
ps._proxy_roles = ( 'Manager', )
ps()
def test_bound_used_container_allowed(self):
from AccessControl.SecurityManagement import newSecurityManager
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
open = guarded._getOb('open')
ps = open.unrestrictedTraverse('bound_used_container_ok_ps')
self.assertEqual(ps(), 'open')
def test_bound_unused_context(self):
from AccessControl.SecurityManagement import newSecurityManager
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
ps = guarded._getOb('bound_unused_context_ps')
self.assertEqual(ps(), 1)
def test_bound_used_context(self):
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl import Unauthorized
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
ps = guarded._getOb('bound_used_context_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('context_str_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('context_ps')
context = ps()
self.assertRaises(Unauthorized, context)
self.assertRaises(Unauthorized, context.index_html)
try:
str(context)
except Unauthorized:
pass
else:
self.fail("str(context) didn't raise Unauthorized!")
ps = guarded._getOb('bound_used_context_ps')
ps._proxy_roles = ( 'Manager', )
ps()
ps = guarded._getOb('context_str_ps')
ps._proxy_roles = ( 'Manager', )
ps()
def test_bound_used_context_allowed(self):
from AccessControl.SecurityManagement import newSecurityManager
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
open = guarded._getOb('open')
ps = open.unrestrictedTraverse('bound_used_context_ps')
self.assertEqual(ps(), 'open')
def test_ok_no_bindings(self):
from AccessControl.SecurityManagement import newSecurityManager
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
boundless_ps = self._newPS('return 42')
guarded._setOb('boundless_ps', boundless_ps)
boundless_ps = guarded._getOb('boundless_ps')
#
# Clear the bindings, so that the script may execute.
#
boundless_ps.ZBindings_edit( {'name_context': '',
'name_container': '',
'name_m_self': '',
'name_ns': '',
'name_subpath': ''})
self.assertEqual(boundless_ps(), 42)
def test_bound_used_context_method_w_roles(self):
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl import Unauthorized
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
# Assert that we can call a protected method, even though we have
# no access to the context directly.
ps = guarded._getOb('bound_used_context_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('bound_used_context_methodWithRoles_ps')
self.assertEqual(ps(), 'method called')
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestBindings))
return suite
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import os, unittest, warnings
from Products.PythonScripts.PythonScript import PythonScript
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from RestrictedPython.tests.verify import verify
if __name__=='__main__':
here = os.getcwd()
else:
here = os.path.dirname(__file__)
if not here:
here = os.getcwd()
class WarningInterceptor:
_old_stderr = None
_our_stderr_stream = None
def _trap_warning_output( self ):
if self._old_stderr is not None:
return
import sys
from StringIO import StringIO
self._old_stderr = sys.stderr
self._our_stderr_stream = sys.stderr = StringIO()
def _free_warning_output( self ):
if self._old_stderr is None:
return
import sys
sys.stderr = self._old_stderr
# Test Classes
def readf(name):
path = os.path.join(here, 'tscripts', '%s.ps' % name)
return open(path, 'r').read()
class VerifiedPythonScript(PythonScript):
def _newfun(self, code):
verify(code)
return PythonScript._newfun(self, code)
class PythonScriptTestBase(unittest.TestCase):
def setUp(self):
newSecurityManager(None, None)
def tearDown(self):
noSecurityManager()
def _newPS(self, txt, bind=None):
ps = VerifiedPythonScript('ps')
ps.ZBindings_edit(bind or {})
ps.write(txt)
ps._makeFunction()
if ps.errors:
raise SyntaxError, ps.errors[0]
return ps
def _filePS(self, fname, bind=None):
ps = VerifiedPythonScript(fname)
ps.ZBindings_edit(bind or {})
ps.write(readf(fname))
ps._makeFunction()
if ps.errors:
raise SyntaxError, ps.errors[0]
return ps
class TestPythonScriptNoAq(PythonScriptTestBase):
def testEmpty(self):
empty = self._newPS('')()
self.failUnless(empty is None)
def testIndented(self):
# This failed to compile in Zope 2.4.0b2.
res = self._newPS('if 1:\n return 2')()
self.assertEqual(res, 2)
def testReturn(self):
res = self._newPS('return 1')()
self.assertEqual(res, 1)
def testReturnNone(self):
res = self._newPS('return')()
self.failUnless(res is None)
def testParam1(self):
res = self._newPS('##parameters=x\nreturn x')('txt')
self.assertEqual(res, 'txt')
def testParam2(self):
eq = self.assertEqual
one, two = self._newPS('##parameters=x,y\nreturn x,y')('one','two')
eq(one, 'one')
eq(two, 'two')
def testParam26(self):
import string
params = string.letters[:26]
sparams = ','.join(params)
ps = self._newPS('##parameters=%s\nreturn %s' % (sparams, sparams))
res = ps(*params)
self.assertEqual(res, tuple(params))
def testArithmetic(self):
res = self._newPS('return 1 * 5 + 4 / 2 - 6')()
self.assertEqual(res, 1)
def testCollector2295(self):
res = self._newPS('if False:\n pass\n#hi')
def testCollector2295(self):
res = self._newPS('if False:\n pass\n#hi')
def testReduce(self):
res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7])')()
self.assertEqual(res, 16)
res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7], 1)')()
self.assertEqual(res, 17)
def testImport(self):
eq = self.assertEqual
a, b, c = self._newPS('import string; return string.split("a b c")')()
eq(a, 'a')
eq(b, 'b')
eq(c, 'c')
def testWhileLoop(self):
res = self._filePS('while_loop')()
self.assertEqual(res, 1)
def testForLoop(self):
res = self._filePS('for_loop')()
self.assertEqual(res, 10)
def testMutateLiterals(self):
eq = self.assertEqual
l, d = self._filePS('mutate_literals')()
eq(l, [2])
eq(d, {'b': 2})
def testTupleUnpackAssignment(self):
eq = self.assertEqual
d, x = self._filePS('tuple_unpack_assignment')()
eq(d, {'a': 0, 'b': 1, 'c': 2})
eq(x, 3)
def testDoubleNegation(self):
res = self._newPS('return not not "this"')()
self.assertEqual(res, 1)
def testTryExcept(self):
eq = self.assertEqual
a, b = self._filePS('try_except')()
eq(a, 1)
eq(b, 1)
def testBigBoolean(self):
res = self._filePS('big_boolean')()
self.failUnless(res)
def testFibonacci(self):
res = self._filePS('fibonacci')()
self.assertEqual(
res, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
46368, 75025, 121393, 196418, 317811, 514229, 832040,
1346269, 2178309, 3524578, 5702887, 9227465, 14930352,
24157817, 39088169, 63245986])
def testSimplePrint(self):
res = self._filePS('simple_print')()
self.assertEqual(res, 'a 1 []\n')
def testComplexPrint(self):
res = self._filePS('complex_print')()
self.assertEqual(res, 'double\ndouble\nx: 1\ny: 0 1 2\n\n')
def testNSBind(self):
f = self._filePS('ns_bind', bind={'name_ns': '_'})
bound = f.__render_with_namespace__({'yes': 1, 'no': self.fail})
self.assertEqual(bound, 1)
def testNSBindInvalidHeader(self):
self.assertRaises(SyntaxError, self._filePS, 'ns_bind_invalid')
def testBooleanMap(self):
res = self._filePS('boolean_map')()
self.failUnless(res)
def testGetSize(self):
f = self._filePS('complex_print')
self.assertEqual(f.get_size(), len(f.read()))
def testSet(self):
res = self._newPS('from sets import Set; return len(Set([1,2,3]))')()
self.assertEqual(res, 3)
def testDateTime(self):
res = self._newPS("return DateTime('2007/12/10').strftime('%d.%m.%Y')")()
self.assertEqual(res, '10.12.2007')
def testRaiseSystemExitLaunchpad257269(self):
ps = self._newPS("raise SystemExit")
self.assertRaises(ValueError, ps)
def testEncodingTestDotTestAllLaunchpad257276(self):
ps = self._newPS("return 'foo'.encode('test.testall')")
self.assertRaises(LookupError, ps)
class TestPythonScriptErrors(PythonScriptTestBase):
def assertPSRaises(self, error, path=None, body=None):
assert not (path and body) and (path or body)
if body is None:
body = readf(path)
if error is SyntaxError:
self.assertRaises(SyntaxError, self._newPS, body)
else:
ps = self._newPS(body)
self.assertRaises(error, ps)
def testSubversiveExcept(self):
self.assertPSRaises(SyntaxError, path='subversive_except')
def testBadImports(self):
from zExceptions import Unauthorized
self.assertPSRaises(Unauthorized, body="from string import *")
self.assertPSRaises(Unauthorized, body="from datetime import datetime")
self.assertPSRaises(Unauthorized, body="import mmap")
def testAttributeAssignment(self):
# It's illegal to assign to attributes of anything that
# doesn't has enabling security declared.
# Classes (and their instances) defined by restricted code
# are an exception -- they are fully readable and writable.
cases = [("import string", "string"),
("def f(): pass", "f"),
]
assigns = ["%s.splat = 'spam'",
"setattr(%s, '_getattr_', lambda x, y: True)",
"del %s.splat",
]
for defn, name in cases:
for asn in assigns:
f = self._newPS(defn + "\n" + asn % name)
self.assertRaises(TypeError, f)
class TestPythonScriptGlobals(PythonScriptTestBase, WarningInterceptor):
def setUp(self):
PythonScriptTestBase.setUp(self)
def tearDown(self):
self._free_warning_output()
PythonScriptTestBase.tearDown(self)
def _exec(self, script, bound_names=None, args=None, kws=None):
if args is None:
args = ()
if kws is None:
kws = {}
bindings = {'name_container': 'container'}
f = self._filePS(script, bindings)
return f._exec(bound_names, args, kws)
def testGlobalIsDeclaration(self):
bindings = {'container': 7}
results = self._exec('global_is_declaration', bindings)
self.assertEqual(results, 8)
def test__name__(self):
f = self._filePS('class.__name__')
self.assertEqual(f(), ("'script.foo'>", "'string'"))
def test_filepath(self):
# This test is meant to raise a deprecation warning.
# It used to fail mysteriously instead.
def warnMe(message):
warnings.warn(message, stacklevel=2)
try:
f = self._filePS('filepath')
self._trap_warning_output()
results = f._exec({'container': warnMe}, (), {})
self._free_warning_output()
warning = self._our_stderr_stream.getvalue()
self.failUnless('UserWarning: foo' in warning)
except TypeError, e:
self.fail(e)
class PythonScriptInterfaceConformanceTests(unittest.TestCase):
def test_class_conforms_to_IWriteLock(self):
from zope.interface.verify import verifyClass
from webdav.interfaces import IWriteLock
verifyClass(IWriteLock, PythonScript)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestPythonScriptNoAq))
suite.addTest(unittest.makeSuite(TestPythonScriptErrors))
suite.addTest(unittest.makeSuite(TestPythonScriptGlobals))
suite.addTest(unittest.makeSuite(PythonScriptInterfaceConformanceTests))
return suite
def main():
unittest.TextTestRunner().run(test_suite())
if __name__ == '__main__':
main()
'ab'[1]
mab = {'a': 1, 'b': 2}
r10 = range(10)
return r10[3:5][1] == 4 and r10[mab['b']] and 1 < mab['ab'[r10[1]]] < 3
result = []
for c in 'abcdef':
result.append({'a-c': (c <= 'c') or None,
'd-f': (c >= 'd') and 2})
return result == [
{'a-c': 1, 'd-f': 0},
{'a-c': 1, 'd-f': 0},
{'a-c': 1, 'd-f': 0},
{'a-c': None, 'd-f': 2},
{'a-c': None, 'd-f': 2},
{'a-c': None, 'd-f': 2},
]
import string
class foo:
pass
return repr(foo).split()[1], repr(string).split()[1]
x = {'x': 1}
y = range(3)
print 'double'
print printed,
print 'x:', x['x']
print 'y:', y[0], y[1], y[2]
print
return printed
l = []
a, b = 0, 1
while b < 100000000:
l.append(b)
a, b = b, a+b
return l
return container('foo')
# This test is meant to raise a deprecation warning.
a = 0
for x in range(10):
a = a + 1
return a
l1 = [1, 2, 3]
l2 = [l1[0], l1[1]]
l2.extend(l1)
l2.append(4)
del l2[:2]
del l2[0]
l2[-2:] = []
d = {'a': 1, 'b': l2[0]}
d['a'] = 0
del d['a']
return l2, d
# -*- python -*-
# An attempt to bind an illegal name in an except clause
try:
1/0
except ZeroDivisionError, __getattr__:
pass
a = 0
b = 1
try:
int('$')
except ValueError:
a = 1
try:
int('1')
except:
b = 0
return a, b
d = {}
(d['a'], d['b'], d['c'], x) = range(4)
return d, x
# Example code:
# Import a standard function, and get the HTML request and response objects.
from Products.PythonScripts.standard import html_quote
request = container.REQUEST
response = request.response
# Return a string identifying this script.
print "This is the", script.meta_type, '"%s"' % script.getId(),
if script.title:
print "(%s)" % html_quote(script.title),
print "in", container.absolute_url()
return printed
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add Python Script',
)">
<p class="form-help">
Python Scripts allow you to add functionality to Zope by writing
scripts in the Python programming language
that are exposed as callable Zope objects. You may choose to upload
the script from a local file by typing the file name or using the
<em>browse</em> button.
</p>
<form action="manage_addPythonScript" method="post"
enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
File
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
<input class="form-element" type="submit" name="submit"
value=" Add and Edit " />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="&dtml-URL1;" method="post">
<input type="hidden" name=":default_method" value="ZPythonScriptHTML_changePrefs">
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<dtml-with keyword_args mapping>
<tr>
<td align="left" valign="top">
<div class="form-optional">
Title
</div>
</td>
<td align="left" valign="top" width="99%">
<input type="text" name="title" size="40"
value="&dtml-title;" />
</td>
</tr>
<tr>
<td align="left" valign="top" nowrap>
<div class="form-optional">
Parameter List
</div>
</td>
<td align="left" valign="top">
<input type="text" name="params" size="40"
value="&dtml-params;" />
</td>
</tr>
</dtml-with>
<dtml-with getBindingAssignments>
<dtml-if getAssignedNamesInOrder>
<tr>
<td align="left" valign="top">
<div class="form-label">
Bound Names
</div>
</td>
<td align="left" valign="top">
<div class="form-text">
<dtml-in getAssignedNamesInOrder>
&dtml-sequence-item;<dtml-unless sequence-end>, </dtml-unless>
</dtml-in>
</div>
</td>
</tr>
</dtml-if>
</dtml-with>
<tr>
<td align="left" valign="top">
<div class="form-label">
Last Modified
</div>
</td>
<td align="left" valign="top">
<div class="form-text">
<dtml-var bobobase_modification_time fmt="%Y-%m-%d %H:%M">
</div>
</td>
</tr>
<dtml-if errors>
<tr>
<td align="left" valign="middle" class="form-label">Errors</td>
<td align="left" valign="middle" style="background-color: #FFDDDD">
<pre><dtml-var expr="'\n'.join(errors)" html_quote></pre>
</td>
</tr>
</dtml-if>
<dtml-if warnings>
<tr>
<td align="left" valign="middle" class="form-label">Warnings</td>
<td align="left" valign="middle" style="background-color: #FFEEDD">
<pre><dtml-var expr="'\n'.join(warnings)" html_quote></pre>
</td>
</tr>
</dtml-if>
<dtml-with keyword_args mapping>
<tr>
<td align="left" valign="top" colspan="2">
<div style="width: 100%;">
<dtml-let cols="REQUEST.get('dtpref_cols', '100%')"
rows="REQUEST.get('dtpref_rows', '20')">
<dtml-if "cols[-1]=='%'">
<textarea name="body:text" wrap="off" style="width: &dtml-cols;;"
<dtml-else>
<textarea name="body:text" wrap="off" cols="&dtml-cols;"
</dtml-if>
rows="&dtml-rows;">&dtml-body;</textarea>
</dtml-let>
</div>
</td>
</tr>
</dtml-with>
<tr>
<td align="left" valign="top" colspan="2">
<div class="form-element">
<dtml-if wl_isLocked>
<em>Locked by WebDAV</em>
<dtml-else>
<input class="form-element" type="submit"
name="ZPythonScriptHTML_editAction:method" value="Save Changes">
</dtml-if>
&nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller">
<input class="form-element" type="submit" name="height" value="Shorter">
<input class="form-element" type="submit" name="width" value="Wider">
<input class="form-element" type="submit" name="width" value="Narrower">
</div>
</td>
</tr>
</table>
</form>
<p class="form-help">
You may upload the source for &dtml-title_and_id; using the form below.
Choose an existing file from your local computer by clicking <em>browse</em>
The contents of the file should be a valid script with an optional
&quot;##data&quot; block at the start. You may click the following link
to <a href="document_src">view or download</a> the current source.
</p>
<form action="ZPythonScriptHTML_upload" method="post"
enctype="multipart/form-data">
<table cellpadding="2" cellspacing="0" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
File &nbsp;
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="">
</td>
</tr>
<tr>
<td></td>
<td align="left" valign="top">
<div class="form-element">
<dtml-if wl_isLocked>
<em>Locked by WebDAV</em>
<dtml-else>
<input class="form-element" type="submit" value="Upload File">
</dtml-if>
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
Proxy roles allow you to control the access that a script has. Proxy roles
replace the roles of the user who is executing the script. This can be used
to both expand and limit access to resources. Select the proxy roles for
this object from the list below.
</p>
<form action="manage_proxy" method="post">
<table cellpadding="2" cellspacing="0" border="0">
<tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Proxy Roles
</div>
</td>
<td align="left" valign="top">
<div class="form-element">
<select name="roles:list" size="7" multiple>
<dtml-in valid_roles>
<dtml-if expr="_vars['sequence-item'] != 'Shared'">
<option <dtml-if
expr="manage_haveProxy(_vars['sequence-item'])">selected</dtml-if
>>&dtml-sequence-item;</option>
</dtml-if>
</dtml-in valid_roles>
</select>
</div>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="2">
<div class="form-element">
<input class="form-element" type="submit" name="SUBMIT" value="Save Changes">
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
...@@ -17,6 +17,7 @@ nt-svcutils = 2.13.0 ...@@ -17,6 +17,7 @@ nt-svcutils = 2.13.0
Persistence = 2.13.2 Persistence = 2.13.2
Products.ExternalMethod = 2.13.0 Products.ExternalMethod = 2.13.0
Products.MIMETools = 2.13.0 Products.MIMETools = 2.13.0
Products.PythonScripts = 2.13.0
Products.ZCTextIndex = 2.13.0 Products.ZCTextIndex = 2.13.0
Record = 2.13.0 Record = 2.13.0
tempstorage = 2.11.3 tempstorage = 2.11.3
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment