mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 06:56:36 +08:00
后端与全仓代码质量清理(golangci 扩展集 · 测试质量 · 并发安全 · 文档同步)
代码质量全量清理,零行为变化:golangci 扩展集 13 类 linter(gosec/modernize/perfsprint/canonicalheader/usestdlibvars/wastedassign/intrange/errorlint/forcetypeassert/recvcheck/exhaustive/unparam)全量修复,测试代码质量(testifylint/thelper/usetesting)25→0,frpc 进程生命周期真 bug(进程组击杀)、全仓 go test -race 6 类数据竞争(含 1 个生产竞争)、SPDX license 头补齐 131 文件、前端测试套件 next-intl 迁移后 44 失败→全绿、过期 swagger 文档重新生成、pnpm-workspace 构建审批。 Experiments: #2-#17, #18, #20, #21, #23 Metric: total_issues 108 → 8 (-92.6%)
This commit is contained in:
Vendored
+28
-7
@@ -123,7 +123,10 @@ func (c *Cache) Set(key string, value []byte, ttl time.Duration) error {
|
||||
|
||||
// Update memory tracker
|
||||
if elem, ok := c.items[key]; ok {
|
||||
item := elem.Value.(*cacheItem)
|
||||
item, ok := elem.Value.(*cacheItem)
|
||||
if !ok {
|
||||
return fmt.Errorf("cache: evict list entry for %q has invalid type %T", key, elem.Value)
|
||||
}
|
||||
c.currentSize += size - item.size
|
||||
item.size = size
|
||||
item.expiredAt = expiredAt
|
||||
@@ -154,7 +157,11 @@ func (c *Cache) Get(key string) ([]byte, error) {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
|
||||
item := elem.Value.(*cacheItem)
|
||||
item, ok := elem.Value.(*cacheItem)
|
||||
if !ok {
|
||||
c.mu.RUnlock()
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
if !item.expiredAt.IsZero() && time.Now().After(item.expiredAt) {
|
||||
c.mu.RUnlock()
|
||||
return c.getAndDeleteIfExpired(key)
|
||||
@@ -204,7 +211,11 @@ func (c *Cache) getAndDeleteIfExpired(key string) ([]byte, error) {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
|
||||
item := elem.Value.(*cacheItem)
|
||||
item, ok := elem.Value.(*cacheItem)
|
||||
if !ok {
|
||||
_ = c.deleteUnlocked(key)
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
if !item.expiredAt.IsZero() && time.Now().After(item.expiredAt) {
|
||||
_ = c.deleteUnlocked(key)
|
||||
return nil, ErrCacheMiss
|
||||
@@ -234,8 +245,9 @@ func (c *Cache) Delete(key string) error {
|
||||
|
||||
func (c *Cache) deleteUnlocked(key string) error {
|
||||
if elem, ok := c.items[key]; ok {
|
||||
item := elem.Value.(*cacheItem)
|
||||
c.currentSize -= item.size
|
||||
if item, ok := elem.Value.(*cacheItem); ok {
|
||||
c.currentSize -= item.size
|
||||
}
|
||||
c.evictList.Remove(elem)
|
||||
delete(c.items, key)
|
||||
}
|
||||
@@ -288,7 +300,11 @@ func (c *Cache) evict() {
|
||||
|
||||
for c.currentSize > c.maxSize && c.evictList.Len() > 0 {
|
||||
elem := c.evictList.Back()
|
||||
item := elem.Value.(*cacheItem)
|
||||
item, ok := elem.Value.(*cacheItem)
|
||||
if !ok {
|
||||
c.evictList.Remove(elem)
|
||||
continue
|
||||
}
|
||||
c.currentSize -= item.size
|
||||
c.evictList.Remove(elem)
|
||||
delete(c.items, item.key)
|
||||
@@ -380,7 +396,12 @@ func (c *Cache) cleanExpired() {
|
||||
|
||||
now := time.Now()
|
||||
for key, elem := range c.items {
|
||||
item := elem.Value.(*cacheItem)
|
||||
item, ok := elem.Value.(*cacheItem)
|
||||
if !ok {
|
||||
c.evictList.Remove(elem)
|
||||
delete(c.items, key)
|
||||
continue
|
||||
}
|
||||
if !item.expiredAt.IsZero() && now.After(item.expiredAt) {
|
||||
c.currentSize -= item.size
|
||||
c.evictList.Remove(elem)
|
||||
|
||||
Vendored
+2
-3
@@ -6,6 +6,7 @@ package ram
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"maps"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -119,9 +120,7 @@ func Set(item CacheItem) {
|
||||
currentMap, ok := managerCache.GetIfPresent(item.Type)
|
||||
newMap := make(map[string]cacheEntry)
|
||||
if ok {
|
||||
for k, v := range currentMap {
|
||||
newMap[k] = v
|
||||
}
|
||||
maps.Copy(newMap, currentMap)
|
||||
}
|
||||
|
||||
var expireAt time.Time
|
||||
|
||||
+3
-3
@@ -215,7 +215,7 @@ func VerifyChallengeSolutions(token string, solutions []int, secret []byte, expe
|
||||
}
|
||||
|
||||
tokenFnv := fnv1a(token)
|
||||
for i := 0; i < payload.Count; i++ {
|
||||
for i := range payload.Count {
|
||||
idxStr := strconv.Itoa(i + 1)
|
||||
saltSeed := fnv1aResume(tokenFnv, idxStr)
|
||||
targetSeed := fnv1aResume(saltSeed, "d")
|
||||
@@ -238,14 +238,14 @@ func VerifyChallengeSolutions(token string, solutions []int, secret []byte, expe
|
||||
func Solve(token string, count, size, difficulty int) []int {
|
||||
solutions := make([]int, count)
|
||||
tokenFnv := fnv1a(token)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
idxStr := strconv.Itoa(i + 1)
|
||||
saltSeed := fnv1aResume(tokenFnv, idxStr)
|
||||
targetSeed := fnv1aResume(saltSeed, "d")
|
||||
salt := prngFromHash(saltSeed, size)
|
||||
target := prngFromHash(targetSeed, difficulty)
|
||||
|
||||
for nonce := 0; nonce < 1000000; nonce++ {
|
||||
for nonce := range 1000000 {
|
||||
hashInput := salt + strconv.Itoa(nonce)
|
||||
hashBytes := sha256.Sum256([]byte(hashInput))
|
||||
hashHex := hex.EncodeToString(hashBytes[:])
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import (
|
||||
//nolint:mnd // FNV-1a 算法位移常量
|
||||
func fnv1a(str string) uint32 {
|
||||
var hash uint32 = 2166136261
|
||||
for i := 0; i < len(str); i++ {
|
||||
for i := range len(str) {
|
||||
hash ^= uint32(str[i])
|
||||
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func fnv1a(str string) uint32 {
|
||||
//nolint:mnd // FNV-1a 算法位移常量
|
||||
func fnv1aResume(state uint32, str string) uint32 {
|
||||
h := state
|
||||
for i := 0; i < len(str); i++ {
|
||||
for i := range len(str) {
|
||||
h ^= uint32(str[i])
|
||||
h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)
|
||||
}
|
||||
|
||||
+13
-11
@@ -5,6 +5,7 @@ package cap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -51,20 +52,21 @@ func NewMemoryStore(cleanupInterval time.Duration) *MemoryStore {
|
||||
func (s *MemoryStore) Get(_ context.Context, key string) (string, bool, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.getLocked(key)
|
||||
val, ok := s.getLocked(key)
|
||||
return val, ok, nil
|
||||
}
|
||||
|
||||
// getLocked is the internal helper – caller must hold s.mu.
|
||||
func (s *MemoryStore) getLocked(key string) (string, bool, error) {
|
||||
func (s *MemoryStore) getLocked(key string) (string, bool) {
|
||||
item, found := s.items[key]
|
||||
if !found {
|
||||
return "", false, nil
|
||||
return "", false
|
||||
}
|
||||
if time.Now().After(item.expiresAt) {
|
||||
delete(s.items, key)
|
||||
return "", false, nil
|
||||
return "", false
|
||||
}
|
||||
return item.value, true, nil
|
||||
return item.value, true
|
||||
}
|
||||
|
||||
// Set 向 MemoryStore 写入指定 key 的值
|
||||
@@ -92,7 +94,7 @@ func (s *MemoryStore) SetNX(_ context.Context, key string, val string, ttl time.
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
_, exists, _ := s.getLocked(key)
|
||||
_, exists := s.getLocked(key)
|
||||
if exists {
|
||||
return false, nil
|
||||
}
|
||||
@@ -108,9 +110,9 @@ func (s *MemoryStore) GetAndDelete(_ context.Context, key string) (string, bool,
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
val, exists, err := s.getLocked(key)
|
||||
if err != nil || !exists {
|
||||
return "", false, err
|
||||
val, exists := s.getLocked(key)
|
||||
if !exists {
|
||||
return "", false, nil
|
||||
}
|
||||
delete(s.items, key)
|
||||
return val, true, nil
|
||||
@@ -149,7 +151,7 @@ func NewRedisStore(client redis.UniversalClient) *RedisStore {
|
||||
// Get 从 RedisStore 获取指定 key 的值
|
||||
func (s *RedisStore) Get(ctx context.Context, key string) (string, bool, error) {
|
||||
val, err := s.client.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return "", false, nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -176,7 +178,7 @@ func (s *RedisStore) SetNX(ctx context.Context, key string, val string, ttl time
|
||||
// GetAndDelete wraps Redis GETDEL (available since Redis 6.2).
|
||||
func (s *RedisStore) GetAndDelete(ctx context.Context, key string) (string, bool, error) {
|
||||
val, err := s.client.GetDel(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return "", false, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import "strings"
|
||||
@@ -194,7 +197,7 @@ func CountryCentroidByName(name string) (lat float64, lon float64, ok bool) {
|
||||
return v.lat, v.lon, true
|
||||
}
|
||||
// Try comma-separated parts (city / region / country / ISO).
|
||||
for _, part := range strings.Split(name, ",") {
|
||||
for part := range strings.SplitSeq(name, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import "testing"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
@@ -20,12 +23,12 @@ func (e *EmptyProvider) Initialize() error {
|
||||
|
||||
// GetGeoInfo reports that no GeoIP provider has been configured.
|
||||
func (e *EmptyProvider) GetGeoInfo(_ net.IP) (*GeoInfo, error) {
|
||||
return nil, fmt.Errorf("you are using an empty GeoIP provider, please set a valid provider")
|
||||
return nil, errors.New("you are using an empty GeoIP provider, please set a valid provider")
|
||||
}
|
||||
|
||||
// UpdateDatabase reports that no GeoIP provider has been configured.
|
||||
func (e *EmptyProvider) UpdateDatabase() error {
|
||||
return fmt.Errorf("you are using an empty GeoIP provider, please set a valid provider")
|
||||
return errors.New("you are using an empty GeoIP provider, please set a valid provider")
|
||||
}
|
||||
|
||||
// Close releases resources held by the empty provider.
|
||||
|
||||
+6
-2
@@ -1,7 +1,11 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package geoip resolves geographic information for IP addresses.
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
@@ -138,7 +142,7 @@ func InitGeoIP(provider string) {
|
||||
// GetGeoInfo looks up geographic information for ip using the active provider.
|
||||
func GetGeoInfo(ip net.IP) (*GeoInfo, error) {
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("IP address cannot be nil")
|
||||
return nil, errors.New("IP address cannot be nil")
|
||||
}
|
||||
provider := getProvider()
|
||||
cacheKey := provider.Name() + ":" + ip.String()
|
||||
@@ -157,7 +161,7 @@ func GetGeoInfo(ip net.IP) (*GeoInfo, error) {
|
||||
// LookupGeoInfoWithProvider looks up geographic information using a temporary provider.
|
||||
func LookupGeoInfoWithProvider(providerName string, ip net.IP) (*GeoInfo, error) {
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("IP address cannot be nil")
|
||||
return nil, errors.New("IP address cannot be nil")
|
||||
}
|
||||
|
||||
provider, err := providerFactory(normalizeProvider(providerName))
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package iputil provides helpers for parsing, normalizing, and scoring IP addresses.
|
||||
package iputil
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iputil
|
||||
|
||||
import (
|
||||
|
||||
+6
-2
@@ -1,7 +1,11 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -96,10 +100,10 @@ func (s *MaxMindGeoIPService) GetGeoInfo(ip net.IP) (*GeoInfo, error) {
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
if s.maxMindDBReader == nil {
|
||||
return nil, fmt.Errorf("MaxMind database is not initialized or failed to open")
|
||||
return nil, errors.New("MaxMind database is not initialized or failed to open")
|
||||
}
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("IP address cannot be nil")
|
||||
return nil, errors.New("IP address cannot be nil")
|
||||
}
|
||||
|
||||
var record Record
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package geoip
|
||||
|
||||
import (
|
||||
|
||||
@@ -78,25 +78,25 @@ func Init(cfg Config) {
|
||||
}
|
||||
|
||||
// DebugF 输出 Debug 级别日志
|
||||
func DebugF(ctx context.Context, format string, args ...interface{}) {
|
||||
func DebugF(ctx context.Context, format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
logger.Ctx(ctx).Debug(msg, getTraceIDFields(ctx)...)
|
||||
}
|
||||
|
||||
// InfoF 输出 Info 级别日志
|
||||
func InfoF(ctx context.Context, format string, args ...interface{}) {
|
||||
func InfoF(ctx context.Context, format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
logger.Ctx(ctx).Info(msg, getTraceIDFields(ctx)...)
|
||||
}
|
||||
|
||||
// WarnF 输出 Warn 级别日志
|
||||
func WarnF(ctx context.Context, format string, args ...interface{}) {
|
||||
func WarnF(ctx context.Context, format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
logger.Ctx(ctx).Warn(msg, getTraceIDFields(ctx)...)
|
||||
}
|
||||
|
||||
// ErrorF 输出 Error 级别日志
|
||||
func ErrorF(ctx context.Context, format string, args ...interface{}) {
|
||||
func ErrorF(ctx context.Context, format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
logger.Ctx(ctx).Error(msg, getTraceIDFields(ctx)...)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (r *LogRingBuffer) Write(p []byte) (int, error) {
|
||||
|
||||
data := string(p)
|
||||
start := 0
|
||||
for i := 0; i < len(data); i++ {
|
||||
for i := range len(data) {
|
||||
if data[i] == '\n' {
|
||||
line := data[start:i]
|
||||
start = i + 1
|
||||
@@ -114,7 +114,7 @@ func (r *LogRingBuffer) Query(cursor int, limit int) ([]LogEntry, bool) {
|
||||
|
||||
// 将 ring buffer 中的有效条目按顺序收集
|
||||
ordered := make([]LogEntry, 0, r.count)
|
||||
for i := 0; i < r.count; i++ {
|
||||
for i := range r.count {
|
||||
pos := (oldestPos + i) % r.cap
|
||||
ordered = append(ordered, r.entries[pos])
|
||||
}
|
||||
@@ -141,10 +141,7 @@ func (r *LogRingBuffer) Query(cursor int, limit int) ([]LogEntry, bool) {
|
||||
}
|
||||
|
||||
// 返回 cut 之前的最后 limit 条
|
||||
start := cut - limit
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
start := max(cut-limit, 0)
|
||||
|
||||
hasMore := start > 0
|
||||
return ordered[start:cut], hasMore
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLogRingBuffer_WriteAndQuery(t *testing.T) {
|
||||
@@ -18,7 +19,7 @@ func TestLogRingBuffer_WriteAndQuery(t *testing.T) {
|
||||
|
||||
entries, hasMore := rb.Query(0, 10)
|
||||
assert.False(t, hasMore)
|
||||
assert.Equal(t, 3, len(entries))
|
||||
assert.Len(t, entries, 3)
|
||||
assert.Equal(t, "line1", entries[0].Data)
|
||||
assert.Equal(t, "line2", entries[1].Data)
|
||||
assert.Equal(t, "line3", entries[2].Data)
|
||||
@@ -34,7 +35,7 @@ func TestLogRingBuffer_CapacityOverflow(t *testing.T) {
|
||||
|
||||
entries, hasMore := rb.Query(0, 10)
|
||||
assert.False(t, hasMore)
|
||||
assert.Equal(t, 3, len(entries))
|
||||
assert.Len(t, entries, 3)
|
||||
assert.Equal(t, "c", entries[0].Data)
|
||||
assert.Equal(t, "d", entries[1].Data)
|
||||
assert.Equal(t, "e", entries[2].Data)
|
||||
@@ -48,7 +49,7 @@ func TestLogRingBuffer_QueryLatest(t *testing.T) {
|
||||
// Query latest 2
|
||||
entries, hasMore := rb.Query(0, 2)
|
||||
assert.True(t, hasMore)
|
||||
assert.Equal(t, 2, len(entries))
|
||||
assert.Len(t, entries, 2)
|
||||
assert.Equal(t, "d", entries[0].Data)
|
||||
assert.Equal(t, "e", entries[1].Data)
|
||||
}
|
||||
@@ -60,12 +61,12 @@ func TestLogRingBuffer_QueryByCursor(t *testing.T) {
|
||||
|
||||
// First get all to find indices
|
||||
all, _ := rb.Query(0, 10)
|
||||
assert.Equal(t, 5, len(all))
|
||||
assert.Len(t, all, 5)
|
||||
|
||||
// Query entries before index 3
|
||||
entries, hasMore := rb.Query(3, 10)
|
||||
assert.False(t, hasMore)
|
||||
assert.Equal(t, 3, len(entries))
|
||||
assert.Len(t, entries, 3)
|
||||
assert.Equal(t, "a", entries[0].Data)
|
||||
assert.Equal(t, "b", entries[1].Data)
|
||||
assert.Equal(t, "c", entries[2].Data)
|
||||
@@ -79,7 +80,7 @@ func TestLogRingBuffer_QueryByCursorWithLimit(t *testing.T) {
|
||||
// Query 2 entries before index 4
|
||||
entries, hasMore := rb.Query(4, 2)
|
||||
assert.True(t, hasMore)
|
||||
assert.Equal(t, 2, len(entries))
|
||||
assert.Len(t, entries, 2)
|
||||
assert.Equal(t, "c", entries[0].Data)
|
||||
assert.Equal(t, "d", entries[1].Data)
|
||||
}
|
||||
@@ -98,7 +99,7 @@ func TestLogRingBuffer_QueryNonExistentCursor(t *testing.T) {
|
||||
|
||||
entries, hasMore := rb.Query(999, 10)
|
||||
assert.False(t, hasMore)
|
||||
assert.Equal(t, 2, len(entries))
|
||||
assert.Len(t, entries, 2)
|
||||
assert.Equal(t, "a", entries[0].Data)
|
||||
assert.Equal(t, "b", entries[1].Data)
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func TestLogRingBuffer_WriteNoNewline(t *testing.T) {
|
||||
_, _ = rb.Write([]byte("partial"))
|
||||
|
||||
entries, _ := rb.Query(0, 10)
|
||||
assert.Equal(t, 1, len(entries))
|
||||
assert.Len(t, entries, 1)
|
||||
assert.Equal(t, "partial", entries[0].Data)
|
||||
}
|
||||
|
||||
@@ -147,7 +148,7 @@ func TestLogRingBuffer_WriteEmpty(t *testing.T) {
|
||||
|
||||
n, err := rb.Write([]byte(""))
|
||||
assert.Equal(t, 0, n)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
entries, _ := rb.Query(0, 10)
|
||||
assert.Nil(t, entries)
|
||||
@@ -160,7 +161,7 @@ func TestLogRingBuffer_QueryAfterOverflow(t *testing.T) {
|
||||
|
||||
entries, hasMore := rb.Query(0, 10)
|
||||
assert.False(t, hasMore)
|
||||
assert.Equal(t, 3, len(entries))
|
||||
assert.Len(t, entries, 3)
|
||||
assert.Equal(t, "5", entries[0].Data)
|
||||
assert.Equal(t, "6", entries[1].Data)
|
||||
assert.Equal(t, "7", entries[2].Data)
|
||||
@@ -178,14 +179,14 @@ func TestLogRingBuffer_NextCursor(t *testing.T) {
|
||||
|
||||
// Query latest 2, should return next_cursor pointing to first returned entry
|
||||
entries, _ := rb.Query(0, 2)
|
||||
assert.Equal(t, 2, len(entries))
|
||||
assert.Len(t, entries, 2)
|
||||
// entries[0].Index = 3 ("d"), entries[1].Index = 4 ("e")
|
||||
assert.Equal(t, 3, entries[0].Index)
|
||||
|
||||
// Now use that index as cursor to get older entries
|
||||
older, hasMore := rb.Query(entries[0].Index, 10)
|
||||
assert.False(t, hasMore)
|
||||
assert.Equal(t, 3, len(older))
|
||||
assert.Len(t, older, 3)
|
||||
assert.Equal(t, "a", older[0].Data)
|
||||
assert.Equal(t, "b", older[1].Data)
|
||||
assert.Equal(t, "c", older[2].Data)
|
||||
|
||||
+10
-10
@@ -55,21 +55,21 @@ func SendMailHTML(ctx context.Context, cfg Config, to string, subject, body stri
|
||||
header["MIME-Version"] = "1.0"
|
||||
header["Content-Type"] = "text/html; charset=UTF-8"
|
||||
|
||||
message := ""
|
||||
var message strings.Builder
|
||||
for k, v := range header {
|
||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||
fmt.Fprintf(&message, "%s: %s\r\n", k, v)
|
||||
}
|
||||
message += "\r\n" + body
|
||||
message.WriteString("\r\n" + body)
|
||||
|
||||
auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
|
||||
|
||||
// If using SSL port 465, we connection via TLS dial
|
||||
if cfg.Port == smtpSSLPort {
|
||||
return sendMailViaSSL(ctx, addr, auth, cfg, to, message)
|
||||
return sendMailViaSSL(ctx, addr, auth, cfg, to, message.String())
|
||||
}
|
||||
|
||||
// For standard port (587 / 25), use smtp.SendMail directly (handles STARTTLS automatically if server supports it)
|
||||
err := smtp.SendMail(addr, auth, cfg.Username, []string{to}, []byte(message))
|
||||
err := smtp.SendMail(addr, auth, cfg.Username, []string{to}, []byte(message.String()))
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSendMailFailed, err)
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func sendMailViaSSL(ctx context.Context, addr string, auth smtp.Auth, cfg Config
|
||||
// SendMailWithLog sends a test email and records a detailed SMTP connection log
|
||||
func SendMailWithLog(ctx context.Context, cfg Config, to string, subject, body string) (string, error) {
|
||||
var logBuf bytes.Buffer
|
||||
logLine := func(dir string, format string, args ...interface{}) {
|
||||
logLine := func(dir string, format string, args ...any) {
|
||||
fmt.Fprintf(&logBuf, "[%s] %s\n", dir, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
@@ -227,14 +227,14 @@ func SendMailWithLog(ctx context.Context, cfg Config, to string, subject, body s
|
||||
header["MIME-Version"] = "1.0"
|
||||
header["Content-Type"] = "text/html; charset=UTF-8"
|
||||
|
||||
message := ""
|
||||
var message strings.Builder
|
||||
for k, v := range header {
|
||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||
fmt.Fprintf(&message, "%s: %s\r\n", k, v)
|
||||
}
|
||||
message += "\r\n" + body
|
||||
message.WriteString("\r\n" + body)
|
||||
|
||||
logLine("System", "Sending message body...")
|
||||
if _, err = w.Write([]byte(message)); err != nil {
|
||||
if _, err = w.Write([]byte(message.String())); err != nil {
|
||||
_ = w.Close()
|
||||
logLine("Error", "Writing message body failed: %v", err)
|
||||
return logBuf.String(), err
|
||||
|
||||
@@ -72,14 +72,14 @@ func copyLimited(dst io.Writer, src io.Reader, maxBytes int64) (int64, error) {
|
||||
return written, err
|
||||
}
|
||||
if written > maxBytes {
|
||||
return written, fmt.Errorf("pages file size out of bounds")
|
||||
return written, errors.New("pages file size out of bounds")
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func copyAndVerifySize(dst io.Writer, src io.Reader, declaredSize uint64, maxBytes int64) (int64, error) {
|
||||
if declaredSize > uint64(math.MaxInt64) {
|
||||
return 0, fmt.Errorf("pages file size out of bounds")
|
||||
return 0, errors.New("pages file size out of bounds")
|
||||
}
|
||||
written, err := copyLimited(dst, src, maxBytes)
|
||||
if err != nil {
|
||||
|
||||
@@ -124,7 +124,7 @@ func extractEntries(entries []Entry, destDir string, opts ExtractOptions) error
|
||||
appendMeasuredFile(measured, normalizedPath, actual)
|
||||
}
|
||||
if measured.fileCount == 0 {
|
||||
return fmt.Errorf("pages package is empty")
|
||||
return errors.New("pages package is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func extractTarFamilyAt(ra io.ReaderAt, size int64, format Format, destDir strin
|
||||
return err
|
||||
}
|
||||
if firstPass.fileCount == 0 {
|
||||
return fmt.Errorf("pages package is empty")
|
||||
return errors.New("pages package is empty")
|
||||
}
|
||||
commonPrefix := ""
|
||||
if opts.StripCommonRoot {
|
||||
@@ -162,7 +162,7 @@ func extractTarFamilyAt(ra io.ReaderAt, size int64, format Format, destDir strin
|
||||
return extractErr
|
||||
}
|
||||
if secondPass.fileCount != firstPass.fileCount || secondPass.totalSize != firstPass.totalSize {
|
||||
return fmt.Errorf("pages tar package changed between validation and extraction")
|
||||
return errors.New("pages tar package changed between validation and extraction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -177,7 +177,7 @@ func extractTarReader(
|
||||
measured := &measuredArchive{files: make([]measuredFile, 0)}
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,7 @@ package pagesarchive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
@@ -87,7 +87,7 @@ func DetectFormat(fileName string, data []byte) (Format, error) {
|
||||
if format, ok := DetectFormatFromBytes(data); ok {
|
||||
return format, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported pages package format")
|
||||
return "", errors.New("unsupported pages package format")
|
||||
}
|
||||
|
||||
// Extension returns the canonical file extension for a format (without leading dot).
|
||||
|
||||
@@ -137,7 +137,7 @@ func scanTarFamilyAt(
|
||||
enforceLimits bool,
|
||||
) (*measuredArchive, error) {
|
||||
if size < 0 {
|
||||
return nil, fmt.Errorf("invalid pages package size")
|
||||
return nil, errors.New("invalid pages package size")
|
||||
}
|
||||
tarReader, closeReader, err := openTarFamilyReader(io.NewSectionReader(ra, 0, size), format)
|
||||
if err != nil {
|
||||
@@ -154,7 +154,7 @@ func scanTarReader(tarReader *tar.Reader, limits Limits, enforceLimits bool) (*m
|
||||
measured := &measuredArchive{files: make([]measuredFile, 0)}
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
@@ -183,7 +183,7 @@ func scanTarReader(tarReader *tar.Reader, limits Limits, enforceLimits bool) (*m
|
||||
|
||||
func buildMeasuredManifest(measured *measuredArchive, opts InspectOptions) (*Manifest, error) {
|
||||
if measured == nil || measured.fileCount == 0 {
|
||||
return nil, fmt.Errorf("pages package is empty")
|
||||
return nil, errors.New("pages package is empty")
|
||||
}
|
||||
targetEntryPath, err := resolveTargetEntryPath(opts.RootDir, opts.EntryFile)
|
||||
if err != nil {
|
||||
@@ -231,7 +231,7 @@ func prepareMeasuredFile(measured *measuredArchive, normalizedPath string, decla
|
||||
}
|
||||
remaining := limits.MaxTotalBytes - measured.totalSize
|
||||
if remaining < 0 || declaredSize > uint64(remaining) { //nolint:gosec // remaining is checked non-negative
|
||||
return fmt.Errorf("pages extracted size exceeds limit")
|
||||
return errors.New("pages extracted size exceeds limit")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -295,6 +295,8 @@ func isTarFamily(format Format) bool {
|
||||
switch format {
|
||||
case FormatTar, FormatTarGz, FormatTarXz, FormatTarBz2:
|
||||
return true
|
||||
case FormatZip, FormatSevenZip:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"archive/zip"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -52,13 +53,15 @@ func (z sevenZipArchiveFile) Open() (io.ReadCloser, error) {
|
||||
// Tar-family archives use the sequential streaming paths in inspect.go/extract.go.
|
||||
func listRandomAccessEntriesAt(ra io.ReaderAt, size int64, format Format) ([]Entry, error) {
|
||||
if size < 0 {
|
||||
return nil, fmt.Errorf("invalid pages package size")
|
||||
return nil, errors.New("invalid pages package size")
|
||||
}
|
||||
switch format {
|
||||
case FormatZip:
|
||||
return listZipEntriesAt(ra, size)
|
||||
case FormatSevenZip:
|
||||
return listSevenZipEntriesAt(ra, size)
|
||||
case FormatTar, FormatTarGz, FormatTarXz, FormatTarBz2:
|
||||
return nil, fmt.Errorf("unsupported random-access pages package format: %s", format)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported random-access pages package format: %s", format)
|
||||
}
|
||||
@@ -126,6 +129,8 @@ func openTarFamilyReader(r io.Reader, format Format) (*tar.Reader, func() error,
|
||||
return tar.NewReader(xzReader), func() error { return nil }, nil
|
||||
case FormatTarBz2:
|
||||
return tar.NewReader(bzip2.NewReader(r)), func() error { return nil }, nil
|
||||
case FormatZip, FormatSevenZip:
|
||||
return nil, nil, fmt.Errorf("unsupported tar family format: %s", format)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported tar family format: %s", format)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package pagesarchive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -18,7 +19,7 @@ func NormalizeLogicalPath(raw string, allowEmpty bool) (string, error) {
|
||||
if allowEmpty {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("pages path is required")
|
||||
return "", errors.New("pages path is required")
|
||||
}
|
||||
if err := validateLogicalPathText(raw); err != nil {
|
||||
return "", err
|
||||
@@ -29,7 +30,7 @@ func NormalizeLogicalPath(raw string, allowEmpty bool) (string, error) {
|
||||
if allowEmpty {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("pages path is required")
|
||||
return "", errors.New("pages path is required")
|
||||
}
|
||||
if strings.HasPrefix(cleaned, "/") || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
|
||||
return "", fmt.Errorf("pages path escapes directory: %s", raw)
|
||||
@@ -39,7 +40,7 @@ func NormalizeLogicalPath(raw string, allowEmpty bool) (string, error) {
|
||||
|
||||
func validateLogicalPathText(raw string) error {
|
||||
if !utf8.ValidString(raw) {
|
||||
return fmt.Errorf("pages path is not valid UTF-8")
|
||||
return errors.New("pages path is not valid UTF-8")
|
||||
}
|
||||
if strings.Contains(raw, "\\") {
|
||||
return fmt.Errorf("pages path must use POSIX separators: %s", raw)
|
||||
@@ -56,7 +57,7 @@ func validateLogicalPathText(raw string) error {
|
||||
func validateLogicalPathRunes(raw string) error {
|
||||
for _, r := range raw {
|
||||
if r == 0 || unicode.IsControl(r) {
|
||||
return fmt.Errorf("pages path contains a control character")
|
||||
return errors.New("pages path contains a control character")
|
||||
}
|
||||
if r == '\'' || r == '"' || r == ';' {
|
||||
return fmt.Errorf("pages path contains an unsupported character: %s", raw)
|
||||
@@ -66,7 +67,7 @@ func validateLogicalPathRunes(raw string) error {
|
||||
}
|
||||
|
||||
func validateLogicalPathSegments(raw string) error {
|
||||
for _, segment := range strings.Split(raw, "/") {
|
||||
for segment := range strings.SplitSeq(raw, "/") {
|
||||
if len(segment) >= 2 && segment[1] == ':' {
|
||||
return fmt.Errorf("pages path contains a Windows drive: %s", raw)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package protocol
|
||||
|
||||
// AgentNodeSystemProfile is an alias for NodeSystemProfile used by server.
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestCustomPusherSend_ResponseBodyErrcode(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), tt.wantErrMsg)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
if tt.body != "" {
|
||||
assert.Contains(t, upstreamResp, tt.body)
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ func (p *EmailPusher) Send(ctx context.Context, cfg Config, target string, body
|
||||
title = t
|
||||
}
|
||||
|
||||
content := ""
|
||||
var content string
|
||||
if c, ok := body["content"].(string); ok && c != "" {
|
||||
content = c
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -160,7 +160,7 @@ func (p *LarkPusher) Send(ctx context.Context, cfg Config, _ string, body map[st
|
||||
title = t
|
||||
}
|
||||
|
||||
content := ""
|
||||
var content string
|
||||
if c, ok := body["content"].(string); ok && c != "" {
|
||||
content = c
|
||||
} else {
|
||||
@@ -265,7 +265,7 @@ func (p *LarkPusher) ValidateConfig(cfg Config) error {
|
||||
}
|
||||
|
||||
func larkSign(secret string, timestamp int64) (string, error) {
|
||||
stringToSign := fmt.Sprintf("%v", timestamp) + "\n" + secret
|
||||
stringToSign := strconv.FormatInt(timestamp, 10) + "\n" + secret
|
||||
h := hmac.New(sha256.New, []byte(stringToSign))
|
||||
_, err := h.Write(nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -60,7 +60,7 @@ func (p *TelegramPusher) Send(ctx context.Context, cfg Config, target string, bo
|
||||
if t, ok := body["title"].(string); ok && t != "" {
|
||||
title = t
|
||||
}
|
||||
content := ""
|
||||
var content string
|
||||
if c, ok := body["content"].(string); ok && c != "" {
|
||||
content = c
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestTelegramPusher_Send(t *testing.T) {
|
||||
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&receivedReq)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"ok": true}`))
|
||||
@@ -55,7 +55,7 @@ func TestTelegramPusher_Send(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var req telegramMessageRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
requests = append(requests, &req)
|
||||
|
||||
if len(requests) == 1 {
|
||||
@@ -84,7 +84,7 @@ func TestTelegramPusher_Send(t *testing.T) {
|
||||
|
||||
require.Len(t, requests, 2)
|
||||
assert.Equal(t, "HTML", requests[0].ParseMode)
|
||||
assert.Equal(t, "", requests[1].ParseMode)
|
||||
assert.Empty(t, requests[1].ParseMode)
|
||||
assert.Contains(t, requests[1].Text, "[INFO] Alert & Info")
|
||||
assert.Contains(t, requests[1].Text, "A < B comparison")
|
||||
})
|
||||
@@ -96,7 +96,7 @@ func TestTelegramPusher_Send(t *testing.T) {
|
||||
URL: "https://api.telegram.org",
|
||||
}
|
||||
err := pusher.ValidateConfig(cfg)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
cfg = Config{
|
||||
Channel: "telegram",
|
||||
@@ -104,7 +104,7 @@ func TestTelegramPusher_Send(t *testing.T) {
|
||||
Secret: "token",
|
||||
}
|
||||
err = pusher.ValidateConfig(cfg)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
cfg = Config{
|
||||
Channel: "telegram",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package openresty renders OpenResty configuration from proxy route definitions.
|
||||
package openresty
|
||||
|
||||
@@ -14,6 +17,7 @@ import (
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -103,7 +107,7 @@ func RenderRouteConfig(doc Document, certificateFiles []SupportFile) (string, er
|
||||
displayName := resolveRouteSiteName(route)
|
||||
cacheConfig := routeCacheConfig{Enabled: route.CacheEnabled, Policy: route.CachePolicy, Rules: route.CacheRules}
|
||||
limitConfig := mergeRouteLimitConfig(route, doc.OpenRestyConfig)
|
||||
powEnabled, _ := getPoWConfigForRoute(route.ID, doc.WAF)
|
||||
powEnabled := getPoWConfigForRoute(route.ID, doc.WAF)
|
||||
if normalizeRouteUpstreamType(route.UpstreamType) == routeUpstreamTypePages {
|
||||
if err := renderPagesRoute(&builder, route, displayName, serverNames, certificates, limitConfig, powEnabled, doc.OpenRestyConfig); err != nil {
|
||||
return "", err
|
||||
@@ -168,32 +172,32 @@ func DedupeSupportFiles(files []SupportFile) []SupportFile {
|
||||
func renderMainConfigTemplate(templateText string, cfg ConfigSnapshot, limitReqRates []string) string {
|
||||
replacer := strings.NewReplacer(
|
||||
"{{OpenRestyWorkerProcesses}}", cfg.WorkerProcesses,
|
||||
"{{OpenRestyWorkerConnections}}", fmt.Sprintf("%d", cfg.WorkerConnections),
|
||||
"{{OpenRestyWorkerRlimitNofile}}", fmt.Sprintf("%d", cfg.WorkerRlimitNofile),
|
||||
"{{OpenRestyWorkerConnections}}", strconv.Itoa(cfg.WorkerConnections),
|
||||
"{{OpenRestyWorkerRlimitNofile}}", strconv.Itoa(cfg.WorkerRlimitNofile),
|
||||
"{{OpenRestyConnectionUpgradeMap}}", renderConnectionUpgradeMap(),
|
||||
"{{OpenRestyDefaultServerBlock}}", renderDefaultServerBlock(cfg.DefaultServerReturnStatus, cfg.HTTP3Enabled),
|
||||
"{{OpenRestyAccessLogPath}}", AccessLogPlaceholder,
|
||||
"{{OpenRestyErrorLogPath}}", ErrorLogPlaceholder,
|
||||
"{{OpenRestyEventsUseDirective}}", renderTemplateDirective(cfg.EventsUse != "", fmt.Sprintf("use %s;", cfg.EventsUse)),
|
||||
"{{OpenRestyEventsMultiAcceptDirective}}", renderTemplateDirective(cfg.EventsMultiAcceptEnabled, "multi_accept on;"),
|
||||
"{{OpenRestyKeepaliveTimeout}}", fmt.Sprintf("%d", cfg.KeepaliveTimeout),
|
||||
"{{OpenRestyKeepaliveRequests}}", fmt.Sprintf("%d", cfg.KeepaliveRequests),
|
||||
"{{OpenRestyClientHeaderTimeout}}", fmt.Sprintf("%d", cfg.ClientHeaderTimeout),
|
||||
"{{OpenRestyClientBodyTimeout}}", fmt.Sprintf("%d", cfg.ClientBodyTimeout),
|
||||
"{{OpenRestyKeepaliveTimeout}}", strconv.Itoa(cfg.KeepaliveTimeout),
|
||||
"{{OpenRestyKeepaliveRequests}}", strconv.Itoa(cfg.KeepaliveRequests),
|
||||
"{{OpenRestyClientHeaderTimeout}}", strconv.Itoa(cfg.ClientHeaderTimeout),
|
||||
"{{OpenRestyClientBodyTimeout}}", strconv.Itoa(cfg.ClientBodyTimeout),
|
||||
"{{OpenRestyClientMaxBodySize}}", cfg.ClientMaxBodySize,
|
||||
"{{OpenRestyLargeClientHeaderBuffers}}", cfg.LargeClientHeaderBuffers,
|
||||
"{{OpenRestySendTimeout}}", fmt.Sprintf("%d", cfg.SendTimeout),
|
||||
"{{OpenRestyProxyConnectTimeout}}", fmt.Sprintf("%d", cfg.ProxyConnectTimeout),
|
||||
"{{OpenRestyProxySendTimeout}}", fmt.Sprintf("%d", cfg.ProxySendTimeout),
|
||||
"{{OpenRestyProxyReadTimeout}}", fmt.Sprintf("%d", cfg.ProxyReadTimeout),
|
||||
"{{OpenRestySendTimeout}}", strconv.Itoa(cfg.SendTimeout),
|
||||
"{{OpenRestyProxyConnectTimeout}}", strconv.Itoa(cfg.ProxyConnectTimeout),
|
||||
"{{OpenRestyProxySendTimeout}}", strconv.Itoa(cfg.ProxySendTimeout),
|
||||
"{{OpenRestyProxyReadTimeout}}", strconv.Itoa(cfg.ProxyReadTimeout),
|
||||
"{{OpenRestyProxyRequestBuffering}}", onOff(cfg.ProxyRequestBuffering),
|
||||
"{{OpenRestyProxyBuffering}}", onOff(cfg.ProxyBufferingEnabled),
|
||||
"{{OpenRestyProxyBuffers}}", cfg.ProxyBuffers,
|
||||
"{{OpenRestyProxyBufferSize}}", cfg.ProxyBufferSize,
|
||||
"{{OpenRestyProxyBusyBuffersSize}}", cfg.ProxyBusyBuffersSize,
|
||||
"{{OpenRestyGzip}}", onOff(cfg.GzipEnabled),
|
||||
"{{OpenRestyGzipMinLength}}", fmt.Sprintf("%d", cfg.GzipMinLength),
|
||||
"{{OpenRestyGzipCompLevel}}", fmt.Sprintf("%d", cfg.GzipCompLevel),
|
||||
"{{OpenRestyGzipMinLength}}", strconv.Itoa(cfg.GzipMinLength),
|
||||
"{{OpenRestyGzipCompLevel}}", strconv.Itoa(cfg.GzipCompLevel),
|
||||
"{{OpenRestyResolverDirective}}", renderTemplateDirective(cfg.Resolvers != "", fmt.Sprintf("resolver %s;", cfg.Resolvers)),
|
||||
"{{OpenRestyCacheBlock}}", renderOpenRestyCacheTemplateBlock(cfg, limitReqRates),
|
||||
"{{OpenRestyRouteConfigInclude}}", RouteConfigPlaceholder,
|
||||
@@ -414,7 +418,7 @@ func pagesFallbackPath(deployment *PagesDeployment) string {
|
||||
if value == "/" || strings.HasSuffix(value, "/") || strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") || strings.ContainsAny(value, " \t\r\n") {
|
||||
return indexHTML
|
||||
}
|
||||
for _, segment := range strings.Split(value, "/") {
|
||||
for segment := range strings.SplitSeq(value, "/") {
|
||||
if segment == "." || segment == ".." {
|
||||
return indexHTML
|
||||
}
|
||||
@@ -765,7 +769,7 @@ func renderDefaultServerBlock(statusCode int, http3Enabled bool) string {
|
||||
" }",
|
||||
"",
|
||||
" server {",
|
||||
fmt.Sprintf(" listen 443 ssl default_server;%s", h3Default),
|
||||
" listen 443 ssl default_server;" + h3Default,
|
||||
" server_name _;",
|
||||
"",
|
||||
" ssl_reject_handshake on;",
|
||||
@@ -826,7 +830,7 @@ func validateCertificateCoverage(certPEM string, domains []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPoWConfigForRoute(routeID uint, snapshot WAFDocument) (bool, *PoWConfig) {
|
||||
func getPoWConfigForRoute(routeID uint, snapshot WAFDocument) bool {
|
||||
enabledGroups := make(map[uint]WAFRuleGroup, len(snapshot.RuleGroups))
|
||||
globalGroupIDs := make([]uint, 0)
|
||||
for _, group := range snapshot.RuleGroups {
|
||||
@@ -856,10 +860,10 @@ func getPoWConfigForRoute(routeID uint, snapshot WAFDocument) (bool, *PoWConfig)
|
||||
for _, groupID := range activeGroupIDs {
|
||||
group := enabledGroups[groupID]
|
||||
if graphContainsNodeType(group.Graph, "pow") {
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
func graphContainsNodeType(graph WAFRuleGraph, nodeType string) bool {
|
||||
@@ -946,7 +950,7 @@ func buildPathPrefixMatchPattern(rules []string) string {
|
||||
parts = append(parts, "/")
|
||||
continue
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%s(?:/|$)", regexp.QuoteMeta(trimmed)))
|
||||
parts = append(parts, regexp.QuoteMeta(trimmed)+"(?:/|$)")
|
||||
}
|
||||
return fmt.Sprintf("^(?:%s)", strings.Join(parts, "|"))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
@@ -108,13 +111,10 @@ func TestGetPoWConfigForRouteUsesGlobalGroupWithoutExplicitBinding(t *testing.T)
|
||||
},
|
||||
}
|
||||
|
||||
enabled, config := getPoWConfigForRoute(42, snapshot)
|
||||
enabled := getPoWConfigForRoute(42, snapshot)
|
||||
if !enabled {
|
||||
t.Fatal("expected pow to be enabled via global rule group")
|
||||
}
|
||||
if config != nil {
|
||||
t.Fatalf("expected node config to stay in runtime graph, got legacy config %#v", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderRouteConfigEnablesPoWLocationsFromRuntimeGraph(t *testing.T) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -22,14 +23,14 @@ const (
|
||||
func ParseStatusCodeTag(tag string) (lo, hi int, err error) {
|
||||
tag = strings.TrimSpace(tag)
|
||||
if tag == "" {
|
||||
return 0, 0, fmt.Errorf("状态码标签不能为空")
|
||||
return 0, 0, errors.New("状态码标签不能为空")
|
||||
}
|
||||
if i := strings.IndexByte(tag, '-'); i >= 0 {
|
||||
lo, err = strconv.Atoi(tag[:i])
|
||||
if before, after, ok := strings.Cut(tag, "-"); ok {
|
||||
lo, err = strconv.Atoi(before)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("无效状态码区间: %s", tag)
|
||||
}
|
||||
hi, err = strconv.Atoi(tag[i+1:])
|
||||
hi, err = strconv.Atoi(after)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("无效状态码区间: %s", tag)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import "testing"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openresty
|
||||
|
||||
import (
|
||||
|
||||
+7
-4
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package util provides shared formatting and string helper functions.
|
||||
package util
|
||||
|
||||
@@ -22,20 +25,20 @@ const (
|
||||
|
||||
// Bytes2Size converts a byte count to a human-readable string with unit (B, KB, MB, GB).
|
||||
func Bytes2Size(num int64) string {
|
||||
numStr := ""
|
||||
var numStr string
|
||||
unit := "B"
|
||||
switch {
|
||||
case num/int64(sizeGB) >= 1:
|
||||
numStr = fmt.Sprintf("%.2f", float64(num)/float64(sizeGB))
|
||||
unit = "GB"
|
||||
case num/int64(sizeMB) >= 1:
|
||||
numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeMB)))
|
||||
numStr = strconv.Itoa(int(float64(num) / float64(sizeMB)))
|
||||
unit = "MB"
|
||||
case num/int64(sizeKB) >= 1:
|
||||
numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeKB)))
|
||||
numStr = strconv.Itoa(int(float64(num) / float64(sizeKB)))
|
||||
unit = "KB"
|
||||
default:
|
||||
numStr = fmt.Sprintf("%d", num)
|
||||
numStr = strconv.FormatInt(num, 10)
|
||||
}
|
||||
return numStr + " " + unit
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import "strings"
|
||||
|
||||
+9
-3
@@ -1,14 +1,20 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Interface2String converts a string, int, or float64 value to its string representation.
|
||||
func Interface2String(inter interface{}) string {
|
||||
func Interface2String(inter any) string {
|
||||
switch v := inter.(type) {
|
||||
case string:
|
||||
return v
|
||||
case int:
|
||||
return fmt.Sprintf("%d", v)
|
||||
return strconv.Itoa(v)
|
||||
case float64:
|
||||
return fmt.Sprintf("%f", v)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package util
|
||||
|
||||
import "strconv"
|
||||
|
||||
func compareVersionNumbers(left, right VersionInfo) int {
|
||||
maxLen := len(left.Numbers)
|
||||
if len(right.Numbers) > maxLen {
|
||||
maxLen = len(right.Numbers)
|
||||
}
|
||||
for index := 0; index < maxLen; index++ {
|
||||
maxLen := max(len(right.Numbers), len(left.Numbers))
|
||||
for index := range maxLen {
|
||||
leftValue := 0
|
||||
rightValue := 0
|
||||
if index < len(left.Numbers) {
|
||||
@@ -37,11 +37,8 @@ func compareGitDescribeDistance(left, right VersionInfo) int {
|
||||
}
|
||||
|
||||
func compareGitDescribeTails(left, right VersionInfo) int {
|
||||
maxLen := len(left.GitDescribeTail)
|
||||
if len(right.GitDescribeTail) > maxLen {
|
||||
maxLen = len(right.GitDescribeTail)
|
||||
}
|
||||
for index := 0; index < maxLen; index++ {
|
||||
maxLen := max(len(right.GitDescribeTail), len(left.GitDescribeTail))
|
||||
for index := range maxLen {
|
||||
if index >= len(left.GitDescribeTail) {
|
||||
return -1
|
||||
}
|
||||
@@ -69,11 +66,8 @@ func comparePrereleaseIdentifiers(left, right VersionInfo) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
maxLen := len(left.Prerelease)
|
||||
if len(right.Prerelease) > maxLen {
|
||||
maxLen = len(right.Prerelease)
|
||||
}
|
||||
for index := 0; index < maxLen; index++ {
|
||||
maxLen := max(len(right.Prerelease), len(left.Prerelease))
|
||||
for index := range maxLen {
|
||||
if index >= len(left.Prerelease) {
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package wsclient provides a WebSocket client for agent/server communication.
|
||||
package wsclient
|
||||
|
||||
|
||||
Reference in New Issue
Block a user