diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..d8e9561
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8a2c9e5..3ce3588 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,9 +1,5 @@
-
-
-
-
diff --git a/postfix-config/comma-list-scanner/comma-list-scanner.go b/postfix-config/comma-list-scanner/comma-list-scanner.go
index 461eb99..6a3e6a1 100644
--- a/postfix-config/comma-list-scanner/comma-list-scanner.go
+++ b/postfix-config/comma-list-scanner/comma-list-scanner.go
@@ -3,6 +3,7 @@ package comma_list_scanner
import (
"bufio"
"bytes"
+ "fmt"
"io"
)
@@ -18,8 +19,15 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
- if i := bytes.IndexByte(data, ','); i >= 0 {
- return i + 1, bytes.TrimSpace(data[0:i]), nil
+ println("data", fmt.Sprintf("%s", data))
+ println("index", bytes.IndexAny(data, " ,"))
+ if i := bytes.IndexAny(data, " ,"); i >= 0 {
+ // consume all spaces after the comma
+ j := i + 1
+ for j < len(data) && data[j] == ' ' {
+ j++
+ }
+ return j, bytes.TrimSpace(data[0:i]), nil
}
// If we're at EOF, we have a final non-terminated line. Return it.
if atEOF {
@@ -34,6 +42,7 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner {
func (c *CommaListScanner) Scan() bool {
if c.r.Scan() {
c.text = c.r.Text()
+ return true
}
c.err = c.r.Err()
return false
@@ -42,3 +51,7 @@ func (c *CommaListScanner) Scan() bool {
func (c *CommaListScanner) Text() string {
return c.text
}
+
+func (c *CommaListScanner) Err() error {
+ return c.err
+}
diff --git a/postfix-config/comma-list-scanner/comma-list-scanner_test.go b/postfix-config/comma-list-scanner/comma-list-scanner_test.go
index 7e8fa3b..a4b779d 100644
--- a/postfix-config/comma-list-scanner/comma-list-scanner_test.go
+++ b/postfix-config/comma-list-scanner/comma-list-scanner_test.go
@@ -6,22 +6,40 @@ import (
"testing"
)
-var testCommaList = []struct {
- text string
- out []string
-}{
- {"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}},
- {"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}},
+var testCommaList = []string{
+ "hello, wow-this-is-cool, amazing",
+ "hello, wow-this-is-cool",
+ "hello, wow-this-is-cool, ",
+ "hello, wow-this-is-cool,",
+ ",hello, wow-this-is-cool",
+ ",hello, wow-this-is-cool,",
+ "hello, wow-this-is-cool,,,",
}
func TestNewCommaListScanner(t *testing.T) {
for _, i := range testCommaList {
- t.Run(i.text, func(t *testing.T) {
- s := NewCommaListScanner(strings.NewReader(i.text))
- n := 0
+ t.Run(i, func(t *testing.T) {
+ // use comma list scanner
+ s := NewCommaListScanner(strings.NewReader(i))
+ n := strings.Count(i, ",")
+ a := make([]string, 0, n+1)
for s.Scan() {
- assert.Equal(t, i.out[n], s.Text())
+ a = append(a, s.Text())
}
+ assert.NoError(t, s.Err())
+
+ // test against splitting and trimming strings
+ b := strings.Split(i, ",")
+ for i := 0; i < len(b); i++ {
+ c := strings.TrimSpace(b[i])
+ if c == "" {
+ b = append(b[0:i], b[i+1:]...)
+ i--
+ } else {
+ b[i] = c
+ }
+ }
+ assert.Equal(t, b, a)
})
}
}
diff --git a/postfix-config/config-parser/config-parser.go b/postfix-config/config-parser/config-parser.go
index f349ea2..7738bd3 100644
--- a/postfix-config/config-parser/config-parser.go
+++ b/postfix-config/config-parser/config-parser.go
@@ -38,7 +38,7 @@ scanAgain:
}
func (c *ConfigParser) Pair() (string, string) {
- return c.pair[0], c.pair[1]
+ return strings.TrimSpace(c.pair[0]), strings.TrimSpace(c.pair[1])
}
func (c *ConfigParser) Err() error {
diff --git a/postfix-config/config.go b/postfix-config/config.go
index e36c104..4057930 100644
--- a/postfix-config/config.go
+++ b/postfix-config/config.go
@@ -9,7 +9,20 @@ type Config struct {
VirtualMailboxMaps mapProvider.MapProvider
AliasMaps mapProvider.MapProvider
LocalRecipientMaps mapProvider.MapProvider
- SmtpdSenderLoginMaps string // TODO(melon): union map?
+ SmtpdSenderLoginMaps mapProvider.MapProvider
+}
+
+var parseProviderData = map[string]string{
+ "virtual_mailbox_domains": "comma",
+ "virtual_alias_maps": "comma",
+ "virtual_mailbox_maps": "comma",
+ "alias_maps": "comma",
+ "local_recipient_maps": "comma",
+ "smtpd_sender_login_maps": "union",
+}
+
+func (c *Config) ParseProvider(k string) string {
+ return parseProviderData[k]
}
func (c *Config) SetKey(k string, m mapProvider.MapProvider) {
@@ -25,14 +38,6 @@ func (c *Config) SetKey(k string, m mapProvider.MapProvider) {
case "local_recipient_maps":
c.LocalRecipientMaps = m
case "smtpd_sender_login_maps":
- c.SmtpdSenderLoginMaps = ""
+ c.SmtpdSenderLoginMaps = m
}
}
-
-func (c *Config) NeedsMapProvider(k string) bool {
- switch k {
- case "virtual_mailbox_domains", "virtual_alias_maps", "virtual_mailbox_maps", "alias_maps", "local_recipient_maps", "smtpd_sender_login_maps":
- return true
- }
- return false
-}
diff --git a/postfix-config/decoder.go b/postfix-config/decoder.go
index 4dfd143..0d96848 100644
--- a/postfix-config/decoder.go
+++ b/postfix-config/decoder.go
@@ -1,18 +1,20 @@
package postfix_config
import (
- "bufio"
+ "errors"
"fmt"
+ commaListScanner "github.com/1f349/lotus/postfix-config/comma-list-scanner"
configParser "github.com/1f349/lotus/postfix-config/config-parser"
mapProvider "github.com/1f349/lotus/postfix-config/map-provider"
"io"
+ "path/filepath"
"strings"
)
type Decoder struct {
- r *configParser.ConfigParser
- v *Config
- t map[string]string
+ r *configParser.ConfigParser
+ temp map[string]string
+ basePath string
}
func NewDecoder(r io.Reader) *Decoder {
@@ -20,30 +22,87 @@ func NewDecoder(r io.Reader) *Decoder {
}
func (d *Decoder) Load() error {
- d.v = &Config{}
for d.r.Scan() {
k, v := d.r.Pair()
- if d.v.NeedsMapProvider(k) {
+ d.temp[k] = v
+ }
+ if err := d.r.Err(); err != nil {
+ return err
+ }
+
+ switch d.value.ParseProvider(k) {
+ case "comma":
m := mapProvider.SequenceMapProvider{}
- s := bufio.NewScanner(strings.NewReader(v))
- s.Split(bufio.ScanWords)
+ s := commaListScanner.NewCommaListScanner(strings.NewReader(v))
for s.Scan() {
a := s.Text()
println("a", a)
if strings.HasPrefix(a, "$") {
- // is variable
+ m = append(m, &mapProvider.Variable{Name: a[1:]})
+ continue
}
- n := strings.IndexByte(a, ':')
- if n == -1 {
- return fmt.Errorf("missing prefix")
+
+ v2, err := d.createValue(a)
+ if err != nil {
+ return err
}
+ m = append(m, v2)
}
if err := s.Err(); err != nil {
return err
}
- d.v.SetKey(k, m)
+ d.value.SetKey(k, m)
+ case "union":
+ if !strings.HasPrefix(v, "unionmap:{") || !strings.HasSuffix(v, "}") {
+ return errors.New("key requires a union map")
+ }
+ v = v[len("unionmap:{") : len(v)-1]
+
+ m := mapProvider.SequenceMapProvider{}
+ s := commaListScanner.NewCommaListScanner(strings.NewReader(v))
+ for s.Scan() {
+ a := s.Text()
+ v2, err := d.createValue(a)
+ if err != nil {
+ return err
+ }
+ m = append(m, v2)
+ }
+ default:
+ return fmt.Errorf("key '%s' has no defined parse provider", k)
}
}
return d.r.Err()
}
+
+func (d *Decoder) createValue(a string) (mapProvider.MapProvider, error) {
+ n := strings.IndexByte(a, ':')
+ if n == -1 {
+ return nil, fmt.Errorf("missing prefix")
+ }
+
+ namespace := a[:n]
+ value := a[n+1:]
+ switch namespace {
+ case "mysql":
+ if !filepath.IsAbs(value) {
+ value = filepath.Join(d.basePath, value)
+ }
+ provider, err := mapProvider.NewMySqlMapProvider(value)
+ if err != nil {
+ return nil, err
+ }
+ return provider, nil
+ case "hash":
+ if !filepath.IsAbs(value) {
+ value = filepath.Join(d.basePath, value)
+ }
+ provider, err := mapProvider.NewHashMapProvider(value)
+ if err != nil {
+ return nil, err
+ }
+ return provider, nil
+ }
+ return nil, errors.New("invalid provider namespace")
+}
diff --git a/postfix-config/decoder_test.go b/postfix-config/decoder_test.go
index e646731..7bc4e2e 100644
--- a/postfix-config/decoder_test.go
+++ b/postfix-config/decoder_test.go
@@ -5,6 +5,8 @@ import (
_ "embed"
configParser "github.com/1f349/lotus/postfix-config/config-parser"
"github.com/stretchr/testify/assert"
+ "os"
+ "path/filepath"
"testing"
)
@@ -12,7 +14,15 @@ import (
var exampleConfig []byte
func TestDecoder_Load(t *testing.T) {
+ // get working directory
+ wd, err := os.Getwd()
+ assert.NoError(t, err)
+
+ // read example config
b := bytes.NewReader(exampleConfig)
- d := &Decoder{r: configParser.NewConfigParser(b)}
+ d := &Decoder{
+ r: configParser.NewConfigParser(b),
+ basePath: filepath.Join(wd, "test-data"),
+ }
assert.NoError(t, d.Load())
}
diff --git a/postfix-config/example.cf b/postfix-config/example.cf
index c16fd44..359b837 100644
--- a/postfix-config/example.cf
+++ b/postfix-config/example.cf
@@ -1,10 +1,10 @@
# this only contains the relevant config properties
-recipient_delimiter = +
+#recipient_delimiter = +
-virtual_mailbox_domains = mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf
-virtual_alias_maps = mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_wildcard_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_catchall_maps.cf
-virtual_mailbox_maps = mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_mailbox_maps.cf
-alias_maps = hash:/etc/aliases $virtual_alias_maps
+virtual_mailbox_domains = mysql:mysql_virtual_domains_maps.cf
+virtual_alias_maps = mysql:mysql_virtual_alias_maps.cf, mysql:mysql_virtual_alias_wildcard_maps.cf, mysql:mysql_virtual_alias_domain_maps.cf, mysql:mysql_virtual_alias_user_maps.cf, mysql:mysql_virtual_alias_userdomain_maps.cf, mysql:mysql_virtual_alias_domain_catchall_maps.cf, mysql:mysql_virtual_alias_user_catchall_maps.cf
+virtual_mailbox_maps = mysql:mysql_virtual_mailbox_maps.cf, mysql:mysql_virtual_alias_domain_mailbox_maps.cf, mysql:mysql_virtual_alias_user_mailbox_maps.cf, mysql:mysql_virtual_alias_userdomain_mailbox_maps.cf
+alias_maps = hash:aliases.txt $virtual_alias_maps
local_recipient_maps = $virtual_mailbox_maps $alias_maps
-smtpd_sender_login_maps = unionmap:{ hash:/etc/aliases, mysql:/etc/postfix/sql/mysql_sender_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_maps.cf }
+smtpd_sender_login_maps = unionmap:{ hash:aliases.txt, mysql:mysql_sender_alias_maps.cf, mysql:mysql_virtual_alias_maps.cf, mysql:mysql_virtual_alias_domain_maps.cf, mysql:mysql_virtual_alias_user_maps.cf, mysql:mysql_virtual_alias_userdomain_maps.cf }
diff --git a/postfix-config/test-data/aliases.txt b/postfix-config/test-data/aliases.txt
new file mode 100644
index 0000000..01c5c6a
--- /dev/null
+++ b/postfix-config/test-data/aliases.txt
@@ -0,0 +1 @@
+a: a b
\ No newline at end of file
diff --git a/postfix-config/test-data/mysql_sender_alias_maps.cf b/postfix-config/test-data/mysql_sender_alias_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_domain_catchall_maps.cf b/postfix-config/test-data/mysql_virtual_alias_domain_catchall_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_domain_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_alias_domain_mailbox_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_domain_maps.cf b/postfix-config/test-data/mysql_virtual_alias_domain_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_maps.cf b/postfix-config/test-data/mysql_virtual_alias_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_user_catchall_maps.cf b/postfix-config/test-data/mysql_virtual_alias_user_catchall_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_user_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_alias_user_mailbox_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_user_maps.cf b/postfix-config/test-data/mysql_virtual_alias_user_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_userdomain_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_alias_userdomain_mailbox_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_userdomain_maps.cf b/postfix-config/test-data/mysql_virtual_alias_userdomain_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_alias_wildcard_maps.cf b/postfix-config/test-data/mysql_virtual_alias_wildcard_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_domains_maps.cf b/postfix-config/test-data/mysql_virtual_domains_maps.cf
new file mode 100644
index 0000000..e69de29
diff --git a/postfix-config/test-data/mysql_virtual_mailbox_maps.cf b/postfix-config/test-data/mysql_virtual_mailbox_maps.cf
new file mode 100644
index 0000000..e69de29