Initial static files port.
This commit is contained in:
commit
86b1ac8f39
|
|
@ -0,0 +1,50 @@
|
||||||
|
*.doc diff=astextplain
|
||||||
|
*.DOC diff=astextplain
|
||||||
|
*.docx diff=astextplain
|
||||||
|
*.DOCX diff=astextplain
|
||||||
|
*.dot diff=astextplain
|
||||||
|
*.DOT diff=astextplain
|
||||||
|
*.pdf diff=astextplain
|
||||||
|
*.PDF diff=astextplain
|
||||||
|
*.rtf diff=astextplain
|
||||||
|
*.RTF diff=astextplain
|
||||||
|
|
||||||
|
*.jpg binary
|
||||||
|
*.png binary
|
||||||
|
*.gif binary
|
||||||
|
|
||||||
|
*.cs text=auto diff=csharp
|
||||||
|
*.vb text=auto
|
||||||
|
*.resx text=auto
|
||||||
|
*.c text=auto
|
||||||
|
*.cpp text=auto
|
||||||
|
*.cxx text=auto
|
||||||
|
*.h text=auto
|
||||||
|
*.hxx text=auto
|
||||||
|
*.py text=auto
|
||||||
|
*.rb text=auto
|
||||||
|
*.java text=auto
|
||||||
|
*.html text=auto
|
||||||
|
*.htm text=auto
|
||||||
|
*.css text=auto
|
||||||
|
*.scss text=auto
|
||||||
|
*.sass text=auto
|
||||||
|
*.less text=auto
|
||||||
|
*.js text=auto
|
||||||
|
*.lisp text=auto
|
||||||
|
*.clj text=auto
|
||||||
|
*.sql text=auto
|
||||||
|
*.php text=auto
|
||||||
|
*.lua text=auto
|
||||||
|
*.m text=auto
|
||||||
|
*.asm text=auto
|
||||||
|
*.erl text=auto
|
||||||
|
*.fs text=auto
|
||||||
|
*.fsx text=auto
|
||||||
|
*.hs text=auto
|
||||||
|
|
||||||
|
*.csproj text=auto
|
||||||
|
*.vbproj text=auto
|
||||||
|
*.fsproj text=auto
|
||||||
|
*.dbproj text=auto
|
||||||
|
*.sln text=auto eol=crlf
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
[Oo]bj/
|
||||||
|
[Bb]in/
|
||||||
|
TestResults/
|
||||||
|
.nuget/
|
||||||
|
_ReSharper.*/
|
||||||
|
packages/
|
||||||
|
artifacts/
|
||||||
|
PublishProfiles/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.cache
|
||||||
|
*.docstates
|
||||||
|
_ReSharper.*
|
||||||
|
nuget.exe
|
||||||
|
*net45.csproj
|
||||||
|
*k10.csproj
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.pidb
|
||||||
|
*.userprefs
|
||||||
|
*DS_Store
|
||||||
|
*.ncrunchsolution
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 2013
|
||||||
|
VisualStudioVersion = 12.0.30110.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.StaticFiles.net45", "src\Microsoft.AspNet.StaticFiles\Microsoft.AspNet.StaticFiles.net45.csproj", "{49278B83-CC12-49EA-8F61-D143863DD21B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.StaticFiles.k10", "src\Microsoft.AspNet.StaticFiles\Microsoft.AspNet.StaticFiles.k10.csproj", "{297551FE-7539-4E43-A6B8-165C7789F48D}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{40EE0889-960E-41B4-A3D3-9CE963EB0797}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8B21A3A9-9CA6-4857-A6E0-1A3203404B60}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StaticFileSample.k10", "samples\StaticFileSample\StaticFileSample.k10.csproj", "{8C5384FC-24F3-47F2-897C-A151604F011C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StaticFileSample.net45", "samples\StaticFileSample\StaticFileSample.net45.csproj", "{742E16CD-8217-4386-AAF0-5F116E62CD82}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{49278B83-CC12-49EA-8F61-D143863DD21B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{49278B83-CC12-49EA-8F61-D143863DD21B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{49278B83-CC12-49EA-8F61-D143863DD21B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{49278B83-CC12-49EA-8F61-D143863DD21B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{297551FE-7539-4E43-A6B8-165C7789F48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{297551FE-7539-4E43-A6B8-165C7789F48D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{297551FE-7539-4E43-A6B8-165C7789F48D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{297551FE-7539-4E43-A6B8-165C7789F48D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8C5384FC-24F3-47F2-897C-A151604F011C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8C5384FC-24F3-47F2-897C-A151604F011C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8C5384FC-24F3-47F2-897C-A151604F011C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8C5384FC-24F3-47F2-897C-A151604F011C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{742E16CD-8217-4386-AAF0-5F116E62CD82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{742E16CD-8217-4386-AAF0-5F116E62CD82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{742E16CD-8217-4386-AAF0-5F116E62CD82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{742E16CD-8217-4386-AAF0-5F116E62CD82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{297551FE-7539-4E43-A6B8-165C7789F48D} = {40EE0889-960E-41B4-A3D3-9CE963EB0797}
|
||||||
|
{49278B83-CC12-49EA-8F61-D143863DD21B} = {40EE0889-960E-41B4-A3D3-9CE963EB0797}
|
||||||
|
{8C5384FC-24F3-47F2-897C-A151604F011C} = {8B21A3A9-9CA6-4857-A6E0-1A3203404B60}
|
||||||
|
{742E16CD-8217-4386-AAF0-5F116E62CD82} = {8B21A3A9-9CA6-4857-A6E0-1A3203404B60}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/" />
|
||||||
|
<add key="NuGet.org" value="https://nuget.org/api/v2/" />
|
||||||
|
</packageSources>
|
||||||
|
<packageSourceCredentials>
|
||||||
|
<AspNetVNext>
|
||||||
|
<add key="Username" value="aspnetreadonly" />
|
||||||
|
<add key="ClearTextPassword" value="4d8a2d9c-7b80-4162-9978-47e918c9658c" />
|
||||||
|
</AspNetVNext>
|
||||||
|
</packageSourceCredentials>
|
||||||
|
</configuration>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
@echo off
|
||||||
|
cd %~dp0
|
||||||
|
|
||||||
|
SETLOCAL
|
||||||
|
SET CACHED_NUGET=%LocalAppData%\NuGet\NuGet.exe
|
||||||
|
|
||||||
|
IF EXIST %CACHED_NUGET% goto copynuget
|
||||||
|
echo Downloading latest version of NuGet.exe...
|
||||||
|
IF NOT EXIST %LocalAppData%\NuGet md %LocalAppData%\NuGet
|
||||||
|
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://www.nuget.org/nuget.exe' -OutFile '%CACHED_NUGET%'"
|
||||||
|
|
||||||
|
:copynuget
|
||||||
|
IF EXIST .nuget\nuget.exe goto restore
|
||||||
|
md .nuget
|
||||||
|
copy %CACHED_NUGET% .nuget\nuget.exe > nul
|
||||||
|
|
||||||
|
:restore
|
||||||
|
IF EXIST packages\KoreBuild goto run
|
||||||
|
.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
|
||||||
|
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
||||||
|
|
||||||
|
:run
|
||||||
|
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"sources": ["src"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
var VERSION='0.1'
|
||||||
|
var FULL_VERSION='0.1'
|
||||||
|
var AUTHORS='Microsoft'
|
||||||
|
|
||||||
|
use-standard-lifecycle
|
||||||
|
k-standard-goals
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
#if NET45
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.Owin.Hosting;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace StaticFilesSample
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
const string baseUrl = "http://localhost:9001/";
|
||||||
|
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
#if NET45
|
||||||
|
using (WebApp.Start<Startup>(new StartOptions(baseUrl)))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Listening at {0}", baseUrl);
|
||||||
|
Process.Start(baseUrl);
|
||||||
|
Console.WriteLine("Press any key to exit");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Console.WriteLine("Hello World");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#if NET45
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet;
|
||||||
|
using Owin;
|
||||||
|
using Microsoft.AspNet.StaticFiles;
|
||||||
|
|
||||||
|
namespace StaticFilesSample
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configuration(IAppBuilder app)
|
||||||
|
{
|
||||||
|
app.UseErrorPage();
|
||||||
|
|
||||||
|
// Temporary bridge from katana to Owin
|
||||||
|
app.UseBuilder(ConfigurePK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigurePK(IBuilder builder)
|
||||||
|
{
|
||||||
|
builder.UseFileServer(new FileServerOptions()
|
||||||
|
{
|
||||||
|
EnableDirectoryBrowsing = true,
|
||||||
|
FileSystem = new PhysicalFileSystem(@"c:\temp")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"version" : "0.1-alpha-*",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.FileSystems": "0.1-alpha-*",
|
||||||
|
"Microsoft.AspNet.StaticFiles": "",
|
||||||
|
"Microsoft.AspNet.Abstractions": "0.1-alpha-*"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"net45": {
|
||||||
|
"dependencies": {
|
||||||
|
"Owin": "1.0",
|
||||||
|
"Microsoft.Owin": "2.1.0",
|
||||||
|
"Microsoft.Owin.Diagnostics": "2.1.0",
|
||||||
|
"Microsoft.Owin.Hosting": "2.1.0",
|
||||||
|
"Microsoft.Owin.Host.HttpListener": "2.1.0",
|
||||||
|
"Microsoft.AspNet.AppBuilderSupport": "0.1-alpha-*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"k10" : { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
internal const string ServerCapabilitiesKey = "server.Capabilities";
|
||||||
|
internal const string SendFileVersionKey = "sendfile.Version";
|
||||||
|
internal const string SendFileVersion = "1.0";
|
||||||
|
|
||||||
|
internal const string Location = "Location";
|
||||||
|
internal const string IfMatch = "If-Match";
|
||||||
|
internal const string IfNoneMatch = "If-None-Match";
|
||||||
|
internal const string IfModifiedSince = "If-Modified-Since";
|
||||||
|
internal const string IfUnmodifiedSince = "If-Unmodified-Since";
|
||||||
|
internal const string IfRange = "If-Range";
|
||||||
|
internal const string Range = "Range";
|
||||||
|
internal const string ContentRange = "Content-Range";
|
||||||
|
internal const string LastModified = "Last-Modified";
|
||||||
|
internal const string ETag = "ETag";
|
||||||
|
|
||||||
|
internal const string HttpDateFormat = "r";
|
||||||
|
|
||||||
|
internal const string TextHtmlUtf8 = "text/html; charset=utf-8";
|
||||||
|
|
||||||
|
internal const int Status200Ok = 200;
|
||||||
|
internal const int Status206PartialContent = 206;
|
||||||
|
internal const int Status304NotModified = 304;
|
||||||
|
internal const int Status412PreconditionFailed = 412;
|
||||||
|
internal const int Status416RangeNotSatisfiable = 416;
|
||||||
|
|
||||||
|
internal static readonly Task CompletedTask = CreateCompletedTask();
|
||||||
|
|
||||||
|
private static Task CreateCompletedTask()
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<object>();
|
||||||
|
tcs.SetResult(null);
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,432 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.ContentTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a mapping between file extensions and MIME types.
|
||||||
|
/// </summary>
|
||||||
|
public class FileExtensionContentTypeProvider : IContentTypeProvider
|
||||||
|
{
|
||||||
|
#region Extension mapping table
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new provider with a set of default mappings.
|
||||||
|
/// </summary>
|
||||||
|
public FileExtensionContentTypeProvider()
|
||||||
|
: this(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ ".323", "text/h323" },
|
||||||
|
{ ".3g2", "video/3gpp2" },
|
||||||
|
{ ".3gp2", "video/3gpp2" },
|
||||||
|
{ ".3gp", "video/3gpp" },
|
||||||
|
{ ".3gpp", "video/3gpp" },
|
||||||
|
{ ".aac", "audio/aac" },
|
||||||
|
{ ".aaf", "application/octet-stream" },
|
||||||
|
{ ".aca", "application/octet-stream" },
|
||||||
|
{ ".accdb", "application/msaccess" },
|
||||||
|
{ ".accde", "application/msaccess" },
|
||||||
|
{ ".accdt", "application/msaccess" },
|
||||||
|
{ ".acx", "application/internet-property-stream" },
|
||||||
|
{ ".adt", "audio/vnd.dlna.adts" },
|
||||||
|
{ ".adts", "audio/vnd.dlna.adts" },
|
||||||
|
{ ".afm", "application/octet-stream" },
|
||||||
|
{ ".ai", "application/postscript" },
|
||||||
|
{ ".aif", "audio/x-aiff" },
|
||||||
|
{ ".aifc", "audio/aiff" },
|
||||||
|
{ ".aiff", "audio/aiff" },
|
||||||
|
{ ".application", "application/x-ms-application" },
|
||||||
|
{ ".art", "image/x-jg" },
|
||||||
|
{ ".asd", "application/octet-stream" },
|
||||||
|
{ ".asf", "video/x-ms-asf" },
|
||||||
|
{ ".asi", "application/octet-stream" },
|
||||||
|
{ ".asm", "text/plain" },
|
||||||
|
{ ".asr", "video/x-ms-asf" },
|
||||||
|
{ ".asx", "video/x-ms-asf" },
|
||||||
|
{ ".atom", "application/atom+xml" },
|
||||||
|
{ ".au", "audio/basic" },
|
||||||
|
{ ".avi", "video/x-msvideo" },
|
||||||
|
{ ".axs", "application/olescript" },
|
||||||
|
{ ".bas", "text/plain" },
|
||||||
|
{ ".bcpio", "application/x-bcpio" },
|
||||||
|
{ ".bin", "application/octet-stream" },
|
||||||
|
{ ".bmp", "image/bmp" },
|
||||||
|
{ ".c", "text/plain" },
|
||||||
|
{ ".cab", "application/vnd.ms-cab-compressed" },
|
||||||
|
{ ".calx", "application/vnd.ms-office.calx" },
|
||||||
|
{ ".cat", "application/vnd.ms-pki.seccat" },
|
||||||
|
{ ".cdf", "application/x-cdf" },
|
||||||
|
{ ".chm", "application/octet-stream" },
|
||||||
|
{ ".class", "application/x-java-applet" },
|
||||||
|
{ ".clp", "application/x-msclip" },
|
||||||
|
{ ".cmx", "image/x-cmx" },
|
||||||
|
{ ".cnf", "text/plain" },
|
||||||
|
{ ".cod", "image/cis-cod" },
|
||||||
|
{ ".cpio", "application/x-cpio" },
|
||||||
|
{ ".cpp", "text/plain" },
|
||||||
|
{ ".crd", "application/x-mscardfile" },
|
||||||
|
{ ".crl", "application/pkix-crl" },
|
||||||
|
{ ".crt", "application/x-x509-ca-cert" },
|
||||||
|
{ ".csh", "application/x-csh" },
|
||||||
|
{ ".css", "text/css" },
|
||||||
|
{ ".csv", "application/octet-stream" },
|
||||||
|
{ ".cur", "application/octet-stream" },
|
||||||
|
{ ".dcr", "application/x-director" },
|
||||||
|
{ ".deploy", "application/octet-stream" },
|
||||||
|
{ ".der", "application/x-x509-ca-cert" },
|
||||||
|
{ ".dib", "image/bmp" },
|
||||||
|
{ ".dir", "application/x-director" },
|
||||||
|
{ ".disco", "text/xml" },
|
||||||
|
{ ".dlm", "text/dlm" },
|
||||||
|
{ ".doc", "application/msword" },
|
||||||
|
{ ".docm", "application/vnd.ms-word.document.macroEnabled.12" },
|
||||||
|
{ ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
|
||||||
|
{ ".dot", "application/msword" },
|
||||||
|
{ ".dotm", "application/vnd.ms-word.template.macroEnabled.12" },
|
||||||
|
{ ".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
|
||||||
|
{ ".dsp", "application/octet-stream" },
|
||||||
|
{ ".dtd", "text/xml" },
|
||||||
|
{ ".dvi", "application/x-dvi" },
|
||||||
|
{ ".dvr-ms", "video/x-ms-dvr" },
|
||||||
|
{ ".dwf", "drawing/x-dwf" },
|
||||||
|
{ ".dwp", "application/octet-stream" },
|
||||||
|
{ ".dxr", "application/x-director" },
|
||||||
|
{ ".eml", "message/rfc822" },
|
||||||
|
{ ".emz", "application/octet-stream" },
|
||||||
|
{ ".eot", "application/vnd.ms-fontobject" },
|
||||||
|
{ ".eps", "application/postscript" },
|
||||||
|
{ ".etx", "text/x-setext" },
|
||||||
|
{ ".evy", "application/envoy" },
|
||||||
|
{ ".fdf", "application/vnd.fdf" },
|
||||||
|
{ ".fif", "application/fractals" },
|
||||||
|
{ ".fla", "application/octet-stream" },
|
||||||
|
{ ".flr", "x-world/x-vrml" },
|
||||||
|
{ ".flv", "video/x-flv" },
|
||||||
|
{ ".gif", "image/gif" },
|
||||||
|
{ ".gtar", "application/x-gtar" },
|
||||||
|
{ ".gz", "application/x-gzip" },
|
||||||
|
{ ".h", "text/plain" },
|
||||||
|
{ ".hdf", "application/x-hdf" },
|
||||||
|
{ ".hdml", "text/x-hdml" },
|
||||||
|
{ ".hhc", "application/x-oleobject" },
|
||||||
|
{ ".hhk", "application/octet-stream" },
|
||||||
|
{ ".hhp", "application/octet-stream" },
|
||||||
|
{ ".hlp", "application/winhlp" },
|
||||||
|
{ ".hqx", "application/mac-binhex40" },
|
||||||
|
{ ".hta", "application/hta" },
|
||||||
|
{ ".htc", "text/x-component" },
|
||||||
|
{ ".htm", "text/html" },
|
||||||
|
{ ".html", "text/html" },
|
||||||
|
{ ".htt", "text/webviewhtml" },
|
||||||
|
{ ".hxt", "text/html" },
|
||||||
|
{ ".ical", "text/calendar" },
|
||||||
|
{ ".icalendar", "text/calendar" },
|
||||||
|
{ ".ico", "image/x-icon" },
|
||||||
|
{ ".ics", "text/calendar" },
|
||||||
|
{ ".ief", "image/ief" },
|
||||||
|
{ ".ifb", "text/calendar" },
|
||||||
|
{ ".iii", "application/x-iphone" },
|
||||||
|
{ ".inf", "application/octet-stream" },
|
||||||
|
{ ".ins", "application/x-internet-signup" },
|
||||||
|
{ ".isp", "application/x-internet-signup" },
|
||||||
|
{ ".IVF", "video/x-ivf" },
|
||||||
|
{ ".jar", "application/java-archive" },
|
||||||
|
{ ".java", "application/octet-stream" },
|
||||||
|
{ ".jck", "application/liquidmotion" },
|
||||||
|
{ ".jcz", "application/liquidmotion" },
|
||||||
|
{ ".jfif", "image/pjpeg" },
|
||||||
|
{ ".jpb", "application/octet-stream" },
|
||||||
|
{ ".jpe", "image/jpeg" },
|
||||||
|
{ ".jpeg", "image/jpeg" },
|
||||||
|
{ ".jpg", "image/jpeg" },
|
||||||
|
{ ".js", "application/javascript" },
|
||||||
|
{ ".jsx", "text/jscript" },
|
||||||
|
{ ".latex", "application/x-latex" },
|
||||||
|
{ ".lit", "application/x-ms-reader" },
|
||||||
|
{ ".lpk", "application/octet-stream" },
|
||||||
|
{ ".lsf", "video/x-la-asf" },
|
||||||
|
{ ".lsx", "video/x-la-asf" },
|
||||||
|
{ ".lzh", "application/octet-stream" },
|
||||||
|
{ ".m13", "application/x-msmediaview" },
|
||||||
|
{ ".m14", "application/x-msmediaview" },
|
||||||
|
{ ".m1v", "video/mpeg" },
|
||||||
|
{ ".m2ts", "video/vnd.dlna.mpeg-tts" },
|
||||||
|
{ ".m3u", "audio/x-mpegurl" },
|
||||||
|
{ ".m4a", "audio/mp4" },
|
||||||
|
{ ".m4v", "video/mp4" },
|
||||||
|
{ ".man", "application/x-troff-man" },
|
||||||
|
{ ".manifest", "application/x-ms-manifest" },
|
||||||
|
{ ".map", "text/plain" },
|
||||||
|
{ ".mdb", "application/x-msaccess" },
|
||||||
|
{ ".mdp", "application/octet-stream" },
|
||||||
|
{ ".me", "application/x-troff-me" },
|
||||||
|
{ ".mht", "message/rfc822" },
|
||||||
|
{ ".mhtml", "message/rfc822" },
|
||||||
|
{ ".mid", "audio/mid" },
|
||||||
|
{ ".midi", "audio/mid" },
|
||||||
|
{ ".mix", "application/octet-stream" },
|
||||||
|
{ ".mmf", "application/x-smaf" },
|
||||||
|
{ ".mno", "text/xml" },
|
||||||
|
{ ".mny", "application/x-msmoney" },
|
||||||
|
{ ".mov", "video/quicktime" },
|
||||||
|
{ ".movie", "video/x-sgi-movie" },
|
||||||
|
{ ".mp2", "video/mpeg" },
|
||||||
|
{ ".mp3", "audio/mpeg" },
|
||||||
|
{ ".mp4", "video/mp4" },
|
||||||
|
{ ".mp4v", "video/mp4" },
|
||||||
|
{ ".mpa", "video/mpeg" },
|
||||||
|
{ ".mpe", "video/mpeg" },
|
||||||
|
{ ".mpeg", "video/mpeg" },
|
||||||
|
{ ".mpg", "video/mpeg" },
|
||||||
|
{ ".mpp", "application/vnd.ms-project" },
|
||||||
|
{ ".mpv2", "video/mpeg" },
|
||||||
|
{ ".ms", "application/x-troff-ms" },
|
||||||
|
{ ".msi", "application/octet-stream" },
|
||||||
|
{ ".mso", "application/octet-stream" },
|
||||||
|
{ ".mvb", "application/x-msmediaview" },
|
||||||
|
{ ".mvc", "application/x-miva-compiled" },
|
||||||
|
{ ".nc", "application/x-netcdf" },
|
||||||
|
{ ".nsc", "video/x-ms-asf" },
|
||||||
|
{ ".nws", "message/rfc822" },
|
||||||
|
{ ".ocx", "application/octet-stream" },
|
||||||
|
{ ".oda", "application/oda" },
|
||||||
|
{ ".odc", "text/x-ms-odc" },
|
||||||
|
{ ".ods", "application/oleobject" },
|
||||||
|
{ ".oga", "audio/ogg" },
|
||||||
|
{ ".ogg", "video/ogg" },
|
||||||
|
{ ".ogv", "video/ogg" },
|
||||||
|
{ ".ogx", "application/ogg" },
|
||||||
|
{ ".one", "application/onenote" },
|
||||||
|
{ ".onea", "application/onenote" },
|
||||||
|
{ ".onetoc", "application/onenote" },
|
||||||
|
{ ".onetoc2", "application/onenote" },
|
||||||
|
{ ".onetmp", "application/onenote" },
|
||||||
|
{ ".onepkg", "application/onenote" },
|
||||||
|
{ ".osdx", "application/opensearchdescription+xml" },
|
||||||
|
{ ".otf", "font/otf" },
|
||||||
|
{ ".p10", "application/pkcs10" },
|
||||||
|
{ ".p12", "application/x-pkcs12" },
|
||||||
|
{ ".p7b", "application/x-pkcs7-certificates" },
|
||||||
|
{ ".p7c", "application/pkcs7-mime" },
|
||||||
|
{ ".p7m", "application/pkcs7-mime" },
|
||||||
|
{ ".p7r", "application/x-pkcs7-certreqresp" },
|
||||||
|
{ ".p7s", "application/pkcs7-signature" },
|
||||||
|
{ ".pbm", "image/x-portable-bitmap" },
|
||||||
|
{ ".pcx", "application/octet-stream" },
|
||||||
|
{ ".pcz", "application/octet-stream" },
|
||||||
|
{ ".pdf", "application/pdf" },
|
||||||
|
{ ".pfb", "application/octet-stream" },
|
||||||
|
{ ".pfm", "application/octet-stream" },
|
||||||
|
{ ".pfx", "application/x-pkcs12" },
|
||||||
|
{ ".pgm", "image/x-portable-graymap" },
|
||||||
|
{ ".pko", "application/vnd.ms-pki.pko" },
|
||||||
|
{ ".pma", "application/x-perfmon" },
|
||||||
|
{ ".pmc", "application/x-perfmon" },
|
||||||
|
{ ".pml", "application/x-perfmon" },
|
||||||
|
{ ".pmr", "application/x-perfmon" },
|
||||||
|
{ ".pmw", "application/x-perfmon" },
|
||||||
|
{ ".png", "image/png" },
|
||||||
|
{ ".pnm", "image/x-portable-anymap" },
|
||||||
|
{ ".pnz", "image/png" },
|
||||||
|
{ ".pot", "application/vnd.ms-powerpoint" },
|
||||||
|
{ ".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" },
|
||||||
|
{ ".potx", "application/vnd.openxmlformats-officedocument.presentationml.template" },
|
||||||
|
{ ".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
|
||||||
|
{ ".ppm", "image/x-portable-pixmap" },
|
||||||
|
{ ".pps", "application/vnd.ms-powerpoint" },
|
||||||
|
{ ".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
|
||||||
|
{ ".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
|
||||||
|
{ ".ppt", "application/vnd.ms-powerpoint" },
|
||||||
|
{ ".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
|
||||||
|
{ ".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
|
||||||
|
{ ".prf", "application/pics-rules" },
|
||||||
|
{ ".prm", "application/octet-stream" },
|
||||||
|
{ ".prx", "application/octet-stream" },
|
||||||
|
{ ".ps", "application/postscript" },
|
||||||
|
{ ".psd", "application/octet-stream" },
|
||||||
|
{ ".psm", "application/octet-stream" },
|
||||||
|
{ ".psp", "application/octet-stream" },
|
||||||
|
{ ".pub", "application/x-mspublisher" },
|
||||||
|
{ ".qt", "video/quicktime" },
|
||||||
|
{ ".qtl", "application/x-quicktimeplayer" },
|
||||||
|
{ ".qxd", "application/octet-stream" },
|
||||||
|
{ ".ra", "audio/x-pn-realaudio" },
|
||||||
|
{ ".ram", "audio/x-pn-realaudio" },
|
||||||
|
{ ".rar", "application/octet-stream" },
|
||||||
|
{ ".ras", "image/x-cmu-raster" },
|
||||||
|
{ ".rf", "image/vnd.rn-realflash" },
|
||||||
|
{ ".rgb", "image/x-rgb" },
|
||||||
|
{ ".rm", "application/vnd.rn-realmedia" },
|
||||||
|
{ ".rmi", "audio/mid" },
|
||||||
|
{ ".roff", "application/x-troff" },
|
||||||
|
{ ".rpm", "audio/x-pn-realaudio-plugin" },
|
||||||
|
{ ".rtf", "application/rtf" },
|
||||||
|
{ ".rtx", "text/richtext" },
|
||||||
|
{ ".scd", "application/x-msschedule" },
|
||||||
|
{ ".sct", "text/scriptlet" },
|
||||||
|
{ ".sea", "application/octet-stream" },
|
||||||
|
{ ".setpay", "application/set-payment-initiation" },
|
||||||
|
{ ".setreg", "application/set-registration-initiation" },
|
||||||
|
{ ".sgml", "text/sgml" },
|
||||||
|
{ ".sh", "application/x-sh" },
|
||||||
|
{ ".shar", "application/x-shar" },
|
||||||
|
{ ".sit", "application/x-stuffit" },
|
||||||
|
{ ".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12" },
|
||||||
|
{ ".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide" },
|
||||||
|
{ ".smd", "audio/x-smd" },
|
||||||
|
{ ".smi", "application/octet-stream" },
|
||||||
|
{ ".smx", "audio/x-smd" },
|
||||||
|
{ ".smz", "audio/x-smd" },
|
||||||
|
{ ".snd", "audio/basic" },
|
||||||
|
{ ".snp", "application/octet-stream" },
|
||||||
|
{ ".spc", "application/x-pkcs7-certificates" },
|
||||||
|
{ ".spl", "application/futuresplash" },
|
||||||
|
{ ".spx", "audio/ogg" },
|
||||||
|
{ ".src", "application/x-wais-source" },
|
||||||
|
{ ".ssm", "application/streamingmedia" },
|
||||||
|
{ ".sst", "application/vnd.ms-pki.certstore" },
|
||||||
|
{ ".stl", "application/vnd.ms-pki.stl" },
|
||||||
|
{ ".sv4cpio", "application/x-sv4cpio" },
|
||||||
|
{ ".sv4crc", "application/x-sv4crc" },
|
||||||
|
{ ".svg", "image/svg+xml" },
|
||||||
|
{ ".svgz", "image/svg+xml" },
|
||||||
|
{ ".swf", "application/x-shockwave-flash" },
|
||||||
|
{ ".t", "application/x-troff" },
|
||||||
|
{ ".tar", "application/x-tar" },
|
||||||
|
{ ".tcl", "application/x-tcl" },
|
||||||
|
{ ".tex", "application/x-tex" },
|
||||||
|
{ ".texi", "application/x-texinfo" },
|
||||||
|
{ ".texinfo", "application/x-texinfo" },
|
||||||
|
{ ".tgz", "application/x-compressed" },
|
||||||
|
{ ".thmx", "application/vnd.ms-officetheme" },
|
||||||
|
{ ".thn", "application/octet-stream" },
|
||||||
|
{ ".tif", "image/tiff" },
|
||||||
|
{ ".tiff", "image/tiff" },
|
||||||
|
{ ".toc", "application/octet-stream" },
|
||||||
|
{ ".tr", "application/x-troff" },
|
||||||
|
{ ".trm", "application/x-msterminal" },
|
||||||
|
{ ".ts", "video/vnd.dlna.mpeg-tts" },
|
||||||
|
{ ".tsv", "text/tab-separated-values" },
|
||||||
|
{ ".ttf", "application/octet-stream" },
|
||||||
|
{ ".tts", "video/vnd.dlna.mpeg-tts" },
|
||||||
|
{ ".txt", "text/plain" },
|
||||||
|
{ ".u32", "application/octet-stream" },
|
||||||
|
{ ".uls", "text/iuls" },
|
||||||
|
{ ".ustar", "application/x-ustar" },
|
||||||
|
{ ".vbs", "text/vbscript" },
|
||||||
|
{ ".vcf", "text/x-vcard" },
|
||||||
|
{ ".vcs", "text/plain" },
|
||||||
|
{ ".vdx", "application/vnd.ms-visio.viewer" },
|
||||||
|
{ ".vml", "text/xml" },
|
||||||
|
{ ".vsd", "application/vnd.visio" },
|
||||||
|
{ ".vss", "application/vnd.visio" },
|
||||||
|
{ ".vst", "application/vnd.visio" },
|
||||||
|
{ ".vsto", "application/x-ms-vsto" },
|
||||||
|
{ ".vsw", "application/vnd.visio" },
|
||||||
|
{ ".vsx", "application/vnd.visio" },
|
||||||
|
{ ".vtx", "application/vnd.visio" },
|
||||||
|
{ ".wav", "audio/wav" },
|
||||||
|
{ ".wax", "audio/x-ms-wax" },
|
||||||
|
{ ".wbmp", "image/vnd.wap.wbmp" },
|
||||||
|
{ ".wcm", "application/vnd.ms-works" },
|
||||||
|
{ ".wdb", "application/vnd.ms-works" },
|
||||||
|
{ ".webm", "video/webm" },
|
||||||
|
{ ".wks", "application/vnd.ms-works" },
|
||||||
|
{ ".wm", "video/x-ms-wm" },
|
||||||
|
{ ".wma", "audio/x-ms-wma" },
|
||||||
|
{ ".wmd", "application/x-ms-wmd" },
|
||||||
|
{ ".wmf", "application/x-msmetafile" },
|
||||||
|
{ ".wml", "text/vnd.wap.wml" },
|
||||||
|
{ ".wmlc", "application/vnd.wap.wmlc" },
|
||||||
|
{ ".wmls", "text/vnd.wap.wmlscript" },
|
||||||
|
{ ".wmlsc", "application/vnd.wap.wmlscriptc" },
|
||||||
|
{ ".wmp", "video/x-ms-wmp" },
|
||||||
|
{ ".wmv", "video/x-ms-wmv" },
|
||||||
|
{ ".wmx", "video/x-ms-wmx" },
|
||||||
|
{ ".wmz", "application/x-ms-wmz" },
|
||||||
|
{ ".woff", "application/font-woff" },
|
||||||
|
{ ".wps", "application/vnd.ms-works" },
|
||||||
|
{ ".wri", "application/x-mswrite" },
|
||||||
|
{ ".wrl", "x-world/x-vrml" },
|
||||||
|
{ ".wrz", "x-world/x-vrml" },
|
||||||
|
{ ".wsdl", "text/xml" },
|
||||||
|
{ ".wtv", "video/x-ms-wtv" },
|
||||||
|
{ ".wvx", "video/x-ms-wvx" },
|
||||||
|
{ ".x", "application/directx" },
|
||||||
|
{ ".xaf", "x-world/x-vrml" },
|
||||||
|
{ ".xaml", "application/xaml+xml" },
|
||||||
|
{ ".xap", "application/x-silverlight-app" },
|
||||||
|
{ ".xbap", "application/x-ms-xbap" },
|
||||||
|
{ ".xbm", "image/x-xbitmap" },
|
||||||
|
{ ".xdr", "text/plain" },
|
||||||
|
{ ".xht", "application/xhtml+xml" },
|
||||||
|
{ ".xhtml", "application/xhtml+xml" },
|
||||||
|
{ ".xla", "application/vnd.ms-excel" },
|
||||||
|
{ ".xlam", "application/vnd.ms-excel.addin.macroEnabled.12" },
|
||||||
|
{ ".xlc", "application/vnd.ms-excel" },
|
||||||
|
{ ".xlm", "application/vnd.ms-excel" },
|
||||||
|
{ ".xls", "application/vnd.ms-excel" },
|
||||||
|
{ ".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
|
||||||
|
{ ".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" },
|
||||||
|
{ ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
|
||||||
|
{ ".xlt", "application/vnd.ms-excel" },
|
||||||
|
{ ".xltm", "application/vnd.ms-excel.template.macroEnabled.12" },
|
||||||
|
{ ".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
|
||||||
|
{ ".xlw", "application/vnd.ms-excel" },
|
||||||
|
{ ".xml", "text/xml" },
|
||||||
|
{ ".xof", "x-world/x-vrml" },
|
||||||
|
{ ".xpm", "image/x-xpixmap" },
|
||||||
|
{ ".xps", "application/vnd.ms-xpsdocument" },
|
||||||
|
{ ".xsd", "text/xml" },
|
||||||
|
{ ".xsf", "text/xml" },
|
||||||
|
{ ".xsl", "text/xml" },
|
||||||
|
{ ".xslt", "text/xml" },
|
||||||
|
{ ".xsn", "application/octet-stream" },
|
||||||
|
{ ".xtp", "application/octet-stream" },
|
||||||
|
{ ".xwd", "image/x-xwindowdump" },
|
||||||
|
{ ".z", "application/x-compress" },
|
||||||
|
{ ".zip", "application/x-zip-compressed" },
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a lookup engine using the provided mapping.
|
||||||
|
/// It is recommended that the IDictionary instance use StringComparer.OrdinalIgnoreCase.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mapping"></param>
|
||||||
|
public FileExtensionContentTypeProvider(IDictionary<string, string> mapping)
|
||||||
|
{
|
||||||
|
if (mapping == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("mapping");
|
||||||
|
}
|
||||||
|
Mappings = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cross reference table of file extensions and content-types.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<string, string> Mappings { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a file path, determine the MIME type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subpath">A file path</param>
|
||||||
|
/// <param name="contentType">The resulting MIME type</param>
|
||||||
|
/// <returns>True if MIME type could be determined</returns>
|
||||||
|
public bool TryGetContentType(string subpath, out string contentType)
|
||||||
|
{
|
||||||
|
string extension = Path.GetExtension(subpath);
|
||||||
|
if (extension == null)
|
||||||
|
{
|
||||||
|
contentType = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Mappings.TryGetValue(extension, out contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.ContentTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to look up MIME types given a file path
|
||||||
|
/// </summary>
|
||||||
|
public interface IContentTypeProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Given a file path, determine the MIME type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subpath">A file path</param>
|
||||||
|
/// <param name="contentType">The resulting MIME type</param>
|
||||||
|
/// <returns>True if MIME type could be determined</returns>
|
||||||
|
bool TryGetContentType(string subpath, out string contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Dictionary>
|
||||||
|
<Words>
|
||||||
|
<Recognized>
|
||||||
|
<Word>Owin</Word>
|
||||||
|
</Recognized>
|
||||||
|
</Words>
|
||||||
|
</Dictionary>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.StaticFiles;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the DefaultFilesMiddleware
|
||||||
|
/// </summary>
|
||||||
|
public static class DefaultFilesExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables default file mapping on the current path from the current directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseDefaultFiles(this IBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseDefaultFiles(new DefaultFilesOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables default file mapping for the given request path from the directory of the same name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="requestPath">The relative request path and physical path.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseDefaultFiles(this IBuilder builder, string requestPath)
|
||||||
|
{
|
||||||
|
return UseDefaultFiles(builder, new DefaultFilesOptions() { RequestPath = new PathString(requestPath) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables default file mapping with the given options
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseDefaultFiles(this IBuilder builder, DefaultFilesOptions options)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("builder");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Use(next => new DefaultFilesMiddleware(next, options).Invoke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This examines a directory path and determines if there is a default file present.
|
||||||
|
/// If so the file name is appended to the path and execution continues.
|
||||||
|
/// Note we don't just serve the file because it may require interpretation.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultFilesMiddleware
|
||||||
|
{
|
||||||
|
private readonly DefaultFilesOptions _options;
|
||||||
|
private readonly PathString _matchUrl;
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the DefaultFilesMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The next middleware in the pipeline.</param>
|
||||||
|
/// <param name="options">The configuration options for this middleware.</param>
|
||||||
|
public DefaultFilesMiddleware(RequestDelegate next, DefaultFilesOptions options)
|
||||||
|
{
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("next");
|
||||||
|
}
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("options");
|
||||||
|
}
|
||||||
|
if (options.FileSystem == null)
|
||||||
|
{
|
||||||
|
options.FileSystem = new PhysicalFileSystem("." + options.RequestPath.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
_options = options;
|
||||||
|
_matchUrl = options.RequestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This examines the request to see if it matches a configured directory, and if there are any files with the
|
||||||
|
/// configured default names in that directory. If so this will append the corresponding file name to the request
|
||||||
|
/// path for a later middleware to handle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
IEnumerable<IFileInfo> dirContents;
|
||||||
|
PathString subpath;
|
||||||
|
if (Helpers.IsGetOrHeadMethod(context.Request.Method)
|
||||||
|
&& Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out subpath)
|
||||||
|
&& _options.FileSystem.TryGetDirectoryContents(subpath.Value, out dirContents))
|
||||||
|
{
|
||||||
|
// Check if any of our default files exist.
|
||||||
|
for (int matchIndex = 0; matchIndex < _options.DefaultFileNames.Count; matchIndex++)
|
||||||
|
{
|
||||||
|
string defaultFile = _options.DefaultFileNames[matchIndex];
|
||||||
|
IFileInfo file;
|
||||||
|
// TryMatchPath will make sure subpath always ends with a "/" by adding it if needed.
|
||||||
|
if (_options.FileSystem.TryGetFileInfo(subpath + defaultFile, out file))
|
||||||
|
{
|
||||||
|
// If the path matches a directory but does not end in a slash, redirect to add the slash.
|
||||||
|
// This prevents relative links from breaking.
|
||||||
|
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 301;
|
||||||
|
context.Response.Headers[Constants.Location] = context.Request.PathBase + context.Request.Path + "/";
|
||||||
|
return Constants.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match found, re-write the url. A later middleware will actually serve the file.
|
||||||
|
context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for selecting default file names.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultFilesOptions : SharedOptionsBase<DefaultFilesOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for the DefaultFilesMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
public DefaultFilesOptions()
|
||||||
|
: this(new SharedOptions())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for the DefaultFilesMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sharedOptions"></param>
|
||||||
|
public DefaultFilesOptions(SharedOptions sharedOptions)
|
||||||
|
: base(sharedOptions)
|
||||||
|
{
|
||||||
|
// Prioritized list
|
||||||
|
DefaultFileNames = new List<string>()
|
||||||
|
{
|
||||||
|
"default.htm",
|
||||||
|
"default.html",
|
||||||
|
"index.htm",
|
||||||
|
"index.html",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ordered list of file names to select by default. List length and ordering may affect performance.
|
||||||
|
/// </summary>
|
||||||
|
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")]
|
||||||
|
public IList<string> DefaultFileNames { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.StaticFiles;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the DirectoryBrowserMiddleware
|
||||||
|
/// </summary>
|
||||||
|
public static class DirectoryBrowserExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enable directory browsing on the current path for the current directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseDirectoryBrowser(this IBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseDirectoryBrowser(new DirectoryBrowserOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables directory browsing for the given request path from the directory of the same name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="requestPath">The relative request path and physical path.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseDirectoryBrowser(this IBuilder builder, string requestPath)
|
||||||
|
{
|
||||||
|
return UseDirectoryBrowser(builder, new DirectoryBrowserOptions() { RequestPath = new PathString(requestPath) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable directory browsing with the given options
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseDirectoryBrowser(this IBuilder builder, DirectoryBrowserOptions options)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("builder");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Use(next => new DirectoryBrowserMiddleware(next, options).Invoke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables directory browsing
|
||||||
|
/// </summary>
|
||||||
|
public class DirectoryBrowserMiddleware
|
||||||
|
{
|
||||||
|
private readonly DirectoryBrowserOptions _options;
|
||||||
|
private readonly PathString _matchUrl;
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the SendFileMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The next middleware in the pipeline.</param>
|
||||||
|
/// <param name="options">The configuration for this middleware.</param>
|
||||||
|
public DirectoryBrowserMiddleware(RequestDelegate next, DirectoryBrowserOptions options)
|
||||||
|
{
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("next");
|
||||||
|
}
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("options");
|
||||||
|
}
|
||||||
|
if (options.Formatter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Args_NoFormatter);
|
||||||
|
}
|
||||||
|
if (options.FileSystem == null)
|
||||||
|
{
|
||||||
|
options.FileSystem = new PhysicalFileSystem("." + options.RequestPath.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
_options = options;
|
||||||
|
_matchUrl = options.RequestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Examines the request to see if it matches a configured directory. If so, a view of the directory contents is returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
// Check if the URL matches any expected paths
|
||||||
|
PathString subpath;
|
||||||
|
IEnumerable<IFileInfo> contents;
|
||||||
|
if (Helpers.IsGetOrHeadMethod(context.Request.Method)
|
||||||
|
&& Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out subpath)
|
||||||
|
&& TryGetDirectoryInfo(subpath, out contents))
|
||||||
|
{
|
||||||
|
// If the path matches a directory but does not end in a slash, redirect to add the slash.
|
||||||
|
// This prevents relative links from breaking.
|
||||||
|
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 301;
|
||||||
|
context.Response.Headers[Constants.Location] = context.Request.PathBase + context.Request.Path + "/";
|
||||||
|
return Constants.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _options.Formatter.GenerateContentAsync(context, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetDirectoryInfo(PathString subpath, out IEnumerable<IFileInfo> contents)
|
||||||
|
{
|
||||||
|
return _options.FileSystem.TryGetDirectoryContents(subpath.Value, out contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.StaticFiles.DirectoryFormatters;
|
||||||
|
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Directory browsing options
|
||||||
|
/// </summary>
|
||||||
|
public class DirectoryBrowserOptions : SharedOptionsBase<DirectoryBrowserOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enabled directory browsing in the current physical directory for all request paths
|
||||||
|
/// </summary>
|
||||||
|
public DirectoryBrowserOptions()
|
||||||
|
: this(new SharedOptions())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enabled directory browsing in the current physical directory for all request paths
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sharedOptions"></param>
|
||||||
|
public DirectoryBrowserOptions(SharedOptions sharedOptions)
|
||||||
|
: base(sharedOptions)
|
||||||
|
{
|
||||||
|
Formatter = new HtmlDirectoryFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The component that generates the view.
|
||||||
|
/// </summary>
|
||||||
|
public IDirectoryFormatter Formatter { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.DirectoryFormatters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an HTML view for a directory.
|
||||||
|
/// </summary>
|
||||||
|
public class HtmlDirectoryFormatter : IDirectoryFormatter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an HTML view for a directory.
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("context");
|
||||||
|
}
|
||||||
|
if (contents == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("contents");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.ContentType = Constants.TextHtmlUtf8;
|
||||||
|
|
||||||
|
if (Helpers.IsHeadMethod(context.Request.Method))
|
||||||
|
{
|
||||||
|
// HEAD, no response body
|
||||||
|
return Constants.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathString requestPath = context.Request.PathBase + context.Request.Path;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.AppendFormat(
|
||||||
|
@"<!DOCTYPE html>
|
||||||
|
<html lang=""{0}"">", CultureInfo.CurrentUICulture.TwoLetterISOLanguageName);
|
||||||
|
|
||||||
|
builder.AppendFormat(@"
|
||||||
|
<head>
|
||||||
|
<title>{0} {1}</title>", HtmlEncode(Resources.HtmlDir_IndexOf), HtmlEncode(requestPath.Value));
|
||||||
|
|
||||||
|
builder.Append(@"
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: ""Segoe UI"", ""Segoe WP"", ""Helvetica Neue"", 'RobotoRegular', sans-serif;
|
||||||
|
font-size: 14px;}
|
||||||
|
header h1 {
|
||||||
|
font-family: ""Segoe UI Light"", ""Helvetica Neue"", 'RobotoLight', ""Segoe UI"", ""Segoe WP"", sans-serif;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 100;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 0px;}
|
||||||
|
#index {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin: 0 0 20px; }
|
||||||
|
#index th {
|
||||||
|
vertical-align: bottom;
|
||||||
|
padding: 10px 5px 5px 5px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #a0a0a0;
|
||||||
|
text-align: center; }
|
||||||
|
#index td { padding: 3px 10px; }
|
||||||
|
#index th, #index td {
|
||||||
|
border-right: 1px #ddd solid;
|
||||||
|
border-bottom: 1px #ddd solid;
|
||||||
|
border-left: 1px transparent solid;
|
||||||
|
border-top: 1px transparent solid;
|
||||||
|
box-sizing: border-box; }
|
||||||
|
#index th:last-child, #index td:last-child {
|
||||||
|
border-right: 1px transparent solid; }
|
||||||
|
#index td.length, td.modified { text-align:right; }
|
||||||
|
a { color:#1ba1e2;text-decoration:none; }
|
||||||
|
a:hover { color:#13709e;text-decoration:underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id=""main"">");
|
||||||
|
builder.AppendFormat(@"
|
||||||
|
<header><h1>{0} <a href=""/"">/</a>", HtmlEncode(Resources.HtmlDir_IndexOf));
|
||||||
|
|
||||||
|
string cumulativePath = "/";
|
||||||
|
foreach (var segment in requestPath.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
cumulativePath = cumulativePath + segment + "/";
|
||||||
|
builder.AppendFormat(@"<a href=""{0}"">{1}/</a>",
|
||||||
|
HtmlEncode(cumulativePath), HtmlEncode(segment));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendFormat(CultureInfo.CurrentUICulture,
|
||||||
|
@"</h1></header>
|
||||||
|
<table id=""index"" summary=""{0}"">
|
||||||
|
<thead>
|
||||||
|
<tr><th abbr=""{1}"">{1}</th><th abbr=""{2}"">{2}</th><th abbr=""{3}"">{4}</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>",
|
||||||
|
HtmlEncode(Resources.HtmlDir_TableSummary),
|
||||||
|
HtmlEncode(Resources.HtmlDir_Name),
|
||||||
|
HtmlEncode(Resources.HtmlDir_Size),
|
||||||
|
HtmlEncode(Resources.HtmlDir_Modified),
|
||||||
|
HtmlEncode(Resources.HtmlDir_LastModified));
|
||||||
|
|
||||||
|
foreach (var subdir in contents.Where(info => info.IsDirectory))
|
||||||
|
{
|
||||||
|
builder.AppendFormat(@"
|
||||||
|
<tr class=""directory"">
|
||||||
|
<td class=""name""><a href=""{0}/"">{0}/</a></td>
|
||||||
|
<td></td>
|
||||||
|
<td class=""modified"">{1}</td>
|
||||||
|
</tr>",
|
||||||
|
HtmlEncode(subdir.Name),
|
||||||
|
HtmlEncode(subdir.LastModified.ToString(CultureInfo.CurrentCulture)));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in contents.Where(info => !info.IsDirectory))
|
||||||
|
{
|
||||||
|
builder.AppendFormat(@"
|
||||||
|
<tr class=""file"">
|
||||||
|
<td class=""name""><a href=""{0}"">{0}</a></td>
|
||||||
|
<td class=""length"">{1}</td>
|
||||||
|
<td class=""modified"">{2}</td>
|
||||||
|
</tr>",
|
||||||
|
HtmlEncode(file.Name),
|
||||||
|
HtmlEncode(file.Length.ToString("n0", CultureInfo.CurrentCulture)),
|
||||||
|
HtmlEncode(file.LastModified.ToString(CultureInfo.CurrentCulture)));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(@"
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>");
|
||||||
|
string data = builder.ToString();
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||||
|
context.Response.ContentLength = bytes.Length;
|
||||||
|
return context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HtmlEncode(string body)
|
||||||
|
{
|
||||||
|
return WebUtility.HtmlEncode(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.DirectoryFormatters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the view for a directory
|
||||||
|
/// </summary>
|
||||||
|
public interface IDirectoryFormatter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the view for a directory.
|
||||||
|
/// Implementers should properly handle HEAD requests.
|
||||||
|
/// Implementers should set all necessary response headers (e.g. Content-Type, Content-Length, etc.).
|
||||||
|
/// </summary>
|
||||||
|
Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.StaticFiles;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods that combine all of the static file middleware components:
|
||||||
|
/// Default files, directory browsing, send file, and static files
|
||||||
|
/// </summary>
|
||||||
|
public static class FileServerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enable all static file middleware (except directory browsing) for the current request path in the current directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseFileServer(this IBuilder builder)
|
||||||
|
{
|
||||||
|
return UseFileServer(builder, new FileServerOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable all static file middleware on for the current request path in the current directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="enableDirectoryBrowsing">Should directory browsing be enabled?</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseFileServer(this IBuilder builder, bool enableDirectoryBrowsing)
|
||||||
|
{
|
||||||
|
return UseFileServer(builder, new FileServerOptions() { EnableDirectoryBrowsing = enableDirectoryBrowsing });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables all static file middleware (except directory browsing) for the given request path from the directory of the same name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="requestPath">The relative request path and physical path.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseFileServer(this IBuilder builder, string requestPath)
|
||||||
|
{
|
||||||
|
return UseFileServer(builder, new FileServerOptions() { RequestPath = new PathString(requestPath) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable all static file middleware with the given options
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseFileServer(this IBuilder builder, FileServerOptions options)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("options");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.EnableDefaultFiles)
|
||||||
|
{
|
||||||
|
builder = builder.UseDefaultFiles(options.DefaultFilesOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.EnableDirectoryBrowsing)
|
||||||
|
{
|
||||||
|
builder = builder.UseDirectoryBrowser(options.DirectoryBrowserOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.UseSendFileFallback()
|
||||||
|
.UseStaticFiles(options.StaticFileOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for all of the static file middleware components
|
||||||
|
/// </summary>
|
||||||
|
public class FileServerOptions : SharedOptionsBase<FileServerOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a combined options class for all of the static file middleware components.
|
||||||
|
/// </summary>
|
||||||
|
public FileServerOptions()
|
||||||
|
: base(new SharedOptions())
|
||||||
|
{
|
||||||
|
StaticFileOptions = new StaticFileOptions(SharedOptions);
|
||||||
|
DirectoryBrowserOptions = new DirectoryBrowserOptions(SharedOptions);
|
||||||
|
DefaultFilesOptions = new DefaultFilesOptions(SharedOptions);
|
||||||
|
EnableDefaultFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for configuring the StaticFileMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
public StaticFileOptions StaticFileOptions { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for configuring the DirectoryBrowserMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
public DirectoryBrowserOptions DirectoryBrowserOptions { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for configuring the DefaultFilesMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
public DefaultFilesOptions DefaultFilesOptions { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Directory browsing is disabled by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableDirectoryBrowsing { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default files are enabled by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableDefaultFiles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
internal static class Helpers
|
||||||
|
{
|
||||||
|
internal static bool IsGetOrHeadMethod(string method)
|
||||||
|
{
|
||||||
|
return IsGetMethod(method) || IsHeadMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsGetMethod(string method)
|
||||||
|
{
|
||||||
|
return string.Equals("GET", method, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsHeadMethod(string method)
|
||||||
|
{
|
||||||
|
return string.Equals("HEAD", method, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool PathEndsInSlash(PathString path)
|
||||||
|
{
|
||||||
|
return path.Value.EndsWith("/", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath)
|
||||||
|
{
|
||||||
|
var path = context.Request.Path;
|
||||||
|
|
||||||
|
if (forDirectory && !PathEndsInSlash(path))
|
||||||
|
{
|
||||||
|
path += new PathString("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.StartsWithSegments(matchUrl, out subpath))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool TryParseHttpDate(string dateString, out DateTime parsedDate)
|
||||||
|
{
|
||||||
|
return DateTime.TryParseExact(dateString, Constants.HttpDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
||||||
|
{
|
||||||
|
internal static class RangeHelpers
|
||||||
|
{
|
||||||
|
// Examples:
|
||||||
|
// bytes=0-499
|
||||||
|
// bytes=500-
|
||||||
|
// bytes=-500
|
||||||
|
// bytes=0-0,-1
|
||||||
|
// bytes=500-600,601-999
|
||||||
|
// Any individual bad range fails the whole parse and the header should be ignored.
|
||||||
|
internal static bool TryParseRanges(string rangeHeader, out IList<Tuple<long?, long?>> parsedRanges)
|
||||||
|
{
|
||||||
|
parsedRanges = null;
|
||||||
|
if (string.IsNullOrWhiteSpace(rangeHeader)
|
||||||
|
|| !rangeHeader.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] subRanges = rangeHeader.Substring("bytes=".Length).Replace(" ", string.Empty).Split(',');
|
||||||
|
|
||||||
|
List<Tuple<long?, long?>> ranges = new List<Tuple<long?, long?>>();
|
||||||
|
|
||||||
|
for (int i = 0; i < subRanges.Length; i++)
|
||||||
|
{
|
||||||
|
long? first = null, second = null;
|
||||||
|
string subRange = subRanges[i];
|
||||||
|
int dashIndex = subRange.IndexOf('-');
|
||||||
|
if (dashIndex < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (dashIndex == 0)
|
||||||
|
{
|
||||||
|
// -500
|
||||||
|
string remainder = subRange.Substring(1);
|
||||||
|
if (!TryParseLong(remainder, out second))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dashIndex == (subRange.Length - 1))
|
||||||
|
{
|
||||||
|
// 500-
|
||||||
|
string remainder = subRange.Substring(0, subRange.Length - 1);
|
||||||
|
if (!TryParseLong(remainder, out first))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 0-499
|
||||||
|
string firstString = subRange.Substring(0, dashIndex);
|
||||||
|
string secondString = subRange.Substring(dashIndex + 1, subRange.Length - dashIndex - 1);
|
||||||
|
if (!TryParseLong(firstString, out first) || !TryParseLong(secondString, out second)
|
||||||
|
|| first.Value > second.Value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.Add(new Tuple<long?, long?>(first, second));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges.Count > 0)
|
||||||
|
{
|
||||||
|
parsedRanges = ranges;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseLong(string input, out long? result)
|
||||||
|
{
|
||||||
|
int temp;
|
||||||
|
if (!string.IsNullOrWhiteSpace(input)
|
||||||
|
&& int.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out temp))
|
||||||
|
{
|
||||||
|
result = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
result = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14.35.1 Byte Ranges - If a syntactically valid byte-range-set includes at least one byte-range-spec whose
|
||||||
|
// first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec
|
||||||
|
// with a non-zero suffix-length, then the byte-range-set is satisfiable.
|
||||||
|
// Adjusts ranges to be absolute and within bounds.
|
||||||
|
internal static IList<Tuple<long, long>> NormalizeRanges(IList<Tuple<long?, long?>> ranges, long length)
|
||||||
|
{
|
||||||
|
IList<Tuple<long, long>> normalizedRanges = new List<Tuple<long, long>>(ranges.Count);
|
||||||
|
for (int i = 0; i < ranges.Count; i++)
|
||||||
|
{
|
||||||
|
Tuple<long?, long?> range = ranges[i];
|
||||||
|
long? start = range.Item1, end = range.Item2;
|
||||||
|
|
||||||
|
// X-[Y]
|
||||||
|
if (start.HasValue)
|
||||||
|
{
|
||||||
|
if (start.Value >= length)
|
||||||
|
{
|
||||||
|
// Not satisfiable, skip/discard.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!end.HasValue || end.Value >= length)
|
||||||
|
{
|
||||||
|
end = length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// suffix range "-X" e.g. the last X bytes, resolve
|
||||||
|
if (end.Value == 0)
|
||||||
|
{
|
||||||
|
// Not satisfiable, skip/discard.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
long bytes = Math.Min(end.Value, length);
|
||||||
|
start = length - bytes;
|
||||||
|
end = start + bytes - 1;
|
||||||
|
}
|
||||||
|
normalizedRanges.Add(new Tuple<long, long>(start.Value, end.Value));
|
||||||
|
}
|
||||||
|
return normalizedRanges;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options common to several middleware components
|
||||||
|
/// </summary>
|
||||||
|
public class SharedOptions
|
||||||
|
{
|
||||||
|
private PathString _requestPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to all request paths and the current physical directory.
|
||||||
|
/// </summary>
|
||||||
|
public SharedOptions()
|
||||||
|
{
|
||||||
|
RequestPath = PathString.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The request path that maps to static resources
|
||||||
|
/// </summary>
|
||||||
|
public PathString RequestPath
|
||||||
|
{
|
||||||
|
get { return _requestPath; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.HasValue && value.Value.EndsWith("/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Request path must not end in a slash");
|
||||||
|
}
|
||||||
|
_requestPath = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file system used to locate resources
|
||||||
|
/// </summary>
|
||||||
|
public IFileSystem FileSystem { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options common to several middleware components
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the subclass</typeparam>
|
||||||
|
public abstract class SharedOptionsBase<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an new instance of the SharedOptionsBase.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sharedOptions"></param>
|
||||||
|
protected SharedOptionsBase(SharedOptions sharedOptions)
|
||||||
|
{
|
||||||
|
if (sharedOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("sharedOptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedOptions = sharedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options common to several middleware components
|
||||||
|
/// </summary>
|
||||||
|
protected SharedOptions SharedOptions { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative request path that maps to static resources.
|
||||||
|
/// </summary>
|
||||||
|
public PathString RequestPath
|
||||||
|
{
|
||||||
|
get { return SharedOptions.RequestPath; }
|
||||||
|
set { SharedOptions.RequestPath = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file system used to locate resources
|
||||||
|
/// </summary>
|
||||||
|
public IFileSystem FileSystem
|
||||||
|
{
|
||||||
|
get { return SharedOptions.FileSystem; }
|
||||||
|
set { SharedOptions.FileSystem = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("Microsoft.AspNet.StaticFiles")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
|
||||||
|
[assembly: Guid("310c92f5-1719-4616-9ca8-a5788fcb86f8")]
|
||||||
|
[assembly: CLSCompliant(true)]
|
||||||
|
[assembly: NeutralResourcesLanguage("en-US")]
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.34006
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.StaticFiles.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No IContentTypeProvider was specified..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Args_NoContentTypeProvider {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Args_NoContentTypeProvider", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No formatter provided..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Args_NoFormatter {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Args_NoFormatter", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to This server does not support the sendfile.SendAsync extension..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Exception_SendFileNotSupported {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Exception_SendFileNotSupported", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Index of.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlDir_IndexOf {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HtmlDir_IndexOf", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Last Modified.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlDir_LastModified {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HtmlDir_LastModified", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Modified.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlDir_Modified {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HtmlDir_Modified", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Name.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlDir_Name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HtmlDir_Name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Size.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlDir_Size {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HtmlDir_Size", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The list of files in the given directory. Column headers are listed in the first row..
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlDir_TableSummary {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HtmlDir_TableSummary", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Args_NoContentTypeProvider" xml:space="preserve">
|
||||||
|
<value>No IContentTypeProvider was specified.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Args_NoFormatter" xml:space="preserve">
|
||||||
|
<value>No formatter provided.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Exception_SendFileNotSupported" xml:space="preserve">
|
||||||
|
<value>This server does not support the sendfile.SendAsync extension.</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlDir_IndexOf" xml:space="preserve">
|
||||||
|
<value>Index of</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlDir_LastModified" xml:space="preserve">
|
||||||
|
<value>Last Modified</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlDir_Modified" xml:space="preserve">
|
||||||
|
<value>Modified</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlDir_Name" xml:space="preserve">
|
||||||
|
<value>Name</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlDir_Size" xml:space="preserve">
|
||||||
|
<value>Size</value>
|
||||||
|
</data>
|
||||||
|
<data name="HtmlDir_TableSummary" xml:space="preserve">
|
||||||
|
<value>The list of files in the given directory. Column headers are listed in the first row.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the SendFileMiddleware
|
||||||
|
/// </summary>
|
||||||
|
public static class SendFileExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provide a SendFile fallback if another component does not.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseSendFileFallback(this IBuilder builder)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("builder");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO builder.GetItem(typeof(ISendFile))
|
||||||
|
|
||||||
|
// Check for advertised support
|
||||||
|
if (IsSendFileSupported(builder.Properties))
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, insert a fallback SendFile middleware and advertise support
|
||||||
|
SetSendFileCapability(builder.Properties);
|
||||||
|
*/
|
||||||
|
return builder.Use(next => new SendFileMiddleware(next).Invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSendFileSupported(IDictionary<string, object> properties)
|
||||||
|
{
|
||||||
|
object obj;
|
||||||
|
if (properties.TryGetValue(Constants.ServerCapabilitiesKey, out obj))
|
||||||
|
{
|
||||||
|
var capabilities = (IDictionary<string, object>)obj;
|
||||||
|
if (capabilities.TryGetValue(Constants.SendFileVersionKey, out obj)
|
||||||
|
&& Constants.SendFileVersion.Equals((string)obj, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetSendFileCapability(IDictionary<string, object> properties)
|
||||||
|
{
|
||||||
|
object obj;
|
||||||
|
if (properties.TryGetValue(Constants.ServerCapabilitiesKey, out obj))
|
||||||
|
{
|
||||||
|
var capabilities = (IDictionary<string, object>)obj;
|
||||||
|
capabilities[Constants.SendFileVersionKey] = Constants.SendFileVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This middleware provides an efficient fallback mechanism for sending static files
|
||||||
|
/// when the server does not natively support such a feature.
|
||||||
|
/// The caller is responsible for setting all headers in advance.
|
||||||
|
/// The caller is responsible for performing the correct impersonation to give access to the file.
|
||||||
|
/// </summary>
|
||||||
|
public class SendFileMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the SendFileMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The next middleware in the pipeline.</param>
|
||||||
|
public SendFileMiddleware(RequestDelegate next)
|
||||||
|
{
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("next");
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
// Check if there is a SendFile feature already present
|
||||||
|
if (context.GetFeature<IHttpSendFile>() == null)
|
||||||
|
{
|
||||||
|
context.SetFeature<IHttpSendFile>(new SendFileWrapper(context.Response.Body));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SendFileWrapper : IHttpSendFile
|
||||||
|
{
|
||||||
|
private readonly Stream _output;
|
||||||
|
|
||||||
|
internal SendFileWrapper(Stream output)
|
||||||
|
{
|
||||||
|
_output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not safe for overlapped writes.
|
||||||
|
public async Task SendFileAsync(string fileName, long offset, long? length, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("fileName");
|
||||||
|
}
|
||||||
|
if (!File.Exists(fileName))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(string.Empty, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(fileName);
|
||||||
|
if (offset < 0 || offset > fileInfo.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length.HasValue &&
|
||||||
|
(length.Value < 0 || length.Value > fileInfo.Length - offset))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("length", length, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 64,
|
||||||
|
#if NET45
|
||||||
|
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||||
|
#else
|
||||||
|
useAsync: true);
|
||||||
|
#endif
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fileStream.Seek(offset, SeekOrigin.Begin);
|
||||||
|
await StreamCopyOperation.CopyToAsync(fileStream, _output, length, cancel);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
fileStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.StaticFiles;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
|
||||||
|
namespace Microsoft.Owin
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extensions for HttpResponse exposing the SendFile extension.
|
||||||
|
/// </summary>
|
||||||
|
public static class SendFileResponseExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the SendFile extension is supported.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <returns>True if sendfile.SendAsync is defined in the environment.</returns>
|
||||||
|
public static bool SupportsSendFile(this HttpResponse response)
|
||||||
|
{
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("response");
|
||||||
|
}
|
||||||
|
return response.HttpContext.GetFeature<IHttpSendFile>() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the given file using the SendFile extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task SendFileAsync(this HttpResponse response, string fileName)
|
||||||
|
{
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("response");
|
||||||
|
}
|
||||||
|
return response.SendFileAsync(fileName, 0, null, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the given file using the SendFile extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="fileName">The full or relative path to the file.</param>
|
||||||
|
/// <param name="offset">The offset in the file.</param>
|
||||||
|
/// <param name="count">The number of types to send, or null to send the remainder of the file.</param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task SendFileAsync(this HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("response");
|
||||||
|
}
|
||||||
|
IHttpSendFile sendFile = response.HttpContext.GetFeature<IHttpSendFile>();
|
||||||
|
if (sendFile == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(Resources.Exception_SendFileNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendFile.SendFileAsync(fileName, offset, count, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,393 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
internal struct StaticFileContext
|
||||||
|
{
|
||||||
|
private readonly HttpContext _context;
|
||||||
|
private readonly StaticFileOptions _options;
|
||||||
|
private readonly PathString _matchUrl;
|
||||||
|
private readonly HttpRequest _request;
|
||||||
|
private readonly HttpResponse _response;
|
||||||
|
private string _method;
|
||||||
|
private bool _isGet;
|
||||||
|
private bool _isHead;
|
||||||
|
private PathString _subPath;
|
||||||
|
private string _contentType;
|
||||||
|
private IFileInfo _fileInfo;
|
||||||
|
private long _length;
|
||||||
|
private DateTime _lastModified;
|
||||||
|
private string _lastModifiedString;
|
||||||
|
private string _etag;
|
||||||
|
private string _etagQuoted;
|
||||||
|
|
||||||
|
private PreconditionState _ifMatchState;
|
||||||
|
private PreconditionState _ifNoneMatchState;
|
||||||
|
private PreconditionState _ifModifiedSinceState;
|
||||||
|
private PreconditionState _ifUnmodifiedSinceState;
|
||||||
|
|
||||||
|
private IList<Tuple<long, long>> _ranges;
|
||||||
|
|
||||||
|
public StaticFileContext(HttpContext context, StaticFileOptions options, PathString matchUrl)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_options = options;
|
||||||
|
_matchUrl = matchUrl;
|
||||||
|
_request = context.Request;
|
||||||
|
_response = context.Response;
|
||||||
|
|
||||||
|
_method = null;
|
||||||
|
_isGet = false;
|
||||||
|
_isHead = false;
|
||||||
|
_subPath = PathString.Empty;
|
||||||
|
_contentType = null;
|
||||||
|
_fileInfo = null;
|
||||||
|
_length = 0;
|
||||||
|
_lastModified = new DateTime();
|
||||||
|
_etag = null;
|
||||||
|
_etagQuoted = null;
|
||||||
|
_lastModifiedString = null;
|
||||||
|
_ifMatchState = PreconditionState.Unspecified;
|
||||||
|
_ifNoneMatchState = PreconditionState.Unspecified;
|
||||||
|
_ifModifiedSinceState = PreconditionState.Unspecified;
|
||||||
|
_ifUnmodifiedSinceState = PreconditionState.Unspecified;
|
||||||
|
_ranges = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum PreconditionState
|
||||||
|
{
|
||||||
|
Unspecified,
|
||||||
|
NotModified,
|
||||||
|
ShouldProcess,
|
||||||
|
PreconditionFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHeadMethod
|
||||||
|
{
|
||||||
|
get { return _isHead; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRangeRequest
|
||||||
|
{
|
||||||
|
get { return _ranges != null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateMethod()
|
||||||
|
{
|
||||||
|
_method = _request.Method;
|
||||||
|
_isGet = Helpers.IsGetMethod(_method);
|
||||||
|
_isHead = Helpers.IsHeadMethod(_method);
|
||||||
|
return _isGet || _isHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the URL matches any expected paths
|
||||||
|
public bool ValidatePath()
|
||||||
|
{
|
||||||
|
return Helpers.TryMatchPath(_context, _matchUrl, forDirectory: false, subpath: out _subPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool LookupContentType()
|
||||||
|
{
|
||||||
|
if (_options.ContentTypeProvider.TryGetContentType(_subPath.Value, out _contentType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_options.ServeUnknownFileTypes)
|
||||||
|
{
|
||||||
|
_contentType = _options.DefaultContentType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool LookupFileInfo()
|
||||||
|
{
|
||||||
|
bool found = _options.FileSystem.TryGetFileInfo(_subPath.Value, out _fileInfo);
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
_length = _fileInfo.Length;
|
||||||
|
|
||||||
|
DateTime last = _fileInfo.LastModified;
|
||||||
|
// Truncate to the second.
|
||||||
|
_lastModified = new DateTime(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Kind);
|
||||||
|
_lastModifiedString = _lastModified.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
long etagHash = _lastModified.ToFileTimeUtc() ^ _length;
|
||||||
|
_etag = Convert.ToString(etagHash, 16);
|
||||||
|
_etagQuoted = '\"' + _etag + '\"';
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ComprehendRequestHeaders()
|
||||||
|
{
|
||||||
|
ComputeIfMatch();
|
||||||
|
|
||||||
|
ComputeIfModifiedSince();
|
||||||
|
|
||||||
|
ComputeRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeIfMatch()
|
||||||
|
{
|
||||||
|
// 14.24 If-Match
|
||||||
|
IList<string> ifMatch = _request.Headers.GetCommaSeparatedValues(Constants.IfMatch); // Removes quotes
|
||||||
|
if (ifMatch != null)
|
||||||
|
{
|
||||||
|
_ifMatchState = PreconditionState.PreconditionFailed;
|
||||||
|
foreach (var segment in ifMatch)
|
||||||
|
{
|
||||||
|
if (segment.Equals("*", StringComparison.Ordinal)
|
||||||
|
|| segment.Equals(_etag, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_ifMatchState = PreconditionState.ShouldProcess;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14.26 If-None-Match
|
||||||
|
IList<string> ifNoneMatch = _request.Headers.GetCommaSeparatedValues(Constants.IfNoneMatch);
|
||||||
|
if (ifNoneMatch != null)
|
||||||
|
{
|
||||||
|
_ifNoneMatchState = PreconditionState.ShouldProcess;
|
||||||
|
foreach (var segment in ifNoneMatch)
|
||||||
|
{
|
||||||
|
if (segment.Equals("*", StringComparison.Ordinal)
|
||||||
|
|| segment.Equals(_etag, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_ifNoneMatchState = PreconditionState.NotModified;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeIfModifiedSince()
|
||||||
|
{
|
||||||
|
// 14.25 If-Modified-Since
|
||||||
|
string ifModifiedSinceString = _request.Headers.Get(Constants.IfModifiedSince);
|
||||||
|
DateTime ifModifiedSince;
|
||||||
|
if (Helpers.TryParseHttpDate(ifModifiedSinceString, out ifModifiedSince))
|
||||||
|
{
|
||||||
|
bool modified = ifModifiedSince < _lastModified;
|
||||||
|
_ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14.28 If-Unmodified-Since
|
||||||
|
string ifUnmodifiedSinceString = _request.Headers.Get(Constants.IfUnmodifiedSince);
|
||||||
|
DateTime ifUnmodifiedSince;
|
||||||
|
if (Helpers.TryParseHttpDate(ifUnmodifiedSinceString, out ifUnmodifiedSince))
|
||||||
|
{
|
||||||
|
bool unmodified = ifUnmodifiedSince >= _lastModified;
|
||||||
|
_ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeRange()
|
||||||
|
{
|
||||||
|
// 14.35 Range
|
||||||
|
// http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-24
|
||||||
|
|
||||||
|
// A server MUST ignore a Range header field received with a request method other
|
||||||
|
// than GET.
|
||||||
|
if (!_isGet)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string rangeHeader = _request.Headers.Get(Constants.Range);
|
||||||
|
IList<Tuple<long?, long?>> ranges;
|
||||||
|
if (!RangeHelpers.TryParseRanges(rangeHeader, out ranges))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges.Count > 1)
|
||||||
|
{
|
||||||
|
// multiple range headers not yet supported
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14.27 If-Range
|
||||||
|
string ifRangeHeader = _request.Headers.Get(Constants.IfRange);
|
||||||
|
if (!string.IsNullOrWhiteSpace(ifRangeHeader))
|
||||||
|
{
|
||||||
|
// If the validator given in the If-Range header field matches the
|
||||||
|
// current validator for the selected representation of the target
|
||||||
|
// resource, then the server SHOULD process the Range header field as
|
||||||
|
// requested. If the validator does not match, the server MUST ignore
|
||||||
|
// the Range header field.
|
||||||
|
DateTime ifRangeLastModified;
|
||||||
|
bool ignoreRangeHeader = false;
|
||||||
|
if (Helpers.TryParseHttpDate(ifRangeHeader, out ifRangeLastModified))
|
||||||
|
{
|
||||||
|
if (_lastModified > ifRangeLastModified)
|
||||||
|
{
|
||||||
|
ignoreRangeHeader = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_etagQuoted.Equals(ifRangeHeader))
|
||||||
|
{
|
||||||
|
ignoreRangeHeader = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ignoreRangeHeader)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ranges = RangeHelpers.NormalizeRanges(ranges, _length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyResponseHeaders(int statusCode)
|
||||||
|
{
|
||||||
|
_response.StatusCode = statusCode;
|
||||||
|
if (statusCode < 400)
|
||||||
|
{
|
||||||
|
// these headers are returned for 200, 206, and 304
|
||||||
|
// they are not returned for 412 and 416
|
||||||
|
if (!string.IsNullOrEmpty(_contentType))
|
||||||
|
{
|
||||||
|
_response.ContentType = _contentType;
|
||||||
|
}
|
||||||
|
_response.Headers.Set(Constants.LastModified, _lastModifiedString);
|
||||||
|
_response.Headers.Set(Constants.ETag, _etagQuoted);
|
||||||
|
}
|
||||||
|
if (statusCode == Constants.Status200Ok)
|
||||||
|
{
|
||||||
|
// this header is only returned here for 200
|
||||||
|
// it already set to the returned range for 206
|
||||||
|
// it is not returned for 304, 412, and 416
|
||||||
|
_response.ContentLength = _length;
|
||||||
|
}
|
||||||
|
_options.OnPrepareResponse(new StaticFileResponseContext()
|
||||||
|
{
|
||||||
|
Context = _context,
|
||||||
|
File = _fileInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreconditionState GetPreconditionState()
|
||||||
|
{
|
||||||
|
return GetMaxPreconditionState(_ifMatchState, _ifNoneMatchState,
|
||||||
|
_ifModifiedSinceState, _ifUnmodifiedSinceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states)
|
||||||
|
{
|
||||||
|
PreconditionState max = PreconditionState.Unspecified;
|
||||||
|
for (int i = 0; i < states.Length; i++)
|
||||||
|
{
|
||||||
|
if (states[i] > max)
|
||||||
|
{
|
||||||
|
max = states[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendStatusAsync(int statusCode)
|
||||||
|
{
|
||||||
|
ApplyResponseHeaders(statusCode);
|
||||||
|
|
||||||
|
return Constants.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendAsync()
|
||||||
|
{
|
||||||
|
ApplyResponseHeaders(Constants.Status200Ok);
|
||||||
|
|
||||||
|
string physicalPath = _fileInfo.PhysicalPath;
|
||||||
|
IHttpSendFile sendFile = _context.GetFeature<IHttpSendFile>();
|
||||||
|
if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
|
||||||
|
{
|
||||||
|
await sendFile.SendFileAsync(physicalPath, 0, _length, _request.CallCanceled);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream readStream = _fileInfo.CreateReadStream();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await StreamCopyOperation.CopyToAsync(readStream, _response.Body, _length, _request.CallCanceled);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
readStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there is only a single range the bytes are sent directly in the body.
|
||||||
|
internal async Task SendRangeAsync()
|
||||||
|
{
|
||||||
|
bool rangeNotSatisfiable = false;
|
||||||
|
if (_ranges.Count == 0)
|
||||||
|
{
|
||||||
|
rangeNotSatisfiable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeNotSatisfiable)
|
||||||
|
{
|
||||||
|
// 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
|
||||||
|
// SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
|
||||||
|
// the current length of the selected resource. e.g. */length
|
||||||
|
_response.Headers[Constants.ContentRange] = "bytes */" + _length.ToString(CultureInfo.InvariantCulture);
|
||||||
|
ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-range is not supported.
|
||||||
|
Debug.Assert(_ranges.Count == 1);
|
||||||
|
|
||||||
|
long start, length;
|
||||||
|
_response.Headers[Constants.ContentRange] = ComputeContentRange(_ranges[0], out start, out length);
|
||||||
|
_response.ContentLength = length;
|
||||||
|
ApplyResponseHeaders(Constants.Status206PartialContent);
|
||||||
|
|
||||||
|
string physicalPath = _fileInfo.PhysicalPath;
|
||||||
|
IHttpSendFile sendFile = _context.GetFeature<IHttpSendFile>();
|
||||||
|
if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
|
||||||
|
{
|
||||||
|
await sendFile.SendFileAsync(physicalPath, start, length, _request.CallCanceled);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream readStream = _fileInfo.CreateReadStream();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
readStream.Seek(start, SeekOrigin.Begin); // TODO: What if !CanSeek?
|
||||||
|
await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _request.CallCanceled);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
readStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This assumes ranges have been normalized to absolute byte offsets.
|
||||||
|
private string ComputeContentRange(Tuple<long, long> range, out long start, out long length)
|
||||||
|
{
|
||||||
|
start = range.Item1;
|
||||||
|
long end = range.Item2;
|
||||||
|
length = end - start + 1;
|
||||||
|
return string.Format(CultureInfo.InvariantCulture, "bytes {0}-{1}/{2}", start, end, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.StaticFiles;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the StaticFileMiddleware
|
||||||
|
/// </summary>
|
||||||
|
public static class StaticFileExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables static file serving for the current request path from the current directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseStaticFiles(this IBuilder builder)
|
||||||
|
{
|
||||||
|
return UseStaticFiles(builder, new StaticFileOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables static file serving for the given request path from the directory of the same name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="requestPath">The relative request path and physical path.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseStaticFiles(this IBuilder builder, string requestPath)
|
||||||
|
{
|
||||||
|
return UseStaticFiles(builder, new StaticFileOptions() { RequestPath = new PathString(requestPath) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables static file serving with the given options
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IBuilder UseStaticFiles(this IBuilder builder, StaticFileOptions options)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("builder");
|
||||||
|
}
|
||||||
|
return builder.Use(next => new StaticFileMiddleware(next, options).Invoke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables serving static files for a given request path
|
||||||
|
/// </summary>
|
||||||
|
public class StaticFileMiddleware
|
||||||
|
{
|
||||||
|
private readonly StaticFileOptions _options;
|
||||||
|
private readonly PathString _matchUrl;
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the StaticFileMiddleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The next middleware in the pipeline.</param>
|
||||||
|
/// <param name="options">The configuration options.</param>
|
||||||
|
public StaticFileMiddleware(RequestDelegate next, StaticFileOptions options)
|
||||||
|
{
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("next");
|
||||||
|
}
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("options");
|
||||||
|
}
|
||||||
|
if (options.ContentTypeProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Args_NoContentTypeProvider);
|
||||||
|
}
|
||||||
|
if (options.FileSystem == null)
|
||||||
|
{
|
||||||
|
options.FileSystem = new PhysicalFileSystem("." + options.RequestPath.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
_options = options;
|
||||||
|
_matchUrl = options.RequestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a request to determine if it matches a known file, and if so, serves it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
var fileContext = new StaticFileContext(context, _options, _matchUrl);
|
||||||
|
if (fileContext.ValidateMethod()
|
||||||
|
&& fileContext.ValidatePath()
|
||||||
|
&& fileContext.LookupContentType()
|
||||||
|
&& fileContext.LookupFileInfo())
|
||||||
|
{
|
||||||
|
fileContext.ComprehendRequestHeaders();
|
||||||
|
|
||||||
|
switch (fileContext.GetPreconditionState())
|
||||||
|
{
|
||||||
|
case StaticFileContext.PreconditionState.Unspecified:
|
||||||
|
case StaticFileContext.PreconditionState.ShouldProcess:
|
||||||
|
if (fileContext.IsHeadMethod)
|
||||||
|
{
|
||||||
|
return fileContext.SendStatusAsync(Constants.Status200Ok);
|
||||||
|
}
|
||||||
|
if (fileContext.IsRangeRequest)
|
||||||
|
{
|
||||||
|
return fileContext.SendRangeAsync();
|
||||||
|
}
|
||||||
|
return fileContext.SendAsync();
|
||||||
|
|
||||||
|
case StaticFileContext.PreconditionState.NotModified:
|
||||||
|
return fileContext.SendStatusAsync(Constants.Status304NotModified);
|
||||||
|
|
||||||
|
case StaticFileContext.PreconditionState.PreconditionFailed:
|
||||||
|
return fileContext.SendStatusAsync(Constants.Status412PreconditionFailed);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException(fileContext.GetPreconditionState().ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.StaticFiles.ContentTypes;
|
||||||
|
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for serving static files
|
||||||
|
/// </summary>
|
||||||
|
public class StaticFileOptions : SharedOptionsBase<StaticFileOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to all request paths in the current physical directory
|
||||||
|
/// </summary>
|
||||||
|
public StaticFileOptions() : this(new SharedOptions())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to all request paths in the current physical directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sharedOptions"></param>
|
||||||
|
public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions)
|
||||||
|
{
|
||||||
|
ContentTypeProvider = new FileExtensionContentTypeProvider();
|
||||||
|
|
||||||
|
OnPrepareResponse = _ => { };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to map files to content-types.
|
||||||
|
/// </summary>
|
||||||
|
public IContentTypeProvider ContentTypeProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default content type for a request if the ContentTypeProvider cannot determine one.
|
||||||
|
/// None is provided by default, so the client must determine the format themselves.
|
||||||
|
/// http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7
|
||||||
|
/// </summary>
|
||||||
|
public string DefaultContentType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the file is not a recognized content-type should it be served?
|
||||||
|
/// Default: false.
|
||||||
|
/// </summary>
|
||||||
|
public bool ServeUnknownFileTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called after the status code and headers have been set, but before the body has been written.
|
||||||
|
/// This can be used to add or change the response headers.
|
||||||
|
/// </summary>
|
||||||
|
public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.Owin.FileSystems;
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information about the request and the file that will be served in response.
|
||||||
|
/// </summary>
|
||||||
|
public class StaticFileResponseContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The request and response information.
|
||||||
|
/// </summary>
|
||||||
|
public HttpContext Context { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file to be served.
|
||||||
|
/// </summary>
|
||||||
|
public IFileInfo File { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
{
|
||||||
|
// FYI: In most cases the source will be a FileStream and the destination will be to the network.
|
||||||
|
internal static class StreamCopyOperation
|
||||||
|
{
|
||||||
|
private const int DefaultBufferSize = 1024 * 16;
|
||||||
|
|
||||||
|
internal static async Task CopyToAsync(Stream source, Stream destination, long? length, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
long? bytesRemaining = length;
|
||||||
|
byte[] buffer = new byte[DefaultBufferSize];
|
||||||
|
|
||||||
|
Contract.Assert(source != null);
|
||||||
|
Contract.Assert(destination != null);
|
||||||
|
Contract.Assert(!bytesRemaining.HasValue || bytesRemaining.Value >= 0);
|
||||||
|
Contract.Assert(buffer != null);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// The natural end of the range.
|
||||||
|
if (bytesRemaining.HasValue && bytesRemaining.Value <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
int readLength = buffer.Length;
|
||||||
|
if (bytesRemaining.HasValue)
|
||||||
|
{
|
||||||
|
readLength = (int)Math.Min(bytesRemaining.Value, (long)readLength);
|
||||||
|
}
|
||||||
|
int count = await source.ReadAsync(buffer, 0, readLength, cancel);
|
||||||
|
|
||||||
|
if (bytesRemaining.HasValue)
|
||||||
|
{
|
||||||
|
bytesRemaining -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of the source stream.
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
await destination.WriteAsync(buffer, 0, count, cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"version": "0.1-alpha-*",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Abstractions" : "0.1-alpha-*",
|
||||||
|
"Microsoft.AspNet.HttpFeature" : "0.1-alpha-*",
|
||||||
|
"Microsoft.AspNet.FileSystems" : "0.1-alpha-*"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"net45": {
|
||||||
|
"dependencies": {
|
||||||
|
"Owin" : "1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"k10" : { }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue