table: Add identity, static and regexp table modules

This commit is contained in:
fox.cpp 2020-03-06 02:28:31 +03:00
parent e7d5418b88
commit a5288aa27a
No known key found for this signature in database
GPG key ID: E76D97CCEDE90B6C
4 changed files with 259 additions and 1 deletions

View file

@ -8,7 +8,7 @@ string. They are commonly called "table modules" or just "tables".
# File mapping (file_table) # File mapping (file_table)
This module build string-string mapping from a text file. This module builds string-string mapping from a text file.
File is reloaded every 15 seconds if there are any changes (detected using File is reloaded every 15 seconds if there are any changes (detected using
modification time). No changes are applied if file contains syntax errors. modification time). No changes are applied if file contains syntax errors.
@ -119,3 +119,80 @@ sql_table {
lookup "SELECT alias FROM aliases WHERE address = $1" lookup "SELECT alias FROM aliases WHERE address = $1"
} }
``` ```
# Static table (static)
The 'static' module implements table lookups using key-value pairs in its
configuration.
```
static {
entry KEY1 VALUE1
entry KEY2 VALUE2
...
}
```
## Configuration directives
**Syntax**: entry _key_ _value_
Add an entry to the table.
If the same key is used multiple times, the last one takes effect.
# Regexp rewrite table (regexp)
The 'regexp' module implements table lookups by applying a regular expression
to the key value. If it matches - 'replacement' value is returned with $N
placeholders being replaced with corresponding capture groups from the match.
Otherwise, no value is returned.
The regular expression syntax is the subset of PCRE. See
https://golang.org/pkg/regexp/syntax/ for details.
```
regexp <regexp> <replacement> {
full_match yes
case_insensitive yes
expand_placeholders yes
}
```
## Configuration directives
**Syntax**: full_match _boolean_ ++
**Default**: yes
Whether to implicitly add start/end anchors to the regular expression.
That is, if 'full_match' is yes, then the provided regular expression should
match the whole string. With no - partial match is enough.
**Syntax**: case_insensitive _boolean_ ++
**Default**: yes
Whether to make matching case-insensitive.
**Syntax**: expand_placeholders _boolean_ ++
**Default**: yes
Replace '$name' and '${name}' in the replacement string with contents of
corresponding capture groups from the match.
To insert a literal $ in the output, use $$ in the template.
# Identity table (identity)
The module 'identity' is a table module that just returns the key looked up.
```
identity { }
```
# No-op table (dummy)
The module 'dummy' represents an empty table.
```
dummy { }
```

View file

@ -0,0 +1,38 @@
package table
import (
"github.com/foxcpp/maddy/internal/config"
"github.com/foxcpp/maddy/internal/module"
)
type Identity struct {
modName string
instName string
}
func NewIdentity(modName, instName string, _, _ []string) (module.Module, error) {
return &Identity{
modName: modName,
instName: instName,
}, nil
}
func (s *Identity) Init(cfg *config.Map) error {
return nil
}
func (s *Identity) Name() string {
return s.modName
}
func (s *Identity) InstanceName() string {
return s.modName
}
func (s *Identity) Lookup(key string) (string, bool, error) {
return key, true, nil
}
func init() {
module.Register("identity", NewIdentity)
}

93
internal/table/regexp.go Normal file
View file

@ -0,0 +1,93 @@
package table
import (
"fmt"
"regexp"
"strings"
"github.com/foxcpp/maddy/internal/config"
"github.com/foxcpp/maddy/internal/module"
)
type Regexp struct {
modName string
instName string
inlineArgs []string
re *regexp.Regexp
replacement string
expandPlaceholders bool
}
func NewRegexp(modName, instName string, _, inlineArgs []string) (module.Module, error) {
return &Regexp{
modName: modName,
instName: instName,
inlineArgs: inlineArgs,
}, nil
}
func (r *Regexp) Init(cfg *config.Map) error {
var (
fullMatch bool
caseInsensitive bool
)
cfg.Bool("full_match", false, true, &fullMatch)
cfg.Bool("case_insensitive", false, true, &caseInsensitive)
cfg.Bool("expand_replaceholders", false, true, &r.expandPlaceholders)
if _, err := cfg.Process(); err != nil {
return err
}
if len(r.inlineArgs) < 2 {
return fmt.Errorf("%s: two arguments required", r.modName)
}
regex := r.inlineArgs[0]
r.replacement = r.inlineArgs[1]
if fullMatch {
if !strings.HasPrefix(regex, "^") {
regex = "^" + regex
}
if !strings.HasSuffix(regex, "$") {
regex = regex + "$"
}
}
if caseInsensitive {
regex = "(?i)" + regex
}
var err error
r.re, err = regexp.Compile(regex)
if err != nil {
return fmt.Errorf("%s: %v", r.modName, err)
}
return nil
}
func (r *Regexp) Name() string {
return r.modName
}
func (r *Regexp) InstanceName() string {
return r.modName
}
func (r *Regexp) Lookup(key string) (string, bool, error) {
matches := r.re.FindStringSubmatchIndex(key)
if matches == nil {
return "", false, nil
}
if !r.expandPlaceholders {
return r.replacement, true, nil
}
return string(r.re.ExpandString([]byte{}, r.replacement, key, matches)), true, nil
}
func init() {
module.Register("regexp", NewRegexp)
}

50
internal/table/static.go Normal file
View file

@ -0,0 +1,50 @@
package table
import (
"github.com/foxcpp/maddy/internal/config"
"github.com/foxcpp/maddy/internal/module"
)
type Static struct {
modName string
instName string
m map[string]string
}
func NewStatic(modName, instName string, _, _ []string) (module.Module, error) {
return &Static{
modName: modName,
instName: instName,
m: map[string]string{},
}, nil
}
func (s *Static) Init(cfg *config.Map) error {
cfg.Callback("entry", func(m *config.Map, node config.Node) error {
if len(node.Args) != 2 {
return config.NodeErr(node, "expected exactly two arguments")
}
s.m[node.Args[0]] = node.Args[1]
return nil
})
_, err := cfg.Process()
return err
}
func (s *Static) Name() string {
return s.modName
}
func (s *Static) InstanceName() string {
return s.modName
}
func (s *Static) Lookup(key string) (string, bool, error) {
val, ok := s.m[key]
return val, ok, nil
}
func init() {
module.Register("static", NewStatic)
}