""" Provides a form to send an email to the blog-owner. If you make any changes to this plugin, please send a patch to so I can incorporate them. Thanks! To install: 1) Put contact.py in your plugin directory. 2) In config.py add 'contact' to py['load_plugins'] 3) Add the following variables to config.py: py['contact_urltrigger'] = "/contact" # optional, this is the default py['contact_smtp_server'] = "localhost" # required py['contact_smtp_to'] = "you@example.com" # required 4) Optionally create a template named contact. in your flavour directory. If no contact. exists a simple default is used. See _default_template below. Dependencies: - My compatibility plugin if you're not using pyblosxom 1.2+. $Id: contact.py,v 1.6 2005/08/25 13:21:48 sar Exp $ """ __author__ = "Steven Armstrong " __version__ = "$Revision: 1.6 $ $Date: 2005/08/25 13:21:48 $" __url__ = "http://www.c-area.ch/code/" __description__ = "Provides a form to send an email to the blog-owner." __license__ = "GPL 2+" # Python imports import urlparse # Pyblosxom imports from Pyblosxom.renderers.blosxom import Renderer from Pyblosxom import tools # Variables TRIGGER = "/contact" TRIGGER_KEY = "contact_urltrigger" INIT_KEY = "contact_initiated" MESSAGE_KEY = "contact_error_message" _form_fields = ['name', 'email', 'subject', 'message'] _default_template = """

Contact me

$contact_error_message




""" ################################################################################ ## ## Helper functions ## ################################################################################ rfc822_specials = '()<>@,;:\\"[]' def isAddressValid(addr): ''' Taken from http://www.secureprogramming.com/?action=view&feature=recipes&recipeid=1 Posted by Matt Messier on Tue, Sep 02, 2003 (06:19 PM) GMT >>> isAddressValid('djfhdfh') 0 >>> isAddressValid('djfhdfh@test.com') 8 >>> isAddressValid('dj@fhdfh@test.com') 0 >>> isAddressValid('dj\@fhdfh@test.com') 0 >>> isAddressValid('dj"@"fhdfh@test.com') 0 >>> isAddressValid('dj" "fhdfh@test.com') 0 >>> isAddressValid('dj\" \"fhdfh@test.com') 0 >>> isAddressValid('dj." ".fhdfh@test.com') 13 >>> isAddressValid('dj."@ ".fhdfh@test.com') 14 >>> isAddressValid('dj."@<> ".fhdfh@test.com') 16 >>> isAddressValid('dj."@<>ΓΌ ".fhdfh@test.com') 0 >>> isAddressValid('dj<>fhdfh@test.com') 0 >>> isAddressValid('dj\<\>fhdfh@test.com') 0 >>> isAddressValid('dj\ fhdfh@test.com') 0 >>> isAddressValid('dj\\ fhdfh@test.com') 0 >>> isAddressValid('djfhdfh@test.com.de') 8 >>> isAddressValid('djfhdfh@test.co= 127: return 0 c = c + 1 else: return 0 if addr[c] == '@': break if addr[c] != '.': return 0 c = c + 1 continue if addr[c] == '@': break if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0 if addr[c] in rfc822_specials: return 0 c = c + 1 if not c or addr[c - 1] == '.': return 0 # Next we validate the domain portion (name@domain) domain = c = c + 1 if domain >= len(addr): return 0 count = 0 while c < len(addr): if addr[c] == '.': if c == domain or addr[c - 1] == '.': return 0 count = count + 1 if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0 if addr[c] in rfc822_specials: return 0 c = c + 1 ## The final return statement was modified to return the split point ## (position of @) so that the email can split in its two subsections. if count >= 1: return domain def verify_installation(request): config = request.getConfiguration() retval = 1 if not 'contact_urltrigger' in config: print "Missing optional property: 'contact_urltrigger'" print "Using the default of '%s'" % TRIGGER _required = ['contact_smtp_server', 'contact_smtp_to'] for prop in _required: if not prop in config: print "Missing required property: '%s'" % prop retval = 0 return retval class ContactRenderer(Renderer): def render(self, header=1): config = self._request.getConfiguration() data = self._request.getData() http = self._request.getHttp() # path_info and root_datadir are normaly set after the cb_pathinfo callback. # but if a plugin implements cb_handle (as this one does), # cb_pathinfo is never called. # if root_datadir is not set and flavourdir is not set in config.py # an exception is thrown in BlosxomRenderer._getFlavour. # so set root_datadir here explicitly to prevent that. if not 'path_info' in data: if http.get('PATH_INFO', ''): path_info = http['PATH_INFO'].split('/') path_info = [x for x in path_info if x != ''] data['path_info'] = list(path_info) if not 'root_datadir' in data: data['root_datadir'] = config['datadir'] # initialize flavour self.flavour = self._getFlavour(data.get('flavour', 'html')) data['content-type'] = self.flavour['content_type'].strip() if header: self.addHeader("Status", "200 OK") if self._needs_content_type and data['content-type'] != "": self.addHeader('Content-type', '%s; charset=%s' % (data['content-type'], config.get('blog_encoding', 'iso-8859-1'))) self.showHeaders() d = {} d.update(config) d.update(data) if 'head' in self.flavour: self._outputFlavour(d, 'head') if not 'contact' in self.flavour: self.flavour['contact'] = _default_template self._outputFlavour(d, 'contact') if 'foot' in self.flavour: self._outputFlavour(d, 'foot') self.rendered = 1 def _update_config(config): if TRIGGER_KEY in config: global TRIGGER TRIGGER = config[TRIGGER_KEY] else: # set this explicitly so it's available in templates config[TRIGGER_KEY] = TRIGGER def _handle_post(request): form = request.getForm() data = request.getData() http = request.getHttp() config = request.getConfiguration() email = {} error = False error_messages = [] if not 'HTTP_REFERER' in http or \ not http['HTTP_REFERER'].startswith('://'.join(urlparse.urlsplit(config['base_url'])[0:1])): data[MESSAGE_KEY] = "Posting from foreign hosts not allowed.
\nUse the form below to send your message." return for field in _form_fields: if not field in form: error_messages.append("Missing required field '%s'." % field) error = True else: # strip markup parser = tools.Stripper() parser.feed(form[field].value) email[field] = parser.gettext() if 'email' in email.keys() and not isAddressValid(email['email']): error = True error_messages.append("Invalid email address '%s'. Cannot deliver your message!" % email['email']) if error: data[MESSAGE_KEY] = "
\n".join(error_messages) _remember_email(email, data) else: success, data[MESSAGE_KEY] = _send_email(email, config) if success: _forget_email(data) else: _remember_email(email, data) def _remember_email(email, data): """ Stores form fields in the data dict so they can be used to populate the form in the template. """ for key in email: data["contact_%s" % key] = email[key] def _forget_email(data): """ Resets/forgets any stored form field values. """ for key in _form_fields: key = "contact_%s" % key if key in data: del data[key] def _send_email(email, config): try: try: from email.Utils import formatdate except ImportError: from rfc822 import formatdate import smtplib smtp = smtplib.SMTP(config['contact_smtp_server']) email['to'] = config["contact_smtp_to"] email['date'] = formatdate(localtime=True) msg = """\ From: %(name)s <%(email)s> To: %(to)s Date: %(date)s Subject: %(subject)s %(message)s """ % email smtp.sendmail( from_addr=email['email'], to_addrs=config['contact_smtp_to'], msg=msg ) smtp.quit() except: if hasattr(tools, "log_exception"): tools.log_exception() return (False, "Error: Problem sending email.") else: return (True, "Thanks for feeding my mailbox ;-)") #****************************** # Callbacks #****************************** def cb_start(args): request = args['request'] http = request.getHttp() config = request.getConfiguration() _update_config(config) if http['PATH_INFO'].startswith(TRIGGER): data = request.getData() data[INIT_KEY] = True def cb_handle(args): request = args['request'] data = request.getData() if INIT_KEY in data: http = request.getHttp() if http['REQUEST_METHOD'].upper() == "POST": _handle_post(request) else: _forget_email(data) ContactRenderer(request, request.getResponse()).render() return 1 def cb_end(args): request = args['request'] data = request.getData() if INIT_KEY in data: del data[INIT_KEY] if MESSAGE_KEY in data: del data[MESSAGE_KEY]