From 6107c877e832d6067db269932aa00cbba46e0563 Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Thu, 10 Mar 2016 16:17:40 +0000 Subject: [PATCH] bugdown: Add option to support "file:///" as hyperlink. This contains contributions from Tim Abbott and Igor Tokarev. Fixes #380. --- zerver/lib/bugdown/__init__.py | 8 +++++--- zerver/models.py | 5 ++++- zerver/tests/test_bugdown.py | 16 ++++++++++++++++ zproject/prod_settings_template.py | 4 ++++ zproject/settings.py | 1 + zproject/test_settings.py | 3 +++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py index 20177804c1..bd67109fe6 100644 --- a/zerver/lib/bugdown/__init__.py +++ b/zerver/lib/bugdown/__init__.py @@ -719,7 +719,7 @@ def sanitize_url(url): if not scheme: return sanitize_url('http://' + url) - locless_schemes = ['mailto', 'news'] + locless_schemes = ['mailto', 'news', 'file'] if netloc == '' and scheme not in locless_schemes: # This fails regardless of anything else. # 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 # schemes that do weird things like launch external programs. To be # 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 # 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 )?) # Path is optional | (?:[\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) [!:;\?\),\.\'\"\>]* # Optional punctuation characters (?:\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.preprocessors.add('hanging_ulists', diff --git a/zerver/models.py b/zerver/models.py index 3c6e239da0..5063201dde 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -925,7 +925,10 @@ class Message(ModelReprMixin, models.Model): @staticmethod def content_has_link(content): # 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 def is_status_message(content, rendered_content): diff --git a/zerver/tests/test_bugdown.py b/zerver/tests/test_bugdown.py index 1eae72f72c..be9685fdb7 100644 --- a/zerver/tests/test_bugdown.py +++ b/zerver/tests/test_bugdown.py @@ -211,6 +211,22 @@ class BugdownTest(TestCase): converted = bugdown_convert(inline_url) 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, '

Check out this file file:///Volumes/myserver/Users/Shared/pi.py

') + + 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, '

Check out this file file:///Volumes/myserver/Users/Shared/pi.py

') + def test_inline_youtube(self): # type: () -> None msg = 'Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE' diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py index d0eef7feaf..ef921da38d 100644 --- a/zproject/prod_settings_template.py +++ b/zproject/prod_settings_template.py @@ -140,6 +140,10 @@ ERROR_REPORTING = True # a link to an image is referenced in a message. 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 # directly on the Zulip server. If file storage in Amazon S3 is # desired, you can configure that as follows: diff --git a/zproject/settings.py b/zproject/settings.py index 459a9d2b8f..3435cc4a64 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -179,6 +179,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '', 'FIRST_TIME_TOS_TEMPLATE': None, 'USING_PGROONGA': False, 'POST_MIGRATION_CACHE_FLUSHING': False, + 'ENABLE_FILE_LINKS': False, } for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS): diff --git a/zproject/test_settings.py b/zproject/test_settings.py index d717fa869e..c8e60faf79 100644 --- a/zproject/test_settings.py +++ b/zproject/test_settings.py @@ -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.management']['level'] = 'CRITICAL'