Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ linters:
- varnamelen
- wrapcheck
- wsl
- modernize
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| udp6_addr | User Datagram Protocol Address UDPv6 |
| udp_addr | User Datagram Protocol Address UDP |
| unix_addr | Unix domain socket end point Address |
| uds_exists | Unix domain socket exists (checks filesystem sockets and Linux abstract sockets) |
| uri | URI String |
| url | URL String |
| http_url | HTTP(s) URL String |
Expand Down
67 changes: 67 additions & 0 deletions baked_in.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package validator

import (
"bufio"
"bytes"
"cmp"
"context"
Expand All @@ -15,6 +16,7 @@ import (
"net/url"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -205,6 +207,7 @@ var (
"ip6_addr": isIP6AddrResolvable,
"ip_addr": isIPAddrResolvable,
"unix_addr": isUnixAddrResolvable,
"uds_exists": isUnixDomainSocketExists,
"mac": isMAC,
"hostname": isHostnameRFC952, // RFC 952
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
Expand Down Expand Up @@ -2595,6 +2598,70 @@ func isUnixAddrResolvable(fl FieldLevel) bool {
return err == nil
}

// isUnixDomainSocketExists is the validation function for validating if the field's value is an existing Unix domain socket.
// It handles both filesystem-based sockets and Linux abstract sockets.
// It always returns false for Windows.
func isUnixDomainSocketExists(fl FieldLevel) bool {
if runtime.GOOS == "windows" {
return false
}

sockpath := fl.Field().String()

if sockpath == "" {
return false
}

// On Linux, check for abstract sockets (prefixed with @)
if runtime.GOOS == "linux" && strings.HasPrefix(sockpath, "@") {
return isAbstractSocketExists(sockpath)
}

// For filesystem-based sockets, check if the path exists and is a socket
stats, err := os.Stat(sockpath)
if err != nil {
return false
}

return stats.Mode().Type() == fs.ModeSocket
}

// isAbstractSocketExists checks if a Linux abstract socket exists by reading /proc/net/unix.
// Abstract sockets are identified by an @ prefix in human-readable form.
func isAbstractSocketExists(sockpath string) bool {
file, err := os.Open("/proc/net/unix")
if err != nil {
return false
}
defer func() {
_ = file.Close()
}()

scanner := bufio.NewScanner(file)

// Skip the header line
if !scanner.Scan() {
return false
}

// Abstract sockets in /proc/net/unix are represented with @ prefix
// The socket path is the last field in each line
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)

// The path is the last field (8th field typically)
if len(fields) >= 8 {
path := fields[len(fields)-1]
if path == sockpath {
return true
}
}
}

return false
}

func isIP4Addr(fl FieldLevel) bool {
val := fl.Field().String()

Expand Down
9 changes: 9 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,15 @@ This validates that a string value contains a valid Unix Address.

Usage: unix_addr

# Unix Domain Socket Exists

This validates that a Unix domain socket file exists at the specified path.
It checks both filesystem-based sockets and Linux abstract sockets (prefixed with @).
For filesystem sockets, it verifies the path exists and is a socket file.
For abstract sockets on Linux, it checks /proc/net/unix.

Usage: uds_exists

# Media Access Control Address MAC

This validates that a string value contains a valid MAC Address.
Expand Down
72 changes: 72 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package validator
import (
"bytes"
"context"
"net"
"database/sql"
"runtime"
"database/sql/driver"
"encoding/base64"
"encoding/json"
Expand Down Expand Up @@ -2950,6 +2952,76 @@ func TestUnixAddrValidation(t *testing.T) {
}
}

func TestUnixDomainSocketExistsValidation(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Unix domain sockets are not supported on Windows")
}

validate := New()

errs := validate.Var("", "uds_exists")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "uds_exists")

errs = validate.Var("/tmp/nonexistent.sock", "uds_exists")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "uds_exists")

sockPath := "/tmp/test_validator.sock"
var lc net.ListenConfig
listener, err := lc.Listen(t.Context(), "unix", sockPath)
if err != nil {
t.Fatalf("Failed to create test socket: %v", err)
}
defer func() {
_ = listener.Close()
}()
defer func() {
_ = os.Remove(sockPath)
}()
errs = validate.Var(sockPath, "uds_exists")
Equal(t, errs, nil)

regularFile := "/tmp/test_validator_regular.txt"
if err := os.WriteFile(regularFile, []byte("test"), 0644); err != nil {
t.Fatalf("Failed to create regular file: %v", err)
}
defer func() {
_ = os.Remove(regularFile)
}()
errs = validate.Var(regularFile, "uds_exists")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "uds_exists")

dirPath := "/tmp/test_validator_dir"
if err := os.Mkdir(dirPath, 0755); err != nil && !os.IsExist(err) {
t.Fatalf("Failed to create directory: %v", err)
}
defer func() {
_ = os.RemoveAll(dirPath)
}()
errs = validate.Var(dirPath, "uds_exists")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "uds_exists")

if runtime.GOOS == "linux" {
abstractSockName := "@test_abstract_socket_" + fmt.Sprintf("%d", time.Now().UnixNano())
var lc net.ListenConfig
abstractListener, err := lc.Listen(t.Context(), "unix", "\x00"+abstractSockName[1:])
if err != nil {
t.Fatalf("Failed to create abstract socket: %v", err)
}
defer func() {
_ = abstractListener.Close()
}()
errs = validate.Var(abstractSockName, "uds_exists")
Equal(t, errs, nil)
errs = validate.Var("@nonexistent_abstract_socket", "uds_exists")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "uds_exists")
}
}

func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) {
validate := New()

Expand Down