xpat
This commit is contained in:
@@ -333,6 +333,9 @@ type Database interface {
|
||||
|
||||
// find cites in text
|
||||
FindCitesInText(msg string) ([]string, error)
|
||||
|
||||
// find headers in group with lo/hi watermark and list of patterns
|
||||
FindHeaders(group, headername string, lo, hi int64) (ArticleHeaders, error)
|
||||
}
|
||||
|
||||
func NewDatabase(db_type, schema, host, port, user, password string) Database {
|
||||
|
@@ -37,6 +37,13 @@ func (self ArticleHeaders) Add(key, val string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (self ArticleHeaders) Len() (l int) {
|
||||
for k := range self {
|
||||
l += len(self[k])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self ArticleHeaders) Get(key, fallback string) string {
|
||||
val, ok := self[key]
|
||||
if ok {
|
||||
|
@@ -1108,6 +1108,50 @@ func (self *nntpConnection) handleLine(daemon *NNTPDaemon, code int, line string
|
||||
} else {
|
||||
conn.PrintfLine("500 invalid daemon state, got STAT with group set but we don't have that group now?")
|
||||
}
|
||||
} else if cmd == "XPAT" {
|
||||
var hdr string
|
||||
var msgid string
|
||||
var lo, hi int64
|
||||
var pats []string
|
||||
if len(parts) >= 3 {
|
||||
hdr = parts[1]
|
||||
if ValidMessageID(parts[2]) {
|
||||
msgid = parts[2]
|
||||
} else {
|
||||
lo, hi = parseRange(parts[2])
|
||||
if !ValidNewsgroup(self.group) {
|
||||
conn.PrintfLine("430 no such article")
|
||||
return
|
||||
}
|
||||
}
|
||||
pats = parts[3:]
|
||||
var hdrs ArticleHeaders
|
||||
|
||||
if len(msgid) > 0 {
|
||||
hdrs, err = daemon.database.GetHeadersForMessage(msgid)
|
||||
} else {
|
||||
hdrs, err = daemon.database.FindHeaders(self.group, hdr, lo, hi)
|
||||
}
|
||||
if err == nil {
|
||||
hdrs = headerFindPats(hdr, hdrs, pats)
|
||||
if hdrs.Len() > 0 {
|
||||
conn.PrintfLine("221 Header follows")
|
||||
for _, vals := range hdrs {
|
||||
for idx := range vals {
|
||||
conn.PrintfLine("%s: %s", hdr, vals[idx])
|
||||
}
|
||||
}
|
||||
conn.PrintfLine(".")
|
||||
} else {
|
||||
conn.PrintfLine("430 no such article")
|
||||
}
|
||||
} else {
|
||||
conn.PrintfLine("502 %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
conn.PrintfLine("430 no such article")
|
||||
return
|
||||
} else if cmd == "XHDR" {
|
||||
if len(self.group) > 0 {
|
||||
var msgid string
|
||||
@@ -1606,6 +1650,8 @@ func (self *nntpConnection) runConnection(daemon *NNTPDaemon, inbound, stream, r
|
||||
log.Println(self.name, "TLS initiated", self.authenticated)
|
||||
} else {
|
||||
log.Println("STARTTLS failed:", err)
|
||||
nconn.Close()
|
||||
return
|
||||
}
|
||||
} else if cmd == "CAPABILITIES" {
|
||||
// write capabilities
|
||||
|
@@ -1953,3 +1953,21 @@ func (self *PostgresDatabase) FindCitesInText(text string) (msgids []string, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *PostgresDatabase) FindHeaders(group, headername string, lo, hi int64) (hdr ArticleHeaders, err error) {
|
||||
hdr = make(ArticleHeaders)
|
||||
q := "SELECT header_value FROM nntpheaders WHERE header_name = $1 AND header_article_message_id IN ( SELECT message_id FROM articleposts WHERE newsgroup = $2 )"
|
||||
var rows *sql.Rows
|
||||
rows, err = self.conn.Query(q, strings.ToLower(headername), group)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
var str string
|
||||
rows.Scan(&str)
|
||||
hdr.Add(headername, str)
|
||||
}
|
||||
rows.Close()
|
||||
} else if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -682,3 +682,52 @@ func msgidFrontendSign(sk []byte, msgid string) string {
|
||||
h := sha512.Sum512([]byte(msgid))
|
||||
return cryptoSignFucky(h[:], sk)
|
||||
}
|
||||
|
||||
func patMatch(v, pat string) (found bool) {
|
||||
parts := strings.Split(pat, ",")
|
||||
for _, part := range parts {
|
||||
var invert bool
|
||||
if part[0] == '!' {
|
||||
invert = true
|
||||
if len(parts) == 0 {
|
||||
return
|
||||
}
|
||||
part = part[1:]
|
||||
}
|
||||
found, _ = regexp.MatchString(v, part)
|
||||
log.Println(v, part, found)
|
||||
if invert {
|
||||
found = !found
|
||||
}
|
||||
if found {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func headerFindPats(header string, hdr ArticleHeaders, patterns []string) (found ArticleHeaders) {
|
||||
found = make(ArticleHeaders)
|
||||
if hdr.Has(header) && len(patterns) > 0 {
|
||||
for _, v := range hdr[header] {
|
||||
for _, pat := range patterns {
|
||||
if patMatch(v, pat) {
|
||||
found.Add(header, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseRange(str string) (lo, hi int64) {
|
||||
parts := strings.Split(str, "-")
|
||||
if len(parts) == 1 {
|
||||
i, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
lo, hi = i, i
|
||||
} else if len(parts) == 2 {
|
||||
lo, _ = strconv.ParseInt(parts[0], 10, 64)
|
||||
hi, _ = strconv.ParseInt(parts[1], 10, 64)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
21
contrib/backends/srndv2/src/srnd/vendor/wildmat/LICENSE
vendored
Normal file
21
contrib/backends/srndv2/src/srnd/vendor/wildmat/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Sergey Demyanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
95
contrib/backends/srndv2/src/srnd/vendor/wildmat/wildmat.go
vendored
Normal file
95
contrib/backends/srndv2/src/srnd/vendor/wildmat/wildmat.go
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package wildmat
|
||||
|
||||
// taken from https://github.com/demon-xxi/wildmatch/blob/0d1569265aadb1eb16009dd7bad941b4bd2aca8d/wildmatch.go
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsSubsetOf verifies if `w` wildcard is a subset of `s`.
|
||||
// I.e. checks if `s` is a superset of subset `w`.
|
||||
// Wildcard A is subset of B if any possible path that matches A also matches B.
|
||||
func IsSubsetOf(w string, s string) bool {
|
||||
|
||||
// shortcut for identical sets
|
||||
if s == w {
|
||||
return true
|
||||
}
|
||||
|
||||
// only empty set is a subset of an empty set
|
||||
if len(s) == 0 {
|
||||
return len(w) == 0
|
||||
}
|
||||
|
||||
// find nesting separators
|
||||
sp := strings.Index(s, ",")
|
||||
wp := strings.Index(w, ",")
|
||||
|
||||
// check if this is a nested path
|
||||
if sp >= 0 {
|
||||
|
||||
// if set is nested then tested wildcard must be nested too
|
||||
if wp < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Special case for /**/ mask that matches any number of levels
|
||||
if s[:sp] == "**" &&
|
||||
IsSubsetOf(w[wp+1:], s) ||
|
||||
IsSubsetOf(w, s[sp+1:]) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check that current level names are subsets
|
||||
// and compare rest of the path to be subset also
|
||||
return (IsSubsetOf(w[:wp], s[:sp]) &&
|
||||
IsSubsetOf(w[wp+1:], s[sp+1:]))
|
||||
}
|
||||
|
||||
// subset can't have more levels than set
|
||||
if wp >= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// we are comparing names on the same nesting level here
|
||||
// so let's do symbol by symbol comparison
|
||||
switch s[0] {
|
||||
case '?':
|
||||
// ? matches non empty character. '*' can't be a subset of '?'
|
||||
if len(w) == 0 || w[0] == '*' {
|
||||
return false
|
||||
}
|
||||
// any onther symbol matches '?', so let's skip to next
|
||||
return IsSubsetOf(w[1:], s[1:])
|
||||
case '*':
|
||||
// '*' matches 0 and any other number of symbols
|
||||
// so checking 0 and recursively subset without first letter
|
||||
return IsSubsetOf(w, s[1:]) ||
|
||||
(len(w) > 0 && IsSubsetOf(w[1:], s))
|
||||
default:
|
||||
// making sure next symbol in w exists and it's the same as in set
|
||||
if len(w) == 0 || w[0] != s[0] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// recursively check rest of the set and w
|
||||
return IsSubsetOf(w[1:], s[1:])
|
||||
}
|
||||
|
||||
// IsSubsetOfAny verifies if current wildcard `w` is a subset of any of the given sets.
|
||||
// Wildcard A is subset of B if any possible path that matches A also matches B.
|
||||
// If multiple subsets match then the smallest or first lexicographical set is returned
|
||||
// Return -1 if not found or superset index.
|
||||
func IsSubsetOfAny(w string, sets ...string) (found int) {
|
||||
found = -1 // not found by default
|
||||
for i, superset := range sets {
|
||||
if !IsSubsetOf(w, superset) {
|
||||
continue
|
||||
}
|
||||
if found < 0 || IsSubsetOf(superset, sets[found]) {
|
||||
found = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user