Fix `$` not being escaped in `.env.production` file generated by `mastodon:setup` (#23012)

* Fix `$` not being escaped in `.env.production` file generated by `mastodon:setup`

* Improve robustness of dotenv escaping
pull/23057/head
Claire 2023-01-11 21:53:11 +01:00 committed by GitHub
parent 2ba14097ff
commit a65f86ae55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 50 additions and 11 deletions

View File

@ -395,18 +395,11 @@ namespace :mastodon do
incompatible_syntax = false
env_contents = env.each_pair.map do |key, value|
if value.is_a?(String) && value =~ /[\s\#\\"]/
incompatible_syntax = true
value = value.to_s
escaped = dotenv_escape(value)
incompatible_syntax = true if value != escaped
if value =~ /[']/
value = value.to_s.gsub(/[\\"\$]/) { |x| "\\#{x}" }
"#{key}=\"#{value}\""
else
"#{key}='#{value}'"
end
else
"#{key}=#{value}"
end
escaped
end.join("\n")
generated_header = "# Generated with mastodon:setup on #{Time.now.utc}\n\n".dup
@ -519,3 +512,49 @@ def disable_log_stdout!
HttpLog.configuration.logger = dev_null
Paperclip.options[:log] = false
end
def dotenv_escape(value)
# Dotenv has its own parser, which unfortunately deviates somewhat from
# what shells actually do.
#
# In particular, we can't use Shellwords::escape because it outputs a
# non-quotable string, while Dotenv requires `#` to always be in quoted
# strings.
#
# Therefore, we need to write our own escape code…
# Dotenv's parser has a *lot* of edge cases, and I think not every
# ASCII string can even be represented into something Dotenv can parse,
# so this is a best effort thing.
#
# In particular, strings with all the following probably cannot be
# escaped:
# - `#`, or ends with spaces, which requires some form of quoting (simply escaping won't work)
# - `'` (single quote), preventing us from single-quoting
# - `\` followed by either `r` or `n`
# No character that would cause Dotenv trouble
return value unless /[\s\#\\"'$]/.match?(value)
# As long as the value doesn't include single quotes, we can safely
# rely on single quotes
return "'#{value}'" unless /[']/.match?(value)
# If the value contains the string '\n' or '\r' we simply can't use
# a double-quoted string, because Dotenv will expand \n or \r no
# matter how much escaping we add.
double_quoting_disallowed = /\\[rn]/.match?(value)
value = value.gsub(double_quoting_disallowed ? /[\\"'\s]/ : /[\\"']/) { |x| "\\#{x}" }
# Dotenv is especially tricky with `$` as unbalanced
# parenthesis will make it not unescape `\$` as `$`…
# Variables
value = value.gsub(/\$(?!\()/) { |x| "\\#{x}" }
# Commands
value = value.gsub(/\$(?<cmd>\((?:[^()]|\g<cmd>)+\))/) { |x| "\\#{x}" }
value = "\"#{value}\"" unless double_quoting_disallowed
value
end