mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-12 14:21:01 +00:00
fix(subscription): bound outbound response body (#5493)
This commit is contained in:
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -18,6 +19,24 @@ import (
|
|||||||
"github.com/mhsanaei/3x-ui/v3/internal/util/link"
|
"github.com/mhsanaei/3x-ui/v3/internal/util/link"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxOutboundSubscriptionBytes caps a single outbound subscription response.
|
||||||
|
// It is larger than the 2 MiB user-facing subscription cap because an outbound
|
||||||
|
// subscription may aggregate many upstream outbounds into one document.
|
||||||
|
const maxOutboundSubscriptionBytes int64 = 8 << 20
|
||||||
|
|
||||||
|
var errOutboundSubscriptionBodyTooLarge = errors.New("outbound subscription response body exceeds size limit")
|
||||||
|
|
||||||
|
func readBoundedOutboundSubscriptionBody(r io.Reader) ([]byte, error) {
|
||||||
|
body, err := io.ReadAll(io.LimitReader(r, maxOutboundSubscriptionBytes+1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if int64(len(body)) > maxOutboundSubscriptionBytes {
|
||||||
|
return nil, fmt.Errorf("%w (limit: %d bytes)", errOutboundSubscriptionBodyTooLarge, maxOutboundSubscriptionBytes)
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
// OutboundSubscriptionService manages remote outbound subscriptions.
|
// OutboundSubscriptionService manages remote outbound subscriptions.
|
||||||
type OutboundSubscriptionService struct {
|
type OutboundSubscriptionService struct {
|
||||||
settingService SettingService
|
settingService SettingService
|
||||||
@@ -281,7 +300,7 @@ func (s *OutboundSubscriptionService) fetchAndStore(sub *model.OutboundSubscript
|
|||||||
s.recordError(sub, err)
|
s.recordError(sub, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := readBoundedOutboundSubscriptionBody(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.recordError(sub, err)
|
s.recordError(sub, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,12 +1,38 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/util/link"
|
"github.com/mhsanaei/3x-ui/v3/internal/util/link"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestReadBoundedOutboundSubscriptionBody(t *testing.T) {
|
||||||
|
t.Run("accepts body at the limit", func(t *testing.T) {
|
||||||
|
want := bytes.Repeat([]byte("a"), int(maxOutboundSubscriptionBytes))
|
||||||
|
got, err := readBoundedOutboundSubscriptionBody(bytes.NewReader(want))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("readBoundedOutboundSubscriptionBody: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Fatalf("body mismatch: got %d bytes, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rejects body over the limit", func(t *testing.T) {
|
||||||
|
body := bytes.Repeat([]byte("b"), int(maxOutboundSubscriptionBytes)+1)
|
||||||
|
got, err := readBoundedOutboundSubscriptionBody(bytes.NewReader(body))
|
||||||
|
if !errors.Is(err, errOutboundSubscriptionBodyTooLarge) {
|
||||||
|
t.Fatalf("error = %v, want errOutboundSubscriptionBodyTooLarge", err)
|
||||||
|
}
|
||||||
|
if got != nil {
|
||||||
|
t.Fatalf("oversized body returned %d bytes, want nil", len(got))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultPrefixNumber(t *testing.T) {
|
func TestDefaultPrefixNumber(t *testing.T) {
|
||||||
mk := func(id int, prefix string) *model.OutboundSubscription {
|
mk := func(id int, prefix string) *model.OutboundSubscription {
|
||||||
return &model.OutboundSubscription{Id: id, TagPrefix: prefix}
|
return &model.OutboundSubscription{Id: id, TagPrefix: prefix}
|
||||||
|
|||||||
Reference in New Issue
Block a user