| 5 | from urlparse import urlparse |
| 6 | |
| 7 | class SafeHTMLParser(HTMLParser): |
| 8 | # from html5lib.sanitiser |
| 9 | acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', |
| 10 | 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button', |
| 11 | 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', |
| 12 | 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn', |
| 13 | 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset', |
| 14 | 'figcaption', 'figure', 'footer', 'font', 'header', 'h1', |
| 15 | 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', |
| 16 | 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter', |
| 17 | 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option', |
| 18 | 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select', |
| 19 | 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong', |
| 20 | 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot', |
| 21 | 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video'] |
| 22 | replaces_pre = [["&", "&"], ["\"", """], ["<", "<"], [">", ">"]] |
| 23 | replaces_post = [["\n", "<br/>"], ["\t", " "], [" ", " "], [" ", " "], ["<br/> ", "<br/> "]] |
| 24 | src_schemes = [ "data" ] |
| 25 | #uriregex1 = re.compile(r'(?i)\b((?:(https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?]))') |
| 26 | uriregex1 = re.compile(r'((https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)') |
| 27 | uriregex2 = re.compile(r'<a href="([^"]+)&') |
| 28 | emailregex = re.compile(r'\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b') |
| 29 | |
| 30 | @staticmethod |
| 31 | def replace_pre(text): |
| 32 | for a in SafeHTMLParser.replaces_pre: |
| 33 | text = text.replace(a[0], a[1]) |
| 34 | return text |
| 35 | |
| 36 | @staticmethod |
| 37 | def replace_post(text): |
| 38 | for a in SafeHTMLParser.replaces_post: |
| 39 | text = text.replace(a[0], a[1]) |
| 40 | if len(text) > 1 and text[0] == " ": |
| 41 | text = " " + text[1:] |
| 42 | return text |
| 43 | |
| 44 | def __init__(self, *args, **kwargs): |
| 45 | HTMLParser.__init__(self, *args, **kwargs) |
| 46 | self.reset_safe() |
| 47 | |
| 48 | def reset_safe(self): |
| 49 | self.elements = set() |
| 50 | self.raw = u"" |
| 51 | self.sanitised = u"" |
| 52 | self.has_html = False |
| 53 | self.allow_picture = False |
| 54 | self.allow_external_src = False |
| 55 | |
| 56 | def add_if_acceptable(self, tag, attrs = None): |
| 57 | if tag not in SafeHTMLParser.acceptable_elements: |
| 58 | return |
| 59 | self.sanitised += "<" |
| 60 | if inspect.stack()[1][3] == "handle_endtag": |
| 61 | self.sanitised += "/" |
| 62 | self.sanitised += tag |
| 63 | if attrs is not None: |
| 64 | for attr, val in attrs: |