Move SplitEntries helper to internal package.

This commit is contained in:
Joachim Bauch 2025-12-10 16:17:11 +01:00
commit a1ec06d802
No known key found for this signature in database
GPG key ID: 77C1D22D53E15F02
16 changed files with 134 additions and 32 deletions

View file

@ -26,6 +26,8 @@ import (
"fmt"
"net"
"strings"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
)
type AllowedIps struct {
@ -83,7 +85,7 @@ func parseIPNet(s string) (*net.IPNet, error) {
func ParseAllowedIps(allowed string) (*AllowedIps, error) {
var allowedIps []*net.IPNet
for ip := range SplitEntries(allowed, ",") {
for ip := range internal.SplitEntries(allowed, ",") {
i, err := parseIPNet(ip)
if err != nil {
return nil, err

View file

@ -50,6 +50,7 @@ import (
"github.com/strukturag/nextcloud-spreed-signaling/api"
"github.com/strukturag/nextcloud-spreed-signaling/async"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
"github.com/strukturag/nextcloud-spreed-signaling/log"
)
@ -92,7 +93,7 @@ func NewBackendServer(ctx context.Context, config *goconf.ConfigFile, hub *Hub,
// TODO(jojo): Make the validity for TURN credentials configurable.
turnvalid := 24 * time.Hour
turnserverslist := slices.Collect(SplitEntries(turnservers, ","))
turnserverslist := slices.Collect(internal.SplitEntries(turnservers, ","))
if len(turnserverslist) != 0 {
if turnapikey == "" {
return nil, errors.New("need a TURN API key if TURN servers are configured")

View file

@ -103,7 +103,7 @@ func NewBackendStorageStatic(logger log.Logger, config *goconf.ConfigFile, stats
} else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" {
// Old-style configuration, only hosts are configured and are using a common secret.
allowMap := make(map[string]bool)
for u := range SplitEntries(allowedUrls, ",") {
for u := range internal.SplitEntries(allowedUrls, ",") {
if idx := strings.IndexByte(u, '/'); idx != -1 {
logger.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u)
if u = u[:idx]; u == "" {
@ -285,7 +285,7 @@ func (s *backendStorageStatic) UpsertHost(host string, backends []*Backend, seen
func getConfiguredBackendIDs(backendIds string) (ids []string) {
seen := make(map[string]bool)
for id := range SplitEntries(backendIds, ",") {
for id := range internal.SplitEntries(backendIds, ",") {
if seen[id] {
continue
}
@ -330,7 +330,7 @@ func getConfiguredHosts(logger log.Logger, backendIds string, config *goconf.Con
var urls []string
if u, _ := GetStringOptionWithEnv(config, id, "urls"); u != "" {
urls = slices.Sorted(SplitEntries(u, ","))
urls = slices.Sorted(internal.SplitEntries(u, ","))
urls = slices.Compact(urls)
} else if u, _ := GetStringOptionWithEnv(config, id, "url"); u != "" {
if u = strings.TrimSpace(u); u != "" {

View file

@ -48,6 +48,7 @@ import (
"github.com/mailru/easyjson/jwriter"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
)
var (
@ -566,7 +567,7 @@ func main() {
urls := make([]url.URL, 0)
urlstrings := make([]string, 0)
for host := range signaling.SplitEntries(*addr, ",") {
for host := range internal.SplitEntries(*addr, ",") {
u := url.URL{
Scheme: "ws",
Host: host,

View file

@ -23,10 +23,8 @@ package signaling
import (
"errors"
"iter"
"os"
"regexp"
"strings"
"github.com/dlintw/goconf"
)
@ -87,17 +85,3 @@ func GetStringOptions(config *goconf.ConfigFile, section string, ignoreErrors bo
return result, nil
}
// SplitEntries returns an iterator over all non-empty substrings of s separated
// by sep.
func SplitEntries(s string, sep string) iter.Seq[string] {
return func(yield func(entry string) bool) {
for entry := range strings.SplitSeq(s, sep) {
if entry = strings.TrimSpace(entry); entry != "" {
if !yield(entry) {
return
}
}
}
}
}

View file

@ -109,7 +109,7 @@ func (c *EtcdClient) getConfigStringWithFallback(config *goconf.ConfigFile, opti
func (c *EtcdClient) load(config *goconf.ConfigFile, ignoreErrors bool) error {
var endpoints []string
if endpointsString := c.getConfigStringWithFallback(config, "endpoints"); endpointsString != "" {
endpoints = slices.Collect(SplitEntries(endpointsString, ","))
endpoints = slices.Collect(internal.SplitEntries(endpointsString, ","))
} else if discoverySrv := c.getConfigStringWithFallback(config, "discoverysrv"); discoverySrv != "" {
discoveryService := c.getConfigStringWithFallback(config, "discoveryservice")
clients, err := srv.GetClient("etcd-client", discoverySrv, discoveryService)

View file

@ -44,6 +44,7 @@ import (
status "google.golang.org/grpc/status"
"github.com/strukturag/nextcloud-spreed-signaling/async"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
"github.com/strukturag/nextcloud-spreed-signaling/log"
)
@ -657,7 +658,7 @@ func (c *GrpcClients) loadTargetsStatic(config *goconf.ConfigFile, fromReload bo
}
targets, _ := config.GetString("grpc", "targets")
for target := range SplitEntries(targets, ",") {
for target := range internal.SplitEntries(targets, ",") {
if entries, found := clientsMap[target]; found {
clients = append(clients, entries.clients...)
if dnsDiscovery && entries.entry == nil {

View file

@ -887,7 +887,7 @@ func TestWebsocketFeatures(t *testing.T) {
assert.True(strings.HasPrefix(serverHeader, "nextcloud-spreed-signaling/"), "expected valid server header, got \"%s\"", serverHeader)
features := response.Header.Get("X-Spreed-Signaling-Features")
featuresList := make(map[string]bool)
for f := range SplitEntries(features, ",") {
for f := range internal.SplitEntries(features, ",") {
_, found := featuresList[f]
assert.False(found, "duplicate feature id \"%s\" in \"%s\"", f, features)
featuresList[f] = true

41
internal/split_entries.go Normal file
View file

@ -0,0 +1,41 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 internal
import (
"iter"
"strings"
)
// SplitEntries returns an iterator over all non-empty substrings of s separated
// by sep.
func SplitEntries(s string, sep string) iter.Seq[string] {
return func(yield func(entry string) bool) {
for entry := range strings.SplitSeq(s, sep) {
if entry = strings.TrimSpace(entry); entry != "" {
if !yield(entry) {
return
}
}
}
}
}

View file

@ -0,0 +1,68 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2025 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 internal
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitEntries(t *testing.T) {
t.Parallel()
assert := assert.New(t)
testcases := []struct {
s string
sep string
expected []string
}{
{
"a b",
" ",
[]string{"a", "b"},
},
{
"a b",
",",
[]string{"a b"},
},
{
"a b",
" ",
[]string{"a", "b"},
},
{
"a,b,",
",",
[]string{"a", "b"},
},
{
"a,,b",
",",
[]string{"a", "b"},
},
}
for idx, tc := range testcases {
assert.Equal(tc.expected, slices.Collect(SplitEntries(tc.s, tc.sep)), "failed for testcase %d: %s", idx, tc.s)
}
}

View file

@ -256,7 +256,7 @@ func (p *mcuJanusPublisher) SendMessage(ctx context.Context, message *MessageCli
}
func getFmtpValue(fmtp string, key string) (string, bool) {
for part := range SplitEntries(fmtp, ";") {
for part := range internal.SplitEntries(fmtp, ";") {
kv := strings.SplitN(part, "=", 2)
if len(kv) != 2 {
continue

View file

@ -1609,7 +1609,7 @@ func (m *mcuProxy) loadContinentsMap(config *goconf.ConfigFile) error {
}
var values []string
for v := range SplitEntries(value, ",") {
for v := range internal.SplitEntries(value, ",") {
v = strings.ToUpper(v)
if !IsValidContinent(v) {
m.logger.Printf("Ignore unknown continent %s for override %s", v, option)

View file

@ -38,6 +38,7 @@ import (
"github.com/gorilla/mux"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log"
)
@ -106,7 +107,7 @@ func main() {
writeTimeout = defaultWriteTimeout
}
for address := range signaling.SplitEntries(addr, " ") {
for address := range internal.SplitEntries(addr, " ") {
go func(address string) {
logger.Println("Listening on", address)
listener, err := net.Listen("tcp", address)

View file

@ -46,6 +46,7 @@ import (
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"github.com/strukturag/nextcloud-spreed-signaling/api"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
"github.com/strukturag/nextcloud-spreed-signaling/log"
)
@ -322,7 +323,7 @@ func TestWebsocketFeatures(t *testing.T) {
}
features := response.Header.Get("X-Spreed-Signaling-Features")
featuresList := make(map[string]bool)
for f := range signaling.SplitEntries(features, ",") {
for f := range internal.SplitEntries(features, ",") {
if _, found := featuresList[f]; found {
assert.Fail("duplicate feature", "id \"%s\" in \"%s\"", f, features)
}

View file

@ -30,6 +30,7 @@ import (
"github.com/dlintw/goconf"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
"github.com/strukturag/nextcloud-spreed-signaling/log"
)
@ -89,7 +90,7 @@ func (p *proxyConfigStatic) configure(config *goconf.ConfigFile, fromReload bool
remove := maps.Clone(p.connectionsMap)
mcuUrl, _ := GetStringOptionWithEnv(config, "mcu", "url")
for u := range SplitEntries(mcuUrl, " ") {
for u := range internal.SplitEntries(mcuUrl, " ") {
if existing, found := remove[u]; found {
// Proxy connection still exists in new configuration
delete(remove, u)

View file

@ -43,6 +43,7 @@ import (
"github.com/nats-io/nats.go"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
signalinglog "github.com/strukturag/nextcloud-spreed-signaling/log"
)
@ -342,7 +343,7 @@ func main() {
if writeTimeout <= 0 {
writeTimeout = defaultWriteTimeout
}
for address := range signaling.SplitEntries(saddr, " ") {
for address := range internal.SplitEntries(saddr, " ") {
go func(address string) {
logger.Println("Listening on", address)
listener, err := createTLSListener(address, cert, key)
@ -375,7 +376,7 @@ func main() {
writeTimeout = defaultWriteTimeout
}
for address := range signaling.SplitEntries(addr, " ") {
for address := range internal.SplitEntries(addr, " ") {
go func(address string) {
logger.Println("Listening on", address)
listener, err := createListener(address)