mirror of
https://github.com/rclone/rclone.git
synced 2026-01-10 04:23:28 +00:00
Compare commits
1 Commits
fix-sftp-i
...
dump-curl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b141a553be |
@@ -28,19 +28,16 @@ directories to and from different cloud storage providers.
|
||||
- Alibaba Cloud (Aliyun) Object Storage System (OSS) [:page_facing_up:](https://rclone.org/s3/#alibaba-oss)
|
||||
- Amazon S3 [:page_facing_up:](https://rclone.org/s3/)
|
||||
- ArvanCloud Object Storage (AOS) [:page_facing_up:](https://rclone.org/s3/#arvan-cloud-object-storage-aos)
|
||||
- Bizfly Cloud Simple Storage [:page_facing_up:](https://rclone.org/s3/#bizflycloud)
|
||||
- Backblaze B2 [:page_facing_up:](https://rclone.org/b2/)
|
||||
- Box [:page_facing_up:](https://rclone.org/box/)
|
||||
- Ceph [:page_facing_up:](https://rclone.org/s3/#ceph)
|
||||
- China Mobile Ecloud Elastic Object Storage (EOS) [:page_facing_up:](https://rclone.org/s3/#china-mobile-ecloud-eos)
|
||||
- Citrix ShareFile [:page_facing_up:](https://rclone.org/sharefile/)
|
||||
- Cloudflare R2 [:page_facing_up:](https://rclone.org/s3/#cloudflare-r2)
|
||||
- Cloudinary [:page_facing_up:](https://rclone.org/cloudinary/)
|
||||
- Citrix ShareFile [:page_facing_up:](https://rclone.org/sharefile/)
|
||||
- Cubbit DS3 [:page_facing_up:](https://rclone.org/s3/#Cubbit)
|
||||
- DigitalOcean Spaces [:page_facing_up:](https://rclone.org/s3/#digitalocean-spaces)
|
||||
- Digi Storage [:page_facing_up:](https://rclone.org/koofr/#digi-storage)
|
||||
- Dreamhost [:page_facing_up:](https://rclone.org/s3/#dreamhost)
|
||||
- Drime [:page_facing_up:](https://rclone.org/s3/#drime)
|
||||
- Dropbox [:page_facing_up:](https://rclone.org/dropbox/)
|
||||
- Enterprise File Fabric [:page_facing_up:](https://rclone.org/filefabric/)
|
||||
- Exaba [:page_facing_up:](https://rclone.org/s3/#exaba)
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
_ "github.com/rclone/rclone/backend/compress"
|
||||
_ "github.com/rclone/rclone/backend/crypt"
|
||||
_ "github.com/rclone/rclone/backend/doi"
|
||||
_ "github.com/rclone/rclone/backend/drime"
|
||||
_ "github.com/rclone/rclone/backend/drive"
|
||||
_ "github.com/rclone/rclone/backend/dropbox"
|
||||
_ "github.com/rclone/rclone/backend/fichier"
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
// Package api has type definitions for drime
|
||||
//
|
||||
// Converted from the API docs with help from https://mholt.github.io/json-to-go/
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Types of things in Item
|
||||
const (
|
||||
ItemTypeFolder = "folder"
|
||||
)
|
||||
|
||||
// User information
|
||||
type User struct {
|
||||
Email string `json:"email"`
|
||||
ID json.Number `json:"id"`
|
||||
Avatar string `json:"avatar"`
|
||||
ModelType string `json:"model_type"`
|
||||
OwnsEntry bool `json:"owns_entry"`
|
||||
EntryPermissions []any `json:"entry_permissions"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
// Permissions for a file
|
||||
type Permissions struct {
|
||||
FilesUpdate bool `json:"files.update"`
|
||||
FilesCreate bool `json:"files.create"`
|
||||
FilesDownload bool `json:"files.download"`
|
||||
FilesDelete bool `json:"files.delete"`
|
||||
}
|
||||
|
||||
// Item describes a folder or a file as returned by /drive/file-entries
|
||||
type Item struct {
|
||||
ID json.Number `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description any `json:"description"`
|
||||
FileName string `json:"file_name"`
|
||||
Mime string `json:"mime"`
|
||||
Color any `json:"color"`
|
||||
Backup bool `json:"backup"`
|
||||
Tracked int `json:"tracked"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
UserID json.Number `json:"user_id"`
|
||||
ParentID json.Number `json:"parent_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt any `json:"deleted_at"`
|
||||
IsDeleted int `json:"is_deleted"`
|
||||
Path string `json:"path"`
|
||||
DiskPrefix any `json:"disk_prefix"`
|
||||
Type string `json:"type"`
|
||||
Extension any `json:"extension"`
|
||||
FileHash any `json:"file_hash"`
|
||||
Public bool `json:"public"`
|
||||
Thumbnail bool `json:"thumbnail"`
|
||||
MuxStatus any `json:"mux_status"`
|
||||
ThumbnailURL any `json:"thumbnail_url"`
|
||||
WorkspaceID int `json:"workspace_id"`
|
||||
IsEncrypted int `json:"is_encrypted"`
|
||||
Iv any `json:"iv"`
|
||||
VaultID any `json:"vault_id"`
|
||||
OwnerID int `json:"owner_id"`
|
||||
Hash string `json:"hash"`
|
||||
URL string `json:"url"`
|
||||
Users []User `json:"users"`
|
||||
Tags []any `json:"tags"`
|
||||
Permissions Permissions `json:"permissions"`
|
||||
}
|
||||
|
||||
// Listing response
|
||||
type Listing struct {
|
||||
CurrentPage int `json:"current_page"`
|
||||
Data []Item `json:"data"`
|
||||
From int `json:"from"`
|
||||
LastPage int `json:"last_page"`
|
||||
NextPage int `json:"next_page"`
|
||||
PerPage int `json:"per_page"`
|
||||
PrevPage int `json:"prev_page"`
|
||||
To int `json:"to"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// UploadResponse for a file
|
||||
type UploadResponse struct {
|
||||
Status string `json:"status"`
|
||||
FileEntry Item `json:"fileEntry"`
|
||||
}
|
||||
|
||||
// CreateFolderRequest for a folder
|
||||
type CreateFolderRequest struct {
|
||||
Name string `json:"name"`
|
||||
ParentID json.Number `json:"parentId,omitempty"`
|
||||
}
|
||||
|
||||
// CreateFolderResponse for a folder
|
||||
type CreateFolderResponse struct {
|
||||
Status string `json:"status"`
|
||||
Folder Item `json:"folder"`
|
||||
}
|
||||
|
||||
// Error is returned from drime when things go wrong
|
||||
type Error struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Error returns a string for the error and satisfies the error interface
|
||||
func (e Error) Error() string {
|
||||
out := fmt.Sprintf("Error %q", e.Message)
|
||||
return out
|
||||
}
|
||||
|
||||
// Check Error satisfies the error interface
|
||||
var _ error = (*Error)(nil)
|
||||
|
||||
// DeleteRequest is the input to DELETE /file-entries
|
||||
type DeleteRequest struct {
|
||||
EntryIDs []string `json:"entryIds"`
|
||||
DeleteForever bool `json:"deleteForever"`
|
||||
}
|
||||
|
||||
// DeleteResponse is the input to DELETE /file-entries
|
||||
type DeleteResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Errors map[string]string `json:"errors"`
|
||||
}
|
||||
|
||||
// UpdateItemRequest describes the updates to be done to an item for PUT /file-entries/{id}/
|
||||
type UpdateItemRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateItemResponse is returned by PUT /file-entries/{id}/
|
||||
type UpdateItemResponse struct {
|
||||
Status string `json:"status"`
|
||||
FileEntry Item `json:"fileEntry"`
|
||||
}
|
||||
|
||||
// MoveRequest is the input to /file-entries/move
|
||||
type MoveRequest struct {
|
||||
EntryIDs []string `json:"entryIds"`
|
||||
DestinationID string `json:"destinationId"`
|
||||
}
|
||||
|
||||
// MoveResponse is returned by POST /file-entries/move
|
||||
type MoveResponse struct {
|
||||
Status string `json:"status"`
|
||||
Entries []Item `json:"entries"`
|
||||
}
|
||||
|
||||
// CopyRequest is the input to /file-entries/duplicate
|
||||
type CopyRequest struct {
|
||||
EntryIDs []string `json:"entryIds"`
|
||||
DestinationID string `json:"destinationId"`
|
||||
}
|
||||
|
||||
// CopyResponse is returned by POST /file-entries/duplicate
|
||||
type CopyResponse struct {
|
||||
Status string `json:"status"`
|
||||
Entries []Item `json:"entries"`
|
||||
}
|
||||
|
||||
// MultiPartCreateRequest is the input of POST /s3/multipart/create
|
||||
type MultiPartCreateRequest struct {
|
||||
Filename string `json:"filename"`
|
||||
Mime string `json:"mime"`
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"extension"`
|
||||
ParentID json.Number `json:"parent_id"`
|
||||
RelativePath string `json:"relativePath"`
|
||||
}
|
||||
|
||||
// MultiPartCreateResponse is returned by POST /s3/multipart/create
|
||||
type MultiPartCreateResponse struct {
|
||||
UploadID string `json:"uploadId"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CompletedPart Type for completed parts when making a multipart upload.
|
||||
type CompletedPart struct {
|
||||
ETag string `json:"ETag"`
|
||||
PartNumber int32 `json:"PartNumber"`
|
||||
}
|
||||
|
||||
// MultiPartGetURLsRequest is the input of POST /s3/multipart/batch-sign-part-urls
|
||||
type MultiPartGetURLsRequest struct {
|
||||
UploadID string `json:"uploadId"`
|
||||
Key string `json:"key"`
|
||||
PartNumbers []int `json:"partNumbers"`
|
||||
}
|
||||
|
||||
// MultiPartGetURLsResponse is the result of POST /s3/multipart/batch-sign-part-urls
|
||||
type MultiPartGetURLsResponse struct {
|
||||
URLs []struct {
|
||||
URL string `json:"url"`
|
||||
PartNumber int32 `json:"partNumber"`
|
||||
} `json:"urls"`
|
||||
}
|
||||
|
||||
// MultiPartCompleteRequest is the input to POST /s3/multipart/complete
|
||||
type MultiPartCompleteRequest struct {
|
||||
UploadID string `json:"uploadId"`
|
||||
Key string `json:"key"`
|
||||
Parts []CompletedPart `json:"parts"`
|
||||
}
|
||||
|
||||
// MultiPartCompleteResponse is the result of POST /s3/multipart/complete
|
||||
type MultiPartCompleteResponse struct {
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
// MultiPartEntriesRequest is the input to POST /s3/entries
|
||||
type MultiPartEntriesRequest struct {
|
||||
ClientMime string `json:"clientMime"`
|
||||
ClientName string `json:"clientName"`
|
||||
Filename string `json:"filename"`
|
||||
Size int64 `json:"size"`
|
||||
ClientExtension string `json:"clientExtension"`
|
||||
ParentID json.Number `json:"parent_id"`
|
||||
RelativePath string `json:"relativePath"`
|
||||
}
|
||||
|
||||
// MultiPartEntriesResponse is the result of POST /s3/entries
|
||||
type MultiPartEntriesResponse struct {
|
||||
FileEntry Item `json:"fileEntry"`
|
||||
}
|
||||
|
||||
// MultiPartAbort is the input of POST /s3/multipart/abort
|
||||
type MultiPartAbort struct {
|
||||
UploadID string `json:"uploadId"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
// Drime filesystem interface
|
||||
package drime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fstest/fstests"
|
||||
)
|
||||
|
||||
// TestIntegration runs integration tests against the remote
|
||||
func TestIntegration(t *testing.T) {
|
||||
fstests.Run(t, &fstests.Opt{
|
||||
RemoteName: "TestDrime:",
|
||||
NilObject: (*Object)(nil),
|
||||
ChunkedUpload: fstests.ChunkedUploadConfig{
|
||||
MinChunkSize: minChunkSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Fs) SetUploadChunkSize(cs fs.SizeSuffix) (fs.SizeSuffix, error) {
|
||||
return f.setUploadChunkSize(cs)
|
||||
}
|
||||
|
||||
func (f *Fs) SetUploadCutoff(cs fs.SizeSuffix) (fs.SizeSuffix, error) {
|
||||
return f.setUploadCutoff(cs)
|
||||
}
|
||||
|
||||
var (
|
||||
_ fstests.SetUploadChunkSizer = (*Fs)(nil)
|
||||
_ fstests.SetUploadCutoffer = (*Fs)(nil)
|
||||
)
|
||||
@@ -204,12 +204,6 @@ Example:
|
||||
Help: `URL for HTTP CONNECT proxy
|
||||
|
||||
Set this to a URL for an HTTP proxy which supports the HTTP CONNECT verb.
|
||||
|
||||
Supports the format http://user:pass@host:port, http://host:port, http://host.
|
||||
|
||||
Example:
|
||||
|
||||
http://myUser:myPass@proxyhostname.example.com:8000
|
||||
`,
|
||||
Advanced: true,
|
||||
}, {
|
||||
|
||||
@@ -72,7 +72,7 @@ func (ik *ImageKit) Upload(ctx context.Context, file io.Reader, param UploadPara
|
||||
|
||||
response := &UploadResult{}
|
||||
|
||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, file, formParams, "file", param.FileName, "application/octet-stream")
|
||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, file, formParams, "file", param.FileName)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
||||
|
||||
@@ -1459,7 +1459,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
||||
// opts.Body=0), so upload it as a multipart form POST with
|
||||
// Content-Length set.
|
||||
if size == 0 {
|
||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, opts.Parameters, "content", leaf, opts.ContentType)
|
||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, opts.Parameters, "content", leaf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make multipart upload for 0 length file: %w", err)
|
||||
}
|
||||
|
||||
@@ -1384,7 +1384,7 @@ func (f *Fs) uploadByForm(ctx context.Context, in io.Reader, name string, size i
|
||||
for i := range iVal.NumField() {
|
||||
params.Set(iTyp.Field(i).Tag.Get("json"), iVal.Field(i).String())
|
||||
}
|
||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, params, "file", name, "application/octet-stream")
|
||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, params, "file", name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make multipart upload: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
name: BizflyCloud
|
||||
description: Bizfly Cloud Simple Storage
|
||||
region:
|
||||
hn: Ha Noi
|
||||
hcm: Ho Chi Minh
|
||||
endpoint:
|
||||
hn.ss.bfcplatform.vn: Hanoi endpoint
|
||||
hcm.ss.bfcplatform.vn: Ho Chi Minh endpoint
|
||||
acl: {}
|
||||
bucket_acl: true
|
||||
quirks:
|
||||
force_path_style: true
|
||||
list_url_encode: false
|
||||
use_multipart_etag: false
|
||||
use_already_exists: false
|
||||
@@ -2928,9 +2928,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
|
||||
req := s3.CopyObjectInput{
|
||||
MetadataDirective: types.MetadataDirectiveCopy,
|
||||
}
|
||||
if srcObj.storageClass != nil {
|
||||
req.StorageClass = types.StorageClass(*srcObj.storageClass)
|
||||
}
|
||||
|
||||
// Build upload options including headers and metadata
|
||||
ci := fs.GetConfig(ctx)
|
||||
uploadOptions := fs.MetadataAsOpenOptions(ctx)
|
||||
@@ -4503,12 +4501,7 @@ func (o *Object) prepareUpload(ctx context.Context, src fs.ObjectInfo, options [
|
||||
ACL: types.ObjectCannedACL(o.fs.opt.ACL),
|
||||
Key: &bucketPath,
|
||||
}
|
||||
if tierObj, ok := src.(fs.GetTierer); ok {
|
||||
tier := tierObj.GetTier()
|
||||
if tier != "" {
|
||||
ui.req.StorageClass = types.StorageClass(strings.ToUpper(tier))
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch metadata if --metadata is in use
|
||||
meta, err := fs.GetMetadataOptions(ctx, o.fs, src, options)
|
||||
if err != nil {
|
||||
|
||||
@@ -688,7 +688,7 @@ func (f *Fs) upload(ctx context.Context, in io.Reader, uploadLink, filePath stri
|
||||
"need_idx_progress": {"true"},
|
||||
"replace": {"1"},
|
||||
}
|
||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, in, parameters, "file", f.opt.Enc.FromStandardName(filename), "application/octet-stream")
|
||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, in, parameters, "file", f.opt.Enc.FromStandardName(filename))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
||||
}
|
||||
|
||||
@@ -519,12 +519,6 @@ Example:
|
||||
Help: `URL for HTTP CONNECT proxy
|
||||
|
||||
Set this to a URL for an HTTP proxy which supports the HTTP CONNECT verb.
|
||||
|
||||
Supports the format http://user:pass@host:port, http://host:port, http://host.
|
||||
|
||||
Example:
|
||||
|
||||
http://myUser:myPass@proxyhostname.example.com:8000
|
||||
`,
|
||||
Advanced: true,
|
||||
}, {
|
||||
@@ -925,8 +919,15 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
||||
opt.Port = "22"
|
||||
}
|
||||
|
||||
// Set up sshConfig here from opt
|
||||
// **NB** everything else should be setup in NewFsWithConnection
|
||||
// get proxy URL if set
|
||||
if opt.HTTPProxy != "" {
|
||||
proxyURL, err := url.Parse(opt.HTTPProxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse HTTP Proxy URL: %w", err)
|
||||
}
|
||||
f.proxyURL = proxyURL
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: opt.User,
|
||||
Auth: []ssh.AuthMethod{},
|
||||
@@ -1174,21 +1175,11 @@ func NewFsWithConnection(ctx context.Context, f *Fs, name string, root string, m
|
||||
f.mkdirLock = newStringLock()
|
||||
f.pacer = fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant)))
|
||||
f.savedpswd = ""
|
||||
|
||||
// set the pool drainer timer going
|
||||
if f.opt.IdleTimeout > 0 {
|
||||
f.drain = time.AfterFunc(time.Duration(f.opt.IdleTimeout), func() { _ = f.drainPool(ctx) })
|
||||
}
|
||||
|
||||
// get proxy URL if set
|
||||
if opt.HTTPProxy != "" {
|
||||
proxyURL, err := url.Parse(opt.HTTPProxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse HTTP Proxy URL: %w", err)
|
||||
}
|
||||
f.proxyURL = proxyURL
|
||||
}
|
||||
|
||||
f.features = (&fs.Features{
|
||||
CanHaveEmptyDirectories: true,
|
||||
SlowHash: true,
|
||||
|
||||
@@ -817,7 +817,7 @@ func (f *Fs) upload(ctx context.Context, name string, parent string, size int64,
|
||||
params.Set("filename", url.QueryEscape(name))
|
||||
params.Set("parent_id", parent)
|
||||
params.Set("override-name-exist", strconv.FormatBool(true))
|
||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, nil, "content", name, "application/octet-stream")
|
||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, nil, "content", name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ docs = [
|
||||
"compress.md",
|
||||
"combine.md",
|
||||
"doi.md",
|
||||
"drime.md"
|
||||
"dropbox.md",
|
||||
"filefabric.md",
|
||||
"filelu.md",
|
||||
|
||||
@@ -116,7 +116,6 @@ WebDAV or S3, that work out of the box.)
|
||||
{{< provider name="Akamai Netstorage" home="https://www.akamai.com/us/en/products/media-delivery/netstorage.jsp" config="/netstorage/" >}}
|
||||
{{< provider name="Alibaba Cloud (Aliyun) Object Storage System (OSS)" home="https://www.alibabacloud.com/product/oss/" config="/s3/#alibaba-oss" >}}
|
||||
{{< provider name="Amazon S3" home="https://aws.amazon.com/s3/" config="/s3/" >}}
|
||||
{{< provider name="Bizfly Cloud Simple Storage" home="https://bizflycloud.vn/" config="/s3/#bizflycloud" >}}
|
||||
{{< provider name="Backblaze B2" home="https://www.backblaze.com/cloud-storage" config="/b2/" >}}
|
||||
{{< provider name="Box" home="https://www.box.com/" config="/box/" >}}
|
||||
{{< provider name="Ceph" home="http://ceph.com/" config="/s3/#ceph" >}}
|
||||
@@ -129,7 +128,6 @@ WebDAV or S3, that work out of the box.)
|
||||
{{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}}
|
||||
{{< provider name="Digi Storage" home="https://storage.rcs-rds.ro/" config="/koofr/#digi-storage" >}}
|
||||
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
|
||||
{{< provider name="Drime" home="https://www.drime.cloud/" config="/drime/" >}}
|
||||
{{< provider name="Dropbox" home="https://www.dropbox.com/" config="/dropbox/" >}}
|
||||
{{< provider name="Enterprise File Fabric" home="https://storagemadeeasy.com/about/" config="/filefabric/" >}}
|
||||
{{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}}
|
||||
|
||||
@@ -1060,6 +1060,3 @@ put them back in again. -->
|
||||
- jhasse-shade <jacob@shade.inc>
|
||||
- vyv03354 <VYV03354@nifty.ne.jp>
|
||||
- masrlinu <masrlinu@users.noreply.github.com> <5259918+masrlinu@users.noreply.github.com>
|
||||
- vupn0712 <126212736+vupn0712@users.noreply.github.com>
|
||||
- darkdragon-001 <darkdragon-001@users.noreply.github.com>
|
||||
- sys6101 <csvmen@gmail.com>
|
||||
|
||||
@@ -336,7 +336,7 @@ full new copy of the file.
|
||||
When mounting with `--read-only`, attempts to write to files will fail *silently*
|
||||
as opposed to with a clear warning as in macFUSE.
|
||||
|
||||
## Mounting on Linux
|
||||
# Mounting on Linux
|
||||
|
||||
On newer versions of Ubuntu, you may encounter the following error when running
|
||||
`rclone mount`:
|
||||
|
||||
@@ -43,7 +43,6 @@ See the following for detailed instructions for
|
||||
- [Crypt](/crypt/) - to encrypt other remotes
|
||||
- [DigitalOcean Spaces](/s3/#digitalocean-spaces)
|
||||
- [Digi Storage](/koofr/#digi-storage)
|
||||
- [Drime](/drime/)
|
||||
- [Dropbox](/dropbox/)
|
||||
- [Enterprise File Fabric](/filefabric/)
|
||||
- [FileLu Cloud Storage](/filelu/)
|
||||
@@ -3279,6 +3278,10 @@ The available flags are:
|
||||
- `mapper` dumps the JSON blobs being sent to the program supplied with
|
||||
`--metadata-mapper` and received from it. It can be useful for debugging
|
||||
the metadata mapper interface.
|
||||
- `curl` dumps the HTTP request as a `curl` command. Can be used with
|
||||
the other HTTP debugging flags (e.g. `requests`, `bodies`). By
|
||||
default the auth will be masked - use with `auth` to have the curl
|
||||
commands with authentication too.
|
||||
|
||||
## Filtering
|
||||
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
---
|
||||
title: "Drime"
|
||||
description: "Rclone docs for Drime"
|
||||
versionIntroduced: "v1.73"
|
||||
---
|
||||
|
||||
# {{< icon "fa fa-cloud" >}} Drime
|
||||
|
||||
[Drime](https://drime.cloud/) is a cloud storage and transfer service focused
|
||||
on fast, resilient file delivery. It offers both free and paid tiers with
|
||||
emphasis on high-speed uploads and link sharing.
|
||||
|
||||
To setup Drime you need to log in, navigate to Settings, Developer, and create a
|
||||
token to use as an API access key. Give it a sensible name and copy the token
|
||||
for use in the config.
|
||||
|
||||
## Configuration
|
||||
|
||||
Here is a run through of `rclone config` to make a remote called `remote`.
|
||||
|
||||
Firstly run:
|
||||
|
||||
|
||||
```console
|
||||
rclone config
|
||||
```
|
||||
|
||||
Then follow through the interactive setup:
|
||||
|
||||
|
||||
```text
|
||||
No remotes found, make a new one?
|
||||
n) New remote
|
||||
s) Set configuration password
|
||||
q) Quit config
|
||||
n/s/q> n
|
||||
|
||||
Enter name for new remote.
|
||||
name> remote
|
||||
|
||||
Option Storage.
|
||||
Type of storage to configure.
|
||||
Choose a number from below, or type in your own value.
|
||||
XX / Drime
|
||||
\ (drime)
|
||||
Storage> drime
|
||||
|
||||
Option access_token.
|
||||
API Access token
|
||||
You can get this from the web control panel.
|
||||
Enter a value. Press Enter to leave empty.
|
||||
access_token> YOUR_API_ACCESS_TOKEN
|
||||
|
||||
Edit advanced config?
|
||||
y) Yes
|
||||
n) No (default)
|
||||
y/n> n
|
||||
|
||||
Configuration complete.
|
||||
Options:
|
||||
- type: drime
|
||||
- access_token: YOUR_API_ACCESS_TOKEN
|
||||
Keep this "remote" remote?
|
||||
y) Yes this is OK (default)
|
||||
e) Edit this remote
|
||||
d) Delete this remote
|
||||
y/e/d> y
|
||||
```
|
||||
|
||||
Once configured you can then use `rclone` like this (replace `remote` with the
|
||||
name you gave your remote):
|
||||
|
||||
List directories and files in the top level of your Drime
|
||||
|
||||
```console
|
||||
rclone lsf remote:
|
||||
```
|
||||
|
||||
To copy a local directory to a Drime directory called backup
|
||||
|
||||
```console
|
||||
rclone copy /home/source remote:backup
|
||||
```
|
||||
|
||||
|
||||
### Modification times and hashes
|
||||
|
||||
Drime does not support modification times or hashes.
|
||||
|
||||
This means that by default syncs will only use the size of the file to determine
|
||||
if it needs updating.
|
||||
|
||||
You can use the `--update` flag which will use the time the object was uploaded.
|
||||
For many operations this is sufficient to determine if it has changed. However
|
||||
files created with timestamps in the past will be missed by the sync if using
|
||||
`--update`.
|
||||
|
||||
|
||||
### Restricted filename characters
|
||||
|
||||
In addition to the [default restricted characters set](/overview/#restricted-characters)
|
||||
the following characters are also replaced:
|
||||
|
||||
| Character | Value | Replacement |
|
||||
| --------- |:-----:|:-----------:|
|
||||
| \ | 0x5C | \ |
|
||||
|
||||
File names can also not start or end with the following characters. These only
|
||||
get replaced if they are the first or last character in the name:
|
||||
|
||||
| Character | Value | Replacement |
|
||||
| --------- |:-----:|:-----------:|
|
||||
| SP | 0x20 | ␠ |
|
||||
|
||||
Invalid UTF-8 bytes will also be [replaced](/overview/#invalid-utf8),
|
||||
as they can't be used in JSON strings.
|
||||
|
||||
### Root folder ID
|
||||
|
||||
You can set the `root_folder_id` for rclone. This is the directory
|
||||
(identified by its `Folder ID`) that rclone considers to be the root
|
||||
of your Drime drive.
|
||||
|
||||
Normally you will leave this blank and rclone will determine the
|
||||
correct root to use itself and fill in the value in the config file.
|
||||
|
||||
However you can set this to restrict rclone to a specific folder
|
||||
hierarchy.
|
||||
|
||||
In order to do this you will have to find the `Folder ID` of the
|
||||
directory you wish rclone to display.
|
||||
|
||||
You can do this with rclone
|
||||
|
||||
```console
|
||||
$ rclone lsf -Fip --dirs-only remote:
|
||||
d6341f53-ee65-4f29-9f59-d11e8070b2a0;Files/
|
||||
f4f5c9b8-6ece-478b-b03e-4538edfe5a1c;Photos/
|
||||
d50e356c-29ca-4b27-a3a7-494d91026e04;Videos/
|
||||
```
|
||||
|
||||
The ID to use is the part before the `;` so you could set
|
||||
|
||||
```text
|
||||
root_folder_id = d6341f53-ee65-4f29-9f59-d11e8070b2a0
|
||||
```
|
||||
|
||||
To restrict rclone to the `Files` directory.
|
||||
|
||||
<!-- autogenerated options start - DO NOT EDIT - instead edit fs.RegInfo in backend/drime/drime.go and run make backenddocs to verify --> <!-- markdownlint-disable-line line-length -->
|
||||
### Standard options
|
||||
|
||||
Here are the Standard options specific to drime (Drime).
|
||||
|
||||
#### --drime-access-token
|
||||
|
||||
API Access token
|
||||
|
||||
You can get this from the web control panel.
|
||||
|
||||
Properties:
|
||||
|
||||
- Config: access_token
|
||||
- Env Var: RCLONE_DRIME_ACCESS_TOKEN
|
||||
- Type: string
|
||||
- Required: false
|
||||
|
||||
### Advanced options
|
||||
|
||||
Here are the Advanced options specific to drime (Drime).
|
||||
|
||||
#### --drime-root-folder-id
|
||||
|
||||
ID of the root folder
|
||||
|
||||
Leave this blank normally, rclone will fill it in automatically.
|
||||
|
||||
If you want rclone to be restricted to a particular folder you can
|
||||
fill it in - see the docs for more info.
|
||||
|
||||
|
||||
Properties:
|
||||
|
||||
- Config: root_folder_id
|
||||
- Env Var: RCLONE_DRIME_ROOT_FOLDER_ID
|
||||
- Type: string
|
||||
- Required: false
|
||||
|
||||
#### --drime-workspace-id
|
||||
|
||||
Account ID
|
||||
|
||||
Leave this blank normally, rclone will fill it in automatically.
|
||||
|
||||
|
||||
Properties:
|
||||
|
||||
- Config: workspace_id
|
||||
- Env Var: RCLONE_DRIME_WORKSPACE_ID
|
||||
- Type: string
|
||||
- Required: false
|
||||
|
||||
#### --drime-list-chunk
|
||||
|
||||
Number of items to list in each call
|
||||
|
||||
Properties:
|
||||
|
||||
- Config: list_chunk
|
||||
- Env Var: RCLONE_DRIME_LIST_CHUNK
|
||||
- Type: int
|
||||
- Default: 1000
|
||||
|
||||
#### --drime-encoding
|
||||
|
||||
The encoding for the backend.
|
||||
|
||||
See the [encoding section in the overview](/overview/#encoding) for more info.
|
||||
|
||||
Properties:
|
||||
|
||||
- Config: encoding
|
||||
- Env Var: RCLONE_DRIME_ENCODING
|
||||
- Type: Encoding
|
||||
- Default: Slash,BackSlash,Del,Ctl,LeftSpace,RightSpace,InvalidUtf8,Dot
|
||||
|
||||
#### --drime-description
|
||||
|
||||
Description of the remote.
|
||||
|
||||
Properties:
|
||||
|
||||
- Config: description
|
||||
- Env Var: RCLONE_DRIME_DESCRIPTION
|
||||
- Type: string
|
||||
- Required: false
|
||||
|
||||
<!-- autogenerated options stop -->
|
||||
|
||||
## Limitations
|
||||
|
||||
Drime only supports filenames up to 255 bytes in length, where filenames are
|
||||
encoded in UTF8.
|
||||
|
||||
@@ -498,12 +498,6 @@ URL for HTTP CONNECT proxy
|
||||
|
||||
Set this to a URL for an HTTP proxy which supports the HTTP CONNECT verb.
|
||||
|
||||
Supports the format http://user:pass@host:port, http://host:port, http://host.
|
||||
|
||||
Example:
|
||||
|
||||
http://myUser:myPass@proxyhostname.example.com:8000
|
||||
|
||||
|
||||
Properties:
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ Here is an overview of the major features of each cloud storage system.
|
||||
| Box | SHA1 | R/W | Yes | No | - | - |
|
||||
| Citrix ShareFile | MD5 | R/W | Yes | No | - | - |
|
||||
| Cloudinary | MD5 | R | No | Yes | - | - |
|
||||
| Drime | - | - | No | No | R/W | - |
|
||||
| Dropbox | DBHASH ¹ | R | Yes | No | - | - |
|
||||
| Enterprise File Fabric | - | R/W | Yes | No | R/W | - |
|
||||
| FileLu Cloud Storage | MD5 | R/W | No | Yes | R | - |
|
||||
@@ -516,7 +515,6 @@ upon backend-specific capabilities.
|
||||
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | Yes | No | No |
|
||||
| Box | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | Yes |
|
||||
| Citrix ShareFile | Yes | Yes | Yes | Yes | No | No | No | No | No | No | Yes |
|
||||
| Drime | Yes | Yes | Yes | Yes | No | No | Yes | Yes | No | No | Yes |
|
||||
| Dropbox | Yes | Yes | Yes | Yes | No | No | Yes | No | Yes | Yes | Yes |
|
||||
| Cloudinary | No | No | No | No | No | No | Yes | No | No | No | No |
|
||||
| Enterprise File Fabric | Yes | Yes | Yes | Yes | Yes | No | No | No | No | No | Yes |
|
||||
|
||||
@@ -18,7 +18,6 @@ The S3 backend can be used with a number of different providers:
|
||||
{{< provider name="China Mobile Ecloud Elastic Object Storage (EOS)" home="https://ecloud.10086.cn/home/product-introduction/eos/" config="/s3/#china-mobile-ecloud-eos" >}}
|
||||
{{< provider name="Cloudflare R2" home="https://blog.cloudflare.com/r2-open-beta/" config="/s3/#cloudflare-r2" >}}
|
||||
{{< provider name="Arvan Cloud Object Storage (AOS)" home="https://www.arvancloud.com/en/products/cloud-storage" config="/s3/#arvan-cloud" >}}
|
||||
{{< provider name="Bizfly Cloud Simple Storage" home="https://bizflycloud.vn/" config="/s3/#bizflycloud" >}}
|
||||
{{< provider name="Cubbit DS3" home="https://cubbit.io/ds3-cloud" config="/s3/#Cubbit" >}}
|
||||
{{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}}
|
||||
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
|
||||
@@ -4537,36 +4536,6 @@ server_side_encryption =
|
||||
storage_class =
|
||||
```
|
||||
|
||||
### BizflyCloud {#bizflycloud}
|
||||
|
||||
[Bizfly Cloud Simple Storage](https://bizflycloud.vn/simple-storage) is an
|
||||
S3-compatible service with regions in Hanoi (HN) and Ho Chi Minh City (HCM).
|
||||
|
||||
Use the endpoint for your region:
|
||||
|
||||
- HN: `hn.ss.bfcplatform.vn`
|
||||
- HCM: `hcm.ss.bfcplatform.vn`
|
||||
|
||||
A minimal configuration looks like this.
|
||||
|
||||
```ini
|
||||
[bizfly]
|
||||
type = s3
|
||||
provider = BizflyCloud
|
||||
env_auth = false
|
||||
access_key_id = YOUR_ACCESS_KEY
|
||||
secret_access_key = YOUR_SECRET_KEY
|
||||
region = HN
|
||||
endpoint = hn.ss.bfcplatform.vn
|
||||
location_constraint =
|
||||
acl =
|
||||
server_side_encryption =
|
||||
storage_class =
|
||||
```
|
||||
|
||||
Switch `region` and `endpoint` to `HCM` and `hcm.ss.bfcplatform.vn` for Ho Chi
|
||||
Minh City.
|
||||
|
||||
### Ceph
|
||||
|
||||
[Ceph](https://ceph.com/) is an open-source, unified, distributed
|
||||
|
||||
@@ -1186,12 +1186,6 @@ URL for HTTP CONNECT proxy
|
||||
|
||||
Set this to a URL for an HTTP proxy which supports the HTTP CONNECT verb.
|
||||
|
||||
Supports the format http://user:pass@host:port, http://host:port, http://host.
|
||||
|
||||
Example:
|
||||
|
||||
http://myUser:myPass@proxyhostname.example.com:8000
|
||||
|
||||
|
||||
Properties:
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Thank you to our sponsors:
|
||||
<!-- markdownlint-capture -->
|
||||
<!-- markdownlint-disable line-length no-bare-urls -->
|
||||
|
||||
{{< sponsor src="/img/logos/rabata.svg" width="300" height="200" title="Visit our sponsor Rabata.io" link="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general">}}
|
||||
{{< sponsor src="/img/logos/rabata/txt_1_300x114.png" width="300" height="200" title="Visit our sponsor Rabata.io" link="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general">}}
|
||||
{{< sponsor src="/img/logos/idrive_e2.svg" width="300" height="200" title="Visit our sponsor IDrive e2" link="https://www.idrive.com/e2/?refer=rclone">}}
|
||||
{{< sponsor src="/img/logos/filescom-enterprise-grade-workflows.png" width="300" height="200" title="Start Your Free Trial Today" link="https://files.com/?utm_source=rclone&utm_medium=referral&utm_campaign=banner&utm_term=rclone">}}
|
||||
{{< sponsor src="/img/logos/mega-s4.svg" width="300" height="200" title="MEGA S4: New S3 compatible object storage. High scale. Low cost. Free egress." link="https://mega.io/objectstorage?utm_source=rclone&utm_medium=referral&utm_campaign=rclone-mega-s4&mct=rclonepromo">}}
|
||||
|
||||
@@ -10,21 +10,40 @@
|
||||
{{end}}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Platinum Sponsor</div>
|
||||
<div class="card-header">
|
||||
Platinum Sponsor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general" target="_blank" rel="noopener" title="Visit rclone's sponsor Rabata.io"><img src="/img/logos/rabata.svg"></a><br />
|
||||
<a id="platinum" href="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general" target="_blank" rel="noopener" title="Visit rclone's sponsor Rabata.io"><img style="width: 100%; height: auto;" src="/img/logos/rabata/txt_1_website.png"></a><br />
|
||||
<script>
|
||||
const imgs = [
|
||||
{ href: "https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general", img: "/img/logos/rabata/txt_1_website.png" },
|
||||
{ href: "https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general", img: "/img/logos/rabata/txt_2_website.png" },
|
||||
{ href: "https://rabata.io/grant-application?utm_source=banner&utm_medium=rclone&utm_content=grant1", img: "/img/logos/rabata/100k_website.png" },
|
||||
];
|
||||
const img = imgs[Math.floor(Math.random() * imgs.length)];
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const a = document.getElementById("platinum");
|
||||
a.href = img.href;
|
||||
a.querySelector("img").src = img.img;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Gold Sponsor</div>
|
||||
<div class="card-header">
|
||||
Gold Sponsor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="https://www.idrive.com/e2/?refer=rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor IDrive e2"><img src="/img/logos/idrive_e2.svg" viewBox="0 0 60 55"></a><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Gold Sponsor</div>
|
||||
<div class="card-header">
|
||||
Gold Sponsor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="https://files.com/?utm_source=rclone&utm_medium=referral&utm_campaign=banner&utm_term=rclone" target="_blank" rel="noopener" title="Start Your Free Trial Today"><img style="max-width: 100%; height: auto;" src="/img/logos/filescom-enterprise-grade-workflows.png"></a><br />
|
||||
</div>
|
||||
@@ -32,19 +51,25 @@
|
||||
|
||||
{{if .IsHome}}
|
||||
<div class="card">
|
||||
<div class="card-header">Silver Sponsor</div>
|
||||
<div class="card-header">
|
||||
Silver Sponsor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="https://rcloneview.com/?utm_source=rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor RcloneView"><img src="/img/logos/rcloneview.svg"></a><br />
|
||||
<a href="https://rcloneview.com/?utm_source=rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor RcloneView"><img src="/img/logos/rcloneview-banner.svg"></a><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">Silver Sponsor</div>
|
||||
<div class="card-header">
|
||||
Silver Sponsor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="https://github.com/rclone-ui/rclone-ui" target="_blank" rel="noopener" title="Visit rclone's sponsor rclone UI"><img src="/img/logos/rcloneui.svg"></a><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">Silver Sponsor</div>
|
||||
<div class="card-header">
|
||||
Silver Sponsor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="https://shade.inc/" target="_blank" rel="noopener" title="Visit rclone's sponsor Shade"><img style="max-width: 100%; height: auto;" src="/img/logos/shade.svg"></a><br />
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
<a class="dropdown-item" href="/sharefile/"><i class="fas fa-share-square fa-fw"></i> Citrix ShareFile</a>
|
||||
<a class="dropdown-item" href="/crypt/"><i class="fa fa-lock fa-fw"></i> Crypt (encrypts the others)</a>
|
||||
<a class="dropdown-item" href="/koofr/#digi-storage"><i class="fa fa-cloud fa-fw"></i> Digi Storage</a>
|
||||
<a class="dropdown-item" href="/drime/"><i class="fab fa-cloud fa-fw"></i> Drime</a>
|
||||
<a class="dropdown-item" href="/dropbox/"><i class="fab fa-dropbox fa-fw"></i> Dropbox</a>
|
||||
<a class="dropdown-item" href="/filefabric/"><i class="fa fa-cloud fa-fw"></i> Enterprise File Fabric</a>
|
||||
<a class="dropdown-item" href="/filelu/"><i class="fa fa-folder fa-fw"></i> FileLu Cloud Storage</a>
|
||||
|
||||
@@ -14,6 +14,7 @@ const (
|
||||
DumpGoRoutines
|
||||
DumpOpenFiles
|
||||
DumpMapper
|
||||
DumpCurl
|
||||
)
|
||||
|
||||
type dumpChoices struct{}
|
||||
@@ -29,6 +30,7 @@ func (dumpChoices) Choices() []BitsChoicesInfo {
|
||||
{uint64(DumpGoRoutines), "goroutines"},
|
||||
{uint64(DumpOpenFiles), "openfiles"},
|
||||
{uint64(DumpMapper), "mapper"},
|
||||
{uint64(DumpCurl), "curl"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -24,6 +26,7 @@ import (
|
||||
"github.com/rclone/rclone/lib/structs"
|
||||
"github.com/youmark/pkcs8"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"moul.io/http2curl/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -439,6 +442,18 @@ func cleanAuths(buf []byte) []byte {
|
||||
return buf
|
||||
}
|
||||
|
||||
// cleanCurl gets rid of Auth headers in a curl command
|
||||
func cleanCurl(cmd *http2curl.CurlCommand) {
|
||||
for _, authBuf := range authBufs {
|
||||
auth := "'" + string(authBuf)
|
||||
for i, arg := range *cmd {
|
||||
if strings.HasPrefix(arg, auth) {
|
||||
(*cmd)[i] = auth + "XXXX'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var expireWindow = 30 * time.Second
|
||||
|
||||
func isCertificateExpired(cc *tls.Config) bool {
|
||||
@@ -492,6 +507,26 @@ func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error
|
||||
fs.Debugf(nil, "%s", separatorReq)
|
||||
logMutex.Unlock()
|
||||
}
|
||||
// Dump curl request
|
||||
if t.dump&(fs.DumpCurl) != 0 {
|
||||
cmd, err := http2curl.GetCurlCommand(req)
|
||||
if err != nil {
|
||||
fs.Debugf(nil, "Failed to create curl command: %v", err)
|
||||
} else {
|
||||
// Patch -X HEAD into --head
|
||||
for i := range len(*cmd) - 1 {
|
||||
if (*cmd)[i] == "-X" && (*cmd)[i+1] == "'HEAD'" {
|
||||
(*cmd)[i] = "--head"
|
||||
*cmd = slices.Delete(*cmd, i+1, i+2)
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.dump&fs.DumpAuth == 0 {
|
||||
cleanCurl(cmd)
|
||||
}
|
||||
fs.Debugf(nil, "HTTP REQUEST: %v", cmd)
|
||||
}
|
||||
}
|
||||
// Do round trip
|
||||
resp, err = t.Transport.RoundTrip(req)
|
||||
// Logf response
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"moul.io/http2curl/v2"
|
||||
)
|
||||
|
||||
func TestCleanAuth(t *testing.T) {
|
||||
@@ -61,6 +62,32 @@ func TestCleanAuths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanCurl(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in []string
|
||||
want []string
|
||||
}{{
|
||||
[]string{""},
|
||||
[]string{""},
|
||||
}, {
|
||||
[]string{"floo"},
|
||||
[]string{"floo"},
|
||||
}, {
|
||||
[]string{"'Authorization: AAAAAAAAA'", "'Potato: Help'", ""},
|
||||
[]string{"'Authorization: XXXX'", "'Potato: Help'", ""},
|
||||
}, {
|
||||
[]string{"'X-Auth-Token: AAAAAAAAA'", "'Potato: Help'", ""},
|
||||
[]string{"'X-Auth-Token: XXXX'", "'Potato: Help'", ""},
|
||||
}, {
|
||||
[]string{"'X-Auth-Token: AAAAAAAAA'", "'Authorization: AAAAAAAAA'", "'Potato: Help'", ""},
|
||||
[]string{"'X-Auth-Token: XXXX'", "'Authorization: XXXX'", "'Potato: Help'", ""},
|
||||
}} {
|
||||
in := http2curl.CurlCommand(test.in)
|
||||
cleanCurl(&in)
|
||||
assert.Equal(t, test.want, test.in, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
var certSerial = int64(0)
|
||||
|
||||
// Create a test certificate and key pair that is valid for a specific
|
||||
|
||||
@@ -561,7 +561,7 @@ func TestUploadFile(t *testing.T) {
|
||||
assert.NoError(t, currentFile.Close())
|
||||
}()
|
||||
|
||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, currentFile, url.Values{}, "file", testFileName, "application/octet-stream")
|
||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, currentFile, url.Values{}, "file", testFileName)
|
||||
require.NoError(t, err)
|
||||
|
||||
httpReq := httptest.NewRequest("POST", "/", formReader)
|
||||
@@ -587,7 +587,7 @@ func TestUploadFile(t *testing.T) {
|
||||
assert.NoError(t, currentFile2.Close())
|
||||
}()
|
||||
|
||||
formReader, contentType, _, err = rest.MultipartUpload(ctx, currentFile2, url.Values{}, "file", testFileName, "application/octet-stream")
|
||||
formReader, contentType, _, err = rest.MultipartUpload(ctx, currentFile2, url.Values{}, "file", testFileName)
|
||||
require.NoError(t, err)
|
||||
|
||||
httpReq = httptest.NewRequest("POST", "/", formReader)
|
||||
|
||||
@@ -1273,14 +1273,10 @@ func Run(t *testing.T, opt *Opt) {
|
||||
assert.Equal(t, file2Copy.Path, dst.Remote())
|
||||
|
||||
// check that mutating dst does not mutate src
|
||||
if !strings.Contains(fs.ConfigStringFull(f), "copy_is_hardlink") {
|
||||
err = dst.SetModTime(ctx, fstest.Time("2004-03-03T04:05:06.499999999Z"))
|
||||
if err != fs.ErrorCantSetModTimeWithoutDelete && err != fs.ErrorCantSetModTime {
|
||||
assert.NoError(t, err)
|
||||
// Re-read the source and check its modtime
|
||||
src = fstest.NewObject(ctx, t, f, src.Remote())
|
||||
assert.False(t, src.ModTime(ctx).Equal(dst.ModTime(ctx)), "mutating dst should not mutate src -- is it Copying by pointer?")
|
||||
}
|
||||
err = dst.SetModTime(ctx, fstest.Time("2004-03-03T04:05:06.499999999Z"))
|
||||
if err != fs.ErrorCantSetModTimeWithoutDelete && err != fs.ErrorCantSetModTime {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, src.ModTime(ctx).Equal(dst.ModTime(ctx)), "mutating dst should not mutate src -- is it Copying by pointer?")
|
||||
}
|
||||
|
||||
// Delete copy
|
||||
|
||||
@@ -677,9 +677,3 @@ backends:
|
||||
# with the parent backend having a different precision.
|
||||
- TestServerSideCopyOverSelf
|
||||
- TestServerSideMoveOverSelf
|
||||
- backend: "drime"
|
||||
remote: "TestDrime:"
|
||||
ignoretests:
|
||||
# The TestBisyncRemoteLocal/check_access_filters tests fail due to duplicated objects
|
||||
- cmd/bisync
|
||||
fastlist: false
|
||||
|
||||
@@ -361,6 +361,9 @@ func (dc *DirCache) RootParentID(ctx context.Context, create bool) (ID string, e
|
||||
} else if dc.rootID == dc.trueRootID {
|
||||
return "", errors.New("is root directory")
|
||||
}
|
||||
if dc.rootParentID == "" {
|
||||
return "", errors.New("internal error: didn't find rootParentID")
|
||||
}
|
||||
return dc.rootParentID, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package proxy
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -56,13 +55,7 @@ func HTTPConnectDial(network, addr string, proxyURL *url.URL, proxyDialer proxy.
|
||||
}
|
||||
|
||||
// send CONNECT
|
||||
user := proxyURL.User
|
||||
if user != nil {
|
||||
credential := base64.StdEncoding.EncodeToString([]byte(user.String()))
|
||||
_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Authorization: Basic %s\r\n\r\n", addr, addr, credential)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
|
||||
}
|
||||
_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, fmt.Errorf("HTTP CONNECT proxy failed to send CONNECT: %q", err)
|
||||
|
||||
@@ -14,9 +14,7 @@ import (
|
||||
"maps"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rclone/rclone/fs"
|
||||
@@ -147,7 +145,6 @@ type Opts struct {
|
||||
MultipartMetadataName string // ..this is used for the name of the metadata form part if set
|
||||
MultipartContentName string // ..name of the parameter which is the attached file
|
||||
MultipartFileName string // ..name of the file for the attached file
|
||||
MultipartContentType string // ..content type of the attached file
|
||||
Parameters url.Values // any parameters for the final URL
|
||||
TransferEncoding []string // transfer encoding, set to "identity" to disable chunked encoding
|
||||
Trailer *http.Header // set the request trailer
|
||||
@@ -374,32 +371,6 @@ func (api *Client) Call(ctx context.Context, opts *Opts) (resp *http.Response, e
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
|
||||
func escapeQuotes(s string) string {
|
||||
return quoteEscaper.Replace(s)
|
||||
}
|
||||
|
||||
// multipartFileContentDisposition returns the value of a Content-Disposition header
|
||||
// with the provided field name and file name.
|
||||
func multipartFileContentDisposition(fieldname, filename string) string {
|
||||
return fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
|
||||
escapeQuotes(fieldname), escapeQuotes(filename))
|
||||
}
|
||||
|
||||
// CreateFormFile is a convenience wrapper around [Writer.CreatePart]. It creates
|
||||
// a new form-data header with the provided field name and file name.
|
||||
func CreateFormFile(w *multipart.Writer, fieldname, filename, contentType string) (io.Writer, error) {
|
||||
h := make(textproto.MIMEHeader)
|
||||
// FIXME when go1.24 is no longer supported, change to
|
||||
// multipart.FileContentDisposition and remove definition above
|
||||
h.Set("Content-Disposition", multipartFileContentDisposition(fieldname, filename))
|
||||
if contentType != "" {
|
||||
h.Set("Content-Type", contentType)
|
||||
}
|
||||
return w.CreatePart(h)
|
||||
}
|
||||
|
||||
// MultipartUpload creates an io.Reader which produces an encoded a
|
||||
// multipart form upload from the params passed in and the passed in
|
||||
//
|
||||
@@ -411,10 +382,10 @@ func CreateFormFile(w *multipart.Writer, fieldname, filename, contentType string
|
||||
// the int64 returned is the overhead in addition to the file contents, in case Content-Length is required
|
||||
//
|
||||
// NB This doesn't allow setting the content type of the attachment
|
||||
func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, contentName, fileName string, contentType string) (io.ReadCloser, string, int64, error) {
|
||||
func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, contentName, fileName string) (io.ReadCloser, string, int64, error) {
|
||||
bodyReader, bodyWriter := io.Pipe()
|
||||
writer := multipart.NewWriter(bodyWriter)
|
||||
formContentType := writer.FormDataContentType()
|
||||
contentType := writer.FormDataContentType()
|
||||
|
||||
// Create a Multipart Writer as base for calculating the Content-Length
|
||||
buf := &bytes.Buffer{}
|
||||
@@ -433,7 +404,7 @@ func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, conte
|
||||
}
|
||||
}
|
||||
if in != nil {
|
||||
_, err = CreateFormFile(dummyMultipartWriter, contentName, fileName, contentType)
|
||||
_, err = dummyMultipartWriter.CreateFormFile(contentName, fileName)
|
||||
if err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
@@ -474,7 +445,7 @@ func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, conte
|
||||
}
|
||||
|
||||
if in != nil {
|
||||
part, err := CreateFormFile(writer, contentName, fileName, contentType)
|
||||
part, err := writer.CreateFormFile(contentName, fileName)
|
||||
if err != nil {
|
||||
_ = bodyWriter.CloseWithError(fmt.Errorf("failed to create form file: %w", err))
|
||||
return
|
||||
@@ -496,7 +467,7 @@ func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, conte
|
||||
_ = bodyWriter.Close()
|
||||
}()
|
||||
|
||||
return bodyReader, formContentType, multipartLength, nil
|
||||
return bodyReader, contentType, multipartLength, nil
|
||||
}
|
||||
|
||||
// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
|
||||
@@ -568,7 +539,7 @@ func (api *Client) callCodec(ctx context.Context, opts *Opts, request any, respo
|
||||
opts = opts.Copy()
|
||||
|
||||
var overhead int64
|
||||
opts.Body, opts.ContentType, overhead, err = MultipartUpload(ctx, opts.Body, params, opts.MultipartContentName, opts.MultipartFileName, opts.MultipartContentType)
|
||||
opts.Body, opts.ContentType, overhead, err = MultipartUpload(ctx, opts.Body, params, opts.MultipartContentName, opts.MultipartFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user