Skip to content

Commit ac08c91

Browse files
authored
uri: support ports and split queries/fragments from path (#825)
GCM currently ignores ports in URIs. This means attempting to authenticate to a URI with a port can lead to some unexpected behavior (e.g. shortcutting the provider auto-detect process won't work, even if the provider is set in config). This change adds support for ports by updating GetGitConfigurationScopes() to recognize them. This change also updates GetRemoteUri() to recognize paths with queries and fragments, as that issue was uncovered during the implementation of the GetGitConfigurationScopes() fix. Without it, input paths containing queries and/or fragments get saved as part of Uri.Path, which converts the query '?' and the fragment '#' to url encoding. I validated these changes with unit tests for applicable scenarios and by running a locally-compiled version of GCM with tracing enabled and the following config set: `credential.http://localhost:7990/bitbucket.provider bitbucket` Trace logs showed that the override was recognized and auto-detection was skipped.
2 parents 8b4735f + 62abc30 commit ac08c91

File tree

4 files changed

+53
-9
lines changed

4 files changed

+53
-9
lines changed

src/shared/Core.Tests/InputArgumentsTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,29 @@ public void InputArguments_GetRemoteUri_AuthorityPathUserInfo_ReturnsUriWithAuth
220220
Assert.Equal(expectedUri, actualUri);
221221
}
222222

223+
[Theory]
224+
[InlineData("foo?query=true")]
225+
[InlineData("foo#fragment")]
226+
[InlineData("foo?query=true#fragment")]
227+
public void InputArguments_GetRemoteUri_PathQueryFragment_ReturnsCorrectUri(string path)
228+
{
229+
var expectedUri = new Uri($"https://example.com/{path}");
230+
231+
var dict = new Dictionary<string, string>
232+
{
233+
["protocol"] = "https",
234+
["host"] = "example.com",
235+
["path"] = path
236+
};
237+
238+
var inputArgs = new InputArguments(dict);
239+
240+
Uri actualUri = inputArgs.GetRemoteUri();
241+
242+
Assert.NotNull(actualUri);
243+
Assert.Equal(expectedUri, actualUri);
244+
}
245+
223246
[Fact]
224247
public void InputArguments_GetRemoteUri_IncludeUser_AuthorityPathUserInfo_ReturnsUriWithAll()
225248
{

src/shared/Core.Tests/UriExtensionsTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@ public void UriExtensions_GetQueryParameters()
3030
[InlineData("http://hostname", "http://hostname")]
3131
[InlineData("http://example.com",
3232
"http://example.com")]
33+
[InlineData("http://hostname:7990", "http://hostname:7990")]
3334
[InlineData("http://foo.example.com",
3435
"http://foo.example.com", "http://example.com")]
3536
[InlineData("http://example.com/foo",
3637
"http://example.com/foo", "http://example.com")]
37-
[InlineData("http://example.com/foo/",
38-
"http://example.com/foo", "http://example.com")]
3938
[InlineData("http://example.com/foo?query=true#fragment",
4039
"http://example.com/foo", "http://example.com")]
4140
[InlineData("http://buzz.foo.example.com/bar/baz",

src/shared/Core/InputArguments.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
4+
using System.Linq;
45

56
namespace GitCredentialManager
67
{
@@ -94,10 +95,7 @@ public Uri GetRemoteUri(bool includeUser = false)
9495
string[] hostParts = Host.Split(':');
9596
if (hostParts.Length > 0)
9697
{
97-
var ub = new UriBuilder(Protocol, hostParts[0])
98-
{
99-
Path = Path
100-
};
98+
var ub = new UriBuilder(Protocol, hostParts[0]);
10199

102100
if (hostParts.Length > 1 && int.TryParse(hostParts[1], out int port))
103101
{
@@ -109,6 +107,28 @@ public Uri GetRemoteUri(bool includeUser = false)
109107
ub.UserName = Uri.EscapeDataString(UserName);
110108
}
111109

110+
if (Path != null)
111+
{
112+
string[] pathParts = Path.Split('?', '#');
113+
// We know the first piece is the path
114+
ub.Path = pathParts[0];
115+
116+
switch (pathParts.Length)
117+
{
118+
// If we have 3 items, that means path, query, and fragment
119+
case 3:
120+
ub.Query = pathParts[1];
121+
ub.Fragment = pathParts[2];
122+
break;
123+
// If we have 2 items, we must distinguish between query and fragment
124+
case 2 when Path.Contains('?'):
125+
ub.Query = pathParts[1];
126+
break;
127+
case 2 when Path.Contains('#'):
128+
ub.Fragment = pathParts[1];
129+
break;
130+
}
131+
}
112132
return ub.Uri;
113133
}
114134

src/shared/Core/UriExtensions.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,14 @@ public static IEnumerable<string> GetGitConfigurationScopes(this Uri uri)
8888

8989
string schemeAndDelim = $"{uri.Scheme}{Uri.SchemeDelimiter}";
9090
string host = uri.Host.TrimEnd('/');
91+
// If port is default, don't append
92+
string port = uri.IsDefaultPort ? "" : $":{uri.Port}";
9193
string path = uri.AbsolutePath.Trim('/');
9294

9395
// Unfold the path by component, right-to-left
9496
while (!string.IsNullOrWhiteSpace(path))
9597
{
96-
yield return $"{schemeAndDelim}{host}/{path}";
98+
yield return $"{schemeAndDelim}{host}{port}/{path}";
9799

98100
// Trim off the last path component
99101
if (!TryTrimString(path, StringExtensions.TruncateFromLastIndexOf, '/', out path))
@@ -107,7 +109,7 @@ public static IEnumerable<string> GetGitConfigurationScopes(this Uri uri)
107109
if (!string.IsNullOrWhiteSpace(host) &&
108110
!host.Contains("."))
109111
{
110-
yield return $"{schemeAndDelim}{host}";
112+
yield return $"{schemeAndDelim}{host}{port}";
111113
// If we have reached this point, there are no more subdomains to unfold, so exit early.
112114
yield break;
113115
}
@@ -117,7 +119,7 @@ public static IEnumerable<string> GetGitConfigurationScopes(this Uri uri)
117119
{
118120
if (host.Contains(".")) // Do not emit just the TLD
119121
{
120-
yield return $"{schemeAndDelim}{host}";
122+
yield return $"{schemeAndDelim}{host}{port}";
121123
}
122124

123125
// Trim off the left-most sub-domain

0 commit comments

Comments
 (0)