Răsfoiți Sursa

add an util method to convert []byte to string

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 1 an în urmă
părinte
comite
5d24d665bd

+ 1 - 1
go.mod

@@ -79,7 +79,7 @@ require (
 
 require (
 	cloud.google.com/go v0.112.2 // indirect
-	cloud.google.com/go/auth v0.3.0 // indirect
+	cloud.google.com/go/auth v0.4.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
 	cloud.google.com/go/compute/metadata v0.3.0 // indirect
 	cloud.google.com/go/iam v1.1.8 // indirect

+ 4 - 0
go.sum

@@ -3,8 +3,11 @@ cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw=
 cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
 cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs=
 cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w=
+cloud.google.com/go/auth v0.4.0 h1:vcJWEguhY8KuiHoSs/udg1JtIRYm3YAWPBE1moF1m3U=
+cloud.google.com/go/auth v0.4.0/go.mod h1:tO/chJN3obc5AbRYFQDsuFbL4wW5y8LfbPtDCfgwOVE=
 cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
 cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
+cloud.google.com/go/compute v1.26.0 h1:uHf0NN2nvxl1Gh4QO83yRCOdMK4zivtMS5gv0dEX0hg=
 cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
 cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
@@ -236,6 +239,7 @@ github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD
 github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
 github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
 github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
 github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
 github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=

+ 1 - 1
internal/acme/acme.go

@@ -329,7 +329,7 @@ func (c *Configuration) getLockTime() (time.Time, error) {
 		acmeLog(logger.LevelError, "unable to read lock file %q: %v", c.lockPath, err)
 		return time.Time{}, err
 	}
-	msec, err := strconv.ParseInt(strings.TrimSpace(string(content)), 10, 64)
+	msec, err := strconv.ParseInt(strings.TrimSpace(util.BytesToString(content)), 10, 64)
 	if err != nil {
 		acmeLog(logger.LevelError, "unable to parse lock time: %v", err)
 		return time.Time{}, fmt.Errorf("unable to parse lock time: %w", err)

+ 2 - 2
internal/cmd/portable.go

@@ -196,7 +196,7 @@ Please take a look at the usage below to customize the serving parameters`,
 					fmt.Printf("Unable to read password file %q: %v", portablePasswordFile, err)
 					os.Exit(1)
 				}
-				pwd = strings.TrimSpace(string(content))
+				pwd = strings.TrimSpace(util.BytesToString(content))
 			}
 			service.SetGraceTime(graceTime)
 			service := service.Service{
@@ -523,7 +523,7 @@ func getFileContents(name string) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	return string(contents), nil
+	return util.BytesToString(contents), nil
 }
 
 func convertFsProvider() string {

+ 1 - 1
internal/common/actions.go

@@ -345,7 +345,7 @@ func notificationAsEnvVars(event *notifier.FsEvent) []string {
 	if len(event.Metadata) > 0 {
 		data, err := json.Marshal(event.Metadata)
 		if err == nil {
-			result = append(result, fmt.Sprintf("SFTPGO_ACTION_METADATA=%s", string(data)))
+			result = append(result, fmt.Sprintf("SFTPGO_ACTION_METADATA=%s", util.BytesToString(data)))
 		}
 	}
 	return result

+ 1 - 1
internal/common/dataretention.go

@@ -479,7 +479,7 @@ func (c *RetentionCheck) sendHookNotification(elapsed time.Duration, errCheck er
 
 	cmd := exec.CommandContext(ctx, Config.DataRetentionHook, args...)
 	cmd.Env = append(env,
-		fmt.Sprintf("SFTPGO_DATA_RETENTION_RESULT=%s", string(jsonData)))
+		fmt.Sprintf("SFTPGO_DATA_RETENTION_RESULT=%s", util.BytesToString(jsonData)))
 	err := cmd.Run()
 
 	c.conn.Log(logger.LevelDebug, "notified result using command: %q, elapsed: %s err: %v",

+ 4 - 3
internal/common/eventmanager.go

@@ -811,7 +811,7 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 	if addObjectData {
 		data, err := p.Object.RenderAsJSON(p.Event != operationDelete)
 		if err == nil {
-			dataString := string(data)
+			dataString := util.BytesToString(data)
 			replacements[len(replacements)-3] = p.getStringReplacement(dataString, false)
 			replacements[len(replacements)-1] = p.getStringReplacement(dataString, true)
 		}
@@ -826,7 +826,7 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 	if len(p.Metadata) > 0 {
 		data, err := json.Marshal(p.Metadata)
 		if err == nil {
-			dataString := string(data)
+			dataString := util.BytesToString(data)
 			replacements[len(replacements)-3] = p.getStringReplacement(dataString, false)
 			replacements[len(replacements)-1] = p.getStringReplacement(dataString, true)
 		}
@@ -1466,7 +1466,8 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
 		endpoint, time.Since(startTime), resp.StatusCode)
 	if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusNoContent {
 		if rb, err := io.ReadAll(io.LimitReader(resp.Body, 2048)); err == nil {
-			eventManagerLog(logger.LevelDebug, "error notification response from endpoint %q: %s", endpoint, string(rb))
+			eventManagerLog(logger.LevelDebug, "error notification response from endpoint %q: %s",
+				endpoint, util.BytesToString(rb))
 		}
 		return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
 	}

+ 1 - 1
internal/dataprovider/actions.go

@@ -148,7 +148,7 @@ func executeNotificationCommand(operation, executor, ip, objectType, objectName,
 		fmt.Sprintf("SFTPGO_PROVIDER_IP=%s", ip),
 		fmt.Sprintf("SFTPGO_PROVIDER_ROLE=%s", role),
 		fmt.Sprintf("SFTPGO_PROVIDER_TIMESTAMP=%d", util.GetTimeAsMsSinceEpoch(time.Now())),
-		fmt.Sprintf("SFTPGO_PROVIDER_OBJECT=%s", string(objectAsJSON)))
+		fmt.Sprintf("SFTPGO_PROVIDER_OBJECT=%s", util.BytesToString(objectAsJSON)))
 
 	startTime := time.Now()
 	err := cmd.Run()

+ 1 - 1
internal/dataprovider/admin.go

@@ -297,7 +297,7 @@ func (a *Admin) hashPassword() error {
 			if err != nil {
 				return err
 			}
-			a.Password = string(pwd)
+			a.Password = util.BytesToString(pwd)
 		} else {
 			pwd, err := argon2id.CreateHash(a.Password, argon2Params)
 			if err != nil {

+ 1 - 1
internal/dataprovider/apikey.go

@@ -118,7 +118,7 @@ func (k *APIKey) hashKey() error {
 			if err != nil {
 				return err
 			}
-			k.Key = string(hashed)
+			k.Key = util.BytesToString(hashed)
 		} else {
 			hashed, err := argon2id.CreateHash(k.Key, argon2Params)
 			if err != nil {

+ 5 - 5
internal/dataprovider/dataprovider.go

@@ -3255,7 +3255,7 @@ func hashPlainPassword(plainPwd string) (string, error) {
 		if err != nil {
 			return "", fmt.Errorf("bcrypt hashing error: %w", err)
 		}
-		return string(pwd), nil
+		return util.BytesToString(pwd), nil
 	}
 	pwd, err := argon2id.CreateHash(plainPwd, argon2Params)
 	if err != nil {
@@ -4115,7 +4115,7 @@ func getPreLoginHookResponse(loginMethod, ip, protocol string, userAsJSON []byte
 
 	cmd := exec.CommandContext(ctx, config.PreLoginHook, args...)
 	cmd.Env = append(env,
-		fmt.Sprintf("SFTPGO_LOGIND_USER=%s", string(userAsJSON)),
+		fmt.Sprintf("SFTPGO_LOGIND_USER=%s", util.BytesToString(userAsJSON)),
 		fmt.Sprintf("SFTPGO_LOGIND_METHOD=%s", loginMethod),
 		fmt.Sprintf("SFTPGO_LOGIND_IP=%s", ip),
 		fmt.Sprintf("SFTPGO_LOGIND_PROTOCOL=%s", protocol),
@@ -4162,7 +4162,7 @@ func executePreLoginHook(username, loginMethod, ip, protocol string, oidcTokenFi
 	recoveryCodes := u.Filters.RecoveryCodes
 	err = json.Unmarshal(out, &u)
 	if err != nil {
-		return u, fmt.Errorf("invalid pre-login hook response %q, error: %v", string(out), err)
+		return u, fmt.Errorf("invalid pre-login hook response %q, error: %v", util.BytesToString(out), err)
 	}
 	u.ID = userID
 	u.UsedQuotaSize = userUsedQuotaSize
@@ -4257,7 +4257,7 @@ func ExecutePostLoginHook(user *User, loginMethod, ip, protocol string, err erro
 
 		cmd := exec.CommandContext(ctx, config.PostLoginHook, args...)
 		cmd.Env = append(env,
-			fmt.Sprintf("SFTPGO_LOGIND_USER=%s", string(userAsJSON)),
+			fmt.Sprintf("SFTPGO_LOGIND_USER=%s", util.BytesToString(userAsJSON)),
 			fmt.Sprintf("SFTPGO_LOGIND_IP=%s", ip),
 			fmt.Sprintf("SFTPGO_LOGIND_METHOD=%s", loginMethod),
 			fmt.Sprintf("SFTPGO_LOGIND_STATUS=%s", status),
@@ -4326,7 +4326,7 @@ func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip,
 	cmd := exec.CommandContext(ctx, config.ExternalAuthHook, args...)
 	cmd.Env = append(env,
 		fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%s", username),
-		fmt.Sprintf("SFTPGO_AUTHD_USER=%s", string(userAsJSON)),
+		fmt.Sprintf("SFTPGO_AUTHD_USER=%s", util.BytesToString(userAsJSON)),
 		fmt.Sprintf("SFTPGO_AUTHD_IP=%s", ip),
 		fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%s", password),
 		fmt.Sprintf("SFTPGO_AUTHD_PUBLIC_KEY=%s", pkey),

+ 2 - 2
internal/dataprovider/node.go

@@ -99,7 +99,7 @@ func (n *NodeData) validate() error {
 	if n.Proto != NodeProtoHTTP && n.Proto != NodeProtoHTTPS {
 		return util.NewValidationError(fmt.Sprintf("invalid node proto: %s", n.Proto))
 	}
-	n.Key = kms.NewPlainSecret(string(util.GenerateRandomBytes(32)))
+	n.Key = kms.NewPlainSecret(util.BytesToString(util.GenerateRandomBytes(32)))
 	n.Key.SetAdditionalData(n.Host)
 	if err := n.Key.Encrypt(); err != nil {
 		return fmt.Errorf("unable to encrypt node key: %w", err)
@@ -191,7 +191,7 @@ func (n *Node) generateAuthToken(username, role string) (string, error) {
 	if err != nil {
 		return "", fmt.Errorf("unable to sign authentication token: %w", err)
 	}
-	return string(payload), nil
+	return util.BytesToString(payload), nil
 }
 
 func (n *Node) prepareRequest(ctx context.Context, username, role, relativeURL, method string,

+ 1 - 1
internal/dataprovider/share.go

@@ -155,7 +155,7 @@ func (s *Share) hashPassword() error {
 			if err != nil {
 				return err
 			}
-			s.Password = string(hashed)
+			s.Password = util.BytesToString(hashed)
 		} else {
 			hashed, err := argon2id.CreateHash(s.Password, argon2Params)
 			if err != nil {

+ 1 - 1
internal/ftpd/server.go

@@ -63,7 +63,7 @@ func NewServer(config *Configuration, configDir string, binding Binding, id int)
 		}
 		bannerContent, err := os.ReadFile(bannerFilePath)
 		if err == nil {
-			server.initialMsg = string(bannerContent)
+			server.initialMsg = util.BytesToString(bannerContent)
 		} else {
 			logger.WarnToConsole("unable to read FTPD banner file: %v", err)
 			logger.Warn(logSender, "", "unable to read banner file: %v", err)

+ 2 - 1
internal/httpd/api_defender.go

@@ -25,6 +25,7 @@ import (
 
 	"github.com/drakkan/sftpgo/v2/internal/common"
 	"github.com/drakkan/sftpgo/v2/internal/dataprovider"
+	"github.com/drakkan/sftpgo/v2/internal/util"
 )
 
 func getDefenderHosts(w http.ResponseWriter, r *http.Request) {
@@ -76,7 +77,7 @@ func getIPFromID(r *http.Request) (string, error) {
 	if err != nil {
 		return "", errors.New("invalid host id")
 	}
-	ip := string(decoded)
+	ip := util.BytesToString(decoded)
 	err = validateIPAddress(ip)
 	if err != nil {
 		return "", err

+ 1 - 1
internal/httpd/webadmin.go

@@ -1594,7 +1594,7 @@ func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
 		}
 		return config, err
 	}
-	config.Credentials = kms.NewPlainSecret(string(fileBytes))
+	config.Credentials = kms.NewPlainSecret(util.BytesToString(fileBytes))
 	config.AutomaticCredentials = 0
 	return config, err
 }

+ 2 - 1
internal/kms/builtin.go

@@ -23,6 +23,7 @@ import (
 	"errors"
 	"io"
 
+	"github.com/drakkan/sftpgo/v2/internal/util"
 	sdkkms "github.com/sftpgo/sdk/kms"
 )
 
@@ -132,7 +133,7 @@ func (s *builtinSecret) Decrypt() error {
 			return err
 		}
 		s.Status = sdkkms.SecretStatusPlain
-		s.Payload = string(plaintext)
+		s.Payload = util.BytesToString(plaintext)
 		s.Key = ""
 		s.AdditionalData = ""
 		return nil

+ 2 - 1
internal/kms/local.go

@@ -21,6 +21,7 @@ import (
 	"encoding/hex"
 	"io"
 
+	"github.com/drakkan/sftpgo/v2/internal/util"
 	sdkkms "github.com/sftpgo/sdk/kms"
 	"gocloud.dev/secrets/localsecrets"
 	"golang.org/x/crypto/hkdf"
@@ -104,7 +105,7 @@ func (s *localSecret) Decrypt() error {
 		return err
 	}
 	s.Status = sdkkms.SecretStatusPlain
-	s.Payload = string(plaintext)
+	s.Payload = util.BytesToString(plaintext)
 	s.Key = ""
 	s.AdditionalData = ""
 	s.Mode = 0

+ 11 - 1
internal/logger/logger.go

@@ -29,6 +29,7 @@ import (
 	"os"
 	"path/filepath"
 	"time"
+	"unsafe"
 
 	ftpserverlog "github.com/fclairamb/go-log"
 	"github.com/rs/zerolog"
@@ -283,7 +284,7 @@ func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
 		p = p[0 : n-1]
 	}
 
-	Log(LevelError, l.Sender, "", string(p))
+	Log(LevelError, l.Sender, "", bytesToString(p))
 	return
 }
 
@@ -363,3 +364,12 @@ func (l *LeveledLogger) With(keysAndValues ...any) ftpserverlog.Logger {
 		additionalKeyVals: append(l.additionalKeyVals, keysAndValues...),
 	}
 }
+
+func bytesToString(b []byte) string {
+	// unsafe.SliceData relies on cap whereas we want to rely on len
+	if len(b) == 0 {
+		return ""
+	}
+	// https://github.com/golang/go/blob/4ed358b57efdad9ed710be7f4fc51495a7620ce2/src/strings/builder.go#L41
+	return unsafe.String(unsafe.SliceData(b), len(b))
+}

+ 8 - 7
internal/sftpd/server.go

@@ -513,7 +513,7 @@ func (c *Configuration) configureLoginBanner(serverConfig *ssh.ServerConfig, con
 		}
 		bannerContent, err := os.ReadFile(bannerFilePath)
 		if err == nil {
-			banner := string(bannerContent)
+			banner := util.BytesToString(bannerContent)
 			serverConfig.BannerCallback = func(_ ssh.ConnMetadata) string {
 				return banner
 			}
@@ -603,7 +603,8 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
 
 	logger.Log(logger.LevelInfo, common.ProtocolSSH, connectionID,
 		"User %q logged in with %q, from ip %q, client version %q, negotiated algorithms: %+v",
-		user.Username, loginType, ipAddr, string(sconn.ClientVersion()), sconn.Conn.(ssh.AlgorithmsConnMetadata).Algorithms())
+		user.Username, loginType, ipAddr, util.BytesToString(sconn.ClientVersion()),
+		sconn.Conn.(ssh.AlgorithmsConnMetadata).Algorithms())
 	dataprovider.UpdateLastLogin(&user)
 
 	sshConnection := common.NewSSHConnection(connectionID, conn)
@@ -639,12 +640,12 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
 
 				switch req.Type {
 				case "subsystem":
-					if string(req.Payload[4:]) == "sftp" {
+					if util.BytesToString(req.Payload[4:]) == "sftp" {
 						ok = true
 						connection := &Connection{
 							BaseConnection: common.NewBaseConnection(connID, common.ProtocolSFTP, conn.LocalAddr().String(),
 								conn.RemoteAddr().String(), user),
-							ClientVersion: string(sconn.ClientVersion()),
+							ClientVersion: util.BytesToString(sconn.ClientVersion()),
 							RemoteAddr:    conn.RemoteAddr(),
 							LocalAddr:     conn.LocalAddr(),
 							channel:       channel,
@@ -656,7 +657,7 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
 					connection := Connection{
 						BaseConnection: common.NewBaseConnection(connID, "sshd_exec", conn.LocalAddr().String(),
 							conn.RemoteAddr().String(), user),
-						ClientVersion: string(sconn.ClientVersion()),
+						ClientVersion: util.BytesToString(sconn.ClientVersion()),
 						RemoteAddr:    conn.RemoteAddr(),
 						LocalAddr:     conn.LocalAddr(),
 						channel:       channel,
@@ -816,7 +817,7 @@ func loginUser(user *dataprovider.User, loginMethod, publicKey string, conn ssh.
 	}
 	p := &ssh.Permissions{}
 	p.Extensions = make(map[string]string)
-	p.Extensions["sftpgo_user"] = string(json)
+	p.Extensions["sftpgo_user"] = util.BytesToString(json)
 	p.Extensions["sftpgo_login_method"] = loginMethod
 	return p, nil
 }
@@ -1180,7 +1181,7 @@ func (c *Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass
 	var sshPerm *ssh.Permissions
 
 	ipAddr := util.GetIPFromRemoteAddress(conn.RemoteAddr().String())
-	if user, err = dataprovider.CheckUserAndPass(conn.User(), string(pass), ipAddr, common.ProtocolSSH); err == nil {
+	if user, err = dataprovider.CheckUserAndPass(conn.User(), util.BytesToString(pass), ipAddr, common.ProtocolSSH); err == nil {
 		sshPerm, err = loginUser(&user, method, "", conn)
 	}
 	user.Username = conn.User()

+ 1 - 1
internal/util/i18n.go

@@ -367,7 +367,7 @@ func (e *I18nError) Args() string {
 	if len(e.args) > 0 {
 		data, err := json.Marshal(e.args)
 		if err == nil {
-			return string(data)
+			return BytesToString(data)
 		}
 	}
 	return "{}"

+ 15 - 4
internal/util/util.go

@@ -44,6 +44,7 @@ import (
 	"strings"
 	"time"
 	"unicode"
+	"unsafe"
 
 	"github.com/google/uuid"
 	"github.com/lithammer/shortuuid/v3"
@@ -288,6 +289,16 @@ func ParseBytes(s string) (int64, error) {
 	return 0, fmt.Errorf("unhandled size name: %v", extra)
 }
 
+// BytesToString converts []byte to string without allocations.
+func BytesToString(b []byte) string {
+	// unsafe.SliceData relies on cap whereas we want to rely on len
+	if len(b) == 0 {
+		return ""
+	}
+	// https://github.com/golang/go/blob/4ed358b57efdad9ed710be7f4fc51495a7620ce2/src/strings/builder.go#L41
+	return unsafe.String(unsafe.SliceData(b), len(b))
+}
+
 // GetIPFromRemoteAddress returns the IP from the remote address.
 // If the given remote address cannot be parsed it will be returned unchanged
 func GetIPFromRemoteAddress(remoteAddress string) string {
@@ -660,7 +671,7 @@ func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
 		Type:  "CERTIFICATE",
 		Bytes: tlsCert.Raw,
 	}
-	return string(pem.EncodeToMemory(&publicKeyBlock)), nil
+	return BytesToString(pem.EncodeToMemory(&publicKeyBlock)), nil
 }
 
 // CheckTCP4Port quits the app if bind on the given IPv4 port fails.
@@ -704,7 +715,7 @@ func GetSSHPublicKeyAsString(pubKey []byte) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	return string(ssh.MarshalAuthorizedKey(k)), nil
+	return BytesToString(ssh.MarshalAuthorizedKey(k)), nil
 }
 
 // GetRealIP returns the ip address as result of parsing the specified
@@ -880,7 +891,7 @@ func JSONEscape(val string) string {
 	if err != nil {
 		return ""
 	}
-	return string(b[1 : len(b)-1])
+	return BytesToString(b[1 : len(b)-1])
 }
 
 // ReadConfigFromFile reads a configuration parameter from the specified file
@@ -901,5 +912,5 @@ func ReadConfigFromFile(name, configDir string) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	return strings.TrimSpace(string(val)), nil
+	return strings.TrimSpace(BytesToString(val)), nil
 }

+ 2 - 2
internal/webdavd/file.go

@@ -448,10 +448,10 @@ func (f *webDavFile) Patch(patches []webdav.Proppatch) ([]webdav.Propstat, error
 		for _, p := range patch.Props {
 			if status == http.StatusForbidden && !hasError {
 				if !patch.Remove && util.Contains(lastModifiedProps, p.XMLName.Local) {
-					parsed, err := parseTime(string(p.InnerXML))
+					parsed, err := parseTime(util.BytesToString(p.InnerXML))
 					if err != nil {
 						f.Connection.Log(logger.LevelWarn, "unsupported last modification time: %q, err: %v",
-							string(p.InnerXML), err)
+							util.BytesToString(p.InnerXML), err)
 						hasError = true
 						continue
 					}