Edgewall Software

source: tags/0.3.3/genshi/filters.py

Last change on this file was 299, checked in by cmlenz, 17 years ago

Fixed EOL style.

  • Property svn:eol-style set to native
File size: 7.3 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2006 Edgewall Software
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at http://genshi.edgewall.org/wiki/License.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at http://genshi.edgewall.org/log/.
13
14"""Implementation of a number of stream filters."""
15
16try:
17    frozenset
18except NameError:
19    from sets import ImmutableSet as frozenset
20import re
21
22from genshi.core import Attrs, Namespace, stripentities
23from genshi.core import END, END_NS, START, START_NS
24
25__all__ = ['HTMLSanitizer', 'IncludeFilter']
26
27
28class HTMLSanitizer(object):
29    """A filter that removes potentially dangerous HTML tags and attributes
30    from the stream.
31    """
32
33    _SAFE_TAGS = frozenset(['a', 'abbr', 'acronym', 'address', 'area', 'b',
34        'big', 'blockquote', 'br', 'button', 'caption', 'center', 'cite',
35        'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt',
36        'em', 'fieldset', 'font', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
37        'hr', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
38        'menu', 'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
39        'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
40        'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u',
41        'ul', 'var'])
42
43    _SAFE_ATTRS = frozenset(['abbr', 'accept', 'accept-charset', 'accesskey',
44        'action', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellpadding',
45        'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'class',
46        'clear', 'cols', 'colspan', 'color', 'compact', 'coords', 'datetime',
47        'dir', 'disabled', 'enctype', 'for', 'frame', 'headers', 'height',
48        'href', 'hreflang', 'hspace', 'id', 'ismap', 'label', 'lang',
49        'longdesc', 'maxlength', 'media', 'method', 'multiple', 'name',
50        'nohref', 'noshade', 'nowrap', 'prompt', 'readonly', 'rel', 'rev',
51        'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size',
52        'span', 'src', 'start', 'style', 'summary', 'tabindex', 'target',
53        'title', 'type', 'usemap', 'valign', 'value', 'vspace', 'width'])
54    _URI_ATTRS = frozenset(['action', 'background', 'dynsrc', 'href', 'lowsrc',
55        'src'])
56    _SAFE_SCHEMES = frozenset(['file', 'ftp', 'http', 'https', 'mailto', None])
57
58    def __call__(self, stream, ctxt=None):
59        waiting_for = None
60
61        for kind, data, pos in stream:
62            if kind is START:
63                if waiting_for:
64                    continue
65                tag, attrib = data
66                if tag not in self._SAFE_TAGS:
67                    waiting_for = tag
68                    continue
69
70                new_attrib = Attrs()
71                for attr, value in attrib:
72                    value = stripentities(value)
73                    if attr not in self._SAFE_ATTRS:
74                        continue
75                    elif attr in self._URI_ATTRS:
76                        # Don't allow URI schemes such as "javascript:"
77                        if self._get_scheme(value) not in self._SAFE_SCHEMES:
78                            continue
79                    elif attr == 'style':
80                        # Remove dangerous CSS declarations from inline styles
81                        decls = []
82                        for decl in filter(None, value.split(';')):
83                            is_evil = False
84                            if 'expression' in decl:
85                                is_evil = True
86                            for m in re.finditer(r'url\s*\(([^)]+)', decl):
87                                if self._get_scheme(m.group(1)) not in self._SAFE_SCHEMES:
88                                    is_evil = True
89                                    break
90                            if not is_evil:
91                                decls.append(decl.strip())
92                        if not decls:
93                            continue
94                        value = '; '.join(decls)
95                    new_attrib.append((attr, value))
96
97                yield kind, (tag, new_attrib), pos
98
99            elif kind is END:
100                tag = data
101                if waiting_for:
102                    if waiting_for == tag:
103                        waiting_for = None
104                else:
105                    yield kind, data, pos
106
107            else:
108                if not waiting_for:
109                    yield kind, data, pos
110
111    def _get_scheme(self, text):
112        if ':' not in text:
113            return None
114        chars = [char for char in text.split(':', 1)[0] if char.isalnum()]
115        return ''.join(chars).lower()
116
117
118class IncludeFilter(object):
119    """Template filter providing (very) basic XInclude support
120    (see http://www.w3.org/TR/xinclude/) in templates.
121    """
122
123    NAMESPACE = Namespace('http://www.w3.org/2001/XInclude')
124
125    def __init__(self, loader):
126        """Initialize the filter.
127       
128        @param loader: the `TemplateLoader` to use for resolving references to
129            external template files
130        """
131        self.loader = loader
132
133    def __call__(self, stream, ctxt=None):
134        """Filter the stream, processing any XInclude directives it may
135        contain.
136       
137        @param stream: the markup event stream to filter
138        @param ctxt: the template context
139        """
140        from genshi.template import TemplateError, TemplateNotFound
141
142        ns_prefixes = []
143        in_fallback = False
144        include_href, fallback_stream = None, None
145        namespace = self.NAMESPACE
146
147        for kind, data, pos in stream:
148
149            if kind is START and not in_fallback and data[0] in namespace:
150                tag, attrib = data
151                if tag.localname == 'include':
152                    include_href = attrib.get('href')
153                elif tag.localname == 'fallback':
154                    in_fallback = True
155                    fallback_stream = []
156
157            elif kind is END and data in namespace:
158                if data.localname == 'include':
159                    try:
160                        if not include_href:
161                            raise TemplateError('Include misses required '
162                                                'attribute "href"')
163                        template = self.loader.load(include_href,
164                                                    relative_to=pos[0])
165                        for event in template.generate(ctxt):
166                            yield event
167
168                    except TemplateNotFound:
169                        if fallback_stream is None:
170                            raise
171                        for event in fallback_stream:
172                            yield event
173
174                    include_href = None
175                    fallback_stream = None
176
177                elif data.localname == 'fallback':
178                    in_fallback = False
179
180            elif in_fallback:
181                fallback_stream.append((kind, data, pos))
182
183            elif kind is START_NS and data[1] == namespace:
184                ns_prefixes.append(data[0])
185
186            elif kind is END_NS and data in ns_prefixes:
187                ns_prefixes.pop()
188
189            else:
190                yield kind, data, pos
Note: See TracBrowser for help on using the repository browser.