diff --git a/internal/state/paths.go b/internal/state/paths.go index e0dfeec4..3711d283 100644 --- a/internal/state/paths.go +++ b/internal/state/paths.go @@ -9,6 +9,10 @@ import ( // listSuperiors returns all names superior to the given name, if hierarchies are indicated with the given delimiter. func listSuperiors(name, delimiter string) []string { + if delimiter == "" { + return nil + } + split := strings.Split(name, delimiter) if len(split) == 0 { return nil diff --git a/internal/state/paths_test.go b/internal/state/paths_test.go index e952aad5..4078fda6 100644 --- a/internal/state/paths_test.go +++ b/internal/state/paths_test.go @@ -1,41 +1,71 @@ package state import ( - "fmt" - "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestListSuperiorNames(t *testing.T) { - tests := []struct { + for name, data := range map[string]struct { name, delimiter string - - want []string + want []string }{ - { + "no parents": { name: "this", delimiter: "/", - want: []string{}, + want: nil, }, - { + "has parents": { name: "this/is/a/test", delimiter: "/", want: []string{"this", "this/is", "this/is/a"}, }, - { - name: "/this/is/a/test", + "wrong delimiter": { + name: "this.is.a.test", delimiter: "/", - want: []string{"/this", "/this/is", "/this/is/a"}, + want: nil, + }, + "nil delimiter": { + name: "/nil/delimiter.used", + delimiter: "", + want: nil, }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, data.want, listSuperiors(data.name, data.delimiter)) + }) } +} - for _, tt := range tests { - tt := tt - - t.Run(fmt.Sprintf("%#v", tt), func(t *testing.T) { - if res := listSuperiors(tt.name, tt.delimiter); strings.Join(res, "") != strings.Join(tt.want, "") { - t.Errorf("expected result of %v but got %v", tt.want, res) - } +func TestListInferiorNames(t *testing.T) { + for name, data := range map[string]struct { + parent string + delimiter string + names []string + want []string + }{ + "no children": { + parent: "this", + delimiter: "/", + names: []string{"one", "two", "three", "this", "a/b", "c/d"}, + want: []string{}, + }, + "has children": { + parent: "this", + delimiter: "/", + names: []string{"a/b", "this/one", "this", "this/two", "this/one/two/three", "c/d"}, + want: []string{"this/two", "this/one/two/three", "this/one"}, + }, + "nil delimiter": { + parent: "this", + delimiter: "", + names: []string{"a/b", "this/one", "this", "this/two", "this/one/two/three", "c/d"}, + want: []string{}, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, data.want, listInferiors(data.parent, data.delimiter, data.names)) }) } } diff --git a/internal/state/state.go b/internal/state/state.go index f3eacc40..d84cf3d5 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -2,6 +2,7 @@ package state import ( "context" + "errors" "fmt" "strings" "sync/atomic" @@ -148,6 +149,14 @@ func (state *State) Examine(ctx context.Context, name string, fn func(*Mailbox) } func (state *State) Create(ctx context.Context, name string) error { + if strings.HasPrefix(name, state.delimiter) { + return errors.New("invalid mailbox name: begins with hierarchy separator") + } + + if strings.Contains(name, state.delimiter+state.delimiter) { + return errors.New("invalid mailbox name: has adjacent hierarchy separators") + } + mboxesToCreate, err := db.ReadResult(ctx, state.db(), func(ctx context.Context, client *ent.Client) ([]string, error) { var mboxesToCreate []string // If the mailbox name is suffixed with the server's hierarchy separator, remove the separator and still create diff --git a/tests/create_test.go b/tests/create_test.go index 9ebd456d..2a706185 100644 --- a/tests/create_test.go +++ b/tests/create_test.go @@ -49,6 +49,16 @@ func TestCreateWithDifferentHierarchySeparator(t *testing.T) { }) } +func TestCreateWithNilHierarchySeparator(t *testing.T) { + runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withDelimiter("")), func(client *client.Client, _ *testSession) { + matchMailboxNamesClient(t, client, "", "*", []string{"INBOX"}) + require.NoError(t, client.Create("Folder/Bar")) + matchMailboxNamesClient(t, client, "", "*", []string{"INBOX", "Folder/Bar"}) + require.NoError(t, client.Create("Folder")) + matchMailboxNamesClient(t, client, "", "*", []string{"INBOX", "Folder", "Folder/Bar"}) + }) +} + func TestCreatePreviousLevelHierarchyIfNonExisting(t *testing.T) { runOneToOneTestClientWithAuth(t, defaultServerOptions(t), func(client *client.Client, _ *testSession) { require.NoError(t, client.Create("Folder/Bar/ZZ")) @@ -96,3 +106,17 @@ func TestEnsureNewMailboxWithDeletedNameHasGreaterId(t *testing.T) { }) } + +func TestCreateAdjacentSeparator(t *testing.T) { + runOneToOneTestWithAuth(t, defaultServerOptions(t), func(c *testConnection, _ *testSession) { + c.C(`A001 create foo//bar`) + c.Sx(`^A001 NO .*adjacent hierarchy separators\r\n$`) + }) +} + +func TestCreateBeginsWithSeparator(t *testing.T) { + runOneToOneTestWithAuth(t, defaultServerOptions(t), func(c *testConnection, _ *testSession) { + c.C(`A001 create /foo`) + c.Sx(`^A001 NO .*begins with hierarchy separator\r\n$`) + }) +} diff --git a/tests/list_test.go b/tests/list_test.go index f745bd17..2a17092a 100644 --- a/tests/list_test.go +++ b/tests/list_test.go @@ -259,3 +259,17 @@ func TestListSpecialUseAttributes(t *testing.T) { c.OK(`a`) }) } + +func TestListNilDelimiter(t *testing.T) { + runOneToOneTestWithAuth(t, defaultServerOptions(t, withDelimiter("")), func(c *testConnection, s *testSession) { + s.mailboxCreated("user", []string{"INBOX"}) + s.mailboxCreated("user", []string{"Folders/Custom"}) + + c.C(`a list "" "*"`) + c.S( + `* LIST (\Unmarked) NIL "INBOX"`, + `* LIST (\Unmarked) NIL "Folders/Custom"`, + ) + c.OK(`a`) + }) +} diff --git a/tests/select_test.go b/tests/select_test.go index 7f4c1b56..ada73e09 100644 --- a/tests/select_test.go +++ b/tests/select_test.go @@ -64,3 +64,17 @@ func TestSelectUTF7(t *testing.T) { c.C(`A005 LIST "" "*"`).Sxe(`&ZeVnLIqe-`).OK(`A005`) }) } + +func TestSelectWithNilDelimiter(t *testing.T) { + runOneToOneTestWithAuth(t, defaultServerOptions(t, withDelimiter("")), func(c *testConnection, _ *testSession) { + // Test we can create a mailbox with a UTF-7 name. + c.C("a CREATE A").OK("a") + c.C("a CREATE A/B").OK("a") + c.C("a CREATE A/B/C").OK("a") + + // Test we can select the mailbox. + c.C("A001 SELECT A").OK("A001") + c.C("A002 SELECT A/B").OK("A002") + c.C("A003 SELECT A/B/C").OK("A003") + }) +}