assetPath method
Returns the cache-busted URL for the given staticPath.
Example: '/static/logo.svg' → '/static/logo@hash.svg'.
Implementation
Future<String> assetPath(final String staticPath) async {
final cached = _cache[staticPath];
if (cached != null) return cached;
if (!staticPath.startsWith(mountPrefix)) return staticPath;
final relative = staticPath.substring(mountPrefix.length);
final resolvedRootPath = fileSystemRoot.resolveSymbolicLinksSync();
final joinedPath = p.join(resolvedRootPath, relative);
final normalizedPath = p.normalize(joinedPath);
// Reject traversal before hitting the filesystem
if (!p.isWithin(resolvedRootPath, normalizedPath) &&
normalizedPath != resolvedRootPath) {
throw ArgumentError.value(
staticPath,
'staticPath',
'must stay within $mountPrefix',
);
}
// Ensure target exists (files only) before resolving symlinks
final entityType = FileSystemEntity.typeSync(
normalizedPath,
followLinks: false,
);
if (entityType == FileSystemEntityType.notFound ||
entityType == FileSystemEntityType.directory) {
throw PathNotFoundException(
normalizedPath,
const OSError('No such file or directory', 2),
);
}
final resolvedFilePath = File(normalizedPath).resolveSymbolicLinksSync();
if (!p.isWithin(resolvedRootPath, resolvedFilePath)) {
throw ArgumentError.value(
staticPath,
'staticPath',
'must stay within $mountPrefix',
);
}
final info = await getStaticFileInfo(File(resolvedFilePath));
// Build the busted URL using URL path helpers for readability/portability
final directory = p.url.dirname(staticPath);
final baseName = p.url.basenameWithoutExtension(staticPath);
final ext = p.url.extension(staticPath); // includes leading dot or ''
final bustedName = '$baseName$separator${info.etag}$ext';
final bustedPath = directory == '.'
? '/$bustedName'
: p.url.join(directory, bustedName);
_cache[staticPath] = bustedPath;
return bustedPath;
}