Class: Sxn::Templates::TemplateProcessor
- Inherits:
-
Object
- Object
- Sxn::Templates::TemplateProcessor
- Defined in:
- lib/sxn/templates/template_processor.rb
Overview
TemplateProcessor provides secure, sandboxed template processing using Liquid. It ensures that templates cannot execute arbitrary code or access the filesystem.
Features:
-
Whitelisted variables only
-
No arbitrary code execution
-
Support for nested variable access (session.name, git.branch)
-
Built-in filters (upcase, downcase, join, etc.)
-
Template validation before processing
Example:
processor = TemplateProcessor.new
variables = { session: { name: "test" }, git: { branch: "main" } }
result = processor.process("Hello {{session.name}} on {{git.branch}}", variables)
# => "Hello test on main"
Constant Summary collapse
- MAX_TEMPLATE_SIZE =
Maximum template size in bytes to prevent memory exhaustion
1_048_576- MAX_RENDER_TIME =
Maximum rendering time in seconds to prevent infinite loops
10- ALLOWED_FILTERS =
Allowed Liquid filters for security
%w[ upcase downcase capitalize strip lstrip rstrip size length first last join split sort sort_natural reverse uniq compact date default escape escape_once truncate truncatewords replace replace_first remove remove_first plus minus times divided_by modulo abs ceil floor round at_least at_most ].freeze
Instance Method Summary collapse
-
#extract_variables(template_content) ⇒ Array<String>
Extract variables referenced in a template.
-
#initialize ⇒ TemplateProcessor
constructor
A new instance of TemplateProcessor.
-
#process(template_content, variables = {}, options = {}) ⇒ String
Process a template string with the given variables.
-
#process_file(template_path, variables = {}, options = {}) ⇒ String
Process a template file with the given variables.
-
#validate_syntax(template_content) ⇒ Boolean
(also: #validate_template)
Validate template syntax without processing.
Constructor Details
#initialize ⇒ TemplateProcessor
Returns a new instance of TemplateProcessor.
50 51 52 |
# File 'lib/sxn/templates/template_processor.rb', line 50 def initialize create_secure_liquid_environment end |
Instance Method Details
#extract_variables(template_content) ⇒ Array<String>
Extract variables referenced in a template
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 |
# File 'lib/sxn/templates/template_processor.rb', line 121 def extract_variables(template_content) variables = Set.new loop_variables = Set.new # Extract variables from {% if/unless variable %} expressions template_content.scan(/\{%\s*(?:if|unless)\s+(\w+)(?:\.\w+)*.*?%\}/) do |match| variables.add(match[0]) end # Extract collection variables from {% for item in collection %} expressions template_content.scan(/\{%\s*for\s+(\w+)\s+in\s+(\w+)(?:\.\w+)*.*?%\}/) do |loop_var, collection_var| loop_variables.add(loop_var) variables.add(collection_var) end # Extract variables from {{ variable }} expressions, excluding loop variables # But only from outside control blocks content_outside_blocks = template_content.dup # Remove content inside control blocks to avoid extracting variables from inside conditionals content_outside_blocks.gsub!(/\{%\s*if\s+.*?\{%\s*endif\s*%\}/m, "") content_outside_blocks.gsub!(/\{%\s*unless\s+.*?\{%\s*endunless\s*%\}/m, "") content_outside_blocks.gsub!(/\{%\s*for\s+.*?\{%\s*endfor\s*%\}/m, "") content_outside_blocks.scan(/\{\{\s*(\w+)(?:\.\w+)*.*?\}\}/) do |match| var_name = match[0] variables.add(var_name) unless loop_variables.include?(var_name) end variables.to_a.sort end |
#process(template_content, variables = {}, options = {}) ⇒ String
Process a template string with the given variables
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/sxn/templates/template_processor.rb', line 66 def process(template_content, variables = {}, = {}) = { strict: true, validate: true }.merge() validate_template_size!(template_content) # Sanitize and whitelist variables sanitized_variables = sanitize_variables(variables) # Parse template with syntax validation template = parse_template(template_content, validate: [:validate]) # Render with timeout protection render_with_timeout(template, sanitized_variables, ) rescue Liquid::SyntaxError => e raise Errors::TemplateSyntaxError, "Template syntax error: #{e.}" rescue Errors::TemplateTooLargeError, Errors::TemplateTimeoutError, Errors::TemplateRenderError => e # Re-raise specific template errors as-is raise e rescue StandardError => e raise Errors::TemplateProcessingError, "Template processing failed: #{e.}" end |
#process_file(template_path, variables = {}, options = {}) ⇒ String
Process a template file with the given variables
95 96 97 98 99 100 101 102 |
# File 'lib/sxn/templates/template_processor.rb', line 95 def process_file(template_path, variables = {}, = {}) template_path = Pathname.new(template_path) raise Errors::TemplateNotFoundError, "Template file not found: #{template_path}" unless template_path.exist? template_content = template_path.read process(template_content, variables, ) end |
#validate_syntax(template_content) ⇒ Boolean Also known as: validate_template
Validate template syntax without processing
109 110 111 112 113 114 115 |
# File 'lib/sxn/templates/template_processor.rb', line 109 def validate_syntax(template_content) validate_template_size!(template_content) parse_template(template_content, validate: true) true rescue Liquid::SyntaxError => e raise Errors::TemplateSyntaxError, "Template syntax error: #{e.}" end |