mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
2467b9d542
A subscription's size is the product of its clients and inbounds with each one's remark and endpoints, and nothing bounded that product: data adopted from a node could make one unauthenticated request render gigabytes and run the panel out of memory. Each request now carries an 8 MiB budget that the raw, JSON and Clash outputs charge as they materialize a client's links, configs or proxies. Rendering stops once it is spent, with one warning, and anything below the budget is unchanged.
71 lines
2.2 KiB
Go
71 lines
2.2 KiB
Go
package sub
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
|
|
)
|
|
|
|
// maxRenderedBytes bounds what one subscription response renders: every client and
|
|
// inbound of the subId repeats the remark and endpoints, so the size is a product.
|
|
const maxRenderedBytes = 8 << 20
|
|
|
|
// renderBudget counts rendered bytes against maxRenderedBytes. The zero value
|
|
// is a fresh budget.
|
|
type renderBudget int
|
|
|
|
// take books n more bytes and reports whether they still fit. The caller drops
|
|
// what does not fit and stops rendering: a spent budget admits nothing else.
|
|
func (b *renderBudget) take(n int) bool {
|
|
if b.spent() {
|
|
return false
|
|
}
|
|
*b += renderBudget(n)
|
|
if b.spent() {
|
|
logger.Warningf("sub: rendering stopped at %d bytes, the remaining links are dropped", maxRenderedBytes)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *renderBudget) spent() bool {
|
|
return *b > maxRenderedBytes
|
|
}
|
|
|
|
// keepLink books a rendered raw link, and its line break, against the request's budget.
|
|
func (s *SubService) keepLink(link string) bool {
|
|
return s.rendered.take(len(link) + 1)
|
|
}
|
|
|
|
// budgetedConfig renders a client's JSON configs, keeping those the request's budget admits.
|
|
func (s *SubJsonService) budgetedConfig(subReq *SubService, inbound *model.Inbound, client model.Client, host string) []json_util.RawMessage {
|
|
if subReq.rendered.spent() {
|
|
return nil
|
|
}
|
|
configs := s.getConfig(subReq, inbound, client, host)
|
|
for i, config := range configs {
|
|
if !subReq.rendered.take(len(config)) {
|
|
return configs[:i]
|
|
}
|
|
}
|
|
return configs
|
|
}
|
|
|
|
// budgetedProxies renders a client's Clash proxies, keeping those the request's budget admits;
|
|
// the YAML is written only at the end, and its JSON form is about as long.
|
|
func (s *SubClashService) budgetedProxies(subReq *SubService, inbound *model.Inbound, client model.Client, host string) []map[string]any {
|
|
if subReq.rendered.spent() {
|
|
return nil
|
|
}
|
|
proxies := s.getProxies(subReq, inbound, client, host)
|
|
for i, proxy := range proxies {
|
|
text, _ := json.Marshal(proxy)
|
|
if !subReq.rendered.take(len(text)) {
|
|
return proxies[:i]
|
|
}
|
|
}
|
|
return proxies
|
|
}
|