Archived
1
0
This commit is contained in:
Jeff Becker
2017-10-24 08:51:21 -04:00
parent 3b83f184ea
commit 09504ca363
13 changed files with 401 additions and 14 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View 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.

View 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
}