diff --git a/src/StaticFiles/.gitignore b/src/StaticFiles/.gitignore
new file mode 100644
index 0000000000..bcc811de9a
--- /dev/null
+++ b/src/StaticFiles/.gitignore
@@ -0,0 +1,31 @@
+[Oo]bj/
+[Bb]in/
+TestResults/
+.nuget/
+_ReSharper.*/
+packages/
+artifacts/
+PublishProfiles/
+*.user
+*.suo
+*.cache
+*.docstates
+_ReSharper.*
+nuget.exe
+*net45.csproj
+*net451.csproj
+*k10.csproj
+*.psess
+*.vsp
+*.pidb
+*.userprefs
+*DS_Store
+*.ncrunchsolution
+*.*sdf
+*.ipch
+*.sln.ide
+project.lock.json
+.build/
+.testPublish/
+/.vs/
+global.json
diff --git a/src/StaticFiles/Directory.Build.props b/src/StaticFiles/Directory.Build.props
new file mode 100644
index 0000000000..2c2d9cc9d0
--- /dev/null
+++ b/src/StaticFiles/Directory.Build.props
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ Microsoft ASP.NET Core
+ https://github.com/aspnet/StaticFiles
+ git
+ $(MSBuildThisFileDirectory)
+ $(MSBuildThisFileDirectory)build\Key.snk
+ true
+ true
+ true
+
+
+
diff --git a/src/StaticFiles/Directory.Build.targets b/src/StaticFiles/Directory.Build.targets
new file mode 100644
index 0000000000..53b3f6e1da
--- /dev/null
+++ b/src/StaticFiles/Directory.Build.targets
@@ -0,0 +1,7 @@
+
+
+ $(MicrosoftNETCoreApp20PackageVersion)
+ $(MicrosoftNETCoreApp21PackageVersion)
+ $(NETStandardLibrary20PackageVersion)
+
+
diff --git a/src/StaticFiles/NuGetPackageVerifier.json b/src/StaticFiles/NuGetPackageVerifier.json
new file mode 100644
index 0000000000..4328f86b21
--- /dev/null
+++ b/src/StaticFiles/NuGetPackageVerifier.json
@@ -0,0 +1,13 @@
+{
+ "adx-nonshipping": {
+ "rules": [],
+ "packages": {
+ "Microsoft.AspNetCore.RangeHelper.Sources": {}
+ }
+ },
+ "Default": {
+ "rules": [
+ "DefaultCompositeRule"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/README.md b/src/StaticFiles/README.md
new file mode 100644
index 0000000000..dca5218cb5
--- /dev/null
+++ b/src/StaticFiles/README.md
@@ -0,0 +1,10 @@
+StaticFiles
+===========
+
+AppVeyor: [](https://ci.appveyor.com/project/aspnetci/StaticFiles/branch/dev)
+
+Travis: [](https://travis-ci.org/aspnet/StaticFiles)
+
+This repo contains middleware for handling requests for file system resources including files and directories.
+
+This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
diff --git a/src/StaticFiles/StaticFiles.sln b/src/StaticFiles/StaticFiles.sln
new file mode 100644
index 0000000000..0e5ab3b48f
--- /dev/null
+++ b/src/StaticFiles/StaticFiles.sln
@@ -0,0 +1,110 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26228.9
+MinimumVisualStudioVersion = 15.0.26730.03
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{40EE0889-960E-41B4-A3D3-9CE963EB0797}"
+ ProjectSection(SolutionItems) = preProject
+ src\Directory.Build.props = src\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8B21A3A9-9CA6-4857-A6E0-1A3203404B60}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.StaticFiles", "src\Microsoft.AspNetCore.StaticFiles\Microsoft.AspNetCore.StaticFiles.csproj", "{8D7BC5A4-F19C-4184-8338-A6B42997218C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StaticFileSample", "samples\StaticFileSample\StaticFileSample.csproj", "{092141D9-305A-4FC5-AE74-CB23982CA8D4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EF02AFE8-7C15-4DDB-8B2C-58A676112A98}"
+ ProjectSection(SolutionItems) = preProject
+ test\Directory.Build.props = test\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.StaticFiles.Tests", "test\Microsoft.AspNetCore.StaticFiles.Tests\Microsoft.AspNetCore.StaticFiles.Tests.csproj", "{CC87FE7D-8F42-4BE9-A152-9625E837C1E5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.StaticFiles.FunctionalTests", "test\Microsoft.AspNetCore.StaticFiles.FunctionalTests\Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj", "{FDF0539C-1F62-4B78-91B1-C687886931CA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.RangeHelper.Sources.Test", "test\Microsoft.AspNetCore.RangeHelper.Sources.Test\Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj", "{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{360DC2F8-EEB4-4C69-9784-C686EAD78279}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.RangeHelper.Sources", "Microsoft.AspNetCore.RangeHelper.Sources", "{DB6A1D14-B8A2-488F-9C4B-422FD45C8853}"
+ ProjectSection(SolutionItems) = preProject
+ shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs = shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|x86.ActiveCfg = Release|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|x86.ActiveCfg = Release|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|x86.ActiveCfg = Release|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|x86.Build.0 = Debug|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.ActiveCfg = Release|Any CPU
+ {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.Build.0 = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {8D7BC5A4-F19C-4184-8338-A6B42997218C} = {40EE0889-960E-41B4-A3D3-9CE963EB0797}
+ {092141D9-305A-4FC5-AE74-CB23982CA8D4} = {8B21A3A9-9CA6-4857-A6E0-1A3203404B60}
+ {CC87FE7D-8F42-4BE9-A152-9625E837C1E5} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
+ {FDF0539C-1F62-4B78-91B1-C687886931CA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
+ {DB6A1D14-B8A2-488F-9C4B-422FD45C8853} = {360DC2F8-EEB4-4C69-9784-C686EAD78279}
+ EndGlobalSection
+EndGlobal
diff --git a/src/StaticFiles/build/Key.snk b/src/StaticFiles/build/Key.snk
new file mode 100644
index 0000000000..e10e4889c1
Binary files /dev/null and b/src/StaticFiles/build/Key.snk differ
diff --git a/src/StaticFiles/build/dependencies.props b/src/StaticFiles/build/dependencies.props
new file mode 100644
index 0000000000..531f1f0941
--- /dev/null
+++ b/src/StaticFiles/build/dependencies.props
@@ -0,0 +1,39 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ 2.1.3-rtm-15802
+ 2.0.0
+ 2.1.2
+ 15.6.1
+ 4.7.49
+ 2.0.3
+ 0.8.0
+ 2.3.1
+ 2.4.0-beta.1.build3945
+
+
+
+
+
+
+
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 0.5.1
+ 2.1.2
+ 2.1.1
+ 2.1.0
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+
+
\ No newline at end of file
diff --git a/src/StaticFiles/build/repo.props b/src/StaticFiles/build/repo.props
new file mode 100644
index 0000000000..dab1601c88
--- /dev/null
+++ b/src/StaticFiles/build/repo.props
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Internal.AspNetCore.Universe.Lineup
+ 2.1.0-rc1-*
+ https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/build/sources.props b/src/StaticFiles/build/sources.props
new file mode 100644
index 0000000000..9215df9751
--- /dev/null
+++ b/src/StaticFiles/build/sources.props
@@ -0,0 +1,17 @@
+
+
+
+
+ $(DotNetRestoreSources)
+
+ $(RestoreSources);
+ https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
+
+
+ $(RestoreSources);
+ https://api.nuget.org/v3/index.json;
+
+
+
diff --git a/src/StaticFiles/samples/StaticFileSample/Properties/launchSettings.json b/src/StaticFiles/samples/StaticFileSample/Properties/launchSettings.json
new file mode 100644
index 0000000000..50cb7a7470
--- /dev/null
+++ b/src/StaticFiles/samples/StaticFileSample/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:35192/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "StaticFileSample": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "http://localhost:5000/",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/samples/StaticFileSample/Startup.cs b/src/StaticFiles/samples/StaticFileSample/Startup.cs
new file mode 100644
index 0000000000..e731022e1f
--- /dev/null
+++ b/src/StaticFiles/samples/StaticFileSample/Startup.cs
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace StaticFilesSample
+{
+ public class Startup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddDirectoryBrowser();
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment host)
+ {
+ Console.WriteLine("webroot: " + host.WebRootPath);
+
+ app.UseFileServer(new FileServerOptions
+ {
+ EnableDirectoryBrowsing = true
+ });
+ }
+
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .ConfigureLogging(factory =>
+ {
+ factory.AddFilter("Console", level => level >= LogLevel.Debug);
+ factory.AddConsole();
+ })
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseKestrel()
+ .UseIISIntegration()
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/src/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj b/src/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj
new file mode 100644
index 0000000000..bf7b35d825
--- /dev/null
+++ b/src/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/samples/StaticFileSample/wwwroot/htmlpage.html b/src/StaticFiles/samples/StaticFileSample/wwwroot/htmlpage.html
new file mode 100644
index 0000000000..c2dacddcb9
--- /dev/null
+++ b/src/StaticFiles/samples/StaticFileSample/wwwroot/htmlpage.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+ A static HTML file.
+
+
\ No newline at end of file
diff --git a/src/StaticFiles/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs b/src/StaticFiles/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs
new file mode 100644
index 0000000000..bf9769a8b9
--- /dev/null
+++ b/src/StaticFiles/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs
@@ -0,0 +1,127 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Headers;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Internal
+{
+ ///
+ /// Provides a parser for the Range Header in an .
+ ///
+ internal static class RangeHelper
+ {
+ ///
+ /// Returns the normalized form of the requested range if the Range Header in the is valid.
+ ///
+ /// The associated with the request.
+ /// The associated with the given .
+ /// The total length of the file representation requested.
+ /// The .
+ /// A boolean value which represents if the contain a single valid
+ /// range request. A which represents the normalized form of the
+ /// range parsed from the or null if it cannot be normalized.
+ /// If the Range header exists but cannot be parsed correctly, or if the provided length is 0, then the range request cannot be satisfied (status 416).
+ /// This results in (true,null) return values.
+ public static (bool isRangeRequest, RangeItemHeaderValue range) ParseRange(
+ HttpContext context,
+ RequestHeaders requestHeaders,
+ long length,
+ ILogger logger)
+ {
+ var rawRangeHeader = context.Request.Headers[HeaderNames.Range];
+ if (StringValues.IsNullOrEmpty(rawRangeHeader))
+ {
+ logger.LogTrace("Range header's value is empty.");
+ return (false, null);
+ }
+
+ // Perf: Check for a single entry before parsing it
+ if (rawRangeHeader.Count > 1 || rawRangeHeader[0].IndexOf(',') >= 0)
+ {
+ logger.LogDebug("Multiple ranges are not supported.");
+
+ // The spec allows for multiple ranges but we choose not to support them because the client may request
+ // very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
+ // impact the server. Ignore the header and serve the response normally.
+ return (false, null);
+ }
+
+ var rangeHeader = requestHeaders.Range;
+ if (rangeHeader == null)
+ {
+ logger.LogDebug("Range header's value is invalid.");
+ // Invalid
+ return (false, null);
+ }
+
+ // Already verified above
+ Debug.Assert(rangeHeader.Ranges.Count == 1);
+
+ var ranges = rangeHeader.Ranges;
+ if (ranges == null)
+ {
+ logger.LogDebug("Range header's value is invalid.");
+ return (false, null);
+ }
+
+ if (ranges.Count == 0)
+ {
+ return (true, null);
+ }
+
+ if (length == 0)
+ {
+ return (true, null);
+ }
+
+ // Normalize the ranges
+ var range = NormalizeRange(ranges.SingleOrDefault(), length);
+
+ // Return the single range
+ return (true, range);
+ }
+
+ // Internal for testing
+ internal static RangeItemHeaderValue NormalizeRange(RangeItemHeaderValue range, long length)
+ {
+ var start = range.From;
+ var end = range.To;
+
+ // X-[Y]
+ if (start.HasValue)
+ {
+ if (start.Value >= length)
+ {
+ // Not satisfiable, skip/discard.
+ return null;
+ }
+ if (!end.HasValue || end.Value >= length)
+ {
+ end = length - 1;
+ }
+ }
+ else
+ {
+ // suffix range "-X" e.g. the last X bytes, resolve
+ if (end.Value == 0)
+ {
+ // Not satisfiable, skip/discard.
+ return null;
+ }
+
+ var bytes = Math.Min(end.Value, length);
+ start = length - bytes;
+ end = start + bytes - 1;
+ }
+
+ return new RangeItemHeaderValue(start, end);
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Directory.Build.props b/src/StaticFiles/src/Directory.Build.props
new file mode 100644
index 0000000000..1e0980f663
--- /dev/null
+++ b/src/StaticFiles/src/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Constants.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Constants.cs
new file mode 100644
index 0000000000..b98937a747
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Constants.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ internal static class Constants
+ {
+ internal const string ServerCapabilitiesKey = "server.Capabilities";
+ internal const string SendFileVersionKey = "sendfile.Version";
+ internal const string SendFileVersion = "1.0";
+
+ internal const int Status200Ok = 200;
+ internal const int Status206PartialContent = 206;
+ internal const int Status304NotModified = 304;
+ internal const int Status412PreconditionFailed = 412;
+ internal const int Status416RangeNotSatisfiable = 416;
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/CustomDictionary.xml b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/CustomDictionary.xml
new file mode 100644
index 0000000000..78a76142f7
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/CustomDictionary.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ Owin
+
+
+
+
+
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesExtensions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesExtensions.cs
new file mode 100644
index 0000000000..2e8bea6977
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesExtensions.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods for the DefaultFilesMiddleware
+ ///
+ public static class DefaultFilesExtensions
+ {
+ ///
+ /// Enables default file mapping on the current path
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+
+ ///
+ /// Enables default file mapping for the given request path
+ ///
+ ///
+ /// The relative request path.
+ ///
+ public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, string requestPath)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseDefaultFiles(new DefaultFilesOptions
+ {
+ RequestPath = new PathString(requestPath)
+ });
+ }
+
+ ///
+ /// Enables default file mapping with the given options
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, DefaultFilesOptions options)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ return app.UseMiddleware(Options.Create(options));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesMiddleware.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesMiddleware.cs
new file mode 100644
index 0000000000..a401759b3d
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesMiddleware.cs
@@ -0,0 +1,100 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// This examines a directory path and determines if there is a default file present.
+ /// If so the file name is appended to the path and execution continues.
+ /// Note we don't just serve the file because it may require interpretation.
+ ///
+ public class DefaultFilesMiddleware
+ {
+ private readonly DefaultFilesOptions _options;
+ private readonly PathString _matchUrl;
+ private readonly RequestDelegate _next;
+ private readonly IFileProvider _fileProvider;
+
+ ///
+ /// Creates a new instance of the DefaultFilesMiddleware.
+ ///
+ /// The next middleware in the pipeline.
+ /// The used by this middleware.
+ /// The configuration options for this middleware.
+ public DefaultFilesMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions options)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (hostingEnv == null)
+ {
+ throw new ArgumentNullException(nameof(hostingEnv));
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _next = next;
+ _options = options.Value;
+ _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
+ _matchUrl = _options.RequestPath;
+ }
+
+ ///
+ /// This examines the request to see if it matches a configured directory, and if there are any files with the
+ /// configured default names in that directory. If so this will append the corresponding file name to the request
+ /// path for a later middleware to handle.
+ ///
+ ///
+ ///
+ public Task Invoke(HttpContext context)
+ {
+ PathString subpath;
+ if (Helpers.IsGetOrHeadMethod(context.Request.Method)
+ && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out subpath))
+ {
+ var dirContents = _fileProvider.GetDirectoryContents(subpath.Value);
+ if (dirContents.Exists)
+ {
+ // Check if any of our default files exist.
+ for (int matchIndex = 0; matchIndex < _options.DefaultFileNames.Count; matchIndex++)
+ {
+ string defaultFile = _options.DefaultFileNames[matchIndex];
+ var file = _fileProvider.GetFileInfo(subpath + defaultFile);
+ // TryMatchPath will make sure subpath always ends with a "/" by adding it if needed.
+ if (file.Exists)
+ {
+ // If the path matches a directory but does not end in a slash, redirect to add the slash.
+ // This prevents relative links from breaking.
+ if (!Helpers.PathEndsInSlash(context.Request.Path))
+ {
+ context.Response.StatusCode = 301;
+ context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
+ return Task.CompletedTask;
+ }
+
+ // Match found, re-write the url. A later middleware will actually serve the file.
+ context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);
+ break;
+ }
+ }
+ }
+ }
+
+ return _next(context);
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesOptions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesOptions.cs
new file mode 100644
index 0000000000..72b577dfcc
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesOptions.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.StaticFiles.Infrastructure;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Options for selecting default file names.
+ ///
+ public class DefaultFilesOptions : SharedOptionsBase
+ {
+ ///
+ /// Configuration for the DefaultFilesMiddleware.
+ ///
+ public DefaultFilesOptions()
+ : this(new SharedOptions())
+ {
+ }
+
+ ///
+ /// Configuration for the DefaultFilesMiddleware.
+ ///
+ ///
+ public DefaultFilesOptions(SharedOptions sharedOptions)
+ : base(sharedOptions)
+ {
+ // Prioritized list
+ DefaultFileNames = new List()
+ {
+ "default.htm",
+ "default.html",
+ "index.htm",
+ "index.html",
+ };
+ }
+
+ ///
+ /// An ordered list of file names to select by default. List length and ordering may affect performance.
+ ///
+ public IList DefaultFileNames { get; set; }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserExtensions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserExtensions.cs
new file mode 100644
index 0000000000..dce00489f1
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserExtensions.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods for the DirectoryBrowserMiddleware
+ ///
+ public static class DirectoryBrowserExtensions
+ {
+ ///
+ /// Enable directory browsing on the current path
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+
+ ///
+ /// Enables directory browsing for the given request path
+ ///
+ ///
+ /// The relative request path.
+ ///
+ public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, string requestPath)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseDirectoryBrowser(new DirectoryBrowserOptions
+ {
+ RequestPath = new PathString(requestPath)
+ });
+ }
+
+ ///
+ /// Enable directory browsing with the given options
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, DirectoryBrowserOptions options)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ return app.UseMiddleware(Options.Create(options));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserMiddleware.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserMiddleware.cs
new file mode 100644
index 0000000000..71765d0459
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserMiddleware.cs
@@ -0,0 +1,109 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Enables directory browsing
+ ///
+ public class DirectoryBrowserMiddleware
+ {
+ private readonly DirectoryBrowserOptions _options;
+ private readonly PathString _matchUrl;
+ private readonly RequestDelegate _next;
+ private readonly IDirectoryFormatter _formatter;
+ private readonly IFileProvider _fileProvider;
+
+ ///
+ /// Creates a new instance of the SendFileMiddleware. Using instance.
+ ///
+ /// The next middleware in the pipeline.
+ /// The used by this middleware.
+ /// The configuration for this middleware.
+ public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions options)
+ : this(next, hostingEnv, HtmlEncoder.Default, options)
+ {
+ }
+
+ ///
+ /// Creates a new instance of the SendFileMiddleware.
+ ///
+ /// The next middleware in the pipeline.
+ /// The used by this middleware.
+ /// The used by the default .
+ /// The configuration for this middleware.
+ public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, HtmlEncoder encoder, IOptions options)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (hostingEnv == null)
+ {
+ throw new ArgumentNullException(nameof(hostingEnv));
+ }
+
+ if (encoder == null)
+ {
+ throw new ArgumentNullException(nameof(encoder));
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _next = next;
+ _options = options.Value;
+ _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
+ _formatter = options.Value.Formatter ?? new HtmlDirectoryFormatter(encoder);
+ _matchUrl = _options.RequestPath;
+ }
+
+ ///
+ /// Examines the request to see if it matches a configured directory. If so, a view of the directory contents is returned.
+ ///
+ ///
+ ///
+ public Task Invoke(HttpContext context)
+ {
+ // Check if the URL matches any expected paths
+ PathString subpath;
+ IDirectoryContents contents;
+ if (Helpers.IsGetOrHeadMethod(context.Request.Method)
+ && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out subpath)
+ && TryGetDirectoryInfo(subpath, out contents))
+ {
+ // If the path matches a directory but does not end in a slash, redirect to add the slash.
+ // This prevents relative links from breaking.
+ if (!Helpers.PathEndsInSlash(context.Request.Path))
+ {
+ context.Response.StatusCode = 301;
+ context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
+ return Task.CompletedTask;
+ }
+
+ return _formatter.GenerateContentAsync(context, contents);
+ }
+
+ return _next(context);
+ }
+
+ private bool TryGetDirectoryInfo(PathString subpath, out IDirectoryContents contents)
+ {
+ contents = _fileProvider.GetDirectoryContents(subpath.Value);
+ return contents.Exists;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserOptions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserOptions.cs
new file mode 100644
index 0000000000..611df33e54
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserOptions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.AspNetCore.StaticFiles.Infrastructure;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Directory browsing options
+ ///
+ public class DirectoryBrowserOptions : SharedOptionsBase
+ {
+ ///
+ /// Enabled directory browsing for all request paths
+ ///
+ public DirectoryBrowserOptions()
+ : this(new SharedOptions())
+ {
+ }
+
+ ///
+ /// Enabled directory browsing all request paths
+ ///
+ ///
+ public DirectoryBrowserOptions(SharedOptions sharedOptions)
+ : base(sharedOptions)
+ {
+ }
+
+ ///
+ /// The component that generates the view.
+ ///
+ public IDirectoryFormatter Formatter { get; set; }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserServiceExtensions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserServiceExtensions.cs
new file mode 100644
index 0000000000..36d164c443
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserServiceExtensions.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods for adding directory browser services.
+ ///
+ public static class DirectoryBrowserServiceExtensions
+ {
+ ///
+ /// Adds directory browser middleware services.
+ ///
+ /// The to add services to.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddDirectoryBrowser(this IServiceCollection services)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ services.AddWebEncoders();
+
+ return services;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileExtensionContentTypeProvider.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileExtensionContentTypeProvider.cs
new file mode 100644
index 0000000000..f2e8f1b788
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileExtensionContentTypeProvider.cs
@@ -0,0 +1,459 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Provides a mapping between file extensions and MIME types.
+ ///
+ public class FileExtensionContentTypeProvider : IContentTypeProvider
+ {
+ #region Extension mapping table
+ ///
+ /// Creates a new provider with a set of default mappings.
+ ///
+ public FileExtensionContentTypeProvider()
+ : this(new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { ".323", "text/h323" },
+ { ".3g2", "video/3gpp2" },
+ { ".3gp2", "video/3gpp2" },
+ { ".3gp", "video/3gpp" },
+ { ".3gpp", "video/3gpp" },
+ { ".aac", "audio/aac" },
+ { ".aaf", "application/octet-stream" },
+ { ".aca", "application/octet-stream" },
+ { ".accdb", "application/msaccess" },
+ { ".accde", "application/msaccess" },
+ { ".accdt", "application/msaccess" },
+ { ".acx", "application/internet-property-stream" },
+ { ".adt", "audio/vnd.dlna.adts" },
+ { ".adts", "audio/vnd.dlna.adts" },
+ { ".afm", "application/octet-stream" },
+ { ".ai", "application/postscript" },
+ { ".aif", "audio/x-aiff" },
+ { ".aifc", "audio/aiff" },
+ { ".aiff", "audio/aiff" },
+ { ".appcache", "text/cache-manifest" },
+ { ".application", "application/x-ms-application" },
+ { ".art", "image/x-jg" },
+ { ".asd", "application/octet-stream" },
+ { ".asf", "video/x-ms-asf" },
+ { ".asi", "application/octet-stream" },
+ { ".asm", "text/plain" },
+ { ".asr", "video/x-ms-asf" },
+ { ".asx", "video/x-ms-asf" },
+ { ".atom", "application/atom+xml" },
+ { ".au", "audio/basic" },
+ { ".avi", "video/x-msvideo" },
+ { ".axs", "application/olescript" },
+ { ".bas", "text/plain" },
+ { ".bcpio", "application/x-bcpio" },
+ { ".bin", "application/octet-stream" },
+ { ".bmp", "image/bmp" },
+ { ".c", "text/plain" },
+ { ".cab", "application/vnd.ms-cab-compressed" },
+ { ".calx", "application/vnd.ms-office.calx" },
+ { ".cat", "application/vnd.ms-pki.seccat" },
+ { ".cdf", "application/x-cdf" },
+ { ".chm", "application/octet-stream" },
+ { ".class", "application/x-java-applet" },
+ { ".clp", "application/x-msclip" },
+ { ".cmx", "image/x-cmx" },
+ { ".cnf", "text/plain" },
+ { ".cod", "image/cis-cod" },
+ { ".cpio", "application/x-cpio" },
+ { ".cpp", "text/plain" },
+ { ".crd", "application/x-mscardfile" },
+ { ".crl", "application/pkix-crl" },
+ { ".crt", "application/x-x509-ca-cert" },
+ { ".csh", "application/x-csh" },
+ { ".css", "text/css" },
+ { ".csv", "application/octet-stream" },
+ { ".cur", "application/octet-stream" },
+ { ".dcr", "application/x-director" },
+ { ".deploy", "application/octet-stream" },
+ { ".der", "application/x-x509-ca-cert" },
+ { ".dib", "image/bmp" },
+ { ".dir", "application/x-director" },
+ { ".disco", "text/xml" },
+ { ".dlm", "text/dlm" },
+ { ".doc", "application/msword" },
+ { ".docm", "application/vnd.ms-word.document.macroEnabled.12" },
+ { ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
+ { ".dot", "application/msword" },
+ { ".dotm", "application/vnd.ms-word.template.macroEnabled.12" },
+ { ".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
+ { ".dsp", "application/octet-stream" },
+ { ".dtd", "text/xml" },
+ { ".dvi", "application/x-dvi" },
+ { ".dvr-ms", "video/x-ms-dvr" },
+ { ".dwf", "drawing/x-dwf" },
+ { ".dwp", "application/octet-stream" },
+ { ".dxr", "application/x-director" },
+ { ".eml", "message/rfc822" },
+ { ".emz", "application/octet-stream" },
+ { ".eot", "application/vnd.ms-fontobject" },
+ { ".eps", "application/postscript" },
+ { ".etx", "text/x-setext" },
+ { ".evy", "application/envoy" },
+ { ".fdf", "application/vnd.fdf" },
+ { ".fif", "application/fractals" },
+ { ".fla", "application/octet-stream" },
+ { ".flr", "x-world/x-vrml" },
+ { ".flv", "video/x-flv" },
+ { ".gif", "image/gif" },
+ { ".gtar", "application/x-gtar" },
+ { ".gz", "application/x-gzip" },
+ { ".h", "text/plain" },
+ { ".hdf", "application/x-hdf" },
+ { ".hdml", "text/x-hdml" },
+ { ".hhc", "application/x-oleobject" },
+ { ".hhk", "application/octet-stream" },
+ { ".hhp", "application/octet-stream" },
+ { ".hlp", "application/winhlp" },
+ { ".hqx", "application/mac-binhex40" },
+ { ".hta", "application/hta" },
+ { ".htc", "text/x-component" },
+ { ".htm", "text/html" },
+ { ".html", "text/html" },
+ { ".htt", "text/webviewhtml" },
+ { ".hxt", "text/html" },
+ { ".ical", "text/calendar" },
+ { ".icalendar", "text/calendar" },
+ { ".ico", "image/x-icon" },
+ { ".ics", "text/calendar" },
+ { ".ief", "image/ief" },
+ { ".ifb", "text/calendar" },
+ { ".iii", "application/x-iphone" },
+ { ".inf", "application/octet-stream" },
+ { ".ins", "application/x-internet-signup" },
+ { ".isp", "application/x-internet-signup" },
+ { ".IVF", "video/x-ivf" },
+ { ".jar", "application/java-archive" },
+ { ".java", "application/octet-stream" },
+ { ".jck", "application/liquidmotion" },
+ { ".jcz", "application/liquidmotion" },
+ { ".jfif", "image/pjpeg" },
+ { ".jpb", "application/octet-stream" },
+ { ".jpe", "image/jpeg" },
+ { ".jpeg", "image/jpeg" },
+ { ".jpg", "image/jpeg" },
+ { ".js", "application/javascript" },
+ { ".json", "application/json" },
+ { ".jsx", "text/jscript" },
+ { ".latex", "application/x-latex" },
+ { ".lit", "application/x-ms-reader" },
+ { ".lpk", "application/octet-stream" },
+ { ".lsf", "video/x-la-asf" },
+ { ".lsx", "video/x-la-asf" },
+ { ".lzh", "application/octet-stream" },
+ { ".m13", "application/x-msmediaview" },
+ { ".m14", "application/x-msmediaview" },
+ { ".m1v", "video/mpeg" },
+ { ".m2ts", "video/vnd.dlna.mpeg-tts" },
+ { ".m3u", "audio/x-mpegurl" },
+ { ".m4a", "audio/mp4" },
+ { ".m4v", "video/mp4" },
+ { ".man", "application/x-troff-man" },
+ { ".manifest", "application/x-ms-manifest" },
+ { ".map", "text/plain" },
+ { ".markdown", "text/markdown" },
+ { ".md", "text/markdown" },
+ { ".mdb", "application/x-msaccess" },
+ { ".mdp", "application/octet-stream" },
+ { ".me", "application/x-troff-me" },
+ { ".mht", "message/rfc822" },
+ { ".mhtml", "message/rfc822" },
+ { ".mid", "audio/mid" },
+ { ".midi", "audio/mid" },
+ { ".mix", "application/octet-stream" },
+ { ".mmf", "application/x-smaf" },
+ { ".mno", "text/xml" },
+ { ".mny", "application/x-msmoney" },
+ { ".mov", "video/quicktime" },
+ { ".movie", "video/x-sgi-movie" },
+ { ".mp2", "video/mpeg" },
+ { ".mp3", "audio/mpeg" },
+ { ".mp4", "video/mp4" },
+ { ".mp4v", "video/mp4" },
+ { ".mpa", "video/mpeg" },
+ { ".mpe", "video/mpeg" },
+ { ".mpeg", "video/mpeg" },
+ { ".mpg", "video/mpeg" },
+ { ".mpp", "application/vnd.ms-project" },
+ { ".mpv2", "video/mpeg" },
+ { ".ms", "application/x-troff-ms" },
+ { ".msi", "application/octet-stream" },
+ { ".mso", "application/octet-stream" },
+ { ".mvb", "application/x-msmediaview" },
+ { ".mvc", "application/x-miva-compiled" },
+ { ".nc", "application/x-netcdf" },
+ { ".nsc", "video/x-ms-asf" },
+ { ".nws", "message/rfc822" },
+ { ".ocx", "application/octet-stream" },
+ { ".oda", "application/oda" },
+ { ".odc", "text/x-ms-odc" },
+ { ".ods", "application/oleobject" },
+ { ".oga", "audio/ogg" },
+ { ".ogg", "video/ogg" },
+ { ".ogv", "video/ogg" },
+ { ".ogx", "application/ogg" },
+ { ".one", "application/onenote" },
+ { ".onea", "application/onenote" },
+ { ".onetoc", "application/onenote" },
+ { ".onetoc2", "application/onenote" },
+ { ".onetmp", "application/onenote" },
+ { ".onepkg", "application/onenote" },
+ { ".osdx", "application/opensearchdescription+xml" },
+ { ".otf", "font/otf" },
+ { ".p10", "application/pkcs10" },
+ { ".p12", "application/x-pkcs12" },
+ { ".p7b", "application/x-pkcs7-certificates" },
+ { ".p7c", "application/pkcs7-mime" },
+ { ".p7m", "application/pkcs7-mime" },
+ { ".p7r", "application/x-pkcs7-certreqresp" },
+ { ".p7s", "application/pkcs7-signature" },
+ { ".pbm", "image/x-portable-bitmap" },
+ { ".pcx", "application/octet-stream" },
+ { ".pcz", "application/octet-stream" },
+ { ".pdf", "application/pdf" },
+ { ".pfb", "application/octet-stream" },
+ { ".pfm", "application/octet-stream" },
+ { ".pfx", "application/x-pkcs12" },
+ { ".pgm", "image/x-portable-graymap" },
+ { ".pko", "application/vnd.ms-pki.pko" },
+ { ".pma", "application/x-perfmon" },
+ { ".pmc", "application/x-perfmon" },
+ { ".pml", "application/x-perfmon" },
+ { ".pmr", "application/x-perfmon" },
+ { ".pmw", "application/x-perfmon" },
+ { ".png", "image/png" },
+ { ".pnm", "image/x-portable-anymap" },
+ { ".pnz", "image/png" },
+ { ".pot", "application/vnd.ms-powerpoint" },
+ { ".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" },
+ { ".potx", "application/vnd.openxmlformats-officedocument.presentationml.template" },
+ { ".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
+ { ".ppm", "image/x-portable-pixmap" },
+ { ".pps", "application/vnd.ms-powerpoint" },
+ { ".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
+ { ".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
+ { ".ppt", "application/vnd.ms-powerpoint" },
+ { ".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
+ { ".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
+ { ".prf", "application/pics-rules" },
+ { ".prm", "application/octet-stream" },
+ { ".prx", "application/octet-stream" },
+ { ".ps", "application/postscript" },
+ { ".psd", "application/octet-stream" },
+ { ".psm", "application/octet-stream" },
+ { ".psp", "application/octet-stream" },
+ { ".pub", "application/x-mspublisher" },
+ { ".qt", "video/quicktime" },
+ { ".qtl", "application/x-quicktimeplayer" },
+ { ".qxd", "application/octet-stream" },
+ { ".ra", "audio/x-pn-realaudio" },
+ { ".ram", "audio/x-pn-realaudio" },
+ { ".rar", "application/octet-stream" },
+ { ".ras", "image/x-cmu-raster" },
+ { ".rf", "image/vnd.rn-realflash" },
+ { ".rgb", "image/x-rgb" },
+ { ".rm", "application/vnd.rn-realmedia" },
+ { ".rmi", "audio/mid" },
+ { ".roff", "application/x-troff" },
+ { ".rpm", "audio/x-pn-realaudio-plugin" },
+ { ".rtf", "application/rtf" },
+ { ".rtx", "text/richtext" },
+ { ".scd", "application/x-msschedule" },
+ { ".sct", "text/scriptlet" },
+ { ".sea", "application/octet-stream" },
+ { ".setpay", "application/set-payment-initiation" },
+ { ".setreg", "application/set-registration-initiation" },
+ { ".sgml", "text/sgml" },
+ { ".sh", "application/x-sh" },
+ { ".shar", "application/x-shar" },
+ { ".sit", "application/x-stuffit" },
+ { ".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12" },
+ { ".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide" },
+ { ".smd", "audio/x-smd" },
+ { ".smi", "application/octet-stream" },
+ { ".smx", "audio/x-smd" },
+ { ".smz", "audio/x-smd" },
+ { ".snd", "audio/basic" },
+ { ".snp", "application/octet-stream" },
+ { ".spc", "application/x-pkcs7-certificates" },
+ { ".spl", "application/futuresplash" },
+ { ".spx", "audio/ogg" },
+ { ".src", "application/x-wais-source" },
+ { ".ssm", "application/streamingmedia" },
+ { ".sst", "application/vnd.ms-pki.certstore" },
+ { ".stl", "application/vnd.ms-pki.stl" },
+ { ".sv4cpio", "application/x-sv4cpio" },
+ { ".sv4crc", "application/x-sv4crc" },
+ { ".svg", "image/svg+xml" },
+ { ".svgz", "image/svg+xml" },
+ { ".swf", "application/x-shockwave-flash" },
+ { ".t", "application/x-troff" },
+ { ".tar", "application/x-tar" },
+ { ".tcl", "application/x-tcl" },
+ { ".tex", "application/x-tex" },
+ { ".texi", "application/x-texinfo" },
+ { ".texinfo", "application/x-texinfo" },
+ { ".tgz", "application/x-compressed" },
+ { ".thmx", "application/vnd.ms-officetheme" },
+ { ".thn", "application/octet-stream" },
+ { ".tif", "image/tiff" },
+ { ".tiff", "image/tiff" },
+ { ".toc", "application/octet-stream" },
+ { ".tr", "application/x-troff" },
+ { ".trm", "application/x-msterminal" },
+ { ".ts", "video/vnd.dlna.mpeg-tts" },
+ { ".tsv", "text/tab-separated-values" },
+ { ".ttc", "application/x-font-ttf" },
+ { ".ttf", "application/x-font-ttf" },
+ { ".tts", "video/vnd.dlna.mpeg-tts" },
+ { ".txt", "text/plain" },
+ { ".u32", "application/octet-stream" },
+ { ".uls", "text/iuls" },
+ { ".ustar", "application/x-ustar" },
+ { ".vbs", "text/vbscript" },
+ { ".vcf", "text/x-vcard" },
+ { ".vcs", "text/plain" },
+ { ".vdx", "application/vnd.ms-visio.viewer" },
+ { ".vml", "text/xml" },
+ { ".vsd", "application/vnd.visio" },
+ { ".vss", "application/vnd.visio" },
+ { ".vst", "application/vnd.visio" },
+ { ".vsto", "application/x-ms-vsto" },
+ { ".vsw", "application/vnd.visio" },
+ { ".vsx", "application/vnd.visio" },
+ { ".vtx", "application/vnd.visio" },
+ { ".wav", "audio/wav" },
+ { ".wax", "audio/x-ms-wax" },
+ { ".wbmp", "image/vnd.wap.wbmp" },
+ { ".wcm", "application/vnd.ms-works" },
+ { ".wdb", "application/vnd.ms-works" },
+ { ".webm", "video/webm" },
+ { ".webp", "image/webp" },
+ { ".wks", "application/vnd.ms-works" },
+ { ".wm", "video/x-ms-wm" },
+ { ".wma", "audio/x-ms-wma" },
+ { ".wmd", "application/x-ms-wmd" },
+ { ".wmf", "application/x-msmetafile" },
+ { ".wml", "text/vnd.wap.wml" },
+ { ".wmlc", "application/vnd.wap.wmlc" },
+ { ".wmls", "text/vnd.wap.wmlscript" },
+ { ".wmlsc", "application/vnd.wap.wmlscriptc" },
+ { ".wmp", "video/x-ms-wmp" },
+ { ".wmv", "video/x-ms-wmv" },
+ { ".wmx", "video/x-ms-wmx" },
+ { ".wmz", "application/x-ms-wmz" },
+ { ".woff", "application/font-woff" }, // https://www.w3.org/TR/WOFF/#appendix-b
+ { ".woff2", "font/woff2" }, // https://www.w3.org/TR/WOFF2/#IMT
+ { ".wps", "application/vnd.ms-works" },
+ { ".wri", "application/x-mswrite" },
+ { ".wrl", "x-world/x-vrml" },
+ { ".wrz", "x-world/x-vrml" },
+ { ".wsdl", "text/xml" },
+ { ".wtv", "video/x-ms-wtv" },
+ { ".wvx", "video/x-ms-wvx" },
+ { ".x", "application/directx" },
+ { ".xaf", "x-world/x-vrml" },
+ { ".xaml", "application/xaml+xml" },
+ { ".xap", "application/x-silverlight-app" },
+ { ".xbap", "application/x-ms-xbap" },
+ { ".xbm", "image/x-xbitmap" },
+ { ".xdr", "text/plain" },
+ { ".xht", "application/xhtml+xml" },
+ { ".xhtml", "application/xhtml+xml" },
+ { ".xla", "application/vnd.ms-excel" },
+ { ".xlam", "application/vnd.ms-excel.addin.macroEnabled.12" },
+ { ".xlc", "application/vnd.ms-excel" },
+ { ".xlm", "application/vnd.ms-excel" },
+ { ".xls", "application/vnd.ms-excel" },
+ { ".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
+ { ".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" },
+ { ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
+ { ".xlt", "application/vnd.ms-excel" },
+ { ".xltm", "application/vnd.ms-excel.template.macroEnabled.12" },
+ { ".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
+ { ".xlw", "application/vnd.ms-excel" },
+ { ".xml", "text/xml" },
+ { ".xof", "x-world/x-vrml" },
+ { ".xpm", "image/x-xpixmap" },
+ { ".xps", "application/vnd.ms-xpsdocument" },
+ { ".xsd", "text/xml" },
+ { ".xsf", "text/xml" },
+ { ".xsl", "text/xml" },
+ { ".xslt", "text/xml" },
+ { ".xsn", "application/octet-stream" },
+ { ".xtp", "application/octet-stream" },
+ { ".xwd", "image/x-xwindowdump" },
+ { ".z", "application/x-compress" },
+ { ".zip", "application/x-zip-compressed" },
+ })
+ {
+ }
+ #endregion
+
+ ///
+ /// Creates a lookup engine using the provided mapping.
+ /// It is recommended that the IDictionary instance use StringComparer.OrdinalIgnoreCase.
+ ///
+ ///
+ public FileExtensionContentTypeProvider(IDictionary mapping)
+ {
+ if (mapping == null)
+ {
+ throw new ArgumentNullException(nameof(mapping));
+ }
+ Mappings = mapping;
+ }
+
+ ///
+ /// The cross reference table of file extensions and content-types.
+ ///
+ public IDictionary Mappings { get; private set; }
+
+ ///
+ /// Given a file path, determine the MIME type
+ ///
+ /// A file path
+ /// The resulting MIME type
+ /// True if MIME type could be determined
+ public bool TryGetContentType(string subpath, out string contentType)
+ {
+ string extension = GetExtension(subpath);
+ if (extension == null)
+ {
+ contentType = null;
+ return false;
+ }
+ return Mappings.TryGetValue(extension, out contentType);
+ }
+
+ private static string GetExtension(string path)
+ {
+ // Don't use Path.GetExtension as that may throw an exception if there are
+ // invalid characters in the path. Invalid characters should be handled
+ // by the FileProviders
+
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return null;
+ }
+
+ int index = path.LastIndexOf('.');
+ if (index < 0)
+ {
+ return null;
+ }
+
+ return path.Substring(index);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileServerExtensions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileServerExtensions.cs
new file mode 100644
index 0000000000..c9eb06e4c3
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileServerExtensions.cs
@@ -0,0 +1,104 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.StaticFiles;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods that combine all of the static file middleware components:
+ /// Default files, directory browsing, send file, and static files
+ ///
+ public static class FileServerExtensions
+ {
+ ///
+ /// Enable all static file middleware (except directory browsing) for the current request path in the current directory.
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseFileServer(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseFileServer(new FileServerOptions());
+ }
+
+ ///
+ /// Enable all static file middleware on for the current request path in the current directory.
+ ///
+ ///
+ /// Should directory browsing be enabled?
+ ///
+ public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, bool enableDirectoryBrowsing)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseFileServer(new FileServerOptions
+ {
+ EnableDirectoryBrowsing = enableDirectoryBrowsing
+ });
+ }
+
+ ///
+ /// Enables all static file middleware (except directory browsing) for the given request path from the directory of the same name
+ ///
+ ///
+ /// The relative request path.
+ ///
+ public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, string requestPath)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ if (requestPath == null)
+ {
+ throw new ArgumentNullException(nameof(requestPath));
+ }
+
+ return app.UseFileServer(new FileServerOptions
+ {
+ RequestPath = new PathString(requestPath)
+ });
+ }
+
+ ///
+ /// Enable all static file middleware with the given options
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, FileServerOptions options)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (options.EnableDefaultFiles)
+ {
+ app.UseDefaultFiles(options.DefaultFilesOptions);
+ }
+
+ if (options.EnableDirectoryBrowsing)
+ {
+ app.UseDirectoryBrowser(options.DirectoryBrowserOptions);
+ }
+
+ return app.UseStaticFiles(options.StaticFileOptions);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileServerOptions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileServerOptions.cs
new file mode 100644
index 0000000000..f46e274cc1
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/FileServerOptions.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.StaticFiles.Infrastructure;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Options for all of the static file middleware components
+ ///
+ public class FileServerOptions : SharedOptionsBase
+ {
+ ///
+ /// Creates a combined options class for all of the static file middleware components.
+ ///
+ public FileServerOptions()
+ : base(new SharedOptions())
+ {
+ StaticFileOptions = new StaticFileOptions(SharedOptions);
+ DirectoryBrowserOptions = new DirectoryBrowserOptions(SharedOptions);
+ DefaultFilesOptions = new DefaultFilesOptions(SharedOptions);
+ EnableDefaultFiles = true;
+ }
+
+ ///
+ /// Options for configuring the StaticFileMiddleware.
+ ///
+ public StaticFileOptions StaticFileOptions { get; private set; }
+
+ ///
+ /// Options for configuring the DirectoryBrowserMiddleware.
+ ///
+ public DirectoryBrowserOptions DirectoryBrowserOptions { get; private set; }
+
+ ///
+ /// Options for configuring the DefaultFilesMiddleware.
+ ///
+ public DefaultFilesOptions DefaultFilesOptions { get; private set; }
+
+ ///
+ /// Directory browsing is disabled by default.
+ ///
+ public bool EnableDirectoryBrowsing { get; set; }
+
+ ///
+ /// Default files are enabled by default.
+ ///
+ public bool EnableDefaultFiles { get; set; }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Helpers.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Helpers.cs
new file mode 100644
index 0000000000..733377cef3
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Helpers.cs
@@ -0,0 +1,47 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ internal static class Helpers
+ {
+ internal static IFileProvider ResolveFileProvider(IHostingEnvironment hostingEnv)
+ {
+ if (hostingEnv.WebRootFileProvider == null) {
+ throw new InvalidOperationException("Missing FileProvider.");
+ }
+ return hostingEnv.WebRootFileProvider;
+ }
+
+ internal static bool IsGetOrHeadMethod(string method)
+ {
+ return HttpMethods.IsGet(method) || HttpMethods.IsHead(method);
+ }
+
+ internal static bool PathEndsInSlash(PathString path)
+ {
+ return path.Value.EndsWith("/", StringComparison.Ordinal);
+ }
+
+ internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath)
+ {
+ var path = context.Request.Path;
+
+ if (forDirectory && !PathEndsInSlash(path))
+ {
+ path += new PathString("/");
+ }
+
+ if (path.StartsWithSegments(matchUrl, out subpath))
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/HtmlDirectoryFormatter.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/HtmlDirectoryFormatter.cs
new file mode 100644
index 0000000000..22ddcb99d2
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/HtmlDirectoryFormatter.cs
@@ -0,0 +1,172 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Generates an HTML view for a directory.
+ ///
+ public class HtmlDirectoryFormatter : IDirectoryFormatter
+ {
+ private const string TextHtmlUtf8 = "text/html; charset=utf-8";
+
+ private HtmlEncoder _htmlEncoder;
+
+ public HtmlDirectoryFormatter(HtmlEncoder encoder)
+ {
+ if (encoder == null)
+ {
+ throw new ArgumentNullException(nameof(encoder));
+ }
+ _htmlEncoder = encoder;
+ }
+
+ ///
+ /// Generates an HTML view for a directory.
+ ///
+ public virtual Task GenerateContentAsync(HttpContext context, IEnumerable contents)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+ if (contents == null)
+ {
+ throw new ArgumentNullException(nameof(contents));
+ }
+
+ context.Response.ContentType = TextHtmlUtf8;
+
+ if (HttpMethods.IsHead(context.Request.Method))
+ {
+ // HEAD, no response body
+ return Task.CompletedTask;
+ }
+
+ PathString requestPath = context.Request.PathBase + context.Request.Path;
+
+ var builder = new StringBuilder();
+
+ builder.AppendFormat(
+@"
+", CultureInfo.CurrentUICulture.TwoLetterISOLanguageName);
+
+ builder.AppendFormat(@"
+
+ {0} {1}", HtmlEncode(Resources.HtmlDir_IndexOf), HtmlEncode(requestPath.Value));
+
+ builder.Append(@"
+
+
+
+ ");
+ builder.AppendFormat(@"
+ {0} /", HtmlEncode(Resources.HtmlDir_IndexOf));
+
+ string cumulativePath = "/";
+ foreach (var segment in requestPath.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ cumulativePath = cumulativePath + segment + "/";
+ builder.AppendFormat(@"{1}/",
+ HtmlEncode(cumulativePath), HtmlEncode(segment));
+ }
+
+ builder.AppendFormat(CultureInfo.CurrentUICulture,
+ @"
+
+
+ | {1} | {2} | {4} |
+
+ ",
+ HtmlEncode(Resources.HtmlDir_TableSummary),
+ HtmlEncode(Resources.HtmlDir_Name),
+ HtmlEncode(Resources.HtmlDir_Size),
+ HtmlEncode(Resources.HtmlDir_Modified),
+ HtmlEncode(Resources.HtmlDir_LastModified));
+
+ foreach (var subdir in contents.Where(info => info.IsDirectory))
+ {
+ builder.AppendFormat(@"
+
+ | {0}/ |
+ |
+ {1} |
+
",
+ HtmlEncode(subdir.Name),
+ HtmlEncode(subdir.LastModified.ToString(CultureInfo.CurrentCulture)));
+ }
+
+ foreach (var file in contents.Where(info => !info.IsDirectory))
+ {
+ builder.AppendFormat(@"
+
+ | {0} |
+ {1} |
+ {2} |
+
",
+ HtmlEncode(file.Name),
+ HtmlEncode(file.Length.ToString("n0", CultureInfo.CurrentCulture)),
+ HtmlEncode(file.LastModified.ToString(CultureInfo.CurrentCulture)));
+ }
+
+ builder.Append(@"
+
+
+
+
+");
+ string data = builder.ToString();
+ byte[] bytes = Encoding.UTF8.GetBytes(data);
+ context.Response.ContentLength = bytes.Length;
+ return context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
+ }
+
+ private string HtmlEncode(string body)
+ {
+ return _htmlEncoder.Encode(body);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/IContentTypeProvider.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/IContentTypeProvider.cs
new file mode 100644
index 0000000000..ba5065df03
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/IContentTypeProvider.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Used to look up MIME types given a file path
+ ///
+ public interface IContentTypeProvider
+ {
+ ///
+ /// Given a file path, determine the MIME type
+ ///
+ /// A file path
+ /// The resulting MIME type
+ /// True if MIME type could be determined
+ bool TryGetContentType(string subpath, out string contentType);
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/IDirectoryFormatter.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/IDirectoryFormatter.cs
new file mode 100644
index 0000000000..6f379dea98
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/IDirectoryFormatter.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Generates the view for a directory
+ ///
+ public interface IDirectoryFormatter
+ {
+ ///
+ /// Generates the view for a directory.
+ /// Implementers should properly handle HEAD requests.
+ /// Implementers should set all necessary response headers (e.g. Content-Type, Content-Length, etc.).
+ ///
+ Task GenerateContentAsync(HttpContext context, IEnumerable contents);
+ }
+}
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptions.cs
new file mode 100644
index 0000000000..1c1cc80ad5
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptions.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
+{
+ ///
+ /// Options common to several middleware components
+ ///
+ public class SharedOptions
+ {
+ private PathString _requestPath;
+
+ ///
+ /// Defaults to all request paths.
+ ///
+ public SharedOptions()
+ {
+ RequestPath = PathString.Empty;
+ }
+
+ ///
+ /// The request path that maps to static resources
+ ///
+ public PathString RequestPath
+ {
+ get { return _requestPath; }
+ set
+ {
+ if (value.HasValue && value.Value.EndsWith("/", StringComparison.Ordinal))
+ {
+ throw new ArgumentException("Request path must not end in a slash");
+ }
+ _requestPath = value;
+ }
+ }
+
+ ///
+ /// The file system used to locate resources
+ ///
+ public IFileProvider FileProvider { get; set; }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptionsBase.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptionsBase.cs
new file mode 100644
index 0000000000..16900ec6fb
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptionsBase.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
+{
+ ///
+ /// Options common to several middleware components
+ ///
+ public abstract class SharedOptionsBase
+ {
+ ///
+ /// Creates an new instance of the SharedOptionsBase.
+ ///
+ ///
+ protected SharedOptionsBase(SharedOptions sharedOptions)
+ {
+ if (sharedOptions == null)
+ {
+ throw new ArgumentNullException(nameof(sharedOptions));
+ }
+
+ SharedOptions = sharedOptions;
+ }
+
+ ///
+ /// Options common to several middleware components
+ ///
+ protected SharedOptions SharedOptions { get; private set; }
+
+ ///
+ /// The relative request path that maps to static resources.
+ ///
+ public PathString RequestPath
+ {
+ get { return SharedOptions.RequestPath; }
+ set { SharedOptions.RequestPath = value; }
+ }
+
+ ///
+ /// The file system used to locate resources
+ ///
+ public IFileProvider FileProvider
+ {
+ get { return SharedOptions.FileProvider; }
+ set { SharedOptions.FileProvider = value; }
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs
new file mode 100644
index 0000000000..7fa6e35083
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs
@@ -0,0 +1,159 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Defines *all* the logger messages produced by static files
+ ///
+ internal static class LoggerExtensions
+ {
+ private static Action _logMethodNotSupported;
+ private static Action _logFileServed;
+ private static Action _logPathMismatch;
+ private static Action _logFileTypeNotSupported;
+ private static Action _logFileNotFound;
+ private static Action _logPathNotModified;
+ private static Action _logPreconditionFailed;
+ private static Action _logHandled;
+ private static Action _logRangeNotSatisfiable;
+ private static Action _logSendingFileRange;
+ private static Action _logCopyingFileRange;
+ private static Action _logCopyingBytesToResponse;
+ private static Action _logWriteCancelled;
+
+ static LoggerExtensions()
+ {
+ _logMethodNotSupported = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 1,
+ formatString: "{Method} requests are not supported");
+ _logFileServed = LoggerMessage.Define(
+ logLevel: LogLevel.Information,
+ eventId: 2,
+ formatString: "Sending file. Request path: '{VirtualPath}'. Physical path: '{PhysicalPath}'");
+ _logPathMismatch = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 3,
+ formatString: "The request path {Path} does not match the path filter");
+ _logFileTypeNotSupported = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 4,
+ formatString: "The request path {Path} does not match a supported file type");
+ _logFileNotFound = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 5,
+ formatString: "The request path {Path} does not match an existing file");
+ _logPathNotModified = LoggerMessage.Define(
+ logLevel: LogLevel.Information,
+ eventId: 6,
+ formatString: "The file {Path} was not modified");
+ _logPreconditionFailed = LoggerMessage.Define(
+ logLevel: LogLevel.Information,
+ eventId: 7,
+ formatString: "Precondition for {Path} failed");
+ _logHandled = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 8,
+ formatString: "Handled. Status code: {StatusCode} File: {Path}");
+ _logRangeNotSatisfiable = LoggerMessage.Define(
+ logLevel: LogLevel.Warning,
+ eventId: 9,
+ formatString: "Range not satisfiable for {Path}");
+ _logSendingFileRange = LoggerMessage.Define(
+ logLevel: LogLevel.Information,
+ eventId: 10,
+ formatString: "Sending {Range} of file {Path}");
+ _logCopyingFileRange = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 11,
+ formatString: "Copying {Range} of file {Path} to the response body");
+ _logCopyingBytesToResponse = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 12,
+ formatString: "Copying bytes {Start}-{End} of file {Path} to response body");
+ _logWriteCancelled = LoggerMessage.Define(
+ logLevel: LogLevel.Debug,
+ eventId: 14,
+ formatString: "The file transmission was cancelled");
+ }
+
+ public static void LogRequestMethodNotSupported(this ILogger logger, string method)
+ {
+ _logMethodNotSupported(logger, method, null);
+ }
+
+ public static void LogFileServed(this ILogger logger, string virtualPath, string physicalPath)
+ {
+ if (string.IsNullOrEmpty(physicalPath))
+ {
+ physicalPath = "N/A";
+ }
+ _logFileServed(logger, virtualPath, physicalPath, null);
+ }
+
+ public static void LogPathMismatch(this ILogger logger, string path)
+ {
+ _logPathMismatch(logger, path, null);
+ }
+
+ public static void LogFileTypeNotSupported(this ILogger logger, string path)
+ {
+ _logFileTypeNotSupported(logger, path, null);
+ }
+
+ public static void LogFileNotFound(this ILogger logger, string path)
+ {
+ _logFileNotFound(logger, path, null);
+ }
+
+ public static void LogPathNotModified(this ILogger logger, string path)
+ {
+ _logPathNotModified(logger, path, null);
+ }
+
+ public static void LogPreconditionFailed(this ILogger logger, string path)
+ {
+ _logPreconditionFailed(logger, path, null);
+ }
+
+ public static void LogHandled(this ILogger logger, int statusCode, string path)
+ {
+ _logHandled(logger, statusCode, path, null);
+ }
+
+ public static void LogRangeNotSatisfiable(this ILogger logger, string path)
+ {
+ _logRangeNotSatisfiable(logger, path, null);
+ }
+
+ public static void LogSendingFileRange(this ILogger logger, StringValues range, string path)
+ {
+ _logSendingFileRange(logger, range, path, null);
+ }
+
+ public static void LogCopyingFileRange(this ILogger logger, StringValues range, string path)
+ {
+ _logCopyingFileRange(logger, range, path, null);
+ }
+
+ public static void LogCopyingBytesToResponse(this ILogger logger, long start, long? end, string path)
+ {
+ _logCopyingBytesToResponse(
+ logger,
+ start,
+ end != null ? end.ToString() : "*",
+ path,
+ null);
+ }
+
+ public static void LogWriteCancelled(this ILogger logger, Exception ex)
+ {
+ _logWriteCancelled(logger, ex);
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj
new file mode 100644
index 0000000000..46d506ff58
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj
@@ -0,0 +1,23 @@
+
+
+
+ ASP.NET Core static files middleware. Includes middleware for serving static files, directory browsing, and default files.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;staticfiles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Properties/AssemblyInfo.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..3af9731ac7
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.StaticFiles.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Resources.Designer.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Resources.Designer.cs
new file mode 100644
index 0000000000..850389c5b1
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Resources.Designer.cs
@@ -0,0 +1,142 @@
+//
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.StaticFiles.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// No formatter provided.
+ ///
+ internal static string Args_NoFormatter
+ {
+ get { return GetString("Args_NoFormatter"); }
+ }
+
+ ///
+ /// No formatter provided.
+ ///
+ internal static string FormatArgs_NoFormatter()
+ {
+ return GetString("Args_NoFormatter");
+ }
+
+ ///
+ /// Index of
+ ///
+ internal static string HtmlDir_IndexOf
+ {
+ get { return GetString("HtmlDir_IndexOf"); }
+ }
+
+ ///
+ /// Index of
+ ///
+ internal static string FormatHtmlDir_IndexOf()
+ {
+ return GetString("HtmlDir_IndexOf");
+ }
+
+ ///
+ /// Last Modified
+ ///
+ internal static string HtmlDir_LastModified
+ {
+ get { return GetString("HtmlDir_LastModified"); }
+ }
+
+ ///
+ /// Last Modified
+ ///
+ internal static string FormatHtmlDir_LastModified()
+ {
+ return GetString("HtmlDir_LastModified");
+ }
+
+ ///
+ /// Modified
+ ///
+ internal static string HtmlDir_Modified
+ {
+ get { return GetString("HtmlDir_Modified"); }
+ }
+
+ ///
+ /// Modified
+ ///
+ internal static string FormatHtmlDir_Modified()
+ {
+ return GetString("HtmlDir_Modified");
+ }
+
+ ///
+ /// Name
+ ///
+ internal static string HtmlDir_Name
+ {
+ get { return GetString("HtmlDir_Name"); }
+ }
+
+ ///
+ /// Name
+ ///
+ internal static string FormatHtmlDir_Name()
+ {
+ return GetString("HtmlDir_Name");
+ }
+
+ ///
+ /// Size
+ ///
+ internal static string HtmlDir_Size
+ {
+ get { return GetString("HtmlDir_Size"); }
+ }
+
+ ///
+ /// Size
+ ///
+ internal static string FormatHtmlDir_Size()
+ {
+ return GetString("HtmlDir_Size");
+ }
+
+ ///
+ /// The list of files in the given directory. Column headers are listed in the first row.
+ ///
+ internal static string HtmlDir_TableSummary
+ {
+ get { return GetString("HtmlDir_TableSummary"); }
+ }
+
+ ///
+ /// The list of files in the given directory. Column headers are listed in the first row.
+ ///
+ internal static string FormatHtmlDir_TableSummary()
+ {
+ return GetString("HtmlDir_TableSummary");
+ }
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Resources.resx b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Resources.resx
new file mode 100644
index 0000000000..73d3ecda10
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/Resources.resx
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ No formatter provided.
+
+
+ Index of
+
+
+ Last Modified
+
+
+ Modified
+
+
+ Name
+
+
+ Size
+
+
+ The list of files in the given directory. Column headers are listed in the first row.
+
+
\ No newline at end of file
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs
new file mode 100644
index 0000000000..f5024dcb23
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs
@@ -0,0 +1,405 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Headers;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ internal struct StaticFileContext
+ {
+ private const int StreamCopyBufferSize = 64 * 1024;
+ private readonly HttpContext _context;
+ private readonly StaticFileOptions _options;
+ private readonly PathString _matchUrl;
+ private readonly HttpRequest _request;
+ private readonly HttpResponse _response;
+ private readonly ILogger _logger;
+ private readonly IFileProvider _fileProvider;
+ private readonly IContentTypeProvider _contentTypeProvider;
+ private string _method;
+ private bool _isGet;
+ private bool _isHead;
+ private PathString _subPath;
+ private string _contentType;
+ private IFileInfo _fileInfo;
+ private long _length;
+ private DateTimeOffset _lastModified;
+ private EntityTagHeaderValue _etag;
+
+ private RequestHeaders _requestHeaders;
+ private ResponseHeaders _responseHeaders;
+
+ private PreconditionState _ifMatchState;
+ private PreconditionState _ifNoneMatchState;
+ private PreconditionState _ifModifiedSinceState;
+ private PreconditionState _ifUnmodifiedSinceState;
+
+ private RangeItemHeaderValue _range;
+ private bool _isRangeRequest;
+
+ public StaticFileContext(HttpContext context, StaticFileOptions options, PathString matchUrl, ILogger logger, IFileProvider fileProvider, IContentTypeProvider contentTypeProvider)
+ {
+ _context = context;
+ _options = options;
+ _matchUrl = matchUrl;
+ _request = context.Request;
+ _response = context.Response;
+ _logger = logger;
+ _requestHeaders = _request.GetTypedHeaders();
+ _responseHeaders = _response.GetTypedHeaders();
+ _fileProvider = fileProvider;
+ _contentTypeProvider = contentTypeProvider;
+
+ _method = null;
+ _isGet = false;
+ _isHead = false;
+ _subPath = PathString.Empty;
+ _contentType = null;
+ _fileInfo = null;
+ _length = 0;
+ _lastModified = new DateTimeOffset();
+ _etag = null;
+ _ifMatchState = PreconditionState.Unspecified;
+ _ifNoneMatchState = PreconditionState.Unspecified;
+ _ifModifiedSinceState = PreconditionState.Unspecified;
+ _ifUnmodifiedSinceState = PreconditionState.Unspecified;
+ _range = null;
+ _isRangeRequest = false;
+ }
+
+ internal enum PreconditionState
+ {
+ Unspecified,
+ NotModified,
+ ShouldProcess,
+ PreconditionFailed
+ }
+
+ public bool IsHeadMethod
+ {
+ get { return _isHead; }
+ }
+
+ public bool IsRangeRequest
+ {
+ get { return _isRangeRequest; }
+ }
+
+ public string SubPath
+ {
+ get { return _subPath.Value; }
+ }
+
+ public string PhysicalPath
+ {
+ get { return _fileInfo?.PhysicalPath; }
+ }
+
+ public bool ValidateMethod()
+ {
+ _method = _request.Method;
+ _isGet = HttpMethods.IsGet(_method);
+ _isHead = HttpMethods.IsHead(_method);
+ return _isGet || _isHead;
+ }
+
+ // Check if the URL matches any expected paths
+ public bool ValidatePath()
+ {
+ return Helpers.TryMatchPath(_context, _matchUrl, forDirectory: false, subpath: out _subPath);
+ }
+
+ public bool LookupContentType()
+ {
+ if (_contentTypeProvider.TryGetContentType(_subPath.Value, out _contentType))
+ {
+ return true;
+ }
+
+ if (_options.ServeUnknownFileTypes)
+ {
+ _contentType = _options.DefaultContentType;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool LookupFileInfo()
+ {
+ _fileInfo = _fileProvider.GetFileInfo(_subPath.Value);
+ if (_fileInfo.Exists)
+ {
+ _length = _fileInfo.Length;
+
+ DateTimeOffset last = _fileInfo.LastModified;
+ // Truncate to the second.
+ _lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset).ToUniversalTime();
+
+ long etagHash = _lastModified.ToFileTime() ^ _length;
+ _etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
+ }
+ return _fileInfo.Exists;
+ }
+
+ public void ComprehendRequestHeaders()
+ {
+ ComputeIfMatch();
+
+ ComputeIfModifiedSince();
+
+ ComputeRange();
+
+ ComputeIfRange();
+ }
+
+ private void ComputeIfMatch()
+ {
+ // 14.24 If-Match
+ var ifMatch = _requestHeaders.IfMatch;
+ if (ifMatch != null && ifMatch.Any())
+ {
+ _ifMatchState = PreconditionState.PreconditionFailed;
+ foreach (var etag in ifMatch)
+ {
+ if (etag.Equals(EntityTagHeaderValue.Any) || etag.Compare(_etag, useStrongComparison: true))
+ {
+ _ifMatchState = PreconditionState.ShouldProcess;
+ break;
+ }
+ }
+ }
+
+ // 14.26 If-None-Match
+ var ifNoneMatch = _requestHeaders.IfNoneMatch;
+ if (ifNoneMatch != null && ifNoneMatch.Any())
+ {
+ _ifNoneMatchState = PreconditionState.ShouldProcess;
+ foreach (var etag in ifNoneMatch)
+ {
+ if (etag.Equals(EntityTagHeaderValue.Any) || etag.Compare(_etag, useStrongComparison: true))
+ {
+ _ifNoneMatchState = PreconditionState.NotModified;
+ break;
+ }
+ }
+ }
+ }
+
+ private void ComputeIfModifiedSince()
+ {
+ var now = DateTimeOffset.UtcNow;
+
+ // 14.25 If-Modified-Since
+ var ifModifiedSince = _requestHeaders.IfModifiedSince;
+ if (ifModifiedSince.HasValue && ifModifiedSince <= now)
+ {
+ bool modified = ifModifiedSince < _lastModified;
+ _ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
+ }
+
+ // 14.28 If-Unmodified-Since
+ var ifUnmodifiedSince = _requestHeaders.IfUnmodifiedSince;
+ if (ifUnmodifiedSince.HasValue && ifUnmodifiedSince <= now)
+ {
+ bool unmodified = ifUnmodifiedSince >= _lastModified;
+ _ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
+ }
+ }
+
+ private void ComputeIfRange()
+ {
+ // 14.27 If-Range
+ var ifRangeHeader = _requestHeaders.IfRange;
+ if (ifRangeHeader != null)
+ {
+ // If the validator given in the If-Range header field matches the
+ // current validator for the selected representation of the target
+ // resource, then the server SHOULD process the Range header field as
+ // requested. If the validator does not match, the server MUST ignore
+ // the Range header field.
+ if (ifRangeHeader.LastModified.HasValue)
+ {
+ if (_lastModified !=null && _lastModified > ifRangeHeader.LastModified)
+ {
+ _isRangeRequest = false;
+ }
+ }
+ else if (_etag != null && ifRangeHeader.EntityTag != null && !ifRangeHeader.EntityTag.Compare(_etag, useStrongComparison: true))
+ {
+ _isRangeRequest = false;
+ }
+ }
+ }
+
+ private void ComputeRange()
+ {
+ // 14.35 Range
+ // http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-24
+
+ // A server MUST ignore a Range header field received with a request method other
+ // than GET.
+ if (!_isGet)
+ {
+ return;
+ }
+
+ (_isRangeRequest, _range) = RangeHelper.ParseRange(_context, _requestHeaders, _length, _logger);
+ }
+
+ public void ApplyResponseHeaders(int statusCode)
+ {
+ _response.StatusCode = statusCode;
+ if (statusCode < 400)
+ {
+ // these headers are returned for 200, 206, and 304
+ // they are not returned for 412 and 416
+ if (!string.IsNullOrEmpty(_contentType))
+ {
+ _response.ContentType = _contentType;
+ }
+ _responseHeaders.LastModified = _lastModified;
+ _responseHeaders.ETag = _etag;
+ _responseHeaders.Headers[HeaderNames.AcceptRanges] = "bytes";
+ }
+ if (statusCode == Constants.Status200Ok)
+ {
+ // this header is only returned here for 200
+ // it already set to the returned range for 206
+ // it is not returned for 304, 412, and 416
+ _response.ContentLength = _length;
+ }
+ _options.OnPrepareResponse(new StaticFileResponseContext()
+ {
+ Context = _context,
+ File = _fileInfo,
+ });
+ }
+
+ public PreconditionState GetPreconditionState()
+ {
+ return GetMaxPreconditionState(_ifMatchState, _ifNoneMatchState,
+ _ifModifiedSinceState, _ifUnmodifiedSinceState);
+ }
+
+ private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states)
+ {
+ PreconditionState max = PreconditionState.Unspecified;
+ for (int i = 0; i < states.Length; i++)
+ {
+ if (states[i] > max)
+ {
+ max = states[i];
+ }
+ }
+ return max;
+ }
+
+ public Task SendStatusAsync(int statusCode)
+ {
+ ApplyResponseHeaders(statusCode);
+
+ _logger.LogHandled(statusCode, SubPath);
+ return Task.CompletedTask;
+ }
+
+ public async Task SendAsync()
+ {
+ ApplyResponseHeaders(Constants.Status200Ok);
+ string physicalPath = _fileInfo.PhysicalPath;
+ var sendFile = _context.Features.Get();
+ if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
+ {
+ // We don't need to directly cancel this, if the client disconnects it will fail silently.
+ await sendFile.SendFileAsync(physicalPath, 0, _length, CancellationToken.None);
+ return;
+ }
+
+ try
+ {
+ using (var readStream = _fileInfo.CreateReadStream())
+ {
+ // Larger StreamCopyBufferSize is required because in case of FileStream readStream isn't going to be buffering
+ await StreamCopyOperation.CopyToAsync(readStream, _response.Body, _length, StreamCopyBufferSize, _context.RequestAborted);
+ }
+ }
+ catch (OperationCanceledException ex)
+ {
+ _logger.LogWriteCancelled(ex);
+ // Don't throw this exception, it's most likely caused by the client disconnecting.
+ // However, if it was cancelled for any other reason we need to prevent empty responses.
+ _context.Abort();
+ }
+ }
+
+ // When there is only a single range the bytes are sent directly in the body.
+ internal async Task SendRangeAsync()
+ {
+ if (_range == null)
+ {
+ // 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
+ // SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
+ // the current length of the selected resource. e.g. */length
+ _responseHeaders.ContentRange = new ContentRangeHeaderValue(_length);
+ ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable);
+
+ _logger.LogRangeNotSatisfiable(SubPath);
+ return;
+ }
+
+ long start, length;
+ _responseHeaders.ContentRange = ComputeContentRange(_range, out start, out length);
+ _response.ContentLength = length;
+ ApplyResponseHeaders(Constants.Status206PartialContent);
+
+ string physicalPath = _fileInfo.PhysicalPath;
+ var sendFile = _context.Features.Get();
+ if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
+ {
+ _logger.LogSendingFileRange(_response.Headers[HeaderNames.ContentRange], physicalPath);
+ // We don't need to directly cancel this, if the client disconnects it will fail silently.
+ await sendFile.SendFileAsync(physicalPath, start, length, CancellationToken.None);
+ return;
+ }
+
+ try
+ {
+ using (var readStream = _fileInfo.CreateReadStream())
+ {
+ readStream.Seek(start, SeekOrigin.Begin); // TODO: What if !CanSeek?
+ _logger.LogCopyingFileRange(_response.Headers[HeaderNames.ContentRange], SubPath);
+ await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _context.RequestAborted);
+ }
+ }
+ catch (OperationCanceledException ex)
+ {
+ _logger.LogWriteCancelled(ex);
+ // Don't throw this exception, it's most likely caused by the client disconnecting.
+ // However, if it was cancelled for any other reason we need to prevent empty responses.
+ _context.Abort();
+ }
+ }
+
+ // Note: This assumes ranges have been normalized to absolute byte offsets.
+ private ContentRangeHeaderValue ComputeContentRange(RangeItemHeaderValue range, out long start, out long length)
+ {
+ start = range.From.Value;
+ long end = range.To.Value;
+ length = end - start + 1;
+ return new ContentRangeHeaderValue(start, end, _length);
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileExtensions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileExtensions.cs
new file mode 100644
index 0000000000..1f9270a432
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileExtensions.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods for the StaticFileMiddleware
+ ///
+ public static class StaticFileExtensions
+ {
+ ///
+ /// Enables static file serving for the current request path
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+
+ ///
+ /// Enables static file serving for the given request path
+ ///
+ ///
+ /// The relative request path.
+ ///
+ public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, string requestPath)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseStaticFiles(new StaticFileOptions
+ {
+ RequestPath = new PathString(requestPath)
+ });
+ }
+
+ ///
+ /// Enables static file serving with the given options
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ return app.UseMiddleware(Options.Create(options));
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs
new file mode 100644
index 0000000000..46594fc35d
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs
@@ -0,0 +1,142 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Enables serving static files for a given request path
+ ///
+ public class StaticFileMiddleware
+ {
+ private readonly StaticFileOptions _options;
+ private readonly PathString _matchUrl;
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+ private readonly IFileProvider _fileProvider;
+ private readonly IContentTypeProvider _contentTypeProvider;
+
+ ///
+ /// Creates a new instance of the StaticFileMiddleware.
+ ///
+ /// The next middleware in the pipeline.
+ /// The used by this middleware.
+ /// The configuration options.
+ /// An instance used to create loggers.
+ public StaticFileMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions options, ILoggerFactory loggerFactory)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (hostingEnv == null)
+ {
+ throw new ArgumentNullException(nameof(hostingEnv));
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (loggerFactory == null)
+ {
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
+
+ _next = next;
+ _options = options.Value;
+ _contentTypeProvider = options.Value.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
+ _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
+ _matchUrl = _options.RequestPath;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ ///
+ /// Processes a request to determine if it matches a known file, and if so, serves it.
+ ///
+ ///
+ ///
+ public async Task Invoke(HttpContext context)
+ {
+ var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider);
+
+ if (!fileContext.ValidateMethod())
+ {
+ _logger.LogRequestMethodNotSupported(context.Request.Method);
+ }
+ else if (!fileContext.ValidatePath())
+ {
+ _logger.LogPathMismatch(fileContext.SubPath);
+ }
+ else if (!fileContext.LookupContentType())
+ {
+ _logger.LogFileTypeNotSupported(fileContext.SubPath);
+ }
+ else if (!fileContext.LookupFileInfo())
+ {
+ _logger.LogFileNotFound(fileContext.SubPath);
+ }
+ else
+ {
+ // If we get here, we can try to serve the file
+ fileContext.ComprehendRequestHeaders();
+ switch (fileContext.GetPreconditionState())
+ {
+ case StaticFileContext.PreconditionState.Unspecified:
+ case StaticFileContext.PreconditionState.ShouldProcess:
+ if (fileContext.IsHeadMethod)
+ {
+ await fileContext.SendStatusAsync(Constants.Status200Ok);
+ return;
+ }
+
+ try
+ {
+ if (fileContext.IsRangeRequest)
+ {
+ await fileContext.SendRangeAsync();
+ return;
+ }
+
+ await fileContext.SendAsync();
+ _logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath);
+ return;
+ }
+ catch (FileNotFoundException)
+ {
+ context.Response.Clear();
+ }
+ break;
+ case StaticFileContext.PreconditionState.NotModified:
+ _logger.LogPathNotModified(fileContext.SubPath);
+ await fileContext.SendStatusAsync(Constants.Status304NotModified);
+ return;
+
+ case StaticFileContext.PreconditionState.PreconditionFailed:
+ _logger.LogPreconditionFailed(fileContext.SubPath);
+ await fileContext.SendStatusAsync(Constants.Status412PreconditionFailed);
+ return;
+
+ default:
+ var exception = new NotImplementedException(fileContext.GetPreconditionState().ToString());
+ Debug.Fail(exception.ToString());
+ throw exception;
+ }
+ }
+
+ await _next(context);
+ }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileOptions.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileOptions.cs
new file mode 100644
index 0000000000..01cef16b68
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileOptions.cs
@@ -0,0 +1,55 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.AspNetCore.StaticFiles.Infrastructure;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Options for serving static files
+ ///
+ public class StaticFileOptions : SharedOptionsBase
+ {
+ ///
+ /// Defaults to all request paths
+ ///
+ public StaticFileOptions() : this(new SharedOptions())
+ {
+ }
+
+ ///
+ /// Defaults to all request paths
+ ///
+ ///
+ public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions)
+ {
+ OnPrepareResponse = _ => { };
+ }
+
+ ///
+ /// Used to map files to content-types.
+ ///
+ public IContentTypeProvider ContentTypeProvider { get; set; }
+
+ ///
+ /// The default content type for a request if the ContentTypeProvider cannot determine one.
+ /// None is provided by default, so the client must determine the format themselves.
+ /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7
+ ///
+ public string DefaultContentType { get; set; }
+
+ ///
+ /// If the file is not a recognized content-type should it be served?
+ /// Default: false.
+ ///
+ public bool ServeUnknownFileTypes { get; set; }
+
+ ///
+ /// Called after the status code and headers have been set, but before the body has been written.
+ /// This can be used to add or change the response headers.
+ ///
+ public Action OnPrepareResponse { get; set; }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileResponseContext.cs b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileResponseContext.cs
new file mode 100644
index 0000000000..72b25c8259
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/StaticFileResponseContext.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ ///
+ /// Contains information about the request and the file that will be served in response.
+ ///
+ public class StaticFileResponseContext
+ {
+ ///
+ /// The request and response information.
+ ///
+ public HttpContext Context { get; internal set; }
+
+ ///
+ /// The file to be served.
+ ///
+ public IFileInfo File { get; internal set; }
+ }
+}
diff --git a/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/baseline.netcore.json b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/baseline.netcore.json
new file mode 100644
index 0000000000..489e7f3a81
--- /dev/null
+++ b/src/StaticFiles/src/Microsoft.AspNetCore.StaticFiles/baseline.netcore.json
@@ -0,0 +1,1077 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.StaticFiles, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.Extensions.DependencyInjection.DirectoryBrowserServiceExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "AddDirectoryBrowser",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.DefaultFilesExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "UseDefaultFiles",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseDefaultFiles",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "requestPath",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseDefaultFiles",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.AspNetCore.Builder.DefaultFilesOptions"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.DefaultFilesOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptionsBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultFileNames",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultFileNames",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Collections.Generic.IList"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "sharedOptions",
+ "Type": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.DirectoryBrowserExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "UseDirectoryBrowser",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseDirectoryBrowser",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "requestPath",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseDirectoryBrowser",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.AspNetCore.Builder.DirectoryBrowserOptions"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.DirectoryBrowserOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptionsBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Formatter",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.StaticFiles.IDirectoryFormatter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Formatter",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.StaticFiles.IDirectoryFormatter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "sharedOptions",
+ "Type": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.FileServerExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "UseFileServer",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseFileServer",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "enableDirectoryBrowsing",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseFileServer",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "requestPath",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseFileServer",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.AspNetCore.Builder.FileServerOptions"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.FileServerOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptionsBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_StaticFileOptions",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Builder.StaticFileOptions",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DirectoryBrowserOptions",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Builder.DirectoryBrowserOptions",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultFilesOptions",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Builder.DefaultFilesOptions",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_EnableDirectoryBrowsing",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_EnableDirectoryBrowsing",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_EnableDefaultFiles",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_EnableDefaultFiles",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.StaticFileExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "UseStaticFiles",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseStaticFiles",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "requestPath",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UseStaticFiles",
+ "Parameters": [
+ {
+ "Name": "app",
+ "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.AspNetCore.Builder.StaticFileOptions"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Builder.StaticFileOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptionsBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_ContentTypeProvider",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.StaticFiles.IContentTypeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContentTypeProvider",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.StaticFiles.IContentTypeProvider"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultContentType",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultContentType",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ServeUnknownFileTypes",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ServeUnknownFileTypes",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_OnPrepareResponse",
+ "Parameters": [],
+ "ReturnType": "System.Action",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_OnPrepareResponse",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "sharedOptions",
+ "Type": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.DefaultFilesMiddleware",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Invoke",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "hostingEnv",
+ "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.DirectoryBrowserMiddleware",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Invoke",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "hostingEnv",
+ "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "hostingEnv",
+ "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+ },
+ {
+ "Name": "encoder",
+ "Type": "System.Text.Encodings.Web.HtmlEncoder"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.StaticFiles.IContentTypeProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Mappings",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IDictionary",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "TryGetContentType",
+ "Parameters": [
+ {
+ "Name": "subpath",
+ "Type": "System.String"
+ },
+ {
+ "Name": "contentType",
+ "Type": "System.String",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.StaticFiles.IContentTypeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "mapping",
+ "Type": "System.Collections.Generic.IDictionary"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.HtmlDirectoryFormatter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.StaticFiles.IDirectoryFormatter"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GenerateContentAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "contents",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.StaticFiles.IDirectoryFormatter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "encoder",
+ "Type": "System.Text.Encodings.Web.HtmlEncoder"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.IContentTypeProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "TryGetContentType",
+ "Parameters": [
+ {
+ "Name": "subpath",
+ "Type": "System.String"
+ },
+ {
+ "Name": "contentType",
+ "Type": "System.String",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.IDirectoryFormatter",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GenerateContentAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "contents",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Invoke",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "next",
+ "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+ },
+ {
+ "Name": "hostingEnv",
+ "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+ },
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ },
+ {
+ "Name": "loggerFactory",
+ "Type": "Microsoft.Extensions.Logging.ILoggerFactory"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.StaticFileResponseContext",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Context",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_File",
+ "Parameters": [],
+ "ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_RequestPath",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_RequestPath",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.PathString"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_FileProvider",
+ "Parameters": [],
+ "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_FileProvider",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.Extensions.FileProviders.IFileProvider"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptionsBase",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_SharedOptions",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions",
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_RequestPath",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_RequestPath",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.PathString"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_FileProvider",
+ "Parameters": [],
+ "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_FileProvider",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.Extensions.FileProviders.IFileProvider"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "sharedOptions",
+ "Type": "Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions"
+ }
+ ],
+ "Visibility": "Protected",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/StaticFiles/test/Directory.Build.props b/src/StaticFiles/test/Directory.Build.props
new file mode 100644
index 0000000000..c6e5ecaf30
--- /dev/null
+++ b/src/StaticFiles/test/Directory.Build.props
@@ -0,0 +1,19 @@
+
+
+
+
+ netcoreapp2.1
+ $(DeveloperBuildTestTfms)
+ netcoreapp2.1;netcoreapp2.0
+ $(StandardTestTfms);net461
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj b/src/StaticFiles/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj
new file mode 100644
index 0000000000..e75e27a9bb
--- /dev/null
+++ b/src/StaticFiles/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj
@@ -0,0 +1,17 @@
+
+
+
+ $(StandardTestTfms)
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs b/src/StaticFiles/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs
new file mode 100644
index 0000000000..a6e06810a5
--- /dev/null
+++ b/src/StaticFiles/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs
@@ -0,0 +1,110 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Internal
+{
+ public class RangeHelperTests
+ {
+ [Theory]
+ [InlineData(1, 2)]
+ [InlineData(2, 3)]
+ public void NormalizeRange_ReturnsNullWhenRangeStartEqualsOrGreaterThanLength(long start, long end)
+ {
+ // Arrange & Act
+ var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(start, end), 1);
+
+ // Assert
+ Assert.Null(normalizedRange);
+ }
+
+ [Fact]
+ public void NormalizeRange_ReturnsNullWhenRangeEndEqualsZero()
+ {
+ // Arrange & Act
+ var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(null, 0), 1);
+
+ // Assert
+ Assert.Null(normalizedRange);
+ }
+
+ [Theory]
+ [InlineData(0, null, 0, 2)]
+ [InlineData(0, 0, 0, 0)]
+ public void NormalizeRange_ReturnsNormalizedRange(long? start, long? end, long? normalizedStart, long? normalizedEnd)
+ {
+ // Arrange & Act
+ var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(start, end), 3);
+
+ // Assert
+ Assert.Equal(normalizedStart, normalizedRange.From);
+ Assert.Equal(normalizedEnd, normalizedRange.To);
+ }
+
+ [Fact]
+ public void NormalizeRange_ReturnsRangeWithNoChange()
+ {
+ // Arrange & Act
+ var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(1, 3), 4);
+
+ // Assert
+ Assert.Equal(1, normalizedRange.From);
+ Assert.Equal(3, normalizedRange.To);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void ParseRange_ReturnsNullWhenRangeHeaderNotProvided(string range)
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers[HeaderNames.Range] = range;
+
+ // Act
+ var (isRangeRequest, parsedRangeResult) = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), 10, NullLogger.Instance);
+
+ // Assert
+ Assert.False(isRangeRequest);
+ Assert.Null(parsedRangeResult);
+ }
+
+ [Theory]
+ [InlineData("1-2, 3-4")]
+ [InlineData("1-2, ")]
+ public void ParseRange_ReturnsNullWhenMultipleRangesProvidedInRangeHeader(string range)
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers[HeaderNames.Range] = range;
+
+ // Act
+ var (isRangeRequest, parsedRangeResult) = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), 10, NullLogger.Instance);
+
+ // Assert
+ Assert.False(isRangeRequest);
+ Assert.Null(parsedRangeResult);
+ }
+
+ [Fact]
+ public void ParseRange_ReturnsSingleRangeWhenInputValid()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var range = new RangeHeaderValue(1, 2);
+ httpContext.Request.Headers[HeaderNames.Range] = range.ToString();
+
+ // Act
+ var (isRangeRequest, parsedRange) = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), 4, NullLogger.Instance);
+
+ // Assert
+ Assert.True(isRangeRequest);
+ Assert.Equal(1, parsedRange.From);
+ Assert.Equal(2, parsedRange.To);
+ }
+ }
+}
diff --git a/src/StaticFiles/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj b/src/StaticFiles/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj
new file mode 100644
index 0000000000..69f71eecca
--- /dev/null
+++ b/src/StaticFiles/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ $(StandardTestTfms)
+
+
+ true
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StaticFiles/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/StaticFileMiddlewareTests.cs b/src/StaticFiles/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/StaticFileMiddlewareTests.cs
new file mode 100644
index 0000000000..b2ba4a4f47
--- /dev/null
+++ b/src/StaticFiles/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/StaticFileMiddlewareTests.cs
@@ -0,0 +1,247 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing.xunit;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.StaticFiles
+{
+ public class StaticFileMiddlewareTests
+ {
+ [Fact]
+ public async Task ReturnsNotFoundWithoutWwwroot()
+ {
+ var baseAddress = "http://localhost:12345";
+ var builder = new WebHostBuilder()
+ .UseKestrel()
+ .Configure(app => app.UseStaticFiles());
+
+ using (var server = builder.Start(baseAddress))
+ {
+ using (var client = new HttpClient() { BaseAddress = new Uri(baseAddress) })
+ {
+ var response = await client.GetAsync("TestDocument.txt");
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task FoundFile_LastModifiedTrimsSeconds()
+ {
+ var baseAddress = "http://localhost:12345";
+ var builder = new WebHostBuilder()
+ .UseKestrel()
+ .UseWebRoot(AppContext.BaseDirectory)
+ .Configure(app => app.UseStaticFiles());
+
+ using (var server = builder.Start(baseAddress))
+ {
+ using (var client = new HttpClient() { BaseAddress = new Uri(baseAddress) })
+ {
+ var last = File.GetLastWriteTimeUtc(Path.Combine(AppContext.BaseDirectory, "TestDocument.txt"));
+ var response = await client.GetAsync("TestDocument.txt");
+
+ var trimed = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, TimeSpan.Zero).ToUniversalTime();
+
+ Assert.Equal(response.Content.Headers.LastModified.Value, trimed);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ExistingFiles))]
+ public async Task FoundFile_Served_All(string baseUrl, string baseDir, string requestUrl)
+ {
+ await FoundFile_Served(baseUrl, baseDir, requestUrl);
+ }
+
+ [ConditionalTheory]
+ [OSSkipCondition(OperatingSystems.Linux)]
+ [OSSkipCondition(OperatingSystems.MacOSX)]
+ [InlineData("", @".", "/testDocument.Txt")]
+ [InlineData("/somedir", @".", "/somedir/Testdocument.TXT")]
+ [InlineData("/SomeDir", @".", "/soMediR/testdocument.txT")]
+ [InlineData("/somedir", @"SubFolder", "/somedir/Ranges.tXt")]
+ public async Task FoundFile_Served_Windows(string baseUrl, string baseDir, string requestUrl)
+ {
+ await FoundFile_Served(baseUrl, baseDir, requestUrl);
+ }
+
+ private async Task FoundFile_Served(string baseUrl, string baseDir, string requestUrl)
+ {
+ var baseAddress = "http://localhost:12345";
+ var builder = new WebHostBuilder()
+ .UseKestrel()
+ .UseWebRoot(Path.Combine(AppContext.BaseDirectory, baseDir))
+ .Configure(app => app.UseStaticFiles(new StaticFileOptions()
+ {
+ RequestPath = new PathString(baseUrl),
+ }));
+
+ using (var server = builder.Start(baseAddress))
+ {
+ var hostingEnvironment = server.Services.GetService();
+
+ using (var client = new HttpClient() { BaseAddress = new Uri(baseAddress) })
+ {
+ var fileInfo = hostingEnvironment.WebRootFileProvider.GetFileInfo(Path.GetFileName(requestUrl));
+ var response = await client.GetAsync(requestUrl);
+ var responseContent = await response.Content.ReadAsByteArrayAsync();
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
+ Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length);
+
+ using (var stream = fileInfo.CreateReadStream())
+ {
+ var fileContents = new byte[stream.Length];
+ stream.Read(fileContents, 0, (int)stream.Length);
+ Assert.True(responseContent.SequenceEqual(fileContents));
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ExistingFiles))]
+ public async Task HeadFile_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl)
+ {
+ var baseAddress = "http://localhost:12345";
+ var builder = new WebHostBuilder()
+ .UseKestrel()
+ .UseWebRoot(Path.Combine(AppContext.BaseDirectory, baseDir))
+ .Configure(app => app.UseStaticFiles(new StaticFileOptions()
+ {
+ RequestPath = new PathString(baseUrl),
+ }));
+
+ using (var server = builder.Start(baseAddress))
+ {
+ var hostingEnvironment = server.Services.GetService();
+
+ using (var client = new HttpClient() { BaseAddress = new Uri(baseAddress) })
+ {
+ var fileInfo = hostingEnvironment.WebRootFileProvider.GetFileInfo(Path.GetFileName(requestUrl));
+ var request = new HttpRequestMessage(HttpMethod.Head, requestUrl);
+ var response = await client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
+ Assert.Empty((await response.Content.ReadAsByteArrayAsync()));
+ }
+ }
+ }
+
+ public static IEnumerable