diff --git a/lib/kamal/env_file.rb b/lib/kamal/env_file.rb index 0c9f23414..91f10b594 100644 --- a/lib/kamal/env_file.rb +++ b/lib/kamal/env_file.rb @@ -24,6 +24,13 @@ def docker_env_file_line(key, value) # Escape a value to make it safe to dump in a docker file. def escape_docker_env_file_value(value) + # keep non-ascii(UTF-8) characters as is + value.to_s.scan(/[\x00-\x7F]+|[^\x00-\x7F]+/).map do |part| + part.ascii_only? ? escape_docker_env_file_ascii_value(part) : part + end.join + end + + def escape_docker_env_file_ascii_value(value) # Doublequotes are treated literally in docker env files # so remove leading and trailing ones and unescape any others value.to_s.dump[1..-2].gsub(/\\"/, "\"") diff --git a/lib/kamal/utils.rb b/lib/kamal/utils.rb index af35edea1..f095fd60e 100644 --- a/lib/kamal/utils.rb +++ b/lib/kamal/utils.rb @@ -18,10 +18,11 @@ def argumentize(argument, attributes, sensitive: false) # Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option. def optionize(args, with: nil) + args = flatten_args(args).reject { |(_key, value)| value.nil? } options = if with - flatten_args(args).collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" } + args.collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" } else - flatten_args(args).collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] } + args.collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] } end options.flatten.compact diff --git a/test/env_file_test.rb b/test/env_file_test.rb index 6fcef6e39..c6b9e66ec 100644 --- a/test/env_file_test.rb +++ b/test/env_file_test.rb @@ -11,6 +11,33 @@ class EnvFileTest < ActiveSupport::TestCase Kamal::EnvFile.new(env).to_s end + test "to_str won't escape chinese characters" do + env = { + "foo" => 'δ½ ε₯½ means hello, "欒迎" means welcome, that\'s simple! πŸ˜ƒ {smile}' + } + + assert_equal "foo=δ½ ε₯½ means hello, \"欒迎\" means welcome, that's simple! πŸ˜ƒ {smile}\n", + Kamal::EnvFile.new(env).to_s + end + + test "to_s won't escape japanese characters" do + env = { + "foo" => 'こんにけは means hello, "γ‚ˆγ†γ“γ" means welcome, that\'s simple! πŸ˜ƒ {smile}' + } + + assert_equal "foo=こんにけは means hello, \"γ‚ˆγ†γ“γ\" means welcome, that's simple! πŸ˜ƒ {smile}\n", \ + Kamal::EnvFile.new(env).to_s + end + + test "to_s won't escape korean characters" do + env = { + "foo" => 'μ•ˆλ…•ν•˜μ„Έμš” means hello, "μ–΄μ„œ μ˜€μ‹­μ‹œμ˜€" means welcome, that\'s simple! πŸ˜ƒ {smile}' + } + + assert_equal "foo=μ•ˆλ…•ν•˜μ„Έμš” means hello, \"μ–΄μ„œ μ˜€μ‹­μ‹œμ˜€\" means welcome, that's simple! πŸ˜ƒ {smile}\n", \ + Kamal::EnvFile.new(env).to_s + end + test "to_s empty" do assert_equal "\n", Kamal::EnvFile.new({}).to_s end diff --git a/test/integration/docker/deployer/app/.env.erb b/test/integration/docker/deployer/app/.env.erb index dcd2fcf5c..cb2988d6b 100644 --- a/test/integration/docker/deployer/app/.env.erb +++ b/test/integration/docker/deployer/app/.env.erb @@ -1 +1 @@ -SECRET_TOKEN=1234 +SECRET_TOKEN='1234 with "δΈ­ζ–‡"' diff --git a/test/integration/docker/deployer/app_with_roles/.env.erb b/test/integration/docker/deployer/app_with_roles/.env.erb index dcd2fcf5c..cb2988d6b 100644 --- a/test/integration/docker/deployer/app_with_roles/.env.erb +++ b/test/integration/docker/deployer/app_with_roles/.env.erb @@ -1 +1 @@ -SECRET_TOKEN=1234 +SECRET_TOKEN='1234 with "δΈ­ζ–‡"' diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index f8f69b0e0..ac549b972 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -3,8 +3,8 @@ class MainTest < IntegrationTest test "envify, deploy, redeploy, rollback, details and audit" do kamal :envify - assert_local_env_file "SECRET_TOKEN=1234" - assert_remote_env_file "SECRET_TOKEN=1234" + assert_local_env_file "SECRET_TOKEN='1234 with \"δΈ­ζ–‡\"'" + assert_remote_env_file "SECRET_TOKEN=1234 with \"δΈ­ζ–‡\"" remove_local_env_file first_version = latest_app_version @@ -16,7 +16,7 @@ class MainTest < IntegrationTest assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy" assert_env :CLEAR_TOKEN, "4321", version: first_version assert_env :HOST_TOKEN, "abcd", version: first_version - assert_env :SECRET_TOKEN, "1234", version: first_version + assert_env :SECRET_TOKEN, "1234 with \"δΈ­ζ–‡\"", version: first_version second_version = update_app_rev diff --git a/test/utils_test.rb b/test/utils_test.rb index e292e9801..b7abbc74b 100644 --- a/test/utils_test.rb +++ b/test/utils_test.rb @@ -21,6 +21,11 @@ class UtilsTest < ActiveSupport::TestCase Kamal::Utils.optionize({ foo: "bar", baz: "qux", quux: true }, with: "=") end + test "optionize reject nil value" do + assert_equal [ "--foo", "\"bar\"", "--baz", "\"qux\"" ], \ + Kamal::Utils.optionize({ foo: "bar", baz: "qux", quux: nil }) + end + test "no redaction from #to_s" do assert_equal "secret", Kamal::Utils.sensitive("secret").to_s end