Module: Phlex::SGML::Attributes

Extended by:
Attributes
Included in:
Attributes
Defined in:
lib/phlex/sgml/attributes.rb

Constant Summary collapse

UNSAFE_ATTRIBUTES =
Set.new(%w[srcdoc sandbox http-equiv]).freeze
REF_ATTRIBUTES =
Set.new(%w[href src action formaction lowsrc dynsrc background ping xlinkhref]).freeze
NAMED_CHARACTER_REFERENCES =
{
	"colon" => ":",
	"tab" => "\t",
	"newline" => "\n",
}.freeze
UNSAFE_ATTRIBUTE_NAME_CHARS =
%r([<>&"'/=\s\x00])

Instance Method Summary collapse

Instance Method Details

#decode_html_character_references(value) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/phlex/sgml/attributes.rb', line 171

def decode_html_character_references(value)
	value
		.gsub(/&#x([0-9a-f]+);?/i) {
			begin
				[$1.to_i(16)].pack("U*")
			rescue
				""
			end
		}
		.gsub(/&#(\d+);?/) {
			begin
				[$1.to_i].pack("U*")
			rescue
				""
			end
		}
		.gsub(/&([a-z][a-z0-9]+);?/i) {
			NAMED_CHARACTER_REFERENCES[$1.downcase] || ""
		}
end

#generate_attributes(attributes, buffer = +"")) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
# File 'lib/phlex/sgml/attributes.rb', line 15

def generate_attributes(attributes, buffer = +"")
	attributes.each do |k, v|
		next unless v

		name = case k
			when String then k
			when Symbol then k.name.tr("_", "-")
			else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols.")
		end

		value = case v
		when true
			true
		when String
			v.gsub('"', "&quot;")
		when Symbol
			v.name.tr("_", "-").gsub('"', "&quot;")
		when Integer, Float
			v.to_s
		when Date
			v.iso8601
		when Time
			v.respond_to?(:iso8601) ? v.iso8601 : v.strftime("%Y-%m-%dT%H:%M:%S%:z")
		when Hash
			case k
			when :style
				generate_styles(v).gsub('"', "&quot;")
			else
				generate_nested_attributes(v, "#{name}-", buffer)
			end
		when Array
			case k
			when :style
				generate_styles(v).gsub('"', "&quot;")
			else
				generate_nested_tokens(v)
			end
		when Set
			case k
			when :style
				generate_styles(v).gsub('"', "&quot;")
			else
				generate_nested_tokens(v.to_a)
			end
		when Phlex::SGML::SafeObject
			v.to_s.gsub('"', "&quot;")
		else
			if v.respond_to?(:to_h)
				generate_nested_attributes(v.to_h, "#{name}-", buffer)
			else
				raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
			end
		end

		lower_name = name.downcase

		unless Phlex::SGML::SafeObject === v
			normalized_name = lower_name.delete("^a-z-")

			if value != true && REF_ATTRIBUTES.include?(normalized_name)
				case value
				when String
					decoded_value = decode_html_character_references(value)

					if decoded_value.downcase.delete("^a-z:").start_with?("javascript:")
						# We just ignore these because they were likely not specified by the developer.
						next
					end
				else
					raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
				end
			end

			if normalized_name.bytesize > 2 && normalized_name.start_with?("on") && !normalized_name.include?("-")
				raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
			end

			if UNSAFE_ATTRIBUTES.include?(normalized_name)
				raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
			end
		end

		if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
			raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
		end

		if lower_name.to_sym == :id && k != :id
			raise Phlex::ArgumentError.new(":id attribute should only be passed as a lowercase symbol.")
		end

		case value
		when true
			buffer << " " << name
		when String
			buffer << " " << name << '="' << value << '"'
		end
	end

	buffer
end

#generate_nested_attributes(attributes, base_name, buffer = +"")) ⇒ Object

Provides the nested-attributes case for serializing out attributes. This allows us to skip many of the checks the __attributes__ method must perform.



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
# File 'lib/phlex/sgml/attributes.rb', line 118

def generate_nested_attributes(attributes, base_name, buffer = +"")
	attributes.each do |k, v|
		next unless v

		if (root_key = (:_ == k))
			name = ""
			original_base_name = base_name
			base_name = base_name.delete_suffix("-")
		else
			name = case k
				when String then k
				when Symbol then k.name.tr("_", "-")
				else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
			end

			if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
				raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
			end
		end

		case v
		when true
			buffer << " " << base_name << name
		when String
			buffer << " " << base_name << name << '="' << v.gsub('"', "&quot;") << '"'
		when Symbol
			buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', "&quot;") << '"'
		when Integer, Float
			buffer << " " << base_name << name << '="' << v.to_s << '"'
		when Hash
			generate_nested_attributes(v, "#{base_name}#{name}-", buffer)
		when Array
			if (value = generate_nested_tokens(v))
				buffer << " " << base_name << name << '="' << value << '"'
			end
		when Set
			if (value = generate_nested_tokens(v.to_a))
				buffer << " " << base_name << name << '="' << value << '"'
			end
		when Phlex::SGML::SafeObject
			buffer << " " << base_name << name << '="' << v.to_s.gsub('"', "&quot;") << '"'
		else
			raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.")
		end

		if root_key
			base_name = original_base_name
		end

		buffer
	end
end

#generate_nested_tokens(tokens, sep = " ", gsub_from = nil, gsub_to = "") ⇒ Object



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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/phlex/sgml/attributes.rb', line 192

def generate_nested_tokens(tokens, sep = " ", gsub_from = nil, gsub_to	= "")
	buffer = +""

	i, length = 0, tokens.length

	while i < length
		token = tokens[i]

		case token
		when String
			token = token.gsub(gsub_from, gsub_to) if gsub_from
			if i > 0
				buffer << sep << token
			else
				buffer << token
			end
		when Symbol
			if i > 0
				buffer << sep << token.name.tr("_", "-")
			else
				buffer << token.name.tr("_", "-")
			end
		when Integer, Float, Phlex::SGML::SafeObject
			if i > 0
				buffer << sep << token.to_s
			else
				buffer << token.to_s
			end
		when Array
			if token.length > 0 && (value = generate_nested_tokens(token, sep, gsub_from, gsub_to))
				if i > 0
					buffer << sep << value
				else
					buffer << value
				end
			end
		when Set
			if token.length > 0 && (value = generate_nested_tokens(token.to_a, sep, gsub_from, gsub_to))
				if i > 0
					buffer << sep << value
				else
					buffer << value
				end
			end
		when nil
			# Do nothing
		else
			raise Phlex::ArgumentError.new("Invalid token type: #{token.class}.")
		end

		i += 1
	end

	return if buffer.empty?

	buffer.gsub('"', "&quot;")
end

#generate_styles(styles) ⇒ Object

The result is unsafe so should be escaped.



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
308
309
310
311
312
# File 'lib/phlex/sgml/attributes.rb', line 251

def generate_styles(styles)
	case styles
	when Array, Set
		styles.filter_map do |s|
			case s
			when String
				if s == "" || s.end_with?(";")
					s
				else
					"#{s};"
				end
			when Phlex::SGML::SafeObject
				value = s.to_s
				value.end_with?(";") ? value : "#{value};"
			when Hash
				next generate_styles(s)
			when nil
				next nil
			else
				raise Phlex::ArgumentError.new("Invalid style: #{s.inspect}.")
			end
		end.join(" ")
	when Hash
		buffer = +""
		i = 0
		styles.each do |k, v|
			prop = case k
			when String
				k
			when Symbol
				k.name.tr("_", "-")
			else
				raise Phlex::ArgumentError.new("Style keys should be Strings or Symbols.")
			end

			value = case v
			when String
				v
			when Symbol
				v.name.tr("_", "-")
			when Integer, Float, Phlex::SGML::SafeObject
				v.to_s
			when nil
				nil
			else
				raise Phlex::ArgumentError.new("Invalid style value: #{v.inspect}")
			end

			if value
				if i == 0
					buffer << prop << ": " << value << ";"
				else
					buffer << " " << prop << ": " << value << ";"
				end
			end

			i += 1
		end

		buffer
	end
end