add: adding comments to code files
This commit is contained in:
parent
7a65e161f0
commit
54a2ef0dbe
3 changed files with 963 additions and 71 deletions
|
@ -1,39 +1,269 @@
|
||||||
/*
|
/*
|
||||||
* This file is part of the VieoFabric package.
|
* This file is part of the Protevus Platform.
|
||||||
|
* This file is a port of the symfony Cookie.php class to Dart
|
||||||
*
|
*
|
||||||
* (c) Patrick Stewart <patrick@example.com>
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
* (C) Fabien Potencier <fabien@symfony.com>
|
||||||
*
|
*
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'header_utils.dart';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'header_utils.dart';
|
||||||
|
|
||||||
/// Represents a cookie.
|
/// Represents an HTTP cookie.
|
||||||
|
///
|
||||||
|
/// This class encapsulates all the attributes and behaviors of an HTTP cookie,
|
||||||
|
/// including its name, value, expiration, path, domain, security settings,
|
||||||
|
/// and other properties like SameSite and Partitioned.
|
||||||
|
///
|
||||||
|
/// It provides methods to create, modify, and inspect cookie properties,
|
||||||
|
/// as well as to convert cookies to their string representation for use
|
||||||
|
/// in HTTP headers.
|
||||||
|
///
|
||||||
|
/// The Cookie class supports various cookie attributes and security features,
|
||||||
|
/// allowing for fine-grained control over cookie behavior in web applications.
|
||||||
class Cookie {
|
class Cookie {
|
||||||
|
|
||||||
|
/// Constant representing the 'None' value for the SameSite cookie attribute.
|
||||||
|
///
|
||||||
|
/// When set to 'none', the cookie will be sent with all cross-site requests,
|
||||||
|
/// including both cross-site reading and cross-site writing. This setting
|
||||||
|
/// requires the 'Secure' flag to be set in modern browsers.
|
||||||
|
///
|
||||||
|
/// Note: Using 'none' may have security implications and should be used
|
||||||
|
/// carefully, as it allows the cookie to be sent in all contexts.
|
||||||
static const String SAMESITE_NONE = 'none';
|
static const String SAMESITE_NONE = 'none';
|
||||||
|
|
||||||
|
/// Constant representing the 'Lax' value for the SameSite cookie attribute.
|
||||||
|
///
|
||||||
|
/// When set to 'lax', the cookie will be sent with top-level navigations and
|
||||||
|
/// will be sent along with GET requests initiated by third party websites.
|
||||||
|
/// This is less restrictive than 'Strict' but provides some protection against
|
||||||
|
/// CSRF attacks while allowing the cookie to be sent in more scenarios.
|
||||||
|
///
|
||||||
|
/// This is often used as a balance between security and functionality.
|
||||||
static const String SAMESITE_LAX = 'lax';
|
static const String SAMESITE_LAX = 'lax';
|
||||||
|
|
||||||
|
/// Constant representing the 'Strict' value for the SameSite cookie attribute.
|
||||||
|
///
|
||||||
|
/// When set to 'strict', the cookie will only be sent for same-site requests.
|
||||||
|
/// This is the most restrictive setting and provides the highest level of protection
|
||||||
|
/// against cross-site request forgery (CSRF) attacks.
|
||||||
|
///
|
||||||
|
/// With this setting, the cookie will not be sent with any cross-site requests,
|
||||||
|
/// including when a user follows a link from an external site. This can enhance
|
||||||
|
/// security but may impact functionality in some cases where cross-site cookie
|
||||||
|
/// access is required.
|
||||||
static const String SAMESITE_STRICT = 'strict';
|
static const String SAMESITE_STRICT = 'strict';
|
||||||
|
|
||||||
|
/// The name of the cookie.
|
||||||
|
///
|
||||||
|
/// This property represents the name of the cookie, which is used to identify
|
||||||
|
/// the cookie when it's sent between the server and the client. The name is
|
||||||
|
/// a required field for any cookie and must be set when the cookie is created.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before
|
||||||
|
/// it's used, but not necessarily at the point of declaration.
|
||||||
late String name;
|
late String name;
|
||||||
|
|
||||||
|
/// The value of the cookie.
|
||||||
|
///
|
||||||
|
/// This property represents the actual data stored in the cookie. It can be null
|
||||||
|
/// if the cookie is used for deletion (setting an expired cookie) or if it's a
|
||||||
|
/// flag-type cookie where the presence of the cookie itself is meaningful.
|
||||||
|
///
|
||||||
|
/// The value is typically a string, but it's declared as nullable (String?) to
|
||||||
|
/// allow for cases where a cookie might be set without a value or to represent
|
||||||
|
/// a not-yet-initialized state.
|
||||||
String? value;
|
String? value;
|
||||||
|
|
||||||
|
/// The domain that the cookie is available to.
|
||||||
|
///
|
||||||
|
/// This property represents the domain attribute of the cookie. When set, it specifies
|
||||||
|
/// which hosts are allowed to receive the cookie. It's important for controlling
|
||||||
|
/// the scope of the cookie, especially in scenarios involving subdomains.
|
||||||
|
///
|
||||||
|
/// If null, the cookie will only be sent to the host that set the cookie.
|
||||||
|
///
|
||||||
|
/// Note: The domain must be a valid domain string. Setting this incorrectly can
|
||||||
|
/// lead to security vulnerabilities or the cookie not being sent as expected.
|
||||||
String? domain;
|
String? domain;
|
||||||
|
|
||||||
|
/// The expiration time of the cookie.
|
||||||
|
///
|
||||||
|
/// This property represents the time at which the cookie should expire, stored as a Unix timestamp
|
||||||
|
/// (seconds since the Unix epoch). When this time is reached, the cookie is considered expired
|
||||||
|
/// and should be discarded by the client.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before it's used,
|
||||||
|
/// but not necessarily at the point of declaration. This allows for flexible initialization
|
||||||
|
/// patterns, such as setting the expiration time in a constructor or method after the object
|
||||||
|
/// is created.
|
||||||
|
///
|
||||||
|
/// A value of 0 typically indicates that the cookie does not have a specific expiration time
|
||||||
|
/// and should be treated as a session cookie (expires when the browsing session ends).
|
||||||
late int expire;
|
late int expire;
|
||||||
|
|
||||||
|
/// The path on the server where the cookie will be available.
|
||||||
|
///
|
||||||
|
/// This property represents the path attribute of the cookie. It specifies the
|
||||||
|
/// subset of URLs in a domain for which the cookie is valid. When a cookie has a
|
||||||
|
/// path set, it will only be sent to the server for requests to that path and
|
||||||
|
/// its subdirectories.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before
|
||||||
|
/// it's used, but not necessarily at the point of declaration. Typically, it's
|
||||||
|
/// set in the constructor or a method that initializes the cookie.
|
||||||
|
///
|
||||||
|
/// If not explicitly set, the default path is usually '/'.
|
||||||
late String path;
|
late String path;
|
||||||
|
|
||||||
|
/// Indicates whether the cookie should only be transmitted over a secure HTTPS connection.
|
||||||
|
///
|
||||||
|
/// This property is nullable:
|
||||||
|
/// - If set to true, the cookie will only be sent over secure connections.
|
||||||
|
/// - If set to false, the cookie can be sent over any connection.
|
||||||
|
/// - If null, the default secure setting (secureDefault) will be used.
|
||||||
|
///
|
||||||
|
/// The actual security behavior is determined by the [isSecure] method,
|
||||||
|
/// which considers both this property and the [secureDefault] value.
|
||||||
bool? secure;
|
bool? secure;
|
||||||
|
|
||||||
|
/// Indicates whether the cookie should be accessible only through the HTTP protocol.
|
||||||
|
///
|
||||||
|
/// When set to true, the cookie is not accessible to client-side scripts (such as JavaScript),
|
||||||
|
/// which helps mitigate cross-site scripting (XSS) attacks.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before it's used,
|
||||||
|
/// but not necessarily at the point of declaration.
|
||||||
late bool httpOnly;
|
late bool httpOnly;
|
||||||
|
|
||||||
|
/// Indicates whether the cookie should be sent with no URL encoding.
|
||||||
|
///
|
||||||
|
/// When set to true, the cookie name and value will not be URL-encoded when the cookie
|
||||||
|
/// is converted to a string representation. This can be useful in situations where
|
||||||
|
/// the cookie value contains characters that don't need to be encoded or when working
|
||||||
|
/// with systems that expect raw cookie values.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before it's used,
|
||||||
|
/// but not necessarily at the point of declaration.
|
||||||
late bool raw;
|
late bool raw;
|
||||||
|
|
||||||
|
/// The SameSite attribute of the cookie.
|
||||||
|
///
|
||||||
|
/// This property represents the SameSite attribute for the cookie, which controls how the cookie is sent with cross-site requests.
|
||||||
|
/// It can have one of three values:
|
||||||
|
/// - 'strict': The cookie is only sent for same-site requests.
|
||||||
|
/// - 'lax': The cookie is sent for same-site requests and top-level navigation from external sites.
|
||||||
|
/// - 'none': The cookie is sent for all cross-site requests.
|
||||||
|
/// - null: If the SameSite attribute is not set.
|
||||||
|
///
|
||||||
|
/// The SameSite attribute helps protect against cross-site request forgery (CSRF) attacks.
|
||||||
String? sameSite;
|
String? sameSite;
|
||||||
|
|
||||||
|
/// Indicates whether the cookie should be tied to the top-level site in cross-site context.
|
||||||
|
///
|
||||||
|
/// When set to true, the cookie will be partitioned, meaning it will be associated with
|
||||||
|
/// the top-level site in cross-site contexts. This can enhance privacy and security by
|
||||||
|
/// preventing tracking across different sites.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before it's used,
|
||||||
|
/// but not necessarily at the point of declaration.
|
||||||
late bool partitioned;
|
late bool partitioned;
|
||||||
|
|
||||||
|
/// Indicates the default value for the "secure" flag when it's not explicitly set.
|
||||||
|
///
|
||||||
|
/// This property determines whether cookies should be marked as secure by default
|
||||||
|
/// when the [secure] property is null. If set to true, cookies will be treated
|
||||||
|
/// as secure unless explicitly set otherwise.
|
||||||
|
///
|
||||||
|
/// The 'late' keyword indicates that this property will be initialized before
|
||||||
|
/// it's used, but not necessarily at the point of declaration.
|
||||||
late bool secureDefault;
|
late bool secureDefault;
|
||||||
|
|
||||||
|
/// A string containing characters that are reserved and cannot be used in cookie names.
|
||||||
|
///
|
||||||
|
/// This constant defines a list of characters that are considered reserved in the context of cookies.
|
||||||
|
/// These characters have special meanings in cookie syntax and therefore cannot be used directly
|
||||||
|
/// in cookie names, especially when the 'raw' flag is set to true.
|
||||||
|
///
|
||||||
|
/// The reserved characters are:
|
||||||
|
/// - '=': Used to separate cookie name and value
|
||||||
|
/// - ',': Used to separate multiple cookies
|
||||||
|
/// - ';': Used to separate cookie attributes
|
||||||
|
/// - ' ': Space character
|
||||||
|
/// - '\t': Tab character
|
||||||
|
/// - '\r': Carriage return
|
||||||
|
/// - '\n': Line feed
|
||||||
|
/// - '\v': Vertical tab
|
||||||
|
/// - '\f': Form feed
|
||||||
|
///
|
||||||
|
/// This constant is used in validation checks to ensure cookie names do not contain these characters
|
||||||
|
/// when creating or manipulating cookies with the 'raw' option enabled.
|
||||||
static const String RESERVED_CHARS_LIST = "=,; \t\r\n\v\f";
|
static const String RESERVED_CHARS_LIST = "=,; \t\r\n\v\f";
|
||||||
|
|
||||||
|
/// A list of reserved characters in their original form.
|
||||||
|
///
|
||||||
|
/// This constant defines a list of characters that are considered reserved in the context of cookies.
|
||||||
|
/// These characters have special meanings in cookie syntax and therefore need to be encoded
|
||||||
|
/// when used in cookie names or values.
|
||||||
|
///
|
||||||
|
/// The reserved characters are:
|
||||||
|
/// - '=': Used to separate cookie name and value
|
||||||
|
/// - ',': Used to separate multiple cookies
|
||||||
|
/// - ';': Used to separate cookie attributes
|
||||||
|
/// - ' ': Space character
|
||||||
|
/// - '\t': Tab character
|
||||||
|
/// - '\r': Carriage return
|
||||||
|
/// - '\n': Line feed
|
||||||
|
/// - '\v': Vertical tab
|
||||||
|
/// - '\f': Form feed
|
||||||
|
///
|
||||||
|
/// This list is typically used in conjunction with RESERVED_CHARS_TO for encoding/decoding
|
||||||
|
/// cookie names and values to ensure proper handling of these special characters.
|
||||||
static const List<String> RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
|
static const List<String> RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
|
||||||
|
|
||||||
|
/// A list of URL-encoded representations of reserved characters.
|
||||||
|
///
|
||||||
|
/// This constant defines a list of URL-encoded versions of characters that are considered
|
||||||
|
/// reserved in the context of cookies. These encoded versions correspond to the characters
|
||||||
|
/// in RESERVED_CHARS_FROM.
|
||||||
|
///
|
||||||
|
/// The encoded characters are:
|
||||||
|
/// - '%3D': Encoded form of '=' (equals sign)
|
||||||
|
/// - '%2C': Encoded form of ',' (comma)
|
||||||
|
/// - '%3B': Encoded form of ';' (semicolon)
|
||||||
|
/// - '%20': Encoded form of ' ' (space)
|
||||||
|
/// - '%09': Encoded form of '\t' (tab)
|
||||||
|
/// - '%0D': Encoded form of '\r' (carriage return)
|
||||||
|
/// - '%0A': Encoded form of '\n' (line feed)
|
||||||
|
/// - '%0B': Encoded form of '\v' (vertical tab)
|
||||||
|
/// - '%0C': Encoded form of '\f' (form feed)
|
||||||
|
///
|
||||||
|
/// This list is typically used in conjunction with RESERVED_CHARS_FROM for encoding/decoding
|
||||||
|
/// cookie names and values to ensure proper handling of these special characters.
|
||||||
static const List<String> RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
|
static const List<String> RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
|
||||||
|
|
||||||
/// Creates cookie from raw header string.
|
/// Creates a Cookie object from a string representation of a cookie.
|
||||||
|
///
|
||||||
|
/// This static method parses a cookie string and creates a corresponding Cookie object.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [cookie]: A string representation of the cookie.
|
||||||
|
/// - [decode]: A boolean flag indicating whether to decode the cookie name and value (default is false).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Cookie object created from the parsed cookie string.
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Initializes default values for cookie attributes.
|
||||||
|
/// 2. Splits the cookie string into parts.
|
||||||
|
/// 3. Extracts the cookie name and value.
|
||||||
|
/// 4. Parses additional cookie attributes.
|
||||||
|
/// 5. Handles the 'expires' and 'max-age' attributes.
|
||||||
|
/// 6. Creates and returns a new Cookie object with the parsed attributes.
|
||||||
static Cookie fromString(String cookie, {bool decode = false}) {
|
static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
final data = <String, Object?>{
|
final data = <String, Object?>{
|
||||||
'expires': 0,
|
'expires': 0,
|
||||||
|
@ -75,11 +305,45 @@ static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Factory constructor for creating a new cookie.
|
/// Creates a new Cookie instance with the specified parameters.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [name]: The name of the cookie (required).
|
||||||
|
/// - [value]: The value of the cookie (optional).
|
||||||
|
/// - [expire]: The expiration time of the cookie (default: 0).
|
||||||
|
/// - [path]: The path on the server where the cookie will be available (default: '/').
|
||||||
|
/// - [domain]: The domain that the cookie is available to (optional).
|
||||||
|
/// - [secure]: Whether the cookie should only be transmitted over secure HTTPS (optional).
|
||||||
|
/// - [httpOnly]: Whether the cookie should be accessible only through HTTP protocol (default: true).
|
||||||
|
/// - [raw]: Whether the cookie should use no URL encoding (default: false).
|
||||||
|
/// - [sameSite]: The SameSite attribute of the cookie (default: SAMESITE_LAX).
|
||||||
|
/// - [partitioned]: Whether the cookie should be tied to the top-level site in cross-site context (default: false).
|
||||||
|
///
|
||||||
|
/// Returns a new Cookie instance with the specified attributes.
|
||||||
factory Cookie.create(String name, {String? value, dynamic expire = 0, String? path = '/', String? domain, bool? secure, bool httpOnly = true, bool raw = false, String? sameSite = SAMESITE_LAX, bool partitioned = false}) {
|
factory Cookie.create(String name, {String? value, dynamic expire = 0, String? path = '/', String? domain, bool? secure, bool httpOnly = true, bool raw = false, String? sameSite = SAMESITE_LAX, bool partitioned = false}) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal constructor for creating a Cookie instance.
|
||||||
|
///
|
||||||
|
/// This constructor initializes a Cookie object with the provided parameters.
|
||||||
|
/// It performs validation checks on the cookie name and sets default values for certain attributes.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [name]: The name of the cookie.
|
||||||
|
/// - [value]: The value of the cookie.
|
||||||
|
/// - [expire]: The expiration time of the cookie (can be DateTime, int, or String).
|
||||||
|
/// - [path]: The path on the server where the cookie will be available (default is '/').
|
||||||
|
/// - [domain]: The domain that the cookie is available to.
|
||||||
|
/// - [secure]: Whether the cookie should only be transmitted over secure HTTPS.
|
||||||
|
/// - [httpOnly]: Whether the cookie should be accessible only through HTTP protocol.
|
||||||
|
/// - [raw]: Whether the cookie should use no URL encoding.
|
||||||
|
/// - [sameSite]: The SameSite attribute of the cookie.
|
||||||
|
/// - [partitioned]: Whether the cookie should be tied to the top-level site in cross-site context.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if the cookie name contains invalid characters when [raw] is true.
|
||||||
|
/// - [ArgumentError] if the cookie name is empty.
|
||||||
Cookie._internal(this.name, this.value, dynamic expire, String? path, this.domain, this.secure, this.httpOnly, this.raw, this.sameSite, this.partitioned) {
|
Cookie._internal(this.name, this.value, dynamic expire, String? path, this.domain, this.secure, this.httpOnly, this.raw, this.sameSite, this.partitioned) {
|
||||||
if (raw && name.contains(RegExp(r'[' + RESERVED_CHARS_LIST + r']'))) {
|
if (raw && name.contains(RegExp(r'[' + RESERVED_CHARS_LIST + r']'))) {
|
||||||
throw ArgumentError('The cookie name "$name" contains invalid characters.');
|
throw ArgumentError('The cookie name "$name" contains invalid characters.');
|
||||||
|
@ -95,21 +359,70 @@ static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy with a new value.
|
/// Creates a cookie copy with a new value.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [value]. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [value]: The new value to set for the cookie. Can be null to create a cookie without a value.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated value.
|
||||||
Cookie withValue(String? value) {
|
Cookie withValue(String? value) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy with a new domain that the cookie is available to.
|
/// Creates a cookie copy with a new domain that the cookie is available to.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [domain]. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [domain]: The new domain to set for the cookie. Can be null to remove the domain restriction.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated domain.
|
||||||
Cookie withDomain(String? domain) {
|
Cookie withDomain(String? domain) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy with a new time the cookie expires.
|
/// Creates a cookie copy with a new time the cookie expires.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [expire] time. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [expire]: The new expiration time for the cookie. Can be a DateTime, int (Unix timestamp),
|
||||||
|
/// or String (parseable date format).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated expiration time.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if the provided [expire] value is not a valid expiration time format.
|
||||||
Cookie withExpires(dynamic expire) {
|
Cookie withExpires(dynamic expire) {
|
||||||
return Cookie._internal(name, value, _expiresTimestamp(expire), path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, _expiresTimestamp(expire), path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts expires formats to a unix timestamp.
|
/// Converts various expiration time formats to a Unix timestamp.
|
||||||
|
///
|
||||||
|
/// This method takes a dynamic [expire] parameter and converts it to a Unix timestamp
|
||||||
|
/// (seconds since the Unix epoch). It supports the following input types:
|
||||||
|
///
|
||||||
|
/// - [DateTime]: Converts the DateTime to a Unix timestamp.
|
||||||
|
/// - [int]: Assumes the input is already a Unix timestamp and returns it as-is.
|
||||||
|
/// - [String]: Parses the string as a DateTime and converts it to a Unix timestamp.
|
||||||
|
///
|
||||||
|
/// If the input doesn't match any of these types, an [ArgumentError] is thrown.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [expire]: The expiration time in one of the supported formats.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// An integer representing the expiration time as a Unix timestamp.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if the input format is not recognized or cannot be parsed.
|
||||||
static int _expiresTimestamp(dynamic expire) {
|
static int _expiresTimestamp(dynamic expire) {
|
||||||
if (expire is DateTime) {
|
if (expire is DateTime) {
|
||||||
return expire.millisecondsSinceEpoch ~/ 1000;
|
return expire.millisecondsSinceEpoch ~/ 1000;
|
||||||
|
@ -122,22 +435,64 @@ static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy with a new path on the server in which the cookie will be available on.
|
/// Creates a cookie copy with a new path on the server where the cookie will be available.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [path]. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [path]: The new path to set for the cookie. If an empty string is provided, it defaults to '/'.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated path.
|
||||||
Cookie withPath(String path) {
|
Cookie withPath(String path) {
|
||||||
return Cookie._internal(name, value, expire, path.isEmpty ? '/' : path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path.isEmpty ? '/' : path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client.
|
/// Creates a cookie copy that can only be transmitted over a secure HTTPS connection from the client.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [secure] flag. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [secure]: A boolean value indicating whether the cookie should only be transmitted over HTTPS.
|
||||||
|
/// If true, the cookie will only be sent over secure connections.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated secure flag.
|
||||||
Cookie withSecure(bool secure) {
|
Cookie withSecure(bool secure) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy that be accessible only through the HTTP protocol.
|
/// Creates a cookie copy that can be accessible only through the HTTP protocol.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [httpOnly] flag. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [httpOnly]: A boolean value indicating whether the cookie should be accessible only through
|
||||||
|
/// the HTTP protocol. If true, the cookie will not be accessible through client-side scripts.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated httpOnly flag.
|
||||||
Cookie withHttpOnly(bool httpOnly) {
|
Cookie withHttpOnly(bool httpOnly) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy that uses no url encoding.
|
/// Creates a cookie copy that uses no URL encoding.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [raw] flag. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [raw]: A boolean value indicating whether the cookie should use no URL encoding.
|
||||||
|
/// If true, the cookie name and value will not be URL-encoded.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated raw flag.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if [raw] is set to true and the cookie name contains invalid characters.
|
||||||
Cookie withRaw(bool raw) {
|
Cookie withRaw(bool raw) {
|
||||||
if (raw && name.contains(RegExp(r'[' + RESERVED_CHARS_LIST + r']'))) {
|
if (raw && name.contains(RegExp(r'[' + RESERVED_CHARS_LIST + r']'))) {
|
||||||
throw ArgumentError('The cookie name "$name" contains invalid characters.');
|
throw ArgumentError('The cookie name "$name" contains invalid characters.');
|
||||||
|
@ -145,7 +500,23 @@ static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy with SameSite attribute.
|
/// Creates a cookie copy with a new SameSite attribute.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [sameSite] value. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [sameSite]: The new SameSite attribute value for the cookie. Valid values are:
|
||||||
|
/// - [SAMESITE_LAX]: Cookies are not sent on normal cross-site subrequests but are sent when a user navigates to the origin site.
|
||||||
|
/// - [SAMESITE_STRICT]: Cookies are only sent in a first-party context and not sent along with requests initiated by third party websites.
|
||||||
|
/// - [SAMESITE_NONE]: Cookies are sent in all contexts, i.e., in responses to both first-party and cross-origin requests.
|
||||||
|
/// - null: The SameSite attribute is not set.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated SameSite attribute.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if the provided [sameSite] value is not one of the valid options.
|
||||||
Cookie withSameSite(String? sameSite) {
|
Cookie withSameSite(String? sameSite) {
|
||||||
final validSameSite = [SAMESITE_LAX, SAMESITE_STRICT, SAMESITE_NONE, null];
|
final validSameSite = [SAMESITE_LAX, SAMESITE_STRICT, SAMESITE_NONE, null];
|
||||||
if (!validSameSite.contains(sameSite?.toLowerCase())) {
|
if (!validSameSite.contains(sameSite?.toLowerCase())) {
|
||||||
|
@ -155,11 +526,34 @@ static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cookie copy that is tied to the top-level site in cross-site context.
|
/// Creates a cookie copy that is tied to the top-level site in cross-site context.
|
||||||
|
///
|
||||||
|
/// This method returns a new Cookie instance with the same attributes as the current cookie,
|
||||||
|
/// but with the provided [partitioned] flag. All other attributes remain unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [partitioned]: A boolean value indicating whether the cookie should be tied to the top-level site
|
||||||
|
/// in cross-site context. If true, the cookie will be partitioned.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Cookie instance with the updated partitioned flag.
|
||||||
Cookie withPartitioned(bool partitioned) {
|
Cookie withPartitioned(bool partitioned) {
|
||||||
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
return Cookie._internal(name, value, expire, path, domain, secure, httpOnly, raw, sameSite, partitioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the cookie as a string.
|
/// Converts the cookie to its string representation.
|
||||||
|
///
|
||||||
|
/// This method generates a string that represents the cookie in the format used in HTTP headers.
|
||||||
|
/// It includes all the cookie's attributes such as name, value, expiration, path, domain, secure flag,
|
||||||
|
/// HTTP-only flag, SameSite attribute, and partitioned flag.
|
||||||
|
///
|
||||||
|
/// The method handles the following cases:
|
||||||
|
/// - If the cookie is raw, the name and value are not URL-encoded.
|
||||||
|
/// - If the value is null or empty, the cookie is treated as deleted with immediate expiration.
|
||||||
|
/// - If an expiration time is set, it's included in both 'expires' and 'Max-Age' attributes.
|
||||||
|
/// - All other attributes (path, domain, secure, httpOnly, sameSite, partitioned) are added if set.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string representation of the cookie suitable for use in an HTTP header.
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
@ -210,45 +604,129 @@ static Cookie fromString(String cookie, {bool decode = false}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the name of the cookie.
|
/// Gets the name of the cookie.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string representing the name of the cookie.
|
||||||
String getName() => name;
|
String getName() => name;
|
||||||
|
|
||||||
/// Gets the value of the cookie.
|
/// Gets the value of the cookie.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string representing the value of the cookie, or null if the cookie has no value.
|
||||||
String? getValue() => value;
|
String? getValue() => value;
|
||||||
|
|
||||||
/// Gets the domain that the cookie is available to.
|
/// Gets the domain that the cookie is available to.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string representing the domain of the cookie, or null if no domain is set.
|
||||||
String? getDomain() => domain;
|
String? getDomain() => domain;
|
||||||
|
|
||||||
/// Gets the time the cookie expires.
|
/// Gets the expiration time of the cookie.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// An integer representing the expiration time of the cookie as a Unix timestamp.
|
||||||
|
/// If the cookie doesn't have an expiration time set, it returns 0.
|
||||||
int getExpiresTime() => expire;
|
int getExpiresTime() => expire;
|
||||||
|
|
||||||
/// Gets the max-age attribute.
|
/// Calculates and returns the Max-Age attribute value for the cookie.
|
||||||
|
///
|
||||||
|
/// This method computes the number of seconds until the cookie expires.
|
||||||
|
/// It does this by subtracting the current Unix timestamp from the cookie's
|
||||||
|
/// expiration timestamp. The result is always non-negative, with a minimum
|
||||||
|
/// value of 0.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// An integer representing the number of seconds until the cookie expires.
|
||||||
|
/// If the cookie has already expired, it returns 0.
|
||||||
int getMaxAge() {
|
int getMaxAge() {
|
||||||
final maxAge = expire - (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
final maxAge = expire - (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
return max(0, maxAge);
|
return max(0, maxAge);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the path on the server in which the cookie will be available on.
|
/// Gets the path on the server where the cookie will be available.
|
||||||
|
///
|
||||||
|
/// This method returns the path attribute of the cookie, which specifies the
|
||||||
|
/// subset of URLs in a domain for which the cookie is valid.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string representing the path of the cookie.
|
||||||
String getPath() => path;
|
String getPath() => path;
|
||||||
|
|
||||||
/// Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
|
/// Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
|
||||||
|
///
|
||||||
|
/// This method returns the value of the 'secure' flag for the cookie. If the 'secure' flag
|
||||||
|
/// is explicitly set (either true or false), it returns that value. If 'secure' is null,
|
||||||
|
/// it falls back to the default secure setting (secureDefault).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A boolean value: true if the cookie should only be sent over secure connections,
|
||||||
|
/// false otherwise.
|
||||||
bool isSecure() => secure ?? secureDefault;
|
bool isSecure() => secure ?? secureDefault;
|
||||||
|
|
||||||
/// Checks whether the cookie will be made accessible only through the HTTP protocol.
|
/// Checks whether the cookie is accessible only through the HTTP protocol.
|
||||||
|
///
|
||||||
|
/// This method returns the value of the 'httpOnly' flag for the cookie.
|
||||||
|
/// If true, the cookie is inaccessible to client-side scripts like JavaScript,
|
||||||
|
/// which helps mitigate cross-site scripting (XSS) attacks.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A boolean value: true if the cookie is HTTP-only, false otherwise.
|
||||||
bool isHttpOnly() => httpOnly;
|
bool isHttpOnly() => httpOnly;
|
||||||
|
|
||||||
/// Whether this cookie is about to be cleared.
|
/// Checks if the cookie has been cleared or has expired.
|
||||||
|
///
|
||||||
|
/// This method determines whether the cookie is considered cleared by checking two conditions:
|
||||||
|
/// 1. The cookie has an expiration time set (expire != 0).
|
||||||
|
/// 2. The expiration time is in the past (earlier than the current time).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A boolean value: true if the cookie has been cleared or has expired, false otherwise.
|
||||||
bool isCleared() => expire != 0 && expire < (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
bool isCleared() => expire != 0 && expire < (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||||
|
|
||||||
/// Checks if the cookie value should be sent with no url encoding.
|
/// Checks if the cookie value should be sent with no URL encoding.
|
||||||
|
///
|
||||||
|
/// This method returns the value of the 'raw' flag for the cookie.
|
||||||
|
/// If true, the cookie name and value will not be URL-encoded when the cookie is converted to a string.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A boolean value: true if the cookie should be sent raw (without URL encoding), false otherwise.
|
||||||
bool isRaw() => raw;
|
bool isRaw() => raw;
|
||||||
|
|
||||||
/// Checks whether the cookie should be tied to the top-level site in cross-site context.
|
/// Checks whether the cookie should be tied to the top-level site in cross-site context.
|
||||||
|
///
|
||||||
|
/// This method returns the value of the 'partitioned' flag for the cookie.
|
||||||
|
/// If true, the cookie will be partitioned, meaning it will be tied to the top-level site
|
||||||
|
/// in cross-site contexts, which can help improve privacy and security.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A boolean value: true if the cookie is partitioned, false otherwise.
|
||||||
bool isPartitioned() => partitioned;
|
bool isPartitioned() => partitioned;
|
||||||
|
|
||||||
/// Gets the SameSite attribute of the cookie.
|
/// Gets the SameSite attribute of the cookie.
|
||||||
|
///
|
||||||
|
/// This method returns the value of the SameSite attribute for the cookie.
|
||||||
|
/// The SameSite attribute is used to control how cookies are sent with cross-site requests.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string representing the SameSite attribute of the cookie, which can be:
|
||||||
|
/// - 'strict': The cookie is only sent for same-site requests.
|
||||||
|
/// - 'lax': The cookie is sent for same-site requests and top-level navigation from external sites.
|
||||||
|
/// - 'none': The cookie is sent for all cross-site requests.
|
||||||
|
/// - null: If the SameSite attribute is not set.
|
||||||
String? getSameSite() => sameSite;
|
String? getSameSite() => sameSite;
|
||||||
|
|
||||||
/// Sets the default value of the "secure" flag when it is set to null.
|
/// Sets the default value for the "secure" flag when it is not explicitly set.
|
||||||
|
///
|
||||||
|
/// This method allows you to specify a default value for the "secure" flag
|
||||||
|
/// that will be used when the secure property of the cookie is null.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [defaultSecure]: A boolean value indicating whether cookies should be
|
||||||
|
/// secure by default. If true, cookies will be marked as secure by default
|
||||||
|
/// when the secure property is not explicitly set.
|
||||||
|
///
|
||||||
|
/// This setting affects the behavior of the [isSecure] method when the
|
||||||
|
/// secure property is null.
|
||||||
void setSecureDefault(bool defaultSecure) {
|
void setSecureDefault(bool defaultSecure) {
|
||||||
secureDefault = defaultSecure;
|
secureDefault = defaultSecure;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,121 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
* This file is a port of the symfony HeaderBag.php class to Dart
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
* (C) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
/// HeaderBag is a container for HTTP headers.
|
/// HeaderBag is a class that manages HTTP headers.
|
||||||
///
|
///
|
||||||
/// Author: Fabien Potencier <fabien@symfony.com>
|
/// This class provides functionality to store, retrieve, and manipulate HTTP headers.
|
||||||
/// This file is part of the Symfony package.
|
/// It supports operations such as adding, removing, and checking for the presence of headers,
|
||||||
|
/// as well as special handling for Cache-Control directives.
|
||||||
|
///
|
||||||
|
/// Key features:
|
||||||
|
/// - Case-insensitive header names
|
||||||
|
/// - Support for multiple values per header
|
||||||
|
/// - Special handling for Cache-Control headers
|
||||||
|
/// - Methods to add, remove, and check headers
|
||||||
|
/// - Implements Iterable for easy traversal of headers
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// var headers = HeaderBag();
|
||||||
|
/// headers.set('Content-Type', 'application/json');
|
||||||
|
/// headers.add({'Accept': ['text/html', 'application/xhtml+xml']});
|
||||||
|
/// print(headers.get('content-type')); // Prints: application/json
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This class is particularly useful for HTTP clients and servers that need to
|
||||||
|
/// manage complex header scenarios, including multiple header values and
|
||||||
|
/// Cache-Control directives.
|
||||||
class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
|
|
||||||
|
/// A constant string containing uppercase letters and underscore.
|
||||||
|
///
|
||||||
|
/// This constant is used for case-insensitive string operations,
|
||||||
|
/// particularly in header name formatting. It includes the underscore
|
||||||
|
/// character followed by all uppercase letters of the English alphabet.
|
||||||
|
///
|
||||||
|
/// The string is defined as:
|
||||||
|
/// - Underscore: '_'
|
||||||
|
/// - Uppercase letters: 'A' through 'Z'
|
||||||
|
///
|
||||||
|
/// This constant is typically used in conjunction with the 'lower' constant
|
||||||
|
/// for case conversion operations within the HeaderBag class.
|
||||||
static const String upper = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
static const String upper = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
|
||||||
|
/// A constant string containing lowercase letters and hyphen.
|
||||||
|
///
|
||||||
|
/// This constant is used for case-insensitive string operations,
|
||||||
|
/// particularly in header name formatting. It includes the hyphen
|
||||||
|
/// character followed by all lowercase letters of the English alphabet.
|
||||||
|
///
|
||||||
|
/// The string is defined as:
|
||||||
|
/// - Hyphen: '-'
|
||||||
|
/// - Lowercase letters: 'a' through 'z'
|
||||||
|
///
|
||||||
|
/// This constant is typically used in conjunction with the 'upper' constant
|
||||||
|
/// for case conversion operations within the HeaderBag class.
|
||||||
static const String lower = '-abcdefghijklmnopqrstuvwxyz';
|
static const String lower = '-abcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
/// A map to store the headers.
|
/// A map to store the headers.
|
||||||
|
///
|
||||||
|
/// This private field stores all the HTTP headers in the HeaderBag.
|
||||||
|
/// The keys of the map are header names (stored in lowercase),
|
||||||
|
/// and the values are lists of header values.
|
||||||
|
///
|
||||||
|
/// Using a list for values allows for multiple values per header,
|
||||||
|
/// which is common in HTTP headers (e.g., multiple Set-Cookie headers).
|
||||||
|
///
|
||||||
|
/// The use of nullable String (String?) in the list allows for the
|
||||||
|
/// possibility of null values, which might occur in some edge cases.
|
||||||
final Map<String, List<String?>> _headers = {};
|
final Map<String, List<String?>> _headers = {};
|
||||||
|
|
||||||
/// A map to store cache control directives.
|
/// A map to store cache control directives.
|
||||||
|
///
|
||||||
|
/// This map holds key-value pairs representing Cache-Control directives.
|
||||||
|
/// Keys are directive names (e.g., "max-age", "no-cache"), and values are
|
||||||
|
/// the corresponding directive values or true for valueless directives.
|
||||||
|
///
|
||||||
|
/// This map is used internally to manage and manipulate Cache-Control
|
||||||
|
/// header information efficiently, allowing for easy addition, removal,
|
||||||
|
/// and retrieval of individual directives.
|
||||||
final Map<String, dynamic> _cacheControl = {};
|
final Map<String, dynamic> _cacheControl = {};
|
||||||
|
|
||||||
/// Constructor for HeaderBag
|
/// Constructor for HeaderBag
|
||||||
|
///
|
||||||
|
/// Creates a new HeaderBag instance with the given headers.
|
||||||
|
///
|
||||||
|
/// @param headers An optional map of headers to initialize the HeaderBag with.
|
||||||
|
/// The map keys are header names, and the values are lists of
|
||||||
|
/// header values. If not provided, an empty map is used.
|
||||||
|
///
|
||||||
|
/// This constructor initializes the HeaderBag by setting each header in the
|
||||||
|
/// provided map using the `set` method, which ensures proper formatting and
|
||||||
|
/// handling of special headers like 'Cache-Control'.
|
||||||
HeaderBag([Map<String, List<String?>> headers = const {}]) {
|
HeaderBag([Map<String, List<String?>> headers = const {}]) {
|
||||||
headers.forEach((key, values) {
|
headers.forEach((key, values) {
|
||||||
set(key, values);
|
set(key, values);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the headers as a string.
|
/// Returns a string representation of the headers.
|
||||||
|
///
|
||||||
|
/// This method creates a formatted string of all headers in the HeaderBag.
|
||||||
|
/// The headers are sorted alphabetically by key, and each header is
|
||||||
|
/// presented on a new line with the header name capitalized appropriately.
|
||||||
|
/// The header names are right-padded to align all header values.
|
||||||
|
///
|
||||||
|
/// If the HeaderBag is empty, an empty string is returned.
|
||||||
|
///
|
||||||
|
/// @return A formatted string representation of all headers.
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (_headers.isEmpty) {
|
if (_headers.isEmpty) {
|
||||||
|
@ -42,11 +136,17 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
return content.toString();
|
return content.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the headers.
|
/// Returns all headers or headers for a specific key.
|
||||||
///
|
///
|
||||||
/// @param key The name of the headers to return or null to get them all
|
/// If a [key] is provided, this method returns a map containing only that key
|
||||||
|
/// (in lowercase) and its associated list of values. If the key doesn't exist,
|
||||||
|
/// an empty list is returned for that key.
|
||||||
///
|
///
|
||||||
/// @return A map of headers.
|
/// If no [key] is provided, this method returns all headers in the HeaderBag.
|
||||||
|
///
|
||||||
|
/// @param key The optional key to retrieve specific headers.
|
||||||
|
/// @return A map of headers. If a key is provided, the map will contain only
|
||||||
|
/// that key-value pair. If no key is provided, all headers are returned.
|
||||||
Map<String, List<String?>> all([String? key]) {
|
Map<String, List<String?>> all([String? key]) {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
return {key.toLowerCase(): _headers[key.toLowerCase()] ?? []};
|
return {key.toLowerCase(): _headers[key.toLowerCase()] ?? []};
|
||||||
|
@ -56,25 +156,55 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
|
|
||||||
/// Returns the parameter keys.
|
/// Returns the parameter keys.
|
||||||
///
|
///
|
||||||
/// @return A list of keys.
|
/// This method retrieves all the keys from the internal _headers map
|
||||||
|
/// and returns them as a list of strings. The keys represent the names
|
||||||
|
/// of all the headers stored in this HeaderBag.
|
||||||
|
///
|
||||||
|
/// @return A list of strings containing all the header names.
|
||||||
List<String> keys() {
|
List<String> keys() {
|
||||||
return _headers.keys.toList();
|
return _headers.keys.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the current HTTP headers by a new set.
|
/// Replaces the current HTTP headers with a new set of headers.
|
||||||
|
///
|
||||||
|
/// This method first clears all existing headers in the HeaderBag,
|
||||||
|
/// then adds the new headers provided in the [headers] parameter.
|
||||||
|
///
|
||||||
|
/// @param headers A map of new headers to replace the existing ones.
|
||||||
|
/// The map keys are header names, and the values are
|
||||||
|
/// lists of header values.
|
||||||
void replace(Map<String, List<String?>> headers) {
|
void replace(Map<String, List<String?>> headers) {
|
||||||
_headers.clear();
|
_headers.clear();
|
||||||
add(headers);
|
add(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds new headers to the current HTTP headers set.
|
/// Adds new headers to the current HTTP headers set.
|
||||||
|
///
|
||||||
|
/// This method takes a map of headers and adds them to the existing headers
|
||||||
|
/// in the HeaderBag. If a header with the same name already exists, its values
|
||||||
|
/// are appended to the existing values.
|
||||||
|
///
|
||||||
|
/// @param headers A map where keys are header names and values are lists of
|
||||||
|
/// header values to be added.
|
||||||
|
///
|
||||||
|
/// Each header in the input map is added using the `set` method, which handles
|
||||||
|
/// the details of appending values and updating special headers like 'Cache-Control'.
|
||||||
void add(Map<String, List<String?>> headers) {
|
void add(Map<String, List<String?>> headers) {
|
||||||
headers.forEach((key, values) {
|
headers.forEach((key, values) {
|
||||||
set(key, values);
|
set(key, values);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the first header by name or the default one.
|
/// Returns the first value of the specified HTTP header.
|
||||||
|
///
|
||||||
|
/// This method retrieves the first value of the header specified by [key].
|
||||||
|
/// If the header doesn't exist or has no values, it returns the [defaultValue].
|
||||||
|
///
|
||||||
|
/// @param key The name of the HTTP header to retrieve.
|
||||||
|
/// @param defaultValue An optional default value to return if the header
|
||||||
|
/// doesn't exist or has no values. Defaults to null if not specified.
|
||||||
|
/// @return The first value of the specified header, or the default value
|
||||||
|
/// if the header doesn't exist or has no values.
|
||||||
String? get(String key, [String? defaultValue]) {
|
String? get(String key, [String? defaultValue]) {
|
||||||
var headers = all(key)[key.toLowerCase()];
|
var headers = all(key)[key.toLowerCase()];
|
||||||
if (headers == null || headers.isEmpty) {
|
if (headers == null || headers.isEmpty) {
|
||||||
|
@ -85,8 +215,19 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
|
|
||||||
/// Sets a header by name.
|
/// Sets a header by name.
|
||||||
///
|
///
|
||||||
/// @param values The value or an array of values
|
/// This method sets or adds a header to the HeaderBag. It can handle both
|
||||||
/// @param replace Whether to replace the actual value or not (true by default)
|
/// single values and lists of values.
|
||||||
|
///
|
||||||
|
/// @param key The name of the header to set. This will be converted to lowercase.
|
||||||
|
/// @param values The value or list of values to set for the header.
|
||||||
|
/// @param replace Whether to replace the existing values (if any) or append to them.
|
||||||
|
/// Defaults to true.
|
||||||
|
///
|
||||||
|
/// If [replace] is true or the header doesn't exist, it will overwrite any existing
|
||||||
|
/// values. If [replace] is false and the header exists, it will append the new values.
|
||||||
|
///
|
||||||
|
/// For the 'cache-control' header, this method also updates the internal
|
||||||
|
/// cache control directives by parsing the new header value.
|
||||||
void set(String key, dynamic values, [bool replace = true]) {
|
void set(String key, dynamic values, [bool replace = true]) {
|
||||||
key = key.toLowerCase();
|
key = key.toLowerCase();
|
||||||
List<String?> valueList;
|
List<String?> valueList;
|
||||||
|
@ -111,17 +252,39 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the HTTP header is defined.
|
/// Checks if a specific HTTP header is present in the HeaderBag.
|
||||||
|
///
|
||||||
|
/// This method determines whether a header with the given [key] exists
|
||||||
|
/// in the HeaderBag. The header name (key) is case-insensitive.
|
||||||
|
///
|
||||||
|
/// @param key The name of the HTTP header to check for.
|
||||||
|
/// @return true if the header exists, false otherwise.
|
||||||
bool hasHeader(String key) {
|
bool hasHeader(String key) {
|
||||||
return _headers.containsKey(key.toLowerCase());
|
return _headers.containsKey(key.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the given HTTP header contains the given value.
|
/// Checks if a specific HTTP header contains a given value.
|
||||||
|
///
|
||||||
|
/// This method determines whether the header specified by [key] contains
|
||||||
|
/// the given [value]. The header name (key) is case-insensitive.
|
||||||
|
///
|
||||||
|
/// @param key The name of the HTTP header to check.
|
||||||
|
/// @param value The value to search for in the header.
|
||||||
|
/// @return true if the header contains the value, false otherwise.
|
||||||
|
/// If the header doesn't exist, this method returns false.
|
||||||
bool containsHeaderValue(String key, String value) {
|
bool containsHeaderValue(String key, String value) {
|
||||||
return _headers[key.toLowerCase()]?.contains(value) ?? false;
|
return _headers[key.toLowerCase()]?.contains(value) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a header.
|
/// Removes a header from the HeaderBag.
|
||||||
|
///
|
||||||
|
/// This method removes the header specified by [key] from the HeaderBag.
|
||||||
|
/// The key is case-insensitive and will be converted to lowercase before removal.
|
||||||
|
///
|
||||||
|
/// If the removed header is 'cache-control', this method also clears
|
||||||
|
/// the internal cache control directives.
|
||||||
|
///
|
||||||
|
/// @param key The name of the header to remove.
|
||||||
void remove(String key) {
|
void remove(String key) {
|
||||||
key = key.toLowerCase();
|
key = key.toLowerCase();
|
||||||
_headers.remove(key);
|
_headers.remove(key);
|
||||||
|
@ -132,6 +295,17 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
|
|
||||||
/// Returns the HTTP header value converted to a date.
|
/// Returns the HTTP header value converted to a date.
|
||||||
///
|
///
|
||||||
|
/// This method retrieves the value of the specified HTTP header and attempts
|
||||||
|
/// to parse it as a DateTime object. If the header doesn't exist or its value
|
||||||
|
/// is null, the method returns the provided default value.
|
||||||
|
///
|
||||||
|
/// @param key The name of the HTTP header to retrieve and parse as a date.
|
||||||
|
/// @param defaultValue An optional DateTime object to return if the header
|
||||||
|
/// doesn't exist or its value is null. Defaults to null if not specified.
|
||||||
|
/// @return A DateTime object representing the parsed header value, or the
|
||||||
|
/// default value if the header doesn't exist or its value is null.
|
||||||
|
/// @throws Exception if the header value cannot be parsed as a valid date.
|
||||||
|
///
|
||||||
/// Throws an exception when the HTTP header is not parseable.
|
/// Throws an exception when the HTTP header is not parseable.
|
||||||
DateTime? getDate(String key, [DateTime? defaultValue]) {
|
DateTime? getDate(String key, [DateTime? defaultValue]) {
|
||||||
var value = get(key);
|
var value = get(key);
|
||||||
|
@ -147,36 +321,83 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a custom Cache-Control directive.
|
/// Adds a custom Cache-Control directive.
|
||||||
|
///
|
||||||
|
/// This method adds a new directive to the Cache-Control header or updates
|
||||||
|
/// an existing one. The directive is specified by [key], and an optional
|
||||||
|
/// [value] can be provided.
|
||||||
|
///
|
||||||
|
/// @param key The name of the Cache-Control directive to add or update.
|
||||||
|
/// @param value The value of the directive. Defaults to true if not specified.
|
||||||
|
///
|
||||||
|
/// After updating the internal cache control directives, this method
|
||||||
|
/// regenerates the Cache-Control header string and sets it using the `set` method.
|
||||||
void addCacheControlDirective(String key, [dynamic value = true]) {
|
void addCacheControlDirective(String key, [dynamic value = true]) {
|
||||||
_cacheControl[key] = value;
|
_cacheControl[key] = value;
|
||||||
set('Cache-Control', getCacheControlHeader());
|
set('Cache-Control', getCacheControlHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the Cache-Control directive is defined.
|
/// Checks if a specific Cache-Control directive is present.
|
||||||
|
///
|
||||||
|
/// This method determines whether a Cache-Control directive with the given [key]
|
||||||
|
/// exists in the internal cache control directives map.
|
||||||
|
///
|
||||||
|
/// @param key The name of the Cache-Control directive to check for.
|
||||||
|
/// @return true if the directive exists, false otherwise.
|
||||||
bool hasCacheControlDirective(String key) {
|
bool hasCacheControlDirective(String key) {
|
||||||
return _cacheControl.containsKey(key);
|
return _cacheControl.containsKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Cache-Control directive value by name.
|
/// Returns the value of a specific Cache-Control directive.
|
||||||
|
///
|
||||||
|
/// This method retrieves the value associated with the given Cache-Control
|
||||||
|
/// directive [key] from the internal cache control directives map.
|
||||||
|
///
|
||||||
|
/// @param key The name of the Cache-Control directive to retrieve.
|
||||||
|
/// @return The value of the specified Cache-Control directive, or null if
|
||||||
|
/// the directive doesn't exist. The return type is dynamic as
|
||||||
|
/// Cache-Control directive values can be of various types.
|
||||||
dynamic getCacheControlDirective(String key) {
|
dynamic getCacheControlDirective(String key) {
|
||||||
return _cacheControl[key];
|
return _cacheControl[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a Cache-Control directive.
|
/// Removes a Cache-Control directive.
|
||||||
|
///
|
||||||
|
/// This method removes the specified Cache-Control directive from the internal
|
||||||
|
/// cache control directives map and updates the Cache-Control header accordingly.
|
||||||
|
///
|
||||||
|
/// @param key The name of the Cache-Control directive to remove.
|
||||||
|
///
|
||||||
|
/// After removing the directive from the internal map, this method regenerates
|
||||||
|
/// the Cache-Control header string and sets it using the `set` method.
|
||||||
void removeCacheControlDirective(String key) {
|
void removeCacheControlDirective(String key) {
|
||||||
_cacheControl.remove(key);
|
_cacheControl.remove(key);
|
||||||
set('Cache-Control', getCacheControlHeader());
|
set('Cache-Control', getCacheControlHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator for headers.
|
/// Returns an iterator for the headers.
|
||||||
///
|
///
|
||||||
/// @return An iterator of MapEntry.
|
/// This method provides an iterator that allows iteration over all headers
|
||||||
|
/// in the HeaderBag. Each iteration yields a MapEntry where the key is the
|
||||||
|
/// header name (as a String) and the value is a List of String? representing
|
||||||
|
/// the header values.
|
||||||
|
///
|
||||||
|
/// This implementation directly returns the iterator of the internal _headers
|
||||||
|
/// map entries, allowing for efficient iteration over all headers.
|
||||||
|
///
|
||||||
|
/// @return An Iterator<MapEntry<String, List<String?>>> for iterating over
|
||||||
|
/// all headers in the HeaderBag.
|
||||||
@override
|
@override
|
||||||
Iterator<MapEntry<String, List<String?>>> get iterator {
|
Iterator<MapEntry<String, List<String?>>> get iterator {
|
||||||
return _headers.entries.iterator;
|
return _headers.entries.iterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of headers.
|
/// Returns the number of headers.
|
||||||
|
///
|
||||||
|
/// This getter provides the count of unique headers in the HeaderBag.
|
||||||
|
/// It directly returns the length of the internal _headers map,
|
||||||
|
/// which represents the number of distinct header names stored.
|
||||||
|
///
|
||||||
|
/// @return An integer representing the number of headers in the HeaderBag.
|
||||||
@override
|
@override
|
||||||
int get length {
|
int get length {
|
||||||
return _headers.length;
|
return _headers.length;
|
||||||
|
@ -184,15 +405,38 @@ class HeaderBag extends IterableBase<MapEntry<String, List<String?>>> {
|
||||||
|
|
||||||
/// Generates the Cache-Control header value.
|
/// Generates the Cache-Control header value.
|
||||||
///
|
///
|
||||||
/// @return A string representation of the Cache-Control header.
|
/// This method creates a string representation of the Cache-Control header
|
||||||
|
/// based on the directives stored in the internal _cacheControl map.
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Creates a sorted copy of the _cacheControl map using a SplayTreeMap.
|
||||||
|
/// 2. Iterates through the entries of the sorted map.
|
||||||
|
/// 3. Formats each entry as "key=value".
|
||||||
|
/// 4. Joins all formatted entries with ", " as separator.
|
||||||
|
///
|
||||||
|
/// @return A string representation of the Cache-Control header, where
|
||||||
|
/// directives are sorted alphabetically by key and separated by commas.
|
||||||
|
/// For example: "max-age=300, must-revalidate, no-cache".
|
||||||
String getCacheControlHeader() {
|
String getCacheControlHeader() {
|
||||||
var sortedCacheControl = SplayTreeMap<String, dynamic>.from(_cacheControl);
|
var sortedCacheControl = SplayTreeMap<String, dynamic>.from(_cacheControl);
|
||||||
return sortedCacheControl.entries.map((e) => '${e.key}=${e.value}').join(', ');
|
return sortedCacheControl.entries.map((e) => '${e.key}=${e.value}').join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a Cache-Control HTTP header.
|
/// Parses a Cache-Control HTTP header string into a map of directives.
|
||||||
///
|
///
|
||||||
/// @return A map of Cache-Control directives.
|
/// This method takes a Cache-Control header value as a string and converts it
|
||||||
|
/// into a map where keys are directive names and values are directive values.
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Splits the header string by commas to separate individual directives.
|
||||||
|
/// 2. For each directive, splits by '=' to separate the name and value.
|
||||||
|
/// 3. Trims whitespace from directive names and values.
|
||||||
|
/// 4. If a directive has no value (no '='), it's set to true.
|
||||||
|
///
|
||||||
|
/// @param header A string containing the Cache-Control header value.
|
||||||
|
/// @return A Map<String, dynamic> where keys are directive names (String)
|
||||||
|
/// and values are either String (for directives with values) or
|
||||||
|
/// bool (true for directives without values).
|
||||||
Map<String, dynamic> _parseCacheControl(String header) {
|
Map<String, dynamic> _parseCacheControl(String header) {
|
||||||
var parts = header.split(',').map((e) => e.split('=')).toList();
|
var parts = header.split(',').map((e) => e.split('=')).toList();
|
||||||
var map = <String, dynamic>{};
|
var map = <String, dynamic>{};
|
||||||
|
|
|
@ -1,24 +1,62 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
* This file is a port of the symfony HeaderUtils.php class to Dart
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
* (C) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class HeaderUtils {
|
class HeaderUtils {
|
||||||
|
|
||||||
|
/// A constant string representing the "attachment" disposition type.
|
||||||
|
///
|
||||||
|
/// This value is used in HTTP headers, particularly in the Content-Disposition
|
||||||
|
/// header, to indicate that the content is expected to be downloaded and saved
|
||||||
|
/// locally by the user agent, rather than being displayed inline in the browser.
|
||||||
static const String DISPOSITION_ATTACHMENT = 'attachment';
|
static const String DISPOSITION_ATTACHMENT = 'attachment';
|
||||||
|
|
||||||
|
/// A constant string representing the "inline" disposition type.
|
||||||
|
///
|
||||||
|
/// This value is used in HTTP headers, particularly in the Content-Disposition
|
||||||
|
/// header, to indicate that the content is expected to be displayed inline
|
||||||
|
/// in the browser, rather than being downloaded and saved locally.
|
||||||
static const String DISPOSITION_INLINE = 'inline';
|
static const String DISPOSITION_INLINE = 'inline';
|
||||||
|
|
||||||
// This class should not be instantiated.
|
/// Private constructor to prevent instantiation of the HeaderUtils class.
|
||||||
|
///
|
||||||
|
/// This class is intended to be used as a utility class with static methods only.
|
||||||
|
/// The underscore before the constructor name makes it private to this library.
|
||||||
HeaderUtils._();
|
HeaderUtils._();
|
||||||
|
|
||||||
/// Splits an HTTP header by one or more separators.
|
/// Splits an HTTP header by one or more separators.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// This method parses a given HTTP header string and splits it into parts
|
||||||
|
/// based on the provided separators. It handles quoted strings and tokens
|
||||||
|
/// according to HTTP header specifications.
|
||||||
///
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [header]: The HTTP header string to be split.
|
||||||
|
/// - [separators]: A string containing one or more separator characters.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A List of Lists of Strings, where each inner List represents a part of
|
||||||
|
/// the header split by the separators.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if [separators] is empty.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
/// HeaderUtils.split('da, en-gb;q=0.8', ',;')
|
/// HeaderUtils.split('da, en-gb;q=0.8', ',;')
|
||||||
/// // => [['da'], ['en-gb', 'q=0.8']]
|
/// // => [['da'], ['en-gb', 'q=0.8']]
|
||||||
///
|
///
|
||||||
/// @param String separators List of characters to split on, ordered by
|
/// The method uses regular expressions to handle complex cases such as
|
||||||
/// precedence, e.g. ',', ';=', or ',;='
|
/// quoted strings and multiple separators. It preserves the structure of
|
||||||
///
|
/// the original header while splitting it into logical parts.
|
||||||
/// @return List<List<String>> Nested array with as many levels as there are characters in
|
|
||||||
/// separators
|
|
||||||
static List<List<String>> split(String header, String separators) {
|
static List<List<String>> split(String header, String separators) {
|
||||||
if (separators.isEmpty) {
|
if (separators.isEmpty) {
|
||||||
throw ArgumentError('At least one separator must be specified.');
|
throw ArgumentError('At least one separator must be specified.');
|
||||||
|
@ -57,8 +95,16 @@ class HeaderUtils {
|
||||||
/// will be used as the values, or true if the nested array only contains one
|
/// will be used as the values, or true if the nested array only contains one
|
||||||
/// element. Array keys are lowercased.
|
/// element. Array keys are lowercased.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Parameters:
|
||||||
|
/// - [parts]: A List of Lists of Strings, where each inner List represents a part
|
||||||
|
/// to be combined into the associative array.
|
||||||
///
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map<String, dynamic> where the keys are the lowercased first elements of each
|
||||||
|
/// inner List, and the values are either the second elements or true if there's no
|
||||||
|
/// second element.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
/// HeaderUtils.combine([['foo', 'abc'], ['bar']])
|
/// HeaderUtils.combine([['foo', 'abc'], ['bar']])
|
||||||
/// // => {'foo': 'abc', 'bar': true}
|
/// // => {'foo': 'abc', 'bar': true}
|
||||||
static Map<String, dynamic> combine(List<List<String>> parts) {
|
static Map<String, dynamic> combine(List<List<String>> parts) {
|
||||||
|
@ -73,12 +119,21 @@ class HeaderUtils {
|
||||||
|
|
||||||
/// Joins an associative array into a string for use in an HTTP header.
|
/// Joins an associative array into a string for use in an HTTP header.
|
||||||
///
|
///
|
||||||
/// The key and value of each entry are joined with '=', and all entries
|
/// This method takes a Map of key-value pairs and joins them into a single string,
|
||||||
/// are joined with the specified separator and an additional space (for
|
/// suitable for use in an HTTP header. Each key-value pair is formatted as follows:
|
||||||
/// readability). Values are quoted if necessary.
|
/// - If the value is `true`, only the key is included.
|
||||||
|
/// - Otherwise, the pair is formatted as "key=value", where the value is quoted if necessary.
|
||||||
|
///
|
||||||
|
/// The formatted pairs are then joined with the specified separator and an additional space.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [assoc]: A Map<String, dynamic> containing the key-value pairs to be joined.
|
||||||
|
/// - [separator]: A String used to separate the formatted pairs in the output.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String representing the joined key-value pairs, suitable for use in an HTTP header.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
|
||||||
/// HeaderUtils.headerToString({'foo': 'abc', 'bar': true, 'baz': 'a b c'}, ',')
|
/// HeaderUtils.headerToString({'foo': 'abc', 'bar': true, 'baz': 'a b c'}, ',')
|
||||||
/// // => 'foo=abc, bar, baz="a b c"'
|
/// // => 'foo=abc, bar, baz="a b c"'
|
||||||
static String headerToString(Map<String, dynamic> assoc, String separator) {
|
static String headerToString(Map<String, dynamic> assoc, String separator) {
|
||||||
|
@ -93,11 +148,28 @@ class HeaderUtils {
|
||||||
return parts.join('$separator ');
|
return parts.join('$separator ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes a string as a quoted string, if necessary.
|
/// Quotes a string for use in HTTP headers if necessary.
|
||||||
///
|
///
|
||||||
/// If a string contains characters not allowed by the "token" construct in
|
/// This method takes a string and determines whether it needs to be quoted
|
||||||
/// the HTTP specification, it is backslash-escaped and enclosed in quotes
|
/// for use in HTTP headers. If quoting is necessary, it encloses the string
|
||||||
/// to match the "quoted-string" construct.
|
/// in double quotes and escapes any existing double quotes within the string.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [s]: The input string to be quoted if necessary.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String that is either:
|
||||||
|
/// - The original string if it doesn't need quoting
|
||||||
|
/// - The string enclosed in double quotes with internal quotes escaped
|
||||||
|
/// - An empty quoted string ('""') if the input is an empty string
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if the input string is null.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// quote('simple') // => 'simple'
|
||||||
|
/// quote('needs "quotes"') // => '"needs \"quotes\""'
|
||||||
|
/// quote('') // => '""'
|
||||||
static String quote(String? s) {
|
static String quote(String? s) {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
throw ArgumentError('Input string cannot be null');
|
throw ArgumentError('Input string cannot be null');
|
||||||
|
@ -116,30 +188,79 @@ class HeaderUtils {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines if a string can be used unquoted in HTTP headers.
|
||||||
|
///
|
||||||
|
/// This method checks if the given string consists only of characters
|
||||||
|
/// that are allowed in unquoted header values according to HTTP specifications.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [s]: The string to be checked.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// - `true` if the string can be used unquoted in HTTP headers.
|
||||||
|
/// - `false` if the string needs to be quoted for use in HTTP headers.
|
||||||
|
///
|
||||||
|
/// The allowed characters are:
|
||||||
|
/// - Alphanumeric characters (a-z, A-Z, 0-9)
|
||||||
|
/// - The following special characters: !#$%&'*+-\.^_`|~
|
||||||
|
///
|
||||||
|
/// This method is typically used internally by other header-related functions
|
||||||
|
/// to determine whether a value needs quoting before being included in an HTTP header.
|
||||||
static bool _isQuotingAllowed(String s) {
|
static bool _isQuotingAllowed(String s) {
|
||||||
final pattern = RegExp('^[a-zA-Z0-9!#\$%&\'*+\\-\\.^_`|~]+\$');
|
final pattern = RegExp('^[a-zA-Z0-9!#\$%&\'*+\\-\\.^_`|~]+\$');
|
||||||
return pattern.hasMatch(s);
|
return pattern.hasMatch(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes a quoted string.
|
/// Removes quotes and unescapes characters in a string.
|
||||||
///
|
///
|
||||||
/// If passed an unquoted string that matches the "token" construct (as
|
/// This method processes a string that may have been quoted or contain
|
||||||
/// defined in the HTTP specification), it is passed through verbatim.
|
/// escaped characters. It performs the following operations:
|
||||||
|
/// 1. Removes surrounding double quotes if present.
|
||||||
|
/// 2. Unescapes any escaped characters (i.e., removes the backslash).
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [s]: The input string to be unquoted and unescaped.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String with quotes removed and escaped characters processed.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// unquote('"Hello \\"World\\""') // => 'Hello "World"'
|
||||||
|
/// unquote('No \\"quotes\\"') // => 'No "quotes"'
|
||||||
static String unquote(String s) {
|
static String unquote(String s) {
|
||||||
return s.replaceAllMapped(RegExp(r'\\(.)|\"'), (match) => match[1] ?? '');
|
return s.replaceAllMapped(RegExp(r'\\(.)|\"'), (match) => match[1] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an HTTP Content-Disposition field-value.
|
/// Generates an HTTP Content-Disposition header value.
|
||||||
///
|
///
|
||||||
/// @param String disposition One of "inline" or "attachment"
|
/// This method creates a properly formatted Content-Disposition header value
|
||||||
/// @param String filename A unicode string
|
/// based on the given disposition type and filename. It supports both ASCII
|
||||||
/// @param String filenameFallback A string containing only ASCII characters that
|
/// and non-ASCII filenames, providing a fallback for older user agents.
|
||||||
/// is semantically equivalent to filename. If the filename is already ASCII,
|
|
||||||
/// it can be omitted, or just copied from filename
|
|
||||||
///
|
///
|
||||||
/// @throws ArgumentError
|
/// Parameters:
|
||||||
|
/// - [disposition]: The disposition type, must be either "attachment" or "inline".
|
||||||
|
/// - [filename]: The filename to be used in the Content-Disposition header.
|
||||||
|
/// - [filenameFallback]: An optional ASCII-only fallback filename for older user agents.
|
||||||
|
/// If not provided, it defaults to the same value as [filename].
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String representing the formatted Content-Disposition header value.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if:
|
||||||
|
/// - The disposition is neither "attachment" nor "inline".
|
||||||
|
/// - The filename fallback contains non-ASCII characters.
|
||||||
|
/// - The filename fallback contains the "%" character.
|
||||||
|
/// - Either filename or fallback contains "/" or "\" characters.
|
||||||
///
|
///
|
||||||
/// @see RFC 6266
|
/// @see RFC 6266
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// makeDisposition('attachment', 'example.pdf')
|
||||||
|
/// // => 'attachment; filename="example.pdf"'
|
||||||
|
///
|
||||||
|
/// makeDisposition('inline', 'résumé.pdf', 'resume.pdf')
|
||||||
|
/// // => 'inline; filename="resume.pdf"; filename*=utf-8\'\'r%C3%A9sum%C3%A9.pdf'
|
||||||
static String makeDisposition(String disposition, String filename, [String filenameFallback = '']) {
|
static String makeDisposition(String disposition, String filename, [String filenameFallback = '']) {
|
||||||
if (![DISPOSITION_ATTACHMENT, DISPOSITION_INLINE].contains(disposition)) {
|
if (![DISPOSITION_ATTACHMENT, DISPOSITION_INLINE].contains(disposition)) {
|
||||||
throw ArgumentError('The disposition must be either "$DISPOSITION_ATTACHMENT" or "$DISPOSITION_INLINE".');
|
throw ArgumentError('The disposition must be either "$DISPOSITION_ATTACHMENT" or "$DISPOSITION_INLINE".');
|
||||||
|
@ -168,6 +289,36 @@ static bool _isQuotingAllowed(String s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like parse_str(), but preserves dots in variable names.
|
/// Like parse_str(), but preserves dots in variable names.
|
||||||
|
/// Parses a query string into a Map of key-value pairs.
|
||||||
|
///
|
||||||
|
/// This method takes a query string and converts it into a Map where the keys
|
||||||
|
/// are the query parameters and the values are their corresponding values.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [query]: The query string to parse.
|
||||||
|
/// - [ignoreBrackets]: If true, treats square brackets as part of the parameter name.
|
||||||
|
/// Defaults to false.
|
||||||
|
/// - [separator]: The character used to separate key-value pairs in the query string.
|
||||||
|
/// Defaults to '&'.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map<String, dynamic> where keys are the parameter names and values are the
|
||||||
|
/// corresponding parameter values.
|
||||||
|
///
|
||||||
|
/// If [ignoreBrackets] is false (default), the method handles parameters with square
|
||||||
|
/// brackets specially, decoding them from base64 and including the bracket content
|
||||||
|
/// in the resulting key.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// parseQuery('foo=bar&baz=qux')
|
||||||
|
/// // => {'foo': 'bar', 'baz': 'qux'}
|
||||||
|
///
|
||||||
|
/// parseQuery('foo[]=bar&foo[]=baz', false, '&')
|
||||||
|
/// // => {'foo[]': 'bar', 'foo[]': 'baz'}
|
||||||
|
///
|
||||||
|
/// Note: This method includes some specific handling for the character '0' in keys
|
||||||
|
/// and values, truncating strings at this character. It also trims whitespace from
|
||||||
|
/// the left side of keys. This is like parse_str(), but preserves dots in variable names.
|
||||||
static Map<String, dynamic> parseQuery(String query, [bool ignoreBrackets = false, String separator = '&']) {
|
static Map<String, dynamic> parseQuery(String query, [bool ignoreBrackets = false, String separator = '&']) {
|
||||||
final result = <String, dynamic>{};
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
|
@ -213,7 +364,26 @@ static Map<String, dynamic> parseQuery(String query, [bool ignoreBrackets = fals
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Groups parts of a header string based on specified separators.
|
||||||
|
///
|
||||||
|
/// This recursive method processes a list of [RegExpMatch] objects, grouping them
|
||||||
|
/// based on the provided [separators]. It handles nested structures in header strings.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [matches]: A list of [RegExpMatch] objects representing parts of the header.
|
||||||
|
/// - [separators]: A string containing characters used as separators.
|
||||||
|
/// - [first]: A boolean indicating if this is the first call in the recursion (default: true).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A List of Lists of Strings, where each inner List represents a grouped part of the header.
|
||||||
|
///
|
||||||
|
/// The method works by:
|
||||||
|
/// 1. Splitting the parts based on the first separator in the [separators] string.
|
||||||
|
/// 2. Recursively processing subgroups if more separators are available.
|
||||||
|
/// 3. Handling special cases for the last separator and quoted strings.
|
||||||
|
///
|
||||||
|
/// This method is typically used internally by the [split] method to process complex
|
||||||
|
/// header structures with multiple levels of separators.
|
||||||
static List<List<String>> _groupParts(List<RegExpMatch> matches, String separators, [bool first = true]) {
|
static List<List<String>> _groupParts(List<RegExpMatch> matches, String separators, [bool first = true]) {
|
||||||
final separator = separators[0];
|
final separator = separators[0];
|
||||||
separators = separators.substring(1);
|
separators = separators.substring(1);
|
||||||
|
|
Loading…
Reference in a new issue