source: TFP-WebServer/SpaceWizards.HttpListener/src/System/Net/Managed/HttpListenerResponse.Managed.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: 12.5 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//
5// System.Net.HttpListenerResponse
6//
7// Author:
8// Gonzalo Paniagua Javier (gonzalo@novell.com)
9//
10// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
11//
12// Permission is hereby granted, free of charge, to any person obtaining
13// a copy of this software and associated documentation files (the
14// "Software"), to deal in the Software without restriction, including
15// without limitation the rights to use, copy, modify, merge, publish,
16// distribute, sublicense, and/or sell copies of the Software, and to
17// permit persons to whom the Software is furnished to do so, subject to
18// the following conditions:
19//
20// The above copyright notice and this permission notice shall be
21// included in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30//
31
32// ReSharper disable RedundantExtendsListEntry
33
34using System;
35using System.Globalization;
36using System.IO;
37using System.Net;
38using System.Text;
39
40namespace SpaceWizards.HttpListener
41{
42 public sealed partial class HttpListenerResponse : IDisposable
43 {
44 private long _contentLength;
45 private Version _version = HttpVersion.Version11;
46 private int _statusCode = 200;
47 internal object _headersLock = new object();
48 private bool _forceCloseChunked;
49
50 internal HttpListenerResponse(HttpListenerContext context)
51 {
52 _httpContext = context;
53 }
54
55 internal bool ForceCloseChunked => _forceCloseChunked;
56
57 private void EnsureResponseStream()
58 {
59 if (_responseStream == null)
60 {
61 _responseStream = _httpContext!.Connection.GetResponseStream();
62 }
63 }
64
65 public Version ProtocolVersion
66 {
67 get => _version;
68 set
69 {
70 CheckDisposed();
71 if (value == null)
72 {
73 throw new ArgumentNullException(nameof(value));
74 }
75 if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
76 {
77 throw new ArgumentException(SR.net_wrongversion, nameof(value));
78 }
79
80 _version = new Version(value.Major, value.Minor); // match Windows behavior, trimming to just Major.Minor
81 }
82 }
83
84 public int StatusCode
85 {
86 get => _statusCode;
87 set
88 {
89 CheckDisposed();
90
91 if (value < 100 || value > 999)
92 throw new ProtocolViolationException(SR.net_invalidstatus);
93
94 _statusCode = value;
95 }
96 }
97
98 private void Dispose() => Close(true);
99
100 public void Close()
101 {
102 if (Disposed)
103 return;
104
105 Close(false);
106 }
107
108 public void Abort()
109 {
110 if (Disposed)
111 return;
112
113 Close(true);
114 }
115
116 private void Close(bool force)
117 {
118 Disposed = true;
119 _httpContext!.Connection.Close(force);
120 }
121
122 public void Close(byte[] responseEntity, bool willBlock)
123 {
124 CheckDisposed();
125
126 if (responseEntity == null)
127 {
128 throw new ArgumentNullException(nameof(responseEntity));
129 }
130
131 if (!SentHeaders && _boundaryType != BoundaryType.Chunked)
132 {
133 ContentLength64 = responseEntity.Length;
134 }
135
136 if (willBlock)
137 {
138 try
139 {
140 OutputStream.Write(responseEntity, 0, responseEntity.Length);
141 }
142 finally
143 {
144 Close(false);
145 }
146 }
147 else
148 {
149 OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar =>
150 {
151 var thisRef = (HttpListenerResponse)iar.AsyncState!;
152 try
153 {
154 thisRef.OutputStream.EndWrite(iar);
155 }
156 finally
157 {
158 thisRef.Close(false);
159 }
160 }, this);
161 }
162 }
163
164 public void CopyFrom(HttpListenerResponse templateResponse)
165 {
166 _webHeaders.Clear();
167 _webHeaders.Add(templateResponse._webHeaders);
168 _contentLength = templateResponse._contentLength;
169 _statusCode = templateResponse._statusCode;
170 _statusDescription = templateResponse._statusDescription;
171 _keepAlive = templateResponse._keepAlive;
172 _version = templateResponse._version;
173 }
174
175 internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandshake = false)
176 {
177 if (!isWebSocketHandshake)
178 {
179 if (_webHeaders[HttpKnownHeaderNames.Server] == null)
180 {
181 _webHeaders.Set(HttpKnownHeaderNames.Server, HttpHeaderStrings.NetCoreServerName);
182 }
183
184 if (_webHeaders[HttpKnownHeaderNames.Date] == null)
185 {
186 _webHeaders.Set(HttpKnownHeaderNames.Date, DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture));
187 }
188
189 if (_boundaryType == BoundaryType.None)
190 {
191 if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
192 {
193 _keepAlive = false;
194 }
195 else
196 {
197 _boundaryType = BoundaryType.Chunked;
198 }
199
200 if (CanSendResponseBody(_httpContext!.Response.StatusCode))
201 {
202 _contentLength = -1;
203 }
204 else
205 {
206 _boundaryType = BoundaryType.ContentLength;
207 _contentLength = 0;
208 }
209 }
210
211 if (_boundaryType != BoundaryType.Chunked)
212 {
213 if (_boundaryType != BoundaryType.ContentLength && closing)
214 {
215 _contentLength = CanSendResponseBody(_httpContext!.Response.StatusCode) ? -1 : 0;
216 }
217
218 if (_boundaryType == BoundaryType.ContentLength)
219 {
220 _webHeaders.Set(HttpKnownHeaderNames.ContentLength, _contentLength.ToString("D", CultureInfo.InvariantCulture));
221 }
222 }
223
224 /* Apache forces closing the connection for these status codes:
225 * HttpStatusCode.BadRequest 400
226 * HttpStatusCode.RequestTimeout 408
227 * HttpStatusCode.LengthRequired 411
228 * HttpStatusCode.RequestEntityTooLarge 413
229 * HttpStatusCode.RequestUriTooLong 414
230 * HttpStatusCode.InternalServerError 500
231 * HttpStatusCode.ServiceUnavailable 503
232 */
233 bool conn_close = (_statusCode == (int)HttpStatusCode.BadRequest || _statusCode == (int)HttpStatusCode.RequestTimeout
234 || _statusCode == (int)HttpStatusCode.LengthRequired || _statusCode == (int)HttpStatusCode.RequestEntityTooLarge
235 || _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError
236 || _statusCode == (int)HttpStatusCode.ServiceUnavailable);
237
238 if (!conn_close)
239 {
240 conn_close = !_httpContext!.Request.KeepAlive;
241 }
242
243 // They sent both KeepAlive: true and Connection: close
244 if (!_keepAlive || conn_close)
245 {
246 _webHeaders.Set(HttpKnownHeaderNames.Connection, HttpHeaderStrings.Close);
247 conn_close = true;
248 }
249
250 if (SendChunked)
251 {
252 _webHeaders.Set(HttpKnownHeaderNames.TransferEncoding, HttpHeaderStrings.Chunked);
253 }
254
255 int reuses = _httpContext!.Connection.Reuses;
256 if (reuses >= 100)
257 {
258 _forceCloseChunked = true;
259 if (!conn_close)
260 {
261 _webHeaders.Set(HttpKnownHeaderNames.Connection, HttpHeaderStrings.Close);
262 conn_close = true;
263 }
264 }
265
266 if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
267 {
268 if (_keepAlive)
269 {
270 Headers[HttpResponseHeader.KeepAlive] = "true";
271 }
272
273 if (!conn_close)
274 {
275 _webHeaders.Set(HttpKnownHeaderNames.Connection, HttpHeaderStrings.KeepAlive);
276 }
277 }
278
279 ComputeCookies();
280 }
281
282 Encoding encoding = Encoding.Default;
283 StreamWriter writer = new StreamWriter(ms, encoding, 256);
284 writer.Write("HTTP/1.1 {0} ", _statusCode); // "1.1" matches Windows implementation, which ignores the response version
285 writer.Flush();
286 byte[] statusDescriptionBytes = WebHeaderEncoding.GetBytes(StatusDescription);
287 ms.Write(statusDescriptionBytes, 0, statusDescriptionBytes.Length);
288 writer.Write("\r\n");
289
290 writer.Write(FormatHeaders(_webHeaders));
291 writer.Flush();
292#if UNITY_NETFRAMEWORK
293 int preamble = encoding.GetPreamble().Length;
294#else
295 int preamble = encoding.Preamble.Length;
296#endif
297 EnsureResponseStream();
298
299 /* Assumes that the ms was at position 0 */
300 ms.Position = preamble;
301 SentHeaders = !isWebSocketHandshake;
302 }
303
304 private static bool HeaderCanHaveEmptyValue(string name) =>
305 !string.Equals(name, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase);
306
307 private static string FormatHeaders(WebHeaderCollection headers)
308 {
309 var sb = new StringBuilder();
310
311 for (int i = 0; i < headers.Count; i++)
312 {
313 string key = headers.GetKey(i);
314 string[] values = headers.GetValues(i)!;
315
316 int startingLength = sb.Length;
317
318 sb.Append(key).Append(": ");
319 bool anyValues = false;
320 for (int j = 0; j < values.Length; j++)
321 {
322 string value = values[j];
323 if (!string.IsNullOrWhiteSpace(value))
324 {
325 if (anyValues)
326 {
327 if (key.Equals(HttpKnownHeaderNames.SetCookie, StringComparison.OrdinalIgnoreCase) ||
328 key.Equals(HttpKnownHeaderNames.SetCookie2, StringComparison.OrdinalIgnoreCase))
329 {
330 sb.Append("\r\n").Append(key).Append(": ");
331 }
332 else
333 {
334 sb.Append(", ");
335 }
336 }
337 sb.Append(value);
338 anyValues = true;
339 }
340 }
341
342 if (anyValues || HeaderCanHaveEmptyValue(key))
343 {
344 // Complete the header
345 sb.Append("\r\n");
346 }
347 else
348 {
349 // Empty header; remove it.
350 sb.Length = startingLength;
351 }
352 }
353
354 return sb.Append("\r\n").ToString();
355 }
356
357 private bool Disposed { get; set; }
358 internal bool SentHeaders { get; set; }
359 }
360}
Note: See TracBrowser for help on using the repository browser.