/* * 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); } }