This commit is contained in:
DHR60 2026-03-28 23:09:30 +08:00
parent 8d8b3369a2
commit 8599f70b03
12 changed files with 94 additions and 132 deletions

View file

@ -1141,12 +1141,8 @@ public static class ConfigHandler
&& AreEqual(o.Network, n.Network)
&& AreEqual(oTransport.RawHeaderType, nTransport.RawHeaderType)
&& AreEqual(oTransport.RawHost, nTransport.RawHost)
&& AreEqual(oTransport.WsHost, nTransport.WsHost)
&& AreEqual(oTransport.WsPath, nTransport.WsPath)
&& AreEqual(oTransport.HttpupgradeHost, nTransport.HttpupgradeHost)
&& AreEqual(oTransport.HttpupgradePath, nTransport.HttpupgradePath)
&& AreEqual(oTransport.XhttpHost, nTransport.XhttpHost)
&& AreEqual(oTransport.XhttpPath, nTransport.XhttpPath)
&& AreEqual(oTransport.Host, nTransport.Host)
&& AreEqual(oTransport.Path, nTransport.Path)
&& AreEqual(oTransport.XhttpMode, nTransport.XhttpMode)
&& AreEqual(oTransport.XhttpExtra, nTransport.XhttpExtra)
&& AreEqual(oTransport.GrpcAuthority, nTransport.GrpcAuthority)

View file

@ -116,35 +116,35 @@ public class BaseFmt
break;
case nameof(ETransport.ws):
if (transport.WsHost.IsNotEmpty())
if (transport.Host.IsNotEmpty())
{
dicQuery.Add("host", Utils.UrlEncode(transport.WsHost));
dicQuery.Add("host", Utils.UrlEncode(transport.Host));
}
if (transport.WsPath.IsNotEmpty())
if (transport.Path.IsNotEmpty())
{
dicQuery.Add("path", Utils.UrlEncode(transport.WsPath));
dicQuery.Add("path", Utils.UrlEncode(transport.Path));
}
break;
case nameof(ETransport.httpupgrade):
if (transport.HttpupgradeHost.IsNotEmpty())
if (transport.Host.IsNotEmpty())
{
dicQuery.Add("host", Utils.UrlEncode(transport.HttpupgradeHost));
dicQuery.Add("host", Utils.UrlEncode(transport.Host));
}
if (transport.HttpupgradePath.IsNotEmpty())
if (transport.Path.IsNotEmpty())
{
dicQuery.Add("path", Utils.UrlEncode(transport.HttpupgradePath));
dicQuery.Add("path", Utils.UrlEncode(transport.Path));
}
break;
case nameof(ETransport.xhttp):
if (transport.XhttpHost.IsNotEmpty())
if (transport.Host.IsNotEmpty())
{
dicQuery.Add("host", Utils.UrlEncode(transport.XhttpHost));
dicQuery.Add("host", Utils.UrlEncode(transport.Host));
}
if (transport.XhttpPath.IsNotEmpty())
if (transport.Path.IsNotEmpty())
{
dicQuery.Add("path", Utils.UrlEncode(transport.XhttpPath));
dicQuery.Add("path", Utils.UrlEncode(transport.Path));
}
if (transport.XhttpMode.IsNotEmpty() && Global.XhttpMode.Contains(transport.XhttpMode))
{
@ -288,16 +288,16 @@ public class BaseFmt
case nameof(ETransport.ws):
transport = transport with
{
WsHost = GetQueryDecoded(query, "host"),
WsPath = GetQueryDecoded(query, "path", "/"),
Host = GetQueryDecoded(query, "host"),
Path = GetQueryDecoded(query, "path", "/"),
};
break;
case nameof(ETransport.httpupgrade):
transport = transport with
{
HttpupgradeHost = GetQueryDecoded(query, "host"),
HttpupgradePath = GetQueryDecoded(query, "path", "/"),
Host = GetQueryDecoded(query, "host"),
Path = GetQueryDecoded(query, "path", "/"),
};
break;
@ -319,8 +319,8 @@ public class BaseFmt
transport = transport with
{
XhttpHost = GetQueryDecoded(query, "host"),
XhttpPath = GetQueryDecoded(query, "path", "/"),
Host = GetQueryDecoded(query, "host"),
Path = GetQueryDecoded(query, "path", "/"),
XhttpMode = GetQueryDecoded(query, "mode"),
XhttpExtra = xhttpExtra,
};

View file

@ -58,10 +58,10 @@ public class ShadowsocksFmt : BaseFmt
if (item.Network == nameof(ETransport.ws))
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={transport.WsHost};";
pluginArgs += $"host={transport.Host};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = (transport.WsPath ?? string.Empty).Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
var path = (transport.Path ?? string.Empty).Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
if (item.StreamSecurity == Global.StreamSecurity)
@ -235,14 +235,14 @@ public class ShadowsocksFmt : BaseFmt
if (!host.IsNullOrEmpty())
{
var wsHost = host.Replace("host=", "");
t = t with { WsHost = wsHost };
t = t with { Host = wsHost };
item.Sni = wsHost;
}
if (!path.IsNullOrEmpty())
{
var pathValue = path.Replace("path=", "");
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
t = t with { WsPath = pathValue };
t = t with { Path = pathValue };
}
item.SetTransportExtra(t);
}

View file

@ -46,18 +46,18 @@ public class VmessFmt : BaseFmt
host = item.GetNetwork() switch
{
nameof(ETransport.raw) => item.GetTransportExtra().RawHost,
nameof(ETransport.ws) => item.GetTransportExtra().WsHost,
nameof(ETransport.httpupgrade) => item.GetTransportExtra().HttpupgradeHost,
nameof(ETransport.xhttp) => item.GetTransportExtra().XhttpHost,
nameof(ETransport.ws) => item.GetTransportExtra().Host,
nameof(ETransport.httpupgrade) => item.GetTransportExtra().Host,
nameof(ETransport.xhttp) => item.GetTransportExtra().Host,
nameof(ETransport.grpc) => item.GetTransportExtra().GrpcAuthority,
_ => null,
},
path = item.GetNetwork() switch
{
nameof(ETransport.kcp) => item.GetTransportExtra().KcpSeed,
nameof(ETransport.ws) => item.GetTransportExtra().WsPath,
nameof(ETransport.httpupgrade) => item.GetTransportExtra().HttpupgradePath,
nameof(ETransport.xhttp) => item.GetTransportExtra().XhttpPath,
nameof(ETransport.ws) => item.GetTransportExtra().Path,
nameof(ETransport.httpupgrade) => item.GetTransportExtra().Path,
nameof(ETransport.xhttp) => item.GetTransportExtra().Path,
nameof(ETransport.grpc) => item.GetTransportExtra().GrpcServiceName,
_ => null,
},
@ -128,9 +128,9 @@ public class VmessFmt : BaseFmt
{
nameof(ETransport.raw) => transport with { RawHost = Utils.ToString(vmessQRCode.host) },
nameof(ETransport.kcp) => transport with { KcpSeed = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.ws) => transport with { WsHost = Utils.ToString(vmessQRCode.host), WsPath = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.httpupgrade) => transport with { HttpupgradeHost = Utils.ToString(vmessQRCode.host), HttpupgradePath = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.xhttp) => transport with { XhttpHost = Utils.ToString(vmessQRCode.host), XhttpPath = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.ws) => transport with { Host = Utils.ToString(vmessQRCode.host), Path = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.httpupgrade) => transport with { Host = Utils.ToString(vmessQRCode.host), Path = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.xhttp) => transport with { Host = Utils.ToString(vmessQRCode.host), Path = Utils.ToString(vmessQRCode.path) },
nameof(ETransport.grpc) => transport with { GrpcAuthority = Utils.ToString(vmessQRCode.host), GrpcServiceName = Utils.ToString(vmessQRCode.path) },
_ => transport,
};

View file

@ -362,14 +362,13 @@ public sealed class AppManager
{
try
{
if (item.Network == "tcp")
{
item.Network = nameof(ETransport.raw);
}
var extra = item.GetProtocolExtra();
var transport = extra.Transport ?? new TransportExtra();
var network = item.GetNetwork();
if (network == "tcp")
{
network = nameof(ETransport.raw);
item.Network = network;
}
switch (network)
{
@ -382,26 +381,19 @@ public sealed class AppManager
break;
case nameof(ETransport.ws):
transport = transport with
{
WsHost = item.RequestHost.NullIfEmpty(),
WsPath = item.Path.NullIfEmpty(),
};
break;
case nameof(ETransport.httpupgrade):
transport = transport with
{
HttpupgradeHost = item.RequestHost.NullIfEmpty(),
HttpupgradePath = item.Path.NullIfEmpty(),
Host = item.RequestHost.NullIfEmpty(),
Path = item.Path.NullIfEmpty(),
};
break;
case nameof(ETransport.xhttp):
transport = transport with
{
XhttpHost = item.RequestHost.NullIfEmpty(),
XhttpPath = item.Path.NullIfEmpty(),
Host = item.RequestHost.NullIfEmpty(),
Path = item.Path.NullIfEmpty(),
XhttpMode = item.HeaderType.NullIfEmpty(),
XhttpExtra = item.Extra.NullIfEmpty(),
};

View file

@ -169,9 +169,9 @@ public class ProfileItem
public string Network { get; set; }
[Obsolete("Use TransportExtra.RawHeaderType/XhttpMode/GrpcMode/KcpHeaderType instead.")]
public string HeaderType { get; set; }
[Obsolete("Use TransportExtra.RawHost/WsHost/HttpupgradeHost/XhttpHost/GrpcAuthority instead.")]
[Obsolete("Use TransportExtra.RawHost/Host/GrpcAuthority instead.")]
public string RequestHost { get; set; }
[Obsolete("Use TransportExtra.WsPath/HttpupgradePath/XhttpPath/GrpcServiceName/KcpSeed instead.")]
[Obsolete("Use TransportExtra.Path/GrpcServiceName/KcpSeed instead.")]
public string Path { get; set; }
public string StreamSecurity { get; set; }
public string AllowInsecure { get; set; }

View file

@ -5,14 +5,8 @@ public record TransportExtra
public string? RawHeaderType { get; init; }
public string? RawHost { get; init; }
public string? WsHost { get; init; }
public string? WsPath { get; init; }
public string? HttpupgradeHost { get; init; }
public string? HttpupgradePath { get; init; }
public string? XhttpHost { get; init; }
public string? XhttpPath { get; init; }
public string? Host { get; init; }
public string? Path { get; init; }
public string? XhttpMode { get; init; }
public string? XhttpExtra { get; init; }

View file

@ -127,10 +127,10 @@ public partial class CoreConfigSingboxService
if (network == nameof(ETransport.ws))
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={transportExtra.WsHost};";
pluginArgs += $"host={transportExtra.Host};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = (transportExtra.WsPath ?? string.Empty).Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
var path = (transportExtra.Path ?? string.Empty).Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
if (_node.StreamSecurity == Global.StreamSecurity)
@ -384,9 +384,9 @@ public partial class CoreConfigSingboxService
var host = _node.GetNetwork() switch
{
nameof(ETransport.raw) => _node.GetTransportExtra().RawHost,
nameof(ETransport.ws) => _node.GetTransportExtra().WsHost,
nameof(ETransport.httpupgrade) => _node.GetTransportExtra().HttpupgradeHost,
nameof(ETransport.xhttp) => _node.GetTransportExtra().XhttpHost,
nameof(ETransport.ws) => _node.GetTransportExtra().Host,
nameof(ETransport.httpupgrade) => _node.GetTransportExtra().Host,
nameof(ETransport.xhttp) => _node.GetTransportExtra().Host,
nameof(ETransport.grpc) => _node.GetTransportExtra().GrpcAuthority,
_ => null,
};
@ -459,7 +459,7 @@ public partial class CoreConfigSingboxService
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
var wsPath = transportExtra.WsPath;
var wsPath = transportExtra.Path;
// Parse eh and ed parameters from path using regex
if (!wsPath.IsNullOrEmpty())
@ -488,19 +488,19 @@ public partial class CoreConfigSingboxService
}
transport.path = wsPath.NullIfEmpty();
if (transportExtra.WsHost.IsNotEmpty())
if (transportExtra.Host.IsNotEmpty())
{
transport.headers = new()
{
Host = transportExtra.WsHost
Host = transportExtra.Host
};
}
break;
case nameof(ETransport.httpupgrade):
transport.type = nameof(ETransport.httpupgrade);
transport.path = transportExtra.HttpupgradePath.NullIfEmpty();
transport.host = transportExtra.HttpupgradeHost.NullIfEmpty();
transport.path = transportExtra.Path.NullIfEmpty();
transport.host = transportExtra.Host.NullIfEmpty();
break;

View file

@ -367,18 +367,18 @@ public partial class CoreConfigV2rayService
break;
case nameof(ETransport.ws):
host = transport.WsHost?.TrimEx() ?? string.Empty;
path = transport.WsPath?.TrimEx() ?? string.Empty;
host = transport.Host?.TrimEx() ?? string.Empty;
path = transport.Path?.TrimEx() ?? string.Empty;
break;
case nameof(ETransport.httpupgrade):
host = transport.HttpupgradeHost?.TrimEx() ?? string.Empty;
path = transport.HttpupgradePath?.TrimEx() ?? string.Empty;
host = transport.Host?.TrimEx() ?? string.Empty;
path = transport.Path?.TrimEx() ?? string.Empty;
break;
case nameof(ETransport.xhttp):
host = transport.XhttpHost?.TrimEx() ?? string.Empty;
path = transport.XhttpPath?.TrimEx() ?? string.Empty;
host = transport.Host?.TrimEx() ?? string.Empty;
path = transport.Path?.TrimEx() ?? string.Empty;
headerType = transport.XhttpMode?.TrimEx() ?? string.Empty;
xhttpExtra = transport.XhttpExtra?.TrimEx() ?? string.Empty;
break;

View file

@ -80,22 +80,10 @@ public class AddServerViewModel : MyReactiveObject
public string RawHost { get; set; }
[Reactive]
public string WsHost { get; set; }
public string Host { get; set; }
[Reactive]
public string WsPath { get; set; }
[Reactive]
public string HttpupgradeHost { get; set; }
[Reactive]
public string HttpupgradePath { get; set; }
[Reactive]
public string XhttpHost { get; set; }
[Reactive]
public string XhttpPath { get; set; }
public string Path { get; set; }
[Reactive]
public string XhttpMode { get; set; }
@ -154,9 +142,9 @@ public class AddServerViewModel : MyReactiveObject
get => SelectedSource.GetNetwork() switch
{
nameof(ETransport.raw) => RawHost,
nameof(ETransport.ws) => WsHost,
nameof(ETransport.httpupgrade) => HttpupgradeHost,
nameof(ETransport.xhttp) => XhttpHost,
nameof(ETransport.ws) => Host,
nameof(ETransport.httpupgrade) => Host,
nameof(ETransport.xhttp) => Host,
nameof(ETransport.grpc) => GrpcAuthority,
_ => string.Empty,
};
@ -168,13 +156,13 @@ public class AddServerViewModel : MyReactiveObject
RawHost = value;
break;
case nameof(ETransport.ws):
WsHost = value;
Host = value;
break;
case nameof(ETransport.httpupgrade):
HttpupgradeHost = value;
Host = value;
break;
case nameof(ETransport.xhttp):
XhttpHost = value;
Host = value;
break;
case nameof(ETransport.grpc):
GrpcAuthority = value;
@ -189,9 +177,9 @@ public class AddServerViewModel : MyReactiveObject
get => SelectedSource.GetNetwork() switch
{
nameof(ETransport.kcp) => KcpSeed,
nameof(ETransport.ws) => WsPath,
nameof(ETransport.httpupgrade) => HttpupgradePath,
nameof(ETransport.xhttp) => XhttpPath,
nameof(ETransport.ws) => Path,
nameof(ETransport.httpupgrade) => Path,
nameof(ETransport.xhttp) => Path,
nameof(ETransport.grpc) => GrpcServiceName,
_ => string.Empty,
};
@ -203,13 +191,13 @@ public class AddServerViewModel : MyReactiveObject
KcpSeed = value;
break;
case nameof(ETransport.ws):
WsPath = value;
Path = value;
break;
case nameof(ETransport.httpupgrade):
HttpupgradePath = value;
Path = value;
break;
case nameof(ETransport.xhttp):
XhttpPath = value;
Path = value;
break;
case nameof(ETransport.grpc):
GrpcServiceName = value;
@ -311,12 +299,8 @@ public class AddServerViewModel : MyReactiveObject
RawHeaderType = transport.RawHeaderType ?? Global.None;
RawHost = transport.RawHost ?? string.Empty;
WsHost = transport.WsHost ?? string.Empty;
WsPath = transport.WsPath ?? string.Empty;
HttpupgradeHost = transport.HttpupgradeHost ?? string.Empty;
HttpupgradePath = transport.HttpupgradePath ?? string.Empty;
XhttpHost = transport.XhttpHost ?? string.Empty;
XhttpPath = transport.XhttpPath ?? string.Empty;
Host = transport.Host ?? string.Empty;
Path = transport.Path ?? string.Empty;
XhttpMode = transport.XhttpMode ?? string.Empty;
XhttpExtra = transport.XhttpExtra ?? string.Empty;
GrpcAuthority = transport.GrpcAuthority ?? string.Empty;
@ -380,12 +364,8 @@ public class AddServerViewModel : MyReactiveObject
{
RawHeaderType = RawHeaderType.NullIfEmpty(),
RawHost = RawHost.NullIfEmpty(),
WsHost = WsHost.NullIfEmpty(),
WsPath = WsPath.NullIfEmpty(),
HttpupgradeHost = HttpupgradeHost.NullIfEmpty(),
HttpupgradePath = HttpupgradePath.NullIfEmpty(),
XhttpHost = XhttpHost.NullIfEmpty(),
XhttpPath = XhttpPath.NullIfEmpty(),
Host = Host.NullIfEmpty(),
Path = Path.NullIfEmpty(),
XhttpMode = XhttpMode.NullIfEmpty(),
XhttpExtra = XhttpExtra.NullIfEmpty(),
GrpcAuthority = GrpcAuthority.NullIfEmpty(),
@ -518,9 +498,9 @@ public class AddServerViewModel : MyReactiveObject
return SelectedSource.GetNetwork() switch
{
nameof(ETransport.raw) => RawHost,
nameof(ETransport.ws) => WsHost,
nameof(ETransport.httpupgrade) => HttpupgradeHost,
nameof(ETransport.xhttp) => XhttpHost,
nameof(ETransport.ws) => Host,
nameof(ETransport.httpupgrade) => Host,
nameof(ETransport.xhttp) => Host,
nameof(ETransport.grpc) => GrpcAuthority,
_ => string.Empty,
};

View file

@ -207,15 +207,15 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.KcpHeaderType, v => v.cmbHeaderTypeKcp.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.KcpSeed, v => v.txtKcpSeed.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WsHost, v => v.txtRequestHostWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WsPath, v => v.txtPathWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HttpupgradeHost, v => v.txtRequestHostHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HttpupgradePath, v => v.txtPathHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpMode, v => v.cmbHeaderTypeXhttp.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpHost, v => v.txtRequestHostXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpPath, v => v.txtPathXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpExtra, v => v.txtExtraXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.GrpcMode, v => v.cmbHeaderTypeGrpc.SelectedValue).DisposeWith(disposables);

View file

@ -205,15 +205,15 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.KcpHeaderType, v => v.cmbHeaderTypeKcp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.KcpSeed, v => v.txtKcpSeed.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WsHost, v => v.txtRequestHostWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WsPath, v => v.txtPathWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathWs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HttpupgradeHost, v => v.txtRequestHostHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HttpupgradePath, v => v.txtPathHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathHttpupgrade.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpMode, v => v.cmbHeaderTypeXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpHost, v => v.txtRequestHostXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpPath, v => v.txtPathXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XhttpExtra, v => v.txtExtraXhttp.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.GrpcMode, v => v.cmbHeaderTypeGrpc.Text).DisposeWith(disposables);