Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 150bb3f

Browse files
authored
Add BindingAddress to HttpAbstractions (#977)
1 parent d104129 commit 150bb3f

File tree

3 files changed

+226
-0
lines changed

3 files changed

+226
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Globalization;
7+
8+
namespace Microsoft.AspNetCore.Http.Internal
9+
{
10+
public class BindingAddress
11+
{
12+
public string Host { get; private set; }
13+
public string PathBase { get; private set; }
14+
public int Port { get; internal set; }
15+
public string Scheme { get; private set; }
16+
17+
public bool IsUnixPipe
18+
{
19+
get
20+
{
21+
return Host.StartsWith(Constants.UnixPipeHostPrefix, StringComparison.Ordinal);
22+
}
23+
}
24+
25+
public string UnixPipePath
26+
{
27+
get
28+
{
29+
if (!IsUnixPipe)
30+
{
31+
throw new InvalidOperationException("Binding address is not a unix pipe.");
32+
}
33+
34+
return Host.Substring(Constants.UnixPipeHostPrefix.Length - 1);
35+
}
36+
}
37+
38+
public override string ToString()
39+
{
40+
if (IsUnixPipe)
41+
{
42+
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant();
43+
}
44+
else
45+
{
46+
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase.ToString(CultureInfo.InvariantCulture);
47+
}
48+
}
49+
50+
public override int GetHashCode()
51+
{
52+
return ToString().GetHashCode();
53+
}
54+
55+
public override bool Equals(object obj)
56+
{
57+
var other = obj as BindingAddress;
58+
if (other == null)
59+
{
60+
return false;
61+
}
62+
return string.Equals(Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase)
63+
&& string.Equals(Host, other.Host, StringComparison.OrdinalIgnoreCase)
64+
&& Port == other.Port
65+
&& PathBase == other.PathBase;
66+
}
67+
68+
public static BindingAddress Parse(string address)
69+
{
70+
address = address ?? string.Empty;
71+
72+
int schemeDelimiterStart = address.IndexOf("://", StringComparison.Ordinal);
73+
if (schemeDelimiterStart < 0)
74+
{
75+
throw new FormatException($"Invalid url: '{address}'");
76+
}
77+
int schemeDelimiterEnd = schemeDelimiterStart + "://".Length;
78+
79+
var isUnixPipe = address.IndexOf(Constants.UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
80+
81+
int pathDelimiterStart;
82+
int pathDelimiterEnd;
83+
if (!isUnixPipe)
84+
{
85+
pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
86+
pathDelimiterEnd = pathDelimiterStart;
87+
}
88+
else
89+
{
90+
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + Constants.UnixPipeHostPrefix.Length, StringComparison.Ordinal);
91+
pathDelimiterEnd = pathDelimiterStart + ":".Length;
92+
}
93+
94+
if (pathDelimiterStart < 0)
95+
{
96+
pathDelimiterStart = pathDelimiterEnd = address.Length;
97+
}
98+
99+
var serverAddress = new BindingAddress();
100+
serverAddress.Scheme = address.Substring(0, schemeDelimiterStart);
101+
102+
var hasSpecifiedPort = false;
103+
if (!isUnixPipe)
104+
{
105+
int portDelimiterStart = address.LastIndexOf(":", pathDelimiterStart - 1, pathDelimiterStart - schemeDelimiterEnd, StringComparison.Ordinal);
106+
if (portDelimiterStart >= 0)
107+
{
108+
int portDelimiterEnd = portDelimiterStart + ":".Length;
109+
110+
string portString = address.Substring(portDelimiterEnd, pathDelimiterStart - portDelimiterEnd);
111+
int portNumber;
112+
if (int.TryParse(portString, NumberStyles.Integer, CultureInfo.InvariantCulture, out portNumber))
113+
{
114+
hasSpecifiedPort = true;
115+
serverAddress.Host = address.Substring(schemeDelimiterEnd, portDelimiterStart - schemeDelimiterEnd);
116+
serverAddress.Port = portNumber;
117+
}
118+
}
119+
120+
if (!hasSpecifiedPort)
121+
{
122+
if (string.Equals(serverAddress.Scheme, "http", StringComparison.OrdinalIgnoreCase))
123+
{
124+
serverAddress.Port = 80;
125+
}
126+
else if (string.Equals(serverAddress.Scheme, "https", StringComparison.OrdinalIgnoreCase))
127+
{
128+
serverAddress.Port = 443;
129+
}
130+
}
131+
}
132+
133+
if (!hasSpecifiedPort)
134+
{
135+
serverAddress.Host = address.Substring(schemeDelimiterEnd, pathDelimiterStart - schemeDelimiterEnd);
136+
}
137+
138+
if (string.IsNullOrEmpty(serverAddress.Host))
139+
{
140+
throw new FormatException($"Invalid url: '{address}'");
141+
}
142+
143+
if (address[address.Length - 1] == '/')
144+
{
145+
serverAddress.PathBase = address.Substring(pathDelimiterEnd, address.Length - pathDelimiterEnd - 1);
146+
}
147+
else
148+
{
149+
serverAddress.PathBase = address.Substring(pathDelimiterEnd);
150+
}
151+
152+
return serverAddress;
153+
}
154+
}
155+
}

src/Microsoft.AspNetCore.Http/Internal/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ internal static class Constants
77
{
88
internal const string Http = "http";
99
internal const string Https = "https";
10+
internal const string UnixPipeHostPrefix = "unix:/";
1011

1112
internal static class BuilderProperties
1213
{
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
using Xunit;
8+
namespace Microsoft.AspNetCore.Http.Internal.Tests
9+
{
10+
public class BindingAddressTests
11+
{
12+
[Theory]
13+
[InlineData("")]
14+
[InlineData("5000")]
15+
[InlineData("//noscheme")]
16+
public void FromUriThrowsForUrlsWithoutSchemeDelimiter(string url)
17+
{
18+
Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
19+
}
20+
21+
[Theory]
22+
[InlineData("://")]
23+
[InlineData("://:5000")]
24+
[InlineData("http://")]
25+
[InlineData("http://:5000")]
26+
[InlineData("http:///")]
27+
[InlineData("http:///:5000")]
28+
[InlineData("http:////")]
29+
[InlineData("http:////:5000")]
30+
public void FromUriThrowsForUrlsWithoutHost(string url)
31+
{
32+
Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
33+
}
34+
35+
[Theory]
36+
[InlineData("://emptyscheme", "", "emptyscheme", 0, "", "://emptyscheme:0")]
37+
[InlineData("http://+", "http", "+", 80, "", "http://+:80")]
38+
[InlineData("http://*", "http", "*", 80, "", "http://*:80")]
39+
[InlineData("http://localhost", "http", "localhost", 80, "", "http://localhost:80")]
40+
[InlineData("http://www.example.com", "http", "www.example.com", 80, "", "http://www.example.com:80")]
41+
[InlineData("https://www.example.com", "https", "www.example.com", 443, "", "https://www.example.com:443")]
42+
[InlineData("http://www.example.com/", "http", "www.example.com", 80, "", "http://www.example.com:80")]
43+
[InlineData("http://www.example.com/foo?bar=baz", "http", "www.example.com", 80, "/foo?bar=baz", "http://www.example.com:80/foo?bar=baz")]
44+
[InlineData("http://www.example.com:5000", "http", "www.example.com", 5000, "", null)]
45+
[InlineData("https://www.example.com:5000", "https", "www.example.com", 5000, "", null)]
46+
[InlineData("http://www.example.com:5000/", "http", "www.example.com", 5000, "", "http://www.example.com:5000")]
47+
[InlineData("http://www.example.com:NOTAPORT", "http", "www.example.com:NOTAPORT", 80, "", "http://www.example.com:notaport:80")]
48+
[InlineData("https://www.example.com:NOTAPORT", "https", "www.example.com:NOTAPORT", 443, "", "https://www.example.com:notaport:443")]
49+
[InlineData("http://www.example.com:NOTAPORT/", "http", "www.example.com:NOTAPORT", 80, "", "http://www.example.com:notaport:80")]
50+
[InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/tmp/kestrel-test.sock:5000/doesn't/matter", "http://foo::80/tmp/kestrel-test.sock:5000/doesn't/matter")]
51+
[InlineData("http://unix:foo/tmp/kestrel-test.sock", "http", "unix:foo", 80, "/tmp/kestrel-test.sock", "http://unix:foo:80/tmp/kestrel-test.sock")]
52+
[InlineData("http://unix:5000/tmp/kestrel-test.sock", "http", "unix", 5000, "/tmp/kestrel-test.sock", "http://unix:5000/tmp/kestrel-test.sock")]
53+
[InlineData("http://unix:/tmp/kestrel-test.sock", "http", "unix:/tmp/kestrel-test.sock", 0, "", null)]
54+
[InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "", null)]
55+
[InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
56+
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
57+
[InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter", "http://unix:/tmp/kestrel-test.sock")]
58+
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
59+
{
60+
var serverAddress = BindingAddress.Parse(url);
61+
62+
Assert.Equal(scheme, serverAddress.Scheme);
63+
Assert.Equal(host, serverAddress.Host);
64+
Assert.Equal(port, serverAddress.Port);
65+
Assert.Equal(pathBase, serverAddress.PathBase);
66+
67+
Assert.Equal(toString ?? url, serverAddress.ToString());
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)