aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/validate
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-29 03:40:09 +0900
committerOphestra <cat@gensokyo.uk>2025-10-29 03:40:09 +0900
commit274686d10d3386a41f8fb6bc3363269a734afe0e (patch)
tree59ce81d5617cf5e1f67b819dab49b83b71d8ff93 /internal/validate
parent65342d588ff061d2130e6379d86a26be90c908ae (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.go8
-rw-r--r--internal/validate/sysconf_test.go21
-rw-r--r--internal/validate/username.go12
-rw-r--r--internal/validate/username_test.go30
-rw-r--r--internal/validate/validate.go20
-rw-r--r--internal/validate/validate_test.go90
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)
+ }
+ })
+ }
+}