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
This commit is contained in:
		
							parent
							
								
									29c09ab523
								
							
						
					
					
						commit
						fdea93b6be
					
				
					 1 changed files with 50 additions and 11 deletions
				
			
		| 
						 | 
					@ -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 a new issue