| module Liquid |
| # Holds variables. Variables are only loaded "just in time" |
| # and are not evaluated as part of the render stage |
| # |
| # {{ monkey }} |
| # {{ user.name }} |
| # |
| # Variables can be combined with filters: |
| # |
| # {{ user | link }} |
| # |
| class Variable |
| FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o |
| attr_accessor :filters, :name, :line_number |
| attr_reader :parse_context |
| alias_method :options, :parse_context |
| include ParserSwitching |
| |
| def initialize(markup, parse_context) |
| @markup = markup |
| @name = nil |
| @parse_context = parse_context |
| @line_number = parse_context.line_number |
| |
| parse_with_selected_parser(markup) |
| end |
| |
| def raw |
| @markup |
| end |
| |
| def markup_context(markup) |
| "in \"{{#{markup}}}\"" |
| end |
| |
| def lax_parse(markup) |
| @filters = [] |
| return unless markup =~ /(#{QuotedFragment})(.*)/om |
| |
| name_markup = $1 |
| filter_markup = $2 |
| @name = Expression.parse(name_markup) |
| if filter_markup =~ /#{FilterSeparator}\s*(.*)/om |
| filters = $1.scan(FilterParser) |
| filters.each do |f| |
| next unless f =~ /\w+/ |
| filtername = Regexp.last_match(0) |
| filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten |
| @filters << parse_filter_expressions(filtername, filterargs) |
| end |
| end |
| end |
| |
| def strict_parse(markup) |
| @filters = [] |
| p = Parser.new(markup) |
| |
| @name = Expression.parse(p.expression) |
| while p.consume?(:pipe) |
| filtername = p.consume(:id) |
| filterargs = p.consume?(:colon) ? parse_filterargs(p) : [] |
| @filters << parse_filter_expressions(filtername, filterargs) |
| end |
| p.consume(:end_of_string) |
| end |
| |
| def parse_filterargs(p) |
| # first argument |
| filterargs = [p.argument] |
| # followed by comma separated others |
| filterargs << p.argument while p.consume?(:comma) |
| filterargs |
| end |
| |
| def render(context) |
| obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)| |
| filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs) |
| context.invoke(filter_name, output, *filter_args) |
| end |
| |
| obj = context.apply_global_filter(obj) |
| |
| taint_check(context, obj) |
| |
| obj |
| end |
| |
| private |
| |
| def parse_filter_expressions(filter_name, unparsed_args) |
| filter_args = [] |
| keyword_args = {} |
| unparsed_args.each do |a| |
| if matches = a.match(/\A#{TagAttributes}\z/o) |
| keyword_args[matches[1]] = Expression.parse(matches[2]) |
| else |
| filter_args << Expression.parse(a) |
| end |
| end |
| result = [filter_name, filter_args] |
| result << keyword_args unless keyword_args.empty? |
| result |
| end |
| |
| def evaluate_filter_expressions(context, filter_args, filter_kwargs) |
| parsed_args = filter_args.map{ |expr| context.evaluate(expr) } |
| if filter_kwargs |
| parsed_kwargs = {} |
| filter_kwargs.each do |key, expr| |
| parsed_kwargs[key] = context.evaluate(expr) |
| end |
| parsed_args << parsed_kwargs |
| end |
| parsed_args |
| end |
| |
| def taint_check(context, obj) |
| return unless obj.tainted? |
| return if Template.taint_mode == :lax |
| |
| @markup =~ QuotedFragment |
| name = Regexp.last_match(0) |
| |
| error = TaintedError.new("variable '#{name}' is tainted and was not escaped") |
| error.line_number = line_number |
| error.template_name = context.template_name |
| |
| case Template.taint_mode |
| when :warn |
| context.warnings << error |
| when :error |
| raise error |
| end |
| end |
| end |
| end |