[feature] request blocking by http headers (#2409)

This commit is contained in:
kim 2023-12-18 14:18:25 +00:00 committed by GitHub
commit 8ebb7775a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 2561 additions and 81 deletions

View file

@ -18,21 +18,26 @@
package cache
import (
"github.com/superseriousbusiness/gotosocial/internal/cache/headerfilter"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
)
type Caches struct {
// GTS provides access to the collection of gtsmodel object caches.
// (used by the database).
// GTS provides access to the collection of
// gtsmodel object caches. (used by the database).
GTS GTSCaches
// AP provides access to the collection of ActivityPub object caches.
// (planned to be used by the typeconverter).
AP APCaches
// AllowHeaderFilters provides access to
// the allow []headerfilter.Filter cache.
AllowHeaderFilters headerfilter.Cache
// Visibility provides access to the item visibility cache.
// (used by the visibility filter).
// BlockHeaderFilters provides access to
// the block []headerfilter.Filter cache.
BlockHeaderFilters headerfilter.Cache
// Visibility provides access to the item visibility
// cache. (used by the visibility filter).
Visibility VisibilityCache
// prevent pass-by-value.
@ -45,7 +50,6 @@ func (c *Caches) Init() {
log.Infof(nil, "init: %p", c)
c.GTS.Init()
c.AP.Init()
c.Visibility.Init()
// Setup cache invalidate hooks.
@ -58,7 +62,6 @@ func (c *Caches) Start() {
log.Infof(nil, "start: %p", c)
c.GTS.Start()
c.AP.Start()
c.Visibility.Start()
}
@ -67,7 +70,6 @@ func (c *Caches) Stop() {
log.Infof(nil, "stop: %p", c)
c.GTS.Stop()
c.AP.Stop()
c.Visibility.Stop()
}

View file

@ -21,7 +21,6 @@ import (
"fmt"
"strings"
"sync/atomic"
"unsafe"
"golang.org/x/exp/slices"
)
@ -37,17 +36,17 @@ import (
// The .Clear() function can be used to invalidate the cache,
// e.g. when an entry is added / deleted from the database.
type Cache struct {
// atomically updated ptr value to the
// current domain cache radix trie.
rootptr unsafe.Pointer
rootptr atomic.Pointer[root]
}
// Matches checks whether domain matches an entry in the cache.
// If the cache is not currently loaded, then the provided load
// function is used to hydrate it.
func (c *Cache) Matches(domain string, load func() ([]string, error)) (bool, error) {
// Load the current root pointer value.
ptr := atomic.LoadPointer(&c.rootptr)
// Load the current
// root pointer value.
ptr := c.rootptr.Load()
if ptr == nil {
// Cache is not hydrated.
@ -60,35 +59,32 @@ func (c *Cache) Matches(domain string, load func() ([]string, error)) (bool, err
// Allocate new radix trie
// node to store matches.
root := new(root)
ptr = new(root)
// Add each domain to the trie.
for _, domain := range domains {
root.Add(domain)
ptr.Add(domain)
}
// Sort the trie.
root.Sort()
ptr.Sort()
// Store the new node ptr.
ptr = unsafe.Pointer(root)
atomic.StorePointer(&c.rootptr, ptr)
// Store new node ptr.
c.rootptr.Store(ptr)
}
// Look for a match in the trie node.
return (*root)(ptr).Match(domain), nil
// Look for match in trie node.
return ptr.Match(domain), nil
}
// Clear will drop the currently loaded domain list,
// triggering a reload on next call to .Matches().
func (c *Cache) Clear() {
atomic.StorePointer(&c.rootptr, nil)
}
func (c *Cache) Clear() { c.rootptr.Store(nil) }
// String returns a string representation of stored domains in cache.
func (c *Cache) String() string {
if ptr := atomic.LoadPointer(&c.rootptr); ptr != nil {
return (*root)(ptr).String()
if ptr := c.rootptr.Load(); ptr != nil {
return ptr.String()
}
return "<empty>"
}

105
internal/cache/headerfilter/filter.go vendored Normal file
View file

@ -0,0 +1,105 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package headerfilter
import (
"fmt"
"net/http"
"sync/atomic"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/headerfilter"
)
// Cache provides a means of caching headerfilter.Filters in
// memory to reduce load on an underlying storage mechanism.
type Cache struct {
// current cached header filters slice.
ptr atomic.Pointer[headerfilter.Filters]
}
// RegularMatch performs .RegularMatch() on cached headerfilter.Filters, loading using callback if necessary.
func (c *Cache) RegularMatch(h http.Header, load func() ([]*gtsmodel.HeaderFilter, error)) (string, string, error) {
// Load ptr value.
ptr := c.ptr.Load()
if ptr == nil {
// Cache is not hydrated.
// Load filters from callback.
filters, err := loadFilters(load)
if err != nil {
return "", "", err
}
// Store the new
// header filters.
ptr = &filters
c.ptr.Store(ptr)
}
// Deref and perform match.
return ptr.RegularMatch(h)
}
// InverseMatch performs .InverseMatch() on cached headerfilter.Filters, loading using callback if necessary.
func (c *Cache) InverseMatch(h http.Header, load func() ([]*gtsmodel.HeaderFilter, error)) (string, string, error) {
// Load ptr value.
ptr := c.ptr.Load()
if ptr == nil {
// Cache is not hydrated.
// Load filters from callback.
filters, err := loadFilters(load)
if err != nil {
return "", "", err
}
// Store the new
// header filters.
ptr = &filters
c.ptr.Store(ptr)
}
// Deref and perform match.
return ptr.InverseMatch(h)
}
// Clear will drop the currently loaded filters,
// triggering a reload on next call to ._Match().
func (c *Cache) Clear() { c.ptr.Store(nil) }
// loadFilters will load filters from given load callback, creating and parsing raw filters.
func loadFilters(load func() ([]*gtsmodel.HeaderFilter, error)) (headerfilter.Filters, error) {
// Load filters from callback.
hdrFilters, err := load()
if err != nil {
return nil, fmt.Errorf("error reloading cache: %w", err)
}
// Allocate new header filter slice to store expressions.
filters := make(headerfilter.Filters, 0, len(hdrFilters))
// Add all raw expression to filter slice.
for _, filter := range hdrFilters {
if err := filters.Append(filter.Header, filter.Regex); err != nil {
return nil, fmt.Errorf("error appending exprs: %w", err)
}
}
return filters, nil
}