| 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") |