mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
fix #1003
This commit is contained in:
parent
21958768d8
commit
be0dedf260
@ -7,14 +7,12 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircmatch"
|
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
|
"github.com/oragono/oragono/irc/utils"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
|
// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
|
||||||
@ -301,12 +299,16 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
matcher := ircmatch.MakeMatch(userhost)
|
matcher, err := utils.CompileGlob(userhost, false)
|
||||||
|
if err != nil {
|
||||||
|
// not much we can do here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
clients.RLock()
|
clients.RLock()
|
||||||
defer clients.RUnlock()
|
defer clients.RUnlock()
|
||||||
for _, client := range clients.byNick {
|
for _, client := range clients.byNick {
|
||||||
if matcher.Match(client.NickMaskCasefolded()) {
|
if matcher.MatchString(client.NickMaskCasefolded()) {
|
||||||
set.Add(client)
|
set.Add(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,8 +332,9 @@ type MaskInfo struct {
|
|||||||
// UserMaskSet holds a set of client masks and lets you match hostnames to them.
|
// UserMaskSet holds a set of client masks and lets you match hostnames to them.
|
||||||
type UserMaskSet struct {
|
type UserMaskSet struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
masks map[string]MaskInfo
|
serialCacheUpdateMutex sync.Mutex
|
||||||
regexp *regexp.Regexp
|
masks map[string]MaskInfo
|
||||||
|
regexp *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserMaskSet() *UserMaskSet {
|
func NewUserMaskSet() *UserMaskSet {
|
||||||
@ -345,6 +348,9 @@ func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskA
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set.serialCacheUpdateMutex.Lock()
|
||||||
|
defer set.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
set.Lock()
|
set.Lock()
|
||||||
if set.masks == nil {
|
if set.masks == nil {
|
||||||
set.masks = make(map[string]MaskInfo)
|
set.masks = make(map[string]MaskInfo)
|
||||||
@ -373,6 +379,9 @@ func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set.serialCacheUpdateMutex.Lock()
|
||||||
|
defer set.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
set.Lock()
|
set.Lock()
|
||||||
_, removed := set.masks[mask]
|
_, removed := set.masks[mask]
|
||||||
if removed {
|
if removed {
|
||||||
@ -430,31 +439,14 @@ func (set *UserMaskSet) Length() int {
|
|||||||
// parts are re-joined and finally all masks are joined into a big
|
// parts are re-joined and finally all masks are joined into a big
|
||||||
// or-expression.
|
// or-expression.
|
||||||
func (set *UserMaskSet) setRegexp() {
|
func (set *UserMaskSet) setRegexp() {
|
||||||
var re *regexp.Regexp
|
|
||||||
|
|
||||||
set.RLock()
|
set.RLock()
|
||||||
maskExprs := make([]string, len(set.masks))
|
maskExprs := make([]string, len(set.masks))
|
||||||
index := 0
|
|
||||||
for mask := range set.masks {
|
for mask := range set.masks {
|
||||||
manyParts := strings.Split(mask, "*")
|
maskExprs = append(maskExprs, mask)
|
||||||
manyExprs := make([]string, len(manyParts))
|
|
||||||
for mindex, manyPart := range manyParts {
|
|
||||||
oneParts := strings.Split(manyPart, "?")
|
|
||||||
oneExprs := make([]string, len(oneParts))
|
|
||||||
for oindex, onePart := range oneParts {
|
|
||||||
oneExprs[oindex] = regexp.QuoteMeta(onePart)
|
|
||||||
}
|
|
||||||
manyExprs[mindex] = strings.Join(oneExprs, ".")
|
|
||||||
}
|
|
||||||
maskExprs[index] = strings.Join(manyExprs, ".*")
|
|
||||||
index++
|
|
||||||
}
|
}
|
||||||
set.RUnlock()
|
set.RUnlock()
|
||||||
|
|
||||||
if index > 0 {
|
re, _ := utils.CompileMasks(maskExprs)
|
||||||
expr := "^(" + strings.Join(maskExprs, "|") + ")$"
|
|
||||||
re, _ = regexp.Compile(expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
set.Lock()
|
set.Lock()
|
||||||
set.regexp = re
|
set.regexp = re
|
||||||
|
@ -853,7 +853,7 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, glob := range config.Server.WebSockets.AllowedOrigins {
|
for _, glob := range config.Server.WebSockets.AllowedOrigins {
|
||||||
globre, err := utils.CompileGlob(glob)
|
globre, err := utils.CompileGlob(glob, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob)
|
return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob)
|
||||||
}
|
}
|
||||||
@ -1219,7 +1219,7 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
standard, err = utils.CompileGlob(guestFormat)
|
standard, err = utils.CompileGlob(guestFormat, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1235,6 +1235,6 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded))
|
folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded), false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
"github.com/goshuirc/irc-go/ircmatch"
|
|
||||||
"github.com/goshuirc/irc-go/ircmsg"
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
"github.com/oragono/oragono/irc/custime"
|
"github.com/oragono/oragono/irc/custime"
|
||||||
@ -1280,10 +1279,14 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher := ircmatch.MakeMatch(mask)
|
matcher, err := utils.CompileGlob(mask, false)
|
||||||
|
if err != nil {
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for _, clientMask := range client.AllNickmasks() {
|
for _, clientMask := range client.AllNickmasks() {
|
||||||
if !klineMyself && matcher.Match(clientMask) {
|
if !klineMyself && matcher.MatchString(clientMask) {
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>"))
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1327,7 +1330,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
|
|
||||||
for _, mcl := range server.clients.AllClients() {
|
for _, mcl := range server.clients.AllClients() {
|
||||||
for _, clientMask := range mcl.AllNickmasks() {
|
for _, clientMask := range mcl.AllNickmasks() {
|
||||||
if matcher.Match(clientMask) {
|
if matcher.MatchString(clientMask) {
|
||||||
clientsToKill = append(clientsToKill, mcl)
|
clientsToKill = append(clientsToKill, mcl)
|
||||||
killedClientNicks = append(killedClientNicks, mcl.nick)
|
killedClientNicks = append(killedClientNicks, mcl.nick)
|
||||||
}
|
}
|
||||||
|
15
irc/kline.go
15
irc/kline.go
@ -6,12 +6,14 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircmatch"
|
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -23,7 +25,7 @@ type KLineInfo struct {
|
|||||||
// Mask that is blocked.
|
// Mask that is blocked.
|
||||||
Mask string
|
Mask string
|
||||||
// Matcher, to facilitate fast matching.
|
// Matcher, to facilitate fast matching.
|
||||||
Matcher ircmatch.Matcher
|
Matcher *regexp.Regexp
|
||||||
// Info contains information on the ban.
|
// Info contains information on the ban.
|
||||||
Info IPBanInfo
|
Info IPBanInfo
|
||||||
}
|
}
|
||||||
@ -80,9 +82,14 @@ func (km *KLineManager) AddMask(mask string, duration time.Duration, reason, ope
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (km *KLineManager) addMaskInternal(mask string, info IPBanInfo) {
|
func (km *KLineManager) addMaskInternal(mask string, info IPBanInfo) {
|
||||||
|
re, err := utils.CompileGlob(mask, false)
|
||||||
|
// this is validated externally and shouldn't fail regardless
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
kln := KLineInfo{
|
kln := KLineInfo{
|
||||||
Mask: mask,
|
Mask: mask,
|
||||||
Matcher: ircmatch.MakeMatch(mask),
|
Matcher: re,
|
||||||
Info: info,
|
Info: info,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +196,7 @@ func (km *KLineManager) CheckMasks(masks ...string) (isBanned bool, info IPBanIn
|
|||||||
|
|
||||||
for _, entryInfo := range km.entries {
|
for _, entryInfo := range km.entries {
|
||||||
for _, mask := range masks {
|
for _, mask := range masks {
|
||||||
if entryInfo.Matcher.Match(mask) {
|
if entryInfo.Matcher.MatchString(mask) {
|
||||||
return true, entryInfo.Info
|
return true, entryInfo.Info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,21 +11,53 @@ import (
|
|||||||
|
|
||||||
// yet another glob implementation in Go
|
// yet another glob implementation in Go
|
||||||
|
|
||||||
func CompileGlob(glob string) (result *regexp.Regexp, err error) {
|
func addRegexp(buf *bytes.Buffer, glob string, submatch bool) (err error) {
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteByte('^')
|
|
||||||
for _, r := range glob {
|
for _, r := range glob {
|
||||||
switch r {
|
switch r {
|
||||||
case '*':
|
case '*':
|
||||||
buf.WriteString("(.*)")
|
if submatch {
|
||||||
|
buf.WriteString("(.*)")
|
||||||
|
} else {
|
||||||
|
buf.WriteString(".*")
|
||||||
|
}
|
||||||
case '?':
|
case '?':
|
||||||
buf.WriteString("(.)")
|
if submatch {
|
||||||
|
buf.WriteString("(.)")
|
||||||
|
} else {
|
||||||
|
buf.WriteString(".")
|
||||||
|
}
|
||||||
case 0xFFFD:
|
case 0xFFFD:
|
||||||
return nil, &syntax.Error{Code: syntax.ErrInvalidUTF8, Expr: glob}
|
return &syntax.Error{Code: syntax.ErrInvalidUTF8, Expr: glob}
|
||||||
default:
|
default:
|
||||||
buf.WriteString(regexp.QuoteMeta(string(r)))
|
buf.WriteString(regexp.QuoteMeta(string(r)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompileGlob(glob string, submatch bool) (result *regexp.Regexp, err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteByte('^')
|
||||||
|
err = addRegexp(&buf, glob, submatch)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
buf.WriteByte('$')
|
buf.WriteByte('$')
|
||||||
return regexp.Compile(buf.String())
|
return regexp.Compile(buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CompileMasks(masks []string) (result *regexp.Regexp, err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("^(")
|
||||||
|
for i, mask := range masks {
|
||||||
|
err = addRegexp(&buf, mask, false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i != len(masks)-1 {
|
||||||
|
buf.WriteByte('|')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString(")$")
|
||||||
|
return regexp.Compile(buf.String())
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func globMustCompile(glob string) *regexp.Regexp {
|
func globMustCompile(glob string) *regexp.Regexp {
|
||||||
re, err := CompileGlob(glob)
|
re, err := CompileGlob(glob, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -46,3 +46,123 @@ func TestGlob(t *testing.T) {
|
|||||||
assertMatches("S*e", "Skåne", true, t)
|
assertMatches("S*e", "Skåne", true, t)
|
||||||
assertMatches("Sk?ne", "Skåne", true, t)
|
assertMatches("Sk?ne", "Skåne", true, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkGlob(b *testing.B) {
|
||||||
|
g := globMustCompile("https://*google.com")
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
g.MatchString("https://www.google.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGlobCompilation(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompileGlob("https://*google.com", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are actual bans from my production network :-/
|
||||||
|
var bans = []string{
|
||||||
|
"*!*@tor-network.onion",
|
||||||
|
"`!*@*",
|
||||||
|
"qanon!*@*",
|
||||||
|
"*!bibi@tor-network.onion",
|
||||||
|
"shivarm!*@*",
|
||||||
|
"8====d!*@*",
|
||||||
|
"shiviram!*@*",
|
||||||
|
"poop*!*@*",
|
||||||
|
"shivoram!*@*",
|
||||||
|
"shivvy!*@*",
|
||||||
|
"shavirim!*@*",
|
||||||
|
"shivarm_!*@*",
|
||||||
|
"_!*@*",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMasks(t *testing.T) {
|
||||||
|
matcher, err := CompileMasks(bans)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matcher.MatchString("evan!user@tor-network.onion") {
|
||||||
|
t.Errorf("match expected")
|
||||||
|
}
|
||||||
|
if !matcher.MatchString("`!evan@b9un4fv3he44q.example.com") {
|
||||||
|
t.Errorf("match expected")
|
||||||
|
}
|
||||||
|
if matcher.MatchString("horse!horse@t5dwi8vacg47y.example.com") {
|
||||||
|
t.Errorf("match not expected")
|
||||||
|
}
|
||||||
|
if matcher.MatchString("horse_!horse@t5dwi8vacg47y.example.com") {
|
||||||
|
t.Errorf("match not expected")
|
||||||
|
}
|
||||||
|
if matcher.MatchString("shivaram!shivaram@yrqgsrjy2p7my.example.com") {
|
||||||
|
t.Errorf("match not expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMasksCompile(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompileMasks(bans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMasksMatch(b *testing.B) {
|
||||||
|
matcher, _ := CompileMasks(bans)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
matcher.MatchString("evan!user@tor-network.onion")
|
||||||
|
matcher.MatchString("horse_!horse@t5dwi8vacg47y.example.com")
|
||||||
|
matcher.MatchString("shivaram!shivaram@yrqgsrjy2p7my.example.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare performance to compilation of the | clauses as separate regexes
|
||||||
|
// first for compilation, then for matching
|
||||||
|
|
||||||
|
func compileAll(masks []string) (result []*regexp.Regexp, err error) {
|
||||||
|
a := make([]*regexp.Regexp, 0, len(masks))
|
||||||
|
for _, mask := range masks {
|
||||||
|
m, err := CompileGlob(mask, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a = append(a, m)
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesAny(masks []*regexp.Regexp, str string) bool {
|
||||||
|
for _, r := range masks {
|
||||||
|
if r.MatchString(str) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLinearCompile(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
compileAll(bans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLinearMatch(b *testing.B) {
|
||||||
|
a, err := compileAll(bans)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if matchesAny(a, "horse_!horse@t5dwi8vacg47y.example.com") {
|
||||||
|
panic("incorrect match")
|
||||||
|
}
|
||||||
|
if !matchesAny(a, "evan!user@tor-network.onion") {
|
||||||
|
panic("incorrect match")
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
matchesAny(a, "horse_!horse@t5dwi8vacg47y.example.com")
|
||||||
|
matchesAny(a, "evan!user@tor-network.onion")
|
||||||
|
matchesAny(a, "shivaram!shivaram@yrqgsrjy2p7my.example.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
19
vendor/github.com/goshuirc/e-nfa/.travis.yml
generated
vendored
19
vendor/github.com/goshuirc/e-nfa/.travis.yml
generated
vendored
@ -1,19 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.4
|
|
||||||
- tip
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go get golang.org/x/tools/cmd/cover
|
|
||||||
- go get golang.org/x/tools/cmd/vet
|
|
||||||
- go get golang.org/x/tools/cmd/goimports
|
|
||||||
- go get github.com/golang/lint/golint
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go vet ./...
|
|
||||||
# - $HOME/gopath/bin/goveralls -coverprofile=coverage.cov -service=travis-ci
|
|
||||||
# - bash <(curl -s https://codecov.io/bash)
|
|
||||||
- go test -bench=. -benchmem ./...
|
|
||||||
#- sh ./install_all_cmd.sh
|
|
122
vendor/github.com/goshuirc/e-nfa/README.md
generated
vendored
122
vendor/github.com/goshuirc/e-nfa/README.md
generated
vendored
@ -1,122 +0,0 @@
|
|||||||
ε-NFA: Epsilon-Nondeterministic finite automaton
|
|
||||||
==============
|
|
||||||
|
|
||||||
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/kkdai/e-nfa/master/LICENSE) [![GoDoc](https://godoc.org/github.com/kkdai/e-nfa?status.svg)](https://godoc.org/github.com/kkdai/e-nfa) [![Build Status](https://travis-ci.org/kkdai/e-nfa.svg?branch=master)](https://travis-ci.org/kkdai/e-nfa)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
![image](https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/NFAexample.svg/250px-NFAexample.svg.png)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
What is Epsilon-Nondeterministic finite automaton
|
|
||||||
=============
|
|
||||||
|
|
||||||
`ε-NFA`: Epsilon-Nondeterministic finite automaton (so call:Nondeterministic finite automaton with ε-moves)
|
|
||||||
|
|
||||||
In the automata theory, a nondeterministic finite automaton with ε-moves (NFA-ε)(also known as NFA-λ) is an extension of nondeterministic finite automaton(NFA), which allows a transformation to a new state without consuming any input symbols. The transitions without consuming an input symbol are called ε-transitions or λ-transitions. In the state diagrams, they are usually labeled with the Greek letter ε or λ.
|
|
||||||
|
|
||||||
(sited from [here](https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton))
|
|
||||||
|
|
||||||
|
|
||||||
Looking for DFA implement?
|
|
||||||
=============
|
|
||||||
|
|
||||||
I also write a DFA implenent in Go here. [https://github.com/kkdai/dfa](https://github.com/kkdai/dfa)
|
|
||||||
|
|
||||||
Looking for NFA implement?
|
|
||||||
=============
|
|
||||||
|
|
||||||
I also write a NFA implenent in Go here. [https://github.com/kkdai/nfa](https://github.com/kkdai/nfa)
|
|
||||||
|
|
||||||
|
|
||||||
Installation and Usage
|
|
||||||
=============
|
|
||||||
|
|
||||||
|
|
||||||
Install
|
|
||||||
---------------
|
|
||||||
|
|
||||||
go get github.com/kkdai/e-nfa
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Following is sample code to implement a epsilon-NFA automata diagram as follow:
|
|
||||||
|
|
||||||
![image](image/eNFA.png)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kkdai/enfa"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
nfa := NewENFA(0, false)
|
|
||||||
nfa.AddState(1, false)
|
|
||||||
nfa.AddState(2, false)
|
|
||||||
nfa.AddState(3, true)
|
|
||||||
nfa.AddState(4, false)
|
|
||||||
nfa.AddState(5, false)
|
|
||||||
|
|
||||||
nfa.AddTransition(0, "1", 1)
|
|
||||||
nfa.AddTransition(0, "0", 4)
|
|
||||||
|
|
||||||
nfa.AddTransition(1, "1", 2)
|
|
||||||
nfa.AddTransition(1, "", 3) //epsilon
|
|
||||||
nfa.AddTransition(2, "1", 3)
|
|
||||||
nfa.AddTransition(4, "0", 5)
|
|
||||||
nfa.AddTransition(4, "", 1, 2) //E -> epsilon B C
|
|
||||||
nfa.AddTransition(5, "0", 3)
|
|
||||||
|
|
||||||
nfa.PrintTransitionTable()
|
|
||||||
|
|
||||||
if !nfa.VerifyInputs([]string{"1"}) {
|
|
||||||
fmt.Printf("Verify inputs is failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
nfa.Reset()
|
|
||||||
|
|
||||||
if !nfa.VerifyInputs([]string{"1", "1", "1"}) {
|
|
||||||
fmt.Printf("Verify inputs is failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
nfa.Reset()
|
|
||||||
|
|
||||||
if !nfa.VerifyInputs([]string{"0", "1"}) {
|
|
||||||
fmt.Printf"Verify inputs is failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
nfa.Reset()
|
|
||||||
if !nfa.VerifyInputs([]string{"0", "0", "0"}) {
|
|
||||||
fmt.Printf("Verify inputs is failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Inspired By
|
|
||||||
=============
|
|
||||||
|
|
||||||
- [ε-NFA: Wiki](https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton_with_%CE%B5-moves)
|
|
||||||
- [Coursera: Automata](https://class.coursera.org/automata-004/)
|
|
||||||
|
|
||||||
Project52
|
|
||||||
---------------
|
|
||||||
|
|
||||||
It is one of my [project 52](https://github.com/kkdai/project52).
|
|
||||||
|
|
||||||
|
|
||||||
License
|
|
||||||
---------------
|
|
||||||
|
|
||||||
This package is licensed under MIT license. See LICENSE for details.
|
|
185
vendor/github.com/goshuirc/e-nfa/enfa.go
generated
vendored
185
vendor/github.com/goshuirc/e-nfa/enfa.go
generated
vendored
@ -1,185 +0,0 @@
|
|||||||
package enfa
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type transitionInput struct {
|
|
||||||
srcState int
|
|
||||||
input string
|
|
||||||
}
|
|
||||||
|
|
||||||
type destState map[int]bool
|
|
||||||
|
|
||||||
type ENFA struct {
|
|
||||||
initState int
|
|
||||||
currentState map[int]bool
|
|
||||||
totalStates []int
|
|
||||||
finalStates []int
|
|
||||||
transition map[transitionInput]destState
|
|
||||||
inputMap map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
//New a new NFA
|
|
||||||
func NewENFA(initState int, isFinal bool) *ENFA {
|
|
||||||
|
|
||||||
retNFA := &ENFA{
|
|
||||||
transition: make(map[transitionInput]destState),
|
|
||||||
inputMap: make(map[string]bool),
|
|
||||||
initState: initState}
|
|
||||||
|
|
||||||
retNFA.currentState = make(map[int]bool)
|
|
||||||
retNFA.currentState[initState] = true
|
|
||||||
retNFA.AddState(initState, isFinal)
|
|
||||||
return retNFA
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add new state in this NFA
|
|
||||||
func (d *ENFA) AddState(state int, isFinal bool) {
|
|
||||||
if state == -1 {
|
|
||||||
fmt.Println("Cannot add state as -1, it is dead state")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d.totalStates = append(d.totalStates, state)
|
|
||||||
if isFinal {
|
|
||||||
d.finalStates = append(d.finalStates, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add new transition function into NFA
|
|
||||||
func (d *ENFA) AddTransition(srcState int, input string, dstStateList ...int) {
|
|
||||||
find := false
|
|
||||||
|
|
||||||
//find input if exist in NFA input List
|
|
||||||
if _, ok := d.inputMap[input]; !ok {
|
|
||||||
//not exist, new input in this NFA
|
|
||||||
d.inputMap[input] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range d.totalStates {
|
|
||||||
if v == srcState {
|
|
||||||
find = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !find {
|
|
||||||
fmt.Println("No such state:", srcState, " in current NFA")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dstMap := make(map[int]bool)
|
|
||||||
for _, destState := range dstStateList {
|
|
||||||
dstMap[destState] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
targetTrans := transitionInput{srcState: srcState, input: input}
|
|
||||||
d.transition[targetTrans] = dstMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ENFA) CheckPathExist(src int, input string, dst int) bool {
|
|
||||||
retMap, _ := d.transition[transitionInput{srcState: src, input: input}]
|
|
||||||
if _, ok := retMap[dst]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ENFA) Input(testInput string) []int {
|
|
||||||
updateCurrentState := make(map[int]bool)
|
|
||||||
for current, _ := range d.currentState {
|
|
||||||
for _, realTestInput := range []string{testInput, "*", "?"} {
|
|
||||||
intputTrans := transitionInput{srcState: current, input: realTestInput}
|
|
||||||
valMap, ok := d.transition[intputTrans]
|
|
||||||
if ok {
|
|
||||||
for dst, _ := range valMap {
|
|
||||||
updateCurrentState[dst] = true
|
|
||||||
|
|
||||||
//Update epsilon input way... if exist
|
|
||||||
epsilonTrans := transitionInput{srcState: dst}
|
|
||||||
if eMap, ok := d.transition[epsilonTrans]; ok {
|
|
||||||
for eDst, _ := range eMap {
|
|
||||||
updateCurrentState[eDst] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//dead state, remove in current state
|
|
||||||
//do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//update curret state
|
|
||||||
d.currentState = updateCurrentState
|
|
||||||
|
|
||||||
//return result
|
|
||||||
var ret []int
|
|
||||||
for state, _ := range updateCurrentState {
|
|
||||||
ret = append(ret, state)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
//To verify current state if it is final state
|
|
||||||
func (d *ENFA) Verify() bool {
|
|
||||||
for _, val := range d.finalStates {
|
|
||||||
for cState, _ := range d.currentState {
|
|
||||||
if val == cState {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
//Reset NFA state to initilize state, but all state and transition function will remain
|
|
||||||
func (d *ENFA) Reset() {
|
|
||||||
initState := make(map[int]bool)
|
|
||||||
initState[d.initState] = true
|
|
||||||
d.currentState = initState
|
|
||||||
}
|
|
||||||
|
|
||||||
//Verify if list of input could be accept by NFA or not
|
|
||||||
func (d *ENFA) VerifyInputs(inputs []string) bool {
|
|
||||||
for _, v := range inputs {
|
|
||||||
d.Input(v)
|
|
||||||
}
|
|
||||||
return d.Verify()
|
|
||||||
}
|
|
||||||
|
|
||||||
//To print detail transition table contain of current NFA
|
|
||||||
func (d *ENFA) PrintTransitionTable() {
|
|
||||||
fmt.Println("===================================================")
|
|
||||||
//list all inputs
|
|
||||||
var inputList []string
|
|
||||||
for key, _ := range d.inputMap {
|
|
||||||
if key == "" {
|
|
||||||
fmt.Printf("\tε|")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\t%s|", key)
|
|
||||||
}
|
|
||||||
inputList = append(inputList, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Println("---------------------------------------------------")
|
|
||||||
|
|
||||||
for _, state := range d.totalStates {
|
|
||||||
fmt.Printf("%d |", state)
|
|
||||||
for _, key := range inputList {
|
|
||||||
checkInput := transitionInput{srcState: state, input: key}
|
|
||||||
if dstState, ok := d.transition[checkInput]; ok {
|
|
||||||
fmt.Printf("\t")
|
|
||||||
for val, _ := range dstState {
|
|
||||||
fmt.Printf("%d,", val)
|
|
||||||
}
|
|
||||||
fmt.Printf("|")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\tNA|")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("---------------------------------------------------")
|
|
||||||
fmt.Println("===================================================")
|
|
||||||
}
|
|
7
vendor/github.com/goshuirc/irc-go/ircmatch/doc.go
generated
vendored
7
vendor/github.com/goshuirc/irc-go/ircmatch/doc.go
generated
vendored
@ -1,7 +0,0 @@
|
|||||||
// written by Daniel Oaks <daniel@danieloaks.net>
|
|
||||||
// released under the ISC license
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package ircmatch handles matching IRC strings with the traditional glob-like syntax.
|
|
||||||
*/
|
|
||||||
package ircmatch
|
|
57
vendor/github.com/goshuirc/irc-go/ircmatch/ircmatch.go
generated
vendored
57
vendor/github.com/goshuirc/irc-go/ircmatch/ircmatch.go
generated
vendored
@ -1,57 +0,0 @@
|
|||||||
package ircmatch
|
|
||||||
|
|
||||||
import enfa "github.com/goshuirc/e-nfa"
|
|
||||||
|
|
||||||
// Matcher represents an object that can match IRC strings.
|
|
||||||
type Matcher struct {
|
|
||||||
internalENFA *enfa.ENFA
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeMatch creates a Matcher.
|
|
||||||
func MakeMatch(globTemplate string) Matcher {
|
|
||||||
var newmatch Matcher
|
|
||||||
|
|
||||||
// assemble internal enfa
|
|
||||||
newmatch.internalENFA = enfa.NewENFA(0, false)
|
|
||||||
|
|
||||||
var currentState int
|
|
||||||
var lastWasStar bool
|
|
||||||
for _, char := range globTemplate {
|
|
||||||
if char == '*' {
|
|
||||||
if lastWasStar {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newmatch.internalENFA.AddTransition(currentState, "*", currentState)
|
|
||||||
lastWasStar = true
|
|
||||||
continue
|
|
||||||
} else if char == '?' {
|
|
||||||
newmatch.internalENFA.AddState(currentState+1, false)
|
|
||||||
newmatch.internalENFA.AddTransition(currentState, "?", currentState+1)
|
|
||||||
currentState++
|
|
||||||
} else {
|
|
||||||
newmatch.internalENFA.AddState(currentState+1, false)
|
|
||||||
newmatch.internalENFA.AddTransition(currentState, string(char), currentState+1)
|
|
||||||
currentState++
|
|
||||||
}
|
|
||||||
|
|
||||||
lastWasStar = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// create end state
|
|
||||||
newmatch.internalENFA.AddState(currentState+1, true)
|
|
||||||
newmatch.internalENFA.AddTransition(currentState, "", currentState+1)
|
|
||||||
|
|
||||||
return newmatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match returns true if the given string matches this glob.
|
|
||||||
func (menfa *Matcher) Match(search string) bool {
|
|
||||||
var searchChars []string
|
|
||||||
for _, char := range search {
|
|
||||||
searchChars = append(searchChars, string(char))
|
|
||||||
}
|
|
||||||
|
|
||||||
isMatch := menfa.internalENFA.VerifyInputs(searchChars)
|
|
||||||
menfa.internalENFA.Reset()
|
|
||||||
return isMatch
|
|
||||||
}
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -17,11 +17,9 @@ github.com/go-sql-driver/mysql
|
|||||||
github.com/gorilla/websocket
|
github.com/gorilla/websocket
|
||||||
# github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940
|
# github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940
|
||||||
## explicit
|
## explicit
|
||||||
github.com/goshuirc/e-nfa
|
|
||||||
# github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac
|
# github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac
|
||||||
## explicit
|
## explicit
|
||||||
github.com/goshuirc/irc-go/ircfmt
|
github.com/goshuirc/irc-go/ircfmt
|
||||||
github.com/goshuirc/irc-go/ircmatch
|
|
||||||
github.com/goshuirc/irc-go/ircmsg
|
github.com/goshuirc/irc-go/ircmsg
|
||||||
# github.com/onsi/ginkgo v1.12.0
|
# github.com/onsi/ginkgo v1.12.0
|
||||||
## explicit
|
## explicit
|
||||||
|
Loading…
Reference in New Issue
Block a user