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

escape shell args in queryToInput && implement some tests #505

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
9 changes: 5 additions & 4 deletions internal/ffmpeg/device/device_darwin.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package device

import (
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/pkg/core"
"net/url"
"os/exec"
"regexp"
"strings"

"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/pkg/core"
)

func queryToInput(query url.Values) string {
Expand All @@ -26,9 +27,9 @@ func queryToInput(query url.Values) string {
for key, value := range query {
switch key {
case "resolution":
input += " -video_size " + value[0]
input += " -video_size " + shellEscape(value[0])
case "pixel_format", "framerate", "video_size", "capture_cursor", "capture_mouse_clicks", "capture_raw_data":
input += " -" + key + " " + value[0]
input += " -" + key + " " + shellEscape(value[0])
}
}
}
Expand Down
54 changes: 54 additions & 0 deletions internal/ffmpeg/device/device_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package device

import (
"net/url"
"testing"
)

func TestQueryToInput(t *testing.T) {
// Testing when both video and audio are empty
t.Run("Empty video and audio", func(t *testing.T) {
query := url.Values{}
output := queryToInput(query)
expected := ""
if output != expected {
t.Errorf("Expected %q, but got %q", expected, output)
}
})

// Testing when video is not empty
t.Run("Video not empty", func(t *testing.T) {
query := url.Values{}
query.Set("video", "some_video")
output := queryToInput(query)
expected := "-f avfoundation -i \"some_video:\""
if output != expected {
t.Errorf("Expected %q, but got %q", expected, output)
}
})

// Testing when audio is not empty
t.Run("Audio not empty", func(t *testing.T) {
query := url.Values{}
query.Set("audio", "some_audio")
output := queryToInput(query)
expected := "-f avfoundation -i \":some_audio\""
if output != expected {
t.Errorf("Expected %q, but got %q", expected, output)
}
})

// Testing when both video and audio are not empty
t.Run("Both video and audio not empty", func(t *testing.T) {
query := url.Values{}
query.Set("video", "some_video")
query.Set("audio", "some_audio")
output := queryToInput(query)
expected := "-f avfoundation -i \"some_video:some_audio\""
if output != expected {
t.Errorf("Expected %q, but got %q", expected, output)
}
})

// Additional test cases can be added here
}
4 changes: 2 additions & 2 deletions internal/ffmpeg/device/device_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ func queryToInput(query url.Values) string {
for key, value := range query {
switch key {
case "resolution":
input += " -video_size " + value[0]
input += " -video_size " + shellEscape(value[0])
case "video_size", "pixel_format", "input_format", "framerate", "use_libv4l2":
input += " -" + key + " " + value[0]
input += " -" + key + " " + shellEscape(value[0])
}
}

Expand Down
61 changes: 61 additions & 0 deletions internal/ffmpeg/device/device_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package device

import (
"net/url"
"testing"
)

func TestQueryToInput(t *testing.T) {
tests := []struct {
name string
query url.Values
want string
}{
{
name: "video with resolution",
query: url.Values{
"video": {"example_video"},
"resolution": {"1920x1080"},
},
want: "-f v4l2 -video_size 1920x1080 -i example_video",
},
{
name: "video with video_size",
query: url.Values{
"video": {"example_video"},
"video_size": {"1280x720"},
},
want: "-f v4l2 -video_size 1280x720 -i example_video",
},
{
name: "video with multiple options",
query: url.Values{
"video": {"example_video"},
"video_size": {"1280x720"},
"pixel_format": {"yuv420p"},
"framerate": {"30"},
},
want: "-f v4l2 -video_size 1280x720 -pixel_format yuv420p -framerate 30 -i example_video",
},
{
name: "audio",
query: url.Values{
"audio": {"example_audio"},
},
want: "-f alsa -i example_audio",
},
{
name: "empty query",
query: url.Values{},
want: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := queryToInput(tt.query); got != tt.want {
t.Errorf("queryToInput() = %v, want %v", got, tt.want)
}
})
}
}
6 changes: 3 additions & 3 deletions internal/ffmpeg/device/device_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func queryToInput(query url.Values) string {
for key, value := range query {
switch key {
case "resolution":
input += " -video_size " + value[0]
input += " -video_size " + shellEscape(value[0])
case "video_size", "framerate", "pixel_format":
input += " -" + key + " " + value[0]
input += " -" + key + " " + shellEscape(value[0])
}
}
}
Expand All @@ -38,7 +38,7 @@ func queryToInput(query url.Values) string {
for key, value := range query {
switch key {
case "sample_rate", "sample_size", "channels", "audio_buffer_size":
input += " -" + key + " " + value[0]
input += " -" + key + " " + shellEscape(value[0])
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions internal/ffmpeg/device/devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,11 @@ func indexToItem(items []string, index string) string {
}
return index
}

// shellEscape escapes a string for use as a shell argument.
//
// arg: the string to be escaped.
// returns: the escaped string.
func shellEscape(arg string) string {
return "'" + strings.ReplaceAll(arg, "'", "'\\''") + "'"
}
25 changes: 25 additions & 0 deletions internal/ffmpeg/device/devices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package device

import (
"testing"
)

func TestShellEscape(t *testing.T) {
tests := []struct {
arg string
expected string
}{
{"test", "'test'"},
{"hello world", "'hello world'"},
{"'single quotes'", "''\\''single quotes'\\'''"},
{"\"double quotes\"", "'\"double quotes\"'"},
{"single ' quote", "'single '\\'' quote'"},
}

for _, test := range tests {
result := shellEscape(test.arg)
if result != test.expected {
t.Errorf("Expected %s, but got %s for input %s", test.expected, result, test.arg)
}
}
}
9 changes: 9 additions & 0 deletions internal/ffmpeg/hardware/hardware.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ func runToString(bin string, args string) string {
}
}

// cut returns the element at the specified position after splitting the input string using the given separator.
//
// Parameters:
// - s: the input string
// - sep: the separator used to split the string
// - pos: the position of the element to return
//
// Return type:
// - string: the element at the specified position, or an empty string if the position is out of range
func cut(s string, sep byte, pos int) string {
for n := 0; n < pos; n++ {
if i := strings.IndexByte(s, sep); i > 0 {
Expand Down
28 changes: 28 additions & 0 deletions internal/ffmpeg/hardware/hardware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package hardware

import (
"testing"
)

func TestCut(t *testing.T) {
tests := []struct {
input string
sep byte
index int
expected string
}{
{"apple,banana,cherry", ',', 0, "apple"},
{"apple,banana,cherry", ',', 1, "banana"},
{"apple,banana,cherry", ',', 2, "cherry"},
{"apple,banana,cherry", ',', 3, ""},
{"", ',', 0, ""},
{"apple", ',', 0, "apple"},
}

for _, tc := range tests {
result := cut(tc.input, tc.sep, tc.index)
if result != tc.expected {
t.Errorf("Expected '%s', but got '%s'", tc.expected, result)
}
}
}