Browse files

Merge branch 'master' into simple_httpclient

  • Loading branch information...
2 parents 48d698e + 160c961 commit a80333abbca9a4657d1cf984c728680bf7990d00 @bdarnell committed Oct 8, 2010
Showing with 100 additions and 21 deletions.
  1. +2 −1 demos/chat/templates/message.html
  2. +85 −1 tornado/escape.py
  3. +10 −6 tornado/iostream.py
  4. +2 −0 tornado/stack_context.py
  5. +1 −13 website/templates/index.html
View
3 demos/chat/templates/message.html
@@ -1 +1,2 @@
-<div class="message" id="m{{ message["id"] }}"><b>{{ escape(message["from"]) }}: </b>{{ escape(message["body"]) }}</div>
+{% import tornado.escape %}
+<div class="message" id="m{{ message["id"] }}"><b>{{ escape(message["from"]) }}: </b>{{ tornado.escape.linkify(message["body"]) }}</div>
View
86 tornado/escape.py
@@ -49,7 +49,7 @@ def _json_decode(s):
def xhtml_escape(value):
"""Escapes a string so it is valid within XML or XHTML."""
- return utf8(xml.sax.saxutils.escape(value, {'"': "&quot;"}))
+ return xml.sax.saxutils.escape(value, {'"': "&quot;"})
def xhtml_unescape(value):
@@ -95,6 +95,90 @@ def utf8(value):
return value
+# Regex from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
+# Modified to capture protocol and to avoid HTML character entities other than &amp;
+_URL_RE = re.compile(ur"""(?i)\b((?:([a-z][\w-]+):(?:(/{1,3})|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>&]+|&amp;|\(([^\s()<>&]+|(\([^\s()<>&]+\)))*\))+(?:\(([^\s()<>&]+|(\([^\s()<>&]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\xab\xbb\u201c\u201d\u2018\u2019&]))""")
+
+
+def linkify(text, shorten=False, extra_params="",
+ require_protocol=False, permitted_protocols=["http", "https"]):
+ """Converts plain text into HTML with links.
+
+ For example: linkify("Hello http://tornadoweb.org!") would return
+ Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!
+
+ Parameters:
+ shorten: Long urls will be shortened for display.
+ extra_params: Extra text to include in the link tag,
+ e.g. linkify(text, extra_params='rel="nofollow" class="external"')
+ require_protocol: Only linkify urls which include a protocol. If this is
+ False, urls such as www.facebook.com will also be linkified.
+ permitted_protocols: List (or set) of protocols which should be linkified,
+ e.g. linkify(text, permitted_protocols=["http", "ftp", "mailto"]).
+ It is very unsafe to include protocols such as "javascript".
+ """
+ if extra_params:
+ extra_params = " " + extra_params.strip()
+
+ def make_link(m):
+ url = m.group(1)
+ proto = m.group(2)
+ if require_protocol and not proto:
+ return url # not protocol, no linkify
+
+ if proto and proto not in permitted_protocols:
+ return url # bad protocol, no linkify
+
+ href = m.group(1)
+ if not proto:
+ href = "http://" + href # no proto specified, use http
+
+ params = extra_params
+
+ # clip long urls. max_len is just an approximation
+ max_len = 30
+ if shorten and len(url) > max_len:
+ before_clip = url
+ if proto:
+ proto_len = len(proto) + 1 + len(m.group(3) or "") # +1 for :
+ else:
+ proto_len = 0
+
+ parts = url[proto_len:].split("/")
+ if len(parts) > 1:
+ # Grab the whole host part plus the first bit of the path
+ # The path is usually not that interesting once shortened
+ # (no more slug, etc), so it really just provides a little
+ # extra indication of shortening.
+ url = url[:proto_len] + parts[0] + "/" + \
+ parts[1][:8].split('?')[0].split('.')[0]
+
+ if len(url) > max_len * 1.5: # still too long
+ url = url[:max_len]
+
+ if url != before_clip:
+ amp = url.rfind('&')
+ # avoid splitting html char entities
+ if amp > max_len - 5:
+ url = url[:amp]
+ url += "..."
+
+ if len(url) >= len(before_clip):
+ url = before_clip
+ else:
+ # full url is visible on mouse-over (for those who don't
+ # have a status bar, such as Safari by default)
+ params += ' title="%s"' % href
+
+ return u'<a href="%s"%s>%s</a>' % (href, params, url)
+
+ # First HTML-escape so that our strings are all safe.
+ # The regex is modified to avoid character entites other than &amp; so
+ # that we won't pick up &quot;, etc.
+ text = _unicode(xhtml_escape(text))
+ return _URL_RE.sub(make_link, text)
+
+
def _unicode(value):
if isinstance(value, str):
return value.decode("utf-8")
View
16 tornado/iostream.py
@@ -21,6 +21,7 @@
import socket
from tornado import ioloop
+from tornado import stack_context
try:
import ssl # Python 2.6+
@@ -79,14 +80,15 @@ def __init__(self, socket, io_loop=None, max_buffer_size=104857600,
self._write_callback = None
self._close_callback = None
self._state = self.io_loop.ERROR
- self.io_loop.add_handler(
- self.socket.fileno(), self._handle_events, self._state)
+ with stack_context.NullContext():
+ self.io_loop.add_handler(
+ self.socket.fileno(), self._handle_events, self._state)
def read_until(self, delimiter, callback):
"""Call callback when we read the given delimiter."""
assert not self._read_callback, "Already reading"
self._read_delimiter = delimiter
- self._read_callback = callback
+ self._read_callback = stack_context.wrap(callback)
while True:
# See if we've already got the data from a previous read
if self._read_from_buffer():
@@ -103,7 +105,7 @@ def read_bytes(self, num_bytes, callback):
callback("")
return
self._read_bytes = num_bytes
- self._read_callback = callback
+ self._read_callback = stack_context.wrap(callback)
while True:
if self._read_from_buffer():
return
@@ -123,11 +125,11 @@ def write(self, data, callback=None):
self._check_closed()
self._write_buffer += data
self._add_io_state(self.io_loop.WRITE)
- self._write_callback = callback
+ self._write_callback = stack_context.wrap(callback)
def set_close_callback(self, callback):
"""Call the given callback when the stream is closed."""
- self._close_callback = callback
+ self._close_callback = stack_context.wrap(callback)
def close(self):
"""Close this stream."""
@@ -177,6 +179,8 @@ def _run_callback(self, callback, *args, **kwargs):
try:
callback(*args, **kwargs)
except:
+ logging.error("Uncaught exception, closing connection.",
+ exc_info=True)
# Close the socket on an uncaught exception from a user callback
# (It would eventually get closed when the socket object is
# gc'd, but we don't want to rely on gc happening before we
View
2 tornado/stack_context.py
@@ -101,6 +101,8 @@ def wrap(fn):
different execution context (either in a different thread or
asynchronously in the same thread).
'''
+ if fn is None:
+ return None
# functools.wraps doesn't appear to work on functools.partial objects
#@functools.wraps(fn)
def wrapped(callback, contexts, *args, **kwargs):
View
14 website/templates/index.html
@@ -45,18 +45,6 @@
<p>See the <a href="/documentation">Tornado documentation</a> for a detailed walkthrough of the framework.</p>
<h2>Discussion and support</h2>
- <p>You can discuss Tornado and report bugs on <a href="http://groups.google.com/group/python-tornado">the Tornado developer mailing list</a>.
-
- <h2>Links and resources</h2>
- <ul>
- <li><a href="http://tornado.poweredsites.org">tornado.poweredsites.org</a> - listing of sites using Tornado</li>
- <li><a href="http://github.com/szabotshark/t3">T3</a> - Tornado ported to Python 3</li>
- <li><a href="http://github.com/fiorix/cyclone">Cyclone</a> - Tornado ported to the Twisted event loop</li>
- <li><a href="http://github.com/bdarnell/tornado_tracing">Tornado Tracing</a> - Performance tracing library for Tornado</li>
- </ul>
-
- <h2>Updates</h2>
- <p>Follow us on <a href="http://www.facebook.com/pages/Tornado-Web-Server/144144048921">Facebook</a>, <a href="http://twitter.com/tornadoweb">Twitter</a>, or <a href="http://friendfeed.com/tornado-web">FriendFeed</a> to get updates and announcements:</p>
- <div style="margin-top:1em"><a href="http://www.facebook.com/pages/Tornado-Web-Server/144144048921" style="margin-right:10px"><img src="/static/facebook.png" style="width:64px;height:64px" alt="Facebook"/></a><a href="http://twitter.com/tornadoweb" style="margin-right:10px"><img src="/static/twitter.png" style="width:64px;height:64px" alt="Twitter"/></a><a href="http://friendfeed.com/tornado-web" style="margin-right:10px"><img src="/static/friendfeed.png" style="width:64px;height:64px" alt="Facebook"/></a></div>
+ <p>You can discuss Tornado and report bugs on <a href="http://groups.google.com/group/python-tornado">the Tornado developer mailing list</a>. Links to additional resources can be found on the <a href="http://github.com/facebook/tornado/wiki/Links">Tornado wiki</a>.
{% end %}

0 comments on commit a80333a

Please sign in to comment.