Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
c76aaba
feat(headers): add Token interface with tchar validation
nielsenko May 19, 2026
b3af5b3
feat(headers): add HeaderScanner with token/quoted-string readers and…
nielsenko May 19, 2026
da5c7f4
feat(headers): add DeltaSeconds primitive for non-negative second counts
nielsenko May 19, 2026
82dce6e
feat(headers): add Host primitive (uri-host with optional port)
nielsenko May 19, 2026
1585b96
feat(headers): add Origin primitive with explicit opaque (null) sentinel
nielsenko May 19, 2026
b75108a
feat(headers): add LanguageTag primitive (BCP 47)
nielsenko May 19, 2026
c6ada52
feat(headers): add ETagValue primitive with etagc validation and matc…
nielsenko May 19, 2026
6dbb698
feat(headers): add ParameterValue primitive (token/quoted-string)
nielsenko May 19, 2026
d7fb2c9
fix(headers): enforce Content-Range invariants and stop null leaking …
nielsenko May 19, 2026
2102039
fix(headers): HostHeader.fromUri keeps null port for default-port URIs
nielsenko May 19, 2026
28b8ac8
fix(headers): Access-Control-Allow-Origin uses Origin with explicit n…
nielsenko May 19, 2026
87ceb1b
fix(headers): Set-Cookie Domain uses Host, Path uses String
nielsenko May 19, 2026
5bff938
fix(headers): Upgrade protocol version is a Token, not a double
nielsenko May 19, 2026
bb4494a
fix(headers): Digest encoder emits algorithm/qop/nc/stale as bare tokens
nielsenko May 19, 2026
442a124
fix(headers): Permissions-Policy emits origins as sf-strings
nielsenko May 19, 2026
ac4432e
fix(headers): Accept parses q-value as a named parameter
nielsenko May 19, 2026
930bf68
fix(headers): Accept-Encoding tolerates OWS around q-value
nielsenko May 19, 2026
3fbbeb1
fix(headers): Accept-Language tolerates OWS around q-value
nielsenko May 19, 2026
22a6484
fix(headers): TE tolerates OWS around q-value
nielsenko May 19, 2026
3ea6965
fix(headers): remove non-spec 'nested-navigate' from Sec-Fetch-Mode
nielsenko May 19, 2026
615405d
fix(headers): Clear-Site-Data recognizes clientHints value
nielsenko May 19, 2026
ad4b2a9
fix(headers): If-Range rejects weak ETags
nielsenko May 19, 2026
2f6e7a4
fix(headers): Cookie splits on first '=' and preserves opaque values
nielsenko May 19, 2026
1aa0d65
fix(headers): Expect preserves unknown expectations
nielsenko May 19, 2026
15d14d6
fix(headers): harden Set-Cookie parser/encoder (positional parse, lis…
nielsenko May 19, 2026
c00b879
fix(headers): correct Digest scheme separator, token params, and quoting
nielsenko May 28, 2026
4bd91f6
fix(headers): HostHeader.fromUri preserves IPv6 brackets
nielsenko May 28, 2026
1409768
fix(headers): anchor Content-Range parse regex
nielsenko May 28, 2026
b4db972
fix(headers): Permissions-Policy quote-aware parse and CTL-safe render
nielsenko May 28, 2026
1d7c77d
fix(headers): Expect rejects control characters
nielsenko May 28, 2026
5404076
fix(headers): validate IPv6 literals in Host; reject userinfo/control…
nielsenko May 28, 2026
22083c1
fix(headers): DeltaSeconds.parse clamps over-large values
nielsenko May 28, 2026
ea660dd
fix(headers): distinct hashCodes for OpaqueOrigin and Allow-Origin wi…
nielsenko May 28, 2026
e869267
test(headers): move Transfer-Encoding parse/encode cases to relic_cor…
nielsenko May 28, 2026
f5cbd8e
fix(headers): Host rejects control characters and invalid host charac…
nielsenko Jun 10, 2026
aa1038a
fix(headers): remove non-spec clientHints from Clear-Site-Data
nielsenko Jun 10, 2026
f25935d
fix(headers): Content-Range rejects overflowing numeric fields
nielsenko Jun 10, 2026
8b3f950
fix(headers): Permissions-Policy rejects unbalanced inner-list and em…
nielsenko Jun 10, 2026
5204dfc
fix(headers): Set-Cookie encoder always emits the cookie-pair (empty-…
nielsenko Jun 10, 2026
8bfc038
fix(headers): Digest validates unquoted auth-param values as tokens
nielsenko Jun 10, 2026
177d122
fix(headers): HostHeader factory rejects empty host and uses FormatEx…
nielsenko Jun 10, 2026
05f4387
refactor(headers): share q-value formatter and truncate to 3 digits
nielsenko Jun 10, 2026
ea704ef
fix(headers): Expect allows HTAB; correct LanguageTag docstring example
nielsenko Jun 10, 2026
764f031
fix(headers): Content-Encoding accepts any token coding, case-insensi…
nielsenko Jun 10, 2026
fb41b20
fix(headers): Accept-Ranges models a list of range units
nielsenko Jun 10, 2026
dd24d78
fix(headers): Transfer-Encoding case-insensitive; reject chunked-not-…
nielsenko Jun 10, 2026
8635cc8
fix(headers): Authorization case-insensitive scheme dispatch and allo…
nielsenko Jun 10, 2026
767ec51
fix(headers): HSTS case-insensitive directives, quoted and non-negati…
nielsenko Jun 10, 2026
aba8492
fix(headers): Referrer-Policy parses a token list and keeps the last …
nielsenko Jun 10, 2026
e887dc3
fix(headers): COEP/COOP accept report-to parameter; COOP adds noopene…
nielsenko Jun 10, 2026
cef1b7d
fix(headers): Content-Language validates via LanguageTag (full BCP 47)
nielsenko Jun 10, 2026
27b59ca
fix(headers): Retry-After delay rejects signs via delta-seconds parsing
nielsenko Jun 10, 2026
6575e31
test(headers): Range accepts inverted byte ranges; consumer returns 416
nielsenko Jun 10, 2026
a4896b9
fix(headers): Cache-Control name-exact directives, reject negative de…
nielsenko Jun 10, 2026
24c0333
fix(headers): Vary field names compare case-insensitively
nielsenko Jun 10, 2026
0daaa96
fix(headers): CORS Allow-Headers/Expose-Headers compare field names c…
nielsenko Jun 10, 2026
a6d5593
fix(headers): CSP directive names compare case-insensitively
nielsenko Jun 10, 2026
c266ee7
fix(io): copy Transfer-Encoding codings into a growable list before a…
nielsenko Jun 11, 2026
ba4ab84
fix(headers): Cookie preserves duplicate cookie names instead of reje…
nielsenko Jun 11, 2026
a6351fe
fix(headers): malformed q-value defaults to weight 1.0 instead of rej…
nielsenko Jun 12, 2026
2c849df
test(headers): unit-cover TE/Connection validation that dart:io 3.13 …
nielsenko Jun 12, 2026
7e37353
fix(headers): If-Range/From/Cache-Control degrade on malformed input;…
nielsenko Jun 12, 2026
5ef80b7
fix(headers): From is a single mailbox (RFC 9110 10.1.2), not a list
nielsenko Jun 12, 2026
e86fd2f
fix(headers): ETagHeader parses via the ETagValue primitive (etagc va…
nielsenko Jun 12, 2026
55aee1d
fix(headers): reject control characters when serializing Digest and r…
nielsenko Jun 12, 2026
67715f8
fix(headers): Digest nc must be exactly 8 hex digits (RFC 7616)
nielsenko Jun 12, 2026
8e48689
fix(headers): Content-Range constructor rejects negative members
nielsenko Jun 12, 2026
e7edbbf
fix(headers): Accept-Ranges rejects none combined with other range units
nielsenko Jun 12, 2026
8b95d8b
fix(headers): Permissions-Policy validates feature names as tokens
nielsenko Jun 12, 2026
2c0f49f
fix(headers): Content-Language stores the canonical-cased language tag
nielsenko Jun 12, 2026
e5d5295
fix(headers): Cache-Control ignores unknown extension directives (RFC…
nielsenko Jun 12, 2026
ec6e241
style(headers): brace the multi-line if in Host IPvFuture validation
nielsenko Jun 12, 2026
0d12293
test(headers): assert path-specific Allow-Origin rejection reason
nielsenko Jun 12, 2026
1367efa
fix(headers): HSTS tolerates OWS around the directive '='
nielsenko Jun 12, 2026
843d531
fix(headers): LanguageTag rejects duplicate variants and extension si…
nielsenko Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/relic/test/headers/header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,22 @@ void main() {
Headers.accessControlAllowOrigin,
Headers.accessControlExposeHeaders,
Headers.accessControlRequestHeaders,
Headers.cacheControl,
Headers.connection,
Headers.contentDisposition,
Headers.contentEncoding,
Headers.contentLanguage,
Headers.contentLocation,
Headers.contentSecurityPolicy,
Headers.expect,
Headers.from,
Headers.host,
Headers.location,
Headers.origin,
Headers.permissionsPolicy,
Headers.referer,
Headers.server,
Headers.setCookie,
Headers.te,
Headers.trailer,
Headers.upgrade,
Expand Down Expand Up @@ -602,7 +608,7 @@ void main() {
Headers.accessControlAllowOrigin,
(final h) =>
h.accessControlAllowOrigin = AccessControlAllowOriginHeader.origin(
origin: Uri.parse('https://example.com'),
origin: Origin.parse('https://example.com'),
),
),
(
Expand Down Expand Up @@ -703,7 +709,7 @@ void main() {
),
(
Headers.contentRange,
(final h) => h.contentRange = ContentRangeHeader(),
(final h) => h.contentRange = ContentRangeHeader(size: 1234),
),
(
Headers.contentSecurityPolicy,
Expand Down Expand Up @@ -739,7 +745,7 @@ void main() {
(Headers.expires, (final h) => h.expires = DateTime.utc(2025, 9, 23)),
(
Headers.from,
(final h) => h.from = FromHeader.emails(['info@serverpod.com']),
(final h) => h.from = const FromHeader('info@serverpod.com'),
),
(Headers.host, (final h) => h.host = HostHeader('www.example.com', 80)),
(
Expand Down
59 changes: 33 additions & 26 deletions packages/relic/test/headers/typed/accept_encoding_header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,24 @@ void main() {
},
);

test('when an Accept-Encoding header with invalid quality values is passed '
'then the server responds with a bad request including a message that '
'states the quality value is invalid', () async {
expect(
getServerRequestHeaders(
test(
'when an Accept-Encoding header with a malformed quality value is '
'passed then the weight defaults to 1.0 instead of being rejected',
() async {
final headers = await getServerRequestHeaders(
server: server,
headers: {'accept-encoding': 'gzip;q=abc'},
touchHeaders: (final h) => h.acceptEncoding,
),
throwsA(
isA<BadRequestException>().having(
(final e) => e.message,
'message',
contains('Invalid quality value'),
),
),
);
});
);

expect(
headers.acceptEncoding?.encodings
.map((final e) => e.quality)
.toList(),
equals([1.0]),
);
},
);

test(
'when an Accept-Encoding header with wildcard (*) and other encodings is '
Expand Down Expand Up @@ -349,18 +349,25 @@ void main() {
});

group(
'when Accept-Encoding headers with invalid quality values are passed',
'when Accept-Encoding headers with a malformed quality value are passed',
() {
test('then it should return null', () async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (_) {},
headers: {'accept-encoding': 'gzip;q=abc, deflate, br'},
);

expect(Headers.acceptEncoding[headers].valueOrNullIfInvalid, isNull);
expect(() => headers.acceptEncoding, throwsInvalidHeader);
});
test(
'then the header still parses and the weight defaults to 1.0',
() async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (_) {},
headers: {'accept-encoding': 'gzip;q=abc, deflate, br'},
);

expect(
headers.acceptEncoding?.encodings
.map((final e) => e.quality)
.toList(),
equals([1.0, 1.0, 1.0]),
);
},
);
},
);
});
Expand Down
70 changes: 29 additions & 41 deletions packages/relic/test/headers/typed/accept_header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,35 +37,26 @@ void main() {
},
);

test(
'when an Accept header with invalid quality value is passed then the server '
'should respond with a bad request including a message that states the '
'quality value is invalid',
() async {
expect(
getServerRequestHeaders(
server: server,
touchHeaders: (final h) => h.accept,
headers: {'accept': 'text/html;q=abc'},
),
throwsA(
isA<BadRequestException>().having(
(final e) => e.message,
'message',
contains('Invalid quality value'),
),
),
);
},
);
test('when an Accept header with a malformed quality value is passed '
'then the weight defaults to 1.0 instead of being rejected', () async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (final h) => h.accept,
headers: {'accept': 'text/html;q=abc'},
);

final mediaRanges = headers.accept?.mediaRanges;
expect(mediaRanges?.length, equals(1));
expect(mediaRanges?[0].quality, equals(1.0));
});

test('when an Accept header with an invalid value is passed '
'then the server does not respond with a bad request if the headers '
'is not actually used', () async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (_) {},
headers: {'accept': 'text/html;q=abc'},
headers: {'accept': 'invalid'},
);

expect(headers, isNotNull);
Expand Down Expand Up @@ -146,25 +137,22 @@ void main() {

group('when multiple Accept media ranges are passed', () {
test(
'with invalid quality values are passed then the server should respond with a bad request '
'including a message that states the quality value is invalid',
'with malformed quality values then those weights default to 1.0 and '
'valid weights are preserved',
() async {
expect(
getServerRequestHeaders(
server: server,
touchHeaders: (final h) => h.accept,
headers: {
'accept': 'text/html;q=test, application/json;q=abc, */*;q=0.5',
},
),
throwsA(
isA<BadRequestException>().having(
(final e) => e.message,
'message',
contains('Invalid quality value'),
),
),
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (final h) => h.accept,
headers: {
'accept': 'text/html;q=test, application/json;q=abc, */*;q=0.5',
},
);

final mediaRanges = headers.accept?.mediaRanges;
expect(mediaRanges?.length, equals(3));
expect(mediaRanges?[0].quality, equals(1.0));
expect(mediaRanges?[1].quality, equals(1.0));
expect(mediaRanges?[2].quality, equals(0.5));
},
);
test(
Expand Down Expand Up @@ -219,12 +207,12 @@ void main() {

tearDown(() => server.close());

group('when an Accept header with invalid quality value is passed', () {
group('when an Accept header with an invalid value is passed', () {
test('then it should return null', () async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (_) {},
headers: {'accept': 'text/html;q=abc'},
headers: {'accept': 'invalid'},
);

expect(Headers.accept[headers].valueOrNullIfInvalid, isNull);
Expand Down
59 changes: 33 additions & 26 deletions packages/relic/test/headers/typed/accept_language_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,24 @@ void main() {
},
);

test('when an Accept-Language header with invalid quality values is passed '
'then the server responds with a bad request including a message that '
'states the quality value is invalid', () async {
expect(
getServerRequestHeaders(
test(
'when an Accept-Language header with a malformed quality value is '
'passed then the weight defaults to 1.0 instead of being rejected',
() async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (final h) => h.acceptLanguage,
headers: {'accept-language': 'en;q=abc'},
),
throwsA(
isA<BadRequestException>().having(
(final e) => e.message,
'message',
contains('Invalid quality value'),
),
),
);
});
);

expect(
headers.acceptLanguage?.languages
.map((final e) => e.quality)
.toList(),
equals([1.0]),
);
},
);

test(
'when an Accept-Language header with wildcard (*) and other languages is '
Expand Down Expand Up @@ -373,18 +373,25 @@ void main() {
});

group(
'when Accept-Language headers with invalid quality values are passed',
'when Accept-Language headers with a malformed quality value are passed',
() {
test('then it should return null', () async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (_) {},
headers: {'accept-language': 'en;q=abc, fr, de'},
);

expect(Headers.acceptLanguage[headers].valueOrNullIfInvalid, isNull);
expect(() => headers.acceptLanguage, throwsInvalidHeader);
});
test(
'then the header still parses and the weight defaults to 1.0',
() async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (_) {},
headers: {'accept-language': 'en;q=abc, fr, de'},
);

expect(
headers.acceptLanguage?.languages
.map((final e) => e.quality)
.toList(),
equals([1.0, 1.0, 1.0]),
);
},
);
},
);
});
Expand Down
21 changes: 19 additions & 2 deletions packages/relic/test/headers/typed/accept_ranges_header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,24 @@ void main() {
headers: {'accept-ranges': 'bytes'},
);

expect(headers.acceptRanges?.rangeUnit, equals('bytes'));
expect(headers.acceptRanges?.rangeUnits, equals(['bytes']));
expect(headers.acceptRanges?.isBytes, isTrue);
},
);

test(
'when multiple range units are passed then they are all parsed',
() async {
final headers = await getServerRequestHeaders(
server: server,
touchHeaders: (final h) => h.acceptRanges,
headers: {'accept-ranges': 'bytes, custom-unit'},
);

expect(
headers.acceptRanges?.rangeUnits,
equals(['bytes', 'custom-unit']),
);
expect(headers.acceptRanges?.isBytes, isTrue);
},
);
Expand All @@ -72,7 +89,7 @@ void main() {
headers: {'accept-ranges': 'none'},
);

expect(headers.acceptRanges?.rangeUnit, equals('none'));
expect(headers.acceptRanges?.rangeUnits, equals(['none']));
expect(headers.acceptRanges?.isNone, isTrue);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void main() {
expect(allowedHeaders?.length, equals(2));
expect(
allowedHeaders,
containsAll(['Content-Type', 'X-Custom-Header']),
containsAll(['content-type', 'x-custom-header']),
);
},
);
Expand Down
Loading
Loading