drive: move normalizeShareName into pkg drive and make func public (#11638)

This change makes the normalizeShareName function public, so it can be
used for validation in control.

Updates tailscale/corp#16827

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
This commit is contained in:
Charlotte Brandhorst-Satzkorn 2024-04-05 11:43:13 -07:00 committed by GitHub
parent 306bacc669
commit 8c75da27fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 42 additions and 42 deletions

View File

@ -7,14 +7,22 @@
import ( import (
"bytes" "bytes"
"errors"
"net/http" "net/http"
"regexp"
"strings" "strings"
) )
var ( var (
// DisallowShareAs forcibly disables sharing as a specific user, only used // DisallowShareAs forcibly disables sharing as a specific user, only used
// for testing. // for testing.
DisallowShareAs = false DisallowShareAs = false
ErrDriveNotEnabled = errors.New("Taildrive not enabled")
ErrInvalidShareName = errors.New("Share names may only contain the letters a-z, underscore _, parentheses (), or spaces")
)
var (
shareNameRegex = regexp.MustCompile(`^[a-z0-9_\(\) ]+$`)
) )
// AllowShareAs reports whether sharing files as a specific user is allowed. // AllowShareAs reports whether sharing files as a specific user is allowed.
@ -103,3 +111,20 @@ type FileSystemForRemote interface {
// Close() stops serving the WebDAV content // Close() stops serving the WebDAV content
Close() error Close() error
} }
// NormalizeShareName normalizes the given share name and returns an error if
// it contains any disallowed characters.
func NormalizeShareName(name string) (string, error) {
// Force all share names to lowercase to avoid potential incompatibilities
// with clients that don't support case-sensitive filenames.
name = strings.ToLower(name)
// Trim whitespace
name = strings.TrimSpace(name)
if !shareNameRegex.MatchString(name) {
return "", ErrInvalidShareName
}
return name, nil
}

View File

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS // Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
package ipnlocal package drive
import ( import (
"fmt" "fmt"
@ -29,7 +29,7 @@ func TestNormalizeShareName(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(fmt.Sprintf("name %q", tt.name), func(t *testing.T) { t.Run(fmt.Sprintf("name %q", tt.name), func(t *testing.T) {
got, err := normalizeShareName(tt.name) got, err := NormalizeShareName(tt.name)
if tt.err != nil && err != tt.err { if tt.err != nil && err != tt.err {
t.Errorf("wanted error %v, got %v", tt.err, err) t.Errorf("wanted error %v, got %v", tt.err, err)
} else if got != tt.want { } else if got != tt.want {

View File

@ -4,10 +4,8 @@
package ipnlocal package ipnlocal
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"regexp"
"slices" "slices"
"strings" "strings"
@ -24,12 +22,6 @@
DriveLocalPort = 8080 DriveLocalPort = 8080
) )
var (
shareNameRegex = regexp.MustCompile(`^[a-z0-9_\(\) ]+$`)
ErrDriveNotEnabled = errors.New("Taildrive not enabled")
ErrInvalidShareName = errors.New("Share names may only contain the letters a-z, underscore _, parentheses (), or spaces")
)
// DriveSharingEnabled reports whether sharing to remote nodes via Taildrive is // DriveSharingEnabled reports whether sharing to remote nodes via Taildrive is
// enabled. This is currently based on checking for the drive:share node // enabled. This is currently based on checking for the drive:share node
// attribute. // attribute.
@ -62,7 +54,7 @@ func (b *LocalBackend) driveAccessEnabledLocked() bool {
func (b *LocalBackend) DriveSetServerAddr(addr string) error { func (b *LocalBackend) DriveSetServerAddr(addr string) error {
fs, ok := b.sys.DriveForRemote.GetOK() fs, ok := b.sys.DriveForRemote.GetOK()
if !ok { if !ok {
return ErrDriveNotEnabled return drive.ErrDriveNotEnabled
} }
fs.SetFileServerAddr(addr) fs.SetFileServerAddr(addr)
@ -75,7 +67,7 @@ func (b *LocalBackend) DriveSetServerAddr(addr string) error {
// limited to alphanumeric characters and the underscore _. // limited to alphanumeric characters and the underscore _.
func (b *LocalBackend) DriveSetShare(share *drive.Share) error { func (b *LocalBackend) DriveSetShare(share *drive.Share) error {
var err error var err error
share.Name, err = normalizeShareName(share.Name) share.Name, err = drive.NormalizeShareName(share.Name)
if err != nil { if err != nil {
return err return err
} }
@ -91,29 +83,12 @@ func (b *LocalBackend) DriveSetShare(share *drive.Share) error {
return nil return nil
} }
// normalizeShareName normalizes the given share name and returns an error if
// it contains any disallowed characters.
func normalizeShareName(name string) (string, error) {
// Force all share names to lowercase to avoid potential incompatibilities
// with clients that don't support case-sensitive filenames.
name = strings.ToLower(name)
// Trim whitespace
name = strings.TrimSpace(name)
if !shareNameRegex.MatchString(name) {
return "", ErrInvalidShareName
}
return name, nil
}
func (b *LocalBackend) driveSetShareLocked(share *drive.Share) (views.SliceView[*drive.Share, drive.ShareView], error) { func (b *LocalBackend) driveSetShareLocked(share *drive.Share) (views.SliceView[*drive.Share, drive.ShareView], error) {
existingShares := b.pm.prefs.DriveShares() existingShares := b.pm.prefs.DriveShares()
fs, ok := b.sys.DriveForRemote.GetOK() fs, ok := b.sys.DriveForRemote.GetOK()
if !ok { if !ok {
return existingShares, ErrDriveNotEnabled return existingShares, drive.ErrDriveNotEnabled
} }
addedShare := false addedShare := false
@ -151,7 +126,7 @@ func (b *LocalBackend) driveSetShareLocked(share *drive.Share) (views.SliceView[
// - share already exists under new name // - share already exists under new name
func (b *LocalBackend) DriveRenameShare(oldName, newName string) error { func (b *LocalBackend) DriveRenameShare(oldName, newName string) error {
var err error var err error
newName, err = normalizeShareName(newName) newName, err = drive.NormalizeShareName(newName)
if err != nil { if err != nil {
return err return err
} }
@ -172,7 +147,7 @@ func (b *LocalBackend) driveRenameShareLocked(oldName, newName string) (views.Sl
fs, ok := b.sys.DriveForRemote.GetOK() fs, ok := b.sys.DriveForRemote.GetOK()
if !ok { if !ok {
return existingShares, ErrDriveNotEnabled return existingShares, drive.ErrDriveNotEnabled
} }
found := false found := false
@ -212,7 +187,7 @@ func (b *LocalBackend) DriveRemoveShare(name string) error {
// Force all share names to lowercase to avoid potential incompatibilities // Force all share names to lowercase to avoid potential incompatibilities
// with clients that don't support case-sensitive filenames. // with clients that don't support case-sensitive filenames.
var err error var err error
name, err = normalizeShareName(name) name, err = drive.NormalizeShareName(name)
if err != nil { if err != nil {
return err return err
} }
@ -233,7 +208,7 @@ func (b *LocalBackend) driveRemoveShareLocked(name string) (views.SliceView[*dri
fs, ok := b.sys.DriveForRemote.GetOK() fs, ok := b.sys.DriveForRemote.GetOK()
if !ok { if !ok {
return existingShares, ErrDriveNotEnabled return existingShares, drive.ErrDriveNotEnabled
} }
found := false found := false

View File

@ -2404,13 +2404,13 @@ func TestDriveManageShares(t *testing.T) {
{ {
name: "add_bad_name", name: "add_bad_name",
add: &drive.Share{Name: "$"}, add: &drive.Share{Name: "$"},
expect: ErrInvalidShareName, expect: drive.ErrInvalidShareName,
}, },
{ {
name: "add_disabled", name: "add_disabled",
disabled: true, disabled: true,
add: &drive.Share{Name: "a"}, add: &drive.Share{Name: "a"},
expect: ErrDriveNotEnabled, expect: drive.ErrDriveNotEnabled,
}, },
{ {
name: "remove", name: "remove",
@ -2439,7 +2439,7 @@ func TestDriveManageShares(t *testing.T) {
name: "remove_disabled", name: "remove_disabled",
disabled: true, disabled: true,
remove: "b", remove: "b",
expect: ErrDriveNotEnabled, expect: drive.ErrDriveNotEnabled,
}, },
{ {
name: "rename", name: "rename",
@ -2474,13 +2474,13 @@ func TestDriveManageShares(t *testing.T) {
{ {
name: "rename_bad_name", name: "rename_bad_name",
rename: [2]string{"a", "$"}, rename: [2]string{"a", "$"},
expect: ErrInvalidShareName, expect: drive.ErrInvalidShareName,
}, },
{ {
name: "rename_disabled", name: "rename_disabled",
disabled: true, disabled: true,
rename: [2]string{"a", "c"}, rename: [2]string{"a", "c"},
expect: ErrDriveNotEnabled, expect: drive.ErrDriveNotEnabled,
}, },
} }

View File

@ -2792,7 +2792,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
} }
err = h.b.DriveSetShare(&share) err = h.b.DriveSetShare(&share)
if err != nil { if err != nil {
if errors.Is(err, ipnlocal.ErrInvalidShareName) { if errors.Is(err, drive.ErrInvalidShareName) {
http.Error(w, "invalid share name", http.StatusBadRequest) http.Error(w, "invalid share name", http.StatusBadRequest)
return return
} }
@ -2833,7 +2833,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
http.Error(w, "share name already used", http.StatusBadRequest) http.Error(w, "share name already used", http.StatusBadRequest)
return return
} }
if errors.Is(err, ipnlocal.ErrInvalidShareName) { if errors.Is(err, drive.ErrInvalidShareName) {
http.Error(w, "invalid share name", http.StatusBadRequest) http.Error(w, "invalid share name", http.StatusBadRequest)
return return
} }