diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-10-29 03:40:09 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-10-29 03:40:09 +0900 |
| commit | 274686d10d3386a41f8fb6bc3363269a734afe0e (patch) | |
| tree | 59ce81d5617cf5e1f67b819dab49b83b71d8ff93 /internal/validate | |
| parent | 65342d588ff061d2130e6379d86a26be90c908ae (diff) | |
internal/validate: relocate from app
These are free of the dispatcher from internal/app. This change relocates them into their own package.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/validate')
| -rw-r--r-- | internal/validate/sysconf.go | 8 | ||||
| -rw-r--r-- | internal/validate/sysconf_test.go | 21 | ||||
| -rw-r--r-- | internal/validate/username.go | 12 | ||||
| -rw-r--r-- | internal/validate/username_test.go | 30 | ||||
| -rw-r--r-- | internal/validate/validate.go | 20 | ||||
| -rw-r--r-- | internal/validate/validate_test.go | 90 |
6 files changed, 181 insertions, 0 deletions
diff --git a/internal/validate/sysconf.go b/internal/validate/sysconf.go new file mode 100644 index 00000000..0960e9dd --- /dev/null +++ b/internal/validate/sysconf.go @@ -0,0 +1,8 @@ +package validate + +//#include <unistd.h> +import "C" + +const SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX + +func Sysconf(name C.int) int { return int(C.sysconf(name)) } diff --git a/internal/validate/sysconf_test.go b/internal/validate/sysconf_test.go new file mode 100644 index 00000000..95502d34 --- /dev/null +++ b/internal/validate/sysconf_test.go @@ -0,0 +1,21 @@ +package validate_test + +import ( + "testing" + + "hakurei.app/internal/validate" +) + +const ( + _POSIX_LOGIN_NAME_MAX = 9 +) + +func TestSysconf(t *testing.T) { + t.Parallel() + + t.Run("LOGIN_NAME_MAX", func(t *testing.T) { + if got := validate.Sysconf(validate.SC_LOGIN_NAME_MAX); got < _POSIX_LOGIN_NAME_MAX { + t.Errorf("sysconf(_SC_LOGIN_NAME_MAX): %d < _POSIX_LOGIN_NAME_MAX", got) + } + }) +} diff --git a/internal/validate/username.go b/internal/validate/username.go new file mode 100644 index 00000000..2249cd27 --- /dev/null +++ b/internal/validate/username.go @@ -0,0 +1,12 @@ +package validate + +import "regexp" + +// nameRegex is the default NAME_REGEX value from adduser. +var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z][a-zA-Z0-9_-]*\$?$`) + +// IsValidUsername returns whether the argument is a valid username. +func IsValidUsername(username string) bool { + return len(username) < Sysconf(SC_LOGIN_NAME_MAX) && + nameRegex.MatchString(username) +} diff --git a/internal/validate/username_test.go b/internal/validate/username_test.go new file mode 100644 index 00000000..1970fa4a --- /dev/null +++ b/internal/validate/username_test.go @@ -0,0 +1,30 @@ +package validate_test + +import ( + "strings" + "testing" + + "hakurei.app/internal/validate" +) + +func TestIsValidUsername(t *testing.T) { + t.Parallel() + + t.Run("long", func(t *testing.T) { + if validate.IsValidUsername(strings.Repeat("a", validate.Sysconf(validate.SC_LOGIN_NAME_MAX))) { + t.Errorf("IsValidUsername unexpected true") + } + }) + + t.Run("regexp", func(t *testing.T) { + if validate.IsValidUsername("0") { + t.Errorf("IsValidUsername unexpected true") + } + }) + + t.Run("valid", func(t *testing.T) { + if !validate.IsValidUsername("alice") { + t.Errorf("IsValidUsername unexpected false") + } + }) +} diff --git a/internal/validate/validate.go b/internal/validate/validate.go new file mode 100644 index 00000000..a4e82753 --- /dev/null +++ b/internal/validate/validate.go @@ -0,0 +1,20 @@ +// Package validate provides functions for validating string values of various types. +package validate + +import ( + "path/filepath" + "strings" +) + +// DeepContainsH returns whether basepath is equivalent to or is the parent of targpath. +// +// This is used for path hiding warning behaviour, the purpose of which is to improve +// user experience and is *not* a security feature and must not be treated as such. +func DeepContainsH(basepath, targpath string) (bool, error) { + const upper = ".." + string(filepath.Separator) + + rel, err := filepath.Rel(basepath, targpath) + return err == nil && + rel != ".." && + !strings.HasPrefix(rel, upper), err +} diff --git a/internal/validate/validate_test.go b/internal/validate/validate_test.go new file mode 100644 index 00000000..338e5693 --- /dev/null +++ b/internal/validate/validate_test.go @@ -0,0 +1,90 @@ +package validate_test + +import ( + "testing" + + "hakurei.app/internal/validate" +) + +func TestDeepContainsH(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + basepath string + targpath string + want bool + wantErr bool + }{ + { + name: "empty", + want: true, + }, + { + name: "equal abs", + basepath: "/run", + targpath: "/run", + want: true, + }, + { + name: "equal rel", + basepath: "./run", + targpath: "run", + want: true, + }, + { + name: "contains abs", + basepath: "/run", + targpath: "/run/dbus", + want: true, + }, + { + name: "inverse contains abs", + basepath: "/run/dbus", + targpath: "/run", + want: false, + }, + { + name: "contains rel", + basepath: "../run", + targpath: "../run/dbus", + want: true, + }, + { + name: "inverse contains rel", + basepath: "../run/dbus", + targpath: "../run", + want: false, + }, + { + name: "weird abs", + basepath: "/run/dbus", + targpath: "/run/dbus/../current-system", + want: false, + }, + { + name: "weird rel", + basepath: "../run/dbus", + targpath: "../run/dbus/../current-system", + want: false, + }, + + { + name: "invalid mix", + basepath: "/run", + targpath: "./run", + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got, err := validate.DeepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr { + t.Errorf("DeepContainsH: error = %v, wantErr %v", err, tc.wantErr) + } else if got != tc.want { + t.Errorf("DeepContainsH: = %v, want %v", got, tc.want) + } + }) + } +} |
