File: Manifest\EmbeddedFilesManifest.cs
Web Access
Project: src\src\FileProviders\Embedded\src\Microsoft.Extensions.FileProviders.Embedded.csproj (Microsoft.Extensions.FileProviders.Embedded)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Shared;
using Microsoft.Extensions.Primitives;
 
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest;
 
internal sealed class EmbeddedFilesManifest
{
    private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars()
        .Where(c => c != Path.DirectorySeparatorChar && c != Path.AltDirectorySeparatorChar).ToArray();
 
    private static readonly char[] _separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
 
    private readonly ManifestDirectory _rootDirectory;
 
    internal EmbeddedFilesManifest(ManifestDirectory rootDirectory)
    {
        ArgumentNullThrowHelper.ThrowIfNull(rootDirectory);
 
        _rootDirectory = rootDirectory;
    }
 
    internal ManifestEntry? ResolveEntry(string path)
    {
        if (string.IsNullOrEmpty(path) || HasInvalidPathChars(path))
        {
            return null;
        }
 
        // trimmed is a string without leading nor trailing path separators
        // so if we find an empty string while iterating over the segments
        // we know for sure the path is invalid and we treat it as the above
        // case by returning null.
        // Examples of invalid paths are: //wwwroot /\wwwroot //wwwroot//jquery.js
        var trimmed = RemoveLeadingAndTrailingDirectorySeparators(path);
        // Paths consisting only of a single path separator like / or \ are ok.
        if (trimmed.Length == 0)
        {
            return _rootDirectory;
        }
 
        var tokenizer = new StringTokenizer(trimmed, _separators);
        ManifestEntry currentEntry = _rootDirectory;
        foreach (var segment in tokenizer)
        {
            if (segment.Equals(""))
            {
                return null;
            }
 
            currentEntry = currentEntry.Traverse(segment);
        }
 
        return currentEntry;
    }
 
    private static StringSegment RemoveLeadingAndTrailingDirectorySeparators(string path)
    {
        Debug.Assert(path.Length > 0);
        var start = Array.IndexOf(_separators, path[0]) == -1 ? 0 : 1;
        if (start == path.Length)
        {
            return StringSegment.Empty;
        }
 
        var end = Array.IndexOf(_separators, path[path.Length - 1]) == -1 ? path.Length : path.Length - 1;
        var trimmed = new StringSegment(path, start, end - start);
        return trimmed;
    }
 
    internal EmbeddedFilesManifest Scope(string path)
    {
        if (ResolveEntry(path) is ManifestDirectory directory && directory != ManifestEntry.UnknownPath)
        {
            return new EmbeddedFilesManifest(directory.ToRootDirectory());
        }
 
        throw new InvalidOperationException($"Invalid path: '{path}'");
    }
 
    private static bool HasInvalidPathChars(string path) => path.IndexOfAny(_invalidFileNameChars) != -1;
}