Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 50 additions & 11 deletions lib/tasks/mastodon.rake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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