diff --git a/block_types.go b/block_types.go index ad41611..384c8ef 100644 --- a/block_types.go +++ b/block_types.go @@ -17,17 +17,23 @@ type Block struct { type BlockUploadReq struct { AddressID string ShareID string + VolumeID string `json:",omitempty"` LinkID string RevisionID string BlockList []BlockUploadInfo } +type Verifier struct { + Token string +} + type BlockUploadInfo struct { Index int - Size int64 - EncSignature string - Hash string + Verifier *Verifier `json:",omitempty"` + Size int64 `json:",omitempty"` + EncSignature string `json:",omitempty"` + Hash string `json:",omitempty"` } type BlockUploadLink struct { diff --git a/link.go b/link.go index 5822256..dbae317 100644 --- a/link.go +++ b/link.go @@ -36,6 +36,20 @@ func (c *Client) MoveLink(ctx context.Context, shareID, linkID string, req MoveL return nil } +func (c *Client) MoveLinkByVolume(ctx context.Context, volumeID, linkID string, req MoveLinkReq) error { + var res struct { + Code int + } + + if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) { + return r.SetResult(&res).SetBody(req).Put("/drive/v2/volumes/" + volumeID + "/links/" + linkID + "/move") + }); err != nil { + return err + } + + return nil +} + func (c *Client) CreateFile(ctx context.Context, shareID string, req CreateFileReq) (CreateFileRes, error) { var res struct { Code int diff --git a/link_file.go b/link_file.go index 173d9b8..a2c849b 100644 --- a/link_file.go +++ b/link_file.go @@ -89,3 +89,23 @@ func (c *Client) CreateRevision(ctx context.Context, shareID, linkID string) (Cr return res.Revision, nil } + +func (c *Client) GetRevisionVerification(ctx context.Context, shareID, linkID, revisionID string) (RevisionVerificationRes, error) { + var res struct { + VerificationCode string + ContentKeyPacket string + } + + if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) { + return r. + SetResult(&res). + Get("/drive/shares/" + shareID + "/links/" + linkID + "/revisions/" + revisionID + "/verification") + }); err != nil { + return RevisionVerificationRes{}, err + } + + return RevisionVerificationRes{ + VerificationCode: res.VerificationCode, + ContentKeyPacket: res.ContentKeyPacket, + }, nil +} diff --git a/link_file_route_test.go b/link_file_route_test.go new file mode 100644 index 0000000..9deaab1 --- /dev/null +++ b/link_file_route_test.go @@ -0,0 +1,66 @@ +package proton_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/rclone/go-proton-api" + "github.com/stretchr/testify/require" +) + +func TestMoveLinkByVolumeUsesV2Endpoint(t *testing.T) { + var gotMethod string + var gotPath string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotMethod = r.Method + gotPath = r.URL.Path + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"Code":1000}`)) + })) + defer ts.Close() + + m := proton.New(proton.WithHostURL(ts.URL)) + defer m.Close() + + c := m.NewClient("", "", "") + defer c.Close() + + err := c.MoveLinkByVolume(context.Background(), "volume-id", "link-id", proton.MoveLinkReq{ + ParentLinkID: "parent-link-id", + Name: "encrypted-name", + OriginalHash: "original-hash", + Hash: "new-hash", + }) + require.NoError(t, err) + require.Equal(t, http.MethodPut, gotMethod) + require.Equal(t, "/drive/v2/volumes/volume-id/links/link-id/move", gotPath) +} + +func TestGetRevisionVerificationUsesShareVerificationEndpoint(t *testing.T) { + var gotMethod string + var gotPath string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotMethod = r.Method + gotPath = r.URL.Path + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"VerificationCode":"abc","ContentKeyPacket":"def"}`)) + })) + defer ts.Close() + + m := proton.New(proton.WithHostURL(ts.URL)) + defer m.Close() + + c := m.NewClient("", "", "") + defer c.Close() + + res, err := c.GetRevisionVerification(context.Background(), "share-id", "link-id", "revision-id") + require.NoError(t, err) + require.Equal(t, http.MethodGet, gotMethod) + require.Equal(t, "/drive/shares/share-id/links/link-id/revisions/revision-id/verification", gotPath) + require.Equal(t, "abc", res.VerificationCode) + require.Equal(t, "def", res.ContentKeyPacket) +} diff --git a/link_file_types.go b/link_file_types.go index a06a6b4..27a5f63 100644 --- a/link_file_types.go +++ b/link_file_types.go @@ -40,13 +40,17 @@ func GetNameHash(name string, hashKey []byte) (string, error) { type MoveLinkReq struct { ParentLinkID string - Name string // Encrypted File Name - OriginalHash string // Old Encrypted File Name Hash - Hash string // Encrypted File Name Hash by using parent's NodeHashKey + Name string // Encrypted File Name + NameSignatureEmail string `json:",omitempty"` + OriginalHash string // Old Encrypted File Name Hash + Hash string // Encrypted File Name Hash by using parent's NodeHashKey + ContentHash *string `json:",omitempty"` + NodePassphrase string // The passphrase used to unlock the NodeKey, encrypted by the owning Link/Share keyring. - NodePassphraseSignature string // The signature of the NodePassphrase + NodePassphraseSignature string `json:",omitempty"` // The signature of the NodePassphrase + SignatureEmail string `json:",omitempty"` - SignatureAddress string // Signature email address used to sign passphrase and name + SignatureAddress string `json:",omitempty"` // Signature email address used to sign passphrase and name } func (moveLinkReq *MoveLinkReq) SetName(name string, addrKR, nodeKR *crypto.KeyRing) error { @@ -142,6 +146,11 @@ type CreateRevisionRes struct { ID string // Encrypted Revision ID } +type RevisionVerificationRes struct { + VerificationCode string + ContentKeyPacket string +} + type CommitRevisionReq struct { ManifestSignature string SignatureAddress string diff --git a/link_folder.go b/link_folder.go index 7e403a0..a57ff5d 100644 --- a/link_folder.go +++ b/link_folder.go @@ -9,6 +9,10 @@ import ( "github.com/go-resty/resty/v2" ) +func isDeleteChildrenResponseCodeAllowed(code Code) bool { + return code == SuccessCode || code == AFileOrFolderNotFound +} + func (c *Client) ListChildren(ctx context.Context, shareID, linkID string, showAll bool) ([]Link, error) { var res struct { Links []Link @@ -107,7 +111,7 @@ func (c *Client) DeleteChildren(ctx context.Context, shareID, linkID string, chi } for _, res := range res.Responses { - if res.Response.Code != SuccessCode { + if !isDeleteChildrenResponseCodeAllowed(res.Response.Code) { return fmt.Errorf("failed to delete child: %w", res.Response) } } diff --git a/link_folder_test.go b/link_folder_test.go new file mode 100644 index 0000000..252635b --- /dev/null +++ b/link_folder_test.go @@ -0,0 +1,24 @@ +package proton + +import "testing" + +func TestIsDeleteChildrenResponseCodeAllowed(t *testing.T) { + tests := []struct { + name string + code Code + want bool + }{ + {name: "success", code: SuccessCode, want: true}, + {name: "not_found", code: 2501, want: true}, + {name: "conflict", code: AFileOrFolderNameExist, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isDeleteChildrenResponseCodeAllowed(tt.code) + if got != tt.want { + t.Fatalf("unexpected result: got=%v want=%v", got, tt.want) + } + }) + } +} diff --git a/manager_builder.go b/manager_builder.go index 2cb22cc..eff0d6b 100644 --- a/manager_builder.go +++ b/manager_builder.go @@ -22,30 +22,32 @@ const ( ) type managerBuilder struct { - hostURL string - appVersion string - userAgent string - transport http.RoundTripper - verifyProofs bool - cookieJar http.CookieJar - retryCount int - logger resty.Logger - debug bool - panicHandler async.PanicHandler + hostURL string + appVersion string + driveSDKVersion string + userAgent string + transport http.RoundTripper + verifyProofs bool + cookieJar http.CookieJar + retryCount int + logger resty.Logger + debug bool + panicHandler async.PanicHandler } func newManagerBuilder() *managerBuilder { return &managerBuilder{ - hostURL: DefaultHostURL, - appVersion: DefaultAppVersion, - userAgent: DefaultUserAgent, - transport: http.DefaultTransport, - verifyProofs: true, - cookieJar: nil, - retryCount: 3, - logger: nil, - debug: false, - panicHandler: async.NoopPanicHandler{}, + hostURL: DefaultHostURL, + appVersion: DefaultAppVersion, + driveSDKVersion: "", + userAgent: DefaultUserAgent, + transport: http.DefaultTransport, + verifyProofs: true, + cookieJar: nil, + retryCount: 3, + logger: nil, + debug: false, + panicHandler: async.NoopPanicHandler{}, } } @@ -80,6 +82,9 @@ func (builder *managerBuilder) build() *Manager { // Set app version in header. m.rc.OnBeforeRequest(func(_ *resty.Client, req *resty.Request) error { req.SetHeader("x-pm-appversion", builder.appVersion) + if builder.driveSDKVersion != "" { + req.SetHeader("x-pm-drive-sdk-version", builder.driveSDKVersion) + } req.SetHeader("User-Agent", builder.userAgent) return nil }) diff --git a/manager_test.go b/manager_test.go index bf9c890..d6fd69c 100644 --- a/manager_test.go +++ b/manager_test.go @@ -16,6 +16,52 @@ import ( "github.com/stretchr/testify/require" ) +func TestManagerSetsSDKHeadersWhenConfigured(t *testing.T) { + var gotAppVersion string + var gotSDKVersion string + var gotUserAgent string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAppVersion = r.Header.Get("x-pm-appversion") + gotSDKVersion = r.Header.Get("x-pm-drive-sdk-version") + gotUserAgent = r.Header.Get("User-Agent") + w.WriteHeader(http.StatusOK) + })) + defer ts.Close() + + m := proton.New( + proton.WithHostURL(ts.URL), + proton.WithAppVersion("web-drive@5.2.0+af66c8fa"), + proton.WithDriveSDKVersion("js@0.10.0"), + proton.WithUserAgent("go-proton-api-tests"), + ) + defer m.Close() + + require.NoError(t, m.Ping(context.Background())) + require.Equal(t, "web-drive@5.2.0+af66c8fa", gotAppVersion) + require.Equal(t, "js@0.10.0", gotSDKVersion) + require.Equal(t, "go-proton-api-tests", gotUserAgent) +} + +func TestManagerOmitsSDKHeaderByDefault(t *testing.T) { + var gotSDKVersion string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotSDKVersion = r.Header.Get("x-pm-drive-sdk-version") + w.WriteHeader(http.StatusOK) + })) + defer ts.Close() + + m := proton.New( + proton.WithHostURL(ts.URL), + proton.WithAppVersion("test-app-version"), + ) + defer m.Close() + + require.NoError(t, m.Ping(context.Background())) + require.Equal(t, "", gotSDKVersion) +} + func TestConnectionReuse(t *testing.T) { s := server.New() defer s.Close() diff --git a/option.go b/option.go index 5dc3239..37e86a7 100644 --- a/option.go +++ b/option.go @@ -32,6 +32,20 @@ func WithAppVersion(appVersion string) Option { } } +func WithDriveSDKVersion(driveSDKVersion string) Option { + return &withDriveSDKVersion{ + driveSDKVersion: driveSDKVersion, + } +} + +type withDriveSDKVersion struct { + driveSDKVersion string +} + +func (opt withDriveSDKVersion) config(builder *managerBuilder) { + builder.driveSDKVersion = opt.driveSDKVersion +} + type withUserAgent struct { userAgent string } diff --git a/response.go b/response.go index 0ce36d7..5183c25 100644 --- a/response.go +++ b/response.go @@ -34,6 +34,7 @@ const ( // ProtonDrive AFileOrFolderNameExist Code = 2500 ADraftExist Code = 2500 + AFileOrFolderNotFound Code = 2501 ) var (