initial
This commit is contained in:
45
ts/utils/utils.etag.ts
Normal file
45
ts/utils/utils.etag.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* ETag generation utilities
|
||||
*/
|
||||
|
||||
import type * as plugins from '../plugins.js';
|
||||
|
||||
/**
|
||||
* Generate ETag from file stats
|
||||
* Uses weak ETag format: W/"size-mtime"
|
||||
*/
|
||||
export function generateETag(stat: { size: number; mtime: Date }): string {
|
||||
const mtime = stat.mtime.getTime().toString(16);
|
||||
const size = stat.size.toString(16);
|
||||
return `W/"${size}-${mtime}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate strong ETag from content
|
||||
* Uses hash of content
|
||||
*/
|
||||
export async function generateStrongETag(content: Uint8Array): Promise<string> {
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', content as unknown as ArrayBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
return `"${hashHex.slice(0, 32)}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ETag matches
|
||||
*/
|
||||
export function matchesETag(etag: string, ifNoneMatch: string | null): boolean {
|
||||
if (!ifNoneMatch) return false;
|
||||
|
||||
// Handle multiple ETags
|
||||
const etags = ifNoneMatch.split(',').map(e => e.trim());
|
||||
|
||||
// Wildcard match
|
||||
if (etags.includes('*')) return true;
|
||||
|
||||
// Weak comparison (ignore W/ prefix)
|
||||
const normalizeETag = (e: string) => e.replace(/^W\//, '');
|
||||
const normalizedETag = normalizeETag(etag);
|
||||
|
||||
return etags.some(e => normalizeETag(e) === normalizedETag);
|
||||
}
|
||||
Reference in New Issue
Block a user