feat: support custom headers for Xray HTTP outbounds (#9690)

* feat: support custom headers for Xray HTTP outbounds

* feat: simplify HTTP outbound headers input

* feat: validate JSON syntax in HTTP headers

  Keep HTTP outbound headers stored as raw text, while validating
  header names and values against RFC 9110/9112 syntax. Support string
  arrays for multi-value headers, reject duplicate JSON header keys, and
  clarify the invalid header JSON message.

* feat: refactor HTTP header parsing into utility

* Simplify code

* Simplify code

* Update CoreConfigV2rayServiceTests.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
This commit is contained in:
006lp
2026-07-11 14:31:17 +08:00
committed by GitHub
co-authored by 2dust
parent 2c70b018ce
commit 88d59488fd
14 changed files with 225 additions and 6 deletions
@@ -80,6 +80,9 @@ public class AddServerViewModel : MyReactiveObject
[Reactive]
public bool NaiveQuic { get; set; }
[Reactive]
public string HttpHeadersJson { get; set; }
[Reactive]
public string Hy2RealmUrl { get; set; }
@@ -255,7 +258,6 @@ public class AddServerViewModel : MyReactiveObject
{
await SaveServerAsync();
});
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
@@ -311,6 +313,7 @@ public class AddServerViewModel : MyReactiveObject
CongestionControl = protocolExtra.CongestionControl ?? string.Empty;
InsecureConcurrency = protocolExtra.InsecureConcurrency > 0 ? protocolExtra.InsecureConcurrency : null;
NaiveQuic = protocolExtra.NaiveQuic ?? false;
HttpHeadersJson = protocolExtra.HttpHeaders ?? string.Empty;
Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty;
GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
@@ -379,6 +382,11 @@ public class AddServerViewModel : MyReactiveObject
return;
}
}
if (HttpHeadersJson.IsNotEmpty() && JsonUtils.ParseJson(HttpHeadersJson) == null)
{
NoticeManager.Instance.Enqueue(ResUI.InvalidHttpOutboundHeaders);
return;
}
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
SelectedSource.AllowInsecure = AllowInsecure ? Global.StringTrue : Global.StringFalse;
SelectedSource.MuxEnabled = MuxEnabled;
@@ -416,6 +424,7 @@ public class AddServerViewModel : MyReactiveObject
VmessSecurity = VmessSecurity.NullIfEmpty(),
VlessEncryption = VlessEncryption.NullIfEmpty(),
SsMethod = SsMethod.NullIfEmpty(),
HttpHeaders = SelectedSource.ConfigType == EConfigType.HTTP ? HttpHeadersJson.NullIfEmpty() : null,
WgPublicKey = WgPublicKey.NullIfEmpty(),
WgPresharedKey = WgPresharedKey.NullIfEmpty(),
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),