assetPath method

Future<String> assetPath(
  1. String staticPath
)

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;
}