antisamy 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.rdoc +6 -1
  2. data/lib/antisamy/css/css_filter.rb +187 -0
  3. data/lib/antisamy/css/css_scanner.rb +84 -0
  4. data/lib/antisamy/css/css_validator.rb +129 -0
  5. data/lib/antisamy/csspool/rsac/sac/conditions/attribute_condition.rb +50 -0
  6. data/lib/antisamy/csspool/rsac/sac/conditions/begin_hyphen_condition.rb +18 -0
  7. data/lib/antisamy/csspool/rsac/sac/conditions/class_condition.rb +18 -0
  8. data/lib/antisamy/csspool/rsac/sac/conditions/combinator_condition.rb +36 -0
  9. data/lib/antisamy/csspool/rsac/sac/conditions/condition.rb +29 -0
  10. data/lib/antisamy/csspool/rsac/sac/conditions/id_condition.rb +23 -0
  11. data/lib/antisamy/csspool/rsac/sac/conditions/one_of_condition.rb +18 -0
  12. data/lib/antisamy/csspool/rsac/sac/conditions/pseudo_class_condition.rb +20 -0
  13. data/lib/antisamy/csspool/rsac/sac/conditions.rb +5 -0
  14. data/lib/antisamy/csspool/rsac/sac/document_handler.rb +66 -0
  15. data/lib/antisamy/csspool/rsac/sac/error_handler.rb +13 -0
  16. data/lib/antisamy/csspool/rsac/sac/generated_parser.rb +1012 -0
  17. data/lib/antisamy/csspool/rsac/sac/generated_property_parser.rb +9284 -0
  18. data/lib/antisamy/csspool/rsac/sac/lexeme.rb +27 -0
  19. data/lib/antisamy/csspool/rsac/sac/lexical_unit.rb +201 -0
  20. data/lib/antisamy/csspool/rsac/sac/parse_exception.rb +4 -0
  21. data/lib/antisamy/csspool/rsac/sac/parser.rb +109 -0
  22. data/lib/antisamy/csspool/rsac/sac/property_parser.rb +44 -0
  23. data/lib/antisamy/csspool/rsac/sac/selectors/child_selector.rb +36 -0
  24. data/lib/antisamy/csspool/rsac/sac/selectors/conditional_selector.rb +45 -0
  25. data/lib/antisamy/csspool/rsac/sac/selectors/descendant_selector.rb +36 -0
  26. data/lib/antisamy/csspool/rsac/sac/selectors/element_selector.rb +35 -0
  27. data/lib/antisamy/csspool/rsac/sac/selectors/selector.rb +25 -0
  28. data/lib/antisamy/csspool/rsac/sac/selectors/sibling_selector.rb +35 -0
  29. data/lib/antisamy/csspool/rsac/sac/selectors/simple_selector.rb +21 -0
  30. data/lib/antisamy/csspool/rsac/sac/selectors.rb +5 -0
  31. data/lib/antisamy/csspool/rsac/sac/token.rb +25 -0
  32. data/lib/antisamy/csspool/rsac/sac/tokenizer.rb +185 -0
  33. data/lib/antisamy/csspool/rsac/sac.rb +14 -0
  34. data/lib/antisamy/csspool/rsac/stylesheet/rule.rb +20 -0
  35. data/lib/antisamy/csspool/rsac/stylesheet/stylesheet.rb +76 -0
  36. data/lib/antisamy/csspool/rsac/stylesheet.rb +3 -0
  37. data/lib/antisamy/csspool/rsac.rb +1 -0
  38. data/lib/antisamy/html/handler.rb +4 -0
  39. data/lib/antisamy/html/sax_filter.rb +49 -33
  40. data/lib/antisamy/html/scanner.rb +1 -43
  41. data/lib/antisamy/policy.rb +8 -3
  42. data/lib/antisamy/scan_results.rb +68 -0
  43. data/lib/antisamy.rb +4 -0
  44. data/spec/antisamy_spec.rb +111 -3
  45. metadata +39 -3
data/README.rdoc CHANGED
@@ -5,7 +5,7 @@ user-supplier HTML and CSS. Please check out the AntiSamy project over at OWASP[
5
5
 
6
6
  == TODO
7
7
 
8
- * Add CSS scrubbing support
8
+ * At some point support CSS3
9
9
 
10
10
  == Synopsis
11
11
 
@@ -29,6 +29,11 @@ Please check policy-examples[https://github.com/washu/antisamy-ruby/tree/master/
29
29
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
30
30
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
31
31
 
32
+ == csspool
33
+
34
+ We use a forked version of csspool 0.2.6 within antysamy, you can find the license
35
+ for csspool in the rsac[https://github.com/washu/antisamy-ruby/tree/master/lib/antisamy/csspool] directory. csspool was re-namespaced to avoid any conflicts and updated for 1.9.2 ruby compatabilty
36
+
32
37
  == Copyright
33
38
 
34
39
  Copyright (c) 2011 Sal Scotto. See LICENSE.txt for
@@ -0,0 +1,187 @@
1
+ require 'uri'
2
+ module AntiSamy
3
+ class CssFilter < RSAC::DocumentHandler
4
+
5
+ attr_accessor :clean, :errors, :style_sheets
6
+
7
+ def initialize(policy,tag)
8
+ @policy = policy
9
+ @validator = CssValidator.new(@policy)
10
+ @errors = []
11
+ @clean = ''
12
+ @tag = tag
13
+ @selector_open = false
14
+ @style_sheets = []
15
+ @inline = !tag.nil?
16
+ @media_open = false
17
+ @current_media = nil
18
+ end
19
+ # Start of document
20
+ def start_document(input_source) #:nodoc:
21
+ end
22
+
23
+ # Receive notification of the end of a style sheet.
24
+ def end_document(input_source) #:nodoc:
25
+ end
26
+
27
+ # Receive notification of a comment
28
+ def comment(text)
29
+ @errors << ScanMessage.new(ScanMessage::ERROR_COMMENT_REMOVED,@tag,text)
30
+ end
31
+
32
+ def error(exception)
33
+ #puts exception
34
+ end
35
+
36
+ def fatal_error(exception)
37
+ #puts exception
38
+ end
39
+
40
+ def error(t)
41
+ #puts t
42
+ end
43
+
44
+ def warn(t)
45
+ #puts t
46
+ end
47
+
48
+ def warning_error(exception)
49
+ #puts exception
50
+ end
51
+ # Receive notification of an unknown at rule not supported by this parser.
52
+ def ignorable_at_rule(at_rule)
53
+ if inline
54
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_TAG_RULE_NOTFOUND,@tag,at_rule)
55
+ else
56
+ @errors << ScanMessage.new(ScanMessage::ERROR_STYLESHEET_RULE_NOTFOUND,@tag,at_rule)
57
+ end
58
+ end
59
+
60
+ # Namespace declaration
61
+ def namespace_declaration(prefix, uri) #:nodoc:
62
+ end
63
+
64
+ # Called on an import statement
65
+ def import_style(uri, media, default_namespace_uri = nil)
66
+ # check directive
67
+ unless @policy.directive(Policy::EMBED_STYLESHEETS)
68
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_IMPORT_DISABLED,@tag,uri)
69
+ return
70
+ end
71
+ # check for null uri
72
+ if uri.nil?
73
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_IMPORT_URL_INVALID,@tag)
74
+ end
75
+ # check uri rules
76
+ begin
77
+ luri = RSAC::LexicalURI.new(uri)
78
+ link = URI.parse(luri.string_value)
79
+ link.normalize!
80
+ onsite = @policy.expression("offsiteURL")
81
+ offsite = @policy.expression("onsiteURL")
82
+ # bad uri
83
+ raise "Invalid URI Pattern" if link.to_s !~ onsite and link.to_s !~ offsite
84
+ raise "Invalid URI" unless link.absolute?
85
+ @style_sheets << link
86
+ rescue Exception => e
87
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_IMPORT_URL_INVALID,@tag,uri)
88
+ end
89
+ end
90
+
91
+ # Notification of the start of a media statement
92
+ def start_media(media)
93
+ @media_open = true
94
+ @current_media = media
95
+ end
96
+
97
+ # Notification of the end of a media statement
98
+ def end_media(media)
99
+ @media_open = false
100
+ @current_media = nil
101
+ end
102
+
103
+ # Notification of the start of a page statement
104
+ def start_page(name = nil, pseudo_page = nil) #:nodoc:
105
+ end
106
+
107
+ # Notification of the end of a page statement
108
+ def end_page(name = nil, pseudo_page = nil) # :nodoc:
109
+ end
110
+
111
+ # Notification of the beginning of a font face statement.
112
+ def start_font_face #:nodoc:
113
+ end
114
+
115
+ # Notification of the end of a font face statement.
116
+ def end_font_face #:nodoc:
117
+ end
118
+
119
+ # Notification of the beginning of a rule statement.
120
+ def start_selector(selectors)
121
+ count = 0
122
+ selectors.each do |s|
123
+ name = s.to_css
124
+ valid = false
125
+ begin
126
+ @validator.valid_selector?(name,s)
127
+ valid = true
128
+ rescue Exception => e
129
+ if @inline
130
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_TAG_SELECTOR_NOTFOUND,@tag,name)
131
+ else
132
+ @errors << ScanMessage.new(ScanMessage::ERROR_STYLESHEET_SELECTOR_NOTFOUND,@tag,name)
133
+ end
134
+
135
+ end
136
+ if valid
137
+ if count > 0
138
+ clean << ", "
139
+ end
140
+ clean << name
141
+ count += 1
142
+ else
143
+ # not allowed selector
144
+ if @inline
145
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_TAG_SELECTOR_DISALLOWED,@tag,name)
146
+ else
147
+ @errors << ScanMessage.new(ScanMessage::ERROR_STYLESHEET_SELECTOR_DISALLOWED,@tag,name)
148
+ end
149
+ end
150
+ end
151
+ if count > 0
152
+ clean << " {\n"
153
+ @selector_open = true
154
+ end
155
+ end
156
+
157
+ # Notification of the end of a rule statement.
158
+ def end_selector(selectors)
159
+ if @selector_open
160
+ clean << "}\n"
161
+ end
162
+ @selector_open = false
163
+ end
164
+
165
+ # Notification of a declaration.
166
+ def property(name, value, important)
167
+ return unless @selector_open and @inline
168
+ if @validator.valid_property?(name,value)
169
+ clean << "\t" unless @inline
170
+ clean << "#{name}:"
171
+ value.each do |v|
172
+ clean << " #{v.to_s}"
173
+ end
174
+ clean << ";"
175
+ clean << "\n" unless @inline
176
+ else
177
+ cval = value.to_s
178
+ if @inline
179
+ @errors << ScanMessage.new(ScanMessage::ERROR_CSS_TAG_PROPERTY_INVALID,@tag,name,cval)
180
+ else
181
+ @errors << ScanMessage.new(ScanMessage::ERROR_STYLESHEET_PROPERTY_INVALID,@tag,name,cval)
182
+ end
183
+ end
184
+
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,84 @@
1
+ require 'open-uri'
2
+ module AntiSamy
3
+ # Css Scanner class
4
+ class CssScanner
5
+ attr_accessor :policy, :errors
6
+ # Create a scanner with a given policy
7
+ def initialize(policy)
8
+ @policy = policy
9
+ @errors = []
10
+ end
11
+ # Scan the input using the provided input and output encoding
12
+ # will raise an error if nil input or the maximum input size is exceeded
13
+ def scan_inline(a_value,name,max_input)
14
+ return scan_sheet("#{name} { #{a_value} }",max_input,name)
15
+ end
16
+
17
+ def scan_sheet(input,limit,tag = nil)
18
+ raise ArgumentError if input.nil?
19
+ raise ScanError, "Max input Exceeded #{input.size} > #{limit}" if input.size > limit
20
+ space_remaining = limit - input.size
21
+ # check poilcy stuff
22
+ if input =~ /^\s*<!\[CDATA\[(.*)\]\]>\s*$/
23
+ input = $1
24
+ end
25
+ # validator needs token sizes
26
+ filter = CssFilter.new(@policy,tag)
27
+ parser = RSAC::Parser.new(filter)
28
+ parser.error_handler = filter
29
+ parser.logger = filter
30
+ parser.parse(input)
31
+ # Populate the results
32
+ results = ScanResults.new(Time.now)
33
+ if @policy.directive(Policy::USE_XHTML)
34
+ result.clean_html = "<![CDATA[#{filter.clean}]]>"
35
+ else
36
+ results.clean_html = filter.clean
37
+ end
38
+ results.messages = filter.errors
39
+ # check for style sheets
40
+ sheets = filter.style_sheets
41
+ max_sheets = @policy.directive(Policy::MAX_SHEETS).to_i
42
+ max_sheets ||= 1
43
+ import_sheets = 0
44
+ if sheets.size > 0
45
+ timeout = 1000
46
+ if @policy.directive(Policy::CONN_TIMEOUT)
47
+ timeout = @policy.directive(Policy::CONN_TIMEOUT).to_i
48
+ end
49
+ timeout /= 1000
50
+ sheets.each do |sheet|
51
+ sheet_content = ''
52
+ begin
53
+ open(sheet,{:read_timeout => timeout}) do |f|
54
+ sheet_content = f.read(space_remaining)
55
+ end
56
+ space_remaining -= sheet_content.size
57
+ if import_sheets > max_sheets
58
+ # skip any remaing sheets if we exceeded the import count
59
+ results.messages << ScanMessage.new(ScanMessage::ERROR_CSS_IMPORT_EXCEEDED,"@import",sheet)
60
+ break;
61
+ end
62
+
63
+ if sheet_content.size > 0
64
+ #r = scan_sheet(sheet_content,space_remaining)
65
+ parser.parse(sheet_content)
66
+ #results.messages << r.messages
67
+ #results.messages.flatten!
68
+ import_sheets += 1
69
+ end
70
+
71
+ if space_remaining <= 0 or sheet_content.empty?
72
+ results.messages << ScanMessage.new(ScanMessage::ERROR_CSS_IMPORT_INPUT_SIZE,"@import",sheet)
73
+ break
74
+ end
75
+ rescue Exception => e
76
+ results.messages << ScanMessage.new(ScanMessage::ERROR_CSS_IMPORT_FAILURE,"@import",sheet)
77
+ end
78
+ # check the sheet rules
79
+ end
80
+ end
81
+ results
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,129 @@
1
+ module AntiSamy
2
+ class CssValidator
3
+
4
+ def initialize(policy)
5
+ @policy = policy
6
+ end
7
+
8
+ # Check to see if this selector is valid according to the policy
9
+ def valid_selector?(name,selector)
10
+ #puts selector.inspect
11
+ return false if selector.nil?
12
+ case selector.selector_type
13
+ when :SAC_CHILD_SELECTOR
14
+ return valid_selector?(name,selector.selector) && valid_selector?(name,selector.ancestor)
15
+ when :SAC_CONDITIONAL_SELECTOR
16
+ return valid_selector?(name,selector.selector) && valid_condition?(name,selector.condition)
17
+ when :SAC_DESCENDANT_SELECTOR
18
+ return valid_selector?(name,selector.selector) && valid_selector?(name,selector.ancestor)
19
+ when :SAC_ELEMENT_NODE_SELECTOR
20
+ return valid_simple_selector(selector)
21
+ when :SAC_DIRECT_ADJACENT_SELECTOR
22
+ return valid_selector?(name,selector.selector) && valid_selector?(name,selector.sibling)
23
+ when :SAC_ANY_NODE_SELECTOR
24
+ return valid_simple_selector(selector)
25
+ else
26
+ raise ScanError, name
27
+ end
28
+ end
29
+
30
+ # Validate a simple selector
31
+ def valid_simple_selector(selector) #:nodoc:
32
+ valid = false
33
+ inclusion = @policy.expression("cssElementSelector")
34
+ exclusion = @policy.expression("cssElementExclusion")
35
+ begin
36
+ css = selector.to_css
37
+ valid = (css =~ inclusion) and (css !~ exclusion)
38
+ rescue Exception=> e
39
+ end
40
+ valid
41
+ end
42
+
43
+ # Check if a given condition is valid according to the policy
44
+ def valid_condition?(name,condition)
45
+ type = condition.condition_type
46
+ case type
47
+ when :SAC_AND_CONDITION
48
+ a = condition.first
49
+ b = condition.second
50
+ return valid_condition?(name,a) && valid_condition?(name,b)
51
+ when :SAC_CLASS_CONDITION
52
+ inclusion = @policy.expression("cssClassSelector")
53
+ exclusion = @policy.expression("cssClassExclusion")
54
+ return validate_condition(condition,inclusion,exclusion)
55
+ when :SAC_ID_CONDITION
56
+ inclusion = @policy.expression("cssIDSelector")
57
+ exclusion = @policy.expression("cssIDExclusion")
58
+ return validate_condition(condition,inclusion,exclusion)
59
+ when :SAC_PSEUDO_CLASS_CONDITION
60
+ inclusion = @policy.expression("cssPseudoElementSelector")
61
+ exclusion = @policy.expression("cssPsuedoElementExclusion")
62
+ return validate_condition(condition,inclusion,exclusion)
63
+ when :SAC_ONE_OF_ATTRIBUTE_CONDITION
64
+ inclusion = @policy.expression("cssAttributeSelector")
65
+ exclusion = @policy.expression("cssAttributeExclusion")
66
+ return validate_condition(condition,inclusion,exclusion)
67
+ when :SAC_ATTRIBUTE_CONDITION
68
+ inclusion = @policy.expression("cssAttributeSelector")
69
+ exclusion = @policy.expression("cssAttributeExclusion")
70
+ return validate_condition(condition,inclusion,exclusion)
71
+ when :SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION
72
+ inclusion = @policy.expression("cssAttributeSelector")
73
+ exclusion = @policy.expression("cssAttributeExclusion")
74
+ return validate_condition(condition,inclusion,exclusion)
75
+ else
76
+ raise ScanError, name
77
+ end
78
+ end
79
+
80
+ # validate the actual condition
81
+ def validate_condition(condition,inclusion,exclusion) #:nodoc:
82
+ valid = false
83
+ begin
84
+ css = condition.to_css
85
+ valid = (css =~ inclusion) and (css !~ exclusion)
86
+ rescue Exception=> e
87
+ end
88
+ valid
89
+ end
90
+
91
+ # Validate each property value according to teh policy
92
+ def valid_property?(name,value)
93
+ prop = @policy.property(name) unless name.nil?
94
+ return false if prop.nil?
95
+ value.each do |prop_value|
96
+ v = prop_value.string_value
97
+ return false unless validate_value(prop,v)
98
+ end
99
+ return true
100
+ end
101
+
102
+ # is this a valid property value
103
+ def validate_value(property,value) #:nodoc:
104
+ valid = false
105
+ # Check static strings
106
+ property.values.each do |al_val|
107
+ valid = true if al_val.downcase.eql?(value.downcase)
108
+ end
109
+ # Check regular expressions
110
+ unless valid
111
+ property.expressions.each do |xp_val|
112
+ valid = true if value =~ xp_value
113
+ end
114
+ end
115
+ # check short hand
116
+ unless valid
117
+ property.refs.each do |ref|
118
+ real = @policy.property(ref)
119
+ if real
120
+ valid = validate_value(real,value)
121
+ end
122
+ end
123
+ end
124
+ # We will check media above.
125
+ return valid
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,50 @@
1
+ module RSAC
2
+ module Conditions
3
+ class AttributeCondition < Condition
4
+ attr_accessor :local_name, :value, :specified
5
+ alias :specified? :specified
6
+
7
+ class << self
8
+ def build(name, raw)
9
+ condition, value = raw
10
+ case condition
11
+ when "~="
12
+ OneOfCondition.new(name, value)
13
+ when "|="
14
+ BeginHyphenCondition.new(name, value)
15
+ else
16
+ AttributeCondition.new(name, value, true)
17
+ end
18
+ end
19
+ end
20
+
21
+ def initialize(local_name, value, specified, condition_type=:SAC_ATTRIBUTE_CONDITION)
22
+ super(condition_type)
23
+ @local_name = local_name
24
+ @value = value
25
+ @specified = specified
26
+ end
27
+
28
+ def to_css
29
+ "[#{local_name}#{value && "=#{value}"}]"
30
+ end
31
+
32
+ def to_xpath
33
+ "[@#{local_name}#{value && "='#{value}'"}]"
34
+ end
35
+
36
+ def specificity
37
+ [0, 0, 1, 0]
38
+ end
39
+
40
+ def ==(other)
41
+ super && local_name == other.local_name && value == other.value &&
42
+ specified == other.specified
43
+ end
44
+
45
+ def hash
46
+ [local_name, value, specified].hash
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ module RSAC
2
+ module Conditions
3
+ class BeginHyphenCondition < AttributeCondition
4
+
5
+ def initialize(local_name, value)
6
+ super(local_name, value, true, :SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION)
7
+ end
8
+
9
+ def to_css
10
+ "[#{local_name}|=#{value}]"
11
+ end
12
+
13
+ def to_xpath
14
+ "[contains(@#{local_name}, '#{value}')]"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module RSAC
2
+ module Conditions
3
+ class ClassCondition < AttributeCondition
4
+
5
+ def initialize(klass)
6
+ super("class", klass, true, :SAC_CLASS_CONDITION)
7
+ end
8
+
9
+ def to_css
10
+ ".#{value}"
11
+ end
12
+
13
+ def to_xpath
14
+ "[contains(@#{local_name}, '#{value}')]"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ module RSAC
2
+ module Conditions
3
+ class CombinatorCondition < Condition
4
+ attr_accessor :first_condition, :second_condition
5
+ alias :first :first_condition
6
+ alias :second :second_condition
7
+
8
+ def initialize(first, second)
9
+ super(:SAC_AND_CONDITION)
10
+
11
+ @first_condition = first
12
+ @second_condition = second
13
+ end
14
+
15
+ def to_css
16
+ "#{first.to_css}#{second.to_css}"
17
+ end
18
+
19
+ def to_xpath
20
+ "#{first.to_xpath}#{second.to_xpath}"
21
+ end
22
+
23
+ def specificity
24
+ first.specificity.zip(second.specificity).map { |x,y| x + y }
25
+ end
26
+
27
+ def ==(other)
28
+ super && first == other.first && second == other.second
29
+ end
30
+
31
+ def hash
32
+ [first, second].hash
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module RSAC
2
+ module Conditions
3
+ class Condition
4
+
5
+ attr_accessor :condition_type
6
+
7
+ def initialize(condition_type)
8
+ @condition_type = condition_type
9
+ end
10
+
11
+ def ==(other)
12
+ self.class === other && condition_type == other.condition_type
13
+ end
14
+
15
+ def hash
16
+ condition_type.hash
17
+ end
18
+
19
+ def eql?(other)
20
+ self == other
21
+ end
22
+
23
+ def to_css
24
+ nil
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module RSAC
2
+ module Conditions
3
+ class IDCondition < AttributeCondition
4
+
5
+ def initialize(id)
6
+ id = id[1..id.size] if id[0] == ?#
7
+ super("id", id, true, :SAC_ID_CONDITION)
8
+ end
9
+
10
+ def to_css
11
+ "##{value}"
12
+ end
13
+
14
+ def to_xpath
15
+ "[@id='#{value}']"
16
+ end
17
+
18
+ def specificity
19
+ [0, 1, 0, 0]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module RSAC
2
+ module Conditions
3
+ class OneOfCondition < AttributeCondition
4
+
5
+ def initialize(local_name, value)
6
+ super(local_name, value, true, :SAC_ONE_OF_ATTRIBUTE_CONDITION)
7
+ end
8
+
9
+ def to_css
10
+ "[#{local_name}~=#{value}]"
11
+ end
12
+
13
+ def to_xpath
14
+ "[contains(@#{local_name}, '#{value}')]"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module RSAC
2
+ module Conditions
3
+ class PseudoClassCondition < AttributeCondition
4
+ def initialize(pseudo_class)
5
+ super(nil, pseudo_class, false, :SAC_PSEUDO_CLASS_CONDITION)
6
+ end
7
+
8
+ def to_css
9
+ ":#{value}"
10
+ end
11
+
12
+ def to_xpath
13
+ end
14
+
15
+ def specificity
16
+ [0, 0, 0, 0]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ require "antisamy/csspool/rsac/sac/conditions/condition"
2
+
3
+ %w(attribute begin_hyphen class combinator id one_of pseudo_class).each do |type|
4
+ require "antisamy/csspool/rsac/sac/conditions/#{type}_condition"
5
+ end