WebSocketTool/websocket-sharp/Net/HttpListenerResponse.cs
2022-03-14 14:04:39 +08:00

1200 lines
32 KiB
C#

#region License
/*
* HttpListenerResponse.cs
*
* This code is derived from HttpListenerResponse.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2021 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
#region Contributors
/*
* Contributors:
* - Nicholas Devenish
*/
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace WebSocketSharp.Net
{
/// <summary>
/// Represents an HTTP response to an HTTP request received by
/// a <see cref="HttpListener"/> instance.
/// </summary>
/// <remarks>
/// This class cannot be inherited.
/// </remarks>
public sealed class HttpListenerResponse : IDisposable
{
#region Private Fields
private bool _closeConnection;
private Encoding _contentEncoding;
private long _contentLength;
private string _contentType;
private HttpListenerContext _context;
private CookieCollection _cookies;
private bool _disposed;
private WebHeaderCollection _headers;
private bool _headersSent;
private bool _keepAlive;
private ResponseStream _outputStream;
private Uri _redirectLocation;
private bool _sendChunked;
private int _statusCode;
private string _statusDescription;
private Version _version;
#endregion
#region Internal Constructors
internal HttpListenerResponse (HttpListenerContext context)
{
_context = context;
_keepAlive = true;
_statusCode = 200;
_statusDescription = "OK";
_version = HttpVersion.Version11;
}
#endregion
#region Internal Properties
internal bool CloseConnection {
get {
return _closeConnection;
}
set {
_closeConnection = value;
}
}
internal WebHeaderCollection FullHeaders {
get {
var headers = new WebHeaderCollection (HttpHeaderType.Response, true);
if (_headers != null)
headers.Add (_headers);
if (_contentType != null) {
headers.InternalSet (
"Content-Type",
createContentTypeHeaderText (_contentType, _contentEncoding),
true
);
}
if (headers["Server"] == null)
headers.InternalSet ("Server", "websocket-sharp/1.0", true);
if (headers["Date"] == null) {
headers.InternalSet (
"Date",
DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture),
true
);
}
if (_sendChunked) {
headers.InternalSet ("Transfer-Encoding", "chunked", true);
}
else {
headers.InternalSet (
"Content-Length",
_contentLength.ToString (CultureInfo.InvariantCulture),
true
);
}
/*
* Apache forces closing the connection for these status codes:
* - 400 Bad Request
* - 408 Request Timeout
* - 411 Length Required
* - 413 Request Entity Too Large
* - 414 Request-Uri Too Long
* - 500 Internal Server Error
* - 503 Service Unavailable
*/
var closeConn = !_context.Request.KeepAlive
|| !_keepAlive
|| _statusCode == 400
|| _statusCode == 408
|| _statusCode == 411
|| _statusCode == 413
|| _statusCode == 414
|| _statusCode == 500
|| _statusCode == 503;
var reuses = _context.Connection.Reuses;
if (closeConn || reuses >= 100) {
headers.InternalSet ("Connection", "close", true);
}
else {
headers.InternalSet (
"Keep-Alive",
String.Format ("timeout=15,max={0}", 100 - reuses),
true
);
if (_context.Request.ProtocolVersion < HttpVersion.Version11)
headers.InternalSet ("Connection", "keep-alive", true);
}
if (_redirectLocation != null)
headers.InternalSet ("Location", _redirectLocation.AbsoluteUri, true);
if (_cookies != null) {
foreach (var cookie in _cookies) {
headers.InternalSet (
"Set-Cookie",
cookie.ToResponseString (),
true
);
}
}
return headers;
}
}
internal bool HeadersSent {
get {
return _headersSent;
}
set {
_headersSent = value;
}
}
internal string StatusLine {
get {
return String.Format (
"HTTP/{0} {1} {2}\r\n",
_version,
_statusCode,
_statusDescription
);
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the encoding for the entity body data included in
/// the response.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Encoding"/> that represents the encoding for
/// the entity body data.
/// </para>
/// <para>
/// <see langword="null"/> if no encoding is specified.
/// </para>
/// <para>
/// The default value is <see langword="null"/>.
/// </para>
/// </value>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public Encoding ContentEncoding {
get {
return _contentEncoding;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
_contentEncoding = value;
}
}
/// <summary>
/// Gets or sets the number of bytes in the entity body data included in
/// the response.
/// </summary>
/// <value>
/// <para>
/// A <see cref="long"/> that represents the number of bytes in
/// the entity body data.
/// </para>
/// <para>
/// It is used for the value of the Content-Length header.
/// </para>
/// <para>
/// The default value is zero.
/// </para>
/// </value>
/// <exception cref="ArgumentOutOfRangeException">
/// The value specified for a set operation is less than zero.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public long ContentLength64 {
get {
return _contentLength;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
if (value < 0) {
var msg = "Less than zero.";
throw new ArgumentOutOfRangeException (msg, "value");
}
_contentLength = value;
}
}
/// <summary>
/// Gets or sets the media type of the entity body included in
/// the response.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> that represents the media type of
/// the entity body.
/// </para>
/// <para>
/// It is used for the value of the Content-Type header.
/// </para>
/// <para>
/// <see langword="null"/> if no media type is specified.
/// </para>
/// <para>
/// The default value is <see langword="null"/>.
/// </para>
/// </value>
/// <exception cref="ArgumentException">
/// <para>
/// The value specified for a set operation is an empty string.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The value specified for a set operation contains
/// an invalid character.
/// </para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public string ContentType {
get {
return _contentType;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
if (value == null) {
_contentType = null;
return;
}
if (value.Length == 0) {
var msg = "An empty string.";
throw new ArgumentException (msg, "value");
}
if (!isValidForContentType (value)) {
var msg = "It contains an invalid character.";
throw new ArgumentException (msg, "value");
}
_contentType = value;
}
}
/// <summary>
/// Gets or sets the collection of cookies sent with the response.
/// </summary>
/// <value>
/// A <see cref="CookieCollection"/> that contains the cookies sent with
/// the response.
/// </value>
public CookieCollection Cookies {
get {
if (_cookies == null)
_cookies = new CookieCollection ();
return _cookies;
}
set {
_cookies = value;
}
}
/// <summary>
/// Gets or sets the collection of the HTTP headers sent to the client.
/// </summary>
/// <value>
/// A <see cref="WebHeaderCollection"/> that contains the headers sent to
/// the client.
/// </value>
/// <exception cref="InvalidOperationException">
/// The value specified for a set operation is not valid for a response.
/// </exception>
public WebHeaderCollection Headers {
get {
if (_headers == null)
_headers = new WebHeaderCollection (HttpHeaderType.Response, false);
return _headers;
}
set {
if (value == null) {
_headers = null;
return;
}
if (value.State != HttpHeaderType.Response) {
var msg = "The value is not valid for a response.";
throw new InvalidOperationException (msg);
}
_headers = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether the server requests
/// a persistent connection.
/// </summary>
/// <value>
/// <para>
/// <c>true</c> if the server requests a persistent connection;
/// otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>true</c>.
/// </para>
/// </value>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public bool KeepAlive {
get {
return _keepAlive;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
_keepAlive = value;
}
}
/// <summary>
/// Gets a stream instance to which the entity body data can be written.
/// </summary>
/// <value>
/// A <see cref="Stream"/> instance to which the entity body data can be
/// written.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public Stream OutputStream {
get {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_outputStream == null)
_outputStream = _context.Connection.GetResponseStream ();
return _outputStream;
}
}
/// <summary>
/// Gets the HTTP version used for the response.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Version"/> that represents the HTTP version used for
/// the response.
/// </para>
/// <para>
/// Always returns same as 1.1.
/// </para>
/// </value>
public Version ProtocolVersion {
get {
return _version;
}
}
/// <summary>
/// Gets or sets the URL to which the client is redirected to locate
/// a requested resource.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> that represents the absolute URL for
/// the redirect location.
/// </para>
/// <para>
/// It is used for the value of the Location header.
/// </para>
/// <para>
/// <see langword="null"/> if no redirect location is specified.
/// </para>
/// <para>
/// The default value is <see langword="null"/>.
/// </para>
/// </value>
/// <exception cref="ArgumentException">
/// <para>
/// The value specified for a set operation is an empty string.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The value specified for a set operation is not an absolute URL.
/// </para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public string RedirectLocation {
get {
return _redirectLocation != null
? _redirectLocation.OriginalString
: null;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
if (value == null) {
_redirectLocation = null;
return;
}
if (value.Length == 0) {
var msg = "An empty string.";
throw new ArgumentException (msg, "value");
}
Uri uri;
if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) {
var msg = "Not an absolute URL.";
throw new ArgumentException (msg, "value");
}
_redirectLocation = uri;
}
}
/// <summary>
/// Gets or sets a value indicating whether the response uses the chunked
/// transfer encoding.
/// </summary>
/// <value>
/// <para>
/// <c>true</c> if the response uses the chunked transfer encoding;
/// otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>false</c>.
/// </para>
/// </value>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public bool SendChunked {
get {
return _sendChunked;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
_sendChunked = value;
}
}
/// <summary>
/// Gets or sets the HTTP status code returned to the client.
/// </summary>
/// <value>
/// <para>
/// An <see cref="int"/> that represents the HTTP status code for
/// the response to the request.
/// </para>
/// <para>
/// The default value is 200. It indicates that the request has
/// succeeded.
/// </para>
/// </value>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
/// <exception cref="System.Net.ProtocolViolationException">
/// <para>
/// The value specified for a set operation is invalid.
/// </para>
/// <para>
/// Valid values are between 100 and 999 inclusive.
/// </para>
/// </exception>
public int StatusCode {
get {
return _statusCode;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
if (value < 100 || value > 999) {
var msg = "A value is not between 100 and 999 inclusive.";
throw new System.Net.ProtocolViolationException (msg);
}
_statusCode = value;
_statusDescription = value.GetStatusDescription ();
}
}
/// <summary>
/// Gets or sets the description of the HTTP status code returned to
/// the client.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> that represents the description of
/// the HTTP status code for the response to the request.
/// </para>
/// <para>
/// The default value is
/// the <see href="http://tools.ietf.org/html/rfc2616#section-10">
/// RFC 2616</see> description for the <see cref="StatusCode"/>
/// property value.
/// </para>
/// <para>
/// An empty string if an RFC 2616 description does not exist.
/// </para>
/// </value>
/// <exception cref="ArgumentNullException">
/// The value specified for a set operation is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// The value specified for a set operation contains an invalid character.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public string StatusDescription {
get {
return _statusDescription;
}
set {
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
if (value == null)
throw new ArgumentNullException ("value");
if (value.Length == 0) {
_statusDescription = _statusCode.GetStatusDescription ();
return;
}
if (!isValidForStatusDescription (value)) {
var msg = "It contains an invalid character.";
throw new ArgumentException (msg, "value");
}
_statusDescription = value;
}
}
#endregion
#region Private Methods
private bool canSetCookie (Cookie cookie)
{
var found = findCookie (cookie).ToList ();
if (found.Count == 0)
return true;
var ver = cookie.Version;
foreach (var c in found) {
if (c.Version == ver)
return true;
}
return false;
}
private void close (bool force)
{
_disposed = true;
_context.Connection.Close (force);
}
private void close (byte[] responseEntity, int bufferLength, bool willBlock)
{
var stream = OutputStream;
if (willBlock) {
stream.WriteBytes (responseEntity, bufferLength);
close (false);
return;
}
stream.WriteBytesAsync (
responseEntity,
bufferLength,
() => close (false),
null
);
}
private static string createContentTypeHeaderText (
string value, Encoding encoding
)
{
if (value.IndexOf ("charset=", StringComparison.Ordinal) > -1)
return value;
if (encoding == null)
return value;
return String.Format ("{0}; charset={1}", value, encoding.WebName);
}
private IEnumerable<Cookie> findCookie (Cookie cookie)
{
if (_cookies == null || _cookies.Count == 0)
yield break;
foreach (var c in _cookies) {
if (c.EqualsWithoutValueAndVersion (cookie))
yield return c;
}
}
private static bool isValidForContentType (string value)
{
foreach (var c in value) {
if (c < 0x20)
return false;
if (c > 0x7e)
return false;
if ("()<>@:\\[]?{}".IndexOf (c) > -1)
return false;
}
return true;
}
private static bool isValidForStatusDescription (string value)
{
foreach (var c in value) {
if (c < 0x20)
return false;
if (c > 0x7e)
return false;
}
return true;
}
#endregion
#region Public Methods
/// <summary>
/// Closes the connection to the client without sending a response.
/// </summary>
public void Abort ()
{
if (_disposed)
return;
close (true);
}
/// <summary>
/// Appends the specified cookie to the cookies sent with the response.
/// </summary>
/// <param name="cookie">
/// A <see cref="Cookie"/> to append.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception>
public void AppendCookie (Cookie cookie)
{
Cookies.Add (cookie);
}
/// <summary>
/// Appends an HTTP header with the specified name and value to
/// the headers for the response.
/// </summary>
/// <param name="name">
/// A <see cref="string"/> that specifies the name of the header to
/// append.
/// </param>
/// <param name="value">
/// A <see cref="string"/> that specifies the value of the header to
/// append.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="name"/> is an empty string.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> is a string of spaces.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> contains an invalid character.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="value"/> contains an invalid character.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> is a restricted header name.
/// </para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The length of <paramref name="value"/> is greater than 65,535
/// characters.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The current headers do not allow the header.
/// </exception>
public void AppendHeader (string name, string value)
{
Headers.Add (name, value);
}
/// <summary>
/// Sends the response to the client and releases the resources used by
/// this instance.
/// </summary>
public void Close ()
{
if (_disposed)
return;
close (false);
}
/// <summary>
/// Sends the response with the specified entity body data to the client
/// and releases the resources used by this instance.
/// </summary>
/// <param name="responseEntity">
/// An array of <see cref="byte"/> that contains the entity body data.
/// </param>
/// <param name="willBlock">
/// A <see cref="bool"/>: <c>true</c> if this method blocks execution while
/// flushing the stream to the client; otherwise, <c>false</c>.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="responseEntity"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public void Close (byte[] responseEntity, bool willBlock)
{
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (responseEntity == null)
throw new ArgumentNullException ("responseEntity");
var len = responseEntity.LongLength;
if (len > Int32.MaxValue) {
close (responseEntity, 1024, willBlock);
return;
}
var stream = OutputStream;
if (willBlock) {
stream.Write (responseEntity, 0, (int) len);
close (false);
return;
}
stream.BeginWrite (
responseEntity,
0,
(int) len,
ar => {
stream.EndWrite (ar);
close (false);
},
null
);
}
/// <summary>
/// Copies some properties from the specified response instance to
/// this instance.
/// </summary>
/// <param name="templateResponse">
/// A <see cref="HttpListenerResponse"/> to copy.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="templateResponse"/> is <see langword="null"/>.
/// </exception>
public void CopyFrom (HttpListenerResponse templateResponse)
{
if (templateResponse == null)
throw new ArgumentNullException ("templateResponse");
var headers = templateResponse._headers;
if (headers != null) {
if (_headers != null)
_headers.Clear ();
Headers.Add (headers);
}
else {
_headers = null;
}
_contentLength = templateResponse._contentLength;
_statusCode = templateResponse._statusCode;
_statusDescription = templateResponse._statusDescription;
_keepAlive = templateResponse._keepAlive;
_version = templateResponse._version;
}
/// <summary>
/// Configures the response to redirect the client's request to
/// the specified URL.
/// </summary>
/// <remarks>
/// This method sets the <see cref="RedirectLocation"/> property to
/// <paramref name="url"/>, the <see cref="StatusCode"/> property to
/// 302, and the <see cref="StatusDescription"/> property to "Found".
/// </remarks>
/// <param name="url">
/// A <see cref="string"/> that specifies the absolute URL to which
/// the client is redirected to locate a requested resource.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="url"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="url"/> is an empty string.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="url"/> is not an absolute URL.
/// </para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// The response is already being sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance is closed.
/// </exception>
public void Redirect (string url)
{
if (_disposed) {
var name = GetType ().ToString ();
throw new ObjectDisposedException (name);
}
if (_headersSent) {
var msg = "The response is already being sent.";
throw new InvalidOperationException (msg);
}
if (url == null)
throw new ArgumentNullException ("url");
if (url.Length == 0) {
var msg = "An empty string.";
throw new ArgumentException (msg, "url");
}
Uri uri;
if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) {
var msg = "Not an absolute URL.";
throw new ArgumentException (msg, "url");
}
_redirectLocation = uri;
_statusCode = 302;
_statusDescription = "Found";
}
/// <summary>
/// Adds or updates a cookie in the cookies sent with the response.
/// </summary>
/// <param name="cookie">
/// A <see cref="Cookie"/> to set.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="cookie"/> already exists in the cookies but
/// it cannot be updated.
/// </exception>
public void SetCookie (Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException ("cookie");
if (!canSetCookie (cookie)) {
var msg = "It cannot be updated.";
throw new ArgumentException (msg, "cookie");
}
Cookies.Add (cookie);
}
/// <summary>
/// Adds or updates an HTTP header with the specified name and value in
/// the headers for the response.
/// </summary>
/// <param name="name">
/// A <see cref="string"/> that specifies the name of the header to set.
/// </param>
/// <param name="value">
/// A <see cref="string"/> that specifies the value of the header to set.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="name"/> is an empty string.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> is a string of spaces.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> contains an invalid character.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="value"/> contains an invalid character.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> is a restricted header name.
/// </para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The length of <paramref name="value"/> is greater than 65,535
/// characters.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The current headers do not allow the header.
/// </exception>
public void SetHeader (string name, string value)
{
Headers.Set (name, value);
}
#endregion
#region Explicit Interface Implementations
/// <summary>
/// Releases all resources used by this instance.
/// </summary>
void IDisposable.Dispose ()
{
if (_disposed)
return;
close (true);
}
#endregion
}
}