parseRangeHeaders function

RangeParseResult parseRangeHeaders(
  1. String? rangeHeader,
  2. String? ifRangeHeader,
  3. DateTime lastModified,
  4. String? etag,
  5. int assetSize,
)

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