parseRangeHeaders function
Pure parser for the Range and If-Range request headers — exposed for
testing. Production callers should use _parseRange which translates
the result into HttpConnect response side effects.
Implementation
RangeParseResult parseRangeHeaders(String? rangeHeader, String? ifRangeHeader,
DateTime lastModified, String? etag, int assetSize) {
if (ifRangeHeader != null) {
//If-Range is either an HTTP-date OR an entity-tag (RFC 7233 §3.2).
DateTime? ifRangeDate;
try {
ifRangeDate = HttpDate.parse(ifRangeHeader);
} catch (_) { //not a date — fall through to etag check
}
if (ifRangeDate != null) {
if (lastModified.isAfter(ifRangeDate.add(_oneSecond)))
return RangeParseResult._serveFull; //dirty
} else if (etag != null
//RFC 7233 §3.2 + RFC 7232 §2.3.2: strong comparison — equal opaque
//tags AND neither side weak. Checking one side's W/ prefix suffices
//since unequal strings are already rejected by the first clause.
&& (etag != ifRangeHeader.trim() || etag.startsWith('W/'))) {
return RangeParseResult._serveFull; //dirty
}
}
if (rangeHeader == null)
return RangeParseResult._serveFull;
if (!rangeHeader.startsWith("bytes="))
return RangeParseResult._badRequest;
final ranges = <({int start, int end, int length})>[];
for (int i = 6;;) {
final j = rangeHeader.indexOf(',', i);
final matches = _reRange.firstMatch(
(j >= 0 ? rangeHeader.substring(i, j): rangeHeader.substring(i)).trim());
if (matches == null)
return RangeParseResult._badRequest;
//Preserve positional info — empty group means "absent on that side"
//(e.g. `bytes=-100` is a suffix range with values[0]=null, values[1]=100).
final values = List<int?>.filled(2, null);
for (int k = 0; k < 2; ++k) {
final match = matches[k + 1]!;
if (match.isNotEmpty) {
final v = int.tryParse(match); //null on overflow only — regex is digits-only
if (v == null) return RangeParseResult._badRequest;
values[k] = v;
}
}
if (values[0] == null && values[1] == null)
return RangeParseResult._badRequest; //reject `bytes=-`
//RFC 7233 §2.1: a suffix-length of 0 → 416 Range Not Satisfiable.
if (values[0] == null && values[1] == 0)
return RangeParseResult._notSatisfiable;
final range = _Range(values[0], values[1], assetSize);
if (!range.validate(assetSize))
return RangeParseResult._notSatisfiable;
ranges.add((start: range.start, end: range.end, length: range.length));
if (j < 0)
break;
i = j + 1;
}
if (ranges.isEmpty)
return RangeParseResult._badRequest;
return RangeParseResult._(ranges, null, false);
}