1# Copyright 2014 The Distro Tracker Developers 

2# See the COPYRIGHT file at the top-level directory of this distribution and 

3# at https://deb.li/DTAuthors 

4# 

5# This file is part of Distro Tracker. It is subject to the license terms 

6# in the LICENSE file found in the top-level directory of this 

7# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

8# including this file, may be copied, modified, propagated, or distributed 

9# except according to the terms contained in the LICENSE file. 

10 

11""" 

12Module including some utility functions to inject links in plain text. 

13""" 

14import re 

15 

16from django.conf import settings 

17 

18from distro_tracker.core.utils.plugins import PluginRegistry 

19 

20 

21class Linkify(metaclass=PluginRegistry): 

22 """ 

23 A base class representing ways to inject useful links in plain text data 

24 

25 If you want to recognize a new syntax where links could provide value to 

26 a view of the content, just create a subclass and implement the linkify 

27 method. 

28 """ 

29 

30 @staticmethod 

31 def linkify(text): 

32 """ 

33 :param text: the text where we should inject HTML links 

34 :type param: str 

35 :returns: the text formatted with HTML links 

36 :rtype: str 

37 """ 

38 return text 

39 

40 

41class LinkifyHttpLinks(Linkify): 

42 """ 

43 Detect http:// and https:// URLs and transform them in true HTML 

44 links. 

45 """ 

46 

47 @staticmethod 

48 def linkify(text): 

49 return re.sub(r'(?:^|(?<=\s))(https?://[^\s]*)', 

50 r'<a href="\1">\1</a>', 

51 text) 

52 

53 

54class LinkifyDebianBugLinks(Linkify): 

55 """ 

56 Detect "Closes: #123, 234" syntax used in Debian changelogs to close 

57 bugs and inject HTML links to the corresponding bug tracker entry. 

58 Also handles the "Closes: 123 456" fields of .changes files. 

59 """ 

60 

61 close_prefix = 'Closes:' 

62 close_field = 'Closes:' 

63 bug_url = 'https://bugs.debian.org/' 

64 

65 @classmethod 

66 def _linkify_field(cls, text): 

67 if not cls.close_field: 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true

68 return text 

69 split_text = re.split( 

70 '(^' + cls.close_field + r'(?: \d+)+\s*$)', 

71 text, flags=re.IGNORECASE | re.MULTILINE) 

72 generated_link = '' 

73 for i, txt in enumerate(split_text): 

74 if i % 2: 

75 new_txt = re.sub( 

76 r'(\d+)', r'<a href="{}\1">\1</a>'.format(cls.bug_url), 

77 txt, flags=re.IGNORECASE) 

78 generated_link += new_txt 

79 else: 

80 generated_link += txt 

81 return generated_link 

82 

83 @classmethod 

84 def _linkify_changelog_entry(cls, text): 

85 split_text = re.split( 

86 '(' + cls.close_prefix + 

87 r'\s*(?:bug)?(?:#)?\d+(?:\s*,\s*(?:bug)?(?:#)?\d+)*)', 

88 text, flags=re.IGNORECASE) 

89 generated_link = '' 

90 for i, txt in enumerate(split_text): 

91 if i % 2: 

92 new_txt = re.sub( 

93 r'((?:#)?(\d+))', 

94 r'<a href="{}\2">\1</a>'.format(cls.bug_url), 

95 txt, flags=re.IGNORECASE) 

96 generated_link += new_txt 

97 else: 

98 generated_link += txt 

99 return generated_link 

100 

101 @classmethod 

102 def linkify(cls, text): 

103 return cls._linkify_changelog_entry(cls._linkify_field(text)) 

104 

105 

106class LinkifyUbuntuBugLinks(LinkifyDebianBugLinks): 

107 """ 

108 Detect "LP: #123, 234" syntax used in Ubuntu changelogs to close 

109 bugs and inject HTML links to the corresponding bug tracker entry. 

110 """ 

111 

112 close_prefix = 'LP:' 

113 close_field = 'Launchpad-Bugs-Fixed:' 

114 bug_url = 'https://bugs.launchpad.net/bugs/' 

115 

116 

117class LinkifyCVELinks(Linkify): 

118 """ 

119 Detect "CVE-2014-1234" words and transform them into links to the 

120 CVE tracker at cve.mitre.org. The exact URL can be overridden with a 

121 ``DISTRO_TRACKER_CVE_URL`` configuration setting to redirect 

122 the URL to a custom tracker. 

123 """ 

124 

125 @staticmethod 

126 def linkify(text): 

127 address = getattr(settings, 'DISTRO_TRACKER_CVE_URL', 

128 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=') 

129 return re.sub(r'(?:(?<=\s|\()|\A)((CVE)-(\d{4})-(\d{4,}))', 

130 r'<a href="{}\1">\1</a>'.format(address), 

131 text, flags=re.IGNORECASE) 

132 

133 

134def linkify(message): 

135 """ 

136 :param message: the message where we should inject HTML links 

137 :type param: str 

138 :returns: the message formatted with HTML links 

139 :rtype: str 

140 """ 

141 for plugin in Linkify.plugins: 

142 message = plugin.linkify(message) 

143 return message