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 escapingpull/53/head
parent
2ba14097ff
commit
a65f86ae55
|
@ -395,18 +395,11 @@ namespace :mastodon do
|
||||||
incompatible_syntax = false
|
incompatible_syntax = false
|
||||||
|
|
||||||
env_contents = env.each_pair.map do |key, value|
|
env_contents = env.each_pair.map do |key, value|
|
||||||
if value.is_a?(String) && value =~ /[\s\#\\"]/
|
value = value.to_s
|
||||||
incompatible_syntax = true
|
escaped = dotenv_escape(value)
|
||||||
|
incompatible_syntax = true if value != escaped
|
||||||
|
|
||||||
if value =~ /[']/
|
escaped
|
||||||
value = value.to_s.gsub(/[\\"\$]/) { |x| "\\#{x}" }
|
|
||||||
"#{key}=\"#{value}\""
|
|
||||||
else
|
|
||||||
"#{key}='#{value}'"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
"#{key}=#{value}"
|
|
||||||
end
|
|
||||||
end.join("\n")
|
end.join("\n")
|
||||||
|
|
||||||
generated_header = "# Generated with mastodon:setup on #{Time.now.utc}\n\n".dup
|
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
|
HttpLog.configuration.logger = dev_null
|
||||||
Paperclip.options[:log] = false
|
Paperclip.options[:log] = false
|
||||||
end
|
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
|
||||||
|
|
Loading…
Reference in New Issue