antisamy 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +13 -0
- data/LICENSE.txt +20 -20
- data/README.rdoc +41 -41
- data/lib/antisamy.rb +46 -46
- data/lib/antisamy/css/css_filter.rb +187 -187
- data/lib/antisamy/css/css_scanner.rb +84 -84
- data/lib/antisamy/css/css_validator.rb +128 -128
- data/lib/antisamy/csspool/rsac.rb +1 -1
- data/lib/antisamy/csspool/rsac/sac.rb +14 -14
- data/lib/antisamy/csspool/rsac/sac/conditions.rb +5 -5
- data/lib/antisamy/csspool/rsac/sac/conditions/attribute_condition.rb +50 -50
- data/lib/antisamy/csspool/rsac/sac/conditions/begin_hyphen_condition.rb +18 -18
- data/lib/antisamy/csspool/rsac/sac/conditions/class_condition.rb +18 -18
- data/lib/antisamy/csspool/rsac/sac/conditions/combinator_condition.rb +36 -36
- data/lib/antisamy/csspool/rsac/sac/conditions/condition.rb +29 -29
- data/lib/antisamy/csspool/rsac/sac/conditions/id_condition.rb +23 -23
- data/lib/antisamy/csspool/rsac/sac/conditions/one_of_condition.rb +18 -18
- data/lib/antisamy/csspool/rsac/sac/conditions/pseudo_class_condition.rb +20 -20
- data/lib/antisamy/csspool/rsac/sac/document_handler.rb +66 -66
- data/lib/antisamy/csspool/rsac/sac/error_handler.rb +13 -13
- data/lib/antisamy/csspool/rsac/sac/generated_parser.rb +1012 -1012
- data/lib/antisamy/csspool/rsac/sac/generated_property_parser.rb +9284 -9284
- data/lib/antisamy/csspool/rsac/sac/lexeme.rb +27 -27
- data/lib/antisamy/csspool/rsac/sac/lexical_unit.rb +201 -201
- data/lib/antisamy/csspool/rsac/sac/parse_exception.rb +4 -4
- data/lib/antisamy/csspool/rsac/sac/parser.rb +109 -109
- data/lib/antisamy/csspool/rsac/sac/property_parser.rb +44 -44
- data/lib/antisamy/csspool/rsac/sac/selectors.rb +5 -5
- data/lib/antisamy/csspool/rsac/sac/selectors/child_selector.rb +36 -36
- data/lib/antisamy/csspool/rsac/sac/selectors/conditional_selector.rb +45 -45
- data/lib/antisamy/csspool/rsac/sac/selectors/descendant_selector.rb +36 -36
- data/lib/antisamy/csspool/rsac/sac/selectors/element_selector.rb +35 -35
- data/lib/antisamy/csspool/rsac/sac/selectors/selector.rb +25 -25
- data/lib/antisamy/csspool/rsac/sac/selectors/sibling_selector.rb +35 -35
- data/lib/antisamy/csspool/rsac/sac/selectors/simple_selector.rb +21 -21
- data/lib/antisamy/csspool/rsac/sac/token.rb +25 -25
- data/lib/antisamy/csspool/rsac/sac/tokenizer.rb +185 -185
- data/lib/antisamy/csspool/rsac/stylesheet.rb +3 -3
- data/lib/antisamy/csspool/rsac/stylesheet/rule.rb +20 -20
- data/lib/antisamy/csspool/rsac/stylesheet/stylesheet.rb +76 -76
- data/lib/antisamy/html/handler.rb +112 -99
- data/lib/antisamy/html/sax_filter.rb +305 -302
- data/lib/antisamy/html/scanner.rb +47 -43
- data/lib/antisamy/model/attribute.rb +19 -19
- data/lib/antisamy/model/css_property.rb +39 -39
- data/lib/antisamy/model/tag.rb +31 -31
- data/lib/antisamy/policy.rb +577 -545
- data/lib/antisamy/scan_results.rb +89 -89
- data/spec/antisamy_spec.rb +208 -142
- data/spec/spec_helper.rb +12 -12
- metadata +79 -81
@@ -1,43 +1,47 @@
|
|
1
|
-
module AntiSamy
|
2
|
-
class Scanner
|
3
|
-
attr_accessor :policy, :errors, :nofollow, :pae
|
4
|
-
DEFAULT_ENCODE = "UTF-8"
|
5
|
-
ALLOW_EMPTY = %w[br hr a img link iframe script object applet frame base param meta input textarea embed basefont col]
|
6
|
-
# Setup a basic param tag rule
|
7
|
-
begin
|
8
|
-
name_attr = Attribute.new("name")
|
9
|
-
value_attr = Attribute.new("value")
|
10
|
-
name_attr.expressions << /.*/
|
11
|
-
value_attr.expressions << /.*/
|
12
|
-
@@basic_param_tag_rule = Tag.new("param")
|
13
|
-
@@basic_param_tag_rule << name_attr
|
14
|
-
@@basic_param_tag_rule << value_attr
|
15
|
-
@@basic_param_tag_rule.action = Policy::ACTION_VALIDATE
|
16
|
-
end
|
17
|
-
|
18
|
-
# Create a scanner with a given policy
|
19
|
-
def initialize(policy)
|
20
|
-
@policy = policy
|
21
|
-
@errors = []
|
22
|
-
end
|
23
|
-
|
24
|
-
# Scan the input using the provided input and output encoding
|
25
|
-
# will raise an error if nil input or the maximum input size is exceeded
|
26
|
-
def scan(input, input_encode, output_encoder)
|
27
|
-
raise ArgumentError if input.nil?
|
28
|
-
raise ScanError, "Max input Exceeded #{input.size} > #{@policy.max_input}" if input.size > @policy.max_input
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
1
|
+
module AntiSamy
|
2
|
+
class Scanner
|
3
|
+
attr_accessor :policy, :errors, :nofollow, :pae
|
4
|
+
DEFAULT_ENCODE = "UTF-8"
|
5
|
+
ALLOW_EMPTY = %w[br hr a img link iframe script object applet frame base param meta input textarea embed basefont col]
|
6
|
+
# Setup a basic param tag rule
|
7
|
+
begin
|
8
|
+
name_attr = Attribute.new("name")
|
9
|
+
value_attr = Attribute.new("value")
|
10
|
+
name_attr.expressions << /.*/
|
11
|
+
value_attr.expressions << /.*/
|
12
|
+
@@basic_param_tag_rule = Tag.new("param")
|
13
|
+
@@basic_param_tag_rule << name_attr
|
14
|
+
@@basic_param_tag_rule << value_attr
|
15
|
+
@@basic_param_tag_rule.action = Policy::ACTION_VALIDATE
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a scanner with a given policy
|
19
|
+
def initialize(policy)
|
20
|
+
@policy = policy
|
21
|
+
@errors = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Scan the input using the provided input and output encoding
|
25
|
+
# will raise an error if nil input or the maximum input size is exceeded
|
26
|
+
def scan(input, input_encode, output_encoder)
|
27
|
+
raise ArgumentError if input.nil?
|
28
|
+
raise ScanError, "Max input Exceeded #{input.size} > #{@policy.max_input}" if input.size > @policy.max_input
|
29
|
+
fragment = true
|
30
|
+
if input =~ /\<\s?html\s?.*?\>|DOCTYPE/im
|
31
|
+
fragment = false
|
32
|
+
end
|
33
|
+
# check poilcy stuff
|
34
|
+
handler = Handler.new(@policy,output_encoder,fragment)
|
35
|
+
scanner = SaxFilter.new(@policy,handler,@@basic_param_tag_rule,fragment)
|
36
|
+
parser = Nokogiri::HTML::SAX::Parser.new(scanner,input_encode)
|
37
|
+
#parser.parse(input)
|
38
|
+
parser.parse(input) do |ctx|
|
39
|
+
ctx.replace_entities = true
|
40
|
+
end
|
41
|
+
results = ScanResults.new(Time.now)
|
42
|
+
results.clean_html = handler.document
|
43
|
+
results.messages = handler.errors
|
44
|
+
results
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
module AntiSamy
|
2
|
-
# A model for HTML attributes and the "rules" they must follow (either literals or regular expressions) in
|
3
|
-
# order to be considered valid. This is a simple container class
|
4
|
-
class Attribute
|
5
|
-
attr_accessor :name, :description, :action, :values, :expressions
|
6
|
-
ACTION_REMOVE_TAG = "removeTag"
|
7
|
-
ACTION_FILTER_TAG = "filterTag"
|
8
|
-
ACTION_ENCODE_TAG = "encodeTag"
|
9
|
-
ACTION_REMOVE_ATTRIB = "removeAttribute"
|
10
|
-
# Create a new attribute
|
11
|
-
def initialize(name)
|
12
|
-
@name = name
|
13
|
-
@description = nil
|
14
|
-
@action = nil
|
15
|
-
@values = []
|
16
|
-
@expressions = []
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
1
|
+
module AntiSamy
|
2
|
+
# A model for HTML attributes and the "rules" they must follow (either literals or regular expressions) in
|
3
|
+
# order to be considered valid. This is a simple container class
|
4
|
+
class Attribute
|
5
|
+
attr_accessor :name, :description, :action, :values, :expressions
|
6
|
+
ACTION_REMOVE_TAG = "removeTag"
|
7
|
+
ACTION_FILTER_TAG = "filterTag"
|
8
|
+
ACTION_ENCODE_TAG = "encodeTag"
|
9
|
+
ACTION_REMOVE_ATTRIB = "removeAttribute"
|
10
|
+
# Create a new attribute
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
@description = nil
|
14
|
+
@action = nil
|
15
|
+
@values = []
|
16
|
+
@expressions = []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,39 +1,39 @@
|
|
1
|
-
module AntiSamy
|
2
|
-
# A model for CSS properties and the "rules" they must follow (either literals
|
3
|
-
# or regular expressions) in order to be considered valid.
|
4
|
-
class CssProperty
|
5
|
-
attr_accessor :name, :description, :action, :values, :expressions, :refs, :catagories
|
6
|
-
|
7
|
-
# Create a new property
|
8
|
-
def initialize(name)
|
9
|
-
@name = name
|
10
|
-
@description = nil
|
11
|
-
@values = []
|
12
|
-
@expressions = []
|
13
|
-
@refs = []
|
14
|
-
@categories = []
|
15
|
-
@action = nil
|
16
|
-
end
|
17
|
-
|
18
|
-
# Add a literal value to this property
|
19
|
-
def add_value(value)
|
20
|
-
@values << value
|
21
|
-
end
|
22
|
-
|
23
|
-
# Add a regular expression to this property
|
24
|
-
def add_expression(exp)
|
25
|
-
@expressions << exp
|
26
|
-
end
|
27
|
-
|
28
|
-
# Add a shorthand reference to this property
|
29
|
-
def add_ref(ref)
|
30
|
-
@refs << ref
|
31
|
-
end
|
32
|
-
|
33
|
-
# Add a category to this property
|
34
|
-
def add_category(cat)
|
35
|
-
@categories << cat
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
1
|
+
module AntiSamy
|
2
|
+
# A model for CSS properties and the "rules" they must follow (either literals
|
3
|
+
# or regular expressions) in order to be considered valid.
|
4
|
+
class CssProperty
|
5
|
+
attr_accessor :name, :description, :action, :values, :expressions, :refs, :catagories
|
6
|
+
|
7
|
+
# Create a new property
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@description = nil
|
11
|
+
@values = []
|
12
|
+
@expressions = []
|
13
|
+
@refs = []
|
14
|
+
@categories = []
|
15
|
+
@action = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add a literal value to this property
|
19
|
+
def add_value(value)
|
20
|
+
@values << value
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add a regular expression to this property
|
24
|
+
def add_expression(exp)
|
25
|
+
@expressions << exp
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add a shorthand reference to this property
|
29
|
+
def add_ref(ref)
|
30
|
+
@refs << ref
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add a category to this property
|
34
|
+
def add_category(cat)
|
35
|
+
@categories << cat
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/antisamy/model/tag.rb
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
-
module AntiSamy
|
2
|
-
# A model for HTML "tags" and the rules dictating their validation/filtration. Also contains information
|
3
|
-
# about their allowed attributes.
|
4
|
-
class Tag
|
5
|
-
# Name and Action fields. Actions determine what we do when we see this tag
|
6
|
-
attr_accessor :name, :action
|
7
|
-
|
8
|
-
# Create a new Tag object
|
9
|
-
def initialize(name)
|
10
|
-
@name = name
|
11
|
-
@action = action
|
12
|
-
@allowed_attributes = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
# Add an attribute to this property
|
16
|
-
def <<(attribute)
|
17
|
-
@allowed_attributes[attribute.name.downcase] = attribute
|
18
|
-
end
|
19
|
-
|
20
|
-
# fetch the map of attributes
|
21
|
-
def attributes
|
22
|
-
@allowed_attributes
|
23
|
-
end
|
24
|
-
|
25
|
-
# Fetch a property by name form this tag
|
26
|
-
def attribute(name)
|
27
|
-
@allowed_attributes[name]
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
1
|
+
module AntiSamy
|
2
|
+
# A model for HTML "tags" and the rules dictating their validation/filtration. Also contains information
|
3
|
+
# about their allowed attributes.
|
4
|
+
class Tag
|
5
|
+
# Name and Action fields. Actions determine what we do when we see this tag
|
6
|
+
attr_accessor :name, :action
|
7
|
+
|
8
|
+
# Create a new Tag object
|
9
|
+
def initialize(name)
|
10
|
+
@name = name
|
11
|
+
@action = action
|
12
|
+
@allowed_attributes = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add an attribute to this property
|
16
|
+
def <<(attribute)
|
17
|
+
@allowed_attributes[attribute.name.downcase] = attribute
|
18
|
+
end
|
19
|
+
|
20
|
+
# fetch the map of attributes
|
21
|
+
def attributes
|
22
|
+
@allowed_attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
# Fetch a property by name form this tag
|
26
|
+
def attribute(name)
|
27
|
+
@allowed_attributes[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/antisamy/policy.rb
CHANGED
@@ -1,545 +1,577 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
module AntiSamy
|
4
|
-
|
5
|
-
# Schema validation Error
|
6
|
-
class SchemaError < StandardError; end
|
7
|
-
# Policy validation error
|
8
|
-
class PolicyError < StandardError; end
|
9
|
-
|
10
|
-
# Model for our policy engine.
|
11
|
-
# the XSD for AntiSammy is stored in this file after the END section
|
12
|
-
class Policy
|
13
|
-
attr_accessor :max_input
|
14
|
-
# We allow these tags to be empty
|
15
|
-
ALLOWED_EMPTY = ["br", "hr", "a", "img", "link", "iframe", "script", "object", "applet", "frame", "base", "param", "meta", "input", "textarea", "embed", "basefont", "col"]
|
16
|
-
# *Actions*
|
17
|
-
ACTION_FILTER = "filter"
|
18
|
-
ACTION_TRUNCATE = "truncate"
|
19
|
-
ACTION_VALIDATE = "validate"
|
20
|
-
ACTION_REMOVE = "remove"
|
21
|
-
ACTION_ENCODE = "encode"
|
22
|
-
# Anything regular express
|
23
|
-
ANYTHING_REGEX = /.*/
|
24
|
-
# AntiSammy XSD constants
|
25
|
-
DEFAULT_ONINVALID = "removeAttribute"
|
26
|
-
# Directive Name Constants
|
27
|
-
OMIT_XML_DECL = "omitXmlDeclaration"
|
28
|
-
OMIT_DOC_TYPE = "omitDoctypeDeclaration"
|
29
|
-
MAX_INPUT = "maxInputSize"
|
30
|
-
USE_XHTML = "userXHTML"
|
31
|
-
FORMAT_OUTPUT = "formatOutput"
|
32
|
-
# will we allow embedded style sheets
|
33
|
-
EMBED_STYLESHEETS = "embedStyleSheets"
|
34
|
-
# Connection timeout in miliseconds
|
35
|
-
CONN_TIMEOUT = "conenctionTimeout"
|
36
|
-
|
37
|
-
VALIDATE_P_AS_E = "validateParamAsEmbed"
|
38
|
-
PRESERVE_SPACE = "preserveSpace"
|
39
|
-
PRESERVE_COMMENTS = "preserveComments"
|
40
|
-
ON_UNKNOWN_TAG = "onUnknownTag"
|
41
|
-
MAX_SHEETS = "maxStyleSheetImports"
|
42
|
-
|
43
|
-
# Class method to fetch the schema
|
44
|
-
def self.schema
|
45
|
-
data = StringIO.new
|
46
|
-
File.open(__FILE__) do |f|
|
47
|
-
begin
|
48
|
-
line = f.gets
|
49
|
-
end until line.match(/^__END__$/)
|
50
|
-
while line = f.gets
|
51
|
-
data << line
|
52
|
-
end
|
53
|
-
end
|
54
|
-
data.rewind
|
55
|
-
data.read
|
56
|
-
end
|
57
|
-
|
58
|
-
# Create a policy object.
|
59
|
-
# You can pass in either:
|
60
|
-
# * File path
|
61
|
-
# * IO object
|
62
|
-
# * String containing the policy XML
|
63
|
-
# All policies will be validated against the builtin schema file and will raise
|
64
|
-
# an Error if the policy doesnt conform to the schema
|
65
|
-
def initialize(string_or_io)
|
66
|
-
schema = Nokogiri::XML.Schema(Policy.schema)
|
67
|
-
if string_or_io.respond_to?(:read)
|
68
|
-
uri = string_or_io.read
|
69
|
-
else
|
70
|
-
if File.exists?(string_or_io)
|
71
|
-
uri = IO.read(string_or_io)
|
72
|
-
else
|
73
|
-
uri = string_or_io
|
74
|
-
end
|
75
|
-
end
|
76
|
-
doc = Nokogiri::XML.parse(uri)
|
77
|
-
# We now have the Poolicy XML data lets parse it
|
78
|
-
errors = schema.validate(doc)
|
79
|
-
raise SchemaError, errors.join(",") if errors.size > 0
|
80
|
-
@common_regex = {}
|
81
|
-
@common_attrib = {}
|
82
|
-
@tag_rules = {}
|
83
|
-
@css_rules = {}
|
84
|
-
@directives = Hash.new(false)
|
85
|
-
@global_attrib = {}
|
86
|
-
@encode_tags = []
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
if
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
if
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
</xsd:
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
<xsd:
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
</xsd:
|
461
|
-
<xsd:attribute name="
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
<xsd:
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
<xsd:element name="literal" type="
|
504
|
-
</xsd:sequence>
|
505
|
-
|
506
|
-
|
507
|
-
<xsd:attribute name="
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module AntiSamy
|
4
|
+
|
5
|
+
# Schema validation Error
|
6
|
+
class SchemaError < StandardError; end
|
7
|
+
# Policy validation error
|
8
|
+
class PolicyError < StandardError; end
|
9
|
+
|
10
|
+
# Model for our policy engine.
|
11
|
+
# the XSD for AntiSammy is stored in this file after the END section
|
12
|
+
class Policy
|
13
|
+
attr_accessor :max_input
|
14
|
+
# We allow these tags to be empty
|
15
|
+
ALLOWED_EMPTY = ["br", "hr", "a", "img", "link", "iframe", "script", "object", "applet", "frame", "base", "param", "meta", "input", "textarea", "embed", "basefont", "col"]
|
16
|
+
# *Actions*
|
17
|
+
ACTION_FILTER = "filter"
|
18
|
+
ACTION_TRUNCATE = "truncate"
|
19
|
+
ACTION_VALIDATE = "validate"
|
20
|
+
ACTION_REMOVE = "remove"
|
21
|
+
ACTION_ENCODE = "encode"
|
22
|
+
# Anything regular express
|
23
|
+
ANYTHING_REGEX = /.*/
|
24
|
+
# AntiSammy XSD constants
|
25
|
+
DEFAULT_ONINVALID = "removeAttribute"
|
26
|
+
# Directive Name Constants
|
27
|
+
OMIT_XML_DECL = "omitXmlDeclaration"
|
28
|
+
OMIT_DOC_TYPE = "omitDoctypeDeclaration"
|
29
|
+
MAX_INPUT = "maxInputSize"
|
30
|
+
USE_XHTML = "userXHTML"
|
31
|
+
FORMAT_OUTPUT = "formatOutput"
|
32
|
+
# will we allow embedded style sheets
|
33
|
+
EMBED_STYLESHEETS = "embedStyleSheets"
|
34
|
+
# Connection timeout in miliseconds
|
35
|
+
CONN_TIMEOUT = "conenctionTimeout"
|
36
|
+
ANCHORS_NOFOLLOW = "nofollowAnchors"
|
37
|
+
VALIDATE_P_AS_E = "validateParamAsEmbed"
|
38
|
+
PRESERVE_SPACE = "preserveSpace"
|
39
|
+
PRESERVE_COMMENTS = "preserveComments"
|
40
|
+
ON_UNKNOWN_TAG = "onUnknownTag"
|
41
|
+
MAX_SHEETS = "maxStyleSheetImports"
|
42
|
+
|
43
|
+
# Class method to fetch the schema
|
44
|
+
def self.schema
|
45
|
+
data = StringIO.new
|
46
|
+
File.open(__FILE__) do |f|
|
47
|
+
begin
|
48
|
+
line = f.gets
|
49
|
+
end until line.match(/^__END__$/)
|
50
|
+
while line = f.gets
|
51
|
+
data << line
|
52
|
+
end
|
53
|
+
end
|
54
|
+
data.rewind
|
55
|
+
data.read
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create a policy object.
|
59
|
+
# You can pass in either:
|
60
|
+
# * File path
|
61
|
+
# * IO object
|
62
|
+
# * String containing the policy XML
|
63
|
+
# All policies will be validated against the builtin schema file and will raise
|
64
|
+
# an Error if the policy doesnt conform to the schema
|
65
|
+
def initialize(string_or_io)
|
66
|
+
schema = Nokogiri::XML.Schema(Policy.schema)
|
67
|
+
if string_or_io.respond_to?(:read)
|
68
|
+
uri = string_or_io.read
|
69
|
+
else
|
70
|
+
if File.exists?(string_or_io)
|
71
|
+
uri = IO.read(string_or_io)
|
72
|
+
else
|
73
|
+
uri = string_or_io
|
74
|
+
end
|
75
|
+
end
|
76
|
+
doc = Nokogiri::XML.parse(uri)
|
77
|
+
# We now have the Poolicy XML data lets parse it
|
78
|
+
errors = schema.validate(doc)
|
79
|
+
raise SchemaError, errors.join(",") if errors.size > 0
|
80
|
+
@common_regex = {}
|
81
|
+
@common_attrib = {}
|
82
|
+
@tag_rules = {}
|
83
|
+
@css_rules = {}
|
84
|
+
@directives = Hash.new(false)
|
85
|
+
@global_attrib = {}
|
86
|
+
@encode_tags = []
|
87
|
+
@allowed_empty = []
|
88
|
+
@allowed_empty << ALLOWED_EMPTY
|
89
|
+
@allowed_empty.flatten!
|
90
|
+
parse(doc)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get a particular directive
|
94
|
+
def directive(name)
|
95
|
+
@directives[name]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set a directive for the policy
|
99
|
+
def []=(name,value)
|
100
|
+
@directives[name] = value
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get a global attribute
|
104
|
+
def global(name)
|
105
|
+
@global_attrib[name.downcase]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Is the tag in the encode list
|
109
|
+
def encode?(tag)
|
110
|
+
@encode_tags.include?(tag)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return the tag rules
|
114
|
+
def tags
|
115
|
+
@tag_rules
|
116
|
+
end
|
117
|
+
|
118
|
+
# get a specific tag
|
119
|
+
def tag(name)
|
120
|
+
@tag_rules[name.downcase]
|
121
|
+
end
|
122
|
+
|
123
|
+
# return the css rules
|
124
|
+
def properties
|
125
|
+
@css_rules
|
126
|
+
end
|
127
|
+
|
128
|
+
# get a specific css rule
|
129
|
+
def property(prop)
|
130
|
+
@css_rules[prop.downcase]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get the list of attributes
|
134
|
+
def attributes
|
135
|
+
@common_attrib
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get a specific attribute
|
139
|
+
def attribute(name)
|
140
|
+
@common_attrib[name.downcase]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Get the list of expressions
|
144
|
+
def expressions
|
145
|
+
@common_regex
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get a specific expression
|
149
|
+
def expression(name)
|
150
|
+
@common_regex[name]
|
151
|
+
end
|
152
|
+
|
153
|
+
def allow_empty?(name)
|
154
|
+
@allowed_empty.include?(name.downcase)
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def make_re(p,context) #:nodoc:
|
159
|
+
output = StringIO.open('','w')
|
160
|
+
$stderr = output
|
161
|
+
begin
|
162
|
+
r = /#{p}/
|
163
|
+
warning = output.string
|
164
|
+
raise PolicyError, "context=#{context}, error=#{$1}, re=#{p}",caller(2) if warning =~ /warning: (.*)$/
|
165
|
+
return r
|
166
|
+
rescue RegexpError => e
|
167
|
+
raise PolicyError, "context=#{context}, error=#{e.message} re=#{p}", caller(2)
|
168
|
+
ensure
|
169
|
+
$stderr = STDERR
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Parse the Policy file
|
174
|
+
def parse(node) # :nodoc:
|
175
|
+
if node.children.nil? or node.children.last.nil?
|
176
|
+
return
|
177
|
+
end
|
178
|
+
node.children.last.children.each do |section|
|
179
|
+
if section.name.eql?("directives")
|
180
|
+
process_directves(section)
|
181
|
+
elsif section.name.eql?("common-regexps")
|
182
|
+
process_common_regexps(section)
|
183
|
+
elsif section.name.eql?("common-attributes")
|
184
|
+
process_common_attributes(section)
|
185
|
+
elsif section.name.eql?("global-tag-attributes")
|
186
|
+
process_global_attributes(section)
|
187
|
+
elsif section.name.eql?("tags-to-encode")
|
188
|
+
process_tag_to_encode(section)
|
189
|
+
elsif section.name.eql?("tag-rules")
|
190
|
+
process_tag_rules(section)
|
191
|
+
elsif section.name.eql?("css-rules")
|
192
|
+
process_css_rules(section)
|
193
|
+
elsif section.name.eql?("allowed-empty-tags")
|
194
|
+
process_empty_tags(section)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def process_empty_tags(section)# :nodoc:
|
200
|
+
# skip if we had no section
|
201
|
+
return if section.element_children.nil?
|
202
|
+
section.element_children.each do |dir|
|
203
|
+
if dir.name.eql?("literal-list")
|
204
|
+
if dir.element_children
|
205
|
+
dir.element_children.each do |child|
|
206
|
+
tag = child["value"]
|
207
|
+
if tag and !tag.empty?
|
208
|
+
@allowed_empty << tag.downcase
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# process the directives section
|
216
|
+
def process_directves(section) # :nodoc:
|
217
|
+
# skip if we had no section
|
218
|
+
return if section.element_children.nil?
|
219
|
+
# process the rules
|
220
|
+
section.element_children.each do |dir|
|
221
|
+
name = dir["name"]
|
222
|
+
value = dir["value"]
|
223
|
+
if name.eql?("maxInputSize")
|
224
|
+
@max_input = value.to_i
|
225
|
+
else
|
226
|
+
if name.eql?("connectionTimeout") or name.eql?("maxStyleSheetImports")
|
227
|
+
value = value.to_i
|
228
|
+
elsif value =~ /true/i
|
229
|
+
value = true
|
230
|
+
else
|
231
|
+
value = false
|
232
|
+
end
|
233
|
+
@directives[name] = value
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# process the <common-regexp> section
|
239
|
+
def process_common_regexps(section) # :nodoc:
|
240
|
+
# skip if we had no section
|
241
|
+
return if section.element_children.nil?
|
242
|
+
section.element_children.each do |re|
|
243
|
+
@common_regex[re["name"]] = make_re(re["value"],"common-regex(#{re['name']})")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Helper method to process a literal and regex section
|
248
|
+
def process_attr_lists(att,node,exception) # :nodoc:
|
249
|
+
node.element_children.each do |el|
|
250
|
+
if el.name.eql?("regexp-list")
|
251
|
+
if el.element_children
|
252
|
+
el.element_children.each do |re|
|
253
|
+
v = re["value"]
|
254
|
+
n = re["name"]
|
255
|
+
if n and !n.empty?
|
256
|
+
if @common_regex[n].nil?
|
257
|
+
raise PolicyError, "regex #{n} in #{exception} but wasnt found in <common-regex>"
|
258
|
+
else
|
259
|
+
att.expressions << expression(n)
|
260
|
+
end
|
261
|
+
else
|
262
|
+
att.expressions << make_re(v,exception)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
elsif el.name.eql?("literal-list")
|
267
|
+
if el.element_children
|
268
|
+
el.element_children.each do |re|
|
269
|
+
v = re["value"]
|
270
|
+
if v and !v.empty?
|
271
|
+
att.values << v
|
272
|
+
else
|
273
|
+
if re.child and re.child.text?
|
274
|
+
att.values << re.child.content
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# Process the <common-attributes> section
|
284
|
+
def process_common_attributes(section) # :nodoc:
|
285
|
+
# skip if we had no section
|
286
|
+
return if section.element_children.nil?
|
287
|
+
section.element_children.each do |val|
|
288
|
+
invalid = val["onInvalid"]
|
289
|
+
name = val["name"]
|
290
|
+
desc = val["description"]
|
291
|
+
att = Attribute.new(name)
|
292
|
+
att.description = desc
|
293
|
+
att.action = (invalid.nil? or invalid.empty?) ? DEFAULT_ONINVALID : invalid
|
294
|
+
return if val.element_children.nil?
|
295
|
+
process_attr_lists(att,val,"common-attribute(#{name})")
|
296
|
+
@common_attrib[name.downcase] = att
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Process the <global-attributes> section
|
301
|
+
def process_global_attributes(section) # :nodoc:
|
302
|
+
# skip if we had no section
|
303
|
+
return if section.element_children.nil?
|
304
|
+
section.element_children.each do |ga|
|
305
|
+
name = ga["name"]
|
306
|
+
att = @common_attrib[name]
|
307
|
+
raise PolicyError, "global attribute #{name} was not defined in <common-attributes>" if att.nil?
|
308
|
+
@global_attrib[name.downcase] = att
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# process the <tag-to-encode> section
|
313
|
+
def process_tag_to_encode(section) # :nodoc:
|
314
|
+
# skip if we had no section
|
315
|
+
return if section.element_children.nil?
|
316
|
+
section.element_children.each do |tag|
|
317
|
+
if tag.child and tag.child.text?
|
318
|
+
@encode_tags << tag.child.content.downcase
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Process the <tag-ruls> section
|
324
|
+
def process_tag_rules(section) # :nodoc:
|
325
|
+
return if section.element_children.nil?
|
326
|
+
section.element_children.each do |tx|
|
327
|
+
name = tx["name"]
|
328
|
+
action = tx["action"]
|
329
|
+
t = Tag.new(name)
|
330
|
+
t.action = action
|
331
|
+
# Add attributes
|
332
|
+
if tx.element_children
|
333
|
+
tx.element_children.each do |tc|
|
334
|
+
catt = @common_attrib[tc["name"]]
|
335
|
+
if catt # common attrib with value override
|
336
|
+
act = tc["onInvalid"]
|
337
|
+
dec = tc["description"]
|
338
|
+
ncatt = catt.dup
|
339
|
+
ncatt.action = act unless act.nil? or act.empty?
|
340
|
+
ncatt.description = dec unless dec.nil? or dec.empty?
|
341
|
+
t<< ncatt
|
342
|
+
else
|
343
|
+
att = Attribute.new(tc["name"])
|
344
|
+
att.action = tc["onInvalid"]
|
345
|
+
att.description = tc["description"]
|
346
|
+
process_attr_lists(att,tc," tag-rules(#{name})")
|
347
|
+
t<< att
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
# End add attributes
|
352
|
+
@tag_rules[name.downcase] = t
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Process the <css-rules> section
|
357
|
+
def process_css_rules(section) # :nodoc:
|
358
|
+
return if section.element_children.nil?
|
359
|
+
section.element_children.each do |css|
|
360
|
+
name = css["name"]
|
361
|
+
desc = css["description"]
|
362
|
+
action = css["onInvalid"]
|
363
|
+
if action.nil? or action.empty?
|
364
|
+
action = DEFAULT_ONINVALID
|
365
|
+
end
|
366
|
+
prop = CssProperty.new(name)
|
367
|
+
prop.action = action
|
368
|
+
prop.description = desc
|
369
|
+
# Process regex, listerals and shorthands
|
370
|
+
if css.element_children
|
371
|
+
css.element_children.each do |child|
|
372
|
+
empty = child.element_children.nil?
|
373
|
+
# Regex
|
374
|
+
if child.name.eql?("regexp-list")
|
375
|
+
unless empty
|
376
|
+
child.element_children.each do |re|
|
377
|
+
re_name = re["name"]
|
378
|
+
re_value = re["value"]
|
379
|
+
gre = expression(re_name)
|
380
|
+
if gre
|
381
|
+
prop.add_expression(gre)
|
382
|
+
elsif re_value and !re_value.empty?
|
383
|
+
prop.add_expression(make_re(re_value,"css-rule(#{name})"))
|
384
|
+
else
|
385
|
+
raise PolicyError, "#{re_name} was referenced in CSS rule #{name} but wasnt found in <common-regexp>"
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
elsif child.name.eql?("literal-list") # literals
|
390
|
+
unless empty
|
391
|
+
child.element_children.each do |li|
|
392
|
+
prop.add_value(li["value"]) if li["value"]
|
393
|
+
end
|
394
|
+
end
|
395
|
+
elsif child.name.eql?("category-list") # literals
|
396
|
+
unless empty
|
397
|
+
child.element_children.each do |li|
|
398
|
+
prop.add_category(li["value"]) if li["value"]
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
elsif child.name.eql?("shorthand-list") # refs
|
403
|
+
unless empty
|
404
|
+
child.element_children.each do |sl|
|
405
|
+
prop.add_ref(sl["name"]) if sl["name"]
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
@css_rules[name.downcase] = prop
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
|
418
|
+
__END__
|
419
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
420
|
+
<xsd:schema
|
421
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
422
|
+
<xsd:element name="anti-samy-rules">
|
423
|
+
<xsd:complexType>
|
424
|
+
<xsd:sequence>
|
425
|
+
<xsd:element name="directives" type="Directives" maxOccurs="1" minOccurs="1"/>
|
426
|
+
<xsd:element name="common-regexps" type="CommonRegexps" maxOccurs="1" minOccurs="1"/>
|
427
|
+
<xsd:element name="common-attributes" type="AttributeList" maxOccurs="1" minOccurs="1"/>
|
428
|
+
<xsd:element name="global-tag-attributes" type="AttributeList" maxOccurs="1" minOccurs="1"/>
|
429
|
+
<xsd:element name="tags-to-encode" type="TagsToEncodeList" minOccurs="0" maxOccurs="1"/>
|
430
|
+
<xsd:element name="tag-rules" type="TagRules" minOccurs="1" maxOccurs="1"/>
|
431
|
+
<xsd:element name="css-rules" type="CSSRules" minOccurs="1" maxOccurs="1"/>
|
432
|
+
<xsd:element name="allowed-empty-tags" type="AllowedEmptyTags" minOccurs="0" maxOccurs="1"/>
|
433
|
+
</xsd:sequence>
|
434
|
+
</xsd:complexType>
|
435
|
+
</xsd:element>
|
436
|
+
<xsd:complexType name="Directives">
|
437
|
+
<xsd:sequence maxOccurs="unbounded">
|
438
|
+
<xsd:element name="directive" type="Directive" minOccurs="0"/>
|
439
|
+
</xsd:sequence>
|
440
|
+
</xsd:complexType>
|
441
|
+
<xsd:complexType name="Directive">
|
442
|
+
<xsd:attribute name="name" use="required">
|
443
|
+
<xsd:simpleType>
|
444
|
+
<xsd:restriction base="xsd:string">
|
445
|
+
<xsd:enumeration value="omitXmlDeclaration"/>
|
446
|
+
<xsd:enumeration value="omitDoctypeDeclaration"/>
|
447
|
+
<xsd:enumeration value="maxInputSize"/>
|
448
|
+
<xsd:enumeration value="useXHTML"/>
|
449
|
+
<xsd:enumeration value="embedStyleSheets"/>
|
450
|
+
<xsd:enumeration value="maxStyleSheetImports"/>
|
451
|
+
<xsd:enumeration value="connectionTimeout"/>
|
452
|
+
<xsd:enumeration value="nofollowAnchors"/>
|
453
|
+
<xsd:enumeration value="validateParamAsEmbed"/>
|
454
|
+
<xsd:enumeration value="preserveComments"/>
|
455
|
+
<xsd:enumeration value="preserveSpace"/>
|
456
|
+
<xsd:enumeration value="onUnknownTag"/>
|
457
|
+
<xsd:enumeration value="formatOutput"/>
|
458
|
+
</xsd:restriction>
|
459
|
+
</xsd:simpleType>
|
460
|
+
</xsd:attribute>
|
461
|
+
<xsd:attribute name="value" use="required"/>
|
462
|
+
</xsd:complexType>
|
463
|
+
<xsd:complexType name="CommonRegexps">
|
464
|
+
<xsd:sequence maxOccurs="unbounded">
|
465
|
+
<xsd:element name="regexp" type="RegExp" minOccurs="0"/>
|
466
|
+
</xsd:sequence>
|
467
|
+
</xsd:complexType>
|
468
|
+
<xsd:complexType name="AttributeList">
|
469
|
+
<xsd:sequence maxOccurs="unbounded">
|
470
|
+
<xsd:element name="attribute" type="Attribute" minOccurs="0"/>
|
471
|
+
</xsd:sequence>
|
472
|
+
</xsd:complexType>
|
473
|
+
<xsd:complexType name="TagsToEncodeList">
|
474
|
+
<xsd:sequence maxOccurs="unbounded">
|
475
|
+
<xsd:element name="tag" minOccurs="0"/>
|
476
|
+
</xsd:sequence>
|
477
|
+
</xsd:complexType>
|
478
|
+
<xsd:complexType name="TagRules">
|
479
|
+
<xsd:sequence maxOccurs="unbounded">
|
480
|
+
<xsd:element name="tag" type="Tag" minOccurs="0"/>
|
481
|
+
</xsd:sequence>
|
482
|
+
</xsd:complexType>
|
483
|
+
<xsd:complexType name="Tag">
|
484
|
+
<xsd:sequence maxOccurs="unbounded">
|
485
|
+
<xsd:element name="attribute" type="Attribute" minOccurs="0" />
|
486
|
+
</xsd:sequence>
|
487
|
+
<xsd:attribute name="name" use="required"/>
|
488
|
+
<xsd:attribute name="action" use="required">
|
489
|
+
<xsd:simpleType>
|
490
|
+
<xsd:restriction base="xsd:string">
|
491
|
+
<xsd:enumeration value="validate"/>
|
492
|
+
<xsd:enumeration value="truncate"/>
|
493
|
+
<xsd:enumeration value="remove"/>
|
494
|
+
<xsd:enumeration value="filter"/>
|
495
|
+
<xsd:enumeration value="encode"/>
|
496
|
+
</xsd:restriction>
|
497
|
+
</xsd:simpleType>
|
498
|
+
</xsd:attribute>
|
499
|
+
</xsd:complexType>
|
500
|
+
<xsd:complexType name="Attribute">
|
501
|
+
<xsd:sequence>
|
502
|
+
<xsd:element name="regexp-list" type="RegexpList" minOccurs="0"/>
|
503
|
+
<xsd:element name="literal-list" type="LiteralList" minOccurs="0"/>
|
504
|
+
</xsd:sequence>
|
505
|
+
<xsd:attribute name="name" use="required"/>
|
506
|
+
<xsd:attribute name="description"/>
|
507
|
+
<xsd:attribute name="onInvalid">
|
508
|
+
<xsd:simpleType>
|
509
|
+
<xsd:restriction base="xsd:string">
|
510
|
+
<xsd:enumeration value="removeTag"/>
|
511
|
+
<xsd:enumeration value="filterTag"/>
|
512
|
+
<xsd:enumeration value="encodeTag"/>
|
513
|
+
<xsd:enumeration value="removeAttribute"/>
|
514
|
+
</xsd:restriction>
|
515
|
+
</xsd:simpleType>
|
516
|
+
</xsd:attribute>
|
517
|
+
</xsd:complexType>
|
518
|
+
<xsd:complexType name="RegexpList">
|
519
|
+
<xsd:sequence maxOccurs="unbounded">
|
520
|
+
<xsd:element name="regexp" type="RegExp" minOccurs="0"/>
|
521
|
+
</xsd:sequence>
|
522
|
+
</xsd:complexType>
|
523
|
+
<xsd:complexType name="RegExp">
|
524
|
+
<xsd:attribute name="name" type="xsd:string"/>
|
525
|
+
<xsd:attribute name="value" type="xsd:string"/>
|
526
|
+
</xsd:complexType>
|
527
|
+
<xsd:complexType name="LiteralList">
|
528
|
+
<xsd:sequence maxOccurs="unbounded">
|
529
|
+
<xsd:element name="literal" type="Literal" minOccurs="0"/>
|
530
|
+
</xsd:sequence>
|
531
|
+
</xsd:complexType>
|
532
|
+
<xsd:complexType name="Literal">
|
533
|
+
<xsd:attribute name="value" type="xsd:string"/>
|
534
|
+
</xsd:complexType>
|
535
|
+
<xsd:complexType name="CSSRules">
|
536
|
+
<xsd:sequence maxOccurs="unbounded">
|
537
|
+
<xsd:element name="property" type="Property" minOccurs="0"/>
|
538
|
+
</xsd:sequence>
|
539
|
+
</xsd:complexType>
|
540
|
+
<xsd:complexType name="Property">
|
541
|
+
<xsd:sequence>
|
542
|
+
<xsd:element name="category-list" type="CategoryList" minOccurs="0"/>
|
543
|
+
<xsd:element name="literal-list" type="LiteralList" minOccurs="0"/>
|
544
|
+
<xsd:element name="regexp-list" type="RegexpList" minOccurs="0"/>
|
545
|
+
<xsd:element name="shorthand-list" type="ShorthandList" minOccurs="0"/>
|
546
|
+
</xsd:sequence>
|
547
|
+
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
548
|
+
<xsd:attribute name="default" type="xsd:string"/>
|
549
|
+
<xsd:attribute name="description" type="xsd:string"/>
|
550
|
+
</xsd:complexType>
|
551
|
+
<xsd:complexType name="ShorthandList">
|
552
|
+
<xsd:sequence maxOccurs="unbounded">
|
553
|
+
<xsd:element name="shorthand" type="Shorthand" minOccurs="0"/>
|
554
|
+
</xsd:sequence>
|
555
|
+
</xsd:complexType>
|
556
|
+
<xsd:complexType name="Shorthand">
|
557
|
+
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
558
|
+
</xsd:complexType>
|
559
|
+
<xsd:complexType name="CategoryList">
|
560
|
+
<xsd:sequence maxOccurs="unbounded">
|
561
|
+
<xsd:element name="category" type="Category" minOccurs="0"/>
|
562
|
+
</xsd:sequence>
|
563
|
+
</xsd:complexType>
|
564
|
+
<xsd:complexType name="Category">
|
565
|
+
<xsd:attribute name="value" type="xsd:string" use="required"/>
|
566
|
+
</xsd:complexType>
|
567
|
+
<xsd:complexType name="Entity">
|
568
|
+
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
569
|
+
<xsd:attribute name="cdata" type="xsd:string" use="required"/>
|
570
|
+
</xsd:complexType>
|
571
|
+
<xsd:complexType name="AllowedEmptyTags">
|
572
|
+
<xsd:sequence>
|
573
|
+
<xsd:element name="literal-list" type="LiteralList" minOccurs="1"/>
|
574
|
+
</xsd:sequence>
|
575
|
+
</xsd:complexType>
|
576
|
+
|
577
|
+
</xsd:schema>
|