public void GetFileInfo_SampleHtmlInputShouldMatchExpected(string testName)
        {
            var inputText    = TestFileSource.ReadEmbeddedTextFile("HtmlRewriter", $"{testName}_input.html");
            var expectedText = TestFileSource.ReadEmbeddedTextFile("HtmlRewriter", $"{testName}_expected.html");

            mockFileProvider.SetFileInfo("index.html", inputText);
            mockHttpContextAccessor.PathBase = "/path/to/";

            var options = new UrlRewriteSpaStaticFilesOptions
            {
                SourcePathBase = "./",
            };

            using var urlRewriteFileProvider = new UrlRewriteFileProvider(mockFileProvider, mockHttpContextAccessor, options);
            var actual = urlRewriteFileProvider.GetFileInfo("index.html");

            Assert.Equal("index.html", actual.Name);
            Assert.True(actual.Exists);

            using var actualReadStream = actual.CreateReadStream();
            using var actualReader     = new StreamReader(actualReadStream);
            var actualText = actualReader.ReadToEnd();

            Assert.Equal(expectedText, actualText);

            Assert.Null(actual.PhysicalPath);
            Assert.True(actual.LastModified > MockFileProvider.DefaultModifiedTime);
            Assert.Equal(actual.Length, Encoding.UTF8.GetBytes(expectedText).Length);
        }
        public void GetFileInfo_MinimalSampleHtmlFile_CustomRewriter()
        {
            mockFileProvider.SetFileInfo("test.html", "<html />");

            var options = new UrlRewriteSpaStaticFilesOptions
            {
                Rewriters = new[]
                {
                    new HelloWorldRewriter(),
                },
            };

            using var urlRewriteFileProvider = new UrlRewriteFileProvider(mockFileProvider, mockHttpContextAccessor, options);
            var actual = urlRewriteFileProvider.GetFileInfo("test.html");

            Assert.Equal("test.html", actual.Name);
            Assert.True(actual.Exists);

            using var actualReadStream = actual.CreateReadStream();
            using var actualReader     = new StreamReader(actualReadStream);
            var actualText = actualReader.ReadToEnd();

            Assert.Equal("Hello, world!", actualText);

            Assert.Equal(13, actual.Length);
            Assert.Null(actual.PhysicalPath);
            Assert.True(actual.LastModified > MockFileProvider.DefaultModifiedTime);
        }
        public void GetFileInfo_FileDoesNotExist()
        {
            var options = new UrlRewriteSpaStaticFilesOptions();

            using var urlRewriteFileProvider = new UrlRewriteFileProvider(mockFileProvider, mockHttpContextAccessor, options);
            var actual = urlRewriteFileProvider.GetFileInfo("test.html");

            Assert.Equal("test.html", actual.Name);
            Assert.False(actual.Exists);
        }
        public UrlRewriteFileProvider(IFileProvider innerFileProvider, IHttpContextAccessor httpContextAccessor, UrlRewriteSpaStaticFilesOptions?options)
        {
            this.innerFileProvider   = innerFileProvider ?? throw new ArgumentNullException(nameof(innerFileProvider));
            this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
            if (options == null)
            {
                options = new UrlRewriteSpaStaticFilesOptions();
            }

            this.sourcePathBase          = options.SourcePathBase ?? DefaultSourcePathBase;
            this.sourcePathBaseSelector  = options.SourcePathBaseSelector;
            this.targetPathBaseSelector  = options.TargetPathBaseSelector;
            this.maxFileLengthForRewrite = options.MaxFileLengthForRewrite;
            this.rewriters = CreateRewriterCollection(options);
        }
        public void GetFileInfo_MinimalSampleHtmlFile_MaxFileLengthForRewrite(int maxInputLengthOffset)
        {
            var inputText   = "<base href=\"./\" />";
            var inputLength = Encoding.UTF8.GetBytes(inputText).Length;

            mockFileProvider.SetFileInfo("test.html", inputText);
            mockHttpContextAccessor.PathBase = "/virtual";

            var options = new UrlRewriteSpaStaticFilesOptions
            {
                SourcePathBase          = "./",
                MaxFileLengthForRewrite = inputLength + maxInputLengthOffset,
            };

            using var urlRewriteFileProvider = new UrlRewriteFileProvider(mockFileProvider, mockHttpContextAccessor, options);
            var actual = urlRewriteFileProvider.GetFileInfo("test.html");

            Assert.Equal("test.html", actual.Name);
            Assert.True(actual.Exists);

            using var actualReadStream = actual.CreateReadStream();
            using var actualReader     = new StreamReader(actualReadStream);
            var actualText = actualReader.ReadToEnd();

            if (maxInputLengthOffset < 0)
            {
                Assert.Equal(inputText, actualText);

                Assert.NotNull(actual.PhysicalPath);
                Assert.Equal(MockFileProvider.DefaultModifiedTime, actual.LastModified);
                Assert.Equal(actual.Length, inputLength);
            }
            else
            {
                Assert.Equal("<base href=\"/virtual/\" />", actualText);

                Assert.Null(actual.PhysicalPath);
                Assert.True(actual.LastModified > MockFileProvider.DefaultModifiedTime);
                Assert.True(actual.Length > inputLength);
            }
        }
        public void GetFileInfo_MinimalSampleHtmlFile_NoChanges()
        {
            mockFileProvider.SetFileInfo("test.html", "<html />");

            var options = new UrlRewriteSpaStaticFilesOptions();

            using var urlRewriteFileProvider = new UrlRewriteFileProvider(mockFileProvider, mockHttpContextAccessor, options);
            var actual = urlRewriteFileProvider.GetFileInfo("test.html");

            Assert.Equal("test.html", actual.Name);
            Assert.True(actual.Exists);

            using var actualReadStream = actual.CreateReadStream();
            using var actualReader     = new StreamReader(actualReadStream);
            var actualText = actualReader.ReadToEnd();

            Assert.Equal("<html />", actualText);

            Assert.Equal(8, actual.Length);
            Assert.EndsWith("test.html", actual.PhysicalPath, StringComparison.Ordinal);
            Assert.Equal(MockFileProvider.DefaultModifiedTime, actual.LastModified);
        }
        /// <summary>
        /// Constructs a <see cref="UrlRewriteFileProvider"/> with the given options.
        /// </summary>
        public UrlWriteSpaStaticFileProvider(IServiceProvider serviceProvider, UrlRewriteSpaStaticFilesOptions options)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException(nameof(serviceProvider));
            }

            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (string.IsNullOrEmpty(options.RootPath))
            {
                throw new ArgumentException($"The {nameof(options.RootPath)} property of {nameof(options)} cannot be null or empty.", paramName: nameof(options));
            }

            var env = serviceProvider.GetRequiredService <IWebHostEnvironment>();
            var httpContextAccessor = serviceProvider.GetRequiredService <IHttpContextAccessor>();

            var absoluteRootPath = Path.Combine(env.ContentRootPath, options.RootPath);

            if (Directory.Exists(absoluteRootPath))
            {
#pragma warning disable CA2000 // Dispose objects before losing scope
                var physicalFileProvider = new PhysicalFileProvider(absoluteRootPath);

#pragma warning restore CA2000 // Dispose objects before losing scope
                try
                {
                    this.fileProvider = new UrlRewriteFileProvider(physicalFileProvider, httpContextAccessor, options);
                }
                catch (Exception)
                {
                    physicalFileProvider.Dispose();
                    throw;
                }
            }
        }
        public void GetFileInfo_MinimalSampleHtmlFile_SourceAndTargetPathBaseSelectors()
        {
            mockFileProvider.SetFileInfo("test.html", "<base href=\"./root/path/\" />");
            mockHttpContextAccessor.PathBase = "/virtual";

            var options = new UrlRewriteSpaStaticFilesOptions
            {
                SourcePathBaseSelector = (request) => "./root/",
                TargetPathBaseSelector = (request) => (request?.PathBase ?? string.Empty) + "/newroot/",
            };

            using var urlRewriteFileProvider = new UrlRewriteFileProvider(mockFileProvider, mockHttpContextAccessor, options);
            var actual = urlRewriteFileProvider.GetFileInfo("test.html");

            Assert.Equal("test.html", actual.Name);
            Assert.True(actual.Exists);

            using var actualReadStream = actual.CreateReadStream();
            using var actualReader     = new StreamReader(actualReadStream);
            var actualText = actualReader.ReadToEnd();

            Assert.Equal("<base href=\"/virtual/newroot/path/\" />", actualText);
        }
        private static IReadOnlyCollection <ISpaStaticFilesUrlRewriter> CreateRewriterCollection(UrlRewriteSpaStaticFilesOptions options)
        {
            var defaultRewriters = new ISpaStaticFilesUrlRewriter[]
            {
                new HtmlUrlRewriter()
                {
                    UpdateBaseElementHrefOnly = options.UpdateBaseElementHrefOnly
                },
                new ServiceWorkerJsUrlRewriter(),
            };

            var customRewriters = options.Rewriters;

            if (customRewriters == null || !customRewriters.Any())
            {
                return(defaultRewriters);
            }

            var rewriterList = new List <ISpaStaticFilesUrlRewriter>(defaultRewriters.Length + customRewriters.Count);

            rewriterList.AddRange(customRewriters);
            rewriterList.AddRange(defaultRewriters);
            return(rewriterList);
        }