Initial static files port.

This commit is contained in:
Chris Ross 2014-02-06 15:30:21 -08:00
commit 86b1ac8f39
41 changed files with 2940 additions and 0 deletions

50
.gitattributes vendored Normal file
View File

@ -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

22
.gitignore vendored Normal file
View File

@ -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

View File

@ -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

13
NuGet.Config Normal file
View File

@ -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>

23
build.cmd Normal file
View File

@ -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 %*

3
global.json Normal file
View File

@ -0,0 +1,3 @@
{
"sources": ["src"]
}

7
makefile.shade Normal file
View File

@ -0,0 +1,7 @@
var VERSION='0.1'
var FULL_VERSION='0.1'
var AUTHORS='Microsoft'
use-standard-lifecycle
k-standard-goals

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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" : { }
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<Words>
<Recognized>
<Word>Owin</Word>
</Recognized>
</Words>
</Dictionary>

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}
}

View File

@ -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")]

View File

@ -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);
}
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}
}

View File

@ -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" : { }
}
}