Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit d5ecebe

Browse files
authored
improve handling of proxy environmental variables (#28088)
* improve handling of proxy environmental variables * feedback from reviews * update port parsing * use Span instead of Substring to parse port * fix more feedback
1 parent f47a2c7 commit d5ecebe

File tree

2 files changed

+129
-12
lines changed

2 files changed

+129
-12
lines changed

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,26 @@ private static NetworkCredential GetCredentialsFromString(string value)
6565
{
6666
return null;
6767
}
68-
int idx = value.IndexOf(":");
69-
if (idx < 0)
68+
69+
value = Uri.UnescapeDataString(value);
70+
71+
string password = "";
72+
string domain = null;
73+
int idx = value.IndexOf(':');
74+
if (idx != -1)
7075
{
71-
// only user name without password
72-
return new NetworkCredential(value, "");
76+
password = value.Substring(idx+1);
77+
value = value.Substring(0, idx);
7378
}
74-
else
79+
80+
idx = value.IndexOf('\\');
81+
if (idx != -1)
7582
{
76-
return new NetworkCredential(value.Substring(0, idx), value.Substring(idx+1, value.Length - idx -1 ));
83+
domain = value.Substring(0, idx);
84+
value = value.Substring(idx+1);
7785
}
86+
87+
return new NetworkCredential(value, password, domain);
7888
}
7989
}
8090

@@ -168,18 +178,77 @@ private static Uri GetUriFromString(string value)
168178
{
169179
return null;
170180
}
181+
if (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
182+
{
183+
value = value.Substring(7);
184+
}
185+
186+
string user = null;
187+
string password = null;
188+
UInt16 port = 80;
189+
string host = null;
190+
191+
// Check if there is authentication part with user and possibly password.
192+
// Curl accepts raw text and that may break URI parser.
193+
int separatorIndex = value.LastIndexOf('@');
194+
if (separatorIndex != -1)
195+
{
196+
string auth = value.Substring(0, separatorIndex);
197+
198+
// The User and password may or may not be URL encoded.
199+
// Curl seems to accept both. To match that,
200+
// we do opportunistic decode and we use original string if it fails.
201+
try
202+
{
203+
auth = Uri.UnescapeDataString(auth);
204+
}
205+
catch { };
206+
207+
value = value.Substring(separatorIndex + 1);
208+
separatorIndex = auth.IndexOf(':');
209+
if (separatorIndex == -1)
210+
{
211+
user = auth;
212+
}
213+
else
214+
{
215+
user = auth.Substring(0, separatorIndex);
216+
password = auth.Substring(separatorIndex + 1);
217+
}
218+
}
171219

172-
if (!value.Contains("://"))
220+
int ipV6AddressEnd = value.IndexOf(']');
221+
separatorIndex = value.LastIndexOf(':');
222+
// No ':' or it is part of IPv6 address.
223+
if (separatorIndex == -1 || (ipV6AddressEnd != -1 && separatorIndex < ipV6AddressEnd))
173224
{
174-
value = "http://" + value;
225+
host = value;
226+
}
227+
else
228+
{
229+
host = value.Substring(0, separatorIndex);
230+
if (!UInt16.TryParse(value.AsSpan().Slice(separatorIndex + 1), out port))
231+
{
232+
return null;
233+
}
175234
}
176235

177-
if (Uri.TryCreate(value, UriKind.Absolute, out Uri uri) &&
178-
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
236+
try
179237
{
180-
// We only support http and https for now.
181-
return uri;
238+
UriBuilder ub = new UriBuilder("http", host, port);
239+
if (user != null)
240+
{
241+
ub.UserName = Uri.EscapeDataString(user);
242+
}
243+
244+
if (password != null)
245+
{
246+
ub.Password = Uri.EscapeDataString(password);
247+
}
248+
249+
return ub.Uri;
182250
}
251+
catch { };
183252
return null;
184253
}
185254

src/System.Net.Http/tests/UnitTests/HttpEnvironmentProxyTest.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,54 @@ public void HttpProxy_EnvironmentProxy_Loaded()
101101
}).Dispose();
102102
}
103103

104+
[Theory]
105+
[InlineData("1.1.1.5", "1.1.1.5", "80", null, null)]
106+
[InlineData("http://1.1.1.5:3005", "1.1.1.5", "3005", null, null)]
107+
[InlineData("http://foo@1.1.1.5", "1.1.1.5", "80", "foo", "")]
108+
[InlineData("http://[::1]:80", "[::1]", "80", null, null)]
109+
[InlineData("foo:bar@[::1]:3128", "[::1]", "3128", "foo", "bar")]
110+
[InlineData("foo:Pass$!#\\.$@127.0.0.1:3128", "127.0.0.1", "3128", "foo", "Pass$!#\\.$")]
111+
[InlineData("[::1]", "[::1]", "80", null, null)]
112+
[InlineData("domain\\foo:bar@1.1.1.1", "1.1.1.1", "80", "foo", "bar")]
113+
[InlineData("domain%5Cfoo:bar@1.1.1.1", "1.1.1.1", "80", "foo", "bar")]
114+
[InlineData("HTTP://ABC.COM/", "abc.com", "80", null, null)]
115+
public void HttpProxy_Uri_Parsing(string _input, string _host, string _port, string _user , string _password)
116+
{
117+
RemoteInvoke((input, host, port, user, password) =>
118+
{
119+
// Remote exec does not allow to pass null at this moment.
120+
if (user == "null")
121+
{
122+
user = null;
123+
}
124+
if (password == "null")
125+
{
126+
password = null;
127+
}
128+
129+
Environment.SetEnvironmentVariable("all_proxy", input);
130+
IWebProxy p;
131+
Uri u;
132+
133+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
134+
Assert.NotNull(p);
135+
136+
u = p.GetProxy(fooHttp);
137+
Assert.Equal(host, u.Host);
138+
Assert.Equal(Convert.ToInt32(port), u.Port);
139+
140+
if (user != null)
141+
{
142+
NetworkCredential nc = p.Credentials.GetCredential(u, "Basic");
143+
Assert.NotNull(nc);
144+
Assert.Equal(user, nc.UserName);
145+
Assert.Equal(password, nc.Password);
146+
}
147+
148+
return SuccessExitCode;
149+
}, _input, _host, _port, _user ?? "null" , _password ?? "null").Dispose();
150+
}
151+
104152
[Fact]
105153
public void HttpProxy_CredentialParsing_Basic()
106154
{

0 commit comments

Comments
 (0)