bugdown: Add option to support "file:///" as hyperlink.

This contains contributions from Tim Abbott and Igor Tokarev.

Fixes #380.
This commit is contained in:
Kevin Chen
2016-03-10 16:17:40 +00:00
committed by Tim Abbott
parent 50382f153a
commit 6107c877e8
6 changed files with 33 additions and 4 deletions

View File

@@ -719,7 +719,7 @@ def sanitize_url(url):
if not scheme: if not scheme:
return sanitize_url('http://' + url) return sanitize_url('http://' + url)
locless_schemes = ['mailto', 'news'] locless_schemes = ['mailto', 'news', 'file']
if netloc == '' and scheme not in locless_schemes: if netloc == '' and scheme not in locless_schemes:
# This fails regardless of anything else. # This fails regardless of anything else.
# Return immediately to save additional proccessing # Return immediately to save additional proccessing
@@ -729,7 +729,7 @@ def sanitize_url(url):
# appears to have a netloc. Additionally there are plenty of other # appears to have a netloc. Additionally there are plenty of other
# schemes that do weird things like launch external programs. To be # schemes that do weird things like launch external programs. To be
# on the safe side, we whitelist the scheme. # on the safe side, we whitelist the scheme.
if scheme not in ('http', 'https', 'ftp', 'mailto'): if scheme not in ('http', 'https', 'ftp', 'mailto', 'file'):
return None return None
# Upstream code scans path, parameters, and query for colon characters # Upstream code scans path, parameters, and query for colon characters
@@ -1079,12 +1079,14 @@ class Bugdown(markdown.Extension):
%s # zero-to-6 sets of paired parens %s # zero-to-6 sets of paired parens
)?) # Path is optional )?) # Path is optional
| (?:[\w.-]+\@[\w.-]+\.[\w]+) # Email is separate, since it can't have a path | (?:[\w.-]+\@[\w.-]+\.[\w]+) # Email is separate, since it can't have a path
%s # File path start with file:///, enable by setting ENABLE_FILE_LINKS=True
) )
(?= # URL must be followed by (not included in group) (?= # URL must be followed by (not included in group)
[!:;\?\),\.\'\"\>]* # Optional punctuation characters [!:;\?\),\.\'\"\>]* # Optional punctuation characters
(?:\Z|\s) # followed by whitespace or end of string (?:\Z|\s) # followed by whitespace or end of string
) )
""" % (tlds, nested_paren_chunk) """ % (tlds, nested_paren_chunk,
r"| (?:file://(/[^/ ]*)+/?)" if settings.ENABLE_FILE_LINKS else r"")
md.inlinePatterns.add('autolink', AutoLink(link_regex), '>link') md.inlinePatterns.add('autolink', AutoLink(link_regex), '>link')
md.preprocessors.add('hanging_ulists', md.preprocessors.add('hanging_ulists',

View File

@@ -925,7 +925,10 @@ class Message(ModelReprMixin, models.Model):
@staticmethod @staticmethod
def content_has_link(content): def content_has_link(content):
# type: (text_type) -> bool # type: (text_type) -> bool
return 'http://' in content or 'https://' in content or '/user_uploads' in content return ('http://' in content or
'https://' in content or
'/user_uploads' in content or
(settings.ENABLE_FILE_LINKS and 'file:///' in content))
@staticmethod @staticmethod
def is_status_message(content, rendered_content): def is_status_message(content, rendered_content):

View File

@@ -211,6 +211,22 @@ class BugdownTest(TestCase):
converted = bugdown_convert(inline_url) converted = bugdown_convert(inline_url)
self.assertEqual(match, converted) self.assertEqual(match, converted)
def test_inline_file(self):
# type: () -> None
msg = 'Check out this file file:///Volumes/myserver/Users/Shared/pi.py'
converted = bugdown_convert(msg)
self.assertEqual(converted, '<p>Check out this file <a href="file:///Volumes/myserver/Users/Shared/pi.py" target="_blank" title="file:///Volumes/myserver/Users/Shared/pi.py">file:///Volumes/myserver/Users/Shared/pi.py</a></p>')
with self.settings(ENABLE_FILE_LINKS=False):
realm = Realm.objects.create(
domain='file_links_test.example.com',
string_id='file_links_test')
bugdown.make_md_engine(
realm.domain,
{'realm_filters': [[], u'file_links_test.example.com'], 'realm': [u'file_links_test.example.com', 'Realm name']})
converted = bugdown.convert(msg, realm_domain=realm.domain)
self.assertEqual(converted, '<p>Check out this file file:///Volumes/myserver/Users/Shared/pi.py</p>')
def test_inline_youtube(self): def test_inline_youtube(self):
# type: () -> None # type: () -> None
msg = 'Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE' msg = 'Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE'

View File

@@ -140,6 +140,10 @@ ERROR_REPORTING = True
# a link to an image is referenced in a message. # a link to an image is referenced in a message.
INLINE_IMAGE_PREVIEW = True INLINE_IMAGE_PREVIEW = True
# Controls whether or not Zulip will parse links starting with
# "file:///" as a hyperlink (useful if you have e.g. an NFS share).
ENABLE_FILE_LINKS = False
# By default, files uploaded by users and user avatars are stored # By default, files uploaded by users and user avatars are stored
# directly on the Zulip server. If file storage in Amazon S3 is # directly on the Zulip server. If file storage in Amazon S3 is
# desired, you can configure that as follows: # desired, you can configure that as follows:

View File

@@ -179,6 +179,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
'FIRST_TIME_TOS_TEMPLATE': None, 'FIRST_TIME_TOS_TEMPLATE': None,
'USING_PGROONGA': False, 'USING_PGROONGA': False,
'POST_MIGRATION_CACHE_FLUSHING': False, 'POST_MIGRATION_CACHE_FLUSHING': False,
'ENABLE_FILE_LINKS': False,
} }
for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS): for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS):

View File

@@ -91,6 +91,9 @@ CACHES['database'] = {
} }
} }
# Enable file:/// hyperlink support by default in tests
ENABLE_FILE_LINKS = True
LOGGING['loggers']['zulip.requests']['level'] = 'CRITICAL' LOGGING['loggers']['zulip.requests']['level'] = 'CRITICAL'
LOGGING['loggers']['zulip.management']['level'] = 'CRITICAL' LOGGING['loggers']['zulip.management']['level'] = 'CRITICAL'