aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/imap/folder.go4
-rw-r--r--internal/imap/imap.go9
-rw-r--r--pkg/config/url.go46
-rw-r--r--pkg/config/yaml.go45
-rw-r--r--pkg/config/yaml_test.go98
5 files changed, 183 insertions, 19 deletions
diff --git a/internal/imap/folder.go b/internal/imap/folder.go
index 9386293..32bfefe 100644
--- a/internal/imap/folder.go
+++ b/internal/imap/folder.go
@@ -7,6 +7,10 @@ type Folder struct {
delimiter string
}
+func (f Folder) IsBlank() bool {
+ return f.str == ""
+}
+
func (f Folder) String() string {
return f.str
}
diff --git a/internal/imap/imap.go b/internal/imap/imap.go
index 4a18a94..a44a513 100644
--- a/internal/imap/imap.go
+++ b/internal/imap/imap.go
@@ -2,7 +2,6 @@ package imap
import (
"fmt"
- "strings"
"github.com/Necoro/feed2imap-go/pkg/config"
"github.com/Necoro/feed2imap-go/pkg/log"
@@ -31,15 +30,11 @@ func Connect(url config.Url) (*Client, error) {
}
client.delimiter = delim
- toplevel := url.Root
- if toplevel[0] == '/' {
- toplevel = toplevel[1:]
- }
- client.toplevel = client.folderName(strings.Split(toplevel, "/"))
+ client.toplevel = client.folderName(url.RootPath())
log.Printf("Determined '%s' as toplevel, with '%s' as delimiter", client.toplevel, client.delimiter)
- if toplevel != "" {
+ if !client.toplevel.IsBlank() {
if err = conn.ensureFolder(client.toplevel); err != nil {
return nil, err
}
diff --git a/pkg/config/url.go b/pkg/config/url.go
index 39cce16..7e91411 100644
--- a/pkg/config/url.go
+++ b/pkg/config/url.go
@@ -4,6 +4,7 @@ import (
"fmt"
"net"
"net/url"
+ "strings"
"gopkg.in/yaml.v3"
)
@@ -63,7 +64,7 @@ func (u *Url) UnmarshalYAML(value *yaml.Node) (err error) {
return nil
}
-func (u *Url) String() string {
+func (u Url) String() string {
scheme := u.Scheme + "://"
var pwd string
@@ -86,12 +87,28 @@ func (u *Url) HostPort() string {
}
const (
- imapsPort = "993"
- imapPort = "143"
- imapsSchema = "imaps"
- imapSchema = "imap"
+ imapsPort = "993"
+ imapPort = "143"
+ imapsSchema = "imaps"
+ imapsSchemaFull = imapsSchema + "://"
+ imapSchema = "imap"
+ imapSchemaFull = imapSchema + "://"
+ maildirSchemaFull = "maildir://"
)
+func isRecognizedUrl(s string) bool {
+ return isImapUrl(s) || isMaildirUrl(s)
+
+}
+
+func isImapUrl(s string) bool {
+ return strings.HasPrefix(s, imapsSchemaFull) || strings.HasPrefix(s, imapSchemaFull)
+}
+
+func isMaildirUrl(s string) bool {
+ return strings.HasPrefix(s, maildirSchemaFull)
+}
+
func (u *Url) ForceTLS() bool {
return u.Scheme == imapsSchema || u.Port == imapsPort
}
@@ -132,3 +149,22 @@ func (u *Url) validate() (errors []string) {
return
}
+
+func (u Url) BaseUrl() Url {
+ // 'u' is not a pointer and thus a copy, modification is fine
+ u.Root = ""
+ return u
+}
+
+func (u *Url) CommonBaseUrl(other Url) bool {
+ other.Root = ""
+ return other == u.BaseUrl()
+}
+
+func (u *Url) RootPath() []string {
+ path := u.Root
+ if path[0] == '/' {
+ path = path[1:]
+ }
+ return strings.Split(path, "/")
+}
diff --git a/pkg/config/yaml.go b/pkg/config/yaml.go
index 48269ba..57991eb 100644
--- a/pkg/config/yaml.go
+++ b/pkg/config/yaml.go
@@ -118,7 +118,7 @@ func (cfg *Config) parse(in io.Reader) error {
cfg.fixGlobalOptions(parsedCfg.GlobalConfig)
- if err := buildFeeds(parsedCfg.Feeds, []string{}, cfg.Feeds, &cfg.FeedOptions, cfg.AutoTarget); err != nil {
+ if err := buildFeeds(parsedCfg.Feeds, []string{}, cfg.Feeds, &cfg.FeedOptions, cfg.AutoTarget, &cfg.Target); err != nil {
return fmt.Errorf("while parsing: %w", err)
}
@@ -182,12 +182,45 @@ func buildOptions(globalFeedOptions *Options, options Map) (feedOptions Options,
}
// Fetch the group structure and populate the `targetStr` fields in the feeds
-func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, globalFeedOptions *Options, autoTarget bool) error {
+func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds,
+ globalFeedOptions *Options, autoTarget bool, globalTarget *Url) error {
+
for _, f := range cfg {
- target := appTarget(target, f.target(autoTarget))
+ var fTarget []string
+
+ rawTarget := f.target(autoTarget)
+ if isRecognizedUrl(rawTarget) {
+ // this whole block is solely for compatibility with old feed2imap
+ // there it was common to specify the whole URL for each feed
+ if isMaildirUrl(rawTarget) {
+ // old feed2imap supported maildir, we don't
+ return fmt.Errorf("Line %d: Maildir is not supported.", f.Target.Line)
+ }
+
+ url := Url{}
+ if err := url.UnmarshalYAML(&f.Target); err != nil {
+ return err
+ }
+
+ if globalTarget.Empty() {
+ // assign first feed as global url
+ *globalTarget = url.BaseUrl()
+ } else if !globalTarget.CommonBaseUrl(url) {
+ // if we have a url, it must be the same prefix as the global url
+ return fmt.Errorf("Line %d: Given URL endpoint '%s' does not match previous endpoint '%s'.",
+ f.Target.Line,
+ url.BaseUrl(),
+ globalTarget.BaseUrl())
+ }
+
+ fTarget = url.RootPath() // we are given the absolute path, so now appending trickery
+ } else {
+ fTarget = appTarget(target, rawTarget)
+ }
+
switch {
case f.isFeed() && f.isGroup():
- return fmt.Errorf("Entry with targetStr %s is both a Feed and a group", target)
+ return fmt.Errorf("Entry with targetStr %s is both a Feed and a group", fTarget)
case f.isFeed():
name := f.Feed.Name
@@ -211,7 +244,7 @@ func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, globalFeedO
Url: f.Feed.Url,
Exec: f.Feed.Exec,
Options: opt,
- Target: target,
+ Target: fTarget,
}
case f.isGroup():
@@ -221,7 +254,7 @@ func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, globalFeedO
log.Warnf("Unknown option '%s' for group '%s'. Ignored!", optName, f.Group.Group)
}
- if err := buildFeeds(f.Group.Feeds, target, feeds, &opt, autoTarget); err != nil {
+ if err := buildFeeds(f.Group.Feeds, fTarget, feeds, &opt, autoTarget, globalTarget); err != nil {
return err
}
}
diff --git a/pkg/config/yaml_test.go b/pkg/config/yaml_test.go
index e649175..78a3243 100644
--- a/pkg/config/yaml_test.go
+++ b/pkg/config/yaml_test.go
@@ -148,6 +148,29 @@ func TestBuildFeeds(tst *testing.T) {
"bar": &Feed{Name: "bar", Target: t("moep.bar")},
},
},
+ {name: "URL Target", wantErr: false, target: "",
+ feeds: []configGroupFeed{
+ {Target: n("imap://foo.bar:443/INBOX/Feed"), Feed: feed{Name: "muh"}},
+ },
+ result: Feeds{"muh": &Feed{Name: "muh", Target: t("INBOX.Feed")}},
+ },
+ {name: "Multiple URL Targets", wantErr: false, target: "",
+ feeds: []configGroupFeed{
+ {Target: n("imap://foo.bar:443/INBOX/Feed"), Feed: feed{Name: "muh"}},
+ {Target: n("imap://foo.bar:443/INBOX/Feed2"), Feed: feed{Name: "bar"}},
+ },
+ result: Feeds{
+ "muh": &Feed{Name: "muh", Target: t("INBOX.Feed")},
+ "bar": &Feed{Name: "bar", Target: t("INBOX.Feed2")},
+ },
+ },
+ {name: "Mixed URL Targets", wantErr: true, target: "",
+ feeds: []configGroupFeed{
+ {Target: n("imap://foo.bar:443/INBOX/Feed"), Feed: feed{Name: "muh"}},
+ {Target: n("imap://other.bar:443/INBOX/Feed"), Feed: feed{Name: "bar"}},
+ },
+ result: Feeds{},
+ },
{name: "Empty Group", wantErr: false, target: "",
feeds: []configGroupFeed{
{Group: group{Group: "G1"}},
@@ -206,7 +229,8 @@ func TestBuildFeeds(tst *testing.T) {
tst.Run(tt.name, func(tst *testing.T) {
var feeds = Feeds{}
var opts = Options{}
- err := buildFeeds(tt.feeds, t(tt.target), feeds, &opts, !tt.noAutoTarget)
+ var globalTarget = Url{}
+ err := buildFeeds(tt.feeds, t(tt.target), feeds, &opts, !tt.noAutoTarget, &globalTarget)
if (err != nil) != tt.wantErr {
tst.Errorf("buildFeeds() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -447,3 +471,75 @@ feeds:
}
}
}
+
+func TestURLFeedWithoutGlobalTarget(tst *testing.T) {
+ inp := `
+feeds:
+ - name: Foo
+ target: imap://foo.bar:443/INBOX/Feed
+`
+ res := Feeds{
+ "Foo": &Feed{Name: "Foo", Target: t("INBOX.Feed")},
+ }
+
+ c := WithDefault()
+ c.FeedOptions = Options{}
+
+ if err := c.parse(strings.NewReader(inp)); err != nil {
+ tst.Error(err)
+ } else {
+ if diff := cmp.Diff(res, c.Feeds); diff != "" {
+ tst.Error(diff)
+ }
+ if diff := cmp.Diff("imap://foo.bar:443", c.Target.String()); diff != "" {
+ tst.Error(diff)
+ }
+ }
+}
+
+func TestURLFeedWithGlobalTarget(tst *testing.T) {
+ inp := `
+target: imaps://foo.bar/INBOX/Feeds
+feeds:
+ - name: Foo
+ target: imaps://foo.bar:993/Some/Other/Path
+`
+ res := Feeds{
+ "Foo": &Feed{Name: "Foo", Target: t("Some.Other.Path")},
+ }
+
+ c := WithDefault()
+ c.FeedOptions = Options{}
+
+ if err := c.parse(strings.NewReader(inp)); err != nil {
+ tst.Error(err)
+ } else {
+ if diff := cmp.Diff(res, c.Feeds); diff != "" {
+ tst.Error(diff)
+ }
+ if diff := cmp.Diff("imaps://foo.bar:993/INBOX/Feeds", c.Target.String()); diff != "" {
+ tst.Error(diff)
+ }
+ }
+}
+
+func TestURLFeedWithDifferentGlobalTarget(tst *testing.T) {
+ inp := `
+target: imaps://foo.bar/INBOX/Feeds
+feeds:
+ - name: Foo
+ target: imaps://other.bar/INBOX/Feeds
+`
+ errorText := "while parsing: Line 5: Given URL endpoint 'imaps://other.bar:993' does not match previous endpoint 'imaps://foo.bar:993'."
+ c := WithDefault()
+ c.FeedOptions = Options{}
+
+ err := c.parse(strings.NewReader(inp))
+ if err == nil {
+ tst.Error("Expected error.")
+ } else {
+ if diff := cmp.Diff(errorText, err.Error()); diff != "" {
+ tst.Error(diff)
+ }
+ }
+} \ No newline at end of file