import os import urllib import time import re from cPickle import load, dump from webob import Request, Response, html_escape from webob import exc class Commenter(object): def __init__(self, app, storage_dir): self.app = app self.storage_dir = storage_dir if not os.path.exists(storage_dir): os.makedirs(storage_dir) def __call__(self, environ, start_response): req = Request(environ) if req.path_info_peek() == '.comments': return self.process_comment(req)(environ, start_response) # This is the base path of *this* middleware: base_url = req.application_url resp = req.get_response(self.app) if resp.content_type != 'text/html' or resp.status_code != 200: # Not an HTML response, we don't want to # do anything to it return resp(environ, start_response) # Make sure the content isn't gzipped: resp.decode_content() comments = self.get_data(req.url) body = resp.body body = self.add_to_end(body, self.format_comments(comments)) body = self.add_to_end(body, self.submit_form(base_url, req)) resp.body = body return resp(environ, start_response) def get_data(self, url): # Double-quoting makes the filename safe filename = self.url_filename(url) if not os.path.exists(filename): return [] else: f = open(filename, 'rb') data = load(f) f.close() return data def save_data(self, url, data): filename = self.url_filename(url) f = open(filename, 'wb') dump(data, f) f.close() def url_filename(self, url): return os.path.join(self.storage_dir, urllib.quote(url, '')) _end_body_re = re.compile(r'</body.*?>', re.I|re.S) def add_to_end(self, html, extra_html): """ Adds extra_html to the end of the html page (before </body>) """ match = self._end_body_re.search(html) if not match: return html + extra_html else: return html[:match.start()] + extra_html + html[match.start():] def format_comments(self, comments): if not comments: return '' text = [] text.append('<hr>') text.append('<h2><a name="comment-area"></a>Comments (%s):</h2>' % len(comments)) for comment in comments: text.append('<h3><a href="%s">%s</a> at %s:</h3>' % ( html_escape(comment['homepage']), html_escape(comment['name']), time.strftime('%c', comment['time']))) # Susceptible to XSS attacks!: text.append(comment['comments']) return ''.join(text) def submit_form(self, base_path, req): return '''<h2>Leave a comment:</h2> <form action="%s/.comments" method="POST"> <input type="hidden" name="url" value="%s"> <table width="100%%"> <tr><td>Name:</td> <td><input type="text" name="name" style="width: 100%%"></td></tr> <tr><td>URL:</td> <td><input type="text" name="homepage" style="width: 100%%"></td></tr> </table> Comments:<br> <textarea name="comments" rows=10 style="width: 100%%"></textarea><br> <input type="submit" value="Submit comment"> </form> ''' % (base_path, html_escape(req.url)) def process_comment(self, req): try: url = req.params['url'] name = req.params['name'] homepage = req.params['homepage'] comments = req.params['comments'] except KeyError, e: resp = exc.HTTPBadRequest('Missing parameter: %s' % e) return resp data = self.get_data(url) data.append(dict( name=name, homepage=homepage, comments=comments, time=time.gmtime())) self.save_data(url, data) resp = exc.HTTPSeeOther(location=url+'#comment-area') return resp if __name__ == '__main__': import optparse parser = optparse.OptionParser( usage='%prog --port=PORT BASE_DIRECTORY' ) parser.add_option( '-p', '--port', default='8080', dest='port', type='int', help='Port to serve on (default 8080)') parser.add_option( '--comment-data', default='./comments', dest='comment_data', help='Place to put comment data into (default ./comments/)') options, args = parser.parse_args() if not args: parser.error('You must give a BASE_DIRECTORY') base_dir = args[0] from paste.urlparser import StaticURLParser app = StaticURLParser(base_dir) app = Commenter(app, options.comment_data) from wsgiref.simple_server import make_server httpd = make_server('localhost', options.port, app) print 'Serving on http://localhost:%s' % options.port try: httpd.serve_forever() except KeyboardInterrupt: print '^C'