/*
* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
* File: HttpResponseCookie.cs
*
* HttpResponseCookie.cs is part of VNLib.Net.Http which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Net.Http is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Net.Http is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
using System;
using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
namespace VNLib.Net.Http
{
///
/// Represents an HTTP cookie that is set with responses.
///
/// The cookie name
public readonly struct HttpResponseCookie(string name) : IStringSerializeable, IEquatable
{
///
/// The default copy buffer allocated when calling the
/// family of methods.
///
public const int DefaultCookieBufferSize = 4096;
///
/// The name of the cookie to set.
///
public readonly string Name { get; } = name;
///
/// The actual cookie content or value.
///
public readonly string? Value { get; init; }
///
/// The domain this cookie will be sent to.
///
public readonly string? Domain { get; init; }
///
/// The cookie path the client will send this cookie with. Null
/// or empty string for all paths.
///
public readonly string? Path { get; init; }
///
/// Sets the duration of the cookie lifetime (in seconds), aka MaxAge
///
public readonly TimeSpan MaxAge { get; init; }
///
/// Sets the cookie Samesite field.
///
public readonly CookieSameSite SameSite { get; init; }
///
/// Sets the cookie Secure flag. If true only sends the cookie with requests
/// if the connection is secure.
///
public readonly bool Secure { get; init; }
///
/// Sets cookie HttpOnly flag. If true denies JavaScript access to
///
public readonly bool HttpOnly { get; init; }
///
/// Sets the cookie expiration to the duration of the user's session (aka no expiration)
///
public readonly bool IsSession { get; init; }
///
/// Creates an HTTP 1.x spec cookie header value from the
/// cookie fields
///
/// The internal copy buffer defaults to
/// use if you need control over the buffer size
///
///
/// The cookie header value as a string
///
public readonly string Compile()
{
nint bufSize = MemoryUtil.NearestPage(DefaultCookieBufferSize);
return Compile(bufSize.ToInt32());
}
///
/// Creates an HTTP 1.x spec cookie header value from the
/// cookie fields.
///
/// The size of the internal accumulator buffer
/// The cookie header value as a string
///
public readonly string Compile(int bufferSize)
{
using UnsafeMemoryHandle cookieBuffer = MemoryUtil.UnsafeAlloc(bufferSize, false);
ERRNO count = Compile(cookieBuffer.Span);
return cookieBuffer.AsSpan(0, (int)count).ToString();
}
///
/// Creates an HTTP 1.x spec cookie header value from the
/// cookie fields.
///
/// The character buffer to write the cookie data tor
/// The cookie header value as a string
///
public readonly ERRNO Compile(Span buffer)
{
ForwardOnlyWriter writer = new(buffer);
Compile(ref writer);
return writer.Written;
}
///
/// Writes the HTTP 1.x header format for the cookie
///
///
public readonly void Compile(ref ForwardOnlyWriter writer)
{
writer.Append(Name);
writer.Append('=');
writer.Append(Value);
/*
* If a session cookie is set, then do not include a max-age value
* browsers will default to session duration if not set
*/
if (!IsSession)
{
writer.AppendSmall("; Max-Age=");
writer.Append((int)MaxAge.TotalSeconds);
}
if (!string.IsNullOrWhiteSpace(Domain))
{
writer.AppendSmall("; Domain=");
writer.Append(Domain);
}
if (!string.IsNullOrWhiteSpace(Path))
{
//Set path
writer.AppendSmall("; Path=");
writer.Append(Path);
}
writer.AppendSmall("; SameSite=");
switch (SameSite)
{
case CookieSameSite.None:
writer.AppendSmall("None");
break;
case CookieSameSite.Strict:
writer.AppendSmall("Strict");
break;
case CookieSameSite.Lax:
default:
writer.AppendSmall("Lax");
break;
}
if (HttpOnly)
{
writer.AppendSmall("; HttpOnly");
}
if (Secure)
{
writer.AppendSmall("; Secure");
}
}
///
public readonly override int GetHashCode() => string.GetHashCode(Name, StringComparison.OrdinalIgnoreCase);
///
public readonly override bool Equals(object? obj) => obj is HttpResponseCookie other && Equals(other);
///
public readonly bool Equals(HttpResponseCookie other) => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
///
/// Creates an HTTP 1.x spec cookie header value from the
/// cookie fields
///
/// The internal copy buffer defaults to
/// use if you need control over the buffer size
///
///
/// The cookie header value as a string
public override string ToString() => Compile();
///
public static bool operator ==(HttpResponseCookie left, HttpResponseCookie right) => left.Equals(right);
///
public static bool operator !=(HttpResponseCookie left, HttpResponseCookie right) => !(left == right);
}
}