rclone/lib/bucket/bucket.go
Nick Craig-Wood fe19184084 lib/bucket: fix tidying of // in object keys #5858
Before this change, bucket.Join would tidy up object keys by removing
repeated / in them. This means we can't access objects with // in them
which is valid for object keys (but not for file system paths).

This could have consequences for users who are relying on rclone to
fix improper paths for them.
2025-01-22 11:56:05 +00:00

197 lines
4.2 KiB
Go

// Package bucket is contains utilities for managing bucket-based backends
package bucket
import (
"errors"
"strings"
"sync"
)
var (
// ErrAlreadyDeleted is returned when an already deleted
// bucket is passed to Remove
ErrAlreadyDeleted = errors.New("bucket already deleted")
)
// Split takes an absolute path which includes the bucket and
// splits it into a bucket and a path in that bucket
// bucketPath
func Split(absPath string) (bucket, bucketPath string) {
// No bucket
if absPath == "" {
return "", ""
}
slash := strings.IndexRune(absPath, '/')
// Bucket but no path
if slash < 0 {
return absPath, ""
}
return absPath[:slash], absPath[slash+1:]
}
// Join path1 and path2
//
// Like path.Join but does not clean the path - useful to preserve trailing /.
//
// It also does not clean multiple // in the path.
func Join(path1, path2 string) string {
if path1 == "" {
return path2
}
if path2 == "" {
return path1
}
return path1 + "/" + path2
}
// IsAllSlashes returns true if s is all / characters.
//
// It returns false if s is "".
func IsAllSlashes(s string) bool {
if len(s) == 0 {
return false
}
for _, c := range s {
if c != '/' {
return false
}
}
return true
}
// Cache stores whether buckets are available and their IDs
type Cache struct {
mu sync.Mutex // mutex to protect created and deleted
status map[string]bool // true if we have created the container, false if deleted
createMu sync.Mutex // mutex to protect against simultaneous Remove
removeMu sync.Mutex // mutex to protect against simultaneous Create
}
// NewCache creates an empty Cache
func NewCache() *Cache {
return &Cache{
status: make(map[string]bool, 1),
}
}
// MarkOK marks the bucket as being present
func (c *Cache) MarkOK(bucket string) {
if bucket != "" {
c.mu.Lock()
c.status[bucket] = true
c.mu.Unlock()
}
}
// MarkDeleted marks the bucket as being deleted
func (c *Cache) MarkDeleted(bucket string) {
if bucket != "" {
c.mu.Lock()
c.status[bucket] = false
c.mu.Unlock()
}
}
type (
// ExistsFn should be passed to Create to see if a bucket
// exists or not
ExistsFn func() (found bool, err error)
// CreateFn should be passed to Create to make a bucket
CreateFn func() error
)
// Create the bucket with create() if it doesn't exist
//
// If exists is set then if the bucket has been deleted it will call
// exists() to see if it still exists.
//
// If f returns an error we assume the bucket was not created
func (c *Cache) Create(bucket string, create CreateFn, exists ExistsFn) (err error) {
// if we are at the root, then it is OK
if bucket == "" {
return nil
}
c.createMu.Lock()
defer c.createMu.Unlock()
c.mu.Lock()
defer c.mu.Unlock()
// if have exists function and bucket has been deleted, check
// it still exists
if created, ok := c.status[bucket]; ok && !created && exists != nil {
found, err := exists()
if err == nil {
c.status[bucket] = found
}
if err != nil || found {
return err
}
}
// If bucket already exists then it is OK
if created, ok := c.status[bucket]; ok && created {
return nil
}
// Create the bucket
c.mu.Unlock()
err = create()
c.mu.Lock()
if err != nil {
return err
}
// Mark OK if successful
c.status[bucket] = true
return nil
}
// Remove the bucket with f if it exists
//
// If f returns an error we assume the bucket was not removed.
//
// If the bucket has already been deleted it returns ErrAlreadyDeleted
func (c *Cache) Remove(bucket string, f func() error) error {
// if we are at the root, then it is OK
if bucket == "" {
return nil
}
c.removeMu.Lock()
defer c.removeMu.Unlock()
c.mu.Lock()
defer c.mu.Unlock()
// If bucket already deleted then it is OK
if created, ok := c.status[bucket]; ok && !created {
return ErrAlreadyDeleted
}
// Remove the bucket
c.mu.Unlock()
err := f()
c.mu.Lock()
if err != nil {
return err
}
// Mark removed if successful
c.status[bucket] = false
return err
}
// IsDeleted returns true if the bucket has definitely been deleted by
// us, false otherwise.
func (c *Cache) IsDeleted(bucket string) bool {
c.mu.Lock()
created, ok := c.status[bucket]
c.mu.Unlock()
// if status unknown then return false
if !ok {
return false
}
return !created
}