service.go 77 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504
  1. package sub
  2. import (
  3. "crypto/sha256"
  4. "encoding/base64"
  5. "encoding/hex"
  6. "fmt"
  7. "maps"
  8. "net"
  9. "net/url"
  10. "slices"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/gin-gonic/gin"
  15. "github.com/goccy/go-json"
  16. "github.com/mhsanaei/3x-ui/v3/internal/database"
  17. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  18. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  19. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  20. "github.com/mhsanaei/3x-ui/v3/internal/util/random"
  21. wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  22. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  23. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  24. )
  25. // SubService provides business logic for generating subscription links and managing subscription data.
  26. type SubService struct {
  27. address string
  28. remarkTemplate string
  29. datepicker string
  30. // subscriptionBody is true only when rendering the actual subscription
  31. // content a client app imports (raw /sub fetch, /json, /clash). The remark
  32. // template's per-client info is emitted there (on the first link); every
  33. // other context — the sub info page, the panel's link/QR displays — renders
  34. // the name-only template, like Remnawave.
  35. subscriptionBody bool
  36. // usageShown tracks, per client email, whether the info part of the template
  37. // has already been emitted this request, so it appears on the first body
  38. // link only. Per-request state; reset in PrepareForRequest.
  39. usageShown map[string]bool
  40. inboundService service.InboundService
  41. settingService service.SettingService
  42. // nodesByID is populated per request from the Node table so
  43. // resolveInboundAddress can return the node's address for any
  44. // inbound whose NodeID is set. Keeps the per-link host derivation
  45. // O(1) instead of O(N) DB hits.
  46. nodesByID map[int]*model.Node
  47. // statsByEmail maps a client email to its traffic row across ALL inbounds
  48. // loaded for the request. client_traffics.email is globally unique, so this
  49. // lets statsForClient resolve usage for a client even on an inbound that
  50. // doesn't own its row (multi-inbound subscriptions). Filled in
  51. // getInboundsBySubId; reset per request in PrepareForRequest.
  52. statsByEmail map[string]xray.ClientTraffic
  53. }
  54. // NewSubService creates a new subscription service with the given configuration.
  55. func NewSubService(remarkTemplate string) *SubService {
  56. return &SubService{
  57. remarkTemplate: remarkTemplate,
  58. }
  59. }
  60. // ForRequest returns a shallow copy with request-scoped state populated.
  61. // Subscription controllers share one base SubService, so request-specific
  62. // fields such as address and nodesByID must live on a per-request copy.
  63. func (s *SubService) ForRequest(host string) *SubService {
  64. req := *s
  65. req.PrepareForRequest(host)
  66. return &req
  67. }
  68. // PrepareForRequest sets per-request state (host + nodes map) on this
  69. // SubService instance. HTTP handlers should call ForRequest instead so the
  70. // controller's shared base service is never mutated by concurrent requests.
  71. func (s *SubService) PrepareForRequest(host string) {
  72. if !isRoutableHost(host) {
  73. if d := s.configuredPublicHost(); d != "" {
  74. host = d
  75. } else if isLoopbackHost(host) {
  76. host = "localhost"
  77. }
  78. }
  79. s.address = host
  80. s.usageShown = map[string]bool{}
  81. s.statsByEmail = map[string]xray.ClientTraffic{}
  82. s.loadNodes()
  83. s.loadRemarkSettings()
  84. }
  85. // loadRemarkSettings populates the per-request remark formatting state so
  86. // every subscription format — raw, JSON, Clash — renders remarks the same way
  87. // (the date formatter reads datepicker). Loading it only in getSubs left
  88. // JSON/Clash with the zero value.
  89. func (s *SubService) loadRemarkSettings() {
  90. var err error
  91. s.datepicker, err = s.settingService.GetDatepicker()
  92. if err != nil {
  93. s.datepicker = "gregorian"
  94. }
  95. }
  96. func (s *SubService) configuredPublicHost() string {
  97. if d, err := s.settingService.GetSubDomain(); err == nil && d != "" {
  98. return d
  99. }
  100. if d, err := s.settingService.GetWebDomain(); err == nil && d != "" {
  101. return d
  102. }
  103. return ""
  104. }
  105. func isRoutableHost(host string) bool {
  106. if host == "" {
  107. return false
  108. }
  109. if ip := net.ParseIP(strings.Trim(host, "[]")); ip != nil {
  110. return !ip.IsLoopback() && !ip.IsUnspecified()
  111. }
  112. return true
  113. }
  114. func isLoopbackHost(host string) bool {
  115. ip := net.ParseIP(strings.Trim(host, "[]"))
  116. return ip != nil && ip.IsLoopback()
  117. }
  118. // listenIsInternalOnly reports whether a bind address is reachable only from
  119. // the same host — a loopback IP or a unix-domain socket. Such an inbound can't
  120. // be dialed directly by a remote client, so when it is the child side of a
  121. // fallback its share link must be projected through the master. A public or
  122. // wildcard listen (""/0.0.0.0/::) is reachable on its own port and advertises
  123. // itself.
  124. func listenIsInternalOnly(listen string) bool {
  125. if listen == "" {
  126. return false
  127. }
  128. if listen[0] == '@' || listen[0] == '/' {
  129. return true
  130. }
  131. return isLoopbackHost(listen)
  132. }
  133. // matchingClients returns the inbound's clients whose SubID equals subId,
  134. // deduplicated by email. settings.clients can accumulate duplicate entries
  135. // for the same client (multi-node sync/import drift, old DBs): SyncInbound
  136. // dedupes the normalized client_inbounds rows on write but never rewrites
  137. // the legacy JSON, and the subscription builders iterate that JSON — so
  138. // without this guard every duplicate became a duplicate profile in the
  139. // output (#5134). Link generation keys purely on (inbound, email), so
  140. // same-email entries are pure duplicates and dropping them is lossless.
  141. func (s *SubService) matchingClients(inbound *model.Inbound, subId string) []model.Client {
  142. clients, err := s.inboundService.GetClients(inbound)
  143. if err != nil {
  144. logger.Error("SubService - GetClients: Unable to get clients from inbound")
  145. return nil
  146. }
  147. var out []model.Client
  148. seen := make(map[string]struct{}, len(clients))
  149. for _, client := range clients {
  150. if client.SubID != subId {
  151. continue
  152. }
  153. key := strings.ToLower(client.Email)
  154. if _, dup := seen[key]; dup {
  155. continue
  156. }
  157. seen[key] = struct{}{}
  158. out = append(out, client)
  159. }
  160. return out
  161. }
  162. // GetSubs retrieves subscription links for a given subscription ID and host.
  163. func (s *SubService) GetSubs(subId string, host string) ([]string, []string, int64, xray.ClientTraffic, error) {
  164. return s.ForRequest(host).getSubs(subId)
  165. }
  166. func (s *SubService) getSubs(subId string) ([]string, []string, int64, xray.ClientTraffic, error) {
  167. var result []string
  168. var emails []string
  169. var traffic xray.ClientTraffic
  170. var hasEnabledClient bool
  171. inbounds, err := s.getInboundsBySubId(subId)
  172. if err != nil {
  173. return nil, nil, 0, traffic, err
  174. }
  175. externalLinks, err := s.getClientExternalLinksBySubId(subId)
  176. if err != nil {
  177. return nil, nil, 0, traffic, err
  178. }
  179. if len(inbounds) == 0 && len(externalLinks) == 0 {
  180. return nil, nil, 0, traffic, nil
  181. }
  182. seenEmails := make(map[string]struct{})
  183. for _, inbound := range inbounds {
  184. clients := s.matchingClients(inbound, subId)
  185. if len(clients) == 0 {
  186. continue
  187. }
  188. s.projectThroughFallbackMaster(inbound)
  189. // Host overrides apply AFTER fallback projection so a host's
  190. // address/TLS wins over the projected master stream.
  191. hostEps := s.hostEndpoints(inbound, "raw")
  192. for _, client := range clients {
  193. if client.Enable {
  194. hasEnabledClient = true
  195. }
  196. var link string
  197. if len(hostEps) > 0 {
  198. link = s.linkFromHosts(inbound, client, hostEps)
  199. } else {
  200. link = s.GetLink(inbound, client.Email)
  201. }
  202. result = append(result, link)
  203. emails = append(emails, client.Email)
  204. seenEmails[client.Email] = struct{}{}
  205. }
  206. }
  207. for _, ext := range externalLinks {
  208. if ext.Enable {
  209. hasEnabledClient = true
  210. }
  211. for _, el := range expandEntry(ext) {
  212. if link := applyRemarkToLink(el.Link, el.Name); link != "" {
  213. result = append(result, link)
  214. emails = append(emails, ext.Email)
  215. seenEmails[ext.Email] = struct{}{}
  216. }
  217. }
  218. }
  219. uniqueEmails := make([]string, 0, len(seenEmails))
  220. for e := range seenEmails {
  221. uniqueEmails = append(uniqueEmails, e)
  222. }
  223. traffic, lastOnline := s.AggregateTrafficByEmails(uniqueEmails)
  224. traffic.Enable = hasEnabledClient
  225. return result, emails, lastOnline, traffic, nil
  226. }
  227. // inboundLinks builds the share links for every distinct client of one inbound
  228. // the same way getSubs does — managed Host endpoints win over the plain link so
  229. // {{HOST}} and per-host variants render — but across all clients rather than a
  230. // single subId. Dedups duplicate client JSON entries by email (#5134). Backs the
  231. // panel's "Export all inbound links" so it matches the client/QR pages.
  232. func (s *SubService) inboundLinks(inbound *model.Inbound) []string {
  233. clients, err := s.inboundService.GetClients(inbound)
  234. if err != nil {
  235. return nil
  236. }
  237. s.projectThroughFallbackMaster(inbound)
  238. hostEps := s.hostEndpoints(inbound, "raw")
  239. var out []string
  240. seen := make(map[string]struct{}, len(clients))
  241. for _, client := range clients {
  242. key := strings.ToLower(client.Email)
  243. if _, dup := seen[key]; dup {
  244. continue
  245. }
  246. seen[key] = struct{}{}
  247. var link string
  248. if len(hostEps) > 0 {
  249. link = s.linkFromHosts(inbound, client, hostEps)
  250. } else {
  251. link = s.GetLink(inbound, client.Email)
  252. }
  253. out = append(out, splitLinkLines(link)...)
  254. }
  255. return out
  256. }
  257. // AggregateTrafficByEmails resolves traffic for every email in one
  258. // query and folds the rows into a single ClientTraffic + lastOnline.
  259. // xray.ClientTraffic.Email is globally unique, so a multi-inbound
  260. // client's single row is attached to exactly one inbound — iterating
  261. // per-inbound ClientStats would miss it on the others. Used by GetSubs,
  262. // SubClashService.GetClash, and SubJsonService.GetJson to keep the
  263. // sub-info header consistent across all three formats.
  264. func (s *SubService) AggregateTrafficByEmails(emails []string) (xray.ClientTraffic, int64) {
  265. var agg xray.ClientTraffic
  266. var lastOnline int64
  267. if len(emails) == 0 {
  268. return agg, 0
  269. }
  270. db := database.GetDB()
  271. var rows []xray.ClientTraffic
  272. if err := db.
  273. Model(&xray.ClientTraffic{}).
  274. Where("email IN ?", emails).
  275. Find(&rows).Error; err != nil {
  276. logger.Warning("SubService - AggregateTrafficByEmails: load by email:", err)
  277. return agg, 0
  278. }
  279. // total/expiry are configured limits owned by the clients table, not the
  280. // runtime traffic rows. In a multi-node setup the node snapshot can reset
  281. // client_traffics.total/expiry_time to 0, so fall back to the clients
  282. // table to keep the Subscription-Userinfo header in sync with the UI (#4645).
  283. limits := make(map[string][2]int64, len(emails))
  284. var records []model.ClientRecord
  285. if err := db.Model(&model.ClientRecord{}).Where("email IN ?", emails).Find(&records).Error; err != nil {
  286. logger.Warning("SubService - AggregateTrafficByEmails: load client limits:", err)
  287. } else {
  288. for _, r := range records {
  289. limits[r.Email] = [2]int64{r.TotalGB, r.ExpiryTime}
  290. }
  291. }
  292. now := time.Now().UnixMilli()
  293. first := true
  294. for _, ct := range rows {
  295. if ct.LastOnline > lastOnline {
  296. lastOnline = ct.LastOnline
  297. }
  298. total, expiry := ct.Total, ct.ExpiryTime
  299. if lim, ok := limits[ct.Email]; ok {
  300. if total == 0 {
  301. total = lim[0]
  302. }
  303. if expiry == 0 {
  304. expiry = lim[1]
  305. }
  306. }
  307. if first {
  308. agg.Up = ct.Up
  309. agg.Down = ct.Down
  310. agg.Total = total
  311. agg.ExpiryTime = subscriptionExpiryFromClient(now, expiry)
  312. first = false
  313. continue
  314. }
  315. agg.Up += ct.Up
  316. agg.Down += ct.Down
  317. if agg.Total == 0 || total == 0 {
  318. agg.Total = 0
  319. } else {
  320. agg.Total += total
  321. }
  322. normalized := subscriptionExpiryFromClient(now, expiry)
  323. if normalized != agg.ExpiryTime {
  324. agg.ExpiryTime = 0
  325. }
  326. }
  327. return agg, lastOnline
  328. }
  329. func subscriptionExpiryFromClient(nowMs, expiryTime int64) int64 {
  330. if expiryTime > 0 {
  331. return expiryTime
  332. }
  333. if expiryTime < 0 {
  334. return nowMs + (-expiryTime)
  335. }
  336. return 0
  337. }
  338. func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
  339. db := database.GetDB()
  340. var inbounds []*model.Inbound
  341. err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
  342. SELECT DISTINCT inbounds.id
  343. FROM inbounds
  344. JOIN client_inbounds ON client_inbounds.inbound_id = inbounds.id
  345. JOIN clients ON clients.id = client_inbounds.client_id
  346. WHERE
  347. inbounds.protocol in ('vmess','vless','trojan','shadowsocks','hysteria','wireguard')
  348. AND clients.sub_id = ? AND inbounds.enable = ?
  349. )`, subId, true).Order("sub_sort_index ASC").Order("id ASC").Find(&inbounds).Error
  350. if err != nil {
  351. return nil, err
  352. }
  353. s.indexStatsByEmail(inbounds)
  354. return inbounds, nil
  355. }
  356. // indexStatsByEmail records every loaded inbound's client traffic rows keyed by
  357. // email so statsForClient can resolve a client's usage even on an inbound that
  358. // doesn't own its (globally unique) client_traffics row. See statsByEmail.
  359. func (s *SubService) indexStatsByEmail(inbounds []*model.Inbound) {
  360. if s.statsByEmail == nil {
  361. s.statsByEmail = map[string]xray.ClientTraffic{}
  362. }
  363. for _, inbound := range inbounds {
  364. for _, st := range inbound.ClientStats {
  365. s.statsByEmail[st.Email] = st
  366. }
  367. }
  368. }
  369. // projectThroughFallbackMaster mutates the inbound in place so its
  370. // Listen/Port/StreamSettings reflect the externally reachable master
  371. // when applicable. Covers both fallback mechanisms:
  372. // - panel-tracked: an inbound_fallbacks row where child_id = inbound.Id
  373. // - legacy unix-socket: inbound.Listen begins with "@" and some VLESS/
  374. // Trojan inbound's settings.fallbacks references that listen address
  375. //
  376. // Returns true when a projection happened; sub services call this before
  377. // generating links so a child VLESS-WS bound to 127.0.0.1 emits the
  378. // master's :443 + TLS state instead of its own loopback endpoint.
  379. //
  380. // Projection only applies to a child that is not directly reachable on its
  381. // own listen (loopback or a unix-domain socket). An inbound on a public or
  382. // wildcard listen is reachable on its own port, so it advertises its own
  383. // port + security even when a stale fallback rule still names it as a child —
  384. // otherwise its share link would leak the master's port and Reality/TLS
  385. // settings (#4987).
  386. func (s *SubService) projectThroughFallbackMaster(inbound *model.Inbound) bool {
  387. if inbound == nil {
  388. return false
  389. }
  390. if !listenIsInternalOnly(inbound.Listen) {
  391. return false
  392. }
  393. db := database.GetDB()
  394. var master *model.Inbound
  395. var rule model.InboundFallback
  396. if err := db.Where("child_id = ?", inbound.Id).
  397. Order("sort_order ASC, id ASC").
  398. First(&rule).Error; err == nil {
  399. var m model.Inbound
  400. if err := db.Where("id = ?", rule.MasterId).First(&m).Error; err == nil {
  401. master = &m
  402. }
  403. }
  404. if master == nil && len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
  405. var m model.Inbound
  406. if err := db.Model(model.Inbound{}).
  407. Where("JSON_TYPE(settings, '$.fallbacks') = 'array'").
  408. Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", inbound.Listen).
  409. First(&m).Error; err == nil {
  410. master = &m
  411. }
  412. }
  413. if master == nil {
  414. return false
  415. }
  416. inbound.StreamSettings = mergeStreamFromMaster(inbound.StreamSettings, master.StreamSettings)
  417. inbound.Listen = master.Listen
  418. inbound.Port = master.Port
  419. return true
  420. }
  421. // mergeStreamFromMaster copies the master's security + tlsSettings +
  422. // realitySettings + externalProxy onto the child's stream so the child's
  423. // link advertises the master's TLS / Reality state. Transport (network
  424. // + ws/grpc/etc. settings) stays the child's.
  425. func mergeStreamFromMaster(childStream, masterStream string) string {
  426. var stream map[string]any
  427. _ = json.Unmarshal([]byte(childStream), &stream)
  428. if stream == nil {
  429. stream = map[string]any{}
  430. }
  431. var mst map[string]any
  432. _ = json.Unmarshal([]byte(masterStream), &mst)
  433. if mst == nil {
  434. return childStream
  435. }
  436. stream["security"] = mst["security"]
  437. if v, ok := mst["tlsSettings"]; ok {
  438. stream["tlsSettings"] = v
  439. } else {
  440. delete(stream, "tlsSettings")
  441. }
  442. if v, ok := mst["realitySettings"]; ok {
  443. stream["realitySettings"] = v
  444. } else {
  445. delete(stream, "realitySettings")
  446. }
  447. if v, ok := mst["externalProxy"]; ok {
  448. stream["externalProxy"] = v
  449. }
  450. out, err := json.MarshalIndent(stream, "", " ")
  451. if err != nil {
  452. return childStream
  453. }
  454. return string(out)
  455. }
  456. // GetLink dispatches to the protocol-specific generator for one (inbound, client)
  457. // pair. Returns "" when the inbound's protocol doesn't produce a subscription URL
  458. // (socks, http, mixed, wireguard, dokodemo, tunnel). The returned string may
  459. // contain multiple `\n`-separated URLs when the inbound has externalProxy set.
  460. func (s *SubService) GetLink(inbound *model.Inbound, email string) string {
  461. switch inbound.Protocol {
  462. case "vmess":
  463. return s.genVmessLink(inbound, email)
  464. case "vless":
  465. return s.genVlessLink(inbound, email)
  466. case "trojan":
  467. return s.genTrojanLink(inbound, email)
  468. case "shadowsocks":
  469. return s.genShadowsocksLink(inbound, email)
  470. case "hysteria":
  471. return s.genHysteriaLink(inbound, email)
  472. case "mtproto":
  473. return s.genMtprotoLink(inbound, email)
  474. case "wireguard":
  475. return s.genWireguardLink(inbound, email)
  476. }
  477. return ""
  478. }
  479. // genWireguardLink builds a per-client wireguard:// share link mirroring the
  480. // frontend genWireguardLink: the client's private key is the userinfo, the
  481. // server public key (derived from the inbound secretKey) and the client's
  482. // tunnel address ride in the query. Returns "" when the client has no key.
  483. func (s *SubService) genWireguardLink(inbound *model.Inbound, email string) string {
  484. if inbound.Protocol != model.WireGuard {
  485. return ""
  486. }
  487. settings := map[string]any{}
  488. _ = json.Unmarshal([]byte(inbound.Settings), &settings)
  489. secretKey, _ := settings["secretKey"].(string)
  490. clients, _ := s.inboundService.GetClients(inbound)
  491. var client *model.Client
  492. for i := range clients {
  493. if clients[i].Email == email {
  494. client = &clients[i]
  495. break
  496. }
  497. }
  498. if client == nil || client.PrivateKey == "" {
  499. return ""
  500. }
  501. link := fmt.Sprintf("wireguard://%s@%s", encodeUserinfo(client.PrivateKey), joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
  502. params := make(map[string]string)
  503. if secretKey != "" {
  504. if pub, err := wgutil.PublicKeyFromPrivate(secretKey); err == nil {
  505. params["publickey"] = pub
  506. }
  507. }
  508. if len(client.AllowedIPs) > 0 && client.AllowedIPs[0] != "" {
  509. params["address"] = client.AllowedIPs[0]
  510. }
  511. if mtu, ok := settings["mtu"].(float64); ok && mtu > 0 {
  512. params["mtu"] = strconv.Itoa(int(mtu))
  513. }
  514. if dns, ok := settings["dns"].(string); ok && dns != "" {
  515. params["dns"] = dns
  516. }
  517. if client.PreSharedKey != "" {
  518. params["presharedkey"] = client.PreSharedKey
  519. }
  520. if client.KeepAlive > 0 {
  521. params["keepalive"] = strconv.Itoa(client.KeepAlive)
  522. }
  523. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", ""))
  524. }
  525. // genMtprotoLink builds a Telegram proxy deep link for an mtproto inbound:
  526. func (s *SubService) genMtprotoLink(inbound *model.Inbound, _ string) string {
  527. if inbound.Protocol != model.MTProto {
  528. return ""
  529. }
  530. settings := map[string]any{}
  531. _ = json.Unmarshal([]byte(inbound.Settings), &settings)
  532. secret, _ := settings["secret"].(string)
  533. if secret == "" {
  534. if healed, ok := model.HealMtprotoSecret(inbound.Settings); ok {
  535. _ = json.Unmarshal([]byte(healed), &settings)
  536. secret, _ = settings["secret"].(string)
  537. }
  538. }
  539. if secret == "" {
  540. return ""
  541. }
  542. params := map[string]string{
  543. "server": s.resolveInboundAddress(inbound),
  544. "port": fmt.Sprintf("%d", inbound.Port),
  545. "secret": secret,
  546. }
  547. return buildLinkWithParams("tg://proxy", params, "")
  548. }
  549. // Protocol link generators are intentionally ordered as:
  550. // vmess -> vless -> trojan -> shadowsocks -> hysteria.
  551. func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
  552. if inbound.Protocol != model.VMESS {
  553. return ""
  554. }
  555. address := s.resolveInboundAddress(inbound)
  556. obj := map[string]any{
  557. "v": "2",
  558. "add": address,
  559. "port": inbound.Port,
  560. "type": "none",
  561. }
  562. stream := unmarshalStreamSettings(inbound.StreamSettings)
  563. network, _ := stream["network"].(string)
  564. applyVmessNetworkParams(stream, network, obj)
  565. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  566. applyFinalMaskObj(finalmask, obj)
  567. }
  568. security, _ := stream["security"].(string)
  569. obj["tls"] = security
  570. if security == "tls" {
  571. applyVmessTLSParams(stream, obj)
  572. }
  573. clients, _ := s.inboundService.GetClients(inbound)
  574. clientIndex := findClientIndex(clients, email)
  575. obj["id"] = clients[clientIndex].ID
  576. obj["scy"] = clients[clientIndex].Security
  577. externalProxies, _ := stream["externalProxy"].([]any)
  578. if len(externalProxies) > 0 {
  579. return s.buildVmessExternalProxyLinks(externalProxies, obj, inbound, email, network)
  580. }
  581. obj["ps"] = s.genRemark(inbound, email, "", network)
  582. return buildVmessLink(obj)
  583. }
  584. // vlessEncryptionEnabled reports whether the VLESS inbound settings enable
  585. // VLESS-level encryption (vlessenc / ML-KEM). When on, the encryption/decryption
  586. // fields hold a generated dotted string (e.g. "mlkem768x25519plus.native.0rtt.<key>");
  587. // "none" or empty means off. The value is never the literal "vlessenc" — that is
  588. // the `xray vlessenc` CLI subcommand name, not a stored value.
  589. func vlessEncryptionEnabled(settings map[string]any) bool {
  590. for _, key := range []string{"encryption", "decryption"} {
  591. if v, ok := settings[key].(string); ok && v != "" && v != "none" {
  592. return true
  593. }
  594. }
  595. return false
  596. }
  597. // vlessFlowAllowed reports whether a client's XTLS Vision flow belongs in
  598. // generated links/configs. Mirrors inboundCanEnableTlsFlow in
  599. // internal/web/service: Vision runs on TCP with tls/reality (classic), and on
  600. // XHTTP whenever VLESS encryption (vlessenc / ML-KEM) is enabled — there the
  601. // VLESS-level encryption stands in for the transport TLS that Vision relies
  602. // on, regardless of the stream security layer (so XHTTP+REALITY+vlessenc
  603. // keeps its flow too).
  604. func vlessFlowAllowed(network, security string, settings map[string]any) bool {
  605. switch network {
  606. case "tcp":
  607. return security == "tls" || security == "reality"
  608. case "xhttp":
  609. return vlessEncryptionEnabled(settings)
  610. }
  611. return false
  612. }
  613. func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
  614. if inbound.Protocol != model.VLESS {
  615. return ""
  616. }
  617. address := s.resolveInboundAddress(inbound)
  618. stream := unmarshalStreamSettings(inbound.StreamSettings)
  619. clients, _ := s.inboundService.GetClients(inbound)
  620. clientIndex := findClientIndex(clients, email)
  621. uuid := clients[clientIndex].ID
  622. port := inbound.Port
  623. streamNetwork := stream["network"].(string)
  624. params := make(map[string]string)
  625. params["type"] = streamNetwork
  626. // Add encryption parameter for VLESS from inbound settings
  627. var settings map[string]any
  628. _ = json.Unmarshal([]byte(inbound.Settings), &settings)
  629. if encryption, ok := settings["encryption"].(string); ok {
  630. params["encryption"] = encryption
  631. }
  632. applyShareNetworkParams(stream, streamNetwork, params)
  633. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  634. applyFinalMaskParams(finalmask, params)
  635. }
  636. security, _ := stream["security"].(string)
  637. switch security {
  638. case "tls":
  639. applyShareTLSParams(stream, params)
  640. case "reality":
  641. applyShareRealityParams(stream, params)
  642. default:
  643. params["security"] = "none"
  644. }
  645. if len(clients[clientIndex].Flow) > 0 && vlessFlowAllowed(streamNetwork, security, settings) {
  646. params["flow"] = clients[clientIndex].Flow
  647. }
  648. externalProxies, _ := stream["externalProxy"].([]any)
  649. if len(externalProxies) > 0 {
  650. return s.buildExternalProxyURLLinks(
  651. externalProxies,
  652. params,
  653. security,
  654. func(ep map[string]any, dest string, port int) string {
  655. return fmt.Sprintf("vless://%s@%s", applyVlessRoute(uuid, hostVlessRoute(ep)), joinHostPort(dest, port))
  656. },
  657. func(ep map[string]any) string {
  658. return s.endpointRemark(inbound, email, ep, streamNetwork)
  659. },
  660. )
  661. }
  662. link := fmt.Sprintf("vless://%s@%s", uuid, joinHostPort(address, port))
  663. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
  664. }
  665. func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
  666. if inbound.Protocol != model.Trojan {
  667. return ""
  668. }
  669. address := s.resolveInboundAddress(inbound)
  670. stream := unmarshalStreamSettings(inbound.StreamSettings)
  671. clients, _ := s.inboundService.GetClients(inbound)
  672. clientIndex := findClientIndex(clients, email)
  673. password := encodeUserinfo(clients[clientIndex].Password)
  674. port := inbound.Port
  675. streamNetwork := stream["network"].(string)
  676. params := make(map[string]string)
  677. params["type"] = streamNetwork
  678. applyShareNetworkParams(stream, streamNetwork, params)
  679. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  680. applyFinalMaskParams(finalmask, params)
  681. }
  682. security, _ := stream["security"].(string)
  683. switch security {
  684. case "tls":
  685. applyShareTLSParams(stream, params)
  686. case "reality":
  687. applyShareRealityParams(stream, params)
  688. if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
  689. params["flow"] = clients[clientIndex].Flow
  690. }
  691. default:
  692. params["security"] = "none"
  693. }
  694. externalProxies, _ := stream["externalProxy"].([]any)
  695. if len(externalProxies) > 0 {
  696. return s.buildExternalProxyURLLinks(
  697. externalProxies,
  698. params,
  699. security,
  700. func(_ map[string]any, dest string, port int) string {
  701. return fmt.Sprintf("trojan://%s@%s", password, joinHostPort(dest, port))
  702. },
  703. func(ep map[string]any) string {
  704. return s.endpointRemark(inbound, email, ep, streamNetwork)
  705. },
  706. )
  707. }
  708. link := fmt.Sprintf("trojan://%s@%s", password, joinHostPort(address, port))
  709. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
  710. }
  711. // encodeUserinfo percent-encodes a userinfo (password/auth) value so it
  712. // can be safely embedded in a `scheme://<value>@host:port` URL. RFC 3986
  713. // allows `=` in userinfo as a sub-delim, but several Trojan and Hysteria
  714. // clients reject share-links where the password contains literal `/`
  715. // or `=` (notably the common base64-with-padding shape produced by the
  716. // panel). Encode them too — this matches encodeURIComponent() on the
  717. // frontend and round-trips cleanly through net/url's parser.
  718. func encodeUserinfo(s string) string {
  719. return strings.ReplaceAll(url.QueryEscape(s), "+", "%20")
  720. }
  721. // joinHostPort wraps an IPv6 host in square brackets the way RFC 3986
  722. // requires for URI authorities, while leaving IPv4 addresses and hostnames
  723. // untouched. It also strips any brackets already present on the input so
  724. // callers don't have to normalize upstream.
  725. func joinHostPort(host string, port int) string {
  726. host = strings.Trim(host, "[]")
  727. return net.JoinHostPort(host, strconv.Itoa(port))
  728. }
  729. func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
  730. if inbound.Protocol != model.Shadowsocks {
  731. return ""
  732. }
  733. address := s.resolveInboundAddress(inbound)
  734. stream := unmarshalStreamSettings(inbound.StreamSettings)
  735. clients, _ := s.inboundService.GetClients(inbound)
  736. var settings map[string]any
  737. _ = json.Unmarshal([]byte(inbound.Settings), &settings)
  738. inboundPassword := settings["password"].(string)
  739. method := settings["method"].(string)
  740. clientIndex := findClientIndex(clients, email)
  741. streamNetwork := stream["network"].(string)
  742. params := make(map[string]string)
  743. params["type"] = streamNetwork
  744. applyShareNetworkParams(stream, streamNetwork, params)
  745. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  746. applyFinalMaskParams(finalmask, params)
  747. }
  748. security, _ := stream["security"].(string)
  749. if security == "tls" {
  750. applyShareTLSParams(stream, params)
  751. }
  752. // SIP002 clients (v2rayN) ignore the xray-native type/headerType/host/path
  753. // params and only read `plugin`. Re-encode a TCP http header as obfs-local so
  754. // they build a matching tcp/http outbound (v2rayN forces request path "/").
  755. if streamNetwork == "tcp" && params["headerType"] == "http" {
  756. host := params["host"]
  757. delete(params, "type")
  758. delete(params, "headerType")
  759. delete(params, "host")
  760. delete(params, "path")
  761. params["plugin"] = "obfs-local;obfs=http;obfs-host=" + host
  762. }
  763. // SIP002 userinfo is base64(method:password). For SIP022 (2022-blake3-*) the
  764. // userinfo MUST NOT be base64-encoded; method and password are percent-encoded.
  765. var userInfo string
  766. if strings.HasPrefix(method, "2022") {
  767. userInfo = fmt.Sprintf("%s:%s:%s",
  768. url.QueryEscape(method),
  769. url.QueryEscape(inboundPassword),
  770. url.QueryEscape(clients[clientIndex].Password))
  771. } else {
  772. userInfo = base64.RawURLEncoding.EncodeToString(fmt.Appendf(nil, "%s:%s", method, clients[clientIndex].Password))
  773. }
  774. externalProxies, _ := stream["externalProxy"].([]any)
  775. if len(externalProxies) > 0 {
  776. proxyParams := cloneStringMap(params)
  777. proxyParams["security"] = security
  778. return s.buildExternalProxyURLLinks(
  779. externalProxies,
  780. proxyParams,
  781. security,
  782. func(_ map[string]any, dest string, port int) string {
  783. return fmt.Sprintf("ss://%s@%s", userInfo, joinHostPort(dest, port))
  784. },
  785. func(ep map[string]any) string {
  786. return s.endpointRemark(inbound, email, ep, streamNetwork)
  787. },
  788. )
  789. }
  790. link := fmt.Sprintf("ss://%s@%s", userInfo, joinHostPort(address, inbound.Port))
  791. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
  792. }
  793. func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
  794. if inbound.Protocol != model.Hysteria {
  795. return ""
  796. }
  797. var stream map[string]any
  798. _ = json.Unmarshal([]byte(inbound.StreamSettings), &stream)
  799. clients, _ := s.inboundService.GetClients(inbound)
  800. clientIndex := -1
  801. for i, client := range clients {
  802. if client.Email == email {
  803. clientIndex = i
  804. break
  805. }
  806. }
  807. auth := encodeUserinfo(clients[clientIndex].Auth)
  808. params := make(map[string]string)
  809. params["security"] = "tls"
  810. tlsSetting, _ := stream["tlsSettings"].(map[string]any)
  811. alpns, _ := tlsSetting["alpn"].([]any)
  812. var alpn []string
  813. for _, a := range alpns {
  814. alpn = append(alpn, a.(string))
  815. }
  816. if len(alpn) > 0 {
  817. params["alpn"] = strings.Join(alpn, ",")
  818. }
  819. if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
  820. params["sni"], _ = sniValue.(string)
  821. }
  822. tlsSettings, _ := searchKey(tlsSetting, "settings")
  823. if tlsSetting != nil {
  824. if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
  825. params["fp"], _ = fpValue.(string)
  826. }
  827. if echValue, ok := searchKey(tlsSettings, "echConfigList"); ok {
  828. if ech, _ := echValue.(string); ech != "" {
  829. params["ech"] = ech
  830. }
  831. }
  832. if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
  833. params["vcn"] = vcn
  834. }
  835. if pins, ok := pinnedSha256List(tlsSettings); ok {
  836. for i, p := range pins {
  837. pins[i] = hysteriaPinHex(p)
  838. }
  839. params["pinSHA256"] = strings.Join(pins, ",")
  840. }
  841. }
  842. // salamander obfs (Hysteria2). The panel-side link generator already
  843. // emits these; keep the subscription output in sync so a client has
  844. // the obfs password to match the server.
  845. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  846. applyFinalMaskParams(finalmask, params)
  847. if udpMasks, ok := finalmask["udp"].([]any); ok {
  848. for _, m := range udpMasks {
  849. mask, _ := m.(map[string]any)
  850. if mask == nil || mask["type"] != "salamander" {
  851. continue
  852. }
  853. settings, _ := mask["settings"].(map[string]any)
  854. if pw, ok := settings["password"].(string); ok && pw != "" {
  855. params["obfs"] = "salamander"
  856. params["obfs-password"] = pw
  857. break
  858. }
  859. }
  860. }
  861. }
  862. var settings map[string]any
  863. _ = json.Unmarshal([]byte(inbound.Settings), &settings)
  864. version, _ := settings["version"].(float64)
  865. protocol := "hysteria2"
  866. if int(version) == 1 {
  867. protocol = "hysteria"
  868. }
  869. // Fan out one link per External Proxy entry if any. Previously this
  870. // generator ignored `externalProxy` entirely, so the link kept the
  871. // server's own IP/port even when the admin configured an alternate
  872. // endpoint (e.g. a CDN hostname + port that forwards to the node).
  873. // Matches the behaviour of genVlessLink / genTrojanLink / ….
  874. externalProxies, _ := stream["externalProxy"].([]any)
  875. if len(externalProxies) > 0 {
  876. links := make([]string, 0, len(externalProxies))
  877. for _, externalProxy := range externalProxies {
  878. ep, ok := externalProxy.(map[string]any)
  879. if !ok {
  880. continue
  881. }
  882. dest, _ := ep["dest"].(string)
  883. portF, okPort := ep["port"].(float64)
  884. if dest == "" || !okPort {
  885. continue
  886. }
  887. epParams := cloneStringMap(params)
  888. applyExternalProxyHysteriaParams(ep, epParams)
  889. link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(dest, int(portF)))
  890. links = append(links, buildLinkWithParams(link, epParams, s.endpointRemark(inbound, email, ep, "quic")))
  891. }
  892. return strings.Join(links, "\n")
  893. }
  894. // No external proxy configured — use the inbound's resolved address so
  895. // node-managed inbounds get the node's host instead of the central panel's.
  896. if hopPorts := hysteriaHopPorts(stream); hopPorts != "" {
  897. params["mport"] = hopPorts
  898. }
  899. link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
  900. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", "quic"))
  901. }
  902. // hysteriaHopPorts returns the configured Hysteria2 UDP port-hopping range
  903. // (finalmask.quicParams.udpHop.ports), or "" when port hopping is off. The
  904. // range is emitted as the v2rayN-compatible `mport` query param; the URL port
  905. // field stays numeric so .NET-Uri-based importers (v2rayN) can parse the link.
  906. func hysteriaHopPorts(stream map[string]any) string {
  907. finalmask, _ := stream["finalmask"].(map[string]any)
  908. quicParams, _ := finalmask["quicParams"].(map[string]any)
  909. udpHop, _ := quicParams["udpHop"].(map[string]any)
  910. ports, _ := udpHop["ports"].(string)
  911. return strings.TrimSpace(ports)
  912. }
  913. // loadNodes refreshes nodesByID from the DB. Called once per request so
  914. // the per-inbound resolveInboundAddress lookups are pure map reads.
  915. // We filter to address != ” so a half-configured node row doesn't
  916. // accidentally produce a useless host like "https://:2053".
  917. func (s *SubService) loadNodes() {
  918. db := database.GetDB()
  919. var nodes []*model.Node
  920. if err := db.Model(&model.Node{}).Where("address != ''").Find(&nodes).Error; err != nil {
  921. logger.Warning("subscription: load nodes failed:", err)
  922. s.nodesByID = nil
  923. return
  924. }
  925. m := make(map[int]*model.Node, len(nodes))
  926. for _, n := range nodes {
  927. m[n.Id] = n
  928. }
  929. s.nodesByID = m
  930. }
  931. // resolveInboundAddress picks the host an external client should connect to,
  932. // honoring the inbound's share address strategy the same way the panel's
  933. // share/QR link builder does (#5208):
  934. // - "listen": an explicit, client-reachable bind Listen wins, backed by the
  935. // node's address for node-managed inbounds;
  936. // - "custom": the inbound's ShareAddr wins, then node, then listen;
  937. // - "node" (default, and any unknown value): the node's address for
  938. // node-managed inbounds, then a routable Listen — the pre-strategy order.
  939. //
  940. // Every chain ends at the admin's configured public host (Sub/Web domain) and
  941. // then the subscriber's request host (s.address). Preferring the configured
  942. // host over the request host for this last resort keeps a wildcard local inbound
  943. // from advertising a bogus client IP that leaked into the request Host header
  944. // behind NAT/proxy/CDN (#5425). A loopback/wildcard bind or a unix-domain-socket
  945. // listen is a server-side detail and is never advertised; External Proxy still
  946. // overrides everything upstream of this call.
  947. func (s *SubService) resolveInboundAddress(inbound *model.Inbound) string {
  948. var nodeAddr string
  949. if inbound.NodeID != nil && s.nodesByID != nil {
  950. if n, ok := s.nodesByID[*inbound.NodeID]; ok {
  951. nodeAddr = n.Address
  952. }
  953. }
  954. var listenAddr string
  955. if listen := inbound.Listen; listen != "" && listen[0] != '@' && listen[0] != '/' && isRoutableHost(listen) {
  956. listenAddr = listen
  957. }
  958. candidates := []string{nodeAddr, listenAddr}
  959. switch inbound.ShareAddrStrategy {
  960. case "listen":
  961. candidates = []string{listenAddr, nodeAddr}
  962. case "custom":
  963. candidates = []string{strings.TrimSpace(inbound.ShareAddr), nodeAddr, listenAddr}
  964. }
  965. for _, c := range candidates {
  966. if c != "" {
  967. return c
  968. }
  969. }
  970. if d := s.configuredPublicHost(); d != "" {
  971. return d
  972. }
  973. return s.address
  974. }
  975. func findClientIndex(clients []model.Client, email string) int {
  976. for i, client := range clients {
  977. if client.Email == email {
  978. return i
  979. }
  980. }
  981. return -1
  982. }
  983. func unmarshalStreamSettings(streamSettings string) map[string]any {
  984. var stream map[string]any
  985. _ = json.Unmarshal([]byte(streamSettings), &stream)
  986. return stream
  987. }
  988. func applyPathAndHostParams(settings map[string]any, params map[string]string) {
  989. params["path"] = settings["path"].(string)
  990. if host, ok := settings["host"].(string); ok && len(host) > 0 {
  991. params["host"] = host
  992. } else {
  993. headers, _ := settings["headers"].(map[string]any)
  994. params["host"] = searchHost(headers)
  995. }
  996. }
  997. func applyPathAndHostObj(settings map[string]any, obj map[string]any) {
  998. obj["path"] = settings["path"].(string)
  999. if host, ok := settings["host"].(string); ok && len(host) > 0 {
  1000. obj["host"] = host
  1001. } else {
  1002. headers, _ := settings["headers"].(map[string]any)
  1003. obj["host"] = searchHost(headers)
  1004. }
  1005. }
  1006. func applyShareNetworkParams(stream map[string]any, streamNetwork string, params map[string]string) {
  1007. switch streamNetwork {
  1008. case "tcp":
  1009. tcp, _ := stream["tcpSettings"].(map[string]any)
  1010. header, _ := tcp["header"].(map[string]any)
  1011. typeStr, _ := header["type"].(string)
  1012. if typeStr == "http" {
  1013. request := header["request"].(map[string]any)
  1014. requestPath, _ := request["path"].([]any)
  1015. params["path"] = requestPath[0].(string)
  1016. host := ""
  1017. if response, ok := header["response"].(map[string]any); ok {
  1018. if respHeaders, ok := response["headers"].(map[string]any); ok {
  1019. host = searchHost(respHeaders)
  1020. }
  1021. }
  1022. if host == "" {
  1023. headers, _ := request["headers"].(map[string]any)
  1024. host = searchHost(headers)
  1025. }
  1026. params["host"] = host
  1027. params["headerType"] = "http"
  1028. }
  1029. case "kcp":
  1030. applyKcpShareParams(stream, params)
  1031. case "ws":
  1032. ws, _ := stream["wsSettings"].(map[string]any)
  1033. applyPathAndHostParams(ws, params)
  1034. case "grpc":
  1035. grpc, _ := stream["grpcSettings"].(map[string]any)
  1036. params["serviceName"] = grpc["serviceName"].(string)
  1037. params["authority"], _ = grpc["authority"].(string)
  1038. if grpc["multiMode"].(bool) {
  1039. params["mode"] = "multi"
  1040. }
  1041. case "httpupgrade":
  1042. httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
  1043. applyPathAndHostParams(httpupgrade, params)
  1044. case "xhttp":
  1045. xhttp, _ := stream["xhttpSettings"].(map[string]any)
  1046. applyXhttpExtraParams(xhttp, params)
  1047. }
  1048. }
  1049. // applyXhttpExtraObj copies the bidirectional xhttp settings into the
  1050. // VMess base64 JSON link object. VMess supports arbitrary keys, so we
  1051. // flatten the SplitHTTPConfig "extra" fields directly onto obj.
  1052. func applyXhttpExtraObj(xhttp map[string]any, obj map[string]any) {
  1053. if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
  1054. obj["x_padding_bytes"] = xpb
  1055. }
  1056. maps.Copy(obj, buildXhttpExtra(xhttp))
  1057. }
  1058. func applyVmessNetworkParams(stream map[string]any, network string, obj map[string]any) {
  1059. obj["net"] = network
  1060. switch network {
  1061. case "tcp":
  1062. tcp, _ := stream["tcpSettings"].(map[string]any)
  1063. header, _ := tcp["header"].(map[string]any)
  1064. typeStr, _ := header["type"].(string)
  1065. obj["type"] = typeStr
  1066. if typeStr == "http" {
  1067. request := header["request"].(map[string]any)
  1068. requestPath, _ := request["path"].([]any)
  1069. obj["path"] = requestPath[0].(string)
  1070. host := ""
  1071. if response, ok := header["response"].(map[string]any); ok {
  1072. if respHeaders, ok := response["headers"].(map[string]any); ok {
  1073. host = searchHost(respHeaders)
  1074. }
  1075. }
  1076. if host == "" {
  1077. headers, _ := request["headers"].(map[string]any)
  1078. host = searchHost(headers)
  1079. }
  1080. obj["host"] = host
  1081. }
  1082. case "kcp":
  1083. applyKcpShareObj(stream, obj)
  1084. case "ws":
  1085. ws, _ := stream["wsSettings"].(map[string]any)
  1086. applyPathAndHostObj(ws, obj)
  1087. case "grpc":
  1088. grpc, _ := stream["grpcSettings"].(map[string]any)
  1089. obj["path"] = grpc["serviceName"].(string)
  1090. obj["authority"] = grpc["authority"].(string)
  1091. if grpc["multiMode"].(bool) {
  1092. obj["type"] = "multi"
  1093. }
  1094. case "httpupgrade":
  1095. httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
  1096. applyPathAndHostObj(httpupgrade, obj)
  1097. case "xhttp":
  1098. xhttp, _ := stream["xhttpSettings"].(map[string]any)
  1099. applyPathAndHostObj(xhttp, obj)
  1100. if mode, ok := xhttp["mode"].(string); ok {
  1101. obj["mode"] = mode
  1102. }
  1103. applyXhttpExtraObj(xhttp, obj)
  1104. }
  1105. }
  1106. func applyShareTLSParams(stream map[string]any, params map[string]string) {
  1107. params["security"] = "tls"
  1108. tlsSetting, _ := stream["tlsSettings"].(map[string]any)
  1109. alpns, _ := tlsSetting["alpn"].([]any)
  1110. var alpn []string
  1111. for _, a := range alpns {
  1112. alpn = append(alpn, a.(string))
  1113. }
  1114. if len(alpn) > 0 {
  1115. params["alpn"] = strings.Join(alpn, ",")
  1116. }
  1117. if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
  1118. params["sni"], _ = sniValue.(string)
  1119. }
  1120. tlsSettings, _ := searchKey(tlsSetting, "settings")
  1121. if tlsSetting != nil {
  1122. if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
  1123. params["fp"], _ = fpValue.(string)
  1124. }
  1125. if echValue, ok := searchKey(tlsSettings, "echConfigList"); ok {
  1126. if ech, _ := echValue.(string); ech != "" {
  1127. params["ech"] = ech
  1128. }
  1129. }
  1130. if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
  1131. params["vcn"] = vcn
  1132. }
  1133. if pins, ok := pinnedSha256List(tlsSettings); ok {
  1134. params["pcs"] = strings.Join(pins, ",")
  1135. }
  1136. }
  1137. }
  1138. func applyVmessTLSParams(stream map[string]any, obj map[string]any) {
  1139. tlsSetting, _ := stream["tlsSettings"].(map[string]any)
  1140. alpns, _ := tlsSetting["alpn"].([]any)
  1141. if len(alpns) > 0 {
  1142. var alpn []string
  1143. for _, a := range alpns {
  1144. alpn = append(alpn, a.(string))
  1145. }
  1146. obj["alpn"] = strings.Join(alpn, ",")
  1147. }
  1148. if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
  1149. obj["sni"], _ = sniValue.(string)
  1150. }
  1151. tlsSettings, _ := searchKey(tlsSetting, "settings")
  1152. if tlsSetting != nil {
  1153. if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
  1154. obj["fp"], _ = fpValue.(string)
  1155. }
  1156. if echValue, ok := searchKey(tlsSettings, "echConfigList"); ok {
  1157. if ech, _ := echValue.(string); ech != "" {
  1158. obj["ech"] = ech
  1159. }
  1160. }
  1161. if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
  1162. obj["vcn"] = vcn
  1163. }
  1164. if pins, ok := pinnedSha256List(tlsSettings); ok {
  1165. obj["pcs"] = strings.Join(pins, ",")
  1166. }
  1167. }
  1168. }
  1169. // verifyPeerCertByNameValue extracts tlsSettings.settings.verifyPeerCertByName
  1170. // (the v2rayN `vcn` param) as a trimmed string. Like pinnedPeerCertSha256 it is
  1171. // panel-only and flows into share links so clients verify the server
  1172. // certificate by this name — the replacement for the removed allowInsecure.
  1173. func verifyPeerCertByNameValue(tlsClientSettings any) (string, bool) {
  1174. raw, ok := searchKey(tlsClientSettings, "verifyPeerCertByName")
  1175. if !ok {
  1176. return "", false
  1177. }
  1178. s, ok := raw.(string)
  1179. if !ok {
  1180. return "", false
  1181. }
  1182. if s = strings.TrimSpace(s); s == "" {
  1183. return "", false
  1184. }
  1185. return s, true
  1186. }
  1187. // pinnedSha256List extracts tlsSettings.settings.pinnedPeerCertSha256 as a
  1188. // []string. The field is panel-only (stripped before the run-config reaches
  1189. // xray-core via internal/web/service/xray.go) but flows into share links so clients
  1190. // can pin the server's certificate hash.
  1191. func pinnedSha256List(tlsClientSettings any) ([]string, bool) {
  1192. raw, ok := searchKey(tlsClientSettings, "pinnedPeerCertSha256")
  1193. if !ok {
  1194. return nil, false
  1195. }
  1196. arr, ok := raw.([]any)
  1197. if !ok || len(arr) == 0 {
  1198. return nil, false
  1199. }
  1200. out := make([]string, 0, len(arr))
  1201. for _, v := range arr {
  1202. s, ok := v.(string)
  1203. if !ok || s == "" {
  1204. continue
  1205. }
  1206. out = append(out, s)
  1207. }
  1208. if len(out) == 0 {
  1209. return nil, false
  1210. }
  1211. return out, true
  1212. }
  1213. // hysteriaPinHex normalises a pinnedPeerCertSha256 entry into the 64-character
  1214. // lowercase hex form that Xray-core's Hysteria2 pinSHA256 parser requires.
  1215. //
  1216. // The panel stores pins in several shapes: base64 (xray-core's native TLS
  1217. // format, used by the generate button and the JSON subscription) and hex —
  1218. // either bare or colon-separated as `openssl x509 -fingerprint -sha256` emits
  1219. // it. Hysteria2 clients hex-decode pinSHA256 and crash on a base64 value, so
  1220. // each entry is coerced to bare hex here. Anything that is neither a 32-byte
  1221. // hex nor a 32-byte base64 SHA-256 is returned unchanged so unexpected data is
  1222. // not silently dropped. Mirrors decodeCertPin in internal/web/service/node.go.
  1223. func hysteriaPinHex(pin string) string {
  1224. pin = strings.TrimSpace(pin)
  1225. if h := strings.ReplaceAll(pin, ":", ""); len(h) == hex.EncodedLen(sha256.Size) {
  1226. if _, err := hex.DecodeString(h); err == nil {
  1227. return strings.ToLower(h)
  1228. }
  1229. }
  1230. for _, enc := range []*base64.Encoding{
  1231. base64.StdEncoding,
  1232. base64.RawStdEncoding,
  1233. base64.URLEncoding,
  1234. base64.RawURLEncoding,
  1235. } {
  1236. if b, err := enc.DecodeString(pin); err == nil && len(b) == sha256.Size {
  1237. return hex.EncodeToString(b)
  1238. }
  1239. }
  1240. return pin
  1241. }
  1242. func applyShareRealityParams(stream map[string]any, params map[string]string) {
  1243. params["security"] = "reality"
  1244. realitySetting, _ := stream["realitySettings"].(map[string]any)
  1245. realitySettings, _ := searchKey(realitySetting, "settings")
  1246. if realitySetting != nil {
  1247. if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
  1248. sNames, _ := sniValue.([]any)
  1249. params["sni"] = sNames[random.Num(len(sNames))].(string)
  1250. }
  1251. if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
  1252. params["pbk"], _ = pbkValue.(string)
  1253. }
  1254. if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
  1255. shortIds, _ := sidValue.([]any)
  1256. params["sid"] = shortIds[random.Num(len(shortIds))].(string)
  1257. }
  1258. if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
  1259. if fp, ok := fpValue.(string); ok && len(fp) > 0 {
  1260. params["fp"] = fp
  1261. }
  1262. }
  1263. if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
  1264. if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
  1265. params["pqv"] = pqv
  1266. }
  1267. }
  1268. params["spx"] = "/" + random.Seq(15)
  1269. if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
  1270. if spx, ok := spxValue.(string); ok && len(spx) > 0 {
  1271. params["spx"] = spx
  1272. }
  1273. }
  1274. }
  1275. }
  1276. func buildVmessLink(obj map[string]any) string {
  1277. jsonStr, _ := json.MarshalIndent(obj, "", " ")
  1278. return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
  1279. }
  1280. func cloneVmessShareObj(baseObj map[string]any, newSecurity string) map[string]any {
  1281. newObj := map[string]any{}
  1282. for key, value := range baseObj {
  1283. if newSecurity != "none" || (key != "alpn" && key != "sni" && key != "fp" && key != "pcs") {
  1284. newObj[key] = value
  1285. }
  1286. }
  1287. return newObj
  1288. }
  1289. func applyExternalProxyTLSObj(ep map[string]any, obj map[string]any, security string) {
  1290. if security != "tls" {
  1291. return
  1292. }
  1293. if sni, ok := externalProxySNI(ep); ok {
  1294. obj["sni"] = sni
  1295. }
  1296. if fp, ok := ep["fingerprint"].(string); ok && fp != "" {
  1297. obj["fp"] = fp
  1298. }
  1299. if alpn, ok := externalProxyALPN(ep["alpn"]); ok {
  1300. obj["alpn"] = alpn
  1301. }
  1302. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1303. obj["pcs"] = joinAnyStrings(pins)
  1304. }
  1305. if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
  1306. obj["vcn"] = vcn
  1307. }
  1308. if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
  1309. obj["ech"] = ech
  1310. }
  1311. }
  1312. func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, security string) {
  1313. if security != "tls" {
  1314. return
  1315. }
  1316. if sni, ok := externalProxySNI(ep); ok {
  1317. params["sni"] = sni
  1318. }
  1319. if fp, ok := ep["fingerprint"].(string); ok && fp != "" {
  1320. params["fp"] = fp
  1321. }
  1322. if alpn, ok := externalProxyALPN(ep["alpn"]); ok {
  1323. params["alpn"] = alpn
  1324. }
  1325. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1326. params["pcs"] = joinAnyStrings(pins)
  1327. }
  1328. if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
  1329. params["vcn"] = vcn
  1330. }
  1331. if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
  1332. params["ech"] = ech
  1333. }
  1334. }
  1335. // applyExternalProxyHysteriaParams overrides the cert pin for a single
  1336. // external-proxy entry on a Hysteria link. Hysteria carries the pin as a hex
  1337. // `pinSHA256` (not the `pcs` the URL-param protocols use), so each entry is
  1338. // coerced through hysteriaPinHex like the main pin. sni/fp/alpn are left as
  1339. // the inbound's own — Hysteria external proxies are typically alternate
  1340. // endpoints (port-hop / CDN) fronting the same certificate.
  1341. func applyExternalProxyHysteriaParams(ep map[string]any, params map[string]string) {
  1342. pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"])
  1343. if !ok {
  1344. return
  1345. }
  1346. hexPins := make([]string, 0, len(pins))
  1347. for _, p := range pins {
  1348. if s, ok := p.(string); ok {
  1349. hexPins = append(hexPins, hysteriaPinHex(s))
  1350. }
  1351. }
  1352. params["pinSHA256"] = strings.Join(hexPins, ",")
  1353. }
  1354. // cloneStreamForExternalProxy returns a shallow clone of stream with
  1355. // tlsSettings (and its nested settings map) deep-copied. The external
  1356. // proxy loop mutates tlsSettings per iteration, so without isolating
  1357. // those maps each proxy's SNI/fingerprint/ALPN would leak into the next.
  1358. func cloneStreamForExternalProxy(stream map[string]any) map[string]any {
  1359. out := cloneMap(stream)
  1360. ts, ok := out["tlsSettings"].(map[string]any)
  1361. if !ok || ts == nil {
  1362. return out
  1363. }
  1364. clonedTs := cloneMap(ts)
  1365. if inner, ok := clonedTs["settings"].(map[string]any); ok && inner != nil {
  1366. clonedTs["settings"] = cloneMap(inner)
  1367. }
  1368. out["tlsSettings"] = clonedTs
  1369. return out
  1370. }
  1371. func applyExternalProxyTLSToStream(ep map[string]any, stream map[string]any, security string) {
  1372. if security != "tls" {
  1373. return
  1374. }
  1375. tlsSettings, _ := stream["tlsSettings"].(map[string]any)
  1376. if tlsSettings == nil {
  1377. tlsSettings = map[string]any{}
  1378. stream["tlsSettings"] = tlsSettings
  1379. }
  1380. if sni, ok := externalProxySNI(ep); ok {
  1381. tlsSettings["serverName"] = sni
  1382. }
  1383. if fp, ok := ep["fingerprint"].(string); ok && fp != "" {
  1384. tlsSettings["fingerprint"] = fp
  1385. settings, _ := tlsSettings["settings"].(map[string]any)
  1386. if settings == nil {
  1387. settings = map[string]any{}
  1388. tlsSettings["settings"] = settings
  1389. }
  1390. settings["fingerprint"] = fp
  1391. }
  1392. if alpn, ok := externalProxyALPNList(ep["alpn"]); ok {
  1393. tlsSettings["alpn"] = alpn
  1394. }
  1395. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1396. settings, _ := tlsSettings["settings"].(map[string]any)
  1397. if settings == nil {
  1398. settings = map[string]any{}
  1399. tlsSettings["settings"] = settings
  1400. }
  1401. settings["pinnedPeerCertSha256"] = pins
  1402. }
  1403. if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
  1404. settings, _ := tlsSettings["settings"].(map[string]any)
  1405. if settings == nil {
  1406. settings = map[string]any{}
  1407. tlsSettings["settings"] = settings
  1408. }
  1409. settings["echConfigList"] = ech
  1410. }
  1411. if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
  1412. settings, _ := tlsSettings["settings"].(map[string]any)
  1413. if settings == nil {
  1414. settings = map[string]any{}
  1415. tlsSettings["settings"] = settings
  1416. }
  1417. settings["verifyPeerCertByName"] = vcn
  1418. }
  1419. if ai, ok := ep["allowInsecure"].(bool); ok && ai {
  1420. settings, _ := tlsSettings["settings"].(map[string]any)
  1421. if settings == nil {
  1422. settings = map[string]any{}
  1423. tlsSettings["settings"] = settings
  1424. }
  1425. settings["allowInsecure"] = true
  1426. }
  1427. }
  1428. func externalProxySNI(ep map[string]any) (string, bool) {
  1429. if sni, ok := ep["sni"].(string); ok && sni != "" {
  1430. return sni, true
  1431. }
  1432. return "", false
  1433. }
  1434. func externalProxyALPN(value any) (string, bool) {
  1435. switch v := value.(type) {
  1436. case string:
  1437. return v, v != ""
  1438. case []string:
  1439. if len(v) == 0 {
  1440. return "", false
  1441. }
  1442. return strings.Join(v, ","), true
  1443. case []any:
  1444. alpn := make([]string, 0, len(v))
  1445. for _, item := range v {
  1446. if s, ok := item.(string); ok && s != "" {
  1447. alpn = append(alpn, s)
  1448. }
  1449. }
  1450. if len(alpn) == 0 {
  1451. return "", false
  1452. }
  1453. return strings.Join(alpn, ","), true
  1454. default:
  1455. return "", false
  1456. }
  1457. }
  1458. func externalProxyALPNList(value any) ([]any, bool) {
  1459. switch v := value.(type) {
  1460. case string:
  1461. if v == "" {
  1462. return nil, false
  1463. }
  1464. parts := strings.Split(v, ",")
  1465. out := make([]any, 0, len(parts))
  1466. for _, part := range parts {
  1467. if part = strings.TrimSpace(part); part != "" {
  1468. out = append(out, part)
  1469. }
  1470. }
  1471. return out, len(out) > 0
  1472. case []string:
  1473. out := make([]any, 0, len(v))
  1474. for _, item := range v {
  1475. if item != "" {
  1476. out = append(out, item)
  1477. }
  1478. }
  1479. return out, len(out) > 0
  1480. case []any:
  1481. out := make([]any, 0, len(v))
  1482. for _, item := range v {
  1483. if s, ok := item.(string); ok && s != "" {
  1484. out = append(out, s)
  1485. }
  1486. }
  1487. return out, len(out) > 0
  1488. default:
  1489. return nil, false
  1490. }
  1491. }
  1492. // externalProxyPins extracts an external-proxy entry's pinnedPeerCertSha256
  1493. // as a []any of non-empty strings. The []any element type matches what the
  1494. // JSON/Clash sub builders expect when reading the value back off the cloned
  1495. // stream's tlsSettings.settings.
  1496. func externalProxyPins(value any) ([]any, bool) {
  1497. switch v := value.(type) {
  1498. case []string:
  1499. out := make([]any, 0, len(v))
  1500. for _, item := range v {
  1501. if item != "" {
  1502. out = append(out, item)
  1503. }
  1504. }
  1505. return out, len(out) > 0
  1506. case []any:
  1507. out := make([]any, 0, len(v))
  1508. for _, item := range v {
  1509. if s, ok := item.(string); ok && s != "" {
  1510. out = append(out, s)
  1511. }
  1512. }
  1513. return out, len(out) > 0
  1514. default:
  1515. return nil, false
  1516. }
  1517. }
  1518. func joinAnyStrings(items []any) string {
  1519. parts := make([]string, 0, len(items))
  1520. for _, item := range items {
  1521. if s, ok := item.(string); ok {
  1522. parts = append(parts, s)
  1523. }
  1524. }
  1525. return strings.Join(parts, ",")
  1526. }
  1527. // buildVmessExternalProxyLinks is a thin adapter: it maps the legacy
  1528. // externalProxy entries to []ShareEndpoint and renders them through the unified
  1529. // endpoint path. Kept as a thin shim over the unified endpoint builder so
  1530. // genVmessLink keeps calling one helper (now threading transport through).
  1531. func (s *SubService) buildVmessExternalProxyLinks(externalProxies []any, baseObj map[string]any, inbound *model.Inbound, email string, transport string) string {
  1532. eps := make([]ShareEndpoint, 0, len(externalProxies))
  1533. for _, externalProxy := range externalProxies {
  1534. ep, _ := externalProxy.(map[string]any)
  1535. eps = append(eps, externalProxyToEndpoint(ep))
  1536. }
  1537. return s.buildEndpointVmessLinks(eps, baseObj, inbound, email, transport)
  1538. }
  1539. // buildLinkWithParams appends ?query and #fragment to a pre-built
  1540. // scheme://userinfo@host:port string without re-parsing it. The caller
  1541. // has already escaped userinfo via encodeUserinfo (or chosen a base64
  1542. // alphabet with no reserved chars); a url.Parse + .String() round-trip
  1543. // would silently decode that escaping because Go's userinfo emitter
  1544. // leaves sub-delims (=, +, ;) literal, which breaks Trojan/Hysteria/SS
  1545. // clients that reject those chars in the password.
  1546. func buildLinkWithParams(link string, params map[string]string, fragment string) string {
  1547. return appendQueryAndFragment(link, params, fragment, "", false)
  1548. }
  1549. // buildLinkWithParamsAndSecurity is buildLinkWithParams plus an
  1550. // external-proxy override: the `security` key in params is replaced with
  1551. // the supplied value, and TLS hint fields (alpn/sni/fp/pcs) are stripped
  1552. // when the override is `none`.
  1553. func buildLinkWithParamsAndSecurity(link string, params map[string]string, fragment, security string, omitTLSFields bool) string {
  1554. return appendQueryAndFragment(link, params, fragment, security, omitTLSFields)
  1555. }
  1556. func appendQueryAndFragment(link string, params map[string]string, fragment, securityOverride string, omitTLSFields bool) string {
  1557. var sb strings.Builder
  1558. sb.WriteString(link)
  1559. if len(params) > 0 {
  1560. q := url.Values{}
  1561. for k, v := range params {
  1562. if securityOverride != "" && k == "security" {
  1563. v = securityOverride
  1564. }
  1565. if omitTLSFields && (k == "alpn" || k == "sni" || k == "fp" || k == "pcs") {
  1566. continue
  1567. }
  1568. q.Set(k, v)
  1569. }
  1570. encoded := q.Encode()
  1571. if encoded != "" {
  1572. if strings.Contains(link, "?") {
  1573. sb.WriteByte('&')
  1574. } else {
  1575. sb.WriteByte('?')
  1576. }
  1577. sb.WriteString(encoded)
  1578. }
  1579. }
  1580. if fragment != "" {
  1581. sb.WriteByte('#')
  1582. // Match the frontend's encodeURIComponent(remark): spaces become
  1583. // %20 (not + as in query strings).
  1584. sb.WriteString(strings.ReplaceAll(url.QueryEscape(fragment), "+", "%20"))
  1585. }
  1586. return sb.String()
  1587. }
  1588. // buildExternalProxyURLLinks is a thin adapter: it maps the legacy externalProxy
  1589. // entries to []ShareEndpoint and renders them through the unified endpoint path.
  1590. // Kept so the genVless/genTrojan/genShadowsocks call sites are unchanged.
  1591. func (s *SubService) buildExternalProxyURLLinks(
  1592. externalProxies []any,
  1593. params map[string]string,
  1594. baseSecurity string,
  1595. makeLink func(ep map[string]any, dest string, port int) string,
  1596. makeRemark func(ep map[string]any) string,
  1597. ) string {
  1598. eps := make([]ShareEndpoint, 0, len(externalProxies))
  1599. for _, externalProxy := range externalProxies {
  1600. ep, _ := externalProxy.(map[string]any)
  1601. eps = append(eps, externalProxyToEndpoint(ep))
  1602. }
  1603. return s.buildEndpointLinks(eps, params, baseSecurity, func(e ShareEndpoint) string {
  1604. return makeLink(e.ep, e.Address, e.Port)
  1605. }, func(e ShareEndpoint) string {
  1606. return makeRemark(e.ep)
  1607. })
  1608. }
  1609. func cloneStringMap(source map[string]string) map[string]string {
  1610. cloned := make(map[string]string, len(source))
  1611. maps.Copy(cloned, source)
  1612. return cloned
  1613. }
  1614. // genRemark builds the remark for a non-host link (raw default / legacy
  1615. // externalProxy / synthetic JSON-Clash entry). A set remark template drives it
  1616. // in both the body and display contexts (genTemplatedRemark renders the
  1617. // name-only part on displays); with no template it falls back to the inbound
  1618. // remark, extra and email joined by "-".
  1619. func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string, transport string) string {
  1620. if s.remarkTemplate != "" {
  1621. return s.genTemplatedRemark(inbound, s.lookupClient(inbound, email), extra, transport)
  1622. }
  1623. return fallbackRemark(inbound.Remark, extra, email)
  1624. }
  1625. func fallbackRemark(parts ...string) string {
  1626. out := make([]string, 0, len(parts))
  1627. for _, p := range parts {
  1628. if p != "" {
  1629. out = append(out, p)
  1630. }
  1631. }
  1632. return strings.Join(out, "-")
  1633. }
  1634. // findClientStats returns the inbound's traffic record for email, if present.
  1635. func (s *SubService) findClientStats(inbound *model.Inbound, email string) (xray.ClientTraffic, bool) {
  1636. for _, clientStat := range inbound.ClientStats {
  1637. if clientStat.Email == email {
  1638. return clientStat, true
  1639. }
  1640. }
  1641. return xray.ClientTraffic{}, false
  1642. }
  1643. // statsByEmailFromDB resolves a client's traffic row straight from the DB by its
  1644. // globally-unique email, caching the hit into statsByEmail for the rest of the
  1645. // request. It's the last-resort lookup behind statsForClient: the preloaded
  1646. // ClientStats and the statsByEmail index are both keyed by
  1647. // client_traffics.inbound_id, which is written once by AddClientStat and never
  1648. // updated. When an inbound is deleted and recreated it gets a new id, so the old
  1649. // row is orphaned from every loaded inbound and both in-memory paths miss —
  1650. // leaving {{TRAFFIC_USED}} stuck at 0 for pre-existing clients even though their
  1651. // usage is intact (#5567). Matching by email recovers it, the same way the
  1652. // sub-info header's AggregateTrafficByEmails already does.
  1653. func (s *SubService) statsByEmailFromDB(email string) (xray.ClientTraffic, bool) {
  1654. db := database.GetDB()
  1655. if db == nil {
  1656. return xray.ClientTraffic{}, false
  1657. }
  1658. var row xray.ClientTraffic
  1659. if err := db.Model(&xray.ClientTraffic{}).Where("email = ?", email).First(&row).Error; err != nil {
  1660. return xray.ClientTraffic{}, false
  1661. }
  1662. if s.statsByEmail == nil {
  1663. s.statsByEmail = map[string]xray.ClientTraffic{}
  1664. }
  1665. s.statsByEmail[email] = row
  1666. return row, true
  1667. }
  1668. func searchKey(data any, key string) (any, bool) {
  1669. switch val := data.(type) {
  1670. case map[string]any:
  1671. for k, v := range val {
  1672. if k == key {
  1673. return v, true
  1674. }
  1675. if result, ok := searchKey(v, key); ok {
  1676. return result, true
  1677. }
  1678. }
  1679. case []any:
  1680. for _, v := range val {
  1681. if result, ok := searchKey(v, key); ok {
  1682. return result, true
  1683. }
  1684. }
  1685. }
  1686. return nil, false
  1687. }
  1688. // buildXhttpExtra walks an xhttpSettings map and returns the JSON blob
  1689. // that goes into the URL's `extra` param (or, for VMess, the link
  1690. // object). Carries ONLY the bidirectional fields from xray-core's
  1691. // SplitHTTPConfig — i.e. the ones the server enforces and the client
  1692. // must match. Strictly one-sided fields are excluded:
  1693. //
  1694. // - server-only (noSSEHeader, scMaxBufferedPosts, scStreamUpServerSecs,
  1695. // serverMaxHeaderBytes) — client wouldn't read them, so emitting
  1696. // them just bloats the URL.
  1697. // - client-only values are included only when present in the inbound
  1698. // JSON. Some deployments/imported configs carry them there, and the
  1699. // subscription link is the only place clients can receive them.
  1700. //
  1701. // Truthy-only guards keep default inbounds emitting the same compact URL
  1702. // they did before this helper grew.
  1703. func buildXhttpExtra(xhttp map[string]any) map[string]any {
  1704. if xhttp == nil {
  1705. return nil
  1706. }
  1707. extra := map[string]any{}
  1708. if mode, ok := xhttp["mode"].(string); ok && len(mode) > 0 {
  1709. extra["mode"] = mode
  1710. }
  1711. if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
  1712. extra["xPaddingBytes"] = xpb
  1713. }
  1714. if obfs, ok := xhttp["xPaddingObfsMode"].(bool); ok && obfs {
  1715. extra["xPaddingObfsMode"] = true
  1716. for _, field := range []string{"xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"} {
  1717. if v, ok := xhttp[field].(string); ok && len(v) > 0 {
  1718. extra[field] = v
  1719. }
  1720. }
  1721. }
  1722. stringFields := []string{
  1723. "uplinkHTTPMethod",
  1724. "sessionIDPlacement", "sessionIDKey", "sessionIDTable", "sessionIDLength",
  1725. "seqPlacement", "seqKey",
  1726. "uplinkDataPlacement", "uplinkDataKey",
  1727. "scMaxEachPostBytes", "scMinPostsIntervalMs",
  1728. }
  1729. // Values matching xray-core's own defaults are redundant on the wire and
  1730. // the literal scMinPostsIntervalMs=30 is a known DPI fingerprint (#5141).
  1731. // Old panels seeded these defaults into every xhttp inbound, so filter
  1732. // them here instead of requiring every stored config to be re-saved.
  1733. coreDefaults := map[string]string{
  1734. "scMaxEachPostBytes": "1000000",
  1735. "scMinPostsIntervalMs": "30",
  1736. }
  1737. for _, field := range stringFields {
  1738. if v, ok := xhttp[field].(string); ok && len(v) > 0 && v != coreDefaults[field] {
  1739. extra[field] = v
  1740. }
  1741. }
  1742. // Legacy inbounds (pre xray-core #6258) stored sessionPlacement/sessionKey.
  1743. // Lift them onto the renamed keys so links from not-yet-resaved configs
  1744. // still carry the session settings. Mirrors the frontend migration.
  1745. for legacy, renamed := range map[string]string{
  1746. "sessionPlacement": "sessionIDPlacement",
  1747. "sessionKey": "sessionIDKey",
  1748. } {
  1749. if _, exists := extra[renamed]; !exists {
  1750. if v, ok := xhttp[legacy].(string); ok && len(v) > 0 {
  1751. extra[renamed] = v
  1752. }
  1753. }
  1754. }
  1755. for _, field := range []string{"uplinkChunkSize"} {
  1756. if v, ok := nonZeroShareValue(xhttp[field]); ok {
  1757. extra[field] = v
  1758. }
  1759. }
  1760. for _, field := range []string{"noGRPCHeader"} {
  1761. if v, ok := xhttp[field].(bool); ok && v {
  1762. extra[field] = v
  1763. }
  1764. }
  1765. for _, field := range []string{"xmux", "downloadSettings"} {
  1766. if v, ok := nonEmptyShareObject(xhttp[field]); ok {
  1767. extra[field] = v
  1768. }
  1769. }
  1770. // Headers — emitted as the {name: value} map upstream's struct
  1771. // expects. The server runtime ignores this field, but the client
  1772. // (consuming the share link) honors it. Drop any "host" entry —
  1773. // host already wins as a top-level URL param.
  1774. if rawHeaders, ok := xhttp["headers"].(map[string]any); ok && len(rawHeaders) > 0 {
  1775. out := map[string]any{}
  1776. for k, v := range rawHeaders {
  1777. if strings.EqualFold(k, "host") {
  1778. continue
  1779. }
  1780. out[k] = v
  1781. }
  1782. if len(out) > 0 {
  1783. extra["headers"] = out
  1784. }
  1785. }
  1786. if len(extra) == 0 {
  1787. return nil
  1788. }
  1789. return extra
  1790. }
  1791. func nonZeroShareValue(v any) (any, bool) {
  1792. switch value := v.(type) {
  1793. case string:
  1794. return value, value != ""
  1795. case int:
  1796. return value, value != 0
  1797. case int32:
  1798. return value, value != 0
  1799. case int64:
  1800. return value, value != 0
  1801. case float32:
  1802. return value, value != 0
  1803. case float64:
  1804. return value, value != 0
  1805. default:
  1806. return nil, false
  1807. }
  1808. }
  1809. func nonEmptyShareObject(v any) (any, bool) {
  1810. switch value := v.(type) {
  1811. case map[string]any:
  1812. return value, len(value) > 0
  1813. case map[string]string:
  1814. return value, len(value) > 0
  1815. case []any:
  1816. return value, len(value) > 0
  1817. default:
  1818. return nil, false
  1819. }
  1820. }
  1821. // applyXhttpExtraParams emits the full xhttp config into the URL query
  1822. // params of a vless:// / trojan:// / ss:// link. Sets path/host/mode at
  1823. // top level (xray's Build() always lets these win over `extra`) and packs
  1824. // everything else into a JSON `extra` param. Also writes the flat
  1825. // `x_padding_bytes` param sing-box-family clients understand.
  1826. //
  1827. // Without this, the admin's custom xPaddingBytes / sessionKey / etc. never
  1828. // reach the client and handshakes are silently rejected with
  1829. // `invalid padding (...) length: 0` — the client-visible symptom is
  1830. // "xhttp doesn't connect" on OpenWRT / sing-box.
  1831. //
  1832. // Two encodings are written so every popular client can read at least one:
  1833. //
  1834. // - x_padding_bytes=<range> — flat param, understood by sing-box and its
  1835. // derivatives (Podkop, OpenWRT sing-box, Karing, NekoBox, …).
  1836. // - extra=<url-encoded-json> — full xhttp settings blob, which is how
  1837. // xray-core clients (v2rayNG, Happ, Furious, Exclave, …) pick up the
  1838. // bidirectional fields beyond path/host/mode.
  1839. func applyXhttpExtraParams(xhttp map[string]any, params map[string]string) {
  1840. if xhttp == nil {
  1841. return
  1842. }
  1843. applyPathAndHostParams(xhttp, params)
  1844. if mode, ok := xhttp["mode"].(string); ok {
  1845. params["mode"] = mode
  1846. }
  1847. if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
  1848. params["x_padding_bytes"] = xpb
  1849. }
  1850. extra := buildXhttpExtra(xhttp)
  1851. if extra != nil {
  1852. if b, err := json.Marshal(extra); err == nil {
  1853. params["extra"] = string(b)
  1854. }
  1855. }
  1856. }
  1857. var kcpMaskToHeaderType = map[string]string{
  1858. "dns": "dns",
  1859. "dtls": "dtls",
  1860. "srtp": "srtp",
  1861. "utp": "utp",
  1862. "wechat": "wechat-video",
  1863. "wireguard": "wireguard",
  1864. }
  1865. var validFinalMaskUDPTypes = map[string]struct{}{
  1866. "salamander": {},
  1867. "mkcp-legacy": {},
  1868. "xdns": {},
  1869. "xicmp": {},
  1870. "noise": {},
  1871. "header-custom": {},
  1872. "realm": {},
  1873. }
  1874. var validFinalMaskTCPTypes = map[string]struct{}{
  1875. "header-custom": {},
  1876. "fragment": {},
  1877. "sudoku": {},
  1878. }
  1879. // applyKcpShareParams reconstructs legacy KCP share-link fields from either
  1880. // the historical kcpSettings.header/seed shape or the current finalmask model.
  1881. // This keeps subscription output compatible while avoiding panics when older
  1882. // keys are absent from modern inbounds.
  1883. func applyKcpShareParams(stream map[string]any, params map[string]string) {
  1884. extractKcpShareFields(stream).applyToParams(params)
  1885. }
  1886. func applyKcpShareObj(stream map[string]any, obj map[string]any) {
  1887. extractKcpShareFields(stream).applyToObj(obj)
  1888. }
  1889. type kcpShareFields struct {
  1890. headerType string
  1891. seed string
  1892. mtu int
  1893. tti int
  1894. }
  1895. func (f kcpShareFields) applyToParams(params map[string]string) {
  1896. if f.headerType != "" && f.headerType != "none" {
  1897. params["headerType"] = f.headerType
  1898. }
  1899. setStringParam(params, "seed", f.seed)
  1900. setIntParam(params, "mtu", f.mtu)
  1901. setIntParam(params, "tti", f.tti)
  1902. }
  1903. func (f kcpShareFields) applyToObj(obj map[string]any) {
  1904. if f.headerType != "" && f.headerType != "none" {
  1905. obj["type"] = f.headerType
  1906. }
  1907. setStringField(obj, "path", f.seed)
  1908. setIntField(obj, "mtu", f.mtu)
  1909. setIntField(obj, "tti", f.tti)
  1910. }
  1911. func extractKcpShareFields(stream map[string]any) kcpShareFields {
  1912. fields := kcpShareFields{headerType: "none"}
  1913. if kcp, ok := stream["kcpSettings"].(map[string]any); ok {
  1914. if header, ok := kcp["header"].(map[string]any); ok {
  1915. if value, ok := header["type"].(string); ok && value != "" {
  1916. fields.headerType = value
  1917. }
  1918. }
  1919. if value, ok := kcp["seed"].(string); ok && value != "" {
  1920. fields.seed = value
  1921. }
  1922. if value, ok := readPositiveInt(kcp["mtu"]); ok {
  1923. fields.mtu = value
  1924. }
  1925. if value, ok := readPositiveInt(kcp["tti"]); ok {
  1926. fields.tti = value
  1927. }
  1928. }
  1929. for _, rawMask := range normalizedFinalMaskUDPMasks(stream["finalmask"]) {
  1930. mask, _ := rawMask.(map[string]any)
  1931. if mask == nil {
  1932. continue
  1933. }
  1934. if maskType, _ := mask["type"].(string); maskType != "mkcp-legacy" {
  1935. continue
  1936. }
  1937. settings, _ := mask["settings"].(map[string]any)
  1938. header, _ := settings["header"].(string)
  1939. value, _ := settings["value"].(string)
  1940. if header == "" {
  1941. fields.seed = value
  1942. continue
  1943. }
  1944. if mapped, ok := kcpMaskToHeaderType[header]; ok {
  1945. fields.headerType = mapped
  1946. }
  1947. }
  1948. return fields
  1949. }
  1950. func readPositiveInt(value any) (int, bool) {
  1951. switch number := value.(type) {
  1952. case int:
  1953. return number, number > 0
  1954. case int32:
  1955. return int(number), number > 0
  1956. case int64:
  1957. return int(number), number > 0
  1958. case float32:
  1959. parsed := int(number)
  1960. return parsed, parsed > 0
  1961. case float64:
  1962. parsed := int(number)
  1963. return parsed, parsed > 0
  1964. default:
  1965. return 0, false
  1966. }
  1967. }
  1968. func setStringParam(params map[string]string, key, value string) {
  1969. if value == "" {
  1970. delete(params, key)
  1971. return
  1972. }
  1973. params[key] = value
  1974. }
  1975. func setIntParam(params map[string]string, key string, value int) {
  1976. if value <= 0 {
  1977. delete(params, key)
  1978. return
  1979. }
  1980. params[key] = fmt.Sprintf("%d", value)
  1981. }
  1982. func setStringField(obj map[string]any, key, value string) {
  1983. if value == "" {
  1984. delete(obj, key)
  1985. return
  1986. }
  1987. obj[key] = value
  1988. }
  1989. func setIntField(obj map[string]any, key string, value int) {
  1990. if value <= 0 {
  1991. delete(obj, key)
  1992. return
  1993. }
  1994. obj[key] = value
  1995. }
  1996. // applyFinalMaskParams exports the finalmask payload as the compact
  1997. // `fm=<json>` share-link field used by v2rayN-compatible clients.
  1998. func applyFinalMaskParams(finalmask map[string]any, params map[string]string) {
  1999. if fm, ok := marshalFinalMask(finalmask); ok {
  2000. params["fm"] = fm
  2001. }
  2002. }
  2003. func applyFinalMaskObj(finalmask map[string]any, obj map[string]any) {
  2004. if fm, ok := marshalFinalMask(finalmask); ok {
  2005. obj["fm"] = fm
  2006. }
  2007. }
  2008. func marshalFinalMask(finalmask map[string]any) (string, bool) {
  2009. normalized := normalizeFinalMask(finalmask)
  2010. if !hasFinalMaskContent(normalized) {
  2011. return "", false
  2012. }
  2013. b, err := json.Marshal(normalized)
  2014. if err != nil || len(b) == 0 || string(b) == "null" {
  2015. return "", false
  2016. }
  2017. return string(b), true
  2018. }
  2019. func normalizeFinalMask(finalmask map[string]any) map[string]any {
  2020. tcpMasks := normalizedFinalMaskTCPMasks(finalmask)
  2021. udpMasks := normalizedFinalMaskUDPMasks(finalmask)
  2022. quicParams, hasQuicParams := finalmask["quicParams"].(map[string]any)
  2023. if len(tcpMasks) == 0 && len(udpMasks) == 0 && !hasQuicParams {
  2024. return nil
  2025. }
  2026. result := map[string]any{}
  2027. if len(tcpMasks) > 0 {
  2028. result["tcp"] = tcpMasks
  2029. }
  2030. if len(udpMasks) > 0 {
  2031. result["udp"] = udpMasks
  2032. }
  2033. if hasQuicParams && len(quicParams) > 0 {
  2034. result["quicParams"] = quicParams
  2035. }
  2036. return result
  2037. }
  2038. func normalizedFinalMaskTCPMasks(value any) []any {
  2039. finalmask, _ := value.(map[string]any)
  2040. if finalmask == nil {
  2041. return nil
  2042. }
  2043. rawMasks, _ := finalmask["tcp"].([]any)
  2044. if len(rawMasks) == 0 {
  2045. return nil
  2046. }
  2047. normalized := make([]any, 0, len(rawMasks))
  2048. for _, rawMask := range rawMasks {
  2049. mask, _ := rawMask.(map[string]any)
  2050. if mask == nil {
  2051. continue
  2052. }
  2053. maskType, _ := mask["type"].(string)
  2054. if _, ok := validFinalMaskTCPTypes[maskType]; !ok || maskType == "" {
  2055. continue
  2056. }
  2057. normalizedMask := map[string]any{"type": maskType}
  2058. if settings, ok := mask["settings"].(map[string]any); ok && len(settings) > 0 {
  2059. normalizedMask["settings"] = settings
  2060. }
  2061. normalized = append(normalized, normalizedMask)
  2062. }
  2063. if len(normalized) == 0 {
  2064. return nil
  2065. }
  2066. return normalized
  2067. }
  2068. func normalizedFinalMaskUDPMasks(value any) []any {
  2069. finalmask, _ := value.(map[string]any)
  2070. if finalmask == nil {
  2071. return nil
  2072. }
  2073. rawMasks, _ := finalmask["udp"].([]any)
  2074. if len(rawMasks) == 0 {
  2075. return nil
  2076. }
  2077. normalized := make([]any, 0, len(rawMasks))
  2078. for _, rawMask := range rawMasks {
  2079. mask, _ := rawMask.(map[string]any)
  2080. if mask == nil {
  2081. continue
  2082. }
  2083. maskType, _ := mask["type"].(string)
  2084. if _, ok := validFinalMaskUDPTypes[maskType]; !ok || maskType == "" {
  2085. continue
  2086. }
  2087. normalizedMask := map[string]any{"type": maskType}
  2088. if settings, ok := mask["settings"].(map[string]any); ok && len(settings) > 0 {
  2089. normalizedMask["settings"] = settings
  2090. }
  2091. normalized = append(normalized, normalizedMask)
  2092. }
  2093. if len(normalized) == 0 {
  2094. return nil
  2095. }
  2096. return normalized
  2097. }
  2098. func hasFinalMaskContent(value any) bool {
  2099. switch v := value.(type) {
  2100. case nil:
  2101. return false
  2102. case string:
  2103. return len(v) > 0
  2104. case map[string]any:
  2105. for _, item := range v {
  2106. if hasFinalMaskContent(item) {
  2107. return true
  2108. }
  2109. }
  2110. return false
  2111. case []any:
  2112. return slices.ContainsFunc(v, hasFinalMaskContent)
  2113. default:
  2114. return true
  2115. }
  2116. }
  2117. func searchHost(headers any) string {
  2118. data, _ := headers.(map[string]any)
  2119. for k, v := range data {
  2120. if strings.EqualFold(k, "host") {
  2121. switch v.(type) {
  2122. case []any:
  2123. hosts, _ := v.([]any)
  2124. if len(hosts) > 0 {
  2125. return hosts[0].(string)
  2126. } else {
  2127. return ""
  2128. }
  2129. case any:
  2130. return v.(string)
  2131. }
  2132. }
  2133. }
  2134. return ""
  2135. }
  2136. // PageData is a view model for subpage.html
  2137. // PageData contains data for rendering the subscription information page.
  2138. type PageData struct {
  2139. Host string
  2140. BasePath string
  2141. SId string
  2142. Enabled bool
  2143. Download string
  2144. Upload string
  2145. Total string
  2146. Used string
  2147. Remained string
  2148. Expire int64
  2149. LastOnline int64
  2150. Datepicker string
  2151. DownloadByte int64
  2152. UploadByte int64
  2153. TotalByte int64
  2154. SubUrl string
  2155. SubJsonUrl string
  2156. SubClashUrl string
  2157. SubTitle string
  2158. SubSupportUrl string
  2159. Result []string
  2160. Emails []string
  2161. }
  2162. // ResolveRequest extracts scheme and host info from request/headers consistently.
  2163. // ResolveRequest extracts scheme, host, and header information from an HTTP request.
  2164. func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string, hostWithPort string, hostHeader string) {
  2165. // scheme
  2166. scheme = "http"
  2167. if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") {
  2168. scheme = "https"
  2169. }
  2170. // base host (no port)
  2171. if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil && h != "" {
  2172. host = h
  2173. }
  2174. if host == "" {
  2175. host = c.GetHeader("X-Real-IP")
  2176. }
  2177. if host == "" {
  2178. var err error
  2179. host, _, err = net.SplitHostPort(c.Request.Host)
  2180. if err != nil {
  2181. host = c.Request.Host
  2182. }
  2183. }
  2184. // host:port for URLs
  2185. hostWithPort = c.GetHeader("X-Forwarded-Host")
  2186. if hostWithPort == "" {
  2187. hostWithPort = c.Request.Host
  2188. }
  2189. if hostWithPort == "" {
  2190. hostWithPort = host
  2191. }
  2192. // header display host
  2193. hostHeader = c.GetHeader("X-Forwarded-Host")
  2194. if hostHeader == "" {
  2195. hostHeader = c.GetHeader("X-Real-IP")
  2196. }
  2197. if hostHeader == "" {
  2198. hostHeader = host
  2199. }
  2200. return
  2201. }
  2202. // BuildURLs constructs absolute subscription and JSON subscription URLs for a given subscription ID.
  2203. // It prioritizes configured URIs, then individual settings, and finally falls back to request-derived components.
  2204. func (s *SubService) BuildURLs(subPath, subJsonPath, subClashPath, subId string) (subURL, subJsonURL, subClashURL string) {
  2205. if subId == "" {
  2206. return "", "", ""
  2207. }
  2208. configuredSubURI, _ := s.settingService.GetSubURI()
  2209. configuredSubJsonURI, _ := s.settingService.GetSubJsonURI()
  2210. configuredSubClashURI, _ := s.settingService.GetSubClashURI()
  2211. // Same base as the panel's Client Information page; s.address is the
  2212. // subscriber's host already normalized away from any loopback/bind IP.
  2213. base := s.settingService.BuildSubURIBase(s.address)
  2214. subURL = s.buildSingleURL(configuredSubURI, base, subPath, subId)
  2215. // When subURI is explicitly configured (reverse-proxy setup), use its
  2216. // scheme+host as the base for JSON and Clash URLs so they match the
  2217. // reverse-proxy endpoint instead of the raw sub-server port. Fall back
  2218. // to the request-derived base if subURI is empty or can't be parsed
  2219. // into a scheme+host (e.g. a malformed value with no scheme).
  2220. jsonClashBase := base
  2221. if configuredSubURI != "" {
  2222. if derived := s.extractBaseFromURI(configuredSubURI); derived != "" {
  2223. jsonClashBase = derived
  2224. }
  2225. }
  2226. subJsonURL = s.buildSingleURL(configuredSubJsonURI, jsonClashBase, subJsonPath, subId)
  2227. subClashURL = s.buildSingleURL(configuredSubClashURI, jsonClashBase, subClashPath, subId)
  2228. return subURL, subJsonURL, subClashURL
  2229. }
  2230. // extractBaseFromURI extracts scheme://host from a configured URI.
  2231. // e.g., "https://example.com/sub-xxx/" → "https://example.com".
  2232. // Returns "" when the URI is empty or lacks a scheme/host, so callers can
  2233. // fall back to the request-derived base instead of emitting a broken value.
  2234. func (s *SubService) extractBaseFromURI(uri string) string {
  2235. u, err := url.Parse(uri)
  2236. if err != nil || u.Scheme == "" || u.Host == "" {
  2237. return ""
  2238. }
  2239. return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
  2240. }
  2241. // buildSingleURL constructs a single URL using configured URI or base components
  2242. func (s *SubService) buildSingleURL(configuredURI, base, basePath, subId string) string {
  2243. if configuredURI != "" {
  2244. return s.joinPathWithID(configuredURI, subId)
  2245. }
  2246. return s.joinPathWithID(base+basePath, subId)
  2247. }
  2248. // joinPathWithID safely joins a base path with a subscription ID
  2249. func (s *SubService) joinPathWithID(basePath, subId string) string {
  2250. if strings.HasSuffix(basePath, "/") {
  2251. return basePath + subId
  2252. }
  2253. return basePath + "/" + subId
  2254. }
  2255. // BuildPageData parses header and prepares the template view model.
  2256. // BuildPageData constructs page data for rendering the subscription information page.
  2257. func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray.ClientTraffic, lastOnline int64, subs []string, emails []string, subURL, subJsonURL, subClashURL string, basePath string, subTitle string, subSupportUrl string) PageData {
  2258. download := common.FormatTraffic(traffic.Down)
  2259. upload := common.FormatTraffic(traffic.Up)
  2260. total := "∞"
  2261. used := common.FormatTraffic(traffic.Up + traffic.Down)
  2262. remained := ""
  2263. if traffic.Total > 0 {
  2264. total = common.FormatTraffic(traffic.Total)
  2265. left := max(traffic.Total-(traffic.Up+traffic.Down), 0)
  2266. remained = common.FormatTraffic(left)
  2267. }
  2268. datepicker := s.datepicker
  2269. if datepicker == "" {
  2270. datepicker = "gregorian"
  2271. }
  2272. pageLinks := make([]string, 0, len(subs))
  2273. pageEmails := make([]string, 0, len(subs))
  2274. for i, sub := range subs {
  2275. email := ""
  2276. if i < len(emails) {
  2277. email = emails[i]
  2278. }
  2279. for _, link := range splitLinkLines(sub) {
  2280. pageLinks = append(pageLinks, link)
  2281. pageEmails = append(pageEmails, email)
  2282. }
  2283. }
  2284. return PageData{
  2285. Host: hostHeader,
  2286. BasePath: basePath,
  2287. SId: subId,
  2288. Enabled: traffic.Enable,
  2289. Download: download,
  2290. Upload: upload,
  2291. Total: total,
  2292. Used: used,
  2293. Remained: remained,
  2294. Expire: traffic.ExpiryTime / 1000,
  2295. LastOnline: lastOnline,
  2296. Datepicker: datepicker,
  2297. DownloadByte: traffic.Down,
  2298. UploadByte: traffic.Up,
  2299. TotalByte: traffic.Total,
  2300. SubUrl: subURL,
  2301. SubJsonUrl: subJsonURL,
  2302. SubClashUrl: subClashURL,
  2303. SubTitle: subTitle,
  2304. SubSupportUrl: subSupportUrl,
  2305. Result: pageLinks,
  2306. Emails: pageEmails,
  2307. }
  2308. }
  2309. func getHostFromXFH(s string) (string, error) {
  2310. if strings.Contains(s, ":") {
  2311. realHost, _, err := net.SplitHostPort(s)
  2312. if err != nil {
  2313. return "", err
  2314. }
  2315. return realHost, nil
  2316. }
  2317. return s, nil
  2318. }