@remix-run/headers
Version:
A toolkit for working with HTTP headers in JavaScript
85 lines (84 loc) • 3.04 kB
JavaScript
import {} from "./header-value.js";
import { parseHttpDate, removeMilliseconds } from "./utils.js";
import { quoteEtag } from "./utils.js";
/**
* The value of an `If-Range` HTTP header.
*
* The `If-Range` header can contain either an entity tag (ETag) or an HTTP date.
*
* [MDN `If-Range` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Range)
*
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7233#section-3.2)
*/
export class IfRange {
value = '';
/**
* @param init A string or Date to initialize the header
*/
constructor(init) {
if (init) {
if (typeof init === 'string') {
this.value = init;
}
else {
this.value = init.toUTCString();
}
}
}
/**
* Checks if the `If-Range` condition is satisfied for the current resource state.
*
* This method always returns `true` if the `If-Range` header is not present,
* meaning the range request should proceed unconditionally.
*
* The `If-Range` header can contain either:
* - An HTTP date (RFC 7231 IMF-fixdate format)
* - An entity tag (ETag)
*
* When comparing ETags, only strong entity tags are matched as per RFC 7233.
* Weak entity tags (prefixed with `W/`) are never considered a match.
*
* @param resource The current resource state to compare against
* @return `true` if the condition is satisfied, `false` otherwise
*
* @example
* ```ts
* let ifRange = new IfRange('Wed, 21 Oct 2015 07:28:00 GMT')
* ifRange.matches({ lastModified: 1445412480000 }) // true if dates match
* ifRange.matches({ lastModified: new Date('2015-10-21T07:28:00Z') }) // true
*
* let ifRange2 = new IfRange('"abc123"')
* ifRange2.matches({ etag: '"abc123"' }) // true
* ifRange2.matches({ etag: 'W/"abc123"' }) // false (weak ETag)
* ```
*/
matches(resource) {
if (!this.value) {
return true;
}
// Try parsing as HTTP date first
let dateTimestamp = parseHttpDate(this.value);
if (dateTimestamp !== null && resource.lastModified != null) {
return removeMilliseconds(dateTimestamp) === removeMilliseconds(resource.lastModified);
}
// Otherwise treat as ETag
if (resource.etag != null) {
let normalizedTag = quoteEtag(this.value);
let normalizedResourceTag = quoteEtag(resource.etag);
// Weak tags never match in If-Range (strong comparison only, per RFC 7233)
if (normalizedTag.startsWith('W/') || normalizedResourceTag.startsWith('W/')) {
return false;
}
return normalizedTag === normalizedResourceTag;
}
return false;
}
/**
* Returns the string representation of the header value.
*
* @return The header value as a string
*/
toString() {
return this.value;
}
}