source: TFP-WebServer/SpaceWizards.HttpListener/src/System/Net/HttpListener.cs

Last change on this file was 377, checked in by alloc, 2 years ago

Made SpaceWizards.HttpListener compilable against .NET 4.8 with preprocessor define UNITY_NETFRAMEWORK

File size: 9.7 KB
Line 
1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3
4// ReSharper disable RedundantUsingDirective
5// ReSharper disable RedundantUnsafeContext
6
7using System;
8using System.Collections;
9using System.Diagnostics.CodeAnalysis;
10using System.Net;
11using System.Security.Authentication.ExtendedProtection;
12using System.Text;
13using System.Threading.Tasks;
14
15namespace SpaceWizards.HttpListener
16{
17 public sealed unsafe partial class HttpListener : IDisposable
18 {
19 public delegate ExtendedProtectionPolicy ExtendedProtectionSelector(HttpListenerRequest request);
20
21 private readonly object _internalLock;
22 private volatile State _state; // _state is set only within lock blocks, but often read outside locks.
23 private readonly HttpListenerPrefixCollection _prefixes;
24 internal Hashtable _uriPrefixes = new Hashtable();
25 private bool _ignoreWriteExceptions;
26 private readonly ServiceNameStore _defaultServiceNames;
27 private readonly HttpListenerTimeoutManager _timeoutManager;
28 private ExtendedProtectionPolicy _extendedProtectionPolicy;
29 private AuthenticationSchemeSelector? _authenticationDelegate;
30 private AuthenticationSchemes _authenticationScheme = AuthenticationSchemes.Anonymous;
31 private ExtendedProtectionSelector? _extendedProtectionSelectorDelegate;
32 private string? _realm;
33
34 internal ICollection PrefixCollection => _uriPrefixes.Keys;
35
36 public HttpListener()
37 {
38 _state = State.Stopped;
39 _internalLock = new object();
40 _defaultServiceNames = new ServiceNameStore();
41
42 _timeoutManager = new HttpListenerTimeoutManager(this);
43 _prefixes = new HttpListenerPrefixCollection(this);
44
45 // default: no CBT checks on any platform (appcompat reasons); applies also to PolicyEnforcement
46 // config element
47 _extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
48 }
49
50 public AuthenticationSchemeSelector? AuthenticationSchemeSelectorDelegate
51 {
52 get => _authenticationDelegate;
53 set
54 {
55 CheckDisposed();
56 _authenticationDelegate = value;
57 }
58 }
59
60#if !UNITY_NETFRAMEWORK
61 [DisallowNull]
62#endif
63 public ExtendedProtectionSelector? ExtendedProtectionSelectorDelegate
64 {
65 get => _extendedProtectionSelectorDelegate;
66 set
67 {
68 CheckDisposed();
69 if (value == null)
70 {
71 throw new ArgumentNullException(nameof(value));
72 }
73
74 _extendedProtectionSelectorDelegate = value;
75 }
76 }
77
78 public AuthenticationSchemes AuthenticationSchemes
79 {
80 get => _authenticationScheme;
81 set
82 {
83 CheckDisposed();
84 _authenticationScheme = value;
85 }
86 }
87
88 public ExtendedProtectionPolicy ExtendedProtectionPolicy
89 {
90 get => _extendedProtectionPolicy;
91 set
92 {
93 CheckDisposed();
94 if (value == null)
95 {
96 throw new ArgumentNullException(nameof(value));
97 }
98 if (value.CustomChannelBinding != null)
99 {
100 throw new ArgumentException(SR.net_listener_cannot_set_custom_cbt, nameof(value));
101 }
102
103 _extendedProtectionPolicy = value;
104 }
105 }
106
107 public ServiceNameCollection DefaultServiceNames => _defaultServiceNames.ServiceNames;
108
109 public HttpListenerPrefixCollection Prefixes
110 {
111 get
112 {
113 CheckDisposed();
114 return _prefixes;
115 }
116 }
117
118 internal void AddPrefix(string uriPrefix)
119 {
120 string? registeredPrefix = null;
121 try
122 {
123 if (uriPrefix == null)
124 {
125 throw new ArgumentNullException(nameof(uriPrefix));
126 }
127 CheckDisposed();
128 int i;
129 if (string.Compare(uriPrefix, 0, "http://", 0, 7, StringComparison.OrdinalIgnoreCase) == 0)
130 {
131 i = 7;
132 }
133 else if (string.Compare(uriPrefix, 0, "https://", 0, 8, StringComparison.OrdinalIgnoreCase) == 0)
134 {
135 i = 8;
136 }
137 else
138 {
139 throw new ArgumentException(SR.net_listener_scheme, nameof(uriPrefix));
140 }
141
142 int j = ServiceNameStore.FindEndOfHostname(uriPrefix, i);
143 if (i == j)
144 {
145 throw new ArgumentException(SR.net_listener_host, nameof(uriPrefix));
146 }
147 if (uriPrefix[uriPrefix.Length - 1] != '/')
148 {
149 throw new ArgumentException(SR.net_listener_slash, nameof(uriPrefix));
150 }
151 StringBuilder registeredPrefixBuilder = new StringBuilder();
152 if (uriPrefix[j] == ':')
153 {
154 registeredPrefixBuilder.Append(uriPrefix);
155 }
156 else
157 {
158 registeredPrefixBuilder.Append(uriPrefix, 0, j);
159 registeredPrefixBuilder.Append(i == 7 ? ":80" : ":443");
160 registeredPrefixBuilder.Append(uriPrefix, j, uriPrefix.Length - j);
161 }
162 for (i = 0; registeredPrefixBuilder[i] != ':'; i++)
163 {
164 registeredPrefixBuilder[i] = (char)CaseInsensitiveAscii.AsciiToLower[(byte)registeredPrefixBuilder[i]];
165 }
166 registeredPrefix = registeredPrefixBuilder.ToString();
167 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"mapped uriPrefix: {uriPrefix} to registeredPrefix: {registeredPrefix}");
168 if (_state == State.Started)
169 {
170 AddPrefixCore(registeredPrefix);
171 }
172 _uriPrefixes[uriPrefix] = registeredPrefix;
173 _defaultServiceNames.Add(uriPrefix);
174 }
175 catch (Exception exception)
176 {
177 if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, exception);
178 throw;
179 }
180 }
181
182 internal bool ContainsPrefix(string uriPrefix) => _uriPrefixes.Contains(uriPrefix);
183
184 internal bool RemovePrefix(string uriPrefix)
185 {
186 try
187 {
188 CheckDisposed();
189 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"uriPrefix: {uriPrefix}");
190 if (uriPrefix == null)
191 {
192 throw new ArgumentNullException(nameof(uriPrefix));
193 }
194
195 if (!_uriPrefixes.Contains(uriPrefix))
196 {
197 return false;
198 }
199
200 if (_state == State.Started)
201 {
202 RemovePrefixCore((string)_uriPrefixes[uriPrefix]!);
203 }
204
205 _uriPrefixes.Remove(uriPrefix);
206 _defaultServiceNames.Remove(uriPrefix);
207 }
208 catch (Exception exception)
209 {
210 if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, exception);
211 throw;
212 }
213 return true;
214 }
215
216 internal void RemoveAll(bool clear)
217 {
218 CheckDisposed();
219 // go through the uri list and unregister for each one of them
220 if (_uriPrefixes.Count > 0)
221 {
222 if (_state == State.Started)
223 {
224 foreach (string registeredPrefix in _uriPrefixes.Values)
225 {
226 RemovePrefixCore(registeredPrefix);
227 }
228 }
229
230 if (clear)
231 {
232 _uriPrefixes.Clear();
233 _defaultServiceNames.Clear();
234 }
235 }
236 }
237
238 public string? Realm
239 {
240 get => _realm;
241 set
242 {
243 CheckDisposed();
244 _realm = value;
245 }
246 }
247
248 public bool IsListening => _state == State.Started;
249
250 public bool IgnoreWriteExceptions
251 {
252 get => _ignoreWriteExceptions;
253 set
254 {
255 CheckDisposed();
256 _ignoreWriteExceptions = value;
257 }
258 }
259
260 public Task<HttpListenerContext> GetContextAsync()
261 {
262 return Task.Factory.FromAsync(
263 (callback, state) => ((HttpListener)state!).BeginGetContext(callback, state),
264 iar => ((HttpListener)iar!.AsyncState!).EndGetContext(iar),
265 this);
266 }
267
268 public void Close()
269 {
270 try
271 {
272 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info("HttpListenerRequest::Close()");
273 ((IDisposable)this).Dispose();
274 }
275 catch (Exception exception)
276 {
277 if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Close {exception}");
278 throw;
279 }
280 }
281
282 internal void CheckDisposed()
283 {
284 if (_state == State.Closed)
285 {
286 throw new ObjectDisposedException(GetType().FullName);
287 }
288 }
289
290 private enum State
291 {
292 Stopped,
293 Started,
294 Closed,
295 }
296
297 void IDisposable.Dispose() => Dispose();
298 }
299}
Note: See TracBrowser for help on using the repository browser.