orb_template 0.1.3 → 0.2.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -0
- data/README.md +20 -4
- data/docs/2026-03-12-security-analysis.md +715 -0
- data/lib/orb/ast/abstract_node.rb +12 -2
- data/lib/orb/ast/control_expression_node.rb +4 -2
- data/lib/orb/ast/printing_expression_node.rb +4 -2
- data/lib/orb/patterns.rb +13 -1
- data/lib/orb/temple/attributes_compiler.rb +42 -4
- data/lib/orb/temple/compiler.rb +43 -55
- data/lib/orb/temple/engine.rb +4 -1
- data/lib/orb/temple/filters.rb +56 -11
- data/lib/orb/temple/identity.rb +5 -1
- data/lib/orb/token.rb +1 -1
- data/lib/orb/tokenizer2.rb +50 -33
- data/lib/orb/version.rb +1 -1
- metadata +3 -2
data/lib/orb/tokenizer2.rb
CHANGED
|
@@ -18,6 +18,12 @@ module ORB
|
|
|
18
18
|
# Tags that should be ignored
|
|
19
19
|
IGNORED_BODY_TAGS = %w[script style].freeze
|
|
20
20
|
|
|
21
|
+
# Maximum allowed brace nesting depth to prevent memory exhaustion
|
|
22
|
+
MAX_BRACE_DEPTH = 100
|
|
23
|
+
|
|
24
|
+
# Maximum allowed template source size in bytes (2MB)
|
|
25
|
+
MAX_TEMPLATE_SIZE = 2 * 1024 * 1024
|
|
26
|
+
|
|
21
27
|
# Tags that are self-closing by HTML5 spec
|
|
22
28
|
VOID_ELEMENTS = %w[area base br col command embed hr img input keygen link meta param source track wbr].freeze
|
|
23
29
|
|
|
@@ -28,7 +34,6 @@ module ORB
|
|
|
28
34
|
@raise_errors = options.fetch(:raise_errors, true)
|
|
29
35
|
|
|
30
36
|
# Streaming Tokenizer State
|
|
31
|
-
@cursor = 0
|
|
32
37
|
@column = 1
|
|
33
38
|
@line = 1
|
|
34
39
|
@errors = []
|
|
@@ -36,11 +41,15 @@ module ORB
|
|
|
36
41
|
@attributes = []
|
|
37
42
|
@braces = []
|
|
38
43
|
@state = :initial
|
|
39
|
-
@buffer =
|
|
44
|
+
@buffer = +''
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
# Main Entry
|
|
43
48
|
def tokenize
|
|
49
|
+
if @source.string.bytesize > MAX_TEMPLATE_SIZE
|
|
50
|
+
raise ORB::SyntaxError.new("Template exceeds maximum size (#{MAX_TEMPLATE_SIZE} bytes)", 0)
|
|
51
|
+
end
|
|
52
|
+
|
|
44
53
|
next_token until @source.eos?
|
|
45
54
|
|
|
46
55
|
# Consume remaining buffer
|
|
@@ -56,11 +65,6 @@ module ORB
|
|
|
56
65
|
|
|
57
66
|
# Dispatcher based on current state
|
|
58
67
|
def next_token
|
|
59
|
-
# Detect infinite loop
|
|
60
|
-
# if @previous_cursor == @cursor && @previous_state == @state
|
|
61
|
-
# raise "Internal Error: detected infinite loop in :#{@state}"
|
|
62
|
-
# end
|
|
63
|
-
|
|
64
68
|
# Dispatch to state handler
|
|
65
69
|
send(:"next_in_#{@state}")
|
|
66
70
|
end
|
|
@@ -114,7 +118,7 @@ module ORB
|
|
|
114
118
|
add_token(:tag_open, nil)
|
|
115
119
|
move_by_matched
|
|
116
120
|
transition_to(:tag_open)
|
|
117
|
-
elsif @source.scan(OTHER)
|
|
121
|
+
elsif @source.scan(INITIAL_TEXT) || @source.scan(OTHER)
|
|
118
122
|
buffer_matched
|
|
119
123
|
move_by_matched
|
|
120
124
|
else
|
|
@@ -233,7 +237,7 @@ module ORB
|
|
|
233
237
|
current_attribute[2] = attribute_value
|
|
234
238
|
move_by_matched
|
|
235
239
|
transition_to(:tag_open_content)
|
|
236
|
-
elsif @source.scan(
|
|
240
|
+
elsif @source.scan(SINGLE_QUOTED_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
237
241
|
buffer_matched
|
|
238
242
|
move_by_matched
|
|
239
243
|
else
|
|
@@ -249,7 +253,7 @@ module ORB
|
|
|
249
253
|
current_attribute[2] = attribute_value
|
|
250
254
|
move_by_matched
|
|
251
255
|
transition_to(:tag_open_content)
|
|
252
|
-
elsif @source.scan(
|
|
256
|
+
elsif @source.scan(DOUBLE_QUOTED_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
253
257
|
buffer_matched
|
|
254
258
|
move_by_matched
|
|
255
259
|
else
|
|
@@ -260,7 +264,7 @@ module ORB
|
|
|
260
264
|
# Read next token in :attribute_value_expression state
|
|
261
265
|
def next_in_attribute_value_expression
|
|
262
266
|
if @source.scan(BRACE_OPEN)
|
|
263
|
-
|
|
267
|
+
push_brace
|
|
264
268
|
buffer_matched
|
|
265
269
|
move_by_matched
|
|
266
270
|
elsif @source.scan(BRACE_CLOSE)
|
|
@@ -275,7 +279,7 @@ module ORB
|
|
|
275
279
|
move_by_matched
|
|
276
280
|
transition_to(:tag_open_content)
|
|
277
281
|
end
|
|
278
|
-
elsif @source.scan(
|
|
282
|
+
elsif @source.scan(EXPRESSION_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
279
283
|
buffer_matched
|
|
280
284
|
move_by_matched
|
|
281
285
|
else
|
|
@@ -286,7 +290,7 @@ module ORB
|
|
|
286
290
|
# Read next token in :splat_attribute_expression state
|
|
287
291
|
def next_in_splat_attribute_expression
|
|
288
292
|
if @source.scan(BRACE_OPEN)
|
|
289
|
-
|
|
293
|
+
push_brace
|
|
290
294
|
buffer_matched
|
|
291
295
|
move_by_matched
|
|
292
296
|
elsif @source.scan(BRACE_CLOSE)
|
|
@@ -301,7 +305,7 @@ module ORB
|
|
|
301
305
|
clear_braces
|
|
302
306
|
transition_to(:tag_open_content)
|
|
303
307
|
end
|
|
304
|
-
elsif @source.scan(
|
|
308
|
+
elsif @source.scan(EXPRESSION_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
305
309
|
buffer_matched
|
|
306
310
|
move_by_matched
|
|
307
311
|
else
|
|
@@ -346,7 +350,7 @@ module ORB
|
|
|
346
350
|
update_current_token(text)
|
|
347
351
|
move_by_matched
|
|
348
352
|
transition_to(:initial)
|
|
349
|
-
elsif @source.scan(
|
|
353
|
+
elsif @source.scan(COMMENT_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
350
354
|
buffer_matched
|
|
351
355
|
move_by_matched
|
|
352
356
|
else
|
|
@@ -361,7 +365,7 @@ module ORB
|
|
|
361
365
|
update_current_token(text)
|
|
362
366
|
move_by_matched
|
|
363
367
|
transition_to(:initial)
|
|
364
|
-
elsif @source.scan(
|
|
368
|
+
elsif @source.scan(COMMENT_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
365
369
|
buffer_matched
|
|
366
370
|
move_by_matched
|
|
367
371
|
else
|
|
@@ -385,7 +389,7 @@ module ORB
|
|
|
385
389
|
# Read block expression until closing brace
|
|
386
390
|
def next_in_block_open_content
|
|
387
391
|
if @source.scan(BRACE_OPEN)
|
|
388
|
-
|
|
392
|
+
push_brace
|
|
389
393
|
buffer_matched
|
|
390
394
|
move_by_matched
|
|
391
395
|
elsif @source.scan(BRACE_CLOSE)
|
|
@@ -399,7 +403,7 @@ module ORB
|
|
|
399
403
|
buffer_matched
|
|
400
404
|
move_by_matched
|
|
401
405
|
end
|
|
402
|
-
elsif @source.scan(
|
|
406
|
+
elsif @source.scan(BLOCK_CONTENT_TEXT) || @source.scan(OTHER)
|
|
403
407
|
buffer_matched
|
|
404
408
|
move_by_matched
|
|
405
409
|
end
|
|
@@ -437,7 +441,7 @@ module ORB
|
|
|
437
441
|
move_by_matched
|
|
438
442
|
transition_to(:initial)
|
|
439
443
|
elsif @source.scan(BRACE_OPEN)
|
|
440
|
-
|
|
444
|
+
push_brace
|
|
441
445
|
buffer_matched
|
|
442
446
|
move_by_matched
|
|
443
447
|
elsif @source.scan(BRACE_CLOSE)
|
|
@@ -462,7 +466,7 @@ module ORB
|
|
|
462
466
|
move_by_matched
|
|
463
467
|
transition_to(:initial)
|
|
464
468
|
elsif @source.scan(BRACE_OPEN)
|
|
465
|
-
|
|
469
|
+
push_brace
|
|
466
470
|
buffer_matched
|
|
467
471
|
move_by_matched
|
|
468
472
|
elsif @source.scan(BRACE_CLOSE)
|
|
@@ -498,7 +502,7 @@ module ORB
|
|
|
498
502
|
buffer(tmp)
|
|
499
503
|
move_by(tmp)
|
|
500
504
|
end
|
|
501
|
-
elsif @source.scan(
|
|
505
|
+
elsif @source.scan(VERBATIM_TEXT) || @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(OTHER)
|
|
502
506
|
buffer_matched
|
|
503
507
|
move_by_matched
|
|
504
508
|
end
|
|
@@ -537,20 +541,29 @@ module ORB
|
|
|
537
541
|
@braces = []
|
|
538
542
|
end
|
|
539
543
|
|
|
544
|
+
def push_brace
|
|
545
|
+
if @braces.length >= MAX_BRACE_DEPTH
|
|
546
|
+
raise ORB::SyntaxError.new("Maximum brace nesting depth (#{MAX_BRACE_DEPTH}) exceeded", @line)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
@braces << "{"
|
|
550
|
+
end
|
|
551
|
+
|
|
540
552
|
# Moves the cursor
|
|
541
553
|
def move(line, column)
|
|
542
554
|
@line = line
|
|
543
555
|
@column = column
|
|
544
556
|
end
|
|
545
557
|
|
|
558
|
+
# Update line/column tracking from a matched string.
|
|
559
|
+
# Uses String#count and String#rindex instead of re-scanning with StringScanner.
|
|
546
560
|
def move_by(str)
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
end
|
|
561
|
+
newlines = str.count("\n")
|
|
562
|
+
if newlines.positive?
|
|
563
|
+
@line += newlines
|
|
564
|
+
@column = str.length - str.rindex("\n")
|
|
565
|
+
else
|
|
566
|
+
@column += str.length
|
|
554
567
|
end
|
|
555
568
|
end
|
|
556
569
|
|
|
@@ -565,7 +578,11 @@ module ORB
|
|
|
565
578
|
|
|
566
579
|
# Create a new token
|
|
567
580
|
def create_token(type, value, meta = {})
|
|
568
|
-
|
|
581
|
+
if meta.empty?
|
|
582
|
+
Token.new(type, value, { line: @line, column: @column })
|
|
583
|
+
else
|
|
584
|
+
Token.new(type, value, { line: @line, column: @column }.merge!(meta))
|
|
585
|
+
end
|
|
569
586
|
end
|
|
570
587
|
|
|
571
588
|
# Create a token and add it to the token list
|
|
@@ -586,18 +603,18 @@ module ORB
|
|
|
586
603
|
|
|
587
604
|
# Read the buffer to a string
|
|
588
605
|
def read_buffer
|
|
589
|
-
@buffer.
|
|
606
|
+
@buffer.dup
|
|
590
607
|
end
|
|
591
608
|
|
|
592
609
|
# Clear the buffer
|
|
593
610
|
def clear_buffer
|
|
594
|
-
@buffer =
|
|
611
|
+
@buffer = +''
|
|
595
612
|
end
|
|
596
613
|
|
|
597
614
|
# Read the buffer to a string and clear it
|
|
598
615
|
def consume_buffer
|
|
599
|
-
str =
|
|
600
|
-
|
|
616
|
+
str = @buffer
|
|
617
|
+
@buffer = +''
|
|
601
618
|
str
|
|
602
619
|
end
|
|
603
620
|
|
data/lib/orb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: orb_template
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- KUY.io Inc.
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- Makefile
|
|
40
40
|
- README.md
|
|
41
41
|
- Rakefile
|
|
42
|
+
- docs/2026-03-12-security-analysis.md
|
|
42
43
|
- lib/orb.rb
|
|
43
44
|
- lib/orb/ast.rb
|
|
44
45
|
- lib/orb/ast/abstract_node.rb
|
|
@@ -98,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
98
99
|
- !ruby/object:Gem::Version
|
|
99
100
|
version: '0'
|
|
100
101
|
requirements: []
|
|
101
|
-
rubygems_version:
|
|
102
|
+
rubygems_version: 4.0.6
|
|
102
103
|
specification_version: 4
|
|
103
104
|
summary: The ORB template language for Ruby.
|
|
104
105
|
test_files: []
|