Encode URI credentials when rebuilding subscription authority

This commit is contained in:
hellcat 2026-10-23 12:21:25 +08:00
parent 8f7a40208f
commit af3efe5bcd
2 changed files with 28 additions and 1 deletions

View File

@ -78,7 +78,8 @@ final class Codec
throw new InvalidArgumentException('missing_auth');
}
// Rebuild only authority. Keep query bytes intact: do not decode the whole URI.
$authority = ($url['user'] ?? '').(isset($url['pass']) ? ':'.$url['pass'] : '');
// Percent-encode decoded userinfo once; raw brackets belong to credentials, not IPv6.
$authority = rawurlencode($user).(isset($url['pass']) ? ':'.rawurlencode($password) : '');
$authority = ($authority !== '' ? $authority.'@' : '').(str_contains($host, ':') ? '['.$host.']' : $host).':'.$port;
$connection = $authority.($url['path'] ?? '').(isset($url['query']) ? '?'.$url['query'] : '');
return self::node($type, ['host' => $host, 'port' => $port, 'user' => $user, 'password' => $password, 'query' => $query, 'connection' => $connection], rawurldecode($name));

View File

@ -41,6 +41,32 @@ final class CodecTest extends TestCase
self::assertSame('a+b@c#d', Codec::singbox($node, 'x')['password']);
}
public function testRawPasswordBracketsAreEscapedInPublishedUri(): void
{
$node = Codec::parse('trojan://pass[word]@example.invalid:443?security=tls#test');
$uri = Codec::uri($node, 'test');
self::assertStringStartsWith('trojan://pass%5Bword%5D@example.invalid:443?', $uri);
self::assertSame('pass[word]', Codec::singbox($node, 'test')['password']);
self::assertSame($node['id'], Codec::parse($uri)['id']);
}
public function testEscapedUserinfoKeepsExactDecodedPassword(): void
{
$password = 'a[b]+c@d#e:f%2F';
$node = Codec::parse('trojan://'.rawurlencode($password).'@example.invalid:443?security=tls#test');
$uri = Codec::uri($node, 'test');
self::assertStringStartsWith('trojan://'.rawurlencode($password).'@', $uri);
self::assertSame($password, Codec::singbox(Codec::parse($uri), 'test')['password']);
self::assertSame($node['id'], Codec::parse($uri)['id']);
}
public function testPasswordBracketsDoNotChangeIpv6Host(): void
{
$node = Codec::parse('trojan://a[b]@[2001:db8::1]:443?security=tls#test');
self::assertStringStartsWith('trojan://a%5Bb%5D@[2001:db8::1]:443?', Codec::uri($node, 'test'));
self::assertSame('2001:db8::1', Codec::singbox($node, 'test')['server']);
}
public function testSsBase64UrlAndPlugin(): void
{
$auth = rtrim(strtr(base64_encode('aes-128-gcm:a+b'), '+/', '-_'), '=');