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

Commit 6087666

Browse files
hughbestephentoub
authored andcommitted
Fix no exception thrown listening to an already registered prefix
1 parent 9a20b64 commit 6087666

File tree

3 files changed

+286
-14
lines changed

3 files changed

+286
-14
lines changed

src/System.Net.HttpListener/src/System/Net/Managed/HttpEndPointListener.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,7 @@ public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
343343
prefs = _prefixes;
344344
if (prefs.ContainsKey(prefix))
345345
{
346-
HttpListener other = (HttpListener)prefs[prefix];
347-
if (other != listener)
348-
throw new HttpListenerException((int)HttpStatusCode.BadRequest, SR.Format(SR.net_listener_already, prefix));
349-
return;
346+
throw new HttpListenerException((int)HttpStatusCode.BadRequest, SR.Format(SR.net_listener_already, prefix));
350347
}
351348
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
352349
p2[prefix] = listener;

src/System.Net.HttpListener/tests/HttpListenerFactory.cs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,23 @@ internal class HttpListenerFactory : IDisposable
1515
private readonly HttpListener _processPrefixListener;
1616
private readonly Exception _processPrefixException;
1717
private readonly string _processPrefix;
18-
public const string Hostname = "localhost";
18+
private readonly string _hostname;
1919
private readonly string _path;
2020
private readonly int _port;
2121

22-
internal HttpListenerFactory()
22+
internal HttpListenerFactory(string hostname = "localhost")
2323
{
2424
// Find a URL prefix that is not in use on this machine *and* uses a port that's not in use.
2525
// Once we find this prefix, keep a listener on it for the duration of the process, so other processes
2626
// can't steal it.
2727

2828
Guid processGuid = Guid.NewGuid();
29+
_hostname = hostname;
2930
_path = processGuid.ToString("N");
3031

31-
for (int port = 1024; port <= IPEndPoint.MaxPort; port++)
32+
for (int port = 1025; port <= IPEndPoint.MaxPort; port++)
3233
{
33-
string prefix = $"http://{Hostname}:{port}/{_path}/";
34+
string prefix = $"http://{hostname}:{port}/{_path}/";
3435

3536
var listener = new HttpListener();
3637
try
@@ -55,6 +56,17 @@ internal HttpListenerFactory()
5556
// in trying again.
5657
if (!(e is HttpListenerException) && !(e is SocketException))
5758
break;
59+
60+
// If we can't access the host (e.g. if it is '+' or '*' and the current user is the administrator)
61+
// then throw.
62+
if (e is HttpListenerException listenerException)
63+
{
64+
const int ERROR_ACCESS_DENIED = 5;
65+
if (listenerException.ErrorCode == ERROR_ACCESS_DENIED && (hostname == "*" || hostname == "+"))
66+
{
67+
throw new InvalidOperationException($"Access denied for host {hostname}");
68+
}
69+
}
5870
}
5971
}
6072

@@ -89,8 +101,31 @@ public string ListeningUrl
89101
}
90102
}
91103

104+
public string Hostname => _hostname;
92105
public string Path => _path;
93106

107+
private static bool? s_supportsWildcards;
108+
public static bool SupportsWildcards
109+
{
110+
get
111+
{
112+
if (!s_supportsWildcards.HasValue)
113+
{
114+
try
115+
{
116+
new HttpListenerFactory("*");
117+
s_supportsWildcards = true;
118+
}
119+
catch (InvalidOperationException)
120+
{
121+
s_supportsWildcards = false;
122+
}
123+
}
124+
125+
return s_supportsWildcards.Value;
126+
}
127+
}
128+
94129
public HttpListener GetListener() => _processPrefixListener;
95130

96131
public void Dispose() => _processPrefixListener.Close();

src/System.Net.HttpListener/tests/HttpListenerPrefixCollectionTests.cs

Lines changed: 246 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,11 @@ public void Add_AlreadyStarted_ReturnsExpected()
210210
}
211211
}
212212

213-
[Fact]
214-
public void Add_PrefixAlreadyRegisteredAndNotStarted_ThrowsHttpListenerException()
213+
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
214+
[MemberData(nameof(Hosts_TestData))]
215+
public void Add_PrefixAlreadyRegisteredAndNotStarted_ThrowsHttpListenerException(string hostname)
215216
{
216-
using (var factory = new HttpListenerFactory())
217+
using (var factory = new HttpListenerFactory(hostname))
217218
{
218219
string uriPrefix = Assert.Single(factory.GetListener().Prefixes);
219220

@@ -224,10 +225,28 @@ public void Add_PrefixAlreadyRegisteredAndNotStarted_ThrowsHttpListenerException
224225
}
225226
}
226227

227-
[Fact]
228-
public void Add_PrefixAlreadyRegisteredAndStarted_ThrowsHttpListenerException()
228+
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
229+
[MemberData(nameof(Hosts_TestData))]
230+
public void Add_PrefixAlreadyRegisteredWithDifferentPathAndNotStarted_Works(string hostname)
229231
{
230-
using (var factory = new HttpListenerFactory())
232+
using (var factory = new HttpListenerFactory(hostname))
233+
{
234+
var listener = factory.GetListener();
235+
string uriPrefix = Assert.Single(listener.Prefixes);
236+
237+
listener.Prefixes.Add(uriPrefix + "sub_path/");
238+
Assert.Equal(2, listener.Prefixes.Count);
239+
240+
listener.Start();
241+
Assert.True(listener.IsListening);
242+
}
243+
}
244+
245+
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
246+
[MemberData(nameof(Hosts_TestData))]
247+
public void Add_PrefixAlreadyRegisteredAndStarted_ThrowsHttpListenerException(string hostname)
248+
{
249+
using (var factory = new HttpListenerFactory(hostname))
231250
{
232251
HttpListener listener = factory.GetListener();
233252
string uriPrefix = Assert.Single(listener.Prefixes);
@@ -237,6 +256,227 @@ public void Add_PrefixAlreadyRegisteredAndStarted_ThrowsHttpListenerException()
237256
}
238257
}
239258

259+
public static IEnumerable<object[]> Hosts_TestData()
260+
{
261+
yield return new object[] { "localhost" };
262+
yield return new object[] { "127.0.0.1" };
263+
264+
if (HttpListenerFactory.SupportsWildcards)
265+
{
266+
yield return new object[] { "*" };
267+
yield return new object[] { "+" };
268+
}
269+
}
270+
271+
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
272+
[MemberData(nameof(Hosts_TestData))]
273+
public void Add_SamePortDifferentPathDifferentListenerNotStarted_Works(string host)
274+
{
275+
var listener1 = new HttpListener();
276+
var listener2 = new HttpListener();
277+
278+
int? freePort = null;
279+
280+
// Try to find a port that is not being used.
281+
for (int port = 1025; port <= IPEndPoint.MaxPort; port++)
282+
{
283+
string uriPrefix = $"http://{host}:{port}/";
284+
try
285+
{
286+
listener1.Prefixes.Add(uriPrefix);
287+
Assert.Equal(1, listener1.Prefixes.Count);
288+
listener1.Start();
289+
290+
freePort = port;
291+
break;
292+
}
293+
catch (HttpListenerException)
294+
{
295+
// This port is already in use. Skip it and find a port that's not being used.
296+
listener1.Close();
297+
listener1 = new HttpListener();
298+
}
299+
}
300+
301+
try
302+
{
303+
if (!freePort.HasValue)
304+
{
305+
throw new InvalidOperationException("Expected to have a port open on the machine.");
306+
}
307+
308+
listener2.Prefixes.Add($"http://{host}:{freePort}/sub_path/");
309+
Assert.Equal(1, listener2.Prefixes.Count);
310+
311+
listener2.Start();
312+
Assert.True(listener2.IsListening);
313+
}
314+
finally
315+
{
316+
listener1?.Close();
317+
listener2?.Close();
318+
}
319+
}
320+
321+
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
322+
[MemberData(nameof(Hosts_TestData))]
323+
public void Add_SamePortDifferentPathDifferentListenerStarted_Works(string host)
324+
{
325+
var listener1 = new HttpListener();
326+
var listener2 = new HttpListener();
327+
328+
int? freePort1 = null;
329+
int? freePort2 = null;
330+
331+
// Try to find a port that is not being used.
332+
for (int port = 1025; port <= IPEndPoint.MaxPort; port++)
333+
{
334+
string uriPrefix = $"http://127.0.0.1:{port}/";
335+
try
336+
{
337+
if (!freePort1.HasValue)
338+
{
339+
listener1.Prefixes.Add(uriPrefix);
340+
Assert.Equal(1, listener1.Prefixes.Count);
341+
342+
listener1.Start();
343+
freePort1 = port;
344+
}
345+
else if (!freePort2.HasValue)
346+
{
347+
listener2.Prefixes.Add(uriPrefix);
348+
Assert.Equal(1, listener2.Prefixes.Count);
349+
350+
listener2.Start();
351+
freePort2 = port;
352+
353+
break;
354+
}
355+
}
356+
catch (HttpListenerException)
357+
{
358+
// This port is already in use. Skip it and find a port that's not being used.
359+
if (!freePort1.HasValue)
360+
{
361+
listener1.Close();
362+
listener1 = new HttpListener();
363+
}
364+
else if (!freePort2.HasValue)
365+
{
366+
listener2.Close();
367+
listener2 = new HttpListener();
368+
}
369+
}
370+
}
371+
372+
try
373+
{
374+
if (!freePort1.HasValue || !freePort2.HasValue)
375+
{
376+
throw new InvalidOperationException("Expected to have a port open on the machine.");
377+
}
378+
379+
listener1.Prefixes.Add($"http://127.0.0.1:{freePort2}/hola/");
380+
listener2.Prefixes.Add($"http://127.0.0.1:{freePort1}/hola/");
381+
382+
Assert.Equal(2, listener1.Prefixes.Count);
383+
Assert.Equal(2, listener2.Prefixes.Count);
384+
385+
// Conflicts with existing registration: listener2 has registered to listen to http://127.0.0.1:{freePort1}/...
386+
Assert.Throws<HttpListenerException>(() => listener1.Prefixes.Add($"http://127.0.0.1:{freePort1}/"));
387+
Assert.Throws<HttpListenerException>(() => listener1.Prefixes.Add($"http://127.0.0.1:{freePort1}/hola/"));
388+
389+
// Conflicts with existing registration: listener1 has registered to listen to http://127.0.0.1:{freePort2}/...
390+
Assert.Throws<HttpListenerException>(() => listener2.Prefixes.Add($"http://127.0.0.1:{freePort2}/"));
391+
Assert.Throws<HttpListenerException>(() => listener2.Prefixes.Add($"http://127.0.0.1:{freePort2}/hola/"));
392+
}
393+
finally
394+
{
395+
listener1?.Close();
396+
listener2?.Close();
397+
}
398+
}
399+
400+
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
401+
[MemberData(nameof(Hosts_TestData))]
402+
public void Add_SamePortDifferentPathMultipleStarted_Success(string host)
403+
{
404+
var listener1 = new HttpListener();
405+
var listener2 = new HttpListener();
406+
407+
int? freePort1 = null;
408+
int? freePort2 = null;
409+
410+
// Try to find a port that is not being used.
411+
for (int port = 1025; port <= IPEndPoint.MaxPort; port++)
412+
{
413+
string uriPrefix = $"http://{host}:{port}/";
414+
try
415+
{
416+
if (!freePort1.HasValue)
417+
{
418+
listener1.Prefixes.Add(uriPrefix);
419+
Assert.Equal(1, listener1.Prefixes.Count);
420+
421+
listener1.Start();
422+
freePort1 = port;
423+
}
424+
else if (!freePort2.HasValue)
425+
{
426+
listener2.Prefixes.Add(uriPrefix);
427+
Assert.Equal(1, listener2.Prefixes.Count);
428+
429+
listener2.Start();
430+
freePort2 = port;
431+
432+
break;
433+
}
434+
}
435+
catch (HttpListenerException)
436+
{
437+
// This port is already in use. Skip it and find a port that's not being used.
438+
if (!freePort1.HasValue)
439+
{
440+
listener1.Close();
441+
listener1 = new HttpListener();
442+
}
443+
else if (!freePort2.HasValue)
444+
{
445+
listener2.Close();
446+
listener2 = new HttpListener();
447+
}
448+
}
449+
}
450+
451+
try
452+
{
453+
if (!freePort1.HasValue || !freePort2.HasValue)
454+
{
455+
throw new InvalidOperationException("Expected to have a port open on the machine.");
456+
}
457+
458+
listener1.Prefixes.Add($"http://{host}:{freePort1}/hola/");
459+
Assert.Equal(2, listener1.Prefixes.Count);
460+
461+
listener2.Prefixes.Add($"http://{host}:{freePort2}/hola/");
462+
Assert.Equal(2, listener2.Prefixes.Count);
463+
464+
// Conflict: listenerX is already listening to $"http://127.0.0.1:{freePortX}/hola/".
465+
Assert.Throws<HttpListenerException>(() => listener1.Prefixes.Add($"http://{host}:{freePort1}/hola/"));
466+
Assert.Throws<HttpListenerException>(() => listener2.Prefixes.Add($"http://{host}:{freePort2}/hola/"));
467+
468+
// Conflict: listenerX is already listening to $"http://127.0.0.1:{freePortY}/hola/".
469+
Assert.Throws<HttpListenerException>(() => listener1.Prefixes.Add($"http://{host}:{freePort2}/hola/"));
470+
Assert.Throws<HttpListenerException>(() => listener2.Prefixes.Add($"http://{host}:{freePort1}/hola/"));
471+
472+
}
473+
finally
474+
{
475+
listener1?.Close();
476+
listener2?.Close();
477+
}
478+
}
479+
240480
public static IEnumerable<object[]> InvalidPrefix_TestData()
241481
{
242482
// [ActiveIssue(19593, TestPlatforms.OSX)]

0 commit comments

Comments
 (0)