antisamy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ module AntiSamy
2
+
3
+ class ScanError < StandardError; end
4
+
5
+ # Scan message, it will contain a message key, tag and optionally content, value
6
+ class ScanMessage
7
+ # error.tag.notfound
8
+ ERROR_TAG_NOT_IN_POLICY = "error.tag.notfound"
9
+ # error.tag.removed
10
+ ERROR_TAG_DISALLOWED = "error.tag.removed"
11
+ # error.tag.filtered
12
+ ERROR_TAG_FILTERED = "error.tag.filtered"
13
+ # error.tag.encoded
14
+ ERROR_TAG_ENCODED = "error.tag.encoded"
15
+ # error.css.tag.malformed
16
+ ERROR_CSS_TAG_MALFORMED = "error.css.tag.malformed"
17
+ # error.css.attribute.malformed
18
+ ERROR_CSS_ATTRIBUTE_MALFORMED = "error.css.attribute.malformed"
19
+ # error.attribute.invalid.filtered
20
+ ERROR_ATTRIBUTE_CAUSE_FILTER = "error.attribute.invalid.filtered"
21
+ # error.attribute.invalid.encoded
22
+ ERROR_ATTRIBUTE_CAUSE_ENCODE = "error.attribute.invalid.encoded"
23
+ # error.attribute.invalid.filtered
24
+ ERROR_ATTRIBUTE_INVALID_FILTERED = "error.attribute.invalid.filtered"
25
+ # error.attribute.invalid.removed
26
+ ERROR_ATTRIBUTE_INVALID_REMOVED = "error.attribute.invalid.removed"
27
+ # error.attribute.notfound
28
+ ERROR_ATTRIBUTE_NOT_IN_POLICY = "error.attribute.notfound"
29
+ # error.attribute.invalid
30
+ ERROR_ATTRIBUTE_INVALID = "error.attribute.invalid"
31
+
32
+ attr_reader :tag, :content, :value, :msgkey
33
+ def initialize(msgkey, tag, content=nil,value=nil)
34
+ @msgkey = msgkey
35
+ @tag = tag
36
+ @content = content
37
+ @value = value
38
+ end
39
+ def to_s
40
+ "#{self.msgkey} #{@tag} #{@content} #{@value}"
41
+ end
42
+ end
43
+
44
+ class Scanner
45
+ attr_accessor :policy, :errors, :nofollow, :pae
46
+ DEFAULT_ENCODE = "UTF-8"
47
+ ALLOW_EMPTY = %w[br hr a img link iframe script object applet frame base param meta input textarea embed basefont col]
48
+ # Setup a basic param tag rule
49
+ begin
50
+ name_attr = Attribute.new("name")
51
+ value_attr = Attribute.new("value")
52
+ name_attr.expressions << /.*/
53
+ value_attr.expressions << /.*/
54
+ @@basic_param_tag_rule = Tag.new("param")
55
+ @@basic_param_tag_rule << name_attr
56
+ @@basic_param_tag_rule << value_attr
57
+ @@basic_param_tag_rule.action = Policy::ACTION_VALIDATE
58
+ end
59
+
60
+ # Create a scanner with a given policy
61
+ def initialize(policy)
62
+ @policy = policy
63
+ @errors = []
64
+ end
65
+
66
+ # Scan the input using the provided input and output encoding
67
+ # will raise an error if nil input or the maximum input size is exceeded
68
+ def scan(input, input_encode, output_encoder)
69
+ raise ArgumentError if input.nil?
70
+ raise ScanError, "Max input Exceeded" if input.size > @policy.max_input
71
+ # check poilcy stuff
72
+ handler = Handler.new(@policy,output_encoder)
73
+ scanner = SaxFilter.new(@policy,handler,@@basic_param_tag_rule)
74
+ parser = Nokogiri::HTML::SAX::Parser.new(scanner,input_encode)
75
+ #parser.parse(input)
76
+ parser.parse(input) do |ctx|
77
+ ctx.replace_entities = true
78
+ end
79
+ results = ScanResults.new(Time.now)
80
+ results.clean_html = handler.document
81
+ results.messages = handler.errors
82
+ results
83
+ end
84
+ end
85
+ end
@@ -0,0 +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
@@ -0,0 +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
@@ -0,0 +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
@@ -0,0 +1,540 @@
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
+ EMBED_STYLESHEETS = "embedStyleSheets"
33
+ CONN_TIMEOUT = "conenctionTimeout"
34
+ ANCHROS_NOFOLLOW = "nofollowAnchors"
35
+ VALIDATE_P_AS_E = "validateParamAsEmbed"
36
+ PRESERVE_SPACE = "preserveSpace"
37
+ PRESERVE_COMMENTS = "preserveComments"
38
+ ON_UNKNOWN_TAG = "onUnknownTag"
39
+
40
+ # Class method to fetch the schema
41
+ def self.schema
42
+ data = StringIO.new
43
+ File.open(__FILE__) do |f|
44
+ begin
45
+ line = f.gets
46
+ end until line.match(/^__END__$/)
47
+ while line = f.gets
48
+ data << line
49
+ end
50
+ end
51
+ data.rewind
52
+ data.read
53
+ end
54
+
55
+ # Create a policy object.
56
+ # You can pass in either:
57
+ # * File path
58
+ # * IO object
59
+ # * String containing the policy XML
60
+ # All policies will be validated against the builtin schema file and will raise
61
+ # an Error if the policy doesnt conform to the schema
62
+ def initialize(string_or_io)
63
+ schema = Nokogiri::XML.Schema(Policy.schema)
64
+ if string_or_io.respond_to?(:read)
65
+ uri = string_or_io.read
66
+ else
67
+ if File.exists?(string_or_io)
68
+ uri = IO.read(string_or_io)
69
+ else
70
+ uri = string_or_io
71
+ end
72
+ end
73
+ doc = Nokogiri::XML.parse(uri)
74
+ # We now have the Poolicy XML data lets parse it
75
+ errors = schema.validate(doc)
76
+ raise SchemaError, errors.join(",") if errors.size > 0
77
+ @common_regex = {}
78
+ @common_attrib = {}
79
+ @tag_rules = {}
80
+ @css_rules = {}
81
+ @directives = Hash.new(false)
82
+ @global_attrib = {}
83
+ @encode_tags = []
84
+ parse(doc)
85
+ end
86
+
87
+ # Get a particular directive
88
+ def directive(name)
89
+ @directives[name]
90
+ end
91
+
92
+ # Set a directive for the policy
93
+ def []=(name,value)
94
+ @directives[name] = value
95
+ end
96
+
97
+ # Get a global attribute
98
+ def global(name)
99
+ @global_attrib[name.downcase]
100
+ end
101
+
102
+ # Is the tag in the encode list
103
+ def encode?(tag)
104
+ @encode_tags.include?(tag)
105
+ end
106
+
107
+ # Return the tag rules
108
+ def tags
109
+ @tag_rules
110
+ end
111
+
112
+ # get a specific tag
113
+ def tag(name)
114
+ @tag_rules[name.downcase]
115
+ end
116
+
117
+ # return the css rules
118
+ def properties
119
+ @css_rules
120
+ end
121
+
122
+ # get a specific css rule
123
+ def property(prop)
124
+ @css_rules[prop.downcase]
125
+ end
126
+
127
+ # Get the list of attributes
128
+ def attributes
129
+ @common_attrib
130
+ end
131
+
132
+ # Get a specific attribute
133
+ def attribute(name)
134
+ @common_attrib[name.downcase]
135
+ end
136
+
137
+ # Get the list of expressions
138
+ def expressions
139
+ @common_regex
140
+ end
141
+
142
+ # Get a specific expression
143
+ def expression(name)
144
+ @common_regex[name]
145
+ end
146
+
147
+ private
148
+ def make_re(p,context) #:nodoc:
149
+ output = StringIO.open('','w')
150
+ $stderr = output
151
+ begin
152
+ r = /#{p}/
153
+ warning = output.string
154
+ raise PolicyError, "context=#{context}, error=#{$1}, re=#{p}",caller(2) if warning =~ /warning: (.*)$/
155
+ return r
156
+ rescue RegexpError => e
157
+ raise PolicyError, "context=#{context}, error=#{e.message} re=#{p}", caller(2)
158
+ ensure
159
+ $stderr = STDERR
160
+ end
161
+ end
162
+
163
+ # Parse the Policy file
164
+ def parse(node) # :nodoc:
165
+ if node.children.nil? or node.children.last.nil?
166
+ return
167
+ end
168
+ node.children.last.children.each do |section|
169
+ if section.name.eql?("directives")
170
+ process_directves(section)
171
+ elsif section.name.eql?("common-regexps")
172
+ process_common_regexps(section)
173
+ elsif section.name.eql?("common-attributes")
174
+ process_common_attributes(section)
175
+ elsif section.name.eql?("global-tag-attributes")
176
+ process_global_attributes(section)
177
+ elsif section.name.eql?("tags-to-encode")
178
+ process_tag_to_encode(section)
179
+ elsif section.name.eql?("tag-rules")
180
+ process_tag_rules(section)
181
+ elsif section.name.eql?("css-rules")
182
+ process_css_rules(section)
183
+ end
184
+ end
185
+ end
186
+
187
+ # process the directives section
188
+ def process_directves(section) # :nodoc:
189
+ # skip if we had no section
190
+ return if section.element_children.nil?
191
+ # process the rules
192
+ section.element_children.each do |dir|
193
+ name = dir["name"]
194
+ value = dir["value"]
195
+ @directives[name] = value
196
+ if name.eql?("maxInputSize")
197
+ @max_input = value.to_i
198
+ else
199
+ if value =~ /true/
200
+ value = true
201
+ else
202
+ value = false
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ # process the <common-regexp> section
209
+ def process_common_regexps(section) # :nodoc:
210
+ # skip if we had no section
211
+ return if section.element_children.nil?
212
+ section.element_children.each do |re|
213
+ @common_regex[re["name"]] = make_re(re["value"],"common-regex(#{re['name']})")
214
+ end
215
+ end
216
+
217
+ # Helper method to process a literal and regex section
218
+ def process_attr_lists(att,node,exception) # :nodoc:
219
+ node.element_children.each do |el|
220
+ if el.name.eql?("regexp-list")
221
+ if el.element_children
222
+ el.element_children.each do |re|
223
+ v = re["value"]
224
+ n = re["name"]
225
+ if n and !n.empty?
226
+ if @common_regex[n].nil?
227
+ raise PolicyError, "regex #{n} in #{exception} but wasnt found in <common-regex>"
228
+ else
229
+ att.expressions << expression(n)
230
+ end
231
+ else
232
+ att.expressions << make_re(v,exception)
233
+ end
234
+ end
235
+ end
236
+ elsif el.name.eql?("literal-list")
237
+ if el.element_children
238
+ el.element_children.each do |re|
239
+ v = re["value"]
240
+ if v and !v.empty?
241
+ att.values << v
242
+ else
243
+ if re.child and re.child.text?
244
+ att.values << re.child.content
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ # Process the <common-attributes> section
254
+ def process_common_attributes(section) # :nodoc:
255
+ # skip if we had no section
256
+ return if section.element_children.nil?
257
+ section.element_children.each do |val|
258
+ invalid = val["onInvalid"]
259
+ name = val["name"]
260
+ desc = val["description"]
261
+ att = Attribute.new(name)
262
+ att.description = desc
263
+ att.action = (invalid.nil? or invalid.empty?) ? DEFAULT_ONINVALID : invalid
264
+ return if val.element_children.nil?
265
+ process_attr_lists(att,val,"common-attribute(#{name})")
266
+ @common_attrib[name.downcase] = att
267
+ end
268
+ end
269
+
270
+ # Process the <global-attributes> section
271
+ def process_global_attributes(section) # :nodoc:
272
+ # skip if we had no section
273
+ return if section.element_children.nil?
274
+ section.element_children.each do |ga|
275
+ name = ga["name"]
276
+ att = @common_attrib[name]
277
+ raise PolicyError, "global attribute #{name} was not defined in <common-attributes>" if att.nil?
278
+ @global_attrib[name.downcase] = att
279
+ end
280
+ end
281
+
282
+ # process the <tag-to-encode> section
283
+ def process_tag_to_encode(section) # :nodoc:
284
+ # skip if we had no section
285
+ return if section.element_children.nil?
286
+ section.element_children.each do |tag|
287
+ if tag.child and tag.child.text?
288
+ @encode_tags << tag.child.content.downcase
289
+ end
290
+ end
291
+ end
292
+
293
+ # Process the <tag-ruls> section
294
+ def process_tag_rules(section) # :nodoc:
295
+ return if section.element_children.nil?
296
+ section.element_children.each do |tx|
297
+ name = tx["name"]
298
+ action = tx["action"]
299
+ t = Tag.new(name)
300
+ t.action = action
301
+ # Add attributes
302
+ if tx.element_children
303
+ tx.element_children.each do |tc|
304
+ catt = @common_attrib[tc["name"]]
305
+ if catt # common attrib with value override
306
+ act = tc["onInvalid"]
307
+ dec = tc["description"]
308
+ ncatt = catt.dup
309
+ ncatt.action = act unless act.nil? or act.empty?
310
+ ncatt.description = dec unless dec.nil? or dec.empty?
311
+ t<< ncatt
312
+ else
313
+ att = Attribute.new(tc["name"])
314
+ att.action = tc["onInvalid"]
315
+ att.description = tc["description"]
316
+ process_attr_lists(att,tc," tag-rules(#{name})")
317
+ t<< att
318
+ end
319
+ end
320
+ end
321
+ # End add attributes
322
+ @tag_rules[name.downcase] = t
323
+ end
324
+ end
325
+
326
+ # Process the <css-rules> section
327
+ def process_css_rules(section) # :nodoc:
328
+ return if section.element_children.nil?
329
+ section.element_children.each do |css|
330
+ name = css["name"]
331
+ desc = css["description"]
332
+ action = css["onInvalid"]
333
+ if action.nil? or action.empty?
334
+ action = DEFAULT_ONINVALID
335
+ end
336
+ prop = CssProperty.new(name)
337
+ prop.action = action
338
+ prop.description = desc
339
+ # Process regex, listerals and shorthands
340
+ if css.element_children
341
+ css.element_children.each do |child|
342
+ empty = child.element_children.nil?
343
+ # Regex
344
+ if child.name.eql?("regexp-list")
345
+ unless empty
346
+ child.element_children.each do |re|
347
+ re_name = re["name"]
348
+ re_value = re["value"]
349
+ gre = expression(re_name)
350
+ if gre
351
+ prop.add_expression(gre)
352
+ elsif re_value and !re_value.empty?
353
+ prop.add_expression(make_re(re_value,"css-rule(#{name})"))
354
+ else
355
+ raise PolicyError, "#{re_name} was referenced in CSS rule #{name} but wasnt found in <common-regexp>"
356
+ end
357
+ end
358
+ end
359
+ elsif child.name.eql?("literal-list") # literals
360
+ unless empty
361
+ child.element_children.each do |li|
362
+ prop.add_value(li["value"]) if li["value"]
363
+ end
364
+ end
365
+ elsif child.name.eql?("category-list") # literals
366
+ unless empty
367
+ child.element_children.each do |li|
368
+ prop.add_category(li["value"]) if li["value"]
369
+ end
370
+ end
371
+
372
+ elsif child.name.eql?("shorthand-list") # refs
373
+ unless empty
374
+ child.element_children.each do |sl|
375
+ prop.add_ref(sl["name"]) if sl["name"]
376
+ end
377
+ end
378
+ end
379
+ end
380
+ end
381
+ @css_rules[name.downcase] = prop
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+
388
+ __END__
389
+ <?xml version="1.0" encoding="UTF-8"?>
390
+ <xsd:schema
391
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema">
392
+ <xsd:element name="anti-samy-rules">
393
+ <xsd:complexType>
394
+ <xsd:sequence>
395
+ <xsd:element name="directives" type="Directives" maxOccurs="1" minOccurs="1"/>
396
+ <xsd:element name="common-regexps" type="CommonRegexps" maxOccurs="1" minOccurs="1"/>
397
+ <xsd:element name="common-attributes" type="AttributeList" maxOccurs="1" minOccurs="1"/>
398
+ <xsd:element name="global-tag-attributes" type="AttributeList" maxOccurs="1" minOccurs="1"/>
399
+ <xsd:element name="tags-to-encode" type="TagsToEncodeList" minOccurs="0" maxOccurs="1"/>
400
+ <xsd:element name="tag-rules" type="TagRules" minOccurs="1" maxOccurs="1"/>
401
+ <xsd:element name="css-rules" type="CSSRules" minOccurs="1" maxOccurs="1"/>
402
+ </xsd:sequence>
403
+ </xsd:complexType>
404
+ </xsd:element>
405
+ <xsd:complexType name="Directives">
406
+ <xsd:sequence maxOccurs="unbounded">
407
+ <xsd:element name="directive" type="Directive" minOccurs="0"/>
408
+ </xsd:sequence>
409
+ </xsd:complexType>
410
+ <xsd:complexType name="Directive">
411
+ <xsd:attribute name="name" use="required">
412
+ <xsd:simpleType>
413
+ <xsd:restriction base="xsd:string">
414
+ <xsd:enumeration value="omitXmlDeclaration"/>
415
+ <xsd:enumeration value="omitDoctypeDeclaration"/>
416
+ <xsd:enumeration value="maxInputSize"/>
417
+ <xsd:enumeration value="useXHTML"/>
418
+ <xsd:enumeration value="embedStyleSheets"/>
419
+ <xsd:enumeration value="maxStyleSheetImports"/>
420
+ <xsd:enumeration value="connectionTimeout"/>
421
+ <xsd:enumeration value="nofollowAnchors"/>
422
+ <xsd:enumeration value="validateParamAsEmbed"/>
423
+ <xsd:enumeration value="preserveComments"/>
424
+ <xsd:enumeration value="preserveSpace"/>
425
+ <xsd:enumeration value="onUnknownTag"/>
426
+ <xsd:enumeration value="formatOutput"/>
427
+ </xsd:restriction>
428
+ </xsd:simpleType>
429
+ </xsd:attribute>
430
+ <xsd:attribute name="value" use="required"/>
431
+ </xsd:complexType>
432
+ <xsd:complexType name="CommonRegexps">
433
+ <xsd:sequence maxOccurs="unbounded">
434
+ <xsd:element name="regexp" type="RegExp" minOccurs="0"/>
435
+ </xsd:sequence>
436
+ </xsd:complexType>
437
+ <xsd:complexType name="AttributeList">
438
+ <xsd:sequence maxOccurs="unbounded">
439
+ <xsd:element name="attribute" type="Attribute" minOccurs="0"/>
440
+ </xsd:sequence>
441
+ </xsd:complexType>
442
+ <xsd:complexType name="TagsToEncodeList">
443
+ <xsd:sequence maxOccurs="unbounded">
444
+ <xsd:element name="tag" minOccurs="0"/>
445
+ </xsd:sequence>
446
+ </xsd:complexType>
447
+ <xsd:complexType name="TagRules">
448
+ <xsd:sequence maxOccurs="unbounded">
449
+ <xsd:element name="tag" type="Tag" minOccurs="0"/>
450
+ </xsd:sequence>
451
+ </xsd:complexType>
452
+ <xsd:complexType name="Tag">
453
+ <xsd:sequence maxOccurs="unbounded">
454
+ <xsd:element name="attribute" type="Attribute" minOccurs="0" />
455
+ </xsd:sequence>
456
+ <xsd:attribute name="name" use="required"/>
457
+ <xsd:attribute name="action" use="required">
458
+ <xsd:simpleType>
459
+ <xsd:restriction base="xsd:string">
460
+ <xsd:enumeration value="validate"/>
461
+ <xsd:enumeration value="truncate"/>
462
+ <xsd:enumeration value="remove"/>
463
+ <xsd:enumeration value="filter"/>
464
+ <xsd:enumeration value="encode"/>
465
+ </xsd:restriction>
466
+ </xsd:simpleType>
467
+ </xsd:attribute>
468
+ </xsd:complexType>
469
+ <xsd:complexType name="Attribute">
470
+ <xsd:sequence>
471
+ <xsd:element name="regexp-list" type="RegexpList" minOccurs="0"/>
472
+ <xsd:element name="literal-list" type="LiteralList" minOccurs="0"/>
473
+ </xsd:sequence>
474
+ <xsd:attribute name="name" use="required"/>
475
+ <xsd:attribute name="description"/>
476
+ <xsd:attribute name="onInvalid">
477
+ <xsd:simpleType>
478
+ <xsd:restriction base="xsd:string">
479
+ <xsd:enumeration value="removeTag"/>
480
+ <xsd:enumeration value="filterTag"/>
481
+ <xsd:enumeration value="encodeTag"/>
482
+ <xsd:enumeration value="removeAttribute"/>
483
+ </xsd:restriction>
484
+ </xsd:simpleType>
485
+ </xsd:attribute>
486
+ </xsd:complexType>
487
+ <xsd:complexType name="RegexpList">
488
+ <xsd:sequence maxOccurs="unbounded">
489
+ <xsd:element name="regexp" type="RegExp" minOccurs="0"/>
490
+ </xsd:sequence>
491
+ </xsd:complexType>
492
+ <xsd:complexType name="RegExp">
493
+ <xsd:attribute name="name" type="xsd:string"/>
494
+ <xsd:attribute name="value" type="xsd:string"/>
495
+ </xsd:complexType>
496
+ <xsd:complexType name="LiteralList">
497
+ <xsd:sequence maxOccurs="unbounded">
498
+ <xsd:element name="literal" type="Literal" minOccurs="0"/>
499
+ </xsd:sequence>
500
+ </xsd:complexType>
501
+ <xsd:complexType name="Literal">
502
+ <xsd:attribute name="value" type="xsd:string"/>
503
+ </xsd:complexType>
504
+ <xsd:complexType name="CSSRules">
505
+ <xsd:sequence maxOccurs="unbounded">
506
+ <xsd:element name="property" type="Property" minOccurs="0"/>
507
+ </xsd:sequence>
508
+ </xsd:complexType>
509
+ <xsd:complexType name="Property">
510
+ <xsd:sequence>
511
+ <xsd:element name="category-list" type="CategoryList" minOccurs="0"/>
512
+ <xsd:element name="literal-list" type="LiteralList" minOccurs="0"/>
513
+ <xsd:element name="regexp-list" type="RegexpList" minOccurs="0"/>
514
+ <xsd:element name="shorthand-list" type="ShorthandList" minOccurs="0"/>
515
+ </xsd:sequence>
516
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
517
+ <xsd:attribute name="default" type="xsd:string"/>
518
+ <xsd:attribute name="description" type="xsd:string"/>
519
+ </xsd:complexType>
520
+ <xsd:complexType name="ShorthandList">
521
+ <xsd:sequence maxOccurs="unbounded">
522
+ <xsd:element name="shorthand" type="Shorthand" minOccurs="0"/>
523
+ </xsd:sequence>
524
+ </xsd:complexType>
525
+ <xsd:complexType name="Shorthand">
526
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
527
+ </xsd:complexType>
528
+ <xsd:complexType name="CategoryList">
529
+ <xsd:sequence maxOccurs="unbounded">
530
+ <xsd:element name="category" type="Category" minOccurs="0"/>
531
+ </xsd:sequence>
532
+ </xsd:complexType>
533
+ <xsd:complexType name="Category">
534
+ <xsd:attribute name="value" type="xsd:string" use="required"/>
535
+ </xsd:complexType>
536
+ <xsd:complexType name="Entity">
537
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
538
+ <xsd:attribute name="cdata" type="xsd:string" use="required"/>
539
+ </xsd:complexType>
540
+ </xsd:schema>