From 2a8e0cf3750d3f789bcd756e39af04f00fe0e738 Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Thu, 14 May 2020 23:37:35 +0200 Subject: Verbose variant of 'target' in config --- pkg/config/config.go | 6 +-- pkg/config/url.go | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ pkg/config/url_test.go | 108 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 pkg/config/url.go create mode 100644 pkg/config/url_test.go (limited to 'pkg/config') diff --git a/pkg/config/config.go b/pkg/config/config.go index 8e2aff6..377365f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,7 +19,7 @@ type Map map[string]interface{} type GlobalOptions struct { Timeout int `yaml:"timeout"` DefaultEmail string `yaml:"default-email"` - Target string `yaml:"target"` + Target Url `yaml:"target"` Parts []string `yaml:"parts"` MaxFailures int `yaml:"max-failures"` } @@ -29,7 +29,7 @@ var DefaultGlobalOptions = GlobalOptions{ Timeout: 30, MaxFailures: 10, DefaultEmail: username() + "@" + Hostname(), - Target: "", + Target: Url{}, Parts: []string{"text", "html"}, } @@ -77,7 +77,7 @@ func WithDefault() *Config { // Validates the configuration against common mistakes func (cfg *Config) Validate() error { - if cfg.Target == "" { + if cfg.Target.Empty() { return fmt.Errorf("No target set!") } diff --git a/pkg/config/url.go b/pkg/config/url.go new file mode 100644 index 0000000..403f787 --- /dev/null +++ b/pkg/config/url.go @@ -0,0 +1,127 @@ +package config + +import ( + "fmt" + "net" + "net/url" + + "gopkg.in/yaml.v3" +) + +type Url struct { + Scheme string `yaml:"scheme"` + User string `yaml:"user"` + Password string `yaml:"password"` + Host string `yaml:"host"` + Port string `yaml:"port"` + Root string `yaml:"root"` +} + +func (u *Url) Empty() bool { + return u.Host == "" +} + +func (u *Url) UnmarshalYAML(value *yaml.Node) (err error) { + if value.ShortTag() == strTag { + var val string + var rawUrl *url.URL + + if err = value.Decode(&val); err != nil { + return err + } + if rawUrl, err = url.Parse(val); err != nil { + return err + } + + u.Scheme = rawUrl.Scheme + u.User = rawUrl.User.Username() + u.Password, _ = rawUrl.User.Password() + u.Host = rawUrl.Hostname() + u.Port = rawUrl.Port() + u.Root = rawUrl.Path + } else { + type _url Url // avoid recursion + wrapped := (*_url)(u) + if err = value.Decode(wrapped); err != nil { + return err + } + } + + u.sanitize() + + if errors := u.validate(); len(errors) > 0 { + errs := make([]string, len(errors)+1) + copy(errs[1:], errors) + errs[0] = fmt.Sprintf("line %d: Invalid target:", value.Line) + return &yaml.TypeError{Errors: errs} + } + + return nil +} + +func (u *Url) String() string { + var pwd string + if u.Password != "" { + pwd = ":******" + } + + return fmt.Sprintf("%s://%s%s@%s%s", u.Scheme, u.User, pwd, u.HostPort(), u.Root) +} + +func (u *Url) HostPort() string { + if u.Port != "" { + return net.JoinHostPort(u.Host, u.Port) + } + return u.Host +} + +const ( + imapsPort = "993" + imapPort = "143" + imapsSchema = "imaps" + imapSchema = "imap" +) + +func (u *Url) ForceTLS() bool { + return u.Scheme == imapsSchema || u.Port == imapsPort +} + +func (u *Url) setDefaultScheme() { + if u.Scheme == "" { + if u.Port == imapsPort { + u.Scheme = imapsSchema + } else { + u.Scheme = imapSchema + } + } +} + +func (u *Url) setDefaultPort() { + if u.Port == "" { + if u.Scheme == imapsSchema { + u.Port = imapsPort + } else { + u.Port = imapPort + } + } +} + +func (u *Url) sanitize() { + u.setDefaultScheme() + u.setDefaultPort() +} + +func (u *Url) validate() (errors []string) { + if u.Scheme != imapSchema && u.Scheme != imapsSchema { + errors = append(errors, fmt.Sprintf("Unknown scheme %q", u.Scheme)) + } + + if u.Host == "" { + errors = append(errors, "Host not set") + } + + if u.Root == "" || u.Root == "/" { + errors = append(errors, "Root path not set") + } + return +} diff --git a/pkg/config/url_test.go b/pkg/config/url_test.go new file mode 100644 index 0000000..d345690 --- /dev/null +++ b/pkg/config/url_test.go @@ -0,0 +1,108 @@ +package config + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "gopkg.in/yaml.v3" +) + +func TestUrl_Unmarshal(t *testing.T) { + + tests := []struct { + name string + inp string + url Url + wantErr bool + }{ + {name: "Empty", inp: `url: ""`, wantErr: true}, + {name: "Simple String", inp: `url: "imap://user:pass@example.net:143/INBOX"`, url: Url{ + Scheme: "imap", + User: "user", + Password: "pass", + Host: "example.net", + Port: "143", + Root: "/INBOX", + }}, + {name: "Simple String with @", inp: `url: "imaps://user@example:pass@example.net:143/INBOX"`, url: Url{ + Scheme: "imaps", + User: "user@example", + Password: "pass", + Host: "example.net", + Port: "143", + Root: "/INBOX", + }}, + {name: "Simple String with %40", inp: `url: "imap://user%40example:pass@example.net:4711/INBOX"`, url: Url{ + Scheme: "imap", + User: "user@example", + Password: "pass", + Host: "example.net", + Port: "4711", + Root: "/INBOX", + }}, + {name: "Err: Inv scheme", inp: `url: "smtp://user%40example:pass@example.net:4711/INBOX"`, wantErr: true}, + {name: "Err: No Host", inp: `url: "imap://user%40example:pass/INBOX"`, wantErr: true}, + {name: "Err: Scheme Only", inp: `url: "imap://"`, wantErr: true}, + {name: "Err: No Root", inp: `url: "imap://user:pass@example.net:143"`, wantErr: true}, + {name: "Err: No Root: Slash", inp: `url: "imap://user:pass@example.net:143/"`, wantErr: true}, + {name: "Full", inp: `url: + scheme: imap + host: example.net + user: user + password: p4ss + port: 143 + root: INBOX +`, url: Url{ + Scheme: "imap", + User: "user", + Password: "p4ss", + Host: "example.net", + Port: "143", + Root: "INBOX", + }}, + {name: "Default Port", inp: `url: + scheme: imap + host: example.net + user: user + password: p4ss + root: INBOX +`, url: Url{ + Scheme: "imap", + User: "user", + Password: "p4ss", + Host: "example.net", + Port: "143", + Root: "INBOX", + }}, + {name: "Default Scheme", inp: `url: + host: example.net + user: user + password: p4ss + port: 993 + root: INBOX +`, url: Url{ + Scheme: "imaps", + User: "user", + Password: "p4ss", + Host: "example.net", + Port: "993", + Root: "INBOX", + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var u struct { + Url Url `yaml:"url"` + } + err := yaml.Unmarshal([]byte(tt.inp), &u) + if (err != nil) != tt.wantErr { + t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if diff := cmp.Diff(u.Url, tt.url); err == nil && diff != "" { + t.Error(diff) + } + }) + } +} -- cgit v1.2.3-54-g00ecf