s3: use lib/encoder

Co-authored-by: Nick Craig-Wood <nick@craig-wood.com>
This commit is contained in:
Fabian Möller 2018-11-02 13:15:30 +01:00 committed by Nick Craig-Wood
parent a8adce9c59
commit 33f129fbbc
4 changed files with 51 additions and 3 deletions

View File

@ -42,6 +42,7 @@ import (
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
@ -51,6 +52,8 @@ import (
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
const enc = encodings.S3
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@ -900,7 +903,8 @@ func parsePath(path string) (root string) {
// split returns bucket and bucketPath from the rootRelativePath // split returns bucket and bucketPath from the rootRelativePath
// relative to f.root // relative to f.root
func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) { func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) {
return bucket.Split(path.Join(f.root, rootRelativePath)) bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath))
return enc.FromStandardName(bucketName), enc.FromStandardPath(bucketPath)
} }
// split returns bucket and bucketPath from the object // split returns bucket and bucketPath from the object
@ -1096,9 +1100,10 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
}).Fill(f) }).Fill(f)
if f.rootBucket != "" && f.rootDirectory != "" { if f.rootBucket != "" && f.rootDirectory != "" {
// Check to see if the object exists // Check to see if the object exists
encodedDirectory := enc.FromStandardPath(f.rootDirectory)
req := s3.HeadObjectInput{ req := s3.HeadObjectInput{
Bucket: &f.rootBucket, Bucket: &f.rootBucket,
Key: &f.rootDirectory, Key: &encodedDirectory,
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
_, err = f.c.HeadObject(&req) _, err = f.c.HeadObject(&req)
@ -1266,6 +1271,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
fs.Logf(f, "failed to URL decode %q in listing common prefix: %v", *commonPrefix.Prefix, err) fs.Logf(f, "failed to URL decode %q in listing common prefix: %v", *commonPrefix.Prefix, err)
continue continue
} }
remote = enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -1290,6 +1296,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
fs.Logf(f, "failed to URL decode %q in listing: %v", aws.StringValue(object.Key), err) fs.Logf(f, "failed to URL decode %q in listing: %v", aws.StringValue(object.Key), err)
continue continue
} }
remote = enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -1374,7 +1381,7 @@ func (f *Fs) listBuckets(ctx context.Context) (entries fs.DirEntries, err error)
return nil, err return nil, err
} }
for _, bucket := range resp.Buckets { for _, bucket := range resp.Buckets {
bucketName := aws.StringValue(bucket.Name) bucketName := enc.ToStandardName(aws.StringValue(bucket.Name))
f.cache.MarkOK(bucketName) f.cache.MarkOK(bucketName)
d := fs.NewDir(bucketName, aws.TimeValue(bucket.CreationDate)) d := fs.NewDir(bucketName, aws.TimeValue(bucket.CreationDate))
entries = append(entries, d) entries = append(entries, d)

View File

@ -268,6 +268,29 @@ side copy to update the modification if the object can be copied in a single par
In the case the object is larger than 5Gb or is in Glacier or Glacier Deep Archive In the case the object is larger than 5Gb or is in Glacier or Glacier Deep Archive
storage the object will be uploaded rather than copied. storage the object will be uploaded rather than copied.
#### Restricted filename characters
S3 allows any valid UTF-8 string as a key.
Invalid UTF-8 bytes will be [replaced](/overview/#invalid-utf8), as
they can't be used in XML.
The following characters are replaced since these are problematic when
dealing with the REST API:
| Character | Value | Replacement |
| --------- |:-----:|:-----------:|
| NUL | 0x00 | ␀ |
| / | 0x2F | |
The encoding will also encode these file names as they don't seem to
work with the SDK properly:
| File name | Replacement |
| --------- |:-----------:|
| . | |
| .. | |
### Multipart uploads ### ### Multipart uploads ###
rclone supports multipart uploads with S3 which means that it can rclone supports multipart uploads with S3 which means that it can

View File

@ -240,6 +240,23 @@ const FTP = encoder.MultiEncoder(
uint(Display) | uint(Display) |
encoder.EncodeRightSpace) encoder.EncodeRightSpace)
// S3 is the encoding used by the s3 backend
//
// Any UTF-8 character is valid in a key, however it can't handle
// invalid UTF-8 and / have a special meaning.
//
// The SDK can't seem to handle uploading files called '.'
//
// FIXME would be nice to add
// - initial / encoding
// - doubled / encoding
// - trailing / encoding
// so that AWS keys are always valid file names
const S3 = encoder.MultiEncoder(
encoder.EncodeInvalidUtf8 |
encoder.EncodeSlash |
encoder.EncodeDot)
// ByName returns the encoder for a give backend name or nil // ByName returns the encoder for a give backend name or nil
func ByName(name string) encoder.Encoder { func ByName(name string) encoder.Encoder {
switch strings.ToLower(name) { switch strings.ToLower(name) {

View File

@ -27,6 +27,7 @@ const (
OneDrive = Base OneDrive = Base
OpenDrive = Base OpenDrive = Base
Pcloud = Base Pcloud = Base
S3 = Base
) )
// ByName returns the encoder for a give backend name or nil // ByName returns the encoder for a give backend name or nil