@curveball/kernel
Version:
Curveball is a framework writting in Typescript for Node.js
130 lines (105 loc) • 3.16 kB
text/typescript
import RequestInterface from './request.js';
/**
* This method will check the following request headers:
*
* 1. If-Match
* 2. If-None-Match
* 3. If-Modified-Since
* 4. If-Unmodified-Since
*
* To check these headers,v alues for lastModified and etag should be passed.
*
* The result of this function will return a suggested HTTP status. It will
* return
* * '200' if all the preconditions passed.
* * '304' is a 'Not Modified' status should be returned, and
* * '412' if a 'Precondition failed' error should be returned.
*/
export function conditionalCheck(request: RequestInterface, lastModified: Date|null, etag: string|null): 200 | 304 | 412 {
validateETag(etag);
if (request.headers.has('If-Match')) {
return ifMatchCheck(
request.headers.get('If-Match')!,
etag
);
}
if (request.headers.has('If-None-Match')) {
return ifNoneMatchCheck(
request.method,
request.headers.get('If-None-Match')!,
etag
);
}
if (request.headers.has('If-Modified-Since')) {
return ifModifiedSinceCheck(
request.method,
request.headers.get('If-Modified-Since')!,
lastModified
);
}
if (request.headers.has('If-Unmodified-Since')) {
return ifUnmodifiedSinceCheck(
request.headers.get('If-Unmodified-Since')!,
lastModified
);
}
return 200;
}
function ifMatchCheck(header: string, serverState: string | null): 200 | 412 {
if (header==='*') {
return serverState !== null ? 200 : 412;
}
const headerTags = header.split(',').map( foo => foo.trim() );
for(const tag of headerTags) {
if (tag===serverState) return 200;
}
return 412;
}
function ifNoneMatchCheck(method: string, header: string, serverState: string | null): 200 | 304 | 412 {
let pass;
if (header==='*') {
pass = serverState === null;
} else {
const headerTags = header.split(',').map( foo => foo.trim() );
pass = true;
for(const tag of headerTags) {
if (tag===serverState) {
pass = false;
}
}
}
if (!pass) {
if (method === 'GET' || method === 'HEAD') {
return 304;
} else {
return 412;
}
}
return 200;
}
function ifModifiedSinceCheck(method: string, header: string, serverState: Date|null): 200 | 304 {
if (method !== 'GET' && method !== 'HEAD') {
// Only GET and HEAD are supported, everything else is ignored
return 200;
}
if (serverState === null) {
return 200;
}
const headerDate = new Date(header);
return (serverState.getTime() > headerDate.getTime()) ? 200 : 304;
}
function ifUnmodifiedSinceCheck(header: string, serverState: Date|null): 200 | 412 {
// This is not in any spec, but I believe that we should reject requests
// if we don't know that the Last-Modified value is.
if (serverState === null) {
return 412;
}
const headerDate = new Date(header);
return (serverState.getTime() <= headerDate.getTime()) ? 200 : 412;
}
function validateETag(etag: null|string) {
if (etag===null) return;
if (!/^(W\/)?"[\x21\x23-\x7e]+"$/.test(etag)) {
throw new Error('Etags must be valid. Did you forget the double-quotes?');
}
}