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