mirror of
https://github.com/rclone/rclone.git
synced 2025-12-22 19:23:40 +00:00
Compare commits
2 Commits
dump-curl
...
fix-9031-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5bf9b629f | ||
|
|
6f81885ebf |
@@ -133,32 +133,23 @@ type File struct {
|
|||||||
Info map[string]string `json:"fileInfo"` // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file.
|
Info map[string]string `json:"fileInfo"` // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file.
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageAPI is as returned from the b2_authorize_account call
|
// AuthorizeAccountResponse is as returned from the b2_authorize_account call
|
||||||
type StorageAPI struct {
|
type AuthorizeAccountResponse struct {
|
||||||
AbsoluteMinimumPartSize int `json:"absoluteMinimumPartSize"` // The smallest possible size of a part of a large file.
|
AbsoluteMinimumPartSize int `json:"absoluteMinimumPartSize"` // The smallest possible size of a part of a large file.
|
||||||
|
AccountID string `json:"accountId"` // The identifier for the account.
|
||||||
Allowed struct { // An object (see below) containing the capabilities of this auth token, and any restrictions on using it.
|
Allowed struct { // An object (see below) containing the capabilities of this auth token, and any restrictions on using it.
|
||||||
Buckets []struct { // When present, access is restricted to one or more buckets.
|
BucketID string `json:"bucketId"` // When present, access is restricted to one bucket.
|
||||||
ID string `json:"id"` // ID of bucket
|
BucketName string `json:"bucketName"` // When present, name of bucket - may be empty
|
||||||
Name string `json:"name"` // When present, name of bucket - may be empty
|
Capabilities []string `json:"capabilities"` // A list of strings, each one naming a capability the key has.
|
||||||
} `json:"buckets"`
|
|
||||||
Capabilities []string `json:"capabilities"` // A list of strings, each one naming a capability the key has for every bucket.
|
|
||||||
NamePrefix any `json:"namePrefix"` // When present, access is restricted to files whose names start with the prefix
|
NamePrefix any `json:"namePrefix"` // When present, access is restricted to files whose names start with the prefix
|
||||||
} `json:"allowed"`
|
} `json:"allowed"`
|
||||||
APIURL string `json:"apiUrl"` // The base URL to use for all API calls except for uploading and downloading files.
|
APIURL string `json:"apiUrl"` // The base URL to use for all API calls except for uploading and downloading files.
|
||||||
|
AuthorizationToken string `json:"authorizationToken"` // An authorization token to use with all calls, other than b2_authorize_account, that need an Authorization header.
|
||||||
DownloadURL string `json:"downloadUrl"` // The base URL to use for downloading files.
|
DownloadURL string `json:"downloadUrl"` // The base URL to use for downloading files.
|
||||||
MinimumPartSize int `json:"minimumPartSize"` // DEPRECATED: This field will always have the same value as recommendedPartSize. Use recommendedPartSize instead.
|
MinimumPartSize int `json:"minimumPartSize"` // DEPRECATED: This field will always have the same value as recommendedPartSize. Use recommendedPartSize instead.
|
||||||
RecommendedPartSize int `json:"recommendedPartSize"` // The recommended size for each part of a large file. We recommend using this part size for optimal upload performance.
|
RecommendedPartSize int `json:"recommendedPartSize"` // The recommended size for each part of a large file. We recommend using this part size for optimal upload performance.
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizeAccountResponse is as returned from the b2_authorize_account call
|
|
||||||
type AuthorizeAccountResponse struct {
|
|
||||||
AccountID string `json:"accountId"` // The identifier for the account.
|
|
||||||
AuthorizationToken string `json:"authorizationToken"` // An authorization token to use with all calls, other than b2_authorize_account, that need an Authorization header.
|
|
||||||
APIs struct { // Supported APIs for this account / key. These are API-dependent JSON objects.
|
|
||||||
Storage StorageAPI `json:"storageApi"`
|
|
||||||
} `json:"apiInfo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListBucketsRequest is parameters for b2_list_buckets call
|
// ListBucketsRequest is parameters for b2_list_buckets call
|
||||||
type ListBucketsRequest struct {
|
type ListBucketsRequest struct {
|
||||||
AccountID string `json:"accountId"` // The identifier for the account.
|
AccountID string `json:"accountId"` // The identifier for the account.
|
||||||
|
|||||||
131
backend/b2/b2.go
131
backend/b2/b2.go
@@ -607,29 +607,17 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to authorize account: %w", err)
|
return nil, fmt.Errorf("failed to authorize account: %w", err)
|
||||||
}
|
}
|
||||||
// If this is a key limited to one or more buckets, one of them must exist
|
// If this is a key limited to a single bucket, it must exist already
|
||||||
// and be ours.
|
if f.rootBucket != "" && f.info.Allowed.BucketID != "" {
|
||||||
if f.rootBucket != "" && len(f.info.APIs.Storage.Allowed.Buckets) != 0 {
|
allowedBucket := f.opt.Enc.ToStandardName(f.info.Allowed.BucketName)
|
||||||
buckets := f.info.APIs.Storage.Allowed.Buckets
|
if allowedBucket == "" {
|
||||||
var rootFound = false
|
return nil, errors.New("bucket that application key is restricted to no longer exists")
|
||||||
var rootID string
|
|
||||||
for _, b := range buckets {
|
|
||||||
allowedBucket := f.opt.Enc.ToStandardName(b.Name)
|
|
||||||
if allowedBucket == "" {
|
|
||||||
fs.Debugf(f, "bucket %q that application key is restricted to no longer exists", b.ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowedBucket == f.rootBucket {
|
|
||||||
rootFound = true
|
|
||||||
rootID = b.ID
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !rootFound {
|
if allowedBucket != f.rootBucket {
|
||||||
return nil, fmt.Errorf("you must use bucket(s) %q with this application key", buckets)
|
return nil, fmt.Errorf("you must use bucket %q with this application key", allowedBucket)
|
||||||
}
|
}
|
||||||
f.cache.MarkOK(f.rootBucket)
|
f.cache.MarkOK(f.rootBucket)
|
||||||
f.setBucketID(f.rootBucket, rootID)
|
f.setBucketID(f.rootBucket, f.info.Allowed.BucketID)
|
||||||
}
|
}
|
||||||
if f.rootBucket != "" && f.rootDirectory != "" {
|
if f.rootBucket != "" && f.rootDirectory != "" {
|
||||||
// Check to see if the (bucket,directory) is actually an existing file
|
// Check to see if the (bucket,directory) is actually an existing file
|
||||||
@@ -655,7 +643,7 @@ func (f *Fs) authorizeAccount(ctx context.Context) error {
|
|||||||
defer f.authMu.Unlock()
|
defer f.authMu.Unlock()
|
||||||
opts := rest.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/b2api/v4/b2_authorize_account",
|
Path: "/b2api/v1/b2_authorize_account",
|
||||||
RootURL: f.opt.Endpoint,
|
RootURL: f.opt.Endpoint,
|
||||||
UserName: f.opt.Account,
|
UserName: f.opt.Account,
|
||||||
Password: f.opt.Key,
|
Password: f.opt.Key,
|
||||||
@@ -668,13 +656,13 @@ func (f *Fs) authorizeAccount(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to authenticate: %w", err)
|
return fmt.Errorf("failed to authenticate: %w", err)
|
||||||
}
|
}
|
||||||
f.srv.SetRoot(f.info.APIs.Storage.APIURL+"/b2api/v1").SetHeader("Authorization", f.info.AuthorizationToken)
|
f.srv.SetRoot(f.info.APIURL+"/b2api/v1").SetHeader("Authorization", f.info.AuthorizationToken)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasPermission returns if the current AuthorizationToken has the selected permission
|
// hasPermission returns if the current AuthorizationToken has the selected permission
|
||||||
func (f *Fs) hasPermission(permission string) bool {
|
func (f *Fs) hasPermission(permission string) bool {
|
||||||
return slices.Contains(f.info.APIs.Storage.Allowed.Capabilities, permission)
|
return slices.Contains(f.info.Allowed.Capabilities, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUploadURL returns the upload info with the UploadURL and the AuthorizationToken
|
// getUploadURL returns the upload info with the UploadURL and the AuthorizationToken
|
||||||
@@ -1079,83 +1067,44 @@ type listBucketFn func(*api.Bucket) error
|
|||||||
|
|
||||||
// listBucketsToFn lists the buckets to the function supplied
|
// listBucketsToFn lists the buckets to the function supplied
|
||||||
func (f *Fs) listBucketsToFn(ctx context.Context, bucketName string, fn listBucketFn) error {
|
func (f *Fs) listBucketsToFn(ctx context.Context, bucketName string, fn listBucketFn) error {
|
||||||
responses := make([]api.ListBucketsResponse, len(f.info.APIs.Storage.Allowed.Buckets))[:0]
|
var account = api.ListBucketsRequest{
|
||||||
|
AccountID: f.info.AccountID,
|
||||||
call := func(id string) error {
|
BucketID: f.info.Allowed.BucketID,
|
||||||
var account = api.ListBucketsRequest{
|
}
|
||||||
AccountID: f.info.AccountID,
|
if bucketName != "" && account.BucketID == "" {
|
||||||
BucketID: id,
|
account.BucketName = f.opt.Enc.FromStandardName(bucketName)
|
||||||
}
|
|
||||||
if bucketName != "" && account.BucketID == "" {
|
|
||||||
account.BucketName = f.opt.Enc.FromStandardName(bucketName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var response api.ListBucketsResponse
|
|
||||||
opts := rest.Opts{
|
|
||||||
Method: "POST",
|
|
||||||
Path: "/b2_list_buckets",
|
|
||||||
}
|
|
||||||
err := f.pacer.Call(func() (bool, error) {
|
|
||||||
resp, err := f.srv.CallJSON(ctx, &opts, &account, &response)
|
|
||||||
return f.shouldRetry(ctx, resp, err)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
responses = append(responses, response)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range f.info.APIs.Storage.Allowed.Buckets {
|
var response api.ListBucketsResponse
|
||||||
b := &f.info.APIs.Storage.Allowed.Buckets[i]
|
opts := rest.Opts{
|
||||||
// Empty names indicate a bucket that no longer exists, this is non-fatal
|
Method: "POST",
|
||||||
// for multi-bucket API keys.
|
Path: "/b2_list_buckets",
|
||||||
if b.Name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// When requesting a specific bucket skip over non-matching names
|
|
||||||
if bucketName != "" && b.Name != bucketName {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := call(b.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
err := f.pacer.Call(func() (bool, error) {
|
||||||
if len(f.info.APIs.Storage.Allowed.Buckets) == 0 {
|
resp, err := f.srv.CallJSON(ctx, &opts, &account, &response)
|
||||||
err := call("")
|
return f.shouldRetry(ctx, resp, err)
|
||||||
if err != nil {
|
})
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f.bucketIDMutex.Lock()
|
f.bucketIDMutex.Lock()
|
||||||
f.bucketTypeMutex.Lock()
|
f.bucketTypeMutex.Lock()
|
||||||
f._bucketID = make(map[string]string, 1)
|
f._bucketID = make(map[string]string, 1)
|
||||||
f._bucketType = make(map[string]string, 1)
|
f._bucketType = make(map[string]string, 1)
|
||||||
|
for i := range response.Buckets {
|
||||||
for ri := range responses {
|
bucket := &response.Buckets[i]
|
||||||
response := &responses[ri]
|
bucket.Name = f.opt.Enc.ToStandardName(bucket.Name)
|
||||||
for i := range response.Buckets {
|
f.cache.MarkOK(bucket.Name)
|
||||||
bucket := &response.Buckets[i]
|
f._bucketID[bucket.Name] = bucket.ID
|
||||||
bucket.Name = f.opt.Enc.ToStandardName(bucket.Name)
|
f._bucketType[bucket.Name] = bucket.Type
|
||||||
f.cache.MarkOK(bucket.Name)
|
|
||||||
f._bucketID[bucket.Name] = bucket.ID
|
|
||||||
f._bucketType[bucket.Name] = bucket.Type
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f.bucketTypeMutex.Unlock()
|
f.bucketTypeMutex.Unlock()
|
||||||
f.bucketIDMutex.Unlock()
|
f.bucketIDMutex.Unlock()
|
||||||
for ri := range responses {
|
for i := range response.Buckets {
|
||||||
response := &responses[ri]
|
bucket := &response.Buckets[i]
|
||||||
for i := range response.Buckets {
|
err = fn(bucket)
|
||||||
bucket := &response.Buckets[i]
|
if err != nil {
|
||||||
err := fn(bucket)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -1657,7 +1606,7 @@ func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration,
|
|||||||
bucket, bucketPath := f.split(remote)
|
bucket, bucketPath := f.split(remote)
|
||||||
var RootURL string
|
var RootURL string
|
||||||
if f.opt.DownloadURL == "" {
|
if f.opt.DownloadURL == "" {
|
||||||
RootURL = f.info.APIs.Storage.DownloadURL
|
RootURL = f.info.DownloadURL
|
||||||
} else {
|
} else {
|
||||||
RootURL = f.opt.DownloadURL
|
RootURL = f.opt.DownloadURL
|
||||||
}
|
}
|
||||||
@@ -2008,7 +1957,7 @@ func (o *Object) getOrHead(ctx context.Context, method string, options []fs.Open
|
|||||||
// Use downloadUrl from backblaze if downloadUrl is not set
|
// Use downloadUrl from backblaze if downloadUrl is not set
|
||||||
// otherwise use the custom downloadUrl
|
// otherwise use the custom downloadUrl
|
||||||
if o.fs.opt.DownloadURL == "" {
|
if o.fs.opt.DownloadURL == "" {
|
||||||
opts.RootURL = o.fs.info.APIs.Storage.DownloadURL
|
opts.RootURL = o.fs.info.DownloadURL
|
||||||
} else {
|
} else {
|
||||||
opts.RootURL = o.fs.opt.DownloadURL
|
opts.RootURL = o.fs.opt.DownloadURL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ It is useful to know how many requests are sent to the server in different scena
|
|||||||
All copy commands send the following 4 requests:
|
All copy commands send the following 4 requests:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/b2api/v4/b2_authorize_account
|
/b2api/v1/b2_authorize_account
|
||||||
/b2api/v1/b2_create_bucket
|
/b2api/v1/b2_create_bucket
|
||||||
/b2api/v1/b2_list_buckets
|
/b2api/v1/b2_list_buckets
|
||||||
/b2api/v1/b2_list_file_names
|
/b2api/v1/b2_list_file_names
|
||||||
|
|||||||
Reference in New Issue
Block a user