From 72a3c7ab3b814dddd7b30dbfd108ef71af995da1 Mon Sep 17 00:00:00 2001 From: ashmrtn Date: Mon, 17 Oct 2022 10:54:36 -0700 Subject: [PATCH] Trim trailing '/' from CLI input for restore/details (#1175) ## Description Use paths package to trim unescaped trailing '/' characters from input for Exchange mail and OneDrive folder names. Add tests for Exchange showing that the trimming also works properly if the folder name ends with '/'. ## Type of change - [ ] :sunflower: Feature - [x] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Test - [ ] :computer: CI/Deployment - [ ] :hamster: Trivial/Minor ## Issue(s) * closes #1147 ## Test Plan - [x] :muscle: Manual - [x] :zap: Unit test - [ ] :green_heart: E2E --- src/cli/utils/exchange.go | 2 ++ src/cli/utils/flags.go | 14 ++++++++ src/cli/utils/onedrive.go | 2 ++ src/cli/utils/testdata/opts.go | 39 ++++++++++++++++++++-- src/pkg/path/path.go | 8 ++--- src/pkg/selectors/selectors_reduce_test.go | 5 ++- src/pkg/selectors/testdata/details.go | 18 +++++++++- 7 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/cli/utils/exchange.go b/src/cli/utils/exchange.go index b29dabf1a..79ede30d3 100644 --- a/src/cli/utils/exchange.go +++ b/src/cli/utils/exchange.go @@ -142,6 +142,8 @@ func IncludeExchangeRestoreDataSelectors( return } + opts.EmailFolder = trimFolderSlash(opts.EmailFolder) + // or add selectors for each type of data AddExchangeInclude(sel, opts.Users, opts.ContactFolder, opts.Contact, sel.Contacts) AddExchangeInclude(sel, opts.Users, opts.EmailFolder, opts.Email, sel.Mails) diff --git a/src/cli/utils/flags.go b/src/cli/utils/flags.go index 1730eaeda..c4380e13f 100644 --- a/src/cli/utils/flags.go +++ b/src/cli/utils/flags.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/pflag" "github.com/alcionai/corso/src/internal/common" + "github.com/alcionai/corso/src/pkg/path" ) type PopulatedFlags map[string]struct{} @@ -50,3 +51,16 @@ func IsValidBool(in string) bool { _, err := strconv.ParseBool(in) return err == nil } + +// trimFolderSlash takes a set of folder paths and returns a set of folder paths +// with any unescaped trailing `/` characters removed. +func trimFolderSlash(folders []string) []string { + res := make([]string, 0, len(folders)) + + for _, p := range folders { + // Use path package because it has logic to handle escaping already. + res = append(res, path.TrimTrailingSlash(p)) + } + + return res +} diff --git a/src/cli/utils/onedrive.go b/src/cli/utils/onedrive.go index 324a54c6c..41c00b9fd 100644 --- a/src/cli/utils/onedrive.go +++ b/src/cli/utils/onedrive.go @@ -87,6 +87,8 @@ func IncludeOneDriveRestoreDataSelectors( return } + opts.Paths = trimFolderSlash(opts.Paths) + if lp == 0 { opts.Paths = selectors.Any() } diff --git a/src/cli/utils/testdata/opts.go b/src/cli/utils/testdata/opts.go index 6305cdd7e..2af755c52 100644 --- a/src/cli/utils/testdata/opts.go +++ b/src/cli/utils/testdata/opts.go @@ -139,8 +139,38 @@ var ( }, }, { - Name: "EmailsBySubject", + Name: "EmailsFolderPrefixMatchTrailingSlash", Expected: testdata.ExchangeEmailItems, + Opts: utils.ExchangeOpts{ + EmailFolder: []string{testdata.ExchangeEmailInboxPath.Folder() + "/"}, + }, + }, + { + Name: "EmailsFolderWithSlashPrefixMatch", + Expected: []details.DetailsEntry{ + testdata.ExchangeEmailItems[1], + testdata.ExchangeEmailItems[2], + }, + Opts: utils.ExchangeOpts{ + EmailFolder: []string{testdata.ExchangeEmailBasePath2.Folder()}, + }, + }, + { + Name: "EmailsFolderWithSlashPrefixMatchTrailingSlash", + Expected: []details.DetailsEntry{ + testdata.ExchangeEmailItems[1], + testdata.ExchangeEmailItems[2], + }, + Opts: utils.ExchangeOpts{ + EmailFolder: []string{testdata.ExchangeEmailBasePath2.Folder() + "/"}, + }, + }, + { + Name: "EmailsBySubject", + Expected: []details.DetailsEntry{ + testdata.ExchangeEmailItems[0], + testdata.ExchangeEmailItems[1], + }, Opts: utils.ExchangeOpts{ EmailSender: "a-person", }, @@ -180,8 +210,11 @@ var ( }, }, { - Name: "MultipleMailShortRef", - Expected: testdata.ExchangeEmailItems, + Name: "MultipleMailShortRef", + Expected: []details.DetailsEntry{ + testdata.ExchangeEmailItems[0], + testdata.ExchangeEmailItems[1], + }, Opts: utils.ExchangeOpts{ Email: []string{ testdata.ExchangeEmailItemPath1.ShortRef(), diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go index 83f00b1fb..90140b76e 100644 --- a/src/pkg/path/path.go +++ b/src/pkg/path/path.go @@ -145,7 +145,7 @@ func (pb *Builder) appendElements(escaped bool, elements []string) error { tmp := e if escaped { - tmp = trimTrailingSlash(tmp) + tmp = TrimTrailingSlash(tmp) // If tmp was just the path separator then it will be empty now. if len(tmp) == 0 { continue @@ -310,7 +310,7 @@ func (pb Builder) ToDataLayerOneDrivePath( // resource-specific type. If p does not match any resource-specific paths or // is malformed returns an error. func FromDataLayerPath(p string, isItem bool) (Path, error) { - p = trimTrailingSlash(p) + p = TrimTrailingSlash(p) // If p was just the path separator then it will be empty now. if len(p) == 0 { return nil, errors.Errorf("logically empty path given: %s", p) @@ -437,11 +437,11 @@ func validateEscapedElement(element string) error { return nil } -// trimTrailingSlash takes an escaped path element and returns an escaped path +// TrimTrailingSlash takes an escaped path element and returns an escaped path // element with the trailing path separator character(s) removed if they were not // escaped. If there were no trailing path separator character(s) or the separator(s) // were escaped the input is returned unchanged. -func trimTrailingSlash(element string) string { +func TrimTrailingSlash(element string) string { for len(element) > 0 && element[len(element)-1] == pathSeparator { lastIdx := len(element) - 1 numSlashes := 0 diff --git a/src/pkg/selectors/selectors_reduce_test.go b/src/pkg/selectors/selectors_reduce_test.go index e3ecbd736..fcc88d7dc 100644 --- a/src/pkg/selectors/selectors_reduce_test.go +++ b/src/pkg/selectors/selectors_reduce_test.go @@ -92,7 +92,10 @@ func (suite *SelectorReduceSuite) TestReduce() { return sel }, - expected: testdata.ExchangeEmailItems, + expected: []details.DetailsEntry{ + testdata.ExchangeEmailItems[0], + testdata.ExchangeEmailItems[1], + }, }, { name: "ExchangeMailReceivedTime", diff --git a/src/pkg/selectors/testdata/details.go b/src/pkg/selectors/testdata/details.go index 167f89928..d7a90260f 100644 --- a/src/pkg/selectors/testdata/details.go +++ b/src/pkg/selectors/testdata/details.go @@ -36,6 +36,7 @@ func mustAppendPath(p path.Path, newElement string, isItem bool) path.Path { const ( ItemName1 = "item1" ItemName2 = "item2" + ItemName3 = "item3" ) var ( @@ -44,9 +45,11 @@ var ( ExchangeEmailInboxPath = mustParsePath("tenant-id/exchange/user-id/email/Inbox", false) ExchangeEmailBasePath = mustAppendPath(ExchangeEmailInboxPath, "subfolder", false) - ExchangeEmailBasePath2 = mustAppendPath(ExchangeEmailInboxPath, "othersubfolder", false) + ExchangeEmailBasePath2 = mustAppendPath(ExchangeEmailInboxPath, "othersubfolder/", false) + ExchangeEmailBasePath3 = mustAppendPath(ExchangeEmailBasePath2, "subsubfolder", false) ExchangeEmailItemPath1 = mustAppendPath(ExchangeEmailBasePath, ItemName1, true) ExchangeEmailItemPath2 = mustAppendPath(ExchangeEmailBasePath2, ItemName2, true) + ExchangeEmailItemPath3 = mustAppendPath(ExchangeEmailBasePath3, ItemName3, true) ExchangeEmailItems = []details.DetailsEntry{ { @@ -75,6 +78,19 @@ var ( }, }, }, + { + RepoRef: ExchangeEmailItemPath3.String(), + ShortRef: ExchangeEmailItemPath3.ShortRef(), + ParentRef: ExchangeEmailItemPath3.ToBuilder().Dir().ShortRef(), + ItemInfo: details.ItemInfo{ + Exchange: &details.ExchangeInfo{ + ItemType: details.ExchangeMail, + Sender: "another-person", + Subject: "baz", + Received: Time2, + }, + }, + }, } ExchangeContactsRootPath = mustParsePath("tenant-id/exchange/user-id/contacts/contacts", false)