diff --git a/Library/Homebrew/test/utils/service_spec.rb b/Library/Homebrew/test/utils/service_spec.rb new file mode 100644 index 0000000000..c09c6cf828 --- /dev/null +++ b/Library/Homebrew/test/utils/service_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "utils/service" + +RSpec.describe Utils::Service do + describe "::systemd_quote" do + it "quotes empty strings correctly" do + expect(described_class.systemd_quote("")).to eq '""' + end + + it "quotes strings with special characters escaped correctly" do + expect(described_class.systemd_quote("\a\b\f\n\r\t\v\\")) + .to eq '"\\a\\b\\f\\n\\r\\t\\v\\\\"' + expect(described_class.systemd_quote("\"' ")).to eq "\"\\\"' \"" + end + + it "does not escape characters that do not need escaping" do + expect(described_class.systemd_quote("daemon off;")).to eq '"daemon off;"' + expect(described_class.systemd_quote("--timeout=3")).to eq '"--timeout=3"' + expect(described_class.systemd_quote("--answer=foo bar")) + .to eq '"--answer=foo bar"' + end + end +end diff --git a/Library/Homebrew/utils/service.rb b/Library/Homebrew/utils/service.rb index 3c13d75c70..5a3ff05ca1 100644 --- a/Library/Homebrew/utils/service.rb +++ b/Library/Homebrew/utils/service.rb @@ -48,5 +48,40 @@ module Utils def self.systemctl? !systemctl.nil? end + + # Quote a string for use in systemd command lines, e.g., in `ExecStart`. + # https://www.freedesktop.org/software/systemd/man/latest/systemd.syntax.html#Quoting + sig { params(str: String).returns(String) } + def self.systemd_quote(str) + result = +"\"" + # No need to escape single quotes and spaces, as we're always double + # quoting the entire string. + str.each_char do |char| + result << case char + when "\a" + "\\a" + when "\b" + "\\b" + when "\f" + "\\f" + when "\n" + "\\n" + when "\r" + "\\r" + when "\t" + "\\t" + when "\v" + "\\v" + when "\\" + "\\\\" + when "\"" + "\\\"" + else + char + end + end + result << "\"" + result.freeze + end end end