From b76eedc51337277885291c37acff5b5163a29c75 Mon Sep 17 00:00:00 2001 From: "Shota Fukumori (sora_h)" Date: Mon, 7 Sep 2015 20:40:05 +0900 Subject: [PATCH] Unescape correctly ugly but fixes #207 --- lib/dotenv/parser.rb | 10 +++++++--- lib/dotenv/substitutions/unescape.rb | 18 ++++++++++++++++++ lib/dotenv/substitutions/variable.rb | 16 ++++++++-------- spec/dotenv/parser_spec.rb | 7 +++++++ 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 lib/dotenv/substitutions/unescape.rb diff --git a/lib/dotenv/parser.rb b/lib/dotenv/parser.rb index 5170f86b..625e8a02 100644 --- a/lib/dotenv/parser.rb +++ b/lib/dotenv/parser.rb @@ -1,5 +1,6 @@ require "dotenv/substitutions/variable" require "dotenv/substitutions/command" if RUBY_VERSION > "1.8.7" +require "dotenv/substitutions/unescape" module Dotenv class FormatError < SyntaxError; end @@ -8,8 +9,11 @@ class FormatError < SyntaxError; end # and stored in the Environment. It allows for variable substitutions and # exporting of variables. class Parser - @substitutions = - Substitutions.constants.map { |const| Substitutions.const_get(const) } + @substitutions = [ + Substitutions::Variable, + Substitutions.const_defined?(:Command) ? Substitutions::Command : nil, + Substitutions::Unescape + ].compact LINE = / \A @@ -79,7 +83,7 @@ def parse_value(value) end def unescape_characters(value) - value.gsub(/\\([^$])/, '\1') + value.gsub(/([^\\]|^)\\([^$\\])/, '\1\2') end def expand_newlines(value) diff --git a/lib/dotenv/substitutions/unescape.rb b/lib/dotenv/substitutions/unescape.rb new file mode 100644 index 00000000..53c74580 --- /dev/null +++ b/lib/dotenv/substitutions/unescape.rb @@ -0,0 +1,18 @@ +require "English" + +module Dotenv + module Substitutions + # Substitute "\$" to "$" + # + module Unescape + class << self + ESCAPED_DOLLARS = /\\\$/ + ESCAPED_BACKSLASHES = /\\\\/ + + def call(value, _env) + value.gsub(ESCAPED_DOLLARS, '$').gsub(ESCAPED_BACKSLASHES, "\\") + end + end + end + end +end diff --git a/lib/dotenv/substitutions/variable.rb b/lib/dotenv/substitutions/variable.rb index bababacc..ca73bc01 100644 --- a/lib/dotenv/substitutions/variable.rb +++ b/lib/dotenv/substitutions/variable.rb @@ -10,21 +10,21 @@ module Substitutions module Variable class << self VARIABLE = / - (\\)? # is it escaped with a backslash? - (\$) # literal $ - \{? # allow brace wrapping - ([A-Z0-9_]+) # match the variable - \}? # closing brace + (([^\\]|^)\\)? # is it escaped with a backslash but not escaped backslash? + (\$) # literal $ + \{? # allow brace wrapping + ([A-Z0-9_]+) # match the variable + \}? # closing brace /xi def call(value, env) value.gsub(VARIABLE) do |variable| match = $LAST_MATCH_INFO - if match[1] == '\\' - variable[1..-1] + if match[1] + match[2] + variable[match[1].size .. -1] else - env.fetch(match[3]) { ENV[match[3]] } + env.fetch(match[4]) { ENV[match[4]] } end end end diff --git a/spec/dotenv/parser_spec.rb b/spec/dotenv/parser_spec.rb index e0345fbe..aad9900d 100644 --- a/spec/dotenv/parser_spec.rb +++ b/spec/dotenv/parser_spec.rb @@ -64,6 +64,13 @@ def env(string) .to eql("FOO" => "test", "BAR" => "foo${FOO} test") end + it "unescapes correctly" do + expect(env("FOO=\"foo\\$.test\"")).to eql("FOO" => "foo$.test") + expect(env("FOO=\"foo\\$\"")).to eql("FOO" => "foo$") + expect(env("FOO=\"foo\\$.\"")).to eql("FOO" => "foo$.") + expect(env("BAR=bar\nFOO=\"foo\\\\$BAR\"")).to eql("BAR" => "bar", "FOO" => "foo\\bar") + end + it "parses yaml style options" do expect(env("OPTION_A: 1")).to eql("OPTION_A" => "1") end