Smail/server/plugins/catalog/catalog.go

142 lines
3.9 KiB
Go

package catalog
import (
"errors"
"runtime"
"sort"
"strings"
)
var (
ErrUnknownPluginID = errors.New("unknown plugin ID")
ErrInvalidPluginTarget = errors.New("invalid plugin target")
)
type Definition struct {
LogicalID string
WindowsExecutable string
UnixExecutable string
Capabilities []string
HandshakeCapabilities []string
SettingsFields []string
}
var definitions = []Definition{
{
LogicalID: "spam_block",
WindowsExecutable: "spam_block.exe",
UnixExecutable: "spam_block",
Capabilities: []string{"network"},
HandshakeCapabilities: []string{"network"},
SettingsFields: []string{"url", "timeout", "threshold"},
},
{
LogicalID: "wechat_push",
WindowsExecutable: "wechat_push.exe",
UnixExecutable: "wechat_push",
Capabilities: []string{"network"},
HandshakeCapabilities: []string{"network"},
SettingsFields: []string{"appId", "templateId", "userId", "webDomain"},
},
{
LogicalID: "telegram_push",
WindowsExecutable: "pmail_telegram_push.exe",
UnixExecutable: "pmail_telegram_push",
Capabilities: []string{"network"},
HandshakeCapabilities: []string{"network", "userSettings", "settingsHtml"},
SettingsFields: []string{"proxy", "timeout", "debug", "webDomain", "httpsEnabled"},
},
}
func Definitions() []Definition {
result := make([]Definition, len(definitions))
for index, definition := range definitions {
result[index] = cloneDefinition(definition)
}
return result
}
func Lookup(logicalID string) (Definition, bool) {
for _, definition := range definitions {
if definition.LogicalID == logicalID {
return cloneDefinition(definition), true
}
}
return Definition{}, false
}
func ExecutableName(logicalID, goos string) (string, error) {
definition, ok := Lookup(logicalID)
if !ok {
return "", ErrUnknownPluginID
}
if goos == "windows" {
return definition.WindowsExecutable, nil
}
if goos == "linux" || goos == "darwin" {
return definition.UnixExecutable, nil
}
return "", ErrInvalidPluginTarget
}
func CurrentExecutableName(logicalID string) (string, error) {
return ExecutableName(logicalID, runtime.GOOS)
}
func EnabledDefinitions(values []string, goos string) ([]Definition, error) {
enabled := make(map[string]struct{}, len(values))
for _, value := range values {
logicalID, err := LogicalIDFromValue(value, goos)
if err != nil {
return nil, err
}
if _, exists := enabled[logicalID]; exists {
return nil, ErrInvalidPluginTarget
}
enabled[logicalID] = struct{}{}
}
result := make([]Definition, 0, len(enabled))
for _, definition := range definitions {
if _, ok := enabled[definition.LogicalID]; ok {
result = append(result, cloneDefinition(definition))
}
}
sort.Slice(result, func(left, right int) bool {
return result[left].LogicalID < result[right].LogicalID
})
return result, nil
}
func LogicalIDFromValue(value, goos string) (string, error) {
value = strings.TrimSpace(value)
if value == "" || value != strings.ToLower(value) || strings.ContainsAny(value, `/\\*?[]`) {
return "", ErrInvalidPluginTarget
}
if _, ok := Lookup(value); ok {
return value, nil
}
for _, definition := range definitions {
executable, err := ExecutableName(definition.LogicalID, goos)
if err != nil {
return "", err
}
if value == executable {
return definition.LogicalID, nil
}
}
return "", ErrUnknownPluginID
}
func IsCurrentExecutable(logicalID, executable string) bool {
expected, err := CurrentExecutableName(logicalID)
return err == nil && executable == expected
}
func cloneDefinition(source Definition) Definition {
result := source
result.Capabilities = append([]string(nil), source.Capabilities...)
result.HandshakeCapabilities = append([]string(nil), source.HandshakeCapabilities...)
result.SettingsFields = append([]string(nil), source.SettingsFields...)
return result
}