File contents
# ZShell - (c) 2001 Jerome Alet
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#
# author: Jerome Alet - <[email protected]>
#
# This is the Zope shell, an attempt at making the most common
# Unix shell commands available from within Zope.
#
# Feel free to send me any feedback about this software at: [email protected]
#
# $Id: zshell.py,v 1.95 2001/06/02 22:31:53 jerome Exp $
#
# $Log: zshell.py,v $
# Revision 1.95 2001/06/02 22:31:53 jerome
# grep now accepts --owner, --mmin, --mtime, and --newer options
# Both find and grep now accept a --older option
# Overflow error fixed in descend()
#
# Revision 1.94 2001/06/02 16:53:57 jerome
# Correction in match_Owner(): When no --owner option is given, objects now
# always match, which is prefectly correct, contrary to the previous behavior.
# There's still a difference in the recursive behavior of grep and find, find is
# OK, grep is not. Don't know why yet.
#
# Revision 1.93 2001/06/02 11:16:04 jerome
# Modification for correct recursion liomit with maxdepth
#
# Revision 1.92 2001/05/31 19:32:24 jerome
# grep now accepts a --maxdepth option when --recurse is used
#
# Revision 1.91 2001/05/31 18:12:17 jerome
# cp/mv/cut/copy on inexistant objects doesn't raise a Zope Error anymore.
# cp/mv/paste now display more informations.
# history now accepts a --clear option to empty the .zshell_history
# DTML Document.
# history accepts multiple --user arguments to list only
# those commands run by the specified users (NOTarguments are
# allowed too).
# To modify the history document, the user needs the 'Change DTML Documents'
# permissions, he doesn't need a Manager role anymore.
#
# Revision 1.90 2001/05/30 22:18:04 jerome
# First shot at NOTarguments. Seem to work fine.
#
# Revision 1.89 2001/05/30 21:26:07 jerome
# Version number changed to 1.2
# Big reorganisation in command line option handling:
# we can now choose options which may be specified multiple times
# or only a single time on a particular command line:
# Some options may now be specified multiple times, and
# option arguments may use wildcards too: --type, --id, --owner, more to come.
# find's --user option was renamed to --owner
# find's --name option was renamed to --id
# ZClasses' meta_type attribute is callable, this may have raised errors
# because I didn't tested this. It is now fixed.
#
# Revision 1.88 2001/05/28 19:21:01 jerome
# Corrected export
#
# Revision 1.87 2001/05/25 22:00:49 jerome
# An invalid regular expression in grep doesn't cause
# a Zope Error anymore.
# adduser was renamed to addusers since it accepts multiple
# arguments.
# deluser was renamed to delusers since it accepts multiple
# arguments
# takeown now accepts --recurse instead of -R to ask for a
# recursive action.
#
# Revision 1.86 2001/05/25 14:59:10 jerome
# Forgotten a comment to disable the test for permissions
#
# Revision 1.85 2001/05/25 09:48:01 jerome
# setperms now works.
# locate deleted until I know enough about ZCatalog searching
# Version number changed to 1.0 !
#
# Revision 1.84 2001/05/25 08:55:01 jerome
# The lsperms command was added.
# The lsusers command's output is now easier to read.
#
# Revision 1.83 2001/05/25 07:06:35 jerome
# The .zshell_history DTML Document is automatically emptied if it
# still contains the default DTML Document's content.
#
# Revision 1.82 2001/05/25 06:48:25 jerome
# More complete history command.
#
# Revision 1.81 2001/05/24 21:38:44 jerome
# The history command now works.
#
# Revision 1.80 2001/05/24 20:18:04 jerome
# Don't know !
#
# Revision 1.79 2001/05/24 19:36:24 jerome
# Documentation changes wrt the GNU GPL.
#
# Revision 1.78 2001/05/24 15:09:46 jerome
# Version number changed to 1.0pre4
#
# Revision 1.77 2001/05/24 13:51:40 jerome
# Command dump added
#
# Revision 1.76 2001/05/24 13:22:34 jerome
# DocStrings fixes with the help of Jason Cunliffe.
#
# Revision 1.75 2001/05/24 11:16:32 jerome
# suckfs will not exist because wget now can do that, and more...
# Added some urls to the GNU GPL FAQ, backing my position.
# DocStrings fixes.
# ShellExpand now can expand from the filesystem too, but defaults
# to from ZODB.
#
# Revision 1.74 2001/05/23 14:16:01 jerome
# Modification time format in ls output is shorter now
#
# Revision 1.73 2001/05/23 13:52:15 jerome
# Version number changed to 1.0pre3
# manage, view and properties now work fine.
#
# Revision 1.72 2001/05/23 12:28:22 jerome
# The nipltd command works
# The shell wildcards expansion mechanism is now thread safe, thanks to Toby Dickenson
# (the code is there for a long time (2 days), but this info was forgotten when
# committing in CVS.
#
# Revision 1.71 2001/05/22 22:32:03 jerome
# Command export added.
#
# Revision 1.70 2001/05/22 08:17:33 jerome
# The su command was added and works
#
# Revision 1.69 2001/05/21 14:42:01 jerome
# Version number changed to 1.0pre2
# Some preliminary code put inside comments in run_su
#
# Revision 1.68 2001/05/21 10:56:50 jerome
# DocStrings fixes
#
# Revision 1.67 2001/05/21 10:37:44 jerome
# Version number changed to 1.0pre1 for publication
#
# Revision 1.66 2001/05/20 13:09:38 jerome
# There's now a single method to retrieve objects' paths, so if it
# is still incorrect it will be easily fixable.
#
# Revision 1.65 2001/05/20 08:25:59 jerome
# Urls in ls now work with SiteRoots and Apache ProxyPass/ProxyReverse
#
# Revision 1.64 2001/05/19 08:33:10 jerome
# TODO file added to repository.
# The cd command now displays the current directory correctly
# when an error occurs.
# Security checks are now done for all commands.
# DocStrings fixes.
#
# Revision 1.63 2001/05/18 18:16:03 jerome
# The roles command now works.
# The lrole command was renamed to lroles to be more
# consistent with the roles command.
# The lsusers command now lists both roles and local roles.
# The lsuser command was renamed to lsusers to be more
# consistent with the rest.
#
# Revision 1.62 2001/05/18 15:40:18 jerome
# The domains command now works
# The roles command now sort-of works
#
# Revision 1.61 2001/05/18 07:39:08 jerome
# Unexpanded quoted arguments are now unquoted before running the commands
#
# Revision 1.60 2001/05/17 21:30:41 jerome
# Command passwd now works.
#
# Revision 1.59 2001/05/17 20:48:26 jerome
# The wget command works again and saves retrived objects under their original
# id if possible.
#
# Revision 1.58 2001/05/17 20:09:00 jerome
# The command line analyze now takes care of more characters: _,()
# The call command works again.
# The call command doesn't use expanded wildcards anymore.
# Added skeletons for: mkobj, lsperms, setperms
#
# Revision 1.57 2001/05/17 13:29:30 jerome
# Version number changed to 1.0
#
# Revision 1.56 2001/05/16 09:32:28 jerome
# Added a TODO for manage command
#
# Revision 1.55 2001/05/16 09:31:07 jerome
# find was going too deep when maxdepth was used.
# finalisation of grep with replacing in PythonScripts as well
#
# Revision 1.54 2001/05/15 23:57:44 jerome
# Empty line
#
# Revision 1.53 2001/05/15 23:34:39 jerome
# Command grep added. The replace part needs some fix, and the
# rest need some more testing.
#
# Revision 1.52 2001/05/15 09:07:57 jerome
# Precisions in NEWS
# A better message when javascript is unavailable
#
# Revision 1.51 2001/05/14 15:09:12 jerome
# When javascript is disabled then there are links in the results instead of
# new windows openings.
#
# Revision 1.50 2001/05/14 14:53:55 jerome
# Unused variable in import
# google now opens a new window
#
# Revision 1.49 2001/05/14 14:47:50 jerome
# Command properties added.
# manage, view and properties now use the same code.
#
# Revision 1.48 2001/05/14 14:31:50 jerome
# Version changed to 0.9
# Commands view and manage added
#
# Revision 1.47 2001/05/13 22:46:33 jerome
# Commands lsuser, catalog, uncatalog and find added.
# Bug fixes on permissions.
# Links are now correct in ls output.
# ls output now includes the modification time
# access to objects from their path
# is now done using unrestrictedTraverse
# instead of my stincky previous code.
# Shell expansion is better: single quotes,
# double quotes, now work.
# deluser doesn't expand wildcards anymore.
# docstrings for deluser and adduser modified.
# doctsring for call modified to tell people to
# not use it yet.
# An unknown command is now displayed correctly.
#
# Revision 1.46 2001/05/13 15:24:18 jerome
# Typo
#
# Revision 1.45 2001/05/13 08:43:48 jerome
# In run_ls the object url is now relative, because otherwise url
# was very bad.
#
# Revision 1.44 2001/05/12 19:43:05 jerome
# In whoami the result was already safe html
# Some tris to use restrictedTraverse and/or unrestrictedTraverse, but
# without luck. Some debugging code added.
#
# Revision 1.43 2001/05/12 18:38:12 jerome
# The toObject method should be OK now, and uses restrictedTraverse,
# which seems to be severely buggy with regard to the handling of a single
# '/' as the path argument.
#
# Revision 1.42 2001/05/12 09:54:14 jerome
# Added some comments with a code sample on how to use shlex
#
# Revision 1.41 2001/05/11 21:45:33 jerome
# Commands catalog and uncatalog added.
# Preliminary version of the find command.
# Bug fixes on permissions.
#
# Revision 1.40 2001/05/11 13:10:45 jerome
# about command output changed
#
# Revision 1.39 2001/05/11 12:49:48 jerome
# Version changed to 0.7
# Commented out the last bit of "problematic" code in zshell()
# Ready for 0.7
#
# Revision 1.38 2001/05/10 21:58:40 jerome
# Some code deactivated: still problem it seems (nothing here).
# Some skeletons added: find, locate, suckfs, history, grep, replace,
# domains, roles, passwd
# Command mkuf added.
#
# Revision 1.37 2001/05/09 23:04:27 jerome
# Version changed to 0.6
# Now delprop, addprop, setprop and lsprop work fine
# Statically set the action modifier of the form to "zshell", hoping
# to solve the problem many have encountered.
#
# Revision 1.36 2001/05/09 21:34:37 jerome
# Now man, lsprop and ls uses the same method to output their results.
# Bug fixes because of the above.
# Use more Class HTML tag parameter to allow an easily tunnable UI.
#
# Revision 1.35 2001/05/09 18:04:38 jerome
# prop and ls now uses the same code to output their results
#
# Revision 1.34 2001/05/09 15:08:34 jerome
# delprop, lsprop added
# some UI changes for better configurability
#
# Revision 1.33 2001/05/09 12:42:24 jerome
# Some UI changes to make it more easily tunnable: Thanks to Peter Bengtsson.
#
# Revision 1.32 2001/05/09 12:06:36 jerome
# Version number changed to 0.6beta2
# chown command replaced with takeown, and works, found a bug in AccessControl/Owned.py
# in method changeOwner
#
# Revision 1.31 2001/05/08 23:32:13 jerome
# Bug fixes + Security checks.
# Some security checks are not done, because don't know on
# which permission to test. Some may still be incomplete if
# more than one permission is needed: needs testing and testers !
#
# Revision 1.30 2001/05/08 20:59:49 jerome
# Some bugfixes.
# Some comments clarified.
# ShellExpand now redirects all the os module methods which are used by glob.glob
# to our own methods.
# Tests to replace toObject() code with a single call to restrictedTraverse():
# doesn't work in ShellExpand, don't know why... So back to my own code which
# works partially.
#
# Revision 1.29 2001/05/08 15:56:14 jerome
# Some permission checks: untested
#
# Revision 1.28 2001/05/08 07:29:40 jerome
# whoami modified following Michel@DC's advice
#
# Revision 1.27 2001/05/07 23:33:20 jerome
# Version changed to 0.6
# Comments from Michel@DC included or corrections done.
#
# Revision 1.26 2001/05/07 22:32:10 jerome
# Bad test on folderish objects
#
# Revision 1.25 2001/05/07 22:20:56 jerome
# Prompt form named to zshellform
#
# Revision 1.24 2001/05/07 22:18:07 jerome
# Version changed to 0.5
# Commands shutdown and zhelp added
# Shell expansion of wildcards
# Many bugfixes
#
# Revision 1.23 2001/05/07 16:47:36 jerome
# Bugfixes
#
# Revision 1.22 2001/05/07 16:37:07 jerome
# Correction on javascript, with the help of Peter Bengtsson
#
# Revision 1.21 2001/05/07 15:01:39 jerome
# Typos
#
# Revision 1.20 2001/05/07 14:54:38 jerome
# Modifications for new look and output channels separation
#
# Revision 1.18 2001/05/07 13:28:05 jerome
# Look changes: looks far better now, IMHO
# Added an option to not run the commands.
#
# Revision 1.17 2001/05/07 09:38:11 jerome
# Now the final HTML result is created correctly.
# The action field of the prompt is now set correctly to
# the calling url instead of zshell itself.
# Most of the code from the zshell method was moved to
# the class' constructor.
# Some docstrings added.
#
# Revision 1.16 2001/05/06 22:06:43 jerome
# More methods now use the pseudo IO streams and HTML_document output
# 45 old returns last !
#
# Revision 1.15 2001/05/06 12:49:10 jerome
# Doesn't work: big changes not finished.
#
# Revision 1.14 2001/05/06 08:39:39 jerome
# Version number changed to 0.4
# Now needs partially the jaxml module
# Modifications for pseudo stdin, stdout and stderr
#
# Revision 1.13 2001/05/05 22:53:54 jerome
# NEWS file added
# Command call added
#
# Revision 1.12 2001/05/05 22:24:19 jerome
# toObject is better now
# save and discard doesn't run properly yet
#
# Revision 1.11 2001/05/05 21:26:05 jerome
# Preparation for accepting commands as external method's arguments: needs testing.
# Commands enter and leave are OK.
# Commands save and discard don't work because of a stinky toObject method.
#
# Revision 1.10 2001/05/05 13:34:34 jerome
# Bug corrections wrt acquisition.
# Commands mkver, enter and leave added
# Commands save and discard contents deleted: need some work.
#
# Revision 1.9 2001/05/04 15:05:03 jerome
# Version changed to 0.3
# Some preliminary methods to deal with versions
# Deprecated API replaced
#
# Revision 1.8 2001/05/04 13:48:30 jerome
# Bug corrections
# The wget command seems to work now
#
# Revision 1.7 2001/05/04 12:07:33 jerome
# Version changed to 0.2
# CREDITS file added
# Better handling of acquisition when working with the
# Folder hierarchy.
# Now remembers the current working directory from one "Run!" to the other.
#
# Revision 1.6 2001/05/04 10:51:42 jerome
# Added new methods:
# deluser, adduser
# lrole
# whoami
# about
# google
# wget
# mkdir
#
# Added the possibility to use zshell.css as a stylesheet
#
# Revision 1.5 2001/05/04 07:29:54 jerome
# Man arguments are now sorted
#
# Revision 1.4 2001/05/04 07:24:29 jerome
# Help for chown added
#
# Revision 1.3 2001/05/04 07:17:32 jerome
# Docstrings added
#
# Revision 1.2 2001/05/04 06:59:08 jerome
# README and COPYING files added
# CVS tags added to zshell.py
#
#
# standard modules
import sys
import os
import string
import re
import urllib
import cStringIO
import posixpath
import shlex
import rexec
import getopt
import fnmatch
import time
# jerome's modules
try :
import jaxml
except ImportError:
sys.stderr.write("It seems you lack the jaxml python module, download it from:\nhttp://cortex.unice.fr/~jerome/jaxml/\n")
sys.stderr.flush()
raise
# Zope modules
import Globals
import AccessControl, AccessControl.SecurityManagement
from OFS.DTMLDocument import DTMLDocument
from OFS.DTMLMethod import DTMLMethod
from OFS.CopySupport import CopyError
from Products.PythonScripts.PythonScript import PythonScript
from DateTime import DateTime
from ZODB import FileStorage
__version__ = "1.2"
__doc__ = """
ZShell is an external Zope method which allows you to manipulate
the ZODB using standard unix shell's commands from within Zope's
Management Interface in your web browser.
All commands do security checks and either work or exit with a
message, depending on your current privileges.
However, you should keep in mind that ZShell is very powerful:
use it carefully, do backups often, and use Zope's Undo facility
when needed.
If the use of ZShell leads to a data loss, your dog being killed, or
your wife/husband going away, then:
YOU HAVE BEEN WARNED !!!
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
"""
class ZopeShell :
# Just to be sure
__context = None
__version = None
__status = None
__stdin = None
__stdout = None
__stderr = None
__htmlfinal = None
__temphtml = None
__olderror = None
# User Interface:
__prompt_cols = 50 # prompt columns
__prompt_rows = 10 # prompt rows
__prompt_percent = 66 # prompt width in percent
__available_columns = 3 # number of columns on which to display available commands
__theadercolor = "gold" # table header color
__thead_bgcolor = "#4169e1" # table headings color
__trow_evencolor = "white" # even rows' color
__trow_oddcolor = "#dedede" # odd rows' color
def __init__(self, context, zshellscript = None) :
# the current working Folder may be stored in
# an hidden field in the form: check if it is or not
# and set private context accordingly
if hasattr(context, "REQUEST") and context.REQUEST.has_key("zshellpwd") :
pwdobj = self.toObject(context, context.REQUEST["zshellpwd"])
if pwdobj is not None :
self.__context = pwdobj
else :
self.__context = context
else :
self.__context = context
# get active version, if any
if self.__context.REQUEST.cookies.has_key(Globals.VersionNameName) :
self.__version = self.__context.REQUEST.cookies[Globals.VersionNameName]
else :
self.__version = None
# set initial status to None, meaning no running command
self.__status = None
# now we open the standard IO channels
self.__stdin = cStringIO.StringIO()
self.__stdout = cStringIO.StringIO()
self.__stderr = cStringIO.StringIO()
# create an empty HTML document for final result
self.__htmlfinal = jaxml.HTML_document()
# create an empty HTML document for commands results
self.__temphtml = jaxml.HTML_document()
# put intial content in the html final output
self.initHTML()
# get commands either from parameters or from REQUEST
commands = self.getCommands(zshellscript)
# we execute commands before displaying the prompt
# to be able to take care of a change of the current working directory.
# but we only execute them if zshelldontrun is not in the REQUEST, because
# this would mean that probably the user just wants the help
# and the insertion of an empty command in the textarea
if not self.__context.REQUEST.has_key("zshelldontrun") :
status = self.execCommands(commands)
else :
# only executes the man commands, ignore the rest
status = self.execCommands(filter(lambda f: f[:4] == "man ", commands))
# displays the prompt
self.showPrompt(commands)
def __del__(self) :
# try to take care of closing and freeing the IO channels
if self.__stdin is not None :
self.__stdin.close()
del self.__stdin
if self.__stdout is not None :
self.__stdout.close()
del self.__stdout
if self.__stderr is not None :
self.__stderr.close()
del self.__stderr
def getContext(self) :
return self.__context
def get_stdin(self) :
"""Returns the stdin stream's contents"""
if self.__stdin is not None :
# probably no flush
return self.__stdin.getvalue()
def get_stdout(self) :
"""Returns the stdout stream's contents"""
if self.__stdout is not None :
self.__stdout.flush()
return self.__stdout.getvalue()
def get_stderr(self) :
"""Returns the stderr stream's contents"""
if self.__stderr is not None :
self.__stderr.flush()
return self.__stderr.getvalue()
def get_HTML(self) :
"""Returns the final HTML output"""
# the final result is composed of two parts: the prompt and the results.
# we can "pseudo" concatenate them with the jaxml "+" operator:
if (self.__htmlfinal is not None) and (self.__temphtml is not None):
self.__htmlfinal = self.__htmlfinal + self.__temphtml
return str(self.__htmlfinal)
def getStatus(self) :
return self.__status
def initHTML(self) :
x = self.__htmlfinal
if x is not None :
x.html()
x._push()
x.head().title("The Zope Shell")
if hasattr(self.__context, "zshell.css") :
x._push()
x.link(rel="stylesheet", type="text/css", href="%s" % self.ObjectPath(getattr(self.__context, "zshell.css")))
x._pop()
x.script("function setfocus() { document.zshellform['zshellscript:lines'].focus(); document.zshellform['zshellscript:lines'].select(); }")
x._pop()
x.body(bgcolor = "white", onLoad="setfocus()")
def errormessage(self, msg) :
"""outputs an error message both to the screen and to the pseudo stderr stream
don't output anything if the previous message was the same.
"""
if msg != self.__olderror :
self.__stderr.write("%s\n" % msg)
self.htmlmessage(msg)
self.__olderror = msg[:]
return -1
def permissionProblem(self, object, perm) :
msg = "You don't have permission '%s' on"
path = self.ObjectPath(object)
if path is not None :
return self.errormessage((msg + " %s") % (perm, path))
else :
return self.errormessage((msg + " object %s") % (perm, repr(object)))
def roleProblem(self, object, role) :
msg = "You don't have role '%s' on"
path = self.ObjectPath(object)
if path is not None :
return self.errormessage((msg + " %s") % (role, path))
else :
return self.errormessage((msg + " object %s") % (role, repr(object)))
def htmlstr(self, unsafe) :
"""Formats a string to be shown safely in HTML"""
unsafe = string.replace(unsafe, '&', '&')
unsafe = string.replace(unsafe, '<', '<')
return string.replace(unsafe, '>', '>')
def htmlmessage(self, msg, safe = 0) :
"""prints a message in the html output"""
if not safe :
msg = self.htmlstr(msg)
self.printf("%s")
self.__temphtml._text("%s" % msg)
self.__temphtml._br()
def printf(self, msg) :
"""outputs a message only in stdout"""
self.__stdout.write("%s" % msg)
def newwindow(self, url) :
self.__temphtml._push()
self.__temphtml.script("window.open('%s')" % url).noscript().a("Please follow this link to see the result of your command", href="%s" % url)
self.__temphtml._pop()
return 0
def tableDisplay(self, name, labels, table) :
if table :
x = self.__temphtml
x._push()
x.table(border=1)
x._push()
x.tr(bgcolor=self.__theadercolor, Class="%stheader" % name)
for label in labels :
x.th(label, Class="%stheaderlabel" % name)
x._pop()
curcolor = -1
for item in table :
x._push()
if curcolor < 0 :
x.tr(bgcolor = self.__trow_oddcolor, Class="%srow" % name)
else :
x.tr(bgcolor = self.__trow_evencolor, Class="%srow" % name)
curcolor = -curcolor
for key in labels :
x._push()
x.td(("%s" % item[key]) or " ", Class="%s%s" % (name, key), align="left", valign="top")
x._pop()
x._pop()
x._pop()
def ShellExpand(self, path, zodb=1) :
"""Simulates a shell expansion using glob.glob and redirecting some of the os module functions to our own"""
def mylistdir(path, zopeshell = self) :
# the original os.listdir accepts only one argument, but we need
# both the path to scan, and a reference to the current object,
# so we "cheat" using a default argument which is never used
# by os.listdir.
obj = zopeshell.toObject(zopeshell.getContext(), path)
if (obj is not None) and hasattr(obj, "isPrincipiaFolderish") \
and obj.isPrincipiaFolderish and hasattr(obj, "objectIds") :
if zopeshell.HasPerms(obj, 'Access contents information', verbose = 0) :
return obj.objectIds()
return []
def myexists(path, zopeshell = self) :
# see above
return zopeshell.toObject(zopeshell.getContext(), path) is not None
def myisdir(path, zopeshell = self) :
# see above
while path and (path[-1] == '/') :
path = path[:-1]
obj = zopeshell.toObject(zopeshell.getContext(), path or '/')
if (obj is not None) and hasattr(obj, "isPrincipiaFolderish") :
return obj.isPrincipiaFolderish
else :
return 0
# builds a restricted execution environment.
# this way we are thread safe. Thanks to Toby Dickenson.
r_env = rexec.RExec()
osmodule = r_env.add_module('os')
for name,value in os.__dict__.items():
setattr(osmodule,name,value)
ospathmodule = r_env.add_module('os.path')
for name,value in os.path.__dict__.items():
setattr(ospathmodule,name,value)
if zodb :
# we want to expand wildcards from the ZODB
# so we plug in our own methods
osmodule.listdir = mylistdir
ospathmodule.join = posixpath.join
ospathmodule.split = posixpath.split
ospathmodule.exists = myexists
ospathmodule.isdir = myisdir
# do globbing using our own methods
# or the normal ones if zodb==0
r_env.s_exec('import glob')
result = r_env.r_eval('glob.glob')(path)
if not result :
result = [path] # returns the argument unmodified
return result
def ObjectPath(self, object) :
if hasattr(object, "absolute_url") :
url = object.absolute_url()
return '/' + string.join(string.split(url, '/')[3:], '/')
def toObject(self, curdir, path) :
# Michel@DC: ouch, it looks like you go about doing your own
# traversal over Zope objects here, this is bad.
# because of many layers of history, traversal is a
# delicate process that you will probably get wrong if
# you try to do it yourself. Instead of doing your
# own traversal, use
# self.__context.restrictedTraverse() to do the
# traversal for you (assuming __context is a Zope
# container) as a bonus, restrictedTraverse will
# enforce security for you.
# Jerome: Here's my try, but with unrestrictedTraverse.
# it seems that restrictedTraverse is a bit, hmmm..., too restricted !
path = posixpath.normpath(path)
try :
if path == '.' :
return self.__context
elif path == '..' and hasattr(self.__context,'aq_parent') :
if hasattr(self.__context, "isTopLevelPrincipiaApplicationObject") and self.__context.isTopLevelPrincipiaApplicationObject :
return self.__context
else :
return self.__context.aq_parent
else :
return curdir.unrestrictedTraverse(path)
except IndexError:
# BUG in Zope 2.3.2 ! => doesn't take care correctly of the / folder
# when the path argument contains only '/'
return curdir.getPhysicalRoot()
except AttributeError:
return None # nothing, e.g. acl_users/*
except KeyError:
return None # empty folder it seems
except TypeError:
return None # What the hell is this ?
def showPrompt(self, cmds) :
x = self.__htmlfinal
x._push()
x.p()
if self.__version :
x._push()
x.font(color="red").em("You're currently working in version %s" % self.__version)
x._pop()
x._br()
x.table(border=0, width="100%")
x._push()
x.tr(bgcolor="silver").th("Enter your commands below:", width="%i%%" % self.__prompt_percent, align="left", valign="middle").th("Available commands:", align="center", valign="middle")
x._pop()
x.tr()
x._push()
x.td(width="%i%%" % self.__prompt_percent, align="left", valign="top")
# Maybe the following line was causing problems,
# so its commented out for now
# x.form(name="zshellform", action = self.__context.REQUEST.URL0, method="POST")
# and replaced with this one:
x.form(name="zshellform", action = "%s" % self.__context.REQUEST.URL0, method="POST")
x.textarea("%s" % string.join(cmds, '\n'), Class="prompt", rows=self.__prompt_rows, cols=self.__prompt_cols, wrap="physical", name="zshellscript:lines")
x._br().em("Use the man command")._br()._submit(name="run", value="Run !")
# the line below explains why we must first run the commands: we must take
# care of the eventually new current folder
x._hidden(name="zshellpwd", value="%s" % self.ObjectPath(self.__context))
x._pop()
x.td(align="left", valign="top")
x.font(size="-1")
x.table(border=0, cellpadding=0, cellspacing=0, width="100%")
methodslist = map(lambda n: n[4:], filter(lambda f: f[:4] == 'run_', self.__class__.__dict__.keys()))
methodslist.sort()
nbmethods = len(methodslist)
for m in range(0, nbmethods, self.__available_columns) :
x._push()
x.tr(Class="availablerow")
for i in range(self.__available_columns) :
methodindex = m + i
if methodindex < nbmethods :
x._push()
x.td(Class="availablemethod", align="left", valign="top")
x.a("%s" % methodslist[methodindex], href="%s?zshellscript=man%%20%s&&zshellscript=%s&zshelldontrun=1" % (self.__context.REQUEST.URL0, methodslist[methodindex], methodslist[methodindex]))._br()
x._pop()
x._pop()
x._pop()
def getCommands(self, script = None) :
if script is None :
# no argument, get them from REQUEST
if self.__context.REQUEST.has_key("zshellscript") :
script = self.__context.REQUEST["zshellscript"]
else :
script = []
if type(script) != type([]) :
script = [script]
return map(string.strip, filter(None, script))
def execCommands(self, cmds) :
x = self.__temphtml
self.__status = 0
if cmds :
x._push()
x.p().table(width="100%", border=0)
x._push()
x.tr(Class="resultstheader").th("Results:", Class="resultstheaderlabel", bgcolor="silver", colspan=2)
x._pop()
x.tr(Class="resultsbodyrow").td(Class="resultsbody", align="left", valign="top", colspan=2)
for command in cmds :
status = self.execCommand(command)
if status is not None :
self.__status = self.__status + status
if self.__status :
x._br().b("WARNING: %i errors were encountered" % abs(self.__status))
x._pop()
return self.__status
def execCommand(self, cmdline) :
# Unfortunately shlex.shlex needs a file object, not a buffer.
# tokenize the command line
fcmdline = cStringIO.StringIO("%s" % cmdline)
tokenizer = shlex.shlex(fcmdline)
tokenizer.wordchars = tokenizer.wordchars + r".:,?!~/\_$*-+={}[]()"
tokenizer.quotes = tokenizer.quotes + "`"
unexpanded = []
while 1 :
token = tokenizer.get_token()
if token :
unexpanded.append(token)
else :
break
fcmdline.close()
del tokenizer
del fcmdline
# the command is the first element
cmd = unexpanded[0]
cmdname = "run_" + cmd
if not hasattr(self, cmdname) :
return self.errormessage("Unknown command: %s" % cmd)
# skip the command
unexpanded = unexpanded[1:]
# then build a wildcards expanded list of arguments
expanded = []
for i in range(len(unexpanded)) :
# if quoted, then add it without the quotes
# and modify the unexpanded arg accordingly
uarg = unexpanded[i]
if (uarg[0] == uarg[-1] == "'") \
or (uarg[0] == uarg[-1] == '"') :
expanded.append(uarg[1:-1])
unexpanded[i] = uarg[1:-1]
else :
# if not quoted then try to expand wildcards
# and add the result
expanded.extend(self.ShellExpand(uarg) or [uarg])
# Update the history if available
self.UpdateHistory(cmdline)
# then launch the command: we don't do it before updating
# the history because we would miss shutdowns and restarts
return getattr(self, cmdname)(expanded, unexpanded)
def getHistory(self) :
"""Returns the history document: .zshell_history"""
if hasattr(self.__context, ".zshell_history") \
and (self.getMetaType(getattr(self.__context, ".zshell_history")) == 'DTML Document') :
return getattr(self.__context, ".zshell_history")
def UpdateHistory(self, cmdline, clear=0) :
"""Updates the command history"""
# we try to find a DTML Document which id is ".zshell_history"
# and save commands into it.
# BUT we don't test for permissions to modify it because
# a Manager may want to keep an history in a place that's usually
# not writable by anyone.
# I dont know if this is a good thing or not, maybe time will tell...
# if there's no history document, then don't do anything.
history = self.getHistory()
if history is not None :
(username, dummy) = self.WhoAmI()
historyline = "%s,%s,%s" % (DateTime().strftime("%Y-%m-%d %H:%M:%S %Z"), username, cmdline)
oldsrc = history.document_src()
if clear :
if self.HasPerms(history, 'Change DTML Documents') :
oldsrc = "" # we clear the history, and log the history --clear command
else :
# the user doesn't have the correct permissions to clear
# the history, so his history --clear was already logged:
# there's no need to log it a second time.
historyline = ""
else :
if oldsrc[0] == '<' :
# this is probably a non empty .zshell_history DTML Document
# I mean a DTML Document which was not emptied before being
# used as the history document, so it still contains the
# default DTML Document tags: all we have to do is to
# empty it by ourselves: MAY BE DANGEROUS !
oldsrc = ""
else :
oldsrc = oldsrc[:-1] # we want to eat the last '\n'
src = oldsrc + historyline + '\n\n' # Zope eats the last \n character too
history.manage_edit(src, history.title)
def HasPerms(self, object, perms, verbose = 1) :
"""Checks if the user has all the perms permissions on an object"""
if type(perms) != type([]) :
perms = [perms]
SecurityManager = AccessControl.getSecurityManager()
for perm in perms :
if not SecurityManager.checkPermission(perm, object) :
if verbose :
self.permissionProblem(object, perm)
return 0
return 1
def HasRoles(self, object, roles, verbose = 1) :
"""Checks if the user has at least one role in roles in the context of an object"""
if type(roles) != type([]) :
roles = [roles]
ctxtroles = AccessControl.getSecurityManager().getUser().getRolesInContext(object)
for role in roles :
if role in ctxtroles :
return 1
if verbose :
self.roleProblem(object, role)
return 0
def WhoAmI(self) :
"""Returns the current user's name"""
user = AccessControl.getSecurityManager().getUser()
return (user.getUserName(), user.getRoles())
def getMetaType(self, object) :
"""Returns an object's meta type"""
if callable(object.meta_type) :
# at least ZClasses
return object.meta_type()
else :
# the rest
return object.meta_type
def getArgsAndNot(self, allargs) :
"""Splits a list of arguments into two sublists:
- a list of arguments
- a list of NOTarguments
a NOTargument is an argument which begins with '!'
"""
args = []
notargs = []
for arg in allargs :
if arg[0] == '!' :
# add it to the notargs list but we
# don't want the '!' character
notargs.append(arg[1:])
else :
args.append(arg)
if notargs and not args :
# args still empty, we want all of them
# before matching negatively
args = ["*"]
return (args, notargs)
def descend(self, root, func, maxdepth = 0, curdepth = 0) :
status = 0
if root is not None :
if not self.HasPerms(root, 'View') :
status = -1
else :
status = status + func(root)
if (not (maxdepth and (curdepth >= maxdepth))) and hasattr(root, "isPrincipiaFolderish") and root.isPrincipiaFolderish :
curdepth = curdepth + 1
if not self.HasPerms(root, 'Access contents information') :
status = status - 1
for object in root.objectValues() :
status = status + self.descend(object, func, maxdepth, curdepth)
return status
def getopt(self, longs, argv) :
"""Analyses command line options, only long options are recognized"""
# analyse the arguments to extract those which may be lists
# from those which must be single values
# we put them into lists, since not so many options
# will generally be used, this should be at least as fast
# as putting them in mappings
single = []
multiple = []
for l in range(len(longs)) :
long = longs[l]
if long[-1] == '+' :
# this option may be specified multiple times
# but we must modify it for getopt.getopt to
# work correctly
longs[l] = long[:-1] + '='
multiple.append(long[:-1])
elif long[-1] == '=' :
single.append(long[:-1])
else :
single.append(long)
try :
result = {}
options,args = getopt.getopt(argv, '', longs)
if options :
for (o, v) in options :
o = o[2:]
if o in single :
if not result.has_key(o) :
result[o] = v
else :
raise getopt.error, "Option --%s can't be specified more than one time on this command line" % o
elif o in multiple :
if not result.has_key(o) :
result[o] = []
result[o].append(v)
else :
# there's a very big problem !
raise getopt.error, "ZShell internal error while parsing command line arguments"
elif not args :
args = argv # no option and no argument, return argv inchanged
return (result, args)
except getopt.error, msg :
self.errormessage("%s" % msg)
return (None, None)
def match_anystring(self, option, value, options) :
if options.has_key(option) :
if value :
(vals, notvals) = self.getArgsAndNot(options[option])
ok = 0
for val in vals :
if fnmatch.fnmatchcase(value, val) :
ok = 1
break
oknot = 0
for notval in notvals :
if fnmatch.fnmatchcase(value, notval) :
oknot = 1
break
return ok and (not oknot)
return 0
return 1
def match_MetaType(self, object, options) :
"""Returns 1 if an object meta type matches optional --type options.
If no --type option is given, then the object always matches.
"""
return self.match_anystring("type", self.getMetaType(object), options)
def match_Id(self, object, options) :
"""Returns 1 if an object's id matches optional --id options.
If no --id option is given, then the object always matches.
"""
return self.match_anystring("id", object.getId(), options)
def match_Owner(self, object, options) :
"""Returns 1 if an object's owner matches optional --owner options.
If no --owner option is given, then the object always matches.
"""
if options.has_key("owner") :
ownerinfo = object.owner_info()
if ownerinfo is not None :
# at least /Control_Panel/Products[/*] doesn't satisfy the following test
if hasattr(ownerinfo, "has_key") and ownerinfo.has_key('id') :
return self.match_anystring("owner", ownerinfo['id'], options)
return 0 # Not owned or no owner: never match
return 1 # No owner option: always match
def match_Newer(self, object, options) :
"""Returns 1 if an object is newer than an optional --newer option.
Returns -1 if the --newer option argument doesn't exist
Returns 0 if the object is not newer
if modification times are equal then the object matches.
If no --newer option is given, then the object always matches.
"""
if options.has_key("newer") :
objnewer = self.toObject(self.__context, options["newer"])
if objnewer is not None :
if object.bobobase_modification_time() <= objnewer.bobobase_modification_time() :
return 0
else :
return self.errormessage("Object %s doesn't exist" % options["newer"])
return 1
def match_Older(self, object, options) :
"""Returns 1 if an object is older than an optional --older option.
Returns -1 if the --older option argument doesn't exist
Returns 0 if the object is not older
if modification times are equal then the object matches.
If no --older option is given, then the object always matches.
"""
if options.has_key("older") :
objolder = self.toObject(self.__context, options["older"])
if objolder is not None :
if object.bobobase_modification_time() >= objolder.bobobase_modification_time() :
return 0
else :
return self.errormessage("Object %s doesn't exist" % options["older"])
return 1
def match_Time(self, object, options) :
"""Returns 1 if an object is newer than an optional --mmin or --mtime option.
Returns -1 if the --mmin or --mtime option argument is an invalid value.
--mmin expects a duration in minutes.
--mtime expects a duration in days.
Returns 0 if the object is not newer.
If none of --mmin or --mtime option is given, then the object always matches.
"""
if options.has_key("mmin") or options.has_key("mtime") :
if options.has_key("mmin") and options.has_key("mtime") :
return self.errormessage("Options --mmin and --mtime are incompatible")
if options.has_key("mmin") :
timestr = options["mmin"]
else :
timestr = options["mtime"]
try :
if options.has_key("mmin") :
modtime = int(timestr) * 60
else :
modtime = int(timestr) * 60 * 60 * 24
except ValueError :
return self.errormessage("Invalid time %s" % timestr)
testtime = DateTime(int(time.time()) - modtime)
if object.bobobase_modification_time() <= testtime :
return 0
return 1
def match_Many(self, object, options) :
"""Returns 1 if an object matches all criterias.
Returns 0 if it doesn't match some of them.
Returns -1 if there's an error somewhere.
"""
# if id doesn't match then skip
if not self.match_Id(object, options) :
return 0
# if meta type doesn't match then skip
if not self.match_MetaType(object, options) :
return 0
# if owner doesn't match then skip
if not self.match_Owner(object, options) :
return 0
# if not newer than another object then skip
newer = self.match_Newer(object, options)
if newer <= 0 :
return newer
# if not older than another object then skip
older = self.match_Older(object, options)
if older <= 0 :
return older
# if modification time doesn't match options
# --mmin or --mtime then skip
matchtime = self.match_Time(object, options)
if matchtime <= 0 :
return matchtime
return 1
def getMaxDepth(self, options, default=0) :
mx = default
if options.has_key("maxdepth") :
try :
mx = int(options["maxdepth"])
except ValueError :
return self.errormessage("the --maxdepth option's argument must be a numeric")
return mx
def mv_or_cp(self, cmd, expanded) :
if len(expanded) < 2 :
return self.errormessage("Incorrect number of arguments")
else :
status = 0
dst = expanded[-1]
srcs = expanded[:-1]
objids = []
for src in srcs :
if '/' in src :
status = status + self.errormessage('Paths in source objects are not allowed at this time: %s' % src)
else :
objids.append(src)
dsto = self.toObject(self.__context, dst)
if dsto is None :
return status + self.errormessage("Incorrect destination argument: %s" % dst)
if not dsto.isPrincipiaFolderish :
return status + self.errormessage("Not a folderish object: %s" % dst)
# Michel@DC: here you should do a
# SecurityManager.checkPermission('View
# management screens', self.__context) to make
# sure the user has permission to copy or
# paste.
# Jerome: In fact we must test this perm on both
# the source and the destination.
if not self.HasPerms(self.__context, 'View management screens') :
return status - 1
if not self.HasPerms(dsto, 'View management screens') :
return status - 1
# All is fine, do it now.
try :
if cmd == 'cp' :
self._clipboard = self.__context.manage_copyObjects(ids = objids)
action = 'copied'
else :
self._clipboard = self.__context.manage_cutObjects(ids = objids)
action = 'moved'
dsto.manage_pasteObjects(cb_copy_data = self._clipboard)
for oid in objids :
self.htmlmessage('%s %s to %s' % (oid, action, self.ObjectPath(dsto)))
except AttributeError, msg:
status = status + self.errormessage("Object %s doesn't exist" % msg)
except CopyError :
status = status + self.errormessage("Objects can't be %s to %s" % (action, self.ObjectPath(dsto)))
return status
def manage_view_properties(self, expanded, unexpanded, action, perms = None, roles = None) :
"""Called by run_manage, run_view and run_properties"""
if not expanded :
return self.errormessage("Needs at least one object id")
status = 0
for arg in expanded :
object = self.toObject(self.__context, arg)
if object is None :
status = status + self.errormessage('Incorrect path: %s' % arg)
else :
if (perms is not None) and not self.HasPerms(object, perms) :
status = status - 1
elif (roles is not None) and not self.HasRoles(object, roles) :
status = status - 1
else :
status = status + self.newwindow("%s%s" % (object.absolute_url(), action))
return status
# Michel@DC: all methods below here need to validate their operations
# with SecurityManager.checkPermission
def run_su(self, expanded, unexpanded) :
"""Run a command as another user
Accepts a --user xxxx option
and a --password yyyy option.
If the current user has a Manager role in the current context,
then no password is required, else the correct password for
user xxxx must be entered.
If there's no --user option, then an su to user 'admin' is tried:
su --user jerome --password x./32 "rm /QuickStart"
Nota Bene: Both the password and domains must validate for
the new user.
Caveats: su state is not preserved, it's done voluntarily
this way, but just tell me if you prefer another behavior.
"""
options, args = self.getopt(["user=", "password="], unexpanded)
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
if not args :
return self.errormessage("Needs a command to run as another user")
# we display the same message in all cases to prevent
# a brute force attack to learn existing usernames
incorrect_user_or_password = "You must supply a correct username and password"
if not self.HasRoles(self.__context, 'Manager', verbose=0) :
# not an admin, password is required
if not options.has_key("password") :
return self.errormessage("%s" % incorrect_user_or_password)
else :
password = options["password"]
else :
password = None
if not options.has_key("user") :
newusername = "admin"
else :
newusername = options["user"]
newuser = self.__context.acl_users.getUser(newusername)
if (newuser is None) or ((password is not None) and not (newuser.authenticate(password, self.__context.REQUEST))) :
return self.errormessage("%s" % incorrect_user_or_password)
olduser = AccessControl.getSecurityManager().getUser()
oldusername = olduser.getUserName()
AccessControl.SecurityManagement.newSecurityManager(None, newuser)
self.htmlmessage("User '%s' does a su to '%s'" % (oldusername, newusername))
status = self.execCommand(string.join(args, ' '))
if oldusername == 'Anonymous User' :
AccessControl.SecurityManagement.noSecurityManager()
else :
AccessControl.SecurityManagement.newSecurityManager(None, olduser)
self.htmlmessage("Current user '%s' was reset to '%s'" % (newusername, oldusername))
return status
def run_manage(self, expanded, unexpanded) :
"""Manages objects
Accepts multiple arguments
Caveats: Nicer in a browser with JavaScript support
"""
return self.manage_view_properties(expanded, unexpanded, "/manage", roles = "Manager")
def run_properties(self, expanded, unexpanded) :
"""Manages objects properties
Accepts multiple arguments
Caveats: Nicer in a browser with JavaScript support
"""
return self.manage_view_properties(expanded, unexpanded, "/manage_propertiesForm", perms = "Manage properties")
def run_view(self, expanded, unexpanded) :
"""Views objects
Accepts multiple arguments
Caveats: Nicer in a browser with JavaScript support
"""
return self.manage_view_properties(expanded, unexpanded, "", perms = "View")
def run_grep(self, expanded, unexpanded) :
"""Search and optionally replace regexps in objects contents
Accepts many options:
--recurse recursive search
--maxdepth n descend at most n levels
--type metatype search only objects of meta type xxx
--owner xxxx objects owned by user xxxx
--newer /other/object objects modified more recently than other
--older /other/object objects modified less recently than other
--mmin n objects modified less than n minutes ago
--mtime n objects modified less than n days ago
--properties search in properties too
--replace xxxx replaces each occurence of the pattern
with the string xxx.
--invert invert the search: objects that don't match
the regexp are selected
--ignorecase ignore case: this is just for convenience since
it's always possible to use a pattern which
explicitly asks for ignoring case.
e.g.:
grep --recurse --ignorecase �^word" *_html
this one will search recursively for each document which
id ends in "_html" and which has "word" at the beginning of
a line, ignoring differences between UPPER and lower case.
grep --properties --type "DTML*" --type Folder somestring *
This one will search for "somestring" in all objects which
meta type is Folder or begins with "DTML" and their
properties.
The string to search for and the optionally replacing
string could be any regular expression defined by the
standard re python module.
Hint: You may use multiple --type or --owner arguments
and each can contain wildcards.
WARNING: don't forget to enclose your regexps between
single or double quotes or else some regexps
won't work as expected.
Caveats: If not given the --properties option, only
searches in the document_src() and title
attributes of objects that have a document_src
attribute (if not clear email me).
"""
options, args = self.getopt(["recurse", "maxdepth=", "owner+", "mmin=", "mtime=", "newer=", "older=", "type+", "replace=", "properties", "invert", "ignorecase"], expanded)
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
if len(args) < 2 :
return self.errormessage("Needs one pattern and one object id to grep")
if options.has_key("invert") and options.has_key("replace") :
return self.errormessage("Options replace and invert are incompatible")
try :
if options.has_key("ignorecase") :
pattern = re.compile("%s" % args[0], re.I)
else :
pattern = re.compile("%s" % args[0])
except re.error :
return self.errormessage("You probably entered an invalid regular expression: %s" % args[0])
def do_grep(obj, zopeshell = self, options=options, pattern=pattern) :
if obj is not None:
# tests if the object matches the present options
if not zopeshell.match_Many(obj, options) :
return 0
url = zopeshell.ObjectPath(obj)
# in the following lines the absence of an _updateProperty
# attribute indicates an object without properties (e.g. a method)
# which indicates an object in which we can't search or replace
# in properties.
ok = 0
if options.has_key("replace") :
if options.has_key("properties") and hasattr(obj, "propertyMap") \
and hasattr(obj, "_updateProperty") :
if not zopeshell.HasPerms(obj, "Access contents information") :
return 0
for prop in obj.propertyMap() :
value = str(obj.getProperty(prop["id"]))
(newstring, number) = pattern.subn(options["replace"], value)
# we test permissions only when needed:
# it is not quick, but prevent dummy error messages
if number and zopeshell.HasPerms(obj, "Manage properties") :
obj._updateProperty(prop["id"], newstring)
ok = ok + number
if hasattr(obj, "document_src") :
if zopeshell.HasPerms(obj, "View management screens") :
src = obj.document_src()
title = obj.title
(newsrc, numbersrc) = pattern.subn(options["replace"], src)
(newtitle, numbertitle) = pattern.subn(options["replace"], title)
if numbersrc :
src = newsrc
if numbertitle :
title = newtitle
# we test permissions only when needed:
# it is not quick, but prevent dummy error messages
if numbersrc or numbertitle :
ok = ok + numbersrc + numbertitle
if (((obj.__class__ == DTMLDocument) or (DTMLDocument in obj.__class__.__bases__)) \
and zopeshell.HasPerms(obj, "Change DTML Documents")) \
or \
(((obj.__class__ == DTMLMethod) or (DTMLMethod in obj.__class__.__bases__)) \
and zopeshell.HasPerms(obj, "Change DTML Methods")) :
obj.manage_edit(src, title)
elif (((obj.__class__ == PythonScript) or (PythonScript in obj.__class__.__bases__)) \
and zopeshell.HasPerms(obj, "Change Python Scripts")) :
obj.write(src)
else :
zopeshell.errormessage("Don't know how to modify object %s" % (url or repr(obj)))
else :
if options.has_key("properties") and hasattr(obj, "propertyMap") \
and hasattr(obj, "_updateProperty") :
if not zopeshell.HasPerms(obj, "Access contents information") :
return 0
for prop in obj.propertyMap() :
value = str(obj.getProperty(prop["id"]))
result = pattern.findall(value)
if result is not None :
ok = ok + len(result)
if hasattr(obj, "document_src") :
if zopeshell.HasPerms(obj, "View management screens") :
result = pattern.findall(obj.document_src())
if result is not None :
ok = ok + len(result)
result = pattern.findall(obj.title)
if result is not None :
ok = ok + len(result)
if url is not None :
if (ok and not options.has_key("invert")) or \
((not ok) and options.has_key("invert")) :
zopeshell.htmlmessage("%s" % url)
zopeshell.printf("%s\n" % url)
else :
return zopeshell.errormessage("Error while accessing to object %s" % repr(obj))
return 0
mx = 1 # default = no recursivity
if options.has_key("recurse") :
mx = self.getMaxDepth(options, default=0) # default full recursivity
if mx < 0 :
# an error occured, message already displayed
return -1
elif options.has_key("maxdepth") :
return self.errormessage("Option --maxdepth was given, but there's no --recurse option")
status = 0
for arg in args[1:] :
# current depth is already 1.
status = status + self.descend(self.toObject(self.__context, arg), do_grep, maxdepth=mx, curdepth=1)
return status
def run_find(self, expanded, unexpanded) :
"""Search objects in the Folder hierarchy
Accepts a top Folder from which doing the search as
its first argument, and options as its other arguments:
--maxdepth n descend at most n levels
--id xxxx objects which id matchs xxxx
--type metatype objects which meta_type is metatype
--owner xxxx objects owned by user xxxx
--newer /other/object objects modified more recently than other
--older /other/object objects modified less recently than other
--mmin n objects modified less than n minutes ago
--mtime n objects modified less than n days ago
--exec cmd exec command for each object found
you can use {} to match the current full object id
and single or double quotes around the command itself.
e.g.:
find / --owner jerome --id "*_html" --maxdepth 2 --exec "addprop author/string {}"
Hint: You may use multiple --id, --type and --owner arguments
and each can contain wildcards.
WARNING: You may very well become addicted to this command ;-)
"""
if not expanded :
return self.errormessage("Needs one Folder to find from")
options, args = self.getopt(["id+", "maxdepth=", "owner+", "mmin=", "mtime=", "newer=", "older=", "type+", "exec="], expanded[1:])
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
def do_find(obj, zopeshell = self, options=options, args=args) :
if obj is not None:
# tests if the object matches the present options
many = zopeshell.match_Many(obj, options)
if many <= 0 :
return many
url = zopeshell.ObjectPath(obj)
if options.has_key("exec") :
if url is not None :
return zopeshell.execCommand(string.replace(options["exec"], "{}", url))
else :
return zopeshell.errormessage("Error while accessing to object %s" % repr(obj))
if url is not None :
zopeshell.htmlmessage(url)
zopeshell.printf("%s\n" % url)
else :
return zopeshell.errormessage("Error while accessing to object %s" % repr(obj))
return 0
mx = self.getMaxDepth(options, default=0)
if mx < 0 :
# an error occured, message already displayed
return -1
return self.descend(self.toObject(self.__context, expanded[0]), do_find, maxdepth=mx)
def run_catalog(self, expanded, unexpanded) :
"""Catalogs objects in the nearest ZCatalog
Accepts many arguments:
catalog *_dtml MyFolder/*gif [...]
WARNING: All objects may not end in the same
ZCatalog, because the choice of
the nearest ZCatalog is done for each
object.
"""
if not expanded :
return self.errormessage("Needs at least one object id")
status = 0
for arg in expanded :
object = self.toObject(self.__context, arg)
if object is None :
status = status + self.errormessage('Incorrect path: %s' % arg)
else :
# finds all available Catalogs up through the acquisition path
zcatalog = self.__context.superValues(["ZCatalog"])
if not zcatalog :
status = status + self.errormessage("No ZCatalog available for object %s" % self.ObjectPath(object))
else :
zcatalog = zcatalog[0] # we take the first one, which is the nearest
if not self.HasPerms(zcatalog, 'Manage ZCatalog Entries') :
status = status - 1
else :
zcatalog.catalog_object(object, "%s" % self.ObjectPath(object))
self.htmlmessage('%s added to ZCatalog %s' % (self.ObjectPath(object), self.ObjectPath(zcatalog)))
return status
def run_uncatalog(self, expanded, unexpanded) :
"""Uncatalogs objects from the nearest ZCatalog
Accepts many arguments:
uncatalog *_dtml MyFolder/*gif [...]
Caveats: Uncatalogging an object which is not
present in the ZCatalog is accepted.
WARNING: All objects may not be uncatalogged from
the same ZCatalog, because the choice of
the nearest ZCatalog is done for each
object.
"""
if not expanded :
return self.errormessage("Needs at least one object id")
status = 0
for arg in expanded :
object = self.toObject(self.__context, arg)
if object is None :
status = status + self.errormessage('Incorrect path: %s' % arg)
else :
# finds all available Catalogs up through the acquisition path
zcatalog = self.__context.superValues(["ZCatalog"])
if not zcatalog :
status = status + self.errormessage("No ZCatalog available for object %s" % self.ObjectPath(object))
else :
zcatalog = zcatalog[0] # we take the first one, which is the nearest
if not self.HasPerms(zcatalog, 'Manage ZCatalog Entries') :
status = status - 1
else :
# WARNING: we don't verify if the object was really in the ZCatalog
zcatalog.uncatalog_object("%s" % self.ObjectPath(object))
self.htmlmessage('%s removed from ZCatalog %s' % (self.ObjectPath(object), self.ObjectPath(zcatalog)))
return status
def run_lsperms(self, expanded, unexpanded) :
"""Lists permissions on an object
Accepts an object id as its first argument.
Accepts the permissions to list as its other
arguments:
lsperms /QuickSart Change*
This will list all permissions which name matches
Change* on the /QuickStart object
"""
if not unexpanded :
return self.errormessage("Needs at least one object id")
object = self.toObject(self.__context, unexpanded[0])
if object is None :
return self.errormessage("Object %s doesn't exist" % unexpanded[0])
if not self.HasPerms(object, "Change permissions") :
return -1
if len(unexpanded) == 1:
# no permission name, we want all of them
unexpanded.append("*")
result = []
psettings = object.permission_settings()
for perm in object.ac_inherited_permissions(all=1) :
pname = perm[0]
for pattern in unexpanded[1:] :
# I'd prefer to have an fnmatch.fnmatchUNcase()
# to be less strict...
if fnmatch.fnmatchcase(pname, pattern) :
roles = filter(None, map(lambda r: (r["selected"] == 'SELECTED') and r["name"], object.rolesOfPermission(pname)))
acquired = ((filter(lambda p, pn=pname: p["name"] == pn, psettings)[0]["acquire"] == 'CHECKED') and 'Yes') or 'No'
result.append({ "Permission": pname, "Roles": string.join(roles, ', '), "Acquired": acquired })
break
self.tableDisplay("lsperms", ["Permission", "Roles", "Acquired"], result)
def run_setperms(self, expanded, unexpanded) :
"""Sets permissions on an object
Accepts a --noacquire option to not acquire
permissions from the parent object.
Accepts an object id as its following argument.
Accepts a comma separated list of roles to which
give this permission. Each role not in the list
will have this permission removed.
Accepts a list of permissions as its following
arguments:
setperms --noacquire /MyForum Manager,Editor *Postings*
This will give all permissions which name contains Postings
to roles Manager and Editor on the object /MyForum
For other roles this permission will be removed, and will
not be acquired from the parent object.
"""
options, args = self.getopt(["noacquire"], unexpanded)
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
if len(args) == 2 :
# no permission name, we want all of them
args.append("*")
if len(args) < 3 :
return self.errormessage("Needs an object id, a comma separated list of roles and a list of permissions")
object = self.toObject(self.__context, args[0])
if object is None :
return self.errormessage("Object %s doesn't exist" % args[0])
if not self.HasPerms(object, "Change permissions") :
return -1
roles = filter(None, map(string.strip, string.split(args[1], ',')))
acquire = not options.has_key("noacquire")
for perm in object.ac_inherited_permissions(all=1) :
pname = perm[0]
for pattern in unexpanded[1:] :
# I'd prefer to have an fnmatch.fnmatchUNcase()
# to be less strict...
if fnmatch.fnmatchcase(pname, pattern) :
object.manage_permission(permission_to_manage=pname, roles=roles, acquire=acquire)
self.htmlmessage("Permission '%s' on object %s was given to roles %s and %s acquired otherwise" % (pname, self.ObjectPath(object), roles, (((not acquire) and 'not') or '')))
break
def run_passwd(self, expanded, unexpanded) :
"""Change or set a user's password
Accepts a user name as its first argument.
Accepts a password as its second argument.
If only one argument is given, then the
current user's password is changed:
passwd 67G.FDKea
or:
passwd jerome �lD31
Caveats: Expects the user to exist in the nearest user folder.
"""
if len(unexpanded) == 1 :
username = None
passwd = unexpanded[0]
elif len(unexpanded) == 2 :
username = unexpanded[0]
passwd = unexpanded[1]
else :
return self.errormessage("Needs a password or an username + password")
if not self.HasPerms(self.__context.acl_users, 'Manage users') :
return -1
if username is None :
# no username: get the logged in user's one
username = AccessControl.getSecurityManager().getUser().getUserName()
user = self.__context.acl_users.getUser(username)
if user is None :
return self.errormessage("User %s doesn't exist" % username)
roles = user.getRoles()
domains = user.getDomains()
self.__context.acl_users._changeUser(username, passwd, passwd, roles, domains)
self.htmlmessage("Password changed for user %s" % username)
def run_domains(self, expanded, unexpanded) :
"""Changes/delete allowed domains for a user
Accepts a user name as its first argument.
If no other argument is given, then allowed
domains for this user are deleted: the user
can connect from anywhere.
Any other argument is treated as a list of
domains to be ADDED to the existing list of domains
allowed for this user:
domains jerome unice.fr
Caveats: Expects the user to exist in the nearest user folder.
"""
if not unexpanded :
return self.errormessage("Needs at least an username")
if not self.HasPerms(self.__context.acl_users, 'Manage users') :
return -1
username = unexpanded[0]
user = self.__context.acl_users.getUser(username)
if user is None :
return self.errormessage("User %s doesn't exist" % username)
roles = list(user.getRoles())
domains = list(user.getDomains())
if len(unexpanded) > 1 :
# there are domains arguments
# so we add them
domains.extend(unexpanded[1:])
msg = "changed to %s" % repr(domains)
else :
# there's no domain argument
# so we delete all domains
domains = []
msg = "deleted"
self.__context.acl_users._changeUser(username, None, None, roles, domains)
self.htmlmessage("Domains for user %s %s" % (username, msg))
def run_roles(self, expanded, unexpanded) :
"""Changes/delete roles for a user
Accepts a user name as its first argument.
If no other argument is given, then roles
for this user are deleted: the user has no
role.
Any other argument is treated as a list of
roles to be ADDED to this user:
roles jerome Manager Editor
Caveats: Expects the user to exist in the nearest user folder.
"""
if not unexpanded :
return self.errormessage("Needs at least an username")
if not self.HasPerms(self.__context.acl_users, 'Manage users') :
return -1
username = unexpanded[0]
user = self.__context.acl_users.getUser(username)
if user is None :
return self.errormessage("User %s doesn't exist" % username)
roles = list(user.getRoles())
domains = list(user.getDomains())
if len(unexpanded) > 1 :
# there are roles arguments
# so we add them
roles.extend(unexpanded[1:])
msg = "changed to %s" % repr(roles)
else :
# there's no role argument
# so we delete all roles
roles = []
msg = "deleted"
self.__context.acl_users._changeUser(username, None, None, roles, domains)
self.htmlmessage("Roles for user %s %s" % (username, msg))
def run_history(self, expanded, unexpanded) :
"""Displays the commands history.
Accepts an optional --clear argument to empty
the history, which must exist.
Commands stored in a .zshell_history
DTML Document are printed. This document
must be created manually for the history
of commands to be saved.
If the current user has the 'Change DTML Documents'
permission on the .zshell_history document, then he
can either list all commands, along with their
execution date, and the username who launched
them, or list only commands launched by some users.
Otherwise, only the commands launched
by the current user are shown, with no additional
information.
history
or:
history --clear
or:
history --user "jer*" --user !jerome
This one will list all commands launched by
users which name begins with 'jer' but not those
from user 'jerome'.
WARNING: No security check is done to update the
history. However the user needs sufficient
permissions to clear the history.
This may allow a manager to
keep an history of commands run by
other users, but in a place where these
users normally has no write access, in
order to forbid them to modify this
history to hide launched commands
from the manager. Just tell me
if this is not OK, and I'll change it.
"""
options, args = self.getopt(["clear", "user+"], unexpanded)
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
if args :
return self.errormessage("Doesn't need any other argument")
history = self.getHistory()
if history is not None :
if options.has_key("clear") :
# we want to clear it, UpdateHistory will take care of permissions
self.UpdateHistory("history --clear", clear=1)
else :
# we just want to see it.
# Someone who can modify the .zshell_history can see all commands
newhistory = history.document_src()
if not self.HasPerms(history, "Change DTML Documents", verbose=0) :
if options.has_key("user") :
return self.errormessage("You're not allowed to use this option")
# a non-Manager user can only see its commands
(username, dummy) = self.WhoAmI()
lines = filter(lambda line, u=username: line and (string.split(line, ',')[1] == u), string.split(newhistory, '\n'))
newhistory = string.join(map(lambda line: string.split(line, ',')[2], lines), "\n")
else :
# The person has sufficient permissions
# to list only some username's commands
newh = []
for line in filter(None, string.split(newhistory, '\n')) :
cmduser = string.split(line, ',')[1]
# not optimal, but works:
if self.match_anystring("user", cmduser, options) :
newh.append(line)
newhistory = string.join(newh, '\n')
self.htmlmessage(string.replace(newhistory, '\n', '<BR>\n'), safe=1)
self.printf("%s\n" % newhistory)
else :
return self.errormessage("No history available")
def run_call(self, expanded, unexpanded) :
"""Calls an object with optional parameters
The object name may be specified with its full path with dots or slashes:
call MyFolder.MyObject(_,_.REQUEST,arg1="thisarg"[,arg2="anotherarg"][,...])
or:
call MyFolder.MyObject(context,context.REQUEST,arg1="thisarg"[,arg2="anotherarg"][,...])
another example:
call index_html
This last one will return the unrendered version of index_html
Both _ and context may be used if needed.
Hint: Don't use any space in your command, or else enclose your
command between single or double quotes.
WARNING: Calling zshell itself is a Very Bad Idea (tm) and can kill Zope
"""
if not expanded :
return self.errormessage("Needs an object id to call")
# Michel@DC: you should factor the object out of this eval and
# validate it with
# SecurityManager.checkPermission('View', object).
# Also, 'eval' without an namespace qualifying 'in'
# clause can be bad! Try and do this without eval.
# Jerome: Don't know how without eval !
# new code looks very ugly and accessing to object's
# properties doesn't work anymore, unfortunately.
objectstr = string.join(unexpanded, ' ')
pos = string.find(objectstr, '(')
if pos == -1 :
# called without arguments
objpath = objectstr
objargs = ""
else :
# called with arguments, skip them
# because we only want the object name
objpath = objectstr[:pos]
objargs = objectstr[pos:]
objpath = string.replace(objpath, '.', '/')
object = self.toObject(self.__context, objpath)
if object is None :
# maybe should do something to re-allow properties to be used
return self.errormessage("Object %s not found" % objectstr)
else :
if not self.HasPerms(object, 'View') :
return -1
else :
_ = context = self.__context
callresult = str(eval("object%s" % objargs))
self.printf("%s" % callresult)
self.htmlmessage(callresult, safe=1)
def run_lsusers(self, expanded, unexpanded) :
"""Lists users in the nearest User Folder
Accepts multiple user names as its arguments,
wildcards are accepted:
lsusers user1 [jer*] [...]
"""
if not unexpanded :
unexpanded = ["*"] # List all users
if not self.HasPerms(self.__context.acl_users, 'Manage users') :
return -1
result = []
for username in self.__context.acl_users.getUserNames() :
for uname in unexpanded :
if fnmatch.fnmatchcase(username, uname) :
user = self.__context.acl_users.getUser(username)
result.append({ "UserName": username, "Roles": string.join(user.getRoles(), ', '), "InContext": string.join(user.getRolesInContext(self.__context), ', '), "Domains": string.join(user.getDomains(), ', ') })
self.tableDisplay("lsusers", ["UserName", "Roles", "InContext", "Domains"], result)
def run_delusers(self, expanded, unexpanded):
"""Delete users from the nearest User Folder
Accepts multiple user names as its arguments:
delusers user1 [user2] [...]
WARNING: No wildcard is expanded, for security reasons.
"""
if not expanded :
return self.errormessage('Needs an userid as the first argument')
if not self.HasPerms(self.__context.acl_users, 'Manage users') :
return -1
status = 0
usernames = []
for username in unexpanded :
if username not in self.__context.acl_users.getUserNames() :
status = status + self.errormessage("User %s doesn't exists" % username)
else :
usernames.append(username)
self.__context.REQUEST.set("names", usernames)
self.__context.acl_users.manage_users("Delete", REQUEST=self.__context.REQUEST)
if usernames :
self.htmlmessage('Users %s deleted' % string.join(usernames, ", "))
# don't be fucked by Zope's automatic redirection
self.__context.REQUEST.RESPONSE.setStatus(200)
return status
def run_addusers(self, expanded, unexpanded) :
"""Adds users into the nearest User Folder
The users added have no role, and no domain.
Accepts multiple arguments, each argument must be
made of a username, a slash character, and a password:
addusers jerome/kyx.ud34 [john/9!AZce] [...]
"""
if not unexpanded :
return self.errormessage('Needs an userid/password as the first argument')
if not self.HasPerms(self.__context.acl_users, 'Manage users') :
return -1
status = 0
for arg in unexpanded :
split = string.split(arg, '/')
if len(split) != 2 :
status = status + self.errormessage('Incorrect username/password: %s' % arg)
else :
(username, password) = split
if username in self.__context.acl_users.getUserNames() :
status = status + self.errormessage("User %s already exists" % username)
else :
self.__context.REQUEST.set("name", username)
self.__context.REQUEST.set("password", password)
self.__context.REQUEST.set("confirm", password)
self.__context.REQUEST.set("domains", "")
self.__context.REQUEST.set("roles", [])
self.__context.acl_users.manage_users("Add", REQUEST=self.__context.REQUEST)
self.htmlmessage('User %s added with password %s' % (username, password))
# don't be fucked by Zope's automatic redirection
self.__context.REQUEST.RESPONSE.setStatus(200)
return status
def run_lroles(self, expanded, unexpanded) :
"""Sets local roles for an user in the local Folder
Accepts the user's name as its first argument
Accepts a list of local roles to give to this user
as its remaining arguments, if no role name is given,
then all local roles for this user are deleted:
lroles jerome Manager [Moderator] [...]
"""
if not unexpanded :
return self.errormessage('Needs an userid as the first argument')
username = unexpanded[0]
if len(unexpanded) > 1 :
roles = unexpanded[1:]
# Zope accepts even if user and roles don't exist at all
# so we have to test it ourselves
if username not in self.__context.acl_users.getUserNames() :
return self.errormessage("Unknown user %s" % username)
if not self.HasPerms(self.__context, "Change permissions") :
return -1
# should we also test if roles exits ?
self.__context.manage_setLocalRoles(userid=username, roles=roles)
self.htmlmessage('User %s now has local roles: %s' % (username, string.join(roles, ', ')))
else :
self.__context.manage_delLocalRoles(userids=[username])
self.htmlmessage('User %s now has no local role' % username)
def run_whoami(self, expanded, unexpanded) :
"""Shows the current username"""
if expanded :
return self.errormessage("Doesn't need any argument")
(username, roles) = self.WhoAmI()
self.htmlmessage('Username: %s Roles: %s' % (username, string.join(roles, ', ')), safe=1)
self.printf("%s\n" % username)
def run_pack(self, expanded, unexpanded) :
"""Packs the ZODB
Accepts a number of days as its single argument:
pack 3
"""
if not self.HasRoles(self.__context.Control_Panel, 'Manager') :
return -1
if len(unexpanded) > 1 :
return self.errormessage('Needs a number of days as an argument')
elif not unexpanded :
ndays = 0
else :
ndays = int(unexpanded[0])
try:
self.__context.Control_Panel.manage_pack(days = ndays)
except FileStorage.FileStorageError :
pass # no pack needed, but we don't want the error message
self.htmlmessage("Database packed")
def run_restart(self, expanded, unexpanded) :
"""Restarts Zope
Works fine but unfortunately Zope redirects
us automatically to zshell/manage_main
"""
if not self.HasRoles(self.__context.Control_Panel, 'Manager') :
return -1
if expanded :
return self.errormessage("Doesn't need any argument")
self.__context.Control_Panel.manage_restart(self.__context.REQUEST.URL0)
def run_shutdown(self, expanded, unexpanded) :
"""Shutdowns Zope
Works fine but unfortunately looks ugly.
"""
if not self.HasRoles(self.__context.Control_Panel, 'Manager') :
return -1
if expanded :
return self.errormessage("Doesn't need any argument")
self.__context.Control_Panel.manage_shutdown()
def run_mkver(self, expanded, unexpanded) :
"""Create versions
Accepts multiple arguments:
mkver debug [stable] [...]
"""
if not unexpanded :
return self.errormessage('Needs a version id as the first argument')
if not self.HasPerms(self.__context, 'Add Versions') :
return -1
status = 0
for vid in unexpanded :
if vid in self.__context.objectIds() :
status = status + self.errormessage('Object %s already exists' % vid)
else :
self.__context.manage_addProduct["OFSP"].manage_addVersion(id = vid, title = vid)
self.htmlmessage('Version %s created' % vid)
return status
def run_enter(self, expanded, unexpanded) :
"""Enter into a version
Accepts a version id (with an optional path) as its only argument
enter debug
Caveats: it seems you're not really in the version until the end of the
transaction, so commands entered after 'enter' will work outside
of this version.
"""
if not expanded :
return self.errormessage('Needs a version id as an argument')
vexist = self.toObject(self.__context, expanded[0])
if not vexist :
return self.errormessage("Version <em><b>%s</b></em> doesn't exist" % expanded[0])
else :
if not self.HasPerms(vexist, 'Join/leave Versions') :
return -1
vexist.enter(self.__context.REQUEST, self.__context.REQUEST.RESPONSE)
# get_transaction().commit(1) doesn't seem to do it !
# don't be fucked by Zope's automatic redirection
self.__context.REQUEST.RESPONSE.setStatus(200)
self.htmlmessage("You'll be working in version %s at the end of the current transaction" % self.ObjectPath(vexist))
def run_leave(self, expanded, unexpanded) :
"""Quits a version
Accepts a version id as its only argument
leave debug
Caveats: it seems you're not really outside of the version until the end of the
transaction, so commands entered after 'leave' will work inside
this version.
"""
if not expanded :
return self.errormessage('Needs a version id as an argument')
vexist = self.toObject(self.__context, expanded[0])
if not vexist :
return self.errormessage("Version <em><b>%s</b></em> doesn't exist" % expanded[0])
else :
if not self.HasPerms(vexist, 'Join/leave Versions') :
return -1
vexist.leave(self.__context.REQUEST, self.__context.REQUEST.RESPONSE)
# get_transaction().commit(1) doesn't seem to do it !
# don't be fucked by Zope's automatic redirection
self.__context.REQUEST.RESPONSE.setStatus(200)
self.htmlmessage("You'll not be working in version %s anymore at the end of the current transaction" % self.ObjectPath(vexist))
def run_save(self, expanded, unexpanded) :
"""Commits a version's changes
Accepts a commit message as its optional argument:
save The new version seems to be OK
WARNING: Needs testers
"""
if not self.__version :
return self.errormessage("Not in a version")
else :
objver = self.toObject(self.__context, self.__version)
if objver is None :
return self.errormessage("Error while accessing version %s" % self.__version)
else :
if not self.HasPerms(objver, 'Save/discard Version changes') :
return -1
# for save, remark doesn't have a default value (according to Zope 2.3.0 sources)
objver.save(remark = (string.join(expanded, ' ') or 'No comment'))
self.htmlmessage("Version %s saved" % self.ObjectPath(objver))
def run_discard(self, expanded, unexpanded) :
"""Discards a version's changes
Accepts a discard message as its optional argument:
discard I was doing completely wrong
WARNING: Needs testers
"""
if not self.__version :
return self.errormessage("Not in a version")
else :
objver = self.toObject(self.__context, self.__version)
if objver is None :
return self.errormessage("Error while accessing version %s" % self.__version)
else :
if not self.HasPerms(objver, 'Save/discard Version changes') :
return -1
# for discard, remark's default value is an empty string
objver.discard(remark = string.join(expanded, ' '))
self.htmlmessage("Version %s discarded" % self.ObjectPath(objver))
def run_copy(self, expanded, unexpanded) :
"""Copy objects to the clipboard
Accepts multiple objects ids as its arguments, but
each object must be in the current Folder:
copy obj1 [obj2] [...]
"""
if not expanded :
return self.errormessage("Needs some objects ids to copy")
if not self.HasPerms(self.__context, 'View management screens') :
return -1
status = 0
objids = []
for objid in expanded :
if '/' in objid :
status = status + self.errormessage('Paths for objects ids are not allowed at this time: %s' % objid)
else :
objids.append(objid)
try :
self._clipboard = self.__context.manage_copyObjects(ids = objids)
for objid in objids :
self.htmlmessage('%s copied to clipboard' % objid)
except AttributeError, msg :
status = status + self.errormessage("Object %s doesn't exist" % msg)
return status
def run_cut(self, expanded, unexpanded) :
"""Cut objects to the clipboard
Accepts multiple objects ids as its arguments, but
each object must be in the current Folder:
cut obj1 [obj2] [...]
"""
if not expanded :
return self.errormessage("Needs some objects ids to cut")
if not self.HasPerms(self.__context, 'View management screens') :
return -1
status = 0
objids = []
for objid in expanded :
if '/' in objid :
status = status + self.errormessage('Paths for objects ids are not allowed at this time: %s' % objid)
else :
objids.append(objid)
try :
self._clipboard = self.__context.manage_cutObjects(ids = objids)
for objid in objids :
self.htmlmessage('%s cut to clipboard' % objid)
except AttributeError, msg :
status = status + self.errormessage("Object %s doesn't exist" % msg)
return status
def run_paste(self, expanded, unexpanded) :
"""Paste the clipboard's contents into the current Folder"""
if expanded :
return self.errormessage("Doesn't need any argument")
if not self.HasPerms(self.__context, 'View management screens') :
return -1
if not hasattr(self, '_clipboard') :
return self.errormessage("Clipboard is empty")
try :
self.__context.manage_pasteObjects(cb_copy_data = self._clipboard)
self.htmlmessage("Clipboard's content pasted into %s" % self.ObjectPath(self.__context))
except CopyError :
return self.errormessage("Impossible to paste clipboard's content into %s" % self.ObjectPath(self.__context))
def run_dump(self, expanded, unexpanded) :
"""Exports objects source to a directory on the server
Accepts a destination directory as its first
argument. The destination directory must exist
on the server.
Accepts multiple objects ids to export as its
following arguments.
dump /home/jerome/dtml /*_html /*_dtml
Hint: Be careful to give sufficient permissions to
the user which Zope runs as on the destination
directory.
Caveats: Only objects which have a callable
document_src attribute can be exported this way.
"""
if len(expanded) < 2 :
return self.errormessage("Needs at least a destination directory and one object id to dump")
destination = os.path.normpath(os.path.expanduser(expanded[0])) # in case there's a ~username
if not os.path.isdir(destination) :
return self.errormessage("%s is not a directory" % destination)
status = 0
for arg in expanded[1:] :
object = self.toObject(self.__context, arg)
if object is None :
status = status + self.errormessage("Object %s doesn't exist" % arg)
elif not self.HasPerms(object, 'View management screens') :
status = status - 1
elif not hasattr(object, "document_src") or not callable(object.document_src) :
status = status + self.errormessage("Doesn't know how to dump object %s" % arg)
else :
fname = os.path.join(destination, object.getId())
try :
fout = open(fname, "wb")
fout.write(object.document_src())
fout.close()
self.htmlmessage("Object %s dumped to server as %s" % (self.ObjectPath(object), fname))
except IOError, msg :
status = status + self.errormessage('Error %s, occured while dumping %s' % (msg, arg))
return status
def run_export(self, expanded, unexpanded) :
"""Exports objects to the server
Accepts a --xml option to export as xml
data.
Accepts multiple objects ids to export
export --xml MyPath/Myobject /QuickStart
Caveats: Exports exclusively to the server.
The Root Folder is exported as .zexp or .xml
"""
options, args = self.getopt(["xml"], expanded)
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
if not args :
return self.errormessage("Needs at least one object id to export")
status = 0
for arg in args :
object = self.toObject(self.__context, arg)
if object is None :
status = status + self.errormessage("Object %s doesn't exist" % arg)
elif not hasattr(object, "aq_parent") :
status = status + self.errormessage("Object %s is not exportable" % arg)
elif not self.HasPerms(object.aq_parent, 'Import/Export objects') :
status = status - 1
else :
toxml = 0
download = 0 # TODO: Zope 2.3.2 is buggy, so don't allow downloads yet
if options.has_key("xml") :
toxml = 1
object.aq_parent.manage_exportObject(id=object.getId(), download=download, toxml=toxml)
fname = "%s.%s" % (object.getId(), (toxml and 'xml') or 'zexp')
self.htmlmessage("Object %s exported to server as %s" % (self.ObjectPath(object), fname))
return status
def run_import(self, expanded, unexpanded) :
"""Imports objects into the current Folder
Accepts multiple filenames to import:
import one.zexp [two.zexp] [...]
"""
if not unexpanded :
return self.errormessage("Needs some filenames to import")
if not self.HasPerms(self.__context, 'Import/Export objects') :
return -1
for filename in unexpanded :
self.__context.manage_importObject(filename)
self.htmlmessage('%s imported successfully' % filename)
def run_takeown(self, expanded, unexpanded) :
"""Take ownership
Optionally accepts --recurse as an option to ask for a recursive action.
Accepts multiple pathnames as its other arguments:
takeown [--recurse] path/to/obj1 [otherpath/obj2] [...]
Caveats: Due to a bug in Zope 2.3.2, taking ownership
recursively from a folderish object you already own
does nothing, even if you don't already own the full
subtree. You should try to do it with the find command
instead if you don't want Zope's default buggy behavior.
"""
options, args = self.getopt(["recurse"], expanded)
if (options is None) and (args is None) :
return -1 # message was already displayed in self.getopt()
recursive = 0
recursive_msg = ""
if options.has_key("recurse") :
recursive = 1
recursive_msg = "recursively "
if not args :
return self.errormessage("Needs at least one object id")
chownto = AccessControl.getSecurityManager().getUser()
status = 0
for objpath in args :
object = self.toObject(self.__context, objpath)
if object is None :
status = status + self.errormessage('Incorrect path: %s' % objpath)
elif not self.HasPerms(object, 'Take ownership') :
status = status - 1
else :
object.changeOwnership(chownto, recursive = recursive)
self.htmlmessage('%s owner %schanged to %s' % (self.ObjectPath(object), recursive_msg, chownto.getUserName()))
return status
def run_man(self, expanded, unexpanded) :
"""Displays command's man pages
Accepts several commands' names as its arguments
or none to list all commands:
man [cmd1] [...]
"""
methodslist = map(lambda n: n[4:], filter(lambda f: f[:4] == 'run_', self.__class__.__dict__.keys()))
if not unexpanded :
unexpanded = methodslist
unexpanded.sort()
results = []
for method in unexpanded :
if not hasattr(self, 'run_' + method) :
help = "Invalid command"
else :
help = getattr(self, 'run_' + method).__doc__
if not help :
help = "Undocumented command"
else :
help = string.join(map(string.strip, string.split(help, '\n')), '<br />')
command = '<a href="%s?zshellscript=man%%20%s&zshellscript=%s&zshelldontrun=1">%s</a>' % (self.__context.REQUEST.URL0, method, method, method)
results.append({"Command": command, "Help": help})
self.printf("%s: %s\n" % (method, help))
self.tableDisplay("man", ["Command", "Help"], results)
def run_whatis(self, expanded, unexpanded) :
"""An alias to man"""
return self.run_man(expanded, unexpanded)
def run_help(self, expanded, unexpanded) :
"""An alias to man"""
return self.run_man(expanded, unexpanded)
def run_apropos(self, expanded, unexpanded) :
"""An alias to man"""
return self.run_man(expanded, unexpanded)
def run_about(self, expanded, unexpanded) :
"""About this software"""
msg = "ZShell v%s" % __version__
url = "http://cortex.unice.fr/~jerome/zshell/"
who = "Jerome Alet"
email = "[email protected]"
self.__stdout.write("%s (%s) (C) 2001 %s - %s\n%s\n" % (msg, url, who, email, __doc__))
x = self.__temphtml
x._text("%s (" % msg)
x.a(url, href="url", target="top")
x._text(") by ")
x.a(who, href="mailto:%s" % email)
x._br().pre("%s" % __doc__)
def run_zhelp(self, expanded, unexpanded) :
"""Search terms in Zope's internal help
Accepts multiple arguments which are concatenated together:
zhelp ZClass
"""
self.__context.REQUEST.set("SearchableText", string.join(unexpanded, " "))
results = self.__context.HelpSys.results(self.__context, self.__context.REQUEST)
# we just want the result lines begining with an <a href=" tag, the rest is uninteresting
self.htmlmessage(string.join(filter(lambda r: r[:9] == '<a href="', string.split(results, "\n")), "\n"), safe=1)
def run_google(self, expanded, unexpanded) :
"""Search a phrase on Google
google zope
Caveats: Nicer in a browser with JavaScript support
"""
# we need a way to display it correctly in our page
# unfortunately links in google's results are relative
# so while the display is correct, link to previous and next results
# are incorrect.
# current solution: opens a new windows with javascript
self.newwindow('http://www.google.com/search?q=%s' % string.join(unexpanded, '%20'))
def run_nipltd(self, expanded, unexpanded) :
"""Search a phrase on NIP Ltd Zope's archives
nipltd subtransactions
Caveats: Nicer in a browser with JavaScript support
"""
# see the google command
self.newwindow('http://zope.nipltd.com/public/lists/zope-archive.nsf/Main?SearchView&Query=%s' % string.join(unexpanded, '%20'))
def run_wget(self, expanded, unexpanded) :
"""Sucks documents from the web and import them in the current Folder
Accepts multiple arguments, and can retrieve files from the
filesystem, or even complete directories.
wget http://www.zope.org/ http://www.gnu.org/graphics/gnu-head-sm.jpg
or:
wget ~jerome/mydirectory/*.html /home/*/public_html http://localhost
Caveats: Maybe needs a recursive option, but I'm not sure:
it's already very powerful.
"""
if not unexpanded:
return self.errormessage('Needs at least one argument')
if not self.HasPerms(self.__context, 'Add Documents, Images, and Files') :
return -1
# expand arguments from the filesystem, not the ZODB
# in the case there's a file: scheme or no scheme at all.
expanded = []
for uarg in unexpanded :
if uarg[:5] == 'file:' :
uarg = uarg[5:]
uarg = os.path.expanduser(uarg) # in case of a ~username
if os.path.isdir(uarg) : # it's a filesystem directory, so we want all its files
uarg = os.path.join(uarg, '*')
expanded.extend(self.ShellExpand(uarg, zodb=0) or [uarg])
status = 0
for arg in expanded :
try :
# WARNING: both urlopen and object.read may
# raise an IOError, in the latter case that's when
# object is a filesystem directory
object = urllib.urlopen(arg)
info = object.info()
ctype = info.gettype()
mtype = info.getmaintype()
stype = info.getsubtype()
data = object.read()
realurl = object.geturl()
if realurl[-1] == '/' :
fname = 'index' + '_' + stype
else :
fname = filter(None, string.split(realurl, '/'))[-1]
if fname in self.__context.objectIds() :
status = status + self.errormessage('Object %s already exists' % fname)
else :
if mtype == "image" :
# Image
self.__context.manage_addImage(id = fname, file = data, title = realurl, precondition = '', content_type = ctype)
elif ctype == 'text/html' :
# DTML Document
self.__context.manage_addDTMLDocument(id = fname, title = realurl, file = data)
else :
# normal File
self.__context.manage_addFile(id = fname, file = data, title = realurl, precondition = '', content_type = ctype)
self.htmlmessage('%s added as %s, size is %ld bytes' % (realurl, fname, len(data)))
del data
object.close()
del object
except IOError,msg :
status = status + self.errormessage('Error %s, occured while retrieving %s' % (msg, arg))
return status
def run_mkuf(self, expanded, unexpanded) :
"""Creates User Folders
Accepts multiple folders as its arguments:
mkuf /Folder1/Folder2 [...]
Remark: The User Folder is only created in the deepest
folderish object, e.g. in the example above
the User Folder will be created in Folder1/Folder2
but not in Folder1.
"""
if not expanded :
return self.errormessage("Needs at least a folder id")
status = 0
for arg in expanded :
object = self.toObject(self.__context, arg)
if object is not None :
if not object.isPrincipiaFolderish :
status = status + self.errormessage("%s is not a Folderish object" % self.ObjectPath(object))
elif 'acl_users' in object.objectIds() :
status = status + self.errormessage("%s already contains an User Folder or an object named acl_users" % self.ObjectPath(object))
elif not self.HasPerms(object, 'Add User Folders') :
status = status - 1
else :
object.manage_addUserFolder()
self.htmlmessage("User Folder added in object %s" % self.ObjectPath(object))
return status
def run_mkdir(self, expanded, unexpanded) :
"""Create Folders
Accepts multiple pathnames as its arguments,
and creates Folders recursively:
mkdir path/to/folder/to/create [...]
"""
if not expanded:
return self.errormessage('Needs at least one argument')
status = 0
for foldername in expanded :
exist = self.toObject(self.__context, foldername)
if exist is not None :
status = status + self.errormessage('Folder %s already exists' % self.ObjectPath(exist))
continue
if foldername[0] == '/' :
c = '/'
foldername = foldername[1:]
else :
c = ''
components = filter(None, string.split(foldername, '/'))
curpath = c[:]
oldcurpath = curpath[:]
error = 0
for component in components :
if curpath != c :
curpath = curpath + '/' + component
else :
curpath = curpath + component
if self.toObject(self.__context, curpath) is None :
parent = self.toObject(self.__context, oldcurpath)
if parent is None :
status = status + self.errormessage('Unknown error on %s' % foldername)
error = 1
break
if component not in parent.objectIds() :
if not self.HasPerms(parent, "Add Folders") :
status = status - 1
error = 1
break
else :
parent.manage_addFolder(id = component)
oldcurpath = curpath[:]
if not error :
self.htmlmessage('Folder %s created' % foldername)
return status
def run_pwd(self, expanded, unexpanded) :
"""Returns the current working Folder"""
if expanded :
return self.errormessage("Doesn't need any argument")
self.printf("%s" % self.ObjectPath(self.__context))
self.htmlmessage("Current folder is: %s" % self.ObjectPath(self.__context))
def run_cd(self, expanded, unexpanded) :
"""Change Directory
Accepts a full pathname as its single argument:
cd SubFolder/SubSubFolder
"""
if len(expanded) != 1 :
return self.errormessage('Needs one and only one argument')
newdir = self.toObject(self.__context, expanded[0])
if (newdir is not None) and newdir.isPrincipiaFolderish :
self.__context = newdir
self.htmlmessage("Current folder is: %s" % self.ObjectPath(newdir))
else :
self.errormessage("Incorrect path: %s" % expanded[0])
self.run_pwd([], [])
return -1
def run_rm(self, expanded, unexpanded) :
"""Deletes objects
Accepts multiple pathnames as its arguments:
rm path1/object1 [...]
WARNING: deletion is recursive and you can even delete the
current Folder. USE AT YOUR OWN RISK !
"""
if not expanded :
return self.errormessage('Needs at least one argument')
status = 0
for objpath in expanded :
object = self.toObject(self.__context, objpath)
if object is None :
status = status + self.errormessage("Incorrect path: %s" % objpath)
else :
objurl = self.ObjectPath(object)
if hasattr(object, 'aq_parent') :
if not self.HasPerms(object.aq_parent, 'Delete objects') :
status = status - 1
else :
if not hasattr(object.aq_parent, "manage_delObjects") or not callable(object.aq_parent.manage_delObjects) :
status = status + self.errormessage("manage_delObjects: operation not supported on %s" % self.ObjectPath(object.aq_parent))
else :
object.aq_parent.manage_delObjects(ids = [object.getId()])
self.htmlmessage("%s removed" % objurl)
else :
status = status + self.errormessage("Unknown error on: %s" % objurl)
return status
def run_mv(self, expanded, unexpanded) :
"""Moves objects
Accepts multiple source arguments
Each source argument must be an object id and
each object should be in the current Folder.
The last argument is the destination Folder,
and may be a slash delimited pathname:
mv obj1 [obj2] [...] destination
Caveats: can't rename objects yet
"""
return self.mv_or_cp("mv", expanded)
def run_cp(self, expanded, unexpanded) :
"""Copy objects objects
Accepts multiple source arguments
Each source argument must be an object id and
each object should be in the current Folder.
The last argument is the destination Folder,
and may be a slash delimited pathname:
cp obj1 [obj2] [...] destination
Caveats: destination must be a Folder
"""
return self.mv_or_cp("cp", expanded)
def run_ls(self, expanded, unexpanded) :
"""List objects
Accepts multiple arguments:
ls Contr?l_Pan*/Products/Sq* MyFolder/*
"""
if not expanded :
expanded = self.ShellExpand("*") # no argument: list all
if expanded :
status = 0
results = []
for arg in expanded :
object = self.toObject(self.__context, arg)
if object is not None :
if hasattr(object, 'aq_parent') :
if not self.HasPerms(object.aq_parent, 'Access contents information') :
status = status - 1
continue
objurl = self.ObjectPath(object)
urls = '<a href="%s/manage" target="top">Manage</a>/<a href="%s" target="top">View</a>' % (objurl, objurl)
ownerinfo = object.owner_info()
if (ownerinfo is not None) :
if hasattr(ownerinfo, "has_key") and ownerinfo.has_key('id') :
owner = ownerinfo['id']
else :
# at least for /Control_Panel/Products[/*]
owner = repr(ownerinfo)
else :
owner = 'Not owned'
try :
modtime = object.bobobase_modification_time().strftime("%Y-%m-%d %H:%M:%S %Z")
except :
modtime = "Unknown"
results.append({"Id": object.getId(), "Title": object.title, "MetaType": self.getMetaType(object), "Mod. Time": modtime, "Owner": owner, "SubObj": len(object.objectValues()), "Actions": urls })
self.printf("%s\n" % objurl)
self.tableDisplay("ls", ["Id", "Title", "MetaType", "Mod. Time", "Owner", "SubObj", "Actions"], results)
return status
else :
self.htmlmessage("Empty")
def run_setprop(self, expanded, unexpanded) :
"""Sets an object's property value
Accepts a property id as its first argument,
the property value as its second argument, and
objects ids as its remaining arguments:
setprop author "William Shakespeare" Hamlet_dtml Othello_?tml
This sets the property 'author' value to 'William Shakespeare'
to the objects Hamlet_dtml and Othello_?tml in the
current Folder.
"""
if len(expanded) < 3 :
return self.errormessage("Needs at least a property id, a property value and an object id")
property = expanded[0]
value = expanded[1]
status = 0
for arg in expanded[2:] :
object = self.toObject(self.__context, arg)
if object is not None :
if not self.HasPerms(object, 'Manage properties') :
status = status - 1
elif hasattr(object, 'hasProperty') :
if not object.hasProperty(property) :
status = status + self.errormessage("Object %s has no property %s" % (self.ObjectPath(object), property))
else :
# in the following lines the absence of a _setProperty
# attribute indicates an object without properties (e.g. a method)
# which indicates an object for which setting properties is a nonsense
if hasattr(object, "_setProperty") :
object._updateProperty(property, value)
self.htmlmessage("Object %s property %s modified to '%s'" % (self.ObjectPath(object), property, str(value)))
return status
def run_addprop(self, expanded, unexpanded) :
"""Adds an empty property to objects
Accepts a property as its first argument, the
property is composed of 2 components separated by
slashes: the id and the type of the property.
Accepts multiple objects ids as its other arguments:
addprop description/string .
This adds a property named 'description' and type string
to the current Folder.
addprop author/string /QuickStart/*_html
This adds a property named 'author' and type string
to all objects which id match *_html in the
/QuickStart Folder
Caveats: No check is done to verify that the property type is valid.
"""
if len(expanded) < 2 :
return self.errormessage("Needs at least a property/type pair and one object id")
split = string.split(expanded[0], '/', 1)
if len(split) != 2 :
return self.errormessage("Wrong format for the property/type pair: %s" % expanded[0])
(property, proptype) = split
status = 0
for arg in expanded[1:] :
object = self.toObject(self.__context, arg)
if object is not None :
if not self.HasPerms(object, 'Manage properties') :
status = status - 1
elif hasattr(object, 'hasProperty') :
if object.hasProperty(property) :
status = status + self.errormessage("Object %s already has property %s" % (self.ObjectPath(object), property))
else :
# in the following lines the absence of a _setProperty
# attribute indicates an object without properties (e.g. a method)
# which indicates an object for which setting properties is a nonsense
if hasattr(object, "_setProperty") :
object._setProperty(property, "", proptype)
self.htmlmessage("Property %s added to object %s" % (property, self.ObjectPath(object)))
return status
def run_delprop(self, expanded, unexpanded) :
"""Deletes objects properties
Accepts a property name as its first argument.
Accepts multiple objects ids as its other arguments:
delprop MyProperty MyFolder/* *html
"""
if len(expanded) < 2 :
return self.errormessage("Needs at least a property name and one object id")
property = expanded[0]
status = 0
for arg in expanded[1:] :
object = self.toObject(self.__context, arg)
if object is not None :
if not self.HasPerms(object, 'Manage properties') :
status = status - 1
elif hasattr(object, 'hasProperty') :
if not object.hasProperty(property) :
status = status + self.errormessage("Object %s has no property %s" % (self.ObjectPath(object), property))
else :
# in the following lines the absence of a _delProperty
# attribute indicates an object without properties (e.g. a method)
# which indicates an object for which deleting properties is a nonsense
if hasattr(object, "_delProperty") :
object._delProperty(property)
self.htmlmessage("Property %s deleted from object %s" % (property, self.ObjectPath(object)))
return status
def run_lsprop(self, expanded, unexpanded) :
"""List objects properties
Accepts multiple arguments:
lsprop Contr?l_Pan*/Products/Sq* MyFolder/*
Caveats: Not sure that the result is correct wrt acquisition,
please report problems.
"""
if not expanded :
return self.errormessage("Needs at least one object id")
status = 0
results = []
for arg in expanded :
object = self.toObject(self.__context, arg)
if object is not None :
if not self.HasPerms(object, 'Access contents information') :
status = status - 1
elif not hasattr(object, "propertyMap") :
status = status + self.errormessage("Object %s doesn't have any property" % self.ObjectPath(object))
else :
# in the following lines the absence of an _updateProperty
# attribute indicates an object without properties (e.g. a method)
# which indicates an object for which listing properties is a nonsense
if hasattr(object, "_updateProperty") :
for prop in object.propertyMap() :
propid = "%s.%s" % (object.getId(), prop["id"])
proptype = prop["type"]
propmode = prop.get("mode", "")
propvalue = repr(object.getProperty(prop["id"], 'Error'))
results.append({"Property": propid, "Type": proptype, "Value": propvalue, "Mode": propmode})
self.printf("%s\n" % propid)
self.tableDisplay("lsprop", ["Property", "Type", "Value", "Mode"], results)
return status
# How do I know where I am
# this returns a nice simple path of the kind the html form
# products
def get_path(self):
return string.join(self.__context.getPhysicalPath(), '/')
def zshell(self, zshellscript=None, xmlrpcstuff=None) :
# so we can check for the type of data coming in to see
# if this being called by xmlrpc
from types import DictType
# ok we need to be a little more helpful in what we are passing through
# xmlrpc doesnot allow key = value args, so we will pass through a
# hash for the moment and sort it all out.
r = None
if type(xmlrpcstuff)==DictType:
self.REQUEST['zshellscript'] = xmlrpcstuff['command']
self.REQUEST['zshellpwd'] = xmlrpcstuff['path']
r = xmlrpcstuff['result_type']
MyShell = ZopeShell(self)
# well we dont want to return html to command line,
# plus if you do a cd, we want to know the path.
# so lets pass this back in a dict that xml rpc knows how to marshal
if r == 'text':
output = { 'data':MyShell.get_stdout(), 'path':MyShell.get_path() }
return output
else:
return MyShell.get_HTML()