if-range-parse
v0.1.0
Published
Zero-dependency Node.js HTTP If-Range header parser with dual-format support (ETag + HTTP-date, per RFC 7233)
Downloads
164
Maintainers
Readme
if-range-parse
Zero-dependency Node.js parser, serializer, and comparator for the HTTP
If-Range request header (RFC 7233 §3.2).
The If-Range request header lets a client conditionally request either a
byte range or the full representation, by specifying a precondition tied to
either an ETag or an HTTP-date:
If-Range: "abc123"
If-Range: W/"abc123"
If-Range: Wed, 21 Oct 2015 07:28:00 GMTThis package distinguishes both forms at parse time and returns a typed, discriminated union — so the caller never has to hand-roll the ETag-vs-date split that has caused countless bugs in range-request handlers, resumable download libraries, and CDN edge workers.
Highlights
- Dual-format support — ETag (
"…",W/"…","*") and HTTP-date (IMF-fixdate, RFC 850, asctime) in one parser. - Zero runtime dependencies — pure Node.js, no npm bloat.
- Full TypeScript declarations in
index.d.ts, passestsc --noEmit --strict. - Strict-validity — throws
InvalidIfRangeErroron malformed input;TypeErroron non-string input. - Round-trip serialization —
parse → serialize → parseis stable for all supported forms. - RFC 7232 §2.3.2 strong comparison —
etagMatches(a, b)with wildcard ("*") support.
Install
npm install if-range-parseRequires Node.js ≥ 18.
Usage
import { parseIfRange, serializeIfRange, etagMatches } from 'if-range-parse';
// ETag form — strong ETag
parseIfRange('"abc123"');
// → { kind: 'etag',
// etag: { strong: true, tag: 'abc123' },
// raw: '"abc123"' }
// ETag form — weak ETag
parseIfRange('W/"abc123"');
// → { kind: 'etag',
// etag: { strong: false, tag: 'abc123' },
// raw: 'W/"abc123"' }
// HTTP-date form — IMF-fixdate (preferred)
parseIfRange('Wed, 21 Oct 2015 07:28:00 GMT');
// → { kind: 'date',
// date: 2015-10-21T07:28:00.000Z,
// format: 'IMF-fixdate',
// raw: 'Wed, 21 Oct 2015 07:28:00 GMT' }
// HTTP-date form — RFC 850 (legacy)
parseIfRange('Sunday, 06-Nov-94 08:49:37 GMT');
// → { kind: 'date',
// date: 1994-11-06T08:49:37.000Z,
// format: 'RFC-850',
// raw: 'Sunday, 06-Nov-94 08:49:37 GMT' }
// HTTP-date form — asctime (legacy)
parseIfRange('Thu Nov 6 08:49:37 2014');
// → { kind: 'date',
// date: 2014-11-06T08:49:37.000Z,
// format: 'asctime',
// raw: 'Thu Nov 6 08:49:37 2014' }
// Serialize back to header form
serializeIfRange({ kind: 'etag', tag: 'abc123', strong: false });
// → 'W/"abc123"'
serializeIfRange({
kind: 'date',
date: new Date('2015-10-21T07:28:00Z'),
format: 'IMF-fixdate',
});
// → 'Wed, 21 Oct 2015 07:28:00 GMT'
// RFC 7232 §2.3.2 strong comparison
etagMatches('"abc"', '"abc"'); // true (strong == strong, same tag)
etagMatches('W/"abc"', 'W/"abc"'); // true (weak == weak, same tag)
etagMatches('W/"abc"', '"abc"'); // false (weak never matches strong)
etagMatches('"abc"', 'W/"abc"'); // false
etagMatches('"abc"', '"def"'); // false (different tags)
etagMatches('"*"', '"anything"'); // true (wildcard)API
parseIfRange(header: string): IfRangeResult
Parses an If-Range header value into a discriminated union.
| Field | Type | Notes |
|---|---|---|
| kind | 'etag' or 'date' | Discriminator. |
| etag | { strong, tag, wildcard? } | Present iff kind === 'etag'. |
| date | Date | Present iff kind === 'date'. UTC-anchored, always valid (no NaN). |
| format | 'IMF-fixdate' \| 'RFC-850' \| 'asctime' | Present iff kind === 'date'. |
| raw | string | The exact input string (untrimmed). |
Throws:
InvalidIfRangeErroron malformed header values.TypeErroron non-string input (null,undefined, numbers, objects, …).
serializeIfRange(value): string
Serializes a parsed (or compatible) value back into the header form. The input shape mirrors the parsed result:
{ kind: 'etag', tag: string, strong?: boolean }
{ kind: 'etag', etag: { strong: boolean, tag: string } }
{ kind: 'date', date: Date, format: 'IMF-fixdate' | 'RFC-850' | 'asctime' }Throws InvalidIfRangeError on invalid input.
etagMatches(a: string, b: string): boolean
RFC 7232 §2.3.2 comparison of two ETag header strings. Semantics:
| a | b | result | reason |
|---|---|---|---|
| "x" | "x" | true | strong == strong, same tag |
| W/"x" | W/"x" | true | weak == weak, same tag |
| "x" | W/"x" | false | weak never matches strong |
| W/"x" | "x" | false | weak never matches strong |
| "x" | "y" | false | different tags |
| "*" | "anything" | true | wildcard matches anything |
| "*" | "*" | true | wildcard matches wildcard |
Throws InvalidIfRangeError on malformed ETag input.
InvalidIfRangeError extends Error
Thrown by all three functions on malformed input. The name field is
'InvalidIfRangeError' for instanceof checks.
HTTP-date format detection order
Three HTTP-date formats are accepted (per RFC 7231 §7.1.1.1). They are tried in this priority order:
- IMF-fixdate (preferred) —
Sun, 06 Nov 1994 08:49:37 GMT - RFC 850 (legacy) —
Sunday, 06-Nov-94 08:49:37 GMT - asctime (legacy) —
Sun Nov 6 08:49:37 1994
IMF-fixdate takes priority because some inputs match both the IMF-fixdate and
RFC 850 patterns (e.g. Thu, 01-Jan-00 00:00:00 GMT could be parsed either
way). When both match, IMF-fixdate wins.
RFC 850 year-window rule
RFC 7231 specifies a sliding window for the 2-digit year:
| Two-digit year | Full year | |---|---| | 00–68 | 2000–2068 | | 69–99 | 1969–1999 |
94 → 1994, 00 → 2000, 99 → 1999. This matches common practice for
HTTP date parsing.
Out of scope
This package parses the If-Range request header only. It does not:
- Parse the
Content-Rangeresponse header (usecontent-range-parsefor that). - Parse
If-MatchorIf-None-Match(useetag-parse). - Validate byte-range satisfaction logic — that's the caller's job.
- Read from raw HTTP request objects — pass the header string directly.
Limitations
- asctime timezone handling — RFC 7231 §7.1.1.1 says asctime times are "interpreted as UTC". This implementation follows that. Servers that emit asctime in a local timezone will produce incorrect dates.
- Day-of-week mismatch is tolerated — If the day-of-week name doesn't
match the date (e.g.
Sun, 06 Nov 1994 …where Nov 6 1994 was actually a Sunday — fine, but mismatches are NOT rejected). This matches common practice; tighten if you need strict validation. - No validation that the date is in the past — This is a parser, not a validator.
Project facts
- Test count: 132 tests across
tests/parse.test.js. Run withnpm test. - Source LOC: under 500 (per spec budget).
- Zero runtime dependencies.
- License: MIT.
- Engines: Node.js ≥ 18.
License
MIT — see LICENSE.
