diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 969807440f0ba5dafad679b0014311b8feba2344..11a22172e9c1f69437c139afa66525d78d6986a1 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
"""
__docformat__ = 'restructuredtext'
-import base64, binascii, cgi, codecs, httplib, mimetypes, os
-import quopri, random, re, rfc822, stat, sys, time, urllib, urlparse
-import Cookie, socket, errno
-from Cookie import CookieError, BaseCookie, SimpleCookie
-from cStringIO import StringIO
+import base64, binascii, cgi, codecs, mimetypes, os
+import quopri, random, re, rfc822, stat, sys, time
+import socket, errno
+from traceback import format_exc
from roundup import roundupdb, date, hyperdb, password
from roundup.cgi import templating, cgitb, TranslationService
from roundup.cgi import accept_language
from roundup import xmlrpc
+from roundup.anypy.cookie_ import CookieError, BaseCookie, SimpleCookie, \
+ get_cookie_date
+from roundup.anypy.io_ import StringIO
+from roundup.anypy import http_
+from roundup.anypy import urllib_
+
+from email.MIMEBase import MIMEBase
+from email.MIMEText import MIMEText
+from email.MIMEMultipart import MIMEMultipart
+
def initialiseSecurity(security):
'''Create some Permissions and Roles on the security object
def clean_message_callback(match, ok={'a':1,'i':1,'b':1,'br':1}):
""" Strip all non <a>,<i>,<b> and <br> tags from a string
"""
- if ok.has_key(match.group(3).lower()):
+ if match.group(3).lower() in ok:
return match.group(1)
return '<%s>'%match.group(2)
# this is the "cookie path" for this tracker (ie. the path part of
# the "base" url)
- self.cookie_path = urlparse.urlparse(self.base)[2]
+ self.cookie_path = urllib_.urlparse(self.base)[2]
# cookies to set in http responce
# {(path, name): (value, expire)}
self._cookies = {}
# see if we need to re-parse the environment for the form (eg Zope)
if form is None:
- self.form = cgi.FieldStorage(environ=env)
+ self.form = cgi.FieldStorage(fp=request.rfile, environ=env)
else:
self.form = form
self.determine_language()
# Open the database as the correct user.
self.determine_user()
+ self.check_anonymous_access()
# Call the appropriate XML-RPC method.
handler = xmlrpc.RoundupDispatcher(self.db,
# figure out the context and desired content template
self.determine_context()
+ # if we've made it this far the context is to a bit of
+ # Roundup's real web interface (not a file being served up)
+ # so do the Anonymous Web Acess check now
+ self.check_anonymous_access()
+
# possibly handle a form submit action (may change self.classname
# and self.template, and may also append error/ok_messages)
html = self.handle_action()
# authorization, send back a response that will cause the
# browser to prompt the user again.
if self.instance.config.WEB_HTTP_AUTH:
- self.response_code = httplib.UNAUTHORIZED
+ self.response_code = http_.client.UNAUTHORIZED
realm = self.instance.config.TRACKER_NAME
self.setHeader("WWW-Authenticate",
"Basic realm=\"%s\"" % realm)
else:
- self.response_code = httplib.FORBIDDEN
+ self.response_code = http_.client.FORBIDDEN
self.renderFrontPage(message)
except Unauthorised, message:
# users may always see the front page
# we can't map the URL to a class we know about
# reraise the NotFound and let roundup_server
# handle it
- raise NotFound, e
+ raise NotFound(e)
except FormError, e:
self.error_message.append(self._('Form Error: ') + str(e))
self.write_html(self.renderContext())
except:
# Something has gone badly wrong. Therefore, we should
# make sure that the response code indicates failure.
- if self.response_code == httplib.OK:
- self.response_code = httplib.INTERNAL_SERVER_ERROR
+ if self.response_code == http_.client.OK:
+ self.response_code = http_.client.INTERNAL_SERVER_ERROR
# Help the administrator work out what went wrong.
html = ("<h1>Traceback</h1>"
+ cgitb.html(i18n=self.translator)
if not self.instance.config.WEB_DEBUG:
exc_info = sys.exc_info()
subject = "Error: %s" % exc_info[1]
- self.send_html_to_admin(subject, html)
+ self.send_error_to_admin(subject, html, format_exc())
self.write_html(self._(error_message))
else:
self.write_html(html)
"""
# look for client charset
charset_parameter = 0
- if self.form.has_key('@charset'):
+ if '@charset' in self.form:
charset = self.form['@charset'].value
if charset.lower() == "none":
charset = ""
charset_parameter = 1
- elif self.cookie.has_key('roundup_charset'):
+ elif 'roundup_charset' in self.cookie:
charset = self.cookie['roundup_charset'].value
else:
charset = None
uc = int(num)
return unichr(uc)
- for field_name in self.form.keys():
+ for field_name in self.form:
field = self.form[field_name]
if (field.type == 'text/plain') and not field.filename:
try:
# look for language parameter
# then for language cookie
# last for the Accept-Language header
- if self.form.has_key("@language"):
+ if "@language" in self.form:
language = self.form["@language"].value
if language.lower() == "none":
language = ""
self.add_cookie("roundup_language", language)
- elif self.cookie.has_key("roundup_language"):
+ elif "roundup_language" in self.cookie:
language = self.cookie["roundup_language"].value
elif self.instance.config["WEB_USE_BROWSER_LANGUAGE"]:
hal = self.env.get('HTTP_ACCEPT_LANGUAGE')
user = None
# first up, try http authorization if enabled
if self.instance.config['WEB_HTTP_AUTH']:
- if self.env.has_key('REMOTE_USER'):
+ if 'REMOTE_USER' in self.env:
# we have external auth (e.g. by Apache)
user = self.env['REMOTE_USER']
elif self.env.get('HTTP_AUTHORIZATION', ''):
# make sure the anonymous user is valid if we're using it
if user == 'anonymous':
self.make_user_anonymous()
- if not self.db.security.hasPermission('Web Access', self.userid):
- raise Unauthorised, self._("Anonymous users are not "
- "allowed to use the web interface")
else:
self.user = user
# reopen the database as the correct user
self.opendb(self.user)
+ def check_anonymous_access(self):
+ """Check that the Anonymous user is actually allowed to use the web
+ interface and short-circuit all further processing if they're not.
+ """
+ # allow Anonymous to use the "login" and "register" actions (noting
+ # that "register" has its own "Register" permission check)
+
+ if ':action' in self.form:
+ action = self.form[':action']
+ elif '@action' in self.form:
+ action = self.form['@action']
+ else:
+ action = ''
+ if isinstance(action, list):
+ raise SeriousError('broken form: multiple @action values submitted')
+ elif action != '':
+ action = action.value.lower()
+ if action in ('login', 'register'):
+ return
+
+ # allow Anonymous to view the "user" "register" template if they're
+ # allowed to register
+ if (self.db.security.hasPermission('Register', self.userid, 'user')
+ and self.classname == 'user' and self.template == 'register'):
+ return
+
+ # otherwise for everything else
+ if self.user == 'anonymous':
+ if not self.db.security.hasPermission('Web Access', self.userid):
+ raise Unauthorised(self._("Anonymous users are not "
+ "allowed to use the web interface"))
+
def opendb(self, username):
"""Open the database and set the current user.
# see if a template or messages are specified
template_override = ok_message = error_message = None
- for key in self.form.keys():
+ for key in self.form:
if self.FV_TEMPLATE.match(key):
template_override = self.form[key].value
elif self.FV_OK_MESSAGE.match(key):
self.template = ''
return
elif path[0] in ('_file', '@@file'):
- raise SendStaticFile, os.path.join(*path[1:])
+ raise SendStaticFile(os.path.join(*path[1:]))
else:
self.classname = path[0]
if len(path) > 1:
# send the file identified by the designator in path[0]
- raise SendFile, path[0]
+ raise SendFile(path[0])
# see if we got a designator
m = dre.match(self.classname)
try:
klass = self.db.getclass(self.classname)
except KeyError:
- raise NotFound, '%s/%s'%(self.classname, self.nodeid)
+ raise NotFound('%s/%s'%(self.classname, self.nodeid))
if not klass.hasnode(self.nodeid):
- raise NotFound, '%s/%s'%(self.classname, self.nodeid)
+ raise NotFound('%s/%s'%(self.classname, self.nodeid))
# with a designator, we default to item view
self.template = 'item'
else:
try:
self.db.getclass(self.classname)
except KeyError:
- raise NotFound, self.classname
+ raise NotFound(self.classname)
# see if we have a template override
if template_override is not None:
"""
m = dre.match(str(designator))
if not m:
- raise NotFound, str(designator)
+ raise NotFound(str(designator))
classname, nodeid = m.group(1), m.group(2)
try:
klass = self.db.getclass(classname)
except KeyError:
# The classname was not valid.
- raise NotFound, str(designator)
+ raise NotFound(str(designator))
+ # perform the Anonymous user access check
+ self.check_anonymous_access()
# make sure we have the appropriate properties
props = klass.getprops()
- if not props.has_key('type'):
- raise NotFound, designator
- if not props.has_key('content'):
- raise NotFound, designator
+ if 'type' not in props:
+ raise NotFound(designator)
+ if 'content' not in props:
+ raise NotFound(designator)
# make sure we have permission
if not self.db.security.hasPermission('View', self.userid,
classname, 'content', nodeid):
- raise Unauthorised, self._("You are not allowed to view "
- "this file.")
+ raise Unauthorised(self._("You are not allowed to view "
+ "this file."))
- mime_type = klass.get(nodeid, 'type')
+ try:
+ mime_type = klass.get(nodeid, 'type')
+ except IndexError, e:
+ raise NotFound(e)
+ # Can happen for msg class:
+ if not mime_type:
+ mime_type = 'text/plain'
# if the mime_type is HTML-ish then make sure we're allowed to serve up
# HTML-ish content
if os.path.isfile(filename) and filename.startswith(prefix):
break
else:
- raise NotFound, file
+ raise NotFound(file)
# last-modified time
lmt = os.stat(filename)[stat.ST_MTIME]
# XXX see which interfaces set this
#if hasattr(self.request, 'headers'):
#ims = self.request.headers.getheader('if-modified-since')
- if self.env.has_key('HTTP_IF_MODIFIED_SINCE'):
+ if 'HTTP_IF_MODIFIED_SINCE' in self.env:
# cgi will put the header in the env var
ims = self.env['HTTP_IF_MODIFIED_SINCE']
if ims:
self.additional_headers['Content-Length'] = str(len(content))
self.write(content)
- def send_html_to_admin(self, subject, content):
-
+ def send_error_to_admin(self, subject, html, txt):
+ """Send traceback information to admin via email.
+ We send both, the formatted html (with more information) and
+ the text version of the traceback. We use
+ multipart/alternative so the receiver can chose which version
+ to display.
+ """
to = [self.mailer.config.ADMIN_EMAIL]
- message = self.mailer.get_standard_message(to, subject)
- # delete existing content-type headers
- del message['Content-type']
- message['Content-type'] = 'text/html; charset=utf-8'
- message.set_payload(content)
- encode_quopri(message)
- self.mailer.smtp_send(to, str(message))
+ message = MIMEMultipart('alternative')
+ self.mailer.set_message_attributes(message, to, subject)
+ part = MIMEBase('text', 'html')
+ part.set_charset('utf-8')
+ part.set_payload(html)
+ encode_quopri(part)
+ message.attach(part)
+ part = MIMEText(txt)
+ message.attach(part)
+ self.mailer.smtp_send(to, message.as_string())
def renderFrontPage(self, message):
"""Return the front page of the tracker."""
result = result.replace('</body>', s)
return result
except templating.NoTemplate, message:
- return '<strong>%s</strong>'%message
+ return '<strong>%s</strong>'%cgi.escape(str(message))
except templating.Unauthorised, message:
- raise Unauthorised, str(message)
+ raise Unauthorised(cgi.escape(str(message)))
except:
# everything else
if self.instance.config.WEB_DEBUG:
# If possible, send the HTML page template traceback
# to the administrator.
subject = "Templating Error: %s" % exc_info[1]
- self.send_html_to_admin(subject, cgitb.pt_html())
+ self.send_error_to_admin(subject, cgitb.pt_html(), format_exc())
# Now report the error to the user.
return self._(error_message)
except:
# receive an error message, and the adminstrator will
# receive a traceback, albeit with less information
# than the one we tried to generate above.
- raise exc_info[0], exc_info[1], exc_info[2]
+ raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
# these are the actions that are available
actions = (
We explicitly catch Reject and ValueError exceptions and
present their messages to the user.
"""
- if self.form.has_key(':action'):
- action = self.form[':action'].value.lower()
- elif self.form.has_key('@action'):
- action = self.form['@action'].value.lower()
+ if ':action' in self.form:
+ action = self.form[':action']
+ elif '@action' in self.form:
+ action = self.form['@action']
else:
return None
+ if isinstance(action, list):
+ raise SeriousError('broken form: multiple @action values submitted')
+ else:
+ action = action.value.lower()
+
try:
action_klass = self.get_action_class(action)
def get_action_class(self, action_name):
if (hasattr(self.instance, 'cgi_actions') and
- self.instance.cgi_actions.has_key(action_name)):
+ action_name in self.instance.cgi_actions):
# tracker-defined action
action_klass = self.instance.cgi_actions[action_name]
else:
if name == action_name:
break
else:
- raise ValueError, 'No such action "%s"'%action_name
+ raise ValueError('No such action "%s"'%action_name)
return action_klass
def _socket_op(self, call, *args, **kwargs):
def write_html(self, content):
if not self.headers_done:
# at this point, we are sure about Content-Type
- if not self.additional_headers.has_key('Content-Type'):
+ if 'Content-Type' not in self.additional_headers:
self.additional_headers['Content-Type'] = \
'text/html; charset=%s' % self.charset
self.header()
return None
# Return code 416 with a Content-Range header giving the
# allowable range.
- self.response_code = httplib.REQUESTED_RANGE_NOT_SATISFIABLE
+ self.response_code = http_.client.REQUESTED_RANGE_NOT_SATISFIABLE
self.setHeader("Content-Range", "bytes */%d" % length)
return None
# RFC 2616 10.2.7: 206 Partial Content
#
# Tell the client that we are honoring the Range request by
# indicating that we are providing partial content.
- self.response_code = httplib.PARTIAL_CONTENT
+ self.response_code = http_.client.PARTIAL_CONTENT
# RFC 2616 14.16: Content-Range
#
# Tell the client what data we are providing.
# If the client doesn't actually want the body, or if we are
# indicating an invalid range.
if (self.env['REQUEST_METHOD'] == 'HEAD'
- or self.response_code == httplib.REQUESTED_RANGE_NOT_SATISFIABLE):
+ or self.response_code == http_.client.REQUESTED_RANGE_NOT_SATISFIABLE):
return
# Use the optimized "sendfile" operation, if possible.
if hasattr(self.request, "sendfile"):
if headers.get('Content-Type', 'text/html') == 'text/html':
headers['Content-Type'] = 'text/html; charset=utf-8'
- headers = headers.items()
+ headers = list(headers.items())
- for ((path, name), (value, expire)) in self._cookies.items():
+ for ((path, name), (value, expire)) in self._cookies.iteritems():
cookie = "%s=%s; Path=%s;"%(name, value, path)
if expire is not None:
- cookie += " expires=%s;"%Cookie._getdate(expire)
+ cookie += " expires=%s;"%get_cookie_date(expire)
headers.append(('Set-Cookie', cookie))
self._socket_op(self.request.start_response, headers, response)