├── test
├── Microsoft.AspNetCore.StaticFiles.Tests
│ ├── SubFolder
│ │ ├── Empty.txt
│ │ ├── SingleByte.txt
│ │ ├── extra.xml
│ │ ├── ranges.txt
│ │ ├── default.html
│ │ └── 你好
│ │ │ ├── default.html
│ │ │ └── 世界
│ │ │ └── default.html
│ ├── TestDocument.txt
│ ├── Microsoft.AspNetCore.StaticFiles.Tests.csproj
│ ├── StaticFilesTestServer.cs
│ ├── DefaultContentTypeProviderTests.cs
│ ├── StaticFileContextTest.cs
│ ├── DefaultFilesMiddlewareTests.cs
│ ├── DirectoryBrowserMiddlewareTests.cs
│ └── StaticFileMiddlewareTests.cs
├── Microsoft.AspNetCore.StaticFiles.FunctionalTests
│ ├── SubFolder
│ │ ├── Empty.txt
│ │ ├── SingleByte.txt
│ │ ├── extra.xml
│ │ ├── ranges.txt
│ │ └── default.html
│ ├── TestDocument.txt
│ ├── Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj
│ └── StaticFileMiddlewareTests.cs
├── Microsoft.AspNetCore.RangeHelper.Sources.Test
│ ├── Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj
│ └── RangeHelperTests.cs
└── Directory.Build.props
├── korebuild-lock.txt
├── korebuild.json
├── CONTRIBUTING.md
├── version.props
├── .github
└── ISSUE_TEMPLATE.md
├── samples
└── StaticFileSample
│ ├── wwwroot
│ └── htmlpage.html
│ ├── Properties
│ └── launchSettings.json
│ ├── StaticFileSample.csproj
│ └── Startup.cs
├── NuGet.config
├── src
├── Microsoft.AspNetCore.StaticFiles
│ ├── CustomDictionary.xml
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── IContentTypeProvider.cs
│ ├── Constants.cs
│ ├── IDirectoryFormatter.cs
│ ├── DirectoryBrowserServiceExtensions.cs
│ ├── DirectoryBrowserOptions.cs
│ ├── Microsoft.AspNetCore.StaticFiles.csproj
│ ├── Infrastructure
│ │ ├── SharedOptions.cs
│ │ └── SharedOptionsBase.cs
│ ├── DefaultFilesOptions.cs
│ ├── StaticFileResponseContext.cs
│ ├── Helpers.cs
│ ├── FileServerOptions.cs
│ ├── StaticFileOptions.cs
│ ├── StaticFileExtensions.cs
│ ├── DefaultFilesExtensions.cs
│ ├── DirectoryBrowserExtensions.cs
│ ├── FileServerExtensions.cs
│ ├── Resources.Designer.cs
│ ├── DefaultFilesMiddleware.cs
│ ├── DirectoryBrowserMiddleware.cs
│ ├── StaticFileMiddleware.cs
│ ├── HtmlDirectoryFormatter.cs
│ ├── Resources.resx
│ ├── LoggerExtensions.cs
│ └── StaticFileContext.cs
└── Directory.Build.props
├── run.cmd
├── NuGetPackageVerifier.json
├── .vsts-pipelines
└── builds
│ ├── ci-internal.yml
│ └── ci-public.yml
├── .appveyor.yml
├── .gitignore
├── README.md
├── .travis.yml
├── Directory.Build.targets
├── Directory.Build.props
├── .gitattributes
├── shared
└── Microsoft.AspNetCore.RangeHelper.Sources
│ └── RangeHelper.cs
├── run.ps1
├── StaticFiles.sln
├── run.sh
└── LICENSE.txt
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/Empty.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/SubFolder/Empty.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/SingleByte.txt:
--------------------------------------------------------------------------------
1 | A
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/extra.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/SubFolder/SingleByte.txt:
--------------------------------------------------------------------------------
1 | A
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/SubFolder/extra.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/korebuild-lock.txt:
--------------------------------------------------------------------------------
1 | version:3.0.0-alpha1-20181011.3
2 | commithash:e7569d931e994629267ab2646e9926140962b4ac
3 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/TestDocument.txt:
--------------------------------------------------------------------------------
1 | 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/ranges.txt:
--------------------------------------------------------------------------------
1 | 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/TestDocument.txt:
--------------------------------------------------------------------------------
1 | 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/SubFolder/ranges.txt:
--------------------------------------------------------------------------------
1 | 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
--------------------------------------------------------------------------------
/korebuild.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
3 | "channel": "master"
4 | }
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ======
3 |
4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo.
5 |
--------------------------------------------------------------------------------
/version.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 3.0.0
4 | dev
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | THIS ISSUE TRACKER IS CLOSED - please log new issues here: https://github.com/aspnet/Home/issues
2 |
3 | For information about this change, see https://github.com/aspnet/Announcements/issues/283
4 |
--------------------------------------------------------------------------------
/samples/StaticFileSample/wwwroot/htmlpage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | A static HTML file.
10 |
11 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/CustomDictionary.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Owin
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/run.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE"
3 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/NuGetPackageVerifier.json:
--------------------------------------------------------------------------------
1 | {
2 | "adx-nonshipping": {
3 | "rules": [],
4 | "packages": {
5 | "Microsoft.AspNetCore.RangeHelper.Sources": {}
6 | }
7 | },
8 | "Default": {
9 | "rules": [
10 | "DefaultCompositeRule"
11 | ]
12 | }
13 | }
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Hello World
10 |
11 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/你好/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Hello World
10 |
11 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/你好/世界/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Hello World
10 |
11 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/SubFolder/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Hello World
10 |
11 |
--------------------------------------------------------------------------------
/.vsts-pipelines/builds/ci-internal.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | - release/*
4 |
5 | resources:
6 | repositories:
7 | - repository: buildtools
8 | type: git
9 | name: aspnet-BuildTools
10 | ref: refs/heads/master
11 |
12 | phases:
13 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools
14 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | init:
2 | - git config --global core.autocrlf true
3 | branches:
4 | only:
5 | - master
6 | - /^release\/.*$/
7 | - /^(.*\/)?ci-.*$/
8 | build_script:
9 | - ps: .\run.ps1 default-build
10 | clone_depth: 1
11 | environment:
12 | global:
13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
15 | test: 'off'
16 | deploy: 'off'
17 | os: Visual Studio 2017
18 |
--------------------------------------------------------------------------------
/.vsts-pipelines/builds/ci-public.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | - release/*
4 |
5 | # See https://github.com/aspnet/BuildTools
6 | resources:
7 | repositories:
8 | - repository: buildtools
9 | type: github
10 | endpoint: DotNet-Bot GitHub Connection
11 | name: aspnet/BuildTools
12 | ref: refs/heads/master
13 |
14 | phases:
15 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj/
2 | [Bb]in/
3 | TestResults/
4 | .nuget/
5 | _ReSharper.*/
6 | packages/
7 | artifacts/
8 | PublishProfiles/
9 | *.user
10 | *.suo
11 | *.cache
12 | *.docstates
13 | _ReSharper.*
14 | nuget.exe
15 | *net45.csproj
16 | *net451.csproj
17 | *k10.csproj
18 | *.psess
19 | *.vsp
20 | *.pidb
21 | *.userprefs
22 | *DS_Store
23 | *.ncrunchsolution
24 | *.*sdf
25 | *.ipch
26 | *.sln.ide
27 | project.lock.json
28 | .build/
29 | .testPublish/
30 | /.vs/
31 | global.json
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | StaticFiles [Archived]
2 | ======================
3 |
4 | **This GitHub project has been archived.** Ongoing development on this project can be found in .
5 |
6 | This repo contains middleware for handling requests for file system resources including files and directories.
7 |
8 | This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo.
9 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Runtime.CompilerServices;
5 |
6 | [assembly: InternalsVisibleTo("Microsoft.AspNetCore.StaticFiles.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | sudo: false
3 | dist: trusty
4 | env:
5 | global:
6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1
8 | mono: none
9 | os:
10 | - linux
11 | - osx
12 | osx_image: xcode8.2
13 | addons:
14 | apt:
15 | packages:
16 | - libunwind8
17 | branches:
18 | only:
19 | - master
20 | - /^release\/.*$/
21 | - /^(.*\/)?ci-.*$/
22 | before_install:
23 | - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s
24 | /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
25 | /usr/local/lib/; fi
26 | script:
27 | - ./build.sh
28 |
--------------------------------------------------------------------------------
/samples/StaticFileSample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:35192/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "StaticFileSample": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "launchUrl": "http://localhost:5000/",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/samples/StaticFileSample/StaticFileSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(StandardTestTfms)
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/IContentTypeProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | namespace Microsoft.AspNetCore.StaticFiles
5 | {
6 | ///
7 | /// Used to look up MIME types given a file path
8 | ///
9 | public interface IContentTypeProvider
10 | {
11 | ///
12 | /// Given a file path, determine the MIME type
13 | ///
14 | /// A file path
15 | /// The resulting MIME type
16 | /// True if MIME type could be determined
17 | bool TryGetContentType(string subpath, out string contentType);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(MicrosoftNETCoreApp20PackageVersion)
4 | $(MicrosoftNETCoreApp21PackageVersion)
5 | $(MicrosoftNETCoreApp22PackageVersion)
6 | $(NETStandardLibrary20PackageVersion)
7 |
8 | 99.9
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Constants.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Threading.Tasks;
5 |
6 | namespace Microsoft.AspNetCore.StaticFiles
7 | {
8 | internal static class Constants
9 | {
10 | internal const string ServerCapabilitiesKey = "server.Capabilities";
11 | internal const string SendFileVersionKey = "sendfile.Version";
12 | internal const string SendFileVersion = "1.0";
13 |
14 | internal const int Status200Ok = 200;
15 | internal const int Status206PartialContent = 206;
16 | internal const int Status304NotModified = 304;
17 | internal const int Status412PreconditionFailed = 412;
18 | internal const int Status416RangeNotSatisfiable = 416;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(StandardTestTfms)
5 |
6 |
7 |
8 |
9 |
10 | PreserveNewest
11 | PreserveNewest
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/IDirectoryFormatter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.Extensions.FileProviders;
8 |
9 | namespace Microsoft.AspNetCore.StaticFiles
10 | {
11 | ///
12 | /// Generates the view for a directory
13 | ///
14 | public interface IDirectoryFormatter
15 | {
16 | ///
17 | /// Generates the view for a directory.
18 | /// Implementers should properly handle HEAD requests.
19 | /// Implementers should set all necessary response headers (e.g. Content-Type, Content-Length, etc.).
20 | ///
21 | Task GenerateContentAsync(HttpContext context, IEnumerable contents);
22 | }
23 | }
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Microsoft ASP.NET Core
12 | https://github.com/aspnet/StaticFiles
13 | git
14 | $(MSBuildThisFileDirectory)
15 | $(MSBuildThisFileDirectory)build\Key.snk
16 | true
17 | true
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.doc diff=astextplain
2 | *.DOC diff=astextplain
3 | *.docx diff=astextplain
4 | *.DOCX diff=astextplain
5 | *.dot diff=astextplain
6 | *.DOT diff=astextplain
7 | *.pdf diff=astextplain
8 | *.PDF diff=astextplain
9 | *.rtf diff=astextplain
10 | *.RTF diff=astextplain
11 |
12 | *.jpg binary
13 | *.png binary
14 | *.gif binary
15 |
16 | *.cs text=auto diff=csharp
17 | *.vb text=auto
18 | *.resx text=auto
19 | *.c text=auto
20 | *.cpp text=auto
21 | *.cxx text=auto
22 | *.h text=auto
23 | *.hxx text=auto
24 | *.py text=auto
25 | *.rb text=auto
26 | *.java text=auto
27 | *.html text=auto
28 | *.htm text=auto
29 | *.css text=auto
30 | *.scss text=auto
31 | *.sass text=auto
32 | *.less text=auto
33 | *.js text=auto
34 | *.lisp text=auto
35 | *.clj text=auto
36 | *.sql text=auto
37 | *.php text=auto
38 | *.lua text=auto
39 | *.m text=auto
40 | *.asm text=auto
41 | *.erl text=auto
42 | *.fs text=auto
43 | *.fsx text=auto
44 | *.hs text=auto
45 |
46 | *.csproj text=auto
47 | *.vbproj text=auto
48 | *.fsproj text=auto
49 | *.dbproj text=auto
50 | *.sln text=auto eol=crlf
51 | *.sh eol=lf
52 |
--------------------------------------------------------------------------------
/test/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | netcoreapp2.2
6 | $(DeveloperBuildTestTfms)
7 |
8 | $(StandardTestTfms);net461
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserServiceExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace Microsoft.Extensions.DependencyInjection
7 | {
8 | ///
9 | /// Extension methods for adding directory browser services.
10 | ///
11 | public static class DirectoryBrowserServiceExtensions
12 | {
13 | ///
14 | /// Adds directory browser middleware services.
15 | ///
16 | /// The to add services to.
17 | /// The so that additional calls can be chained.
18 | public static IServiceCollection AddDirectoryBrowser(this IServiceCollection services)
19 | {
20 | if (services == null)
21 | {
22 | throw new ArgumentNullException(nameof(services));
23 | }
24 |
25 | services.AddWebEncoders();
26 |
27 | return services;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.AspNetCore.StaticFiles;
5 | using Microsoft.AspNetCore.StaticFiles.Infrastructure;
6 |
7 | namespace Microsoft.AspNetCore.Builder
8 | {
9 | ///
10 | /// Directory browsing options
11 | ///
12 | public class DirectoryBrowserOptions : SharedOptionsBase
13 | {
14 | ///
15 | /// Enabled directory browsing for all request paths
16 | ///
17 | public DirectoryBrowserOptions()
18 | : this(new SharedOptions())
19 | {
20 | }
21 |
22 | ///
23 | /// Enabled directory browsing all request paths
24 | ///
25 | ///
26 | public DirectoryBrowserOptions(SharedOptions sharedOptions)
27 | : base(sharedOptions)
28 | {
29 | }
30 |
31 | ///
32 | /// The component that generates the view.
33 | ///
34 | public IDirectoryFormatter Formatter { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/samples/StaticFileSample/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace StaticFilesSample
9 | {
10 | public class Startup
11 | {
12 | public void ConfigureServices(IServiceCollection services)
13 | {
14 | services.AddDirectoryBrowser();
15 | }
16 |
17 | public void Configure(IApplicationBuilder app, IHostingEnvironment host)
18 | {
19 | Console.WriteLine("webroot: " + host.WebRootPath);
20 |
21 | app.UseFileServer(new FileServerOptions
22 | {
23 | EnableDirectoryBrowsing = true
24 | });
25 | }
26 |
27 | public static void Main(string[] args)
28 | {
29 | var host = new WebHostBuilder()
30 | .ConfigureLogging(factory =>
31 | {
32 | factory.AddFilter("Console", level => level >= LogLevel.Debug);
33 | factory.AddConsole();
34 | })
35 | .UseContentRoot(Directory.GetCurrentDirectory())
36 | .UseKestrel()
37 | .UseIISIntegration()
38 | .UseStartup()
39 | .Build();
40 |
41 | host.Run();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ASP.NET Core static files middleware. Includes middleware for serving static files, directory browsing, and default files.
5 | netstandard2.0
6 | $(NoWarn);CS1591
7 | true
8 | aspnetcore;staticfiles
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFilesTestServer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Runtime.InteropServices;
7 | using Microsoft.AspNetCore.Builder;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.TestHost;
10 | using Microsoft.Extensions.Configuration;
11 | using Microsoft.Extensions.DependencyInjection;
12 |
13 | namespace Microsoft.AspNetCore.StaticFiles
14 | {
15 | public static class StaticFilesTestServer
16 | {
17 | public static TestServer Create(Action configureApp, Action configureServices = null)
18 | {
19 | Action defaultConfigureServices = services => { };
20 | var configuration = new ConfigurationBuilder()
21 | .AddInMemoryCollection(new []
22 | {
23 | new KeyValuePair("webroot", ".")
24 | })
25 | .Build();
26 | var builder = new WebHostBuilder()
27 | .UseConfiguration(configuration)
28 | .Configure(configureApp)
29 | .ConfigureServices(configureServices ?? defaultConfigureServices);
30 | return new TestServer(builder);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.Extensions.FileProviders;
7 |
8 | namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
9 | {
10 | ///
11 | /// Options common to several middleware components
12 | ///
13 | public class SharedOptions
14 | {
15 | private PathString _requestPath;
16 |
17 | ///
18 | /// Defaults to all request paths.
19 | ///
20 | public SharedOptions()
21 | {
22 | RequestPath = PathString.Empty;
23 | }
24 |
25 | ///
26 | /// The request path that maps to static resources
27 | ///
28 | public PathString RequestPath
29 | {
30 | get { return _requestPath; }
31 | set
32 | {
33 | if (value.HasValue && value.Value.EndsWith("/", StringComparison.Ordinal))
34 | {
35 | throw new ArgumentException("Request path must not end in a slash");
36 | }
37 | _requestPath = value;
38 | }
39 | }
40 |
41 | ///
42 | /// The file system used to locate resources
43 | ///
44 | public IFileProvider FileProvider { get; set; }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using Microsoft.AspNetCore.StaticFiles.Infrastructure;
6 |
7 | namespace Microsoft.AspNetCore.Builder
8 | {
9 | ///
10 | /// Options for selecting default file names.
11 | ///
12 | public class DefaultFilesOptions : SharedOptionsBase
13 | {
14 | ///
15 | /// Configuration for the DefaultFilesMiddleware.
16 | ///
17 | public DefaultFilesOptions()
18 | : this(new SharedOptions())
19 | {
20 | }
21 |
22 | ///
23 | /// Configuration for the DefaultFilesMiddleware.
24 | ///
25 | ///
26 | public DefaultFilesOptions(SharedOptions sharedOptions)
27 | : base(sharedOptions)
28 | {
29 | // Prioritized list
30 | DefaultFileNames = new List
31 | {
32 | "default.htm",
33 | "default.html",
34 | "index.htm",
35 | "index.html",
36 | };
37 | }
38 |
39 | ///
40 | /// An ordered list of file names to select by default. List length and ordering may affect performance.
41 | ///
42 | public IList DefaultFileNames { get; set; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/StaticFileResponseContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.Extensions.FileProviders;
7 |
8 | namespace Microsoft.AspNetCore.StaticFiles
9 | {
10 | ///
11 | /// Contains information about the request and the file that will be served in response.
12 | ///
13 | public class StaticFileResponseContext
14 | {
15 | [Obsolete("Use the constructor that passes in the HttpContext and IFileInfo parameters: StaticFileResponseContext(HttpContext context, IFileInfo file)", false)]
16 | public StaticFileResponseContext()
17 | {
18 | }
19 |
20 | public StaticFileResponseContext(HttpContext context, IFileInfo file)
21 | {
22 | if (file == null)
23 | {
24 | throw new ArgumentNullException(nameof(file));
25 | }
26 | if (context == null)
27 | {
28 | throw new ArgumentNullException(nameof(context));
29 | }
30 | Context = context;
31 | File = file;
32 | }
33 |
34 | ///
35 | /// The request and response information.
36 | ///
37 | public HttpContext Context { get; }
38 |
39 | ///
40 | /// The file to be served.
41 | ///
42 | public IFileInfo File { get; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Helpers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.Extensions.FileProviders;
8 |
9 | namespace Microsoft.AspNetCore.StaticFiles
10 | {
11 | internal static class Helpers
12 | {
13 | internal static IFileProvider ResolveFileProvider(IHostingEnvironment hostingEnv)
14 | {
15 | if (hostingEnv.WebRootFileProvider == null) {
16 | throw new InvalidOperationException("Missing FileProvider.");
17 | }
18 | return hostingEnv.WebRootFileProvider;
19 | }
20 |
21 | internal static bool IsGetOrHeadMethod(string method)
22 | {
23 | return HttpMethods.IsGet(method) || HttpMethods.IsHead(method);
24 | }
25 |
26 | internal static bool PathEndsInSlash(PathString path)
27 | {
28 | return path.Value.EndsWith("/", StringComparison.Ordinal);
29 | }
30 |
31 | internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath)
32 | {
33 | var path = context.Request.Path;
34 |
35 | if (forDirectory && !PathEndsInSlash(path))
36 | {
37 | path += new PathString("/");
38 | }
39 |
40 | if (path.StartsWithSegments(matchUrl, out subpath))
41 | {
42 | return true;
43 | }
44 | return false;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(StandardTestTfms)
5 |
6 |
10 | true
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 |
18 |
19 | PreserveNewest
20 | PreserveNewest
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/SharedOptionsBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.Extensions.FileProviders;
7 |
8 | namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
9 | {
10 | ///
11 | /// Options common to several middleware components
12 | ///
13 | public abstract class SharedOptionsBase
14 | {
15 | ///
16 | /// Creates an new instance of the SharedOptionsBase.
17 | ///
18 | ///
19 | protected SharedOptionsBase(SharedOptions sharedOptions)
20 | {
21 | if (sharedOptions == null)
22 | {
23 | throw new ArgumentNullException(nameof(sharedOptions));
24 | }
25 |
26 | SharedOptions = sharedOptions;
27 | }
28 |
29 | ///
30 | /// Options common to several middleware components
31 | ///
32 | protected SharedOptions SharedOptions { get; private set; }
33 |
34 | ///
35 | /// The relative request path that maps to static resources.
36 | ///
37 | public PathString RequestPath
38 | {
39 | get { return SharedOptions.RequestPath; }
40 | set { SharedOptions.RequestPath = value; }
41 | }
42 |
43 | ///
44 | /// The file system used to locate resources
45 | ///
46 | public IFileProvider FileProvider
47 | {
48 | get { return SharedOptions.FileProvider; }
49 | set { SharedOptions.FileProvider = value; }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/FileServerOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.AspNetCore.StaticFiles.Infrastructure;
5 |
6 | namespace Microsoft.AspNetCore.Builder
7 | {
8 | ///
9 | /// Options for all of the static file middleware components
10 | ///
11 | public class FileServerOptions : SharedOptionsBase
12 | {
13 | ///
14 | /// Creates a combined options class for all of the static file middleware components.
15 | ///
16 | public FileServerOptions()
17 | : base(new SharedOptions())
18 | {
19 | StaticFileOptions = new StaticFileOptions(SharedOptions);
20 | DirectoryBrowserOptions = new DirectoryBrowserOptions(SharedOptions);
21 | DefaultFilesOptions = new DefaultFilesOptions(SharedOptions);
22 | EnableDefaultFiles = true;
23 | }
24 |
25 | ///
26 | /// Options for configuring the StaticFileMiddleware.
27 | ///
28 | public StaticFileOptions StaticFileOptions { get; private set; }
29 |
30 | ///
31 | /// Options for configuring the DirectoryBrowserMiddleware.
32 | ///
33 | public DirectoryBrowserOptions DirectoryBrowserOptions { get; private set; }
34 |
35 | ///
36 | /// Options for configuring the DefaultFilesMiddleware.
37 | ///
38 | public DefaultFilesOptions DefaultFilesOptions { get; private set; }
39 |
40 | ///
41 | /// Directory browsing is disabled by default.
42 | ///
43 | public bool EnableDirectoryBrowsing { get; set; }
44 |
45 | ///
46 | /// Default files are enabled by default.
47 | ///
48 | public bool EnableDefaultFiles { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/StaticFileOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.StaticFiles;
6 | using Microsoft.AspNetCore.StaticFiles.Infrastructure;
7 |
8 | namespace Microsoft.AspNetCore.Builder
9 | {
10 | ///
11 | /// Options for serving static files
12 | ///
13 | public class StaticFileOptions : SharedOptionsBase
14 | {
15 | ///
16 | /// Defaults to all request paths
17 | ///
18 | public StaticFileOptions() : this(new SharedOptions())
19 | {
20 | }
21 |
22 | ///
23 | /// Defaults to all request paths
24 | ///
25 | ///
26 | public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions)
27 | {
28 | OnPrepareResponse = _ => { };
29 | }
30 |
31 | ///
32 | /// Used to map files to content-types.
33 | ///
34 | public IContentTypeProvider ContentTypeProvider { get; set; }
35 |
36 | ///
37 | /// The default content type for a request if the ContentTypeProvider cannot determine one.
38 | /// None is provided by default, so the client must determine the format themselves.
39 | /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7
40 | ///
41 | public string DefaultContentType { get; set; }
42 |
43 | ///
44 | /// If the file is not a recognized content-type should it be served?
45 | /// Default: false.
46 | ///
47 | public bool ServeUnknownFileTypes { get; set; }
48 |
49 | ///
50 | /// Called after the status code and headers have been set, but before the body has been written.
51 | /// This can be used to add or change the response headers.
52 | ///
53 | public Action OnPrepareResponse { get; set; }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/StaticFileExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.StaticFiles;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace Microsoft.AspNetCore.Builder
10 | {
11 | ///
12 | /// Extension methods for the StaticFileMiddleware
13 | ///
14 | public static class StaticFileExtensions
15 | {
16 | ///
17 | /// Enables static file serving for the current request path
18 | ///
19 | ///
20 | ///
21 | public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app)
22 | {
23 | if (app == null)
24 | {
25 | throw new ArgumentNullException(nameof(app));
26 | }
27 |
28 | return app.UseMiddleware();
29 | }
30 |
31 | ///
32 | /// Enables static file serving for the given request path
33 | ///
34 | ///
35 | /// The relative request path.
36 | ///
37 | public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, string requestPath)
38 | {
39 | if (app == null)
40 | {
41 | throw new ArgumentNullException(nameof(app));
42 | }
43 |
44 | return app.UseStaticFiles(new StaticFileOptions
45 | {
46 | RequestPath = new PathString(requestPath)
47 | });
48 | }
49 |
50 | ///
51 | /// Enables static file serving with the given options
52 | ///
53 | ///
54 | ///
55 | ///
56 | public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options)
57 | {
58 | if (app == null)
59 | {
60 | throw new ArgumentNullException(nameof(app));
61 | }
62 | if (options == null)
63 | {
64 | throw new ArgumentNullException(nameof(options));
65 | }
66 |
67 | return app.UseMiddleware(Options.Create(options));
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.StaticFiles;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace Microsoft.AspNetCore.Builder
10 | {
11 | ///
12 | /// Extension methods for the DefaultFilesMiddleware
13 | ///
14 | public static class DefaultFilesExtensions
15 | {
16 | ///
17 | /// Enables default file mapping on the current path
18 | ///
19 | ///
20 | ///
21 | public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app)
22 | {
23 | if (app == null)
24 | {
25 | throw new ArgumentNullException(nameof(app));
26 | }
27 |
28 | return app.UseMiddleware();
29 | }
30 |
31 | ///
32 | /// Enables default file mapping for the given request path
33 | ///
34 | ///
35 | /// The relative request path.
36 | ///
37 | public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, string requestPath)
38 | {
39 | if (app == null)
40 | {
41 | throw new ArgumentNullException(nameof(app));
42 | }
43 |
44 | return app.UseDefaultFiles(new DefaultFilesOptions
45 | {
46 | RequestPath = new PathString(requestPath)
47 | });
48 | }
49 |
50 | ///
51 | /// Enables default file mapping with the given options
52 | ///
53 | ///
54 | ///
55 | ///
56 | public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, DefaultFilesOptions options)
57 | {
58 | if (app == null)
59 | {
60 | throw new ArgumentNullException(nameof(app));
61 | }
62 | if (options == null)
63 | {
64 | throw new ArgumentNullException(nameof(options));
65 | }
66 |
67 | return app.UseMiddleware(Options.Create(options));
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.StaticFiles;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace Microsoft.AspNetCore.Builder
10 | {
11 | ///
12 | /// Extension methods for the DirectoryBrowserMiddleware
13 | ///
14 | public static class DirectoryBrowserExtensions
15 | {
16 | ///
17 | /// Enable directory browsing on the current path
18 | ///
19 | ///
20 | ///
21 | public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app)
22 | {
23 | if (app == null)
24 | {
25 | throw new ArgumentNullException(nameof(app));
26 | }
27 |
28 | return app.UseMiddleware();
29 | }
30 |
31 | ///
32 | /// Enables directory browsing for the given request path
33 | ///
34 | ///
35 | /// The relative request path.
36 | ///
37 | public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, string requestPath)
38 | {
39 | if (app == null)
40 | {
41 | throw new ArgumentNullException(nameof(app));
42 | }
43 |
44 | return app.UseDirectoryBrowser(new DirectoryBrowserOptions
45 | {
46 | RequestPath = new PathString(requestPath)
47 | });
48 | }
49 |
50 | ///
51 | /// Enable directory browsing with the given options
52 | ///
53 | ///
54 | ///
55 | ///
56 | public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, DirectoryBrowserOptions options)
57 | {
58 | if (app == null)
59 | {
60 | throw new ArgumentNullException(nameof(app));
61 | }
62 | if (options == null)
63 | {
64 | throw new ArgumentNullException(nameof(options));
65 | }
66 |
67 | return app.UseMiddleware(Options.Create(options));
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/DefaultContentTypeProviderTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Xunit;
5 |
6 | namespace Microsoft.AspNetCore.StaticFiles
7 | {
8 | public class DefaultContentTypeProviderTests
9 | {
10 | [Fact]
11 | public void UnknownExtensionsReturnFalse()
12 | {
13 | var provider = new FileExtensionContentTypeProvider();
14 | string contentType;
15 | Assert.False(provider.TryGetContentType("unknown.ext", out contentType));
16 | }
17 |
18 | [Fact]
19 | public void KnownExtensionsReturnType()
20 | {
21 | var provider = new FileExtensionContentTypeProvider();
22 | Assert.True(provider.TryGetContentType("known.txt", out var contentType));
23 | Assert.Equal("text/plain", contentType);
24 | }
25 |
26 | [Fact]
27 | public void DoubleDottedExtensionsAreNotSupported()
28 | {
29 | var provider = new FileExtensionContentTypeProvider();
30 | string contentType;
31 | Assert.False(provider.TryGetContentType("known.exe.config", out contentType));
32 | }
33 |
34 | [Fact]
35 | public void DashedExtensionsShouldBeMatched()
36 | {
37 | var provider = new FileExtensionContentTypeProvider();
38 | Assert.True(provider.TryGetContentType("known.dvr-ms", out var contentType));
39 | Assert.Equal("video/x-ms-dvr", contentType);
40 | }
41 |
42 | [Fact]
43 | public void BothSlashFormatsAreUnderstood()
44 | {
45 | var provider = new FileExtensionContentTypeProvider();
46 | Assert.True(provider.TryGetContentType(@"/first/example.txt", out var contentType));
47 | Assert.Equal("text/plain", contentType);
48 | Assert.True(provider.TryGetContentType(@"\second\example.txt", out contentType));
49 | Assert.Equal("text/plain", contentType);
50 | }
51 |
52 | [Fact]
53 | public void DotsInDirectoryAreIgnored()
54 | {
55 | var provider = new FileExtensionContentTypeProvider();
56 | Assert.True(provider.TryGetContentType(@"/first.css/example.txt", out var contentType));
57 | Assert.Equal("text/plain", contentType);
58 | Assert.True(provider.TryGetContentType(@"\second.css\example.txt", out contentType));
59 | Assert.Equal("text/plain", contentType);
60 | }
61 |
62 | [Fact]
63 | public void InvalidCharactersAreIgnored()
64 | {
65 | var provider = new FileExtensionContentTypeProvider();
66 | string contentType;
67 | Assert.True(provider.TryGetContentType($"{new string(System.IO.Path.GetInvalidPathChars())}.txt", out contentType));
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/FileServerExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.StaticFiles;
7 |
8 | namespace Microsoft.AspNetCore.Builder
9 | {
10 | ///
11 | /// Extension methods that combine all of the static file middleware components:
12 | /// Default files, directory browsing, send file, and static files
13 | ///
14 | public static class FileServerExtensions
15 | {
16 | ///
17 | /// Enable all static file middleware (except directory browsing) for the current request path in the current directory.
18 | ///
19 | ///
20 | ///
21 | public static IApplicationBuilder UseFileServer(this IApplicationBuilder app)
22 | {
23 | if (app == null)
24 | {
25 | throw new ArgumentNullException(nameof(app));
26 | }
27 |
28 | return app.UseFileServer(new FileServerOptions());
29 | }
30 |
31 | ///
32 | /// Enable all static file middleware on for the current request path in the current directory.
33 | ///
34 | ///
35 | /// Should directory browsing be enabled?
36 | ///
37 | public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, bool enableDirectoryBrowsing)
38 | {
39 | if (app == null)
40 | {
41 | throw new ArgumentNullException(nameof(app));
42 | }
43 |
44 | return app.UseFileServer(new FileServerOptions
45 | {
46 | EnableDirectoryBrowsing = enableDirectoryBrowsing
47 | });
48 | }
49 |
50 | ///
51 | /// Enables all static file middleware (except directory browsing) for the given request path from the directory of the same name
52 | ///
53 | ///
54 | /// The relative request path.
55 | ///
56 | public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, string requestPath)
57 | {
58 | if (app == null)
59 | {
60 | throw new ArgumentNullException(nameof(app));
61 | }
62 |
63 | if (requestPath == null)
64 | {
65 | throw new ArgumentNullException(nameof(requestPath));
66 | }
67 |
68 | return app.UseFileServer(new FileServerOptions
69 | {
70 | RequestPath = new PathString(requestPath)
71 | });
72 | }
73 |
74 | ///
75 | /// Enable all static file middleware with the given options
76 | ///
77 | ///
78 | ///
79 | ///
80 | public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, FileServerOptions options)
81 | {
82 | if (app == null)
83 | {
84 | throw new ArgumentNullException(nameof(app));
85 | }
86 | if (options == null)
87 | {
88 | throw new ArgumentNullException(nameof(options));
89 | }
90 |
91 | if (options.EnableDefaultFiles)
92 | {
93 | app.UseDefaultFiles(options.DefaultFilesOptions);
94 | }
95 |
96 | if (options.EnableDirectoryBrowsing)
97 | {
98 | app.UseDirectoryBrowser(options.DirectoryBrowserOptions);
99 | }
100 |
101 | return app.UseStaticFiles(options.StaticFileOptions);
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.Extensions.Logging.Abstractions;
6 | using Microsoft.Net.Http.Headers;
7 | using Xunit;
8 |
9 | namespace Microsoft.AspNetCore.Internal
10 | {
11 | public class RangeHelperTests
12 | {
13 | [Theory]
14 | [InlineData(1, 2)]
15 | [InlineData(2, 3)]
16 | public void NormalizeRange_ReturnsNullWhenRangeStartEqualsOrGreaterThanLength(long start, long end)
17 | {
18 | // Arrange & Act
19 | var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(start, end), 1);
20 |
21 | // Assert
22 | Assert.Null(normalizedRange);
23 | }
24 |
25 | [Fact]
26 | public void NormalizeRange_ReturnsNullWhenRangeEndEqualsZero()
27 | {
28 | // Arrange & Act
29 | var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(null, 0), 1);
30 |
31 | // Assert
32 | Assert.Null(normalizedRange);
33 | }
34 |
35 | [Theory]
36 | [InlineData(0, null, 0, 2)]
37 | [InlineData(0, 0, 0, 0)]
38 | public void NormalizeRange_ReturnsNormalizedRange(long? start, long? end, long? normalizedStart, long? normalizedEnd)
39 | {
40 | // Arrange & Act
41 | var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(start, end), 3);
42 |
43 | // Assert
44 | Assert.Equal(normalizedStart, normalizedRange.From);
45 | Assert.Equal(normalizedEnd, normalizedRange.To);
46 | }
47 |
48 | [Fact]
49 | public void NormalizeRange_ReturnsRangeWithNoChange()
50 | {
51 | // Arrange & Act
52 | var normalizedRange = RangeHelper.NormalizeRange(new RangeItemHeaderValue(1, 3), 4);
53 |
54 | // Assert
55 | Assert.Equal(1, normalizedRange.From);
56 | Assert.Equal(3, normalizedRange.To);
57 | }
58 |
59 | [Theory]
60 | [InlineData(null)]
61 | [InlineData("")]
62 | public void ParseRange_ReturnsNullWhenRangeHeaderNotProvided(string range)
63 | {
64 | // Arrange
65 | var httpContext = new DefaultHttpContext();
66 | httpContext.Request.Headers[HeaderNames.Range] = range;
67 |
68 | // Act
69 | var (isRangeRequest, parsedRangeResult) = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), 10, NullLogger.Instance);
70 |
71 | // Assert
72 | Assert.False(isRangeRequest);
73 | Assert.Null(parsedRangeResult);
74 | }
75 |
76 | [Theory]
77 | [InlineData("1-2, 3-4")]
78 | [InlineData("1-2, ")]
79 | public void ParseRange_ReturnsNullWhenMultipleRangesProvidedInRangeHeader(string range)
80 | {
81 | // Arrange
82 | var httpContext = new DefaultHttpContext();
83 | httpContext.Request.Headers[HeaderNames.Range] = range;
84 |
85 | // Act
86 | var (isRangeRequest, parsedRangeResult) = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), 10, NullLogger.Instance);
87 |
88 | // Assert
89 | Assert.False(isRangeRequest);
90 | Assert.Null(parsedRangeResult);
91 | }
92 |
93 | [Fact]
94 | public void ParseRange_ReturnsSingleRangeWhenInputValid()
95 | {
96 | // Arrange
97 | var httpContext = new DefaultHttpContext();
98 | var range = new RangeHeaderValue(1, 2);
99 | httpContext.Request.Headers[HeaderNames.Range] = range.ToString();
100 |
101 | // Act
102 | var (isRangeRequest, parsedRange) = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), 4, NullLogger.Instance);
103 |
104 | // Assert
105 | Assert.True(isRangeRequest);
106 | Assert.Equal(1, parsedRange.From);
107 | Assert.Equal(2, parsedRange.To);
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | namespace Microsoft.AspNetCore.StaticFiles
3 | {
4 | using System.Globalization;
5 | using System.Reflection;
6 | using System.Resources;
7 |
8 | internal static class Resources
9 | {
10 | private static readonly ResourceManager _resourceManager
11 | = new ResourceManager("Microsoft.AspNetCore.StaticFiles.Resources", typeof(Resources).GetTypeInfo().Assembly);
12 |
13 | ///
14 | /// No formatter provided.
15 | ///
16 | internal static string Args_NoFormatter
17 | {
18 | get { return GetString("Args_NoFormatter"); }
19 | }
20 |
21 | ///
22 | /// No formatter provided.
23 | ///
24 | internal static string FormatArgs_NoFormatter()
25 | {
26 | return GetString("Args_NoFormatter");
27 | }
28 |
29 | ///
30 | /// Index of
31 | ///
32 | internal static string HtmlDir_IndexOf
33 | {
34 | get { return GetString("HtmlDir_IndexOf"); }
35 | }
36 |
37 | ///
38 | /// Index of
39 | ///
40 | internal static string FormatHtmlDir_IndexOf()
41 | {
42 | return GetString("HtmlDir_IndexOf");
43 | }
44 |
45 | ///
46 | /// Last Modified
47 | ///
48 | internal static string HtmlDir_LastModified
49 | {
50 | get { return GetString("HtmlDir_LastModified"); }
51 | }
52 |
53 | ///
54 | /// Last Modified
55 | ///
56 | internal static string FormatHtmlDir_LastModified()
57 | {
58 | return GetString("HtmlDir_LastModified");
59 | }
60 |
61 | ///
62 | /// Modified
63 | ///
64 | internal static string HtmlDir_Modified
65 | {
66 | get { return GetString("HtmlDir_Modified"); }
67 | }
68 |
69 | ///
70 | /// Modified
71 | ///
72 | internal static string FormatHtmlDir_Modified()
73 | {
74 | return GetString("HtmlDir_Modified");
75 | }
76 |
77 | ///
78 | /// Name
79 | ///
80 | internal static string HtmlDir_Name
81 | {
82 | get { return GetString("HtmlDir_Name"); }
83 | }
84 |
85 | ///
86 | /// Name
87 | ///
88 | internal static string FormatHtmlDir_Name()
89 | {
90 | return GetString("HtmlDir_Name");
91 | }
92 |
93 | ///
94 | /// Size
95 | ///
96 | internal static string HtmlDir_Size
97 | {
98 | get { return GetString("HtmlDir_Size"); }
99 | }
100 |
101 | ///
102 | /// Size
103 | ///
104 | internal static string FormatHtmlDir_Size()
105 | {
106 | return GetString("HtmlDir_Size");
107 | }
108 |
109 | ///
110 | /// The list of files in the given directory. Column headers are listed in the first row.
111 | ///
112 | internal static string HtmlDir_TableSummary
113 | {
114 | get { return GetString("HtmlDir_TableSummary"); }
115 | }
116 |
117 | ///
118 | /// The list of files in the given directory. Column headers are listed in the first row.
119 | ///
120 | internal static string FormatHtmlDir_TableSummary()
121 | {
122 | return GetString("HtmlDir_TableSummary");
123 | }
124 |
125 | private static string GetString(string name, params string[] formatterNames)
126 | {
127 | var value = _resourceManager.GetString(name);
128 |
129 | System.Diagnostics.Debug.Assert(value != null);
130 |
131 | if (formatterNames != null)
132 | {
133 | for (var i = 0; i < formatterNames.Length; i++)
134 | {
135 | value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
136 | }
137 | }
138 |
139 | return value;
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DefaultFilesMiddleware.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Builder;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.Extensions.FileProviders;
10 | using Microsoft.Extensions.Options;
11 | using Microsoft.Net.Http.Headers;
12 |
13 | namespace Microsoft.AspNetCore.StaticFiles
14 | {
15 | ///
16 | /// This examines a directory path and determines if there is a default file present.
17 | /// If so the file name is appended to the path and execution continues.
18 | /// Note we don't just serve the file because it may require interpretation.
19 | ///
20 | public class DefaultFilesMiddleware
21 | {
22 | private readonly DefaultFilesOptions _options;
23 | private readonly PathString _matchUrl;
24 | private readonly RequestDelegate _next;
25 | private readonly IFileProvider _fileProvider;
26 |
27 | ///
28 | /// Creates a new instance of the DefaultFilesMiddleware.
29 | ///
30 | /// The next middleware in the pipeline.
31 | /// The used by this middleware.
32 | /// The configuration options for this middleware.
33 | public DefaultFilesMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions options)
34 | {
35 | if (next == null)
36 | {
37 | throw new ArgumentNullException(nameof(next));
38 | }
39 |
40 | if (hostingEnv == null)
41 | {
42 | throw new ArgumentNullException(nameof(hostingEnv));
43 | }
44 |
45 | if (options == null)
46 | {
47 | throw new ArgumentNullException(nameof(options));
48 | }
49 |
50 | _next = next;
51 | _options = options.Value;
52 | _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
53 | _matchUrl = _options.RequestPath;
54 | }
55 |
56 | ///
57 | /// This examines the request to see if it matches a configured directory, and if there are any files with the
58 | /// configured default names in that directory. If so this will append the corresponding file name to the request
59 | /// path for a later middleware to handle.
60 | ///
61 | ///
62 | ///
63 | public Task Invoke(HttpContext context)
64 | {
65 | if (Helpers.IsGetOrHeadMethod(context.Request.Method)
66 | && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath))
67 | {
68 | var dirContents = _fileProvider.GetDirectoryContents(subpath.Value);
69 | if (dirContents.Exists)
70 | {
71 | // Check if any of our default files exist.
72 | for (int matchIndex = 0; matchIndex < _options.DefaultFileNames.Count; matchIndex++)
73 | {
74 | string defaultFile = _options.DefaultFileNames[matchIndex];
75 | var file = _fileProvider.GetFileInfo(subpath.Value + defaultFile);
76 | // TryMatchPath will make sure subpath always ends with a "/" by adding it if needed.
77 | if (file.Exists)
78 | {
79 | // If the path matches a directory but does not end in a slash, redirect to add the slash.
80 | // This prevents relative links from breaking.
81 | if (!Helpers.PathEndsInSlash(context.Request.Path))
82 | {
83 | context.Response.StatusCode = 301;
84 | context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
85 | return Task.CompletedTask;
86 | }
87 |
88 | // Match found, re-write the url. A later middleware will actually serve the file.
89 | context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);
90 | break;
91 | }
92 | }
93 | }
94 | }
95 |
96 | return _next(context);
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/DirectoryBrowserMiddleware.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Text.Encodings.Web;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Builder;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.Extensions.FileProviders;
11 | using Microsoft.Extensions.Options;
12 | using Microsoft.Net.Http.Headers;
13 |
14 | namespace Microsoft.AspNetCore.StaticFiles
15 | {
16 | ///
17 | /// Enables directory browsing
18 | ///
19 | public class DirectoryBrowserMiddleware
20 | {
21 | private readonly DirectoryBrowserOptions _options;
22 | private readonly PathString _matchUrl;
23 | private readonly RequestDelegate _next;
24 | private readonly IDirectoryFormatter _formatter;
25 | private readonly IFileProvider _fileProvider;
26 |
27 | ///
28 | /// Creates a new instance of the SendFileMiddleware. Using instance.
29 | ///
30 | /// The next middleware in the pipeline.
31 | /// The used by this middleware.
32 | /// The configuration for this middleware.
33 | public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions options)
34 | : this(next, hostingEnv, HtmlEncoder.Default, options)
35 | {
36 | }
37 |
38 | ///
39 | /// Creates a new instance of the SendFileMiddleware.
40 | ///
41 | /// The next middleware in the pipeline.
42 | /// The used by this middleware.
43 | /// The used by the default .
44 | /// The configuration for this middleware.
45 | public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, HtmlEncoder encoder, IOptions options)
46 | {
47 | if (next == null)
48 | {
49 | throw new ArgumentNullException(nameof(next));
50 | }
51 |
52 | if (hostingEnv == null)
53 | {
54 | throw new ArgumentNullException(nameof(hostingEnv));
55 | }
56 |
57 | if (encoder == null)
58 | {
59 | throw new ArgumentNullException(nameof(encoder));
60 | }
61 |
62 | if (options == null)
63 | {
64 | throw new ArgumentNullException(nameof(options));
65 | }
66 |
67 | _next = next;
68 | _options = options.Value;
69 | _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
70 | _formatter = options.Value.Formatter ?? new HtmlDirectoryFormatter(encoder);
71 | _matchUrl = _options.RequestPath;
72 | }
73 |
74 | ///
75 | /// Examines the request to see if it matches a configured directory. If so, a view of the directory contents is returned.
76 | ///
77 | ///
78 | ///
79 | public Task Invoke(HttpContext context)
80 | {
81 | // Check if the URL matches any expected paths
82 | if (Helpers.IsGetOrHeadMethod(context.Request.Method)
83 | && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath)
84 | && TryGetDirectoryInfo(subpath, out var contents))
85 | {
86 | // If the path matches a directory but does not end in a slash, redirect to add the slash.
87 | // This prevents relative links from breaking.
88 | if (!Helpers.PathEndsInSlash(context.Request.Path))
89 | {
90 | context.Response.StatusCode = 301;
91 | context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
92 | return Task.CompletedTask;
93 | }
94 |
95 | return _formatter.GenerateContentAsync(context, contents);
96 | }
97 |
98 | return _next(context);
99 | }
100 |
101 | private bool TryGetDirectoryInfo(PathString subpath, out IDirectoryContents contents)
102 | {
103 | contents = _fileProvider.GetDirectoryContents(subpath.Value);
104 | return contents.Exists;
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.AspNetCore.Http.Headers;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Primitives;
11 | using Microsoft.Net.Http.Headers;
12 |
13 | namespace Microsoft.AspNetCore.Internal
14 | {
15 | ///
16 | /// Provides a parser for the Range Header in an .
17 | ///
18 | internal static class RangeHelper
19 | {
20 | ///
21 | /// Returns the normalized form of the requested range if the Range Header in the is valid.
22 | ///
23 | /// The associated with the request.
24 | /// The associated with the given .
25 | /// The total length of the file representation requested.
26 | /// The .
27 | /// A boolean value which represents if the contain a single valid
28 | /// range request. A which represents the normalized form of the
29 | /// range parsed from the or null if it cannot be normalized.
30 | /// 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).
31 | /// This results in (true,null) return values.
32 | public static (bool isRangeRequest, RangeItemHeaderValue range) ParseRange(
33 | HttpContext context,
34 | RequestHeaders requestHeaders,
35 | long length,
36 | ILogger logger)
37 | {
38 | var rawRangeHeader = context.Request.Headers[HeaderNames.Range];
39 | if (StringValues.IsNullOrEmpty(rawRangeHeader))
40 | {
41 | logger.LogTrace("Range header's value is empty.");
42 | return (false, null);
43 | }
44 |
45 | // Perf: Check for a single entry before parsing it
46 | if (rawRangeHeader.Count > 1 || rawRangeHeader[0].IndexOf(',') >= 0)
47 | {
48 | logger.LogDebug("Multiple ranges are not supported.");
49 |
50 | // The spec allows for multiple ranges but we choose not to support them because the client may request
51 | // very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
52 | // impact the server. Ignore the header and serve the response normally.
53 | return (false, null);
54 | }
55 |
56 | var rangeHeader = requestHeaders.Range;
57 | if (rangeHeader == null)
58 | {
59 | logger.LogDebug("Range header's value is invalid.");
60 | // Invalid
61 | return (false, null);
62 | }
63 |
64 | // Already verified above
65 | Debug.Assert(rangeHeader.Ranges.Count == 1);
66 |
67 | var ranges = rangeHeader.Ranges;
68 | if (ranges == null)
69 | {
70 | logger.LogDebug("Range header's value is invalid.");
71 | return (false, null);
72 | }
73 |
74 | if (ranges.Count == 0)
75 | {
76 | return (true, null);
77 | }
78 |
79 | if (length == 0)
80 | {
81 | return (true, null);
82 | }
83 |
84 | // Normalize the ranges
85 | var range = NormalizeRange(ranges.SingleOrDefault(), length);
86 |
87 | // Return the single range
88 | return (true, range);
89 | }
90 |
91 | // Internal for testing
92 | internal static RangeItemHeaderValue NormalizeRange(RangeItemHeaderValue range, long length)
93 | {
94 | var start = range.From;
95 | var end = range.To;
96 |
97 | // X-[Y]
98 | if (start.HasValue)
99 | {
100 | if (start.Value >= length)
101 | {
102 | // Not satisfiable, skip/discard.
103 | return null;
104 | }
105 | if (!end.HasValue || end.Value >= length)
106 | {
107 | end = length - 1;
108 | }
109 | }
110 | else
111 | {
112 | // suffix range "-X" e.g. the last X bytes, resolve
113 | if (end.Value == 0)
114 | {
115 | // Not satisfiable, skip/discard.
116 | return null;
117 | }
118 |
119 | var bytes = Math.Min(end.Value, length);
120 | start = length - bytes;
121 | end = start + bytes - 1;
122 | }
123 |
124 | return new RangeItemHeaderValue(start, end);
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFileContextTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using Microsoft.AspNetCore.Builder;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.Extensions.FileProviders;
10 | using Microsoft.Extensions.Logging.Abstractions;
11 | using Microsoft.Extensions.Primitives;
12 | using Xunit;
13 |
14 | namespace Microsoft.AspNetCore.StaticFiles
15 | {
16 | public class StaticFileContextTest
17 | {
18 | [Fact]
19 | public void LookupFileInfo_ReturnsFalse_IfFileDoesNotExist()
20 | {
21 | // Arrange
22 | var options = new StaticFileOptions();
23 | var context = new StaticFileContext(new DefaultHttpContext(), options, PathString.Empty, NullLogger.Instance, new TestFileProvider(), new FileExtensionContentTypeProvider());
24 |
25 | // Act
26 | var validateResult = context.ValidatePath();
27 | var lookupResult = context.LookupFileInfo();
28 |
29 | // Assert
30 | Assert.True(validateResult);
31 | Assert.False(lookupResult);
32 | }
33 |
34 | [Fact]
35 | public void LookupFileInfo_ReturnsTrue_IfFileExists()
36 | {
37 | // Arrange
38 | var options = new StaticFileOptions();
39 | var fileProvider = new TestFileProvider();
40 | fileProvider.AddFile("/foo.txt", new TestFileInfo
41 | {
42 | LastModified = new DateTimeOffset(2014, 1, 2, 3, 4, 5, TimeSpan.Zero)
43 | });
44 | var pathString = new PathString("/test");
45 | var httpContext = new DefaultHttpContext();
46 | httpContext.Request.Path = new PathString("/test/foo.txt");
47 | var context = new StaticFileContext(httpContext, options, pathString, NullLogger.Instance, fileProvider, new FileExtensionContentTypeProvider());
48 |
49 | // Act
50 | context.ValidatePath();
51 | var result = context.LookupFileInfo();
52 |
53 | // Assert
54 | Assert.True(result);
55 | }
56 |
57 | private sealed class TestFileProvider : IFileProvider
58 | {
59 | private readonly Dictionary _files = new Dictionary(StringComparer.Ordinal);
60 |
61 | public void AddFile(string path, IFileInfo fileInfo)
62 | {
63 | _files[path] = fileInfo;
64 | }
65 |
66 | public IDirectoryContents GetDirectoryContents(string subpath)
67 | {
68 | throw new NotImplementedException();
69 | }
70 |
71 | public IFileInfo GetFileInfo(string subpath)
72 | {
73 | if (_files.TryGetValue(subpath, out var result))
74 | {
75 | return result;
76 | }
77 |
78 | return new NotFoundFileInfo();
79 | }
80 |
81 | public IChangeToken Watch(string filter)
82 | {
83 | throw new NotSupportedException();
84 | }
85 |
86 | private class NotFoundFileInfo : IFileInfo
87 | {
88 | public bool Exists
89 | {
90 | get
91 | {
92 | return false;
93 | }
94 | }
95 |
96 | public bool IsDirectory
97 | {
98 | get
99 | {
100 | throw new NotImplementedException();
101 | }
102 | }
103 |
104 | public DateTimeOffset LastModified
105 | {
106 | get
107 | {
108 | throw new NotImplementedException();
109 | }
110 | }
111 |
112 | public long Length
113 | {
114 | get
115 | {
116 | throw new NotImplementedException();
117 | }
118 | }
119 |
120 | public string Name
121 | {
122 | get
123 | {
124 | throw new NotImplementedException();
125 | }
126 | }
127 |
128 | public string PhysicalPath
129 | {
130 | get
131 | {
132 | throw new NotImplementedException();
133 | }
134 | }
135 |
136 | public Stream CreateReadStream()
137 | {
138 | throw new NotImplementedException();
139 | }
140 | }
141 | }
142 |
143 | private sealed class TestFileInfo : IFileInfo
144 | {
145 | public bool Exists
146 | {
147 | get { return true; }
148 | }
149 |
150 | public bool IsDirectory
151 | {
152 | get { return false; }
153 | }
154 |
155 | public DateTimeOffset LastModified { get; set; }
156 |
157 | public long Length { get; set; }
158 |
159 | public string Name { get; set; }
160 |
161 | public string PhysicalPath { get; set; }
162 |
163 | public Stream CreateReadStream()
164 | {
165 | throw new NotImplementedException();
166 | }
167 | }
168 | }
169 | }
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Diagnostics;
6 | using System.IO;
7 | using System.Threading.Tasks;
8 | using Microsoft.AspNetCore.Builder;
9 | using Microsoft.AspNetCore.Hosting;
10 | using Microsoft.AspNetCore.Http;
11 | using Microsoft.Extensions.FileProviders;
12 | using Microsoft.Extensions.Logging;
13 | using Microsoft.Extensions.Options;
14 |
15 | namespace Microsoft.AspNetCore.StaticFiles
16 | {
17 | ///
18 | /// Enables serving static files for a given request path
19 | ///
20 | public class StaticFileMiddleware
21 | {
22 | private readonly StaticFileOptions _options;
23 | private readonly PathString _matchUrl;
24 | private readonly RequestDelegate _next;
25 | private readonly ILogger _logger;
26 | private readonly IFileProvider _fileProvider;
27 | private readonly IContentTypeProvider _contentTypeProvider;
28 |
29 | ///
30 | /// Creates a new instance of the StaticFileMiddleware.
31 | ///
32 | /// The next middleware in the pipeline.
33 | /// The used by this middleware.
34 | /// The configuration options.
35 | /// An instance used to create loggers.
36 | public StaticFileMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions options, ILoggerFactory loggerFactory)
37 | {
38 | if (next == null)
39 | {
40 | throw new ArgumentNullException(nameof(next));
41 | }
42 |
43 | if (hostingEnv == null)
44 | {
45 | throw new ArgumentNullException(nameof(hostingEnv));
46 | }
47 |
48 | if (options == null)
49 | {
50 | throw new ArgumentNullException(nameof(options));
51 | }
52 |
53 | if (loggerFactory == null)
54 | {
55 | throw new ArgumentNullException(nameof(loggerFactory));
56 | }
57 |
58 | _next = next;
59 | _options = options.Value;
60 | _contentTypeProvider = options.Value.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
61 | _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
62 | _matchUrl = _options.RequestPath;
63 | _logger = loggerFactory.CreateLogger();
64 | }
65 |
66 | ///
67 | /// Processes a request to determine if it matches a known file, and if so, serves it.
68 | ///
69 | ///
70 | ///
71 | public async Task Invoke(HttpContext context)
72 | {
73 | var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider);
74 |
75 | if (!fileContext.ValidateMethod())
76 | {
77 | _logger.LogRequestMethodNotSupported(context.Request.Method);
78 | }
79 | else if (!fileContext.ValidatePath())
80 | {
81 | _logger.LogPathMismatch(fileContext.SubPath);
82 | }
83 | else if (!fileContext.LookupContentType())
84 | {
85 | _logger.LogFileTypeNotSupported(fileContext.SubPath);
86 | }
87 | else if (!fileContext.LookupFileInfo())
88 | {
89 | _logger.LogFileNotFound(fileContext.SubPath);
90 | }
91 | else
92 | {
93 | // If we get here, we can try to serve the file
94 | fileContext.ComprehendRequestHeaders();
95 | switch (fileContext.GetPreconditionState())
96 | {
97 | case StaticFileContext.PreconditionState.Unspecified:
98 | case StaticFileContext.PreconditionState.ShouldProcess:
99 | if (fileContext.IsHeadMethod)
100 | {
101 | await fileContext.SendStatusAsync(Constants.Status200Ok);
102 | return;
103 | }
104 |
105 | try
106 | {
107 | if (fileContext.IsRangeRequest)
108 | {
109 | await fileContext.SendRangeAsync();
110 | return;
111 | }
112 |
113 | await fileContext.SendAsync();
114 | _logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath);
115 | return;
116 | }
117 | catch (FileNotFoundException)
118 | {
119 | context.Response.Clear();
120 | }
121 | break;
122 | case StaticFileContext.PreconditionState.NotModified:
123 | _logger.LogPathNotModified(fileContext.SubPath);
124 | await fileContext.SendStatusAsync(Constants.Status304NotModified);
125 | return;
126 |
127 | case StaticFileContext.PreconditionState.PreconditionFailed:
128 | _logger.LogPreconditionFailed(fileContext.SubPath);
129 | await fileContext.SendStatusAsync(Constants.Status412PreconditionFailed);
130 | return;
131 |
132 | default:
133 | var exception = new NotImplementedException(fileContext.GetPreconditionState().ToString());
134 | Debug.Fail(exception.ToString());
135 | throw exception;
136 | }
137 | }
138 |
139 | await _next(context);
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/HtmlDirectoryFormatter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Globalization;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Text.Encodings.Web;
10 | using System.Threading.Tasks;
11 | using Microsoft.AspNetCore.Http;
12 | using Microsoft.Extensions.DependencyInjection;
13 | using Microsoft.Extensions.FileProviders;
14 |
15 | namespace Microsoft.AspNetCore.StaticFiles
16 | {
17 | ///
18 | /// Generates an HTML view for a directory.
19 | ///
20 | public class HtmlDirectoryFormatter : IDirectoryFormatter
21 | {
22 | private const string TextHtmlUtf8 = "text/html; charset=utf-8";
23 |
24 | private HtmlEncoder _htmlEncoder;
25 |
26 | public HtmlDirectoryFormatter(HtmlEncoder encoder)
27 | {
28 | if (encoder == null)
29 | {
30 | throw new ArgumentNullException(nameof(encoder));
31 | }
32 | _htmlEncoder = encoder;
33 | }
34 |
35 | ///
36 | /// Generates an HTML view for a directory.
37 | ///
38 | public virtual Task GenerateContentAsync(HttpContext context, IEnumerable contents)
39 | {
40 | if (context == null)
41 | {
42 | throw new ArgumentNullException(nameof(context));
43 | }
44 | if (contents == null)
45 | {
46 | throw new ArgumentNullException(nameof(contents));
47 | }
48 |
49 | context.Response.ContentType = TextHtmlUtf8;
50 |
51 | if (HttpMethods.IsHead(context.Request.Method))
52 | {
53 | // HEAD, no response body
54 | return Task.CompletedTask;
55 | }
56 |
57 | PathString requestPath = context.Request.PathBase + context.Request.Path;
58 |
59 | var builder = new StringBuilder();
60 |
61 | builder.AppendFormat(
62 | @"
63 | ", CultureInfo.CurrentUICulture.TwoLetterISOLanguageName);
64 |
65 | builder.AppendFormat(@"
66 |
67 | {0} {1}", HtmlEncode(Resources.HtmlDir_IndexOf), HtmlEncode(requestPath.Value));
68 |
69 | builder.Append(@"
70 |
103 |
104 |
105 | ");
106 | builder.AppendFormat(@"
107 | {0} /", HtmlEncode(Resources.HtmlDir_IndexOf));
108 |
109 | string cumulativePath = "/";
110 | foreach (var segment in requestPath.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries))
111 | {
112 | cumulativePath = cumulativePath + segment + "/";
113 | builder.AppendFormat(@"{1}/",
114 | HtmlEncode(cumulativePath), HtmlEncode(segment));
115 | }
116 |
117 | builder.AppendFormat(CultureInfo.CurrentUICulture,
118 | @"
119 |
120 |
121 | | {1} | {2} | {4} |
122 |
123 | ",
124 | HtmlEncode(Resources.HtmlDir_TableSummary),
125 | HtmlEncode(Resources.HtmlDir_Name),
126 | HtmlEncode(Resources.HtmlDir_Size),
127 | HtmlEncode(Resources.HtmlDir_Modified),
128 | HtmlEncode(Resources.HtmlDir_LastModified));
129 |
130 | foreach (var subdir in contents.Where(info => info.IsDirectory))
131 | {
132 | builder.AppendFormat(@"
133 |
134 | | {0}/ |
135 | |
136 | {1} |
137 |
",
138 | HtmlEncode(subdir.Name),
139 | HtmlEncode(subdir.LastModified.ToString(CultureInfo.CurrentCulture)));
140 | }
141 |
142 | foreach (var file in contents.Where(info => !info.IsDirectory))
143 | {
144 | builder.AppendFormat(@"
145 |
146 | | {0} |
147 | {1} |
148 | {2} |
149 |
",
150 | HtmlEncode(file.Name),
151 | HtmlEncode(file.Length.ToString("n0", CultureInfo.CurrentCulture)),
152 | HtmlEncode(file.LastModified.ToString(CultureInfo.CurrentCulture)));
153 | }
154 |
155 | builder.Append(@"
156 |
157 |
158 |
159 |
160 | ");
161 | string data = builder.ToString();
162 | byte[] bytes = Encoding.UTF8.GetBytes(data);
163 | context.Response.ContentLength = bytes.Length;
164 | return context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
165 | }
166 |
167 | private string HtmlEncode(string body)
168 | {
169 | return _htmlEncoder.Encode(body);
170 | }
171 | }
172 | }
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | No formatter provided.
122 |
123 |
124 | Index of
125 |
126 |
127 | Last Modified
128 |
129 |
130 | Modified
131 |
132 |
133 | Name
134 |
135 |
136 | Size
137 |
138 |
139 | The list of files in the given directory. Column headers are listed in the first row.
140 |
141 |
--------------------------------------------------------------------------------
/run.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env powershell
2 | #requires -version 4
3 |
4 | <#
5 | .SYNOPSIS
6 | Executes KoreBuild commands.
7 |
8 | .DESCRIPTION
9 | Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`.
10 |
11 | .PARAMETER Command
12 | The KoreBuild command to run.
13 |
14 | .PARAMETER Path
15 | The folder to build. Defaults to the folder containing this script.
16 |
17 | .PARAMETER Channel
18 | The channel of KoreBuild to download. Overrides the value from the config file.
19 |
20 | .PARAMETER DotNetHome
21 | The directory where .NET Core tools will be stored.
22 |
23 | .PARAMETER ToolsSource
24 | The base url where build tools can be downloaded. Overrides the value from the config file.
25 |
26 | .PARAMETER Update
27 | Updates KoreBuild to the latest version even if a lock file is present.
28 |
29 | .PARAMETER Reinstall
30 | Re-installs KoreBuild
31 |
32 | .PARAMETER ConfigFile
33 | The path to the configuration file that stores values. Defaults to korebuild.json.
34 |
35 | .PARAMETER ToolsSourceSuffix
36 | The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores.
37 |
38 | .PARAMETER CI
39 | Sets up CI specific settings and variables.
40 |
41 | .PARAMETER Arguments
42 | Arguments to be passed to the command
43 |
44 | .NOTES
45 | This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be.
46 | When the lockfile is not present, KoreBuild will create one using latest available version from $Channel.
47 |
48 | The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set
49 | in the file are overridden by command line parameters.
50 |
51 | .EXAMPLE
52 | Example config file:
53 | ```json
54 | {
55 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
56 | "channel": "master",
57 | "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools"
58 | }
59 | ```
60 | #>
61 | [CmdletBinding(PositionalBinding = $false)]
62 | param(
63 | [Parameter(Mandatory = $true, Position = 0)]
64 | [string]$Command,
65 | [string]$Path = $PSScriptRoot,
66 | [Alias('c')]
67 | [string]$Channel,
68 | [Alias('d')]
69 | [string]$DotNetHome,
70 | [Alias('s')]
71 | [string]$ToolsSource,
72 | [Alias('u')]
73 | [switch]$Update,
74 | [switch]$Reinstall,
75 | [string]$ToolsSourceSuffix,
76 | [string]$ConfigFile = $null,
77 | [switch]$CI,
78 | [Parameter(ValueFromRemainingArguments = $true)]
79 | [string[]]$Arguments
80 | )
81 |
82 | Set-StrictMode -Version 2
83 | $ErrorActionPreference = 'Stop'
84 |
85 | #
86 | # Functions
87 | #
88 |
89 | function Get-KoreBuild {
90 |
91 | $lockFile = Join-Path $Path 'korebuild-lock.txt'
92 |
93 | if (!(Test-Path $lockFile) -or $Update) {
94 | Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix
95 | }
96 |
97 | $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
98 | if (!$version) {
99 | Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'"
100 | }
101 | $version = $version.TrimStart('version:').Trim()
102 | $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version)
103 |
104 | if ($Reinstall -and (Test-Path $korebuildPath)) {
105 | Remove-Item -Force -Recurse $korebuildPath
106 | }
107 |
108 | if (!(Test-Path $korebuildPath)) {
109 | Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version"
110 | New-Item -ItemType Directory -Path $korebuildPath | Out-Null
111 | $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip"
112 |
113 | try {
114 | $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip"
115 | Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix
116 | if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) {
117 | # Use built-in commands where possible as they are cross-plat compatible
118 | Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath
119 | }
120 | else {
121 | # Fallback to old approach for old installations of PowerShell
122 | Add-Type -AssemblyName System.IO.Compression.FileSystem
123 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath)
124 | }
125 | }
126 | catch {
127 | Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore
128 | throw
129 | }
130 | finally {
131 | Remove-Item $tmpfile -ErrorAction Ignore
132 | }
133 | }
134 |
135 | return $korebuildPath
136 | }
137 |
138 | function Join-Paths([string]$path, [string[]]$childPaths) {
139 | $childPaths | ForEach-Object { $path = Join-Path $path $_ }
140 | return $path
141 | }
142 |
143 | function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) {
144 | if ($RemotePath -notlike 'http*') {
145 | Copy-Item $RemotePath $LocalPath
146 | return
147 | }
148 |
149 | $retries = 10
150 | while ($retries -gt 0) {
151 | $retries -= 1
152 | try {
153 | Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath
154 | return
155 | }
156 | catch {
157 | Write-Verbose "Request failed. $retries retries remaining"
158 | }
159 | }
160 |
161 | Write-Error "Download failed: '$RemotePath'."
162 | }
163 |
164 | #
165 | # Main
166 | #
167 |
168 | # Load configuration or set defaults
169 |
170 | $Path = Resolve-Path $Path
171 | if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' }
172 |
173 | if (Test-Path $ConfigFile) {
174 | try {
175 | $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json
176 | if ($config) {
177 | if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel }
178 | if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource}
179 | }
180 | }
181 | catch {
182 | Write-Host -ForegroundColor Red $Error[0]
183 | Write-Error "$ConfigFile contains invalid JSON."
184 | exit 1
185 | }
186 | }
187 |
188 | if (!$DotNetHome) {
189 | $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } `
190 | elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} `
191 | elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}`
192 | else { Join-Path $PSScriptRoot '.dotnet'}
193 | }
194 |
195 | if (!$Channel) { $Channel = 'master' }
196 | if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' }
197 |
198 | # Execute
199 |
200 | $korebuildPath = Get-KoreBuild
201 | Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1')
202 |
203 | try {
204 | Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI
205 | Invoke-KoreBuildCommand $Command @Arguments
206 | }
207 | finally {
208 | Remove-Module 'KoreBuild' -ErrorAction Ignore
209 | }
210 |
--------------------------------------------------------------------------------
/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Extensions.Primitives;
7 |
8 | namespace Microsoft.AspNetCore.StaticFiles
9 | {
10 | ///
11 | /// Defines *all* the logger messages produced by static files
12 | ///
13 | internal static class LoggerExtensions
14 | {
15 | private static Action _logMethodNotSupported;
16 | private static Action _logFileServed;
17 | private static Action _logPathMismatch;
18 | private static Action _logFileTypeNotSupported;
19 | private static Action _logFileNotFound;
20 | private static Action _logPathNotModified;
21 | private static Action _logPreconditionFailed;
22 | private static Action _logHandled;
23 | private static Action _logRangeNotSatisfiable;
24 | private static Action _logSendingFileRange;
25 | private static Action _logCopyingFileRange;
26 | private static Action _logCopyingBytesToResponse;
27 | private static Action _logWriteCancelled;
28 |
29 | static LoggerExtensions()
30 | {
31 | _logMethodNotSupported = LoggerMessage.Define(
32 | logLevel: LogLevel.Debug,
33 | eventId: 1,
34 | formatString: "{Method} requests are not supported");
35 | _logFileServed = LoggerMessage.Define(
36 | logLevel: LogLevel.Information,
37 | eventId: 2,
38 | formatString: "Sending file. Request path: '{VirtualPath}'. Physical path: '{PhysicalPath}'");
39 | _logPathMismatch = LoggerMessage.Define(
40 | logLevel: LogLevel.Debug,
41 | eventId: 3,
42 | formatString: "The request path {Path} does not match the path filter");
43 | _logFileTypeNotSupported = LoggerMessage.Define(
44 | logLevel: LogLevel.Debug,
45 | eventId: 4,
46 | formatString: "The request path {Path} does not match a supported file type");
47 | _logFileNotFound = LoggerMessage.Define(
48 | logLevel: LogLevel.Debug,
49 | eventId: 5,
50 | formatString: "The request path {Path} does not match an existing file");
51 | _logPathNotModified = LoggerMessage.Define(
52 | logLevel: LogLevel.Information,
53 | eventId: 6,
54 | formatString: "The file {Path} was not modified");
55 | _logPreconditionFailed = LoggerMessage.Define(
56 | logLevel: LogLevel.Information,
57 | eventId: 7,
58 | formatString: "Precondition for {Path} failed");
59 | _logHandled = LoggerMessage.Define(
60 | logLevel: LogLevel.Debug,
61 | eventId: 8,
62 | formatString: "Handled. Status code: {StatusCode} File: {Path}");
63 | _logRangeNotSatisfiable = LoggerMessage.Define(
64 | logLevel: LogLevel.Warning,
65 | eventId: 9,
66 | formatString: "Range not satisfiable for {Path}");
67 | _logSendingFileRange = LoggerMessage.Define(
68 | logLevel: LogLevel.Information,
69 | eventId: 10,
70 | formatString: "Sending {Range} of file {Path}");
71 | _logCopyingFileRange = LoggerMessage.Define(
72 | logLevel: LogLevel.Debug,
73 | eventId: 11,
74 | formatString: "Copying {Range} of file {Path} to the response body");
75 | _logCopyingBytesToResponse = LoggerMessage.Define(
76 | logLevel: LogLevel.Debug,
77 | eventId: 12,
78 | formatString: "Copying bytes {Start}-{End} of file {Path} to response body");
79 | _logWriteCancelled = LoggerMessage.Define(
80 | logLevel: LogLevel.Debug,
81 | eventId: 14,
82 | formatString: "The file transmission was cancelled");
83 | }
84 |
85 | public static void LogRequestMethodNotSupported(this ILogger logger, string method)
86 | {
87 | _logMethodNotSupported(logger, method, null);
88 | }
89 |
90 | public static void LogFileServed(this ILogger logger, string virtualPath, string physicalPath)
91 | {
92 | if (string.IsNullOrEmpty(physicalPath))
93 | {
94 | physicalPath = "N/A";
95 | }
96 | _logFileServed(logger, virtualPath, physicalPath, null);
97 | }
98 |
99 | public static void LogPathMismatch(this ILogger logger, string path)
100 | {
101 | _logPathMismatch(logger, path, null);
102 | }
103 |
104 | public static void LogFileTypeNotSupported(this ILogger logger, string path)
105 | {
106 | _logFileTypeNotSupported(logger, path, null);
107 | }
108 |
109 | public static void LogFileNotFound(this ILogger logger, string path)
110 | {
111 | _logFileNotFound(logger, path, null);
112 | }
113 |
114 | public static void LogPathNotModified(this ILogger logger, string path)
115 | {
116 | _logPathNotModified(logger, path, null);
117 | }
118 |
119 | public static void LogPreconditionFailed(this ILogger logger, string path)
120 | {
121 | _logPreconditionFailed(logger, path, null);
122 | }
123 |
124 | public static void LogHandled(this ILogger logger, int statusCode, string path)
125 | {
126 | _logHandled(logger, statusCode, path, null);
127 | }
128 |
129 | public static void LogRangeNotSatisfiable(this ILogger logger, string path)
130 | {
131 | _logRangeNotSatisfiable(logger, path, null);
132 | }
133 |
134 | public static void LogSendingFileRange(this ILogger logger, StringValues range, string path)
135 | {
136 | _logSendingFileRange(logger, range, path, null);
137 | }
138 |
139 | public static void LogCopyingFileRange(this ILogger logger, StringValues range, string path)
140 | {
141 | _logCopyingFileRange(logger, range, path, null);
142 | }
143 |
144 | public static void LogCopyingBytesToResponse(this ILogger logger, long start, long? end, string path)
145 | {
146 | _logCopyingBytesToResponse(
147 | logger,
148 | start,
149 | end != null ? end.ToString() : "*",
150 | path,
151 | null);
152 | }
153 |
154 | public static void LogWriteCancelled(this ILogger logger, Exception ex)
155 | {
156 | _logWriteCancelled(logger, ex);
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/StaticFiles.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 15
3 | VisualStudioVersion = 15.0.26228.9
4 | MinimumVisualStudioVersion = 15.0.26730.03
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{40EE0889-960E-41B4-A3D3-9CE963EB0797}"
6 | ProjectSection(SolutionItems) = preProject
7 | src\Directory.Build.props = src\Directory.Build.props
8 | EndProjectSection
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8B21A3A9-9CA6-4857-A6E0-1A3203404B60}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.StaticFiles", "src\Microsoft.AspNetCore.StaticFiles\Microsoft.AspNetCore.StaticFiles.csproj", "{8D7BC5A4-F19C-4184-8338-A6B42997218C}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StaticFileSample", "samples\StaticFileSample\StaticFileSample.csproj", "{092141D9-305A-4FC5-AE74-CB23982CA8D4}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EF02AFE8-7C15-4DDB-8B2C-58A676112A98}"
17 | ProjectSection(SolutionItems) = preProject
18 | test\Directory.Build.props = test\Directory.Build.props
19 | EndProjectSection
20 | EndProject
21 | 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}"
22 | EndProject
23 | 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}"
24 | EndProject
25 | 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}"
26 | EndProject
27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{360DC2F8-EEB4-4C69-9784-C686EAD78279}"
28 | EndProject
29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.RangeHelper.Sources", "Microsoft.AspNetCore.RangeHelper.Sources", "{DB6A1D14-B8A2-488F-9C4B-422FD45C8853}"
30 | ProjectSection(SolutionItems) = preProject
31 | shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs = shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs
32 | EndProjectSection
33 | EndProject
34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DE015849-E126-4DFA-A754-E1AC3B1E7925}"
35 | ProjectSection(SolutionItems) = preProject
36 | .appveyor.yml = .appveyor.yml
37 | .travis.yml = .travis.yml
38 | EndProjectSection
39 | EndProject
40 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{D3F67455-9FB9-4354-93EC-B05D755114E9}"
41 | ProjectSection(SolutionItems) = preProject
42 | build\dependencies.props = build\dependencies.props
43 | build\repo.props = build\repo.props
44 | build\sources.props = build\sources.props
45 | EndProjectSection
46 | EndProject
47 | Global
48 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
49 | Debug|Any CPU = Debug|Any CPU
50 | Debug|Mixed Platforms = Debug|Mixed Platforms
51 | Debug|x86 = Debug|x86
52 | Release|Any CPU = Release|Any CPU
53 | Release|Mixed Platforms = Release|Mixed Platforms
54 | Release|x86 = Release|x86
55 | EndGlobalSection
56 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
57 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
60 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
61 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Debug|x86.ActiveCfg = Debug|Any CPU
62 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
65 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
66 | {8D7BC5A4-F19C-4184-8338-A6B42997218C}.Release|x86.ActiveCfg = Release|Any CPU
67 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
69 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
70 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
71 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Debug|x86.ActiveCfg = Debug|Any CPU
72 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
75 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
76 | {092141D9-305A-4FC5-AE74-CB23982CA8D4}.Release|x86.ActiveCfg = Release|Any CPU
77 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
79 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
80 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
81 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Debug|x86.ActiveCfg = Debug|Any CPU
82 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
83 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Any CPU.Build.0 = Release|Any CPU
84 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
85 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
86 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5}.Release|x86.ActiveCfg = Release|Any CPU
87 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
88 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
89 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
90 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
91 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|x86.ActiveCfg = Debug|Any CPU
92 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Debug|x86.Build.0 = Debug|Any CPU
93 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
94 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Any CPU.Build.0 = Release|Any CPU
95 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
96 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
97 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.ActiveCfg = Release|Any CPU
98 | {FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.Build.0 = Release|Any CPU
99 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
100 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
101 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
102 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
103 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.ActiveCfg = Debug|Any CPU
104 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.Build.0 = Debug|Any CPU
105 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
106 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.Build.0 = Release|Any CPU
107 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
108 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
109 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.ActiveCfg = Release|Any CPU
110 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.Build.0 = Release|Any CPU
111 | EndGlobalSection
112 | GlobalSection(SolutionProperties) = preSolution
113 | HideSolutionNode = FALSE
114 | EndGlobalSection
115 | GlobalSection(NestedProjects) = preSolution
116 | {8D7BC5A4-F19C-4184-8338-A6B42997218C} = {40EE0889-960E-41B4-A3D3-9CE963EB0797}
117 | {092141D9-305A-4FC5-AE74-CB23982CA8D4} = {8B21A3A9-9CA6-4857-A6E0-1A3203404B60}
118 | {CC87FE7D-8F42-4BE9-A152-9625E837C1E5} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
119 | {FDF0539C-1F62-4B78-91B1-C687886931CA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
120 | {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
121 | {DB6A1D14-B8A2-488F-9C4B-422FD45C8853} = {360DC2F8-EEB4-4C69-9784-C686EAD78279}
122 | {D3F67455-9FB9-4354-93EC-B05D755114E9} = {DE015849-E126-4DFA-A754-E1AC3B1E7925}
123 | EndGlobalSection
124 | GlobalSection(ExtensibilityGlobals) = postSolution
125 | SolutionGuid = {AF7F03BE-0DB2-48EF-8185-7698995658A6}
126 | EndGlobalSection
127 | EndGlobal
128 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.Tests/DefaultFilesMiddlewareTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Net.Http;
9 | using System.Threading.Tasks;
10 | using Microsoft.AspNetCore.Builder;
11 | using Microsoft.AspNetCore.Http;
12 | using Microsoft.AspNetCore.Http.Extensions;
13 | using Microsoft.AspNetCore.TestHost;
14 | using Microsoft.AspNetCore.Testing.xunit;
15 | using Microsoft.Extensions.FileProviders;
16 | using Xunit;
17 |
18 | namespace Microsoft.AspNetCore.StaticFiles
19 | {
20 | public class DefaultFilesMiddlewareTests
21 | {
22 | [Fact]
23 | public async Task NullArguments()
24 | {
25 | // No exception, default provided
26 | StaticFilesTestServer.Create(app => app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = null }));
27 |
28 | // PathString(null) is OK.
29 | var server = StaticFilesTestServer.Create(app => app.UseDefaultFiles((string)null));
30 | var response = await server.CreateClient().GetAsync("/");
31 | Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
32 | }
33 |
34 | [Theory]
35 | [InlineData("", @".", "/missing.dir")]
36 | [InlineData("", @".", "/missing.dir/")]
37 | [InlineData("/subdir", @".", "/subdir/missing.dir")]
38 | [InlineData("/subdir", @".", "/subdir/missing.dir/")]
39 | [InlineData("", @"./", "/missing.dir")]
40 | public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl)
41 | {
42 | await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl);
43 | }
44 |
45 | [ConditionalTheory]
46 | [OSSkipCondition(OperatingSystems.Linux)]
47 | [OSSkipCondition(OperatingSystems.MacOSX)]
48 | [InlineData("", @".\", "/missing.dir")]
49 | [InlineData("", @".\", "/Missing.dir")]
50 | public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl)
51 | {
52 | await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl);
53 | }
54 |
55 | private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl)
56 | {
57 | using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
58 | {
59 | var server = StaticFilesTestServer.Create(app =>
60 | {
61 | app.UseDefaultFiles(new DefaultFilesOptions
62 | {
63 | RequestPath = new PathString(baseUrl),
64 | FileProvider = fileProvider
65 | });
66 | app.Run(context => context.Response.WriteAsync(context.Request.Path.Value));
67 | });
68 |
69 | var response = await server.CreateClient().GetAsync(requestUrl);
70 | Assert.Equal(HttpStatusCode.OK, response.StatusCode);
71 | Assert.Equal(requestUrl, await response.Content.ReadAsStringAsync()); // Should not be modified
72 | }
73 | }
74 |
75 | [Theory]
76 | [InlineData("", @".", "/SubFolder/")]
77 | [InlineData("", @"./", "/SubFolder/")]
78 | [InlineData("", @"./SubFolder", "/")]
79 | [InlineData("", @"./SubFolder", "/你好/")]
80 | [InlineData("", @"./SubFolder", "/你好/世界/")]
81 | public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl)
82 | {
83 | await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl);
84 | }
85 |
86 | [ConditionalTheory]
87 | [OSSkipCondition(OperatingSystems.Linux)]
88 | [OSSkipCondition(OperatingSystems.MacOSX)]
89 | [InlineData("", @".\", "/SubFolder/")]
90 | [InlineData("", @".\subFolder", "/")]
91 | [InlineData("", @".\SubFolder", "/你好/")]
92 | [InlineData("", @".\SubFolder", "/你好/世界/")]
93 | public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl)
94 | {
95 | await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl);
96 | }
97 |
98 | private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl)
99 | {
100 | using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
101 | {
102 | var server = StaticFilesTestServer.Create(app =>
103 | {
104 | app.UseDefaultFiles(new DefaultFilesOptions
105 | {
106 | RequestPath = new PathString(baseUrl),
107 | FileProvider = fileProvider
108 | });
109 | app.Run(context => context.Response.WriteAsync(context.Request.Path.Value));
110 | });
111 |
112 | var response = await server.CreateClient().GetAsync(requestUrl);
113 | Assert.Equal(HttpStatusCode.OK, response.StatusCode);
114 | Assert.Equal(requestUrl + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified
115 | }
116 | }
117 |
118 | [Theory]
119 | [InlineData("", @".", "/SubFolder", "")]
120 | [InlineData("", @"./", "/SubFolder", "")]
121 | [InlineData("", @"./", "/SubFolder", "?a=b")]
122 | [InlineData("", @"./SubFolder", "/你好", "?a=b")]
123 | [InlineData("", @"./SubFolder", "/你好/世界", "?a=b")]
124 | public async Task NearMatch_RedirectAddSlash_All(string baseUrl, string baseDir, string requestUrl, string queryString)
125 | {
126 | await NearMatch_RedirectAddSlash(baseUrl, baseDir, requestUrl, queryString);
127 | }
128 |
129 | [ConditionalTheory]
130 | [OSSkipCondition(OperatingSystems.Linux)]
131 | [OSSkipCondition(OperatingSystems.MacOSX)]
132 | [InlineData("", @".\", "/SubFolder", "")]
133 | [InlineData("", @".\", "/SubFolder", "?a=b")]
134 | [InlineData("", @".\SubFolder", "/你好", "?a=b")]
135 | [InlineData("", @".\SubFolder", "/你好/世界", "?a=b")]
136 | public async Task NearMatch_RedirectAddSlash_Windows(string baseUrl, string baseDir, string requestUrl, string queryString)
137 | {
138 | await NearMatch_RedirectAddSlash(baseUrl, baseDir, requestUrl, queryString);
139 | }
140 |
141 | private async Task NearMatch_RedirectAddSlash(string baseUrl, string baseDir, string requestUrl, string queryString)
142 | {
143 | using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
144 | {
145 | var server = StaticFilesTestServer.Create(app => app.UseDefaultFiles(new DefaultFilesOptions
146 | {
147 | RequestPath = new PathString(baseUrl),
148 | FileProvider = fileProvider
149 | }));
150 | var response = await server.CreateRequest(requestUrl + queryString).GetAsync();
151 |
152 | Assert.Equal(HttpStatusCode.Moved, response.StatusCode);
153 | // the url in the header of `Location: /xxx/xxx` should be encoded
154 | var expectedURL = UriHelper.BuildRelative(baseUrl, requestUrl + "/", new QueryString(queryString), new FragmentString());
155 | var actualURL = response.Headers.GetValues("Location").FirstOrDefault();
156 | Assert.Equal(expectedURL, actualURL);
157 | Assert.Empty((await response.Content.ReadAsByteArrayAsync()));
158 | }
159 | }
160 |
161 | [Theory]
162 | [InlineData("/SubFolder", @"./", "/SubFolder/")]
163 | [InlineData("/SubFolder", @".", "/somedir/")]
164 | [InlineData("", @"./SubFolder", "/")]
165 | [InlineData("", @"./SubFolder/", "/")]
166 | public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl)
167 | {
168 | await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl);
169 | }
170 |
171 | [ConditionalTheory]
172 | [OSSkipCondition(OperatingSystems.Linux)]
173 | [OSSkipCondition(OperatingSystems.MacOSX)]
174 | [InlineData("/SubFolder", @".\", "/SubFolder/")]
175 | [InlineData("", @".\SubFolder", "/")]
176 | [InlineData("", @".\SubFolder\", "/")]
177 | public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl)
178 | {
179 | await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl);
180 | }
181 |
182 | private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl)
183 | {
184 | using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
185 | {
186 | var server = StaticFilesTestServer.Create(app => app.UseDefaultFiles(new DefaultFilesOptions
187 | {
188 | RequestPath = new PathString(baseUrl),
189 | FileProvider = fileProvider
190 | }));
191 | var response = await server.CreateRequest(requestUrl).GetAsync();
192 |
193 | Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); // Passed through
194 | }
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | #
6 | # variables
7 | #
8 |
9 | RESET="\033[0m"
10 | RED="\033[0;31m"
11 | YELLOW="\033[0;33m"
12 | MAGENTA="\033[0;95m"
13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
14 | [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
15 | verbose=false
16 | update=false
17 | reinstall=false
18 | repo_path="$DIR"
19 | channel=''
20 | tools_source=''
21 | tools_source_suffix=''
22 | ci=false
23 |
24 | #
25 | # Functions
26 | #
27 | __usage() {
28 | echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]"
29 | echo ""
30 | echo "Arguments:"
31 | echo " command The command to be run."
32 | echo " ... Arguments passed to the command. Variable number of arguments allowed."
33 | echo ""
34 | echo "Options:"
35 | echo " --verbose Show verbose output."
36 | echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.."
37 | echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json."
38 | echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet."
39 | echo " --path The directory to build. Defaults to the directory containing the script."
40 | echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file."
41 | echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings."
42 | echo " -u|--update Update to the latest KoreBuild even if the lock file is present."
43 | echo " --reinstall Reinstall KoreBuild."
44 | echo " --ci Apply CI specific settings and environment variables."
45 | echo ""
46 | echo "Description:"
47 | echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be."
48 | echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel."
49 |
50 | if [[ "${1:-}" != '--no-exit' ]]; then
51 | exit 2
52 | fi
53 | }
54 |
55 | get_korebuild() {
56 | local version
57 | local lock_file="$repo_path/korebuild-lock.txt"
58 | if [ ! -f "$lock_file" ] || [ "$update" = true ]; then
59 | __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix"
60 | fi
61 | version="$(grep 'version:*' -m 1 "$lock_file")"
62 | if [[ "$version" == '' ]]; then
63 | __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'"
64 | return 1
65 | fi
66 | version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
67 | local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version"
68 |
69 | if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then
70 | rm -rf "$korebuild_path"
71 | fi
72 |
73 | {
74 | if [ ! -d "$korebuild_path" ]; then
75 | mkdir -p "$korebuild_path"
76 | local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
77 | tmpfile="$(mktemp)"
78 | echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
79 | if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then
80 | unzip -q -d "$korebuild_path" "$tmpfile"
81 | fi
82 | rm "$tmpfile" || true
83 | fi
84 |
85 | source "$korebuild_path/KoreBuild.sh"
86 | } || {
87 | if [ -d "$korebuild_path" ]; then
88 | echo "Cleaning up after failed installation"
89 | rm -rf "$korebuild_path" || true
90 | fi
91 | return 1
92 | }
93 | }
94 |
95 | __error() {
96 | echo -e "${RED}error: $*${RESET}" 1>&2
97 | }
98 |
99 | __warn() {
100 | echo -e "${YELLOW}warning: $*${RESET}"
101 | }
102 |
103 | __machine_has() {
104 | hash "$1" > /dev/null 2>&1
105 | return $?
106 | }
107 |
108 | __get_remote_file() {
109 | local remote_path=$1
110 | local local_path=$2
111 | local remote_path_suffix=$3
112 |
113 | if [[ "$remote_path" != 'http'* ]]; then
114 | cp "$remote_path" "$local_path"
115 | return 0
116 | fi
117 |
118 | local failed=false
119 | if __machine_has wget; then
120 | wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
121 | else
122 | failed=true
123 | fi
124 |
125 | if [ "$failed" = true ] && __machine_has curl; then
126 | failed=false
127 | curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
128 | fi
129 |
130 | if [ "$failed" = true ]; then
131 | __error "Download failed: $remote_path" 1>&2
132 | return 1
133 | fi
134 | }
135 |
136 | #
137 | # main
138 | #
139 |
140 | command="${1:-}"
141 | shift
142 |
143 | while [[ $# -gt 0 ]]; do
144 | case $1 in
145 | -\?|-h|--help)
146 | __usage --no-exit
147 | exit 0
148 | ;;
149 | -c|--channel|-Channel)
150 | shift
151 | channel="${1:-}"
152 | [ -z "$channel" ] && __usage
153 | ;;
154 | --config-file|-ConfigFile)
155 | shift
156 | config_file="${1:-}"
157 | [ -z "$config_file" ] && __usage
158 | if [ ! -f "$config_file" ]; then
159 | __error "Invalid value for --config-file. $config_file does not exist."
160 | exit 1
161 | fi
162 | ;;
163 | -d|--dotnet-home|-DotNetHome)
164 | shift
165 | DOTNET_HOME="${1:-}"
166 | [ -z "$DOTNET_HOME" ] && __usage
167 | ;;
168 | --path|-Path)
169 | shift
170 | repo_path="${1:-}"
171 | [ -z "$repo_path" ] && __usage
172 | ;;
173 | -s|--tools-source|-ToolsSource)
174 | shift
175 | tools_source="${1:-}"
176 | [ -z "$tools_source" ] && __usage
177 | ;;
178 | --tools-source-suffix|-ToolsSourceSuffix)
179 | shift
180 | tools_source_suffix="${1:-}"
181 | [ -z "$tools_source_suffix" ] && __usage
182 | ;;
183 | -u|--update|-Update)
184 | update=true
185 | ;;
186 | --reinstall|-[Rr]einstall)
187 | reinstall=true
188 | ;;
189 | --ci|-[Cc][Ii])
190 | ci=true
191 | ;;
192 | --verbose|-Verbose)
193 | verbose=true
194 | ;;
195 | --)
196 | shift
197 | break
198 | ;;
199 | *)
200 | break
201 | ;;
202 | esac
203 | shift
204 | done
205 |
206 | if ! __machine_has unzip; then
207 | __error 'Missing required command: unzip'
208 | exit 1
209 | fi
210 |
211 | if ! __machine_has curl && ! __machine_has wget; then
212 | __error 'Missing required command. Either wget or curl is required.'
213 | exit 1
214 | fi
215 |
216 | [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json"
217 | if [ -f "$config_file" ]; then
218 | if __machine_has jq ; then
219 | if jq '.' "$config_file" >/dev/null ; then
220 | config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")"
221 | config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")"
222 | else
223 | __error "$config_file contains invalid JSON."
224 | exit 1
225 | fi
226 | elif __machine_has python ; then
227 | if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
228 | config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
229 | config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
230 | else
231 | __error "$config_file contains invalid JSON."
232 | exit 1
233 | fi
234 | elif __machine_has python3 ; then
235 | if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
236 | config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
237 | config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
238 | else
239 | __error "$config_file contains invalid JSON."
240 | exit 1
241 | fi
242 | else
243 | __error 'Missing required command: jq or python. Could not parse the JSON file.'
244 | exit 1
245 | fi
246 |
247 | [ ! -z "${config_channel:-}" ] && channel="$config_channel"
248 | [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
249 | fi
250 |
251 | [ -z "$channel" ] && channel='master'
252 | [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
253 |
254 | get_korebuild
255 | set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci"
256 | invoke_korebuild_command "$command" "$@"
257 |
--------------------------------------------------------------------------------
/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/StaticFileMiddlewareTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net;
9 | using System.Net.Http;
10 | using System.Net.Sockets;
11 | using System.Text;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 | using Microsoft.AspNetCore.Builder;
15 | using Microsoft.AspNetCore.Hosting;
16 | using Microsoft.AspNetCore.Http;
17 | using Microsoft.AspNetCore.Server.IntegrationTesting;
18 | using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
19 | using Microsoft.AspNetCore.Testing.xunit;
20 | using Microsoft.Extensions.DependencyInjection;
21 | using Microsoft.Extensions.Logging.Testing;
22 | using Xunit;
23 |
24 | namespace Microsoft.AspNetCore.StaticFiles
25 | {
26 | public class StaticFileMiddlewareTests : LoggedTest
27 | {
28 | [Fact]
29 | public async Task ReturnsNotFoundWithoutWwwroot()
30 | {
31 | var builder = new WebHostBuilder()
32 | .ConfigureServices(services => services.AddSingleton(LoggerFactory))
33 | .UseKestrel()
34 | .Configure(app => app.UseStaticFiles());
35 |
36 | using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel)))
37 | {
38 | using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) })
39 | {
40 | var response = await client.GetAsync("TestDocument.txt");
41 |
42 | Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
43 | }
44 | }
45 | }
46 |
47 | [Fact]
48 | public async Task FoundFile_LastModifiedTrimsSeconds()
49 | {
50 | var builder = new WebHostBuilder()
51 | .ConfigureServices(services => services.AddSingleton(LoggerFactory))
52 | .UseKestrel()
53 | .UseWebRoot(AppContext.BaseDirectory)
54 | .Configure(app => app.UseStaticFiles());
55 |
56 | using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel)))
57 | {
58 | using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) })
59 | {
60 | var last = File.GetLastWriteTimeUtc(Path.Combine(AppContext.BaseDirectory, "TestDocument.txt"));
61 | var response = await client.GetAsync("TestDocument.txt");
62 |
63 | var trimmed = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, TimeSpan.Zero).ToUniversalTime();
64 |
65 | Assert.Equal(response.Content.Headers.LastModified.Value, trimmed);
66 | }
67 | }
68 | }
69 |
70 | [Theory]
71 | [MemberData(nameof(ExistingFiles))]
72 | public async Task FoundFile_Served_All(string baseUrl, string baseDir, string requestUrl)
73 | {
74 | await FoundFile_Served(baseUrl, baseDir, requestUrl);
75 | }
76 |
77 | [ConditionalTheory]
78 | [OSSkipCondition(OperatingSystems.Linux)]
79 | [OSSkipCondition(OperatingSystems.MacOSX)]
80 | [InlineData("", @".", "/testDocument.Txt")]
81 | [InlineData("/somedir", @".", "/somedir/Testdocument.TXT")]
82 | [InlineData("/SomeDir", @".", "/soMediR/testdocument.txT")]
83 | [InlineData("/somedir", @"SubFolder", "/somedir/Ranges.tXt")]
84 | public async Task FoundFile_Served_Windows(string baseUrl, string baseDir, string requestUrl)
85 | {
86 | await FoundFile_Served(baseUrl, baseDir, requestUrl);
87 | }
88 |
89 | private async Task FoundFile_Served(string baseUrl, string baseDir, string requestUrl)
90 | {
91 | var builder = new WebHostBuilder()
92 | .ConfigureServices(services => services.AddSingleton(LoggerFactory))
93 | .UseKestrel()
94 | .UseWebRoot(Path.Combine(AppContext.BaseDirectory, baseDir))
95 | .Configure(app => app.UseStaticFiles(new StaticFileOptions
96 | {
97 | RequestPath = new PathString(baseUrl),
98 | }));
99 |
100 | using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel)))
101 | {
102 | var hostingEnvironment = server.Services.GetService();
103 |
104 | using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) })
105 | {
106 | var fileInfo = hostingEnvironment.WebRootFileProvider.GetFileInfo(Path.GetFileName(requestUrl));
107 | var response = await client.GetAsync(requestUrl);
108 | var responseContent = await response.Content.ReadAsByteArrayAsync();
109 |
110 | Assert.Equal(HttpStatusCode.OK, response.StatusCode);
111 | Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
112 | Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
113 | Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length);
114 |
115 | using (var stream = fileInfo.CreateReadStream())
116 | {
117 | var fileContents = new byte[stream.Length];
118 | stream.Read(fileContents, 0, (int)stream.Length);
119 | Assert.True(responseContent.SequenceEqual(fileContents));
120 | }
121 | }
122 | }
123 | }
124 |
125 | [Theory]
126 | [MemberData(nameof(ExistingFiles))]
127 | public async Task HeadFile_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl)
128 | {
129 | var builder = new WebHostBuilder()
130 | .ConfigureServices(services => services.AddSingleton(LoggerFactory))
131 | .UseKestrel()
132 | .UseWebRoot(Path.Combine(AppContext.BaseDirectory, baseDir))
133 | .Configure(app => app.UseStaticFiles(new StaticFileOptions
134 | {
135 | RequestPath = new PathString(baseUrl),
136 | }));
137 |
138 | using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel)))
139 | {
140 | var hostingEnvironment = server.Services.GetService();
141 |
142 | using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) })
143 | {
144 | var fileInfo = hostingEnvironment.WebRootFileProvider.GetFileInfo(Path.GetFileName(requestUrl));
145 | var request = new HttpRequestMessage(HttpMethod.Head, requestUrl);
146 | var response = await client.SendAsync(request);
147 |
148 | Assert.Equal(HttpStatusCode.OK, response.StatusCode);
149 | Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
150 | Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
151 | Assert.Empty((await response.Content.ReadAsByteArrayAsync()));
152 | }
153 | }
154 | }
155 |
156 | public static IEnumerable