diff options
| author | 2023-12-18 14:18:25 +0000 | |
|---|---|---|
| committer | 2023-12-18 14:18:25 +0000 | |
| commit | 8ebb7775a35b632d49a8f294d83ac786666631f3 (patch) | |
| tree | 02ac5475274125170132b0a4d9f69bd67491a32c /internal/cache | |
| parent | fix poll total vote double count (#2464) (diff) | |
| download | gotosocial-8ebb7775a35b632d49a8f294d83ac786666631f3.tar.xz | |
[feature] request blocking by http headers (#2409)
Diffstat (limited to 'internal/cache')
| -rw-r--r-- | internal/cache/cache.go | 22 | ||||
| -rw-r--r-- | internal/cache/domain/domain.go | 32 | ||||
| -rw-r--r-- | internal/cache/headerfilter/filter.go | 105 | 
3 files changed, 131 insertions, 28 deletions
| diff --git a/internal/cache/cache.go b/internal/cache/cache.go index dabf151ff..73e3ad6f0 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -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()  } diff --git a/internal/cache/domain/domain.go b/internal/cache/domain/domain.go index 051ec5c1b..1b836ed28 100644 --- a/internal/cache/domain/domain.go +++ b/internal/cache/domain/domain.go @@ -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>"  } diff --git a/internal/cache/headerfilter/filter.go b/internal/cache/headerfilter/filter.go new file mode 100644 index 000000000..96b6e757f --- /dev/null +++ b/internal/cache/headerfilter/filter.go @@ -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 +} | 
