Added download capability for files.

This commit is contained in:
Jimmy Campbell 2016-11-23 12:25:50 -08:00
Родитель 29b3a2591f
Коммит 112032a8be
37 изменённых файлов: 1043 добавлений и 319 удалений

Просмотреть файл

@ -4,8 +4,6 @@
namespace Microsoft.IIS.Administration.Core
{
using System;
public static class Environment
{
public static IAdminHost Host
@ -19,6 +17,5 @@ namespace Microsoft.IIS.Administration.Core
get;
set;
}
}
}

Просмотреть файл

@ -11,8 +11,14 @@ namespace Microsoft.IIS.Administration.Core.Http {
public const string ContentLength = "Content-Length";
public const string ContentRange = "Content-Range";
public const string ContentType = "Content-Type";
public const string Date = "Date";
public const string ETag = "ETag";
public const string IfMatch = "If-Match";
public const string IfModifiedSince = "If-Modified-Since";
public const string IfNoneMatch = "If-None-Match";
public const string IfRange = "If-Range";
public const string IfUnmodifiedSince = "If-Unmodified-Since";
public const string Location = "Location";
public const string Range = "Range";
public const string Total_Count = "X-Total-Count";
public const string Access_Token = "Access-Token";

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Core
{
using Extensions.DependencyInjection;
public interface IServiceCollectionAccessor {
void Use(IServiceCollection services);
}
}

Просмотреть файл

@ -5,11 +5,10 @@
namespace Microsoft.IIS.Administration.Files
{
using Core;
using Extensions.Configuration;
using System;
using System.IO;
class AccessControl
class AccessControl : IAccessControl
{
private FileOptions _options;
@ -17,8 +16,7 @@ namespace Microsoft.IIS.Administration.Files
{
get {
if (_options == null) {
var options = FileOptions.EmptyOptions();
ConfigurationBinder.Bind(ConfigurationHelper.Configuration.GetSection("files"), options);
FileOptions options = FileOptions.FromConfiguration(ConfigurationHelper.Configuration);
for (var i = 0; i < options.Allowed_Roots.Count; i++) {
options.Allowed_Roots[i].Path = System.Environment.ExpandEnvironmentVariables(options.Allowed_Roots[i].Path);
@ -38,18 +36,28 @@ namespace Microsoft.IIS.Administration.Files
public bool IsAccessAllowed(string path, FileAccess fileAccess)
{
if (Options.WildCardRoot != null && (Options.WildCardRoot.Read_Only == false || fileAccess == FileAccess.Read)) {
return true;
var absolutePath = Path.GetFullPath(System.Environment.ExpandEnvironmentVariables(path));
//
// Path must be absolute with no environment variables
if (!absolutePath.Equals(path)) {
return false;
}
path = Path.GetFullPath(System.Environment.ExpandEnvironmentVariables(path));
foreach (var root in _options.Allowed_Roots) {
if (!(root.Read_Only && fileAccess != FileAccess.Read) && path.StartsWith(root.Path, StringComparison.OrdinalIgnoreCase)) {
return true;
//
// Best match
foreach (var root in Options.Allowed_Roots) {
if (absolutePath.StartsWith(root.Path, StringComparison.OrdinalIgnoreCase)) {
return !(root.Read_Only && fileAccess != FileAccess.Read);
}
}
//
// Fall back to wildcard
if (Options.WildCardRoot != null && (Options.WildCardRoot.Read_Only == false || fileAccess == FileAccess.Read)) {
return true;
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using Core;
using System;
public class Defines
{
private const string DOWNLOADS_ENDPOINT = "downloads";
public static readonly string DOWNLOAD_PATH = $"{DOWNLOADS_ENDPOINT}";
public static readonly ResDef DownloadResource = new ResDef("download", new Guid("{9DAF09F0-197B-4164-81D5-B6A25154883A}"), DOWNLOADS_ENDPOINT);
public const string DOWNLOAD_IDENTIFIER = "dl.id";
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using Core.Utils;
using System.Security.Cryptography;
public class Download
{
internal Download()
{
var bytes = new byte[32];
using (var rng = RandomNumberGenerator.Create()) {
rng.GetBytes(bytes);
this.Id = Base64.Encode(bytes);
}
}
public string Id { get; private set; }
public string PhysicalPath { get; set; }
public string Href {
get {
return $"/{Defines.DOWNLOAD_PATH}/{Id}";
}
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
class DownloadService : IDownloadService
{
private const int DEFAULT_DOWNLOAD_TIMEOUT = 5000; // milliseconds
private Dictionary<string, Download> _downloads = new Dictionary<string, Download>();
private IMemoryCache _cache;
public DownloadService(IMemoryCache cache)
{
if (cache == null) {
throw new ArgumentNullException(nameof(cache));
}
_cache = cache;
}
public Download Create(string physicalPath, int? timeout = null)
{
if (physicalPath == null) {
throw new ArgumentNullException(nameof(physicalPath));
}
var dl = new Download()
{
PhysicalPath = physicalPath
};
_cache.Set(dl.Id, dl, TimeSpan.FromMilliseconds(timeout ?? DEFAULT_DOWNLOAD_TIMEOUT));
return dl;
}
public void Remove(string id)
{
_downloads.Remove(id);
}
public Download Get(string id)
{
Download dl = null;
_cache.TryGetValue(id, out dl);
return dl;
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using AspNetCore.Mvc;
using Core.Http;
using System.Threading.Tasks;
public class DownloadsController : ApiBaseController
{
private IFileProvider _fileProvider;
private IDownloadService _downloadService;
public DownloadsController(IDownloadService service)
{
_fileProvider = FileProvider.Default;
_downloadService = service;
}
[HttpHead]
public IActionResult Head(string id)
{
var dl = _downloadService.Get(id);
if (dl == null) {
return NotFound();
}
return Context.GetFileContentHeaders(_fileProvider, _fileProvider.GetFileInfo(dl.PhysicalPath));
}
[HttpGet]
public async Task<IActionResult> Get(string id)
{
var dl = _downloadService.Get(id);
if (dl == null) {
return NotFound();
}
return await Context.GetFileContentAsync(_fileProvider, _fileProvider.GetFileInfo(dl.PhysicalPath));
}
[HttpDelete]
public IActionResult Delete(string id)
{
_downloadService.Remove(id);
return new NoContentResult();
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
public interface IDownloadService
{
Download Create(string physicalPath, int? timeout = null);
void Remove(string id);
Download Get(string id);
}
}

Просмотреть файл

@ -0,0 +1,196 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using Core;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
public class FileProvider : IFileProvider
{
private const string PATH_UNLISTED = "Access Denied";
private const string PATH_IS_READ_ONLY = "Read-Only";
private static IFileProvider _Default = new FileProvider(new AccessControl());
private IAccessControl _accessControl;
public FileProvider(IAccessControl accessControl)
{
if (accessControl == null) {
throw new ArgumentNullException(nameof(accessControl));
}
_accessControl = accessControl;
}
public static IFileProvider Default {
get {
return _Default;
}
}
public string GetName(string path)
{
var info = new FileInfo(path);
return info.Name;
}
public string GetParentPath(string path)
{
var info = new FileInfo(path);
return info.Directory?.FullName;
}
public Stream GetFile(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
{
RequestAccess(path, fileAccess);
return PerformIO(p => new FileStream(p, fileMode, fileAccess, fileShare), path);
}
public FileInfo GetFileInfo(string path)
{
RequestAccess(path, FileAccess.Read);
return PerformIO(p => new FileInfo(p), path);
}
public FileVersionInfo GetFileVersionInfo(string path)
{
RequestAccess(path, FileAccess.Read);
return PerformIO(p => FileVersionInfo.GetVersionInfo(p), path);
}
public DirectoryInfo GetDirectoryInfo(string path)
{
RequestAccess(path, FileAccess.Read);
return new DirectoryInfo(path);
}
public async Task CopyFile(string sourcePath, string destPath, bool copyMetadata = false)
{
RequestAccess(sourcePath, FileAccess.Read);
RequestAccess(destPath, FileAccess.ReadWrite);
using (var srcStream = GetFile(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
using (var destStream = GetFile(destPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read)) {
await srcStream.CopyToAsync(destStream);
}
}
if (copyMetadata) {
var sourceFileInfo = new FileInfo(sourcePath);
var destFileInfo = new FileInfo(destPath);
destFileInfo.CreationTime = sourceFileInfo.CreationTime;
destFileInfo.CreationTimeUtc = sourceFileInfo.CreationTimeUtc;
}
}
public void MoveFile(string sourcePath, string destPath)
{
RequestAccess(sourcePath, FileAccess.ReadWrite);
RequestAccess(destPath, FileAccess.ReadWrite);
PerformIO(p => File.Move(sourcePath, destPath), null);
}
public void MoveDirectory(string sourcePath, string destPath)
{
RequestAccess(sourcePath, FileAccess.ReadWrite);
RequestAccess(destPath, FileAccess.ReadWrite);
PerformIO(p => Directory.Move(sourcePath, destPath), null);
}
public void DeleteFile(string path)
{
RequestAccess(path, FileAccess.ReadWrite);
PerformIO(p => File.Delete(p), path);
}
public void DeleteDirectory(string path)
{
RequestAccess(path, FileAccess.ReadWrite);
PerformIO(p => Directory.Delete(p, true), path);
}
public FileInfo CreateFile(string path)
{
RequestAccess(path, FileAccess.ReadWrite);
return PerformIO(p => {
File.Create(p).Dispose();
return new FileInfo(p);
}, path);
}
public DirectoryInfo CreateDirectory(string path)
{
RequestAccess(path, FileAccess.ReadWrite);
return PerformIO(p => Directory.CreateDirectory(p), path);
}
public bool FileExists(string path)
{
RequestAccess(path, FileAccess.Read);
return PerformIO(p => File.Exists(p), path);
}
public bool DirectoryExists(string path)
{
RequestAccess(path, FileAccess.Read);
return PerformIO(p => Directory.Exists(p), path);
}
private void RequestAccess(string path, FileAccess fileAccess)
{
if (!_accessControl.IsAccessAllowed(path, fileAccess)) {
if (fileAccess != FileAccess.Read && _accessControl.IsAccessAllowed(path, FileAccess.Read)) {
throw new ForbiddenPathException(path, PATH_IS_READ_ONLY);
}
throw new ForbiddenPathException(path);
}
}
private void PerformIO(Action<string> action, string path)
{
PerformIO<object>(p => {
action(p);
return null;
}, path);
}
private T PerformIO<T>(Func<string, T> func, string path)
{
try {
return func(path);
}
catch (IOException e) {
if (e.HResult == IOErrors.FileInUse) {
throw new LockedException(path);
}
throw;
}
catch (UnauthorizedAccessException) {
throw new UnauthorizedArgumentException(path);
}
}
}
}

Просмотреть файл

@ -1,168 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using Core;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
public class FileService
{
private static AccessControl _accessControl = new AccessControl();
public string GetName(string path)
{
var info = new FileInfo(path);
return info.Name;
}
public string GetParentPath(string path)
{
var info = new FileInfo(path);
return info.Directory?.FullName;
}
public Stream GetFile(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
{
if (!_accessControl.IsAccessAllowed(path, fileAccess)) {
throw new ForbiddenException(path);
}
return new FileStream(path, fileMode, fileAccess, fileShare);
}
public FileInfo GetFileInfo(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.Read)) {
throw new ForbiddenException(path);
}
return new FileInfo(path);
}
public FileVersionInfo GetFileVersionInfo(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.Read)) {
throw new ForbiddenException(path);
}
return FileVersionInfo.GetVersionInfo(path);
}
public DirectoryInfo GetDirectoryInfo(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.Read)) {
throw new ForbiddenException(path);
}
return new DirectoryInfo(path);
}
public async Task CopyFile(string sourcePath, string destPath, bool copyMetadata = false)
{
if (!_accessControl.IsAccessAllowed(sourcePath, FileAccess.ReadWrite)) {
throw new ForbiddenException(sourcePath);
}
if (!_accessControl.IsAccessAllowed(destPath, FileAccess.ReadWrite)) {
throw new ForbiddenException(destPath);
}
using (var srcStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
using (var destStream = new FileStream(destPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read)) {
await srcStream.CopyToAsync(destStream);
}
}
if (copyMetadata) {
var sourceFileInfo = new FileInfo(sourcePath);
var destFileInfo = new FileInfo(destPath);
destFileInfo.CreationTime = sourceFileInfo.CreationTime;
destFileInfo.CreationTimeUtc = sourceFileInfo.CreationTimeUtc;
}
}
public void MoveFile(string sourcePath, string destPath)
{
if (!_accessControl.IsAccessAllowed(sourcePath, FileAccess.ReadWrite)) {
throw new ForbiddenException(sourcePath);
}
if (!_accessControl.IsAccessAllowed(destPath, FileAccess.ReadWrite)) {
throw new ForbiddenException(destPath);
}
File.Move(sourcePath, destPath);
}
public void MoveDirectory(string sourcePath, string destPath)
{
if (!_accessControl.IsAccessAllowed(sourcePath, FileAccess.ReadWrite)) {
throw new ForbiddenException(sourcePath);
}
if (!_accessControl.IsAccessAllowed(destPath, FileAccess.ReadWrite)) {
throw new ForbiddenException(destPath);
}
Directory.Move(sourcePath, destPath);
}
public void DeleteFile(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.ReadWrite)) {
throw new ForbiddenException(path);
}
File.Delete(path);
}
public void DeleteDirectory(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.ReadWrite)) {
throw new ForbiddenException(path);
}
Directory.Delete(path, true);
}
public FileInfo CreateFile(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.ReadWrite)) {
throw new ForbiddenException(path);
}
File.Create(path).Dispose();
return new FileInfo(path);
}
public DirectoryInfo CreateDirectory(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.ReadWrite)) {
throw new ForbiddenException(path);
}
Directory.CreateDirectory(path);
return new DirectoryInfo(path);
}
public bool FileExists(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.Read)) {
throw new ForbiddenException(path);
}
return File.Exists(path);
}
public bool DirectoryExists(string path)
{
if (!_accessControl.IsAccessAllowed(path, FileAccess.Read)) {
throw new ForbiddenException(path);
}
return Directory.Exists(path);
}
}
}

Просмотреть файл

@ -11,18 +11,25 @@ namespace Microsoft.IIS.Administration.Files
public static class HttpContextFileExtensions
{
public static async Task<IActionResult> GetFileContentAsync(this HttpContext context, FileInfo fileInfo)
public static async Task<IActionResult> GetFileContentAsync(this HttpContext context, IFileProvider fileProvider, FileInfo fileInfo)
{
var handler = new HttpFileHandler(context, fileInfo);
var handler = new HttpFileHandler(fileProvider, context, fileInfo);
return await handler.GetFileContentAsync();
return await handler.GetFileContent();
}
public static async Task<IActionResult> PutFileContentAsync(this HttpContext context, FileInfo fileInfo)
public static async Task<IActionResult> PutFileContentAsync(this HttpContext context, IFileProvider fileProvider, FileInfo fileInfo)
{
var handler = new HttpFileHandler(context, fileInfo);
var handler = new HttpFileHandler(fileProvider, context, fileInfo);
return await handler.PutFileContentAsync();
return await handler.PutFileContent();
}
public static IActionResult GetFileContentHeaders(this HttpContext context, IFileProvider fileProvider, FileInfo fileInfo)
{
var handler = new HttpFileHandler(fileProvider, context, fileInfo);
return handler.GetFileContentHeaders();
}
}
}

Просмотреть файл

@ -10,6 +10,7 @@ namespace Microsoft.IIS.Administration.Files
using Core.Http;
using Core.Utils;
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography;
@ -21,11 +22,14 @@ namespace Microsoft.IIS.Administration.Files
private const string RangePrefix = "bytes=";
private const string ContentRangePrefix = "bytes ";
private FileInfo _file;
private FileService _service;
private IFileProvider _service;
private HttpContext _context;
public HttpFileHandler(HttpContext context, FileInfo fileInfo)
public HttpFileHandler(IFileProvider fileProvider, HttpContext context, FileInfo fileInfo)
{
if (fileProvider == null) {
throw new ArgumentNullException(nameof(fileProvider));
}
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
@ -35,39 +39,28 @@ namespace Microsoft.IIS.Administration.Files
this._context = context;
this._file = fileInfo;
this._service = new FileService();
this._service = fileProvider;
}
public async Task<IActionResult> GetFileContentAsync()
public async Task<IActionResult> GetFileContent()
{
bool isRangeRequest;
int start = -1, finish = -1;
var etag = ETag.Create(_file);
isRangeRequest = _context.Request.Headers.ContainsKey(HeaderNames.Range);
bool isRangeRequest = _context.Request.Headers.ContainsKey(HeaderNames.Range);
// Validate
if (isRangeRequest) {
ValidateRange(out start, out finish);
}
//
// Content Type
_context.Response.ContentType = "application/octet-stream";
AddFileContentHeaders(etag);
//
// Content-Disposition (file name)
_context.Response.Headers.Add("Content-Disposition", $"inline;filename={UrlEncoder.Default.Encode(_file.Name)}");
//
// Accept Ranges
_context.Response.Headers.Add("Accept-Ranges", "bytes");
//
// ETag
_context.Response.Headers.Add("ETag", etag.Value);
if (IsCachedIfModifiedSince() || IsCachedIfNoneMatch(etag)) {
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
// If the entity tag does not match, then the server SHOULD return the entire entity using a 200 (OK) response.
if (isRangeRequest && ValidateIfRange(_context.Request.Headers, etag)) {
if (isRangeRequest && IsValidIfRange(etag)) {
await RangeContentResponse(_context, _file, start, finish);
}
else {
@ -77,12 +70,15 @@ namespace Microsoft.IIS.Administration.Files
return new EmptyResult();
}
public async Task<IActionResult> PutFileContentAsync()
public async Task<IActionResult> PutFileContent()
{
int start = -1, finish = -1, outOf = -1;
var etag = ETag.Create(_file);
bool isRangeRequest = _context.Request.Headers.ContainsKey(HeaderNames.ContentRange);
ValidateIfMatch();
ValidateIfNoneMatch(etag);
ValidateIfUnmodifiedSince();
if (isRangeRequest) {
ValidateContentRange(out start, out finish, out outOf);
@ -90,26 +86,28 @@ namespace Microsoft.IIS.Administration.Files
var tempCopy = await GetTempCopy(_file.FullName);
if (isRangeRequest) {
try {
using (var stream = _service.GetFile(tempCopy, FileMode.Open, FileAccess.Write, FileShare.Read)) {
var length = finish - start + 1;
stream.Seek(start, SeekOrigin.Begin);
await CopyRangeAsync(_context.Request.Body, stream, 0, length);
try {
if (isRangeRequest) {
try {
using (var stream = _service.GetFile(tempCopy, FileMode.Open, FileAccess.Write, FileShare.Read)) {
var length = finish - start + 1;
stream.Seek(start, SeekOrigin.Begin);
await CopyRangeAsync(_context.Request.Body, stream, 0, length);
}
}
catch (IndexOutOfRangeException) {
throw new ApiArgumentException(HeaderNames.ContentLength);
}
SwapFiles(tempCopy, _file.FullName);
}
catch (IndexOutOfRangeException) {
_service.DeleteFile(tempCopy);
throw new ApiArgumentException(HeaderNames.ContentLength);
else {
using (var stream = _service.GetFile(tempCopy, FileMode.Truncate, FileAccess.Write, FileShare.Read)) {
await _context.Request.Body.CopyToAsync(stream);
}
SwapFiles(tempCopy, _file.FullName);
}
SwapFiles(tempCopy, _file.FullName);
_service.DeleteFile(tempCopy);
}
else {
using (var stream = _service.GetFile(tempCopy, FileMode.Truncate, FileAccess.Write, FileShare.Read)) {
await _context.Request.Body.CopyToAsync(stream);
}
SwapFiles(tempCopy, _file.FullName);
finally {
_service.DeleteFile(tempCopy);
}
@ -117,25 +115,141 @@ namespace Microsoft.IIS.Administration.Files
return new EmptyResult();
}
private static bool ValidateIfRange(IHeaderDictionary headers, ETag etag)
public IActionResult GetFileContentHeaders()
{
int start = -1, finish = -1;
var etag = ETag.Create(_file);
bool isRangeRequest = _context.Request.Headers.ContainsKey(HeaderNames.Range);
// Validate
if (isRangeRequest) {
ValidateRange(out start, out finish);
}
AddFileContentHeaders(etag);
//
// Content Length
if (isRangeRequest) {
_context.Response.ContentLength = finish - start + 1;
}
else {
_context.Response.ContentLength = _file.Length;
}
if (IsCachedIfModifiedSince() || IsCachedIfNoneMatch(etag)) {
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
return new EmptyResult();
}
private void AddFileContentHeaders(ETag etag)
{
//
// Content Type
_context.Response.ContentType = "application/octet-stream";
//
// Content-Disposition (file name)
_context.Response.Headers.Add("Content-Disposition", $"inline;filename={UrlEncoder.Default.Encode(_file.Name)}");
//
// Accept Ranges
_context.Response.Headers.Add("Accept-Ranges", "bytes");
//
// Last Modified
_context.Response.Headers.Add("Last-Modified", _file.LastWriteTimeUtc.ToString("r"));
//
// ETag
_context.Response.Headers.Add(HeaderNames.ETag, etag.Value);
if (IsCachedIfModifiedSince()) {
//
// Date
if (!_context.Response.Headers.ContainsKey(HeaderNames.Date)) {
_context.Response.Headers.Add(HeaderNames.Date, DateTime.UtcNow.ToString());
}
}
}
private bool IsCachedIfModifiedSince()
{
bool result = false;
DateTime ifModifiedSince = default(DateTime);
IHeaderDictionary reqHeaders = _context.Request.Headers;
// Trim milliseconds
var lastModified = _file.LastWriteTimeUtc.AddTicks( - (_file.LastWriteTimeUtc.Ticks % TimeSpan.TicksPerSecond));
if (reqHeaders.ContainsKey(HeaderNames.IfModifiedSince)
&& DateTime.TryParse(reqHeaders[HeaderNames.IfModifiedSince],
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal,
out ifModifiedSince)
&& ifModifiedSince.ToUniversalTime() >= lastModified) {
result = true;
}
return result;
}
private bool IsCachedIfNoneMatch(ETag etag)
{
var headers = _context.Request.Headers;
var isIfNoneMatch = headers.ContainsKey(HeaderNames.IfNoneMatch);
return isIfNoneMatch && (headers[HeaderNames.IfNoneMatch].Equals(etag.Value) || headers[HeaderNames.IfNoneMatch].Equals("*"));
}
private bool IsValidIfRange(ETag etag)
{
var headers = _context.Request.Headers;
bool isIfRange = headers.ContainsKey(HeaderNames.IfRange);
return !isIfRange || isIfRange && headers[HeaderNames.IfRange].Equals(etag.Value);
return !isIfRange || headers[HeaderNames.IfRange].Equals(etag.Value);
}
private void ValidateIfMatch()
{
if (_context.Request.Headers.ContainsKey(HeaderNames.IfMatch)) {
var ifMatch = _context.Request.Headers[HeaderNames.IfMatch].ToString().Trim();
var headers = _context.Request.Headers;
if (!_file.Exists || !ifMatch.Equals(ETag.Create(_file).Value)) {
if (headers.ContainsKey(HeaderNames.IfMatch)) {
var ifMatch = headers[HeaderNames.IfMatch].ToString().Trim();
if (!ifMatch.Equals(ETag.Create(_file).Value)) {
throw new PreconditionFailedException(HeaderNames.IfMatch);
}
}
}
private void ValidateIfUnmodifiedSince()
{
var headers = _context.Request.Headers;
DateTime unmodifiedSince;
if (headers.ContainsKey(HeaderNames.IfUnmodifiedSince) && DateTime.TryParse(headers[HeaderNames.IfUnmodifiedSince], out unmodifiedSince)) {
unmodifiedSince = unmodifiedSince.ToUniversalTime();
if (_file.LastWriteTimeUtc > unmodifiedSince) {
throw new PreconditionFailedException(HeaderNames.IfUnmodifiedSince);
}
}
}
private void ValidateIfNoneMatch(ETag etag)
{
if (IsCachedIfNoneMatch(etag)) {
throw new PreconditionFailedException(HeaderNames.IfNoneMatch);
}
}
private void ValidateRange(out int start, out int finish)
{
//

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using System.IO;
public interface IAccessControl
{
bool IsAccessAllowed(string path, FileAccess fileAccess);
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
public interface IFileProvider
{
string GetName(string path);
string GetParentPath(string path);
Stream GetFile(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare);
FileInfo GetFileInfo(string path);
FileVersionInfo GetFileVersionInfo(string path);
DirectoryInfo GetDirectoryInfo(string path);
Task CopyFile(string sourcePath, string destPath, bool copyMetadata = false);
void MoveFile(string sourcePath, string destPath);
void MoveDirectory(string sourcePath, string destPath);
void DeleteFile(string path);
void DeleteDirectory(string path);
FileInfo CreateFile(string path);
DirectoryInfo CreateDirectory(string path);
bool FileExists(string path);
bool DirectoryExists(string path);
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
public static class IOErrors
{
public const int FileInUse = unchecked((int)0x80070020);
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using Core;
using System;
using System.Dynamic;
using System.Net;
public class ForbiddenPathException : Exception, IError
{
private string _path;
public ForbiddenPathException(string path, Exception innerException = null) : base(string.Empty, innerException) {
this._path = path;
}
public ForbiddenPathException(string path, string message, Exception innerException = null) : base(message == null ? string.Empty : message, innerException) {
this._path = path;
}
public dynamic GetApiError()
{
dynamic obj = new ExpandoObject();
obj.title = "Path Forbidden";
obj.name = _path;
if (!string.IsNullOrEmpty(Message)) {
obj.detail = Message;
}
obj.status = (int)HttpStatusCode.Forbidden;
return obj;
}
}
}

Просмотреть файл

@ -6,7 +6,8 @@ namespace Microsoft.IIS.Administration.Files
{
public class AllowedRoot
{
public bool Read_Only { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public bool Read_Only { get; set; }
}
}

Просмотреть файл

@ -4,13 +4,17 @@
namespace Microsoft.IIS.Administration.Files
{
using Extensions.Configuration;
using System.Collections.Generic;
using System.Linq;
public class FileOptions
{
private bool _searchedForAll;
private AllowedRoot _allPaths;
private FileOptions() { }
public List<AllowedRoot> Allowed_Roots { get; set; }
public AllowedRoot WildCardRoot {
@ -36,5 +40,29 @@ namespace Microsoft.IIS.Administration.Files
Allowed_Roots = new List<AllowedRoot>()
};
}
public static FileOptions FromConfiguration(IConfiguration configuration)
{
FileOptions options = null;
if (configuration.GetSection("files").GetChildren().Count() > 0) {
options = EmptyOptions();
ConfigurationBinder.Bind(configuration.GetSection("files"), options);
}
return options ?? FileOptions.DefaultOptions();
}
public static FileOptions DefaultOptions()
{
var options = EmptyOptions();
options.Allowed_Roots.Add(new AllowedRoot() {
Path = @"%SystemDrive%\inetpub\wwwroot",
Read_Only = false
});
return options;
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
{
using AspNetCore.Builder;
using Core;
using Core.Http;
using Extensions.Caching.Memory;
using Extensions.DependencyInjection;
public class Startup : BaseModule, IServiceCollectionAccessor
{
public Startup() { }
public void Use(IServiceCollection services)
{
services.AddSingleton<IDownloadService>(sp => {
return new DownloadService((IMemoryCache)sp.GetService(typeof(IMemoryCache)));
});
}
public override void Start() {
ConfigureDownloads();
}
private void ConfigureDownloads()
{
var router = Environment.Host.RouteBuilder;
var hal = Environment.Hal;
router.MapWebApiRoute(Defines.DownloadResource.Guid, $"{Defines.DOWNLOAD_PATH}/{{id?}}", new { controller = "downloads" });
}
}
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"description": "Microsoft.IIS.Administration.FileSystem.Core Class Library",
"description": "Microsoft.IIS.Administration.Files Class Library",
"authors": [ "Microsoft" ],
"frameworks": {
"netstandard1.6": {
@ -12,8 +12,6 @@
}
},
"dependencies": {
"NETStandard.Library": "1.6.0",
"Microsoft.IIS.Administration.Core": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0"
"Microsoft.IIS.Administration.Core": "1.0.0"
}
}

Просмотреть файл

@ -13,14 +13,35 @@ namespace Microsoft.IIS.Administration.WebServer.Files
public class ContentController : ApiBaseController
{
private FileService _fileService;
private IFileProvider _fileService;
public ContentController()
{
_fileService = new FileService();
_fileService = FileProvider.Default;
}
[HttpGet]
[HttpHead]
public IActionResult Head(string id)
{
FileId fileId = new FileId(id);
Site site = SiteHelper.GetSite(fileId.SiteId);
if (site == null) {
return NotFound();
}
var physicalPath = FilesHelper.GetPhysicalPath(site, fileId.Path);
if (!_fileService.FileExists(physicalPath)) {
return NotFound();
}
AddHttpLinkHeader(fileId);
return Context.GetFileContentHeaders(_fileService, _fileService.GetFileInfo(physicalPath));
}
public async Task<IActionResult> Get(string id)
{
FileId fileId = new FileId(id);
@ -36,7 +57,9 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return NotFound();
}
return await Context.GetFileContentAsync(_fileService.GetFileInfo(physicalPath));
AddHttpLinkHeader(fileId);
return await Context.GetFileContentAsync(_fileService, _fileService.GetFileInfo(physicalPath));
}
[HttpPut]
@ -55,7 +78,16 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return NotFound();
}
return await Context.PutFileContentAsync(_fileService.GetFileInfo(physicalPath));
AddHttpLinkHeader(fileId);
return await Context.PutFileContentAsync(_fileService, _fileService.GetFileInfo(physicalPath));
}
private void AddHttpLinkHeader(FileId fileId)
{
Context.Response.Headers.Add("Link", $"</{Defines.FILES_PATH}/{fileId.Uuid}>; rel=\"meta\"; title=\"file metadata\", </{Defines.CONTENT_PATH}/{fileId.Uuid}>; rel=\"self\"; title=\"self\"");
}
}
}

Просмотреть файл

@ -19,14 +19,13 @@ namespace Microsoft.IIS.Administration.WebServer.Files
public class FilesController : ApiBaseController
{
private FileService _fileService;
private IFileProvider _fileService;
public FilesController()
{
_fileService = new FileService();
_fileService = FileProvider.Default;
}
[HttpGet]
public object Get()
{
string parentUuid = Context.Request.Query[Defines.PARENT_IDENTIFIER];
@ -51,17 +50,22 @@ namespace Microsoft.IIS.Administration.WebServer.Files
var files = new List<object>();
var dInfo = _fileService.GetDirectoryInfo(physicalPath);
// Files & directories
//
// Files
foreach (var f in dInfo.GetFiles()) {
files.Add(FilesHelper.FileToJsonModelRef(site, Path.Combine(fileId.Path, f.Name)));
}
//
// Directories
foreach (var d in dInfo.GetDirectories()) {
files.Add(FilesHelper.DirectoryToJsonModelRef(site, Path.Combine(fileId.Path, d.Name)));
}
//
// Virtual Directories
foreach (var fullVdir in FilesHelper.GetChildVirtualDirectories(site, fileId.Path)) {
files.Add(FilesHelper.VirtualDirectoryToJsonModelRef(fullVdir));
files.Add(FilesHelper.VdirToJsonModelRef(fullVdir));
}
// Set HTTP header for total count
@ -72,7 +76,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
};
}
[HttpGet]
public object Get(string id)
{
FileId fileId = new FileId(id);
@ -88,16 +92,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return NotFound();
}
switch (FilesHelper.GetFileType(site, fileId.Path, physicalPath)) {
case FileType.File:
return FilesHelper.FileToJsonModel(site, fileId.Path);
case FileType.Directory:
return FilesHelper.DirectoryToJsonModel(site, fileId.Path);
case FileType.VDir:
return FilesHelper.VirtualDirectoryToJsonModel(FilesHelper.ResolveFullVdir(site, fileId.Path));
default:
return null;
}
return FilesHelper.ToJsonModel(site, fileId.Path);
}
[HttpPost]
@ -130,7 +125,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
var physicalPath = FilesHelper.GetPhysicalPath(site, fileId.Path);
if (!_fileService.DirectoryExists(physicalPath)) {
return NotFound();
throw new NotFoundException(physicalPath);
}
//
@ -159,7 +154,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
_fileService.CreateDirectory(Path.Combine(physicalPath, name));
}
return FilesHelper.GetFileType(site, fileId.Path, physicalPath) == FileType.File ? FilesHelper.FileToJsonModel(site, Path.Combine(fileId.Path, name)) : FilesHelper.DirectoryToJsonModel(site, Path.Combine(fileId.Path, name));
return FilesHelper.ToJsonModel(site, Path.Combine(fileId.Path, name));
}
[HttpPatch]

Просмотреть файл

@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.WebServer.Files
{
using Administration.Files;
using AspNetCore.Mvc;
using Core;
using Core.Http;
using Core.Utils;
using Newtonsoft.Json.Linq;
using Sites;
using System;
using System.Net;
using System.Reflection;
using Web.Administration;
public class WsDownloadsController : ApiBaseController
{
private IDownloadService _downloadService;
private IFileProvider _fileService;
public WsDownloadsController(IServiceProvider serviceProvider)
{
_fileService = FileProvider.Default;
_downloadService = (IDownloadService)serviceProvider.GetService(typeof(IDownloadService));
}
[HttpGet]
[HttpHead]
[HttpPatch]
[HttpPut]
public IActionResult NotAllowed()
{
return new StatusCodeResult((int)HttpStatusCode.MethodNotAllowed);
}
[HttpPost]
public IActionResult Post([FromBody] dynamic model)
{
if (_downloadService == null) {
throw new NotFoundException(typeof(IDownloadService).GetTypeInfo().Assembly.FullName);
}
if (model == null) {
throw new ApiArgumentException("model");
}
if (model.file == null) {
throw new ApiArgumentException("file");
}
if (!(model.file is JObject)) {
throw new ApiArgumentException("file", ApiArgumentException.EXPECTED_OBJECT);
}
//
// Check Id
string fileUuid = DynamicHelper.Value(model.file.id);
if (fileUuid == null) {
throw new ApiArgumentException("file.id");
}
int? ttl = DynamicHelper.To<int>(model.ttl);
FileId fileId = new FileId(fileUuid);
Site site = SiteHelper.GetSite(fileId.SiteId);
if (site == null) {
return NotFound();
}
var physicalPath = FilesHelper.GetPhysicalPath(site, fileId.Path);
if (!_fileService.FileExists(physicalPath)) {
throw new NotFoundException(physicalPath);
}
var dl = _downloadService.Create(physicalPath, ttl);
return Created(dl.Href, null);
}
}
}

Просмотреть файл

@ -11,6 +11,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
{
private const string FILES_ENDPOINT = "files";
private const string CONTENT_ENDPOINT = "content";
private const string DOWNLOADS_ENDPOINT = "downloads";
public static readonly string FILES_PATH = $"{WebServer.Defines.PATH}/{FILES_ENDPOINT}";
public static readonly ResDef FilesResource = new ResDef("files", new Guid("CF0CF1C6-8913-4EF0-9833-C11820689252"), FILES_ENDPOINT);
@ -22,5 +23,9 @@ namespace Microsoft.IIS.Administration.WebServer.Files
public static readonly string CONTENT_PATH = $"{FILES_PATH}/{CONTENT_ENDPOINT}";
public static readonly ResDef ContentResource = new ResDef("content", new Guid("345F44E5-F4B5-4289-A551-6353D3A61215"), CONTENT_ENDPOINT);
public const string CONTENT_IDENTIFIER = "content.id";
public static readonly string DOWNLOAD_PATH = $"{FILES_PATH}/{DOWNLOADS_ENDPOINT}";
public static readonly ResDef DownloadResource = new ResDef("download", new Guid("D77024EA-85C1-497A-B204-2FBF5D0E0DAE"), DOWNLOADS_ENDPOINT);
public const string DOWNLOAD_IDENTIFIER = "dl.id";
}
}

Просмотреть файл

@ -13,9 +13,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
private const uint PATH_INDEX = 0;
private const uint SITE_ID_NUM_INDEX = 1;
public string Path { get; private set; }
public long SiteId { get; private set; }
public string Uuid { get; private set; }
private string _path;
public FileId(string uuid)
{
@ -43,5 +41,27 @@ namespace Microsoft.IIS.Administration.WebServer.Files
this.Uuid = Core.Utils.Uuid.Encode($"{this.Path}{DELIMITER}{this.SiteId}", PURPOSE);
}
public string Path {
get {
return _path;
}
private set {
if (!value.StartsWith("/")) {
throw new ArgumentException(nameof(value));
}
var absolute = System.IO.Path.GetFullPath(value);
var slashIndex = absolute.IndexOf('\\');
if (!absolute.Substring(slashIndex, absolute.Length - slashIndex).Equals(value.Replace('/', '\\'))) {
throw new ArgumentException(nameof(value));
}
this._path = value;
}
}
public long SiteId { get; private set; }
public string Uuid { get; private set; }
}
}

Просмотреть файл

@ -2,9 +2,9 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.IIS.Administration.Files
namespace Microsoft.IIS.Administration.WebServer.Files
{
public enum FileType
enum FileType
{
File,
Directory,

Просмотреть файл

@ -16,12 +16,47 @@ namespace Microsoft.IIS.Administration.WebServer.Files
public static class FilesHelper
{
private static readonly Fields DirectoryRefFields = new Fields("name", "id", "type", "path", "physical_path");
private static readonly Fields FileRefFields = new Fields("name", "id", "type", "path", "physical_path");
private static readonly Fields RefFields = new Fields("name", "id", "type", "path", "physical_path");
private static FileService _service = new FileService();
private static IFileProvider _service = FileProvider.Default;
public static object DirectoryToJsonModel(Site site, string path, Fields fields = null, bool full = true)
public static object ToJsonModel(Site site, string path, Fields fields = null, bool full = true)
{
var physicalPath = GetPhysicalPath(site, path);
if (physicalPath != null) {
var fileType = GetFileType(site, path, physicalPath);
switch (fileType) {
case FileType.File:
return FileToJsonModel(site, path, fields, full);
case FileType.Directory:
return DirectoryToJsonModel(site, path, fields, full);
case FileType.VDir:
var app = ResolveApplication(site, path);
var vdir = ResolveVdir(site, path);
return VdirToJsonModel(new Vdir(site, app, vdir), fields, full);
}
}
return null;
}
public static object ToJsonModelRef(Site site, string path, Fields fields = null)
{
if (fields == null || !fields.HasFields) {
return ToJsonModel(site, path, RefFields, false);
}
else {
return ToJsonModel(site, path, fields, false);
}
}
internal static object DirectoryToJsonModel(Site site, string path, Fields fields = null, bool full = true)
{
if (string.IsNullOrEmpty(path)) {
throw new ArgumentNullException(nameof(path));
@ -105,17 +140,17 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return Core.Environment.Hal.Apply(Defines.DirectoriesResource.Guid, obj, full);
}
public static object DirectoryToJsonModelRef(Site site, string path, Fields fields = null)
internal static object DirectoryToJsonModelRef(Site site, string path, Fields fields = null)
{
if (fields == null || !fields.HasFields) {
return DirectoryToJsonModel(site, path, DirectoryRefFields, false);
return DirectoryToJsonModel(site, path, RefFields, false);
}
else {
return DirectoryToJsonModel(site, path, fields, false);
}
}
public static object FileToJsonModel(Site site, string path, Fields fields = null, bool full = true)
internal static object FileToJsonModel(Site site, string path, Fields fields = null, bool full = true)
{
if (string.IsNullOrEmpty(path)) {
throw new ArgumentNullException(nameof(path));
@ -152,9 +187,9 @@ namespace Microsoft.IIS.Administration.WebServer.Files
}
//
// length
if (fields.Exists("length")) {
obj.length = fileInfo.Length;
// size
if (fields.Exists("size")) {
obj.size = fileInfo.Length;
}
//
@ -209,9 +244,9 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return Core.Environment.Hal.Apply(Defines.FilesResource.Guid, obj, full);
}
internal static object VirtualDirectoryToJsonModel(Vdir fullVdir, Fields fields = null, bool full = true)
internal static object VdirToJsonModel(Vdir vdir, Fields fields = null, bool full = true)
{
if (fullVdir == null) {
if (vdir == null) {
return null;
}
@ -219,16 +254,16 @@ namespace Microsoft.IIS.Administration.WebServer.Files
fields = Fields.All;
}
var physicalPath = GetPhysicalPath(fullVdir.Site, fullVdir.Path);
var physicalPath = GetPhysicalPath(vdir.Site, vdir.Path);
dynamic obj = new ExpandoObject();
var FileId = new FileId(fullVdir.Site.Id, fullVdir.Path);
var FileId = new FileId(vdir.Site.Id, vdir.Path);
var dirInfo = _service.GetDirectoryInfo(physicalPath);
//
// name
if (fields.Exists("name")) {
obj.name = fullVdir.Path == "/" ? fullVdir.Site.Name : fullVdir.Path.TrimStart('/');
obj.name = vdir.Path == "/" ? vdir.Site.Name : vdir.Path.TrimStart('/');
}
//
@ -246,7 +281,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
//
// path
if (fields.Exists("path")) {
obj.path = fullVdir.Path.Replace('\\', '/');
obj.path = vdir.Path.Replace('\\', '/');
}
//
@ -270,20 +305,20 @@ namespace Microsoft.IIS.Administration.WebServer.Files
//
// total_files
if (fields.Exists("total_files")) {
obj.total_files = dirInfo.GetFiles().Length + dirInfo.GetDirectories().Length + GetChildVirtualDirectories(fullVdir.Site, fullVdir.Path).Count;
obj.total_files = dirInfo.GetFiles().Length + dirInfo.GetDirectories().Length + GetChildVirtualDirectories(vdir.Site, vdir.Path).Count;
}
//
// parent
if (fields.Exists("parent")) {
if (fullVdir.VirtualDirectory.Path != "/") {
var rootVdir = fullVdir.Application.VirtualDirectories["/"];
obj.parent = rootVdir == null ? null : VirtualDirectoryToJsonModelRef(new Vdir(fullVdir.Site, fullVdir.Application, rootVdir));
if (vdir.VirtualDirectory.Path != "/") {
var rootVdir = vdir.Application.VirtualDirectories["/"];
obj.parent = rootVdir == null ? null : VdirToJsonModelRef(new Vdir(vdir.Site, vdir.Application, rootVdir));
}
else if (fullVdir.Application.Path != "/") {
var rootApp = fullVdir.Site.Applications["/"];
else if (vdir.Application.Path != "/") {
var rootApp = vdir.Site.Applications["/"];
var rootVdir = rootApp == null ? null : rootApp.VirtualDirectories["/"];
obj.parent = rootApp == null || rootVdir == null ? null : VirtualDirectoryToJsonModel(new Vdir(fullVdir.Site, rootApp, rootVdir));
obj.parent = rootApp == null || rootVdir == null ? null : VdirToJsonModel(new Vdir(vdir.Site, rootApp, rootVdir));
}
else {
obj.parent = null;
@ -293,33 +328,33 @@ namespace Microsoft.IIS.Administration.WebServer.Files
//
// website
if (fields.Exists("website")) {
obj.website = SiteHelper.ToJsonModelRef(fullVdir.Site);
obj.website = SiteHelper.ToJsonModelRef(vdir.Site);
}
return Core.Environment.Hal.Apply(Defines.DirectoriesResource.Guid, obj, full);
}
internal static object VirtualDirectoryToJsonModelRef(Vdir vdir, Fields fields = null)
internal static object VdirToJsonModelRef(Vdir vdir, Fields fields = null)
{
if (fields == null || !fields.HasFields) {
return VirtualDirectoryToJsonModel(vdir, DirectoryRefFields, false);
return VdirToJsonModel(vdir, RefFields, false);
}
else {
return VirtualDirectoryToJsonModel(vdir, fields, false);
return VdirToJsonModel(vdir, fields, false);
}
}
public static object FileToJsonModelRef(Site site, string path, Fields fields = null)
internal static object FileToJsonModelRef(Site site, string path, Fields fields = null)
{
if (fields == null || !fields.HasFields) {
return FileToJsonModel(site, path, FileRefFields, false);
return FileToJsonModel(site, path, RefFields, false);
}
else {
return FileToJsonModel(site, path, fields, false);
}
}
public static string UpdateFile(dynamic model, string physicalPath)
internal static string UpdateFile(dynamic model, string physicalPath)
{
if (model == null) {
throw new ApiArgumentException("model");
@ -342,7 +377,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return physicalPath;
}
public static string UpdateDirectory(dynamic model, string directoryPath)
internal static string UpdateDirectory(dynamic model, string directoryPath)
{
if (model == null) {
throw new ApiArgumentException("model");
@ -372,7 +407,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return $"/{Defines.FILES_PATH}/{id}";
}
public static FileType GetFileType(Site site, string path, string physicalPath)
internal static FileType GetFileType(Site site, string path, string physicalPath)
{
// A virtual directory is a directory who's physical path is not the combination of the sites physical path and the relative path from the sites root
// and has same virtual path as app + vdir path
@ -480,9 +515,10 @@ namespace Microsoft.IIS.Administration.WebServer.Files
if (vdir != null) {
var suffix = path.TrimStart(app.Path.TrimEnd('/'), StringComparison.OrdinalIgnoreCase).TrimStart(vdir.Path.TrimEnd('/'), StringComparison.OrdinalIgnoreCase);
physicalPath = Path.Combine(vdir.PhysicalPath, suffix.Trim(PathUtil.SEPARATORS).Replace('/', Path.DirectorySeparatorChar));
physicalPath = System.Environment.ExpandEnvironmentVariables(physicalPath);
}
return System.Environment.ExpandEnvironmentVariables(physicalPath);
return physicalPath;
}
internal static List<Vdir> GetChildVirtualDirectories(Site site, string path)
@ -509,6 +545,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
vdirs.Add(new Vdir(site, app, vdir));
}
}
break;
}
}
return vdirs;
@ -533,7 +570,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
var parentVdir = ResolveVdir(site, parentPath);
if (IsExactVdirPath(site, parentApp, parentVdir, parentPath)) {
parent = VirtualDirectoryToJsonModelRef(new Vdir(site, parentApp, parentVdir));
parent = VdirToJsonModelRef(new Vdir(site, parentApp, parentVdir));
}
else {
var parentPhysicalPath = GetPhysicalPath(site, parentPath);

Просмотреть файл

@ -9,6 +9,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
using Sites;
using Web.Administration;
using Core.Http;
using Administration.Files;
public class Startup : BaseModule
{
@ -19,6 +20,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
ConfigureFiles();
ConfigureDirectories();
ConfigureContent();
ConfigureDownloads();
}
private void ConfigureFiles()
@ -31,9 +33,6 @@ namespace Microsoft.IIS.Administration.WebServer.Files
// Self
hal.ProvideLink(Defines.FilesResource.Guid, "self", file => new { href = $"/{Defines.FILES_PATH}/{file.id}" });
// Files
//hal.ProvideLink(Defines.FilesResource.Guid, "files", file => new { href = $"/{Defines.FILES_PATH}?{Defines.PARENT_IDENTIFIER}={file.id}" });
// Site
Environment.Hal.ProvideLink(Sites.Defines.Resource.Guid, Defines.FilesResource.Name, site => {
var siteId = new SiteId((string)site.id);
@ -64,5 +63,17 @@ namespace Microsoft.IIS.Administration.WebServer.Files
hal.ProvideLink(Defines.FilesResource.Guid, Defines.ContentResource.Name, file => new { href = $"/{Defines.CONTENT_PATH}/{file.id}" });
}
private void ConfigureDownloads()
{
var router = Environment.Host.RouteBuilder;
var hal = Environment.Hal;
router.MapWebApiRoute(Defines.DownloadResource.Guid, $"{Defines.DOWNLOAD_PATH}/{{id?}}", new { controller = "wsdownloads" });
if (Environment.Host.ApplicationBuilder.ApplicationServices.GetService(typeof(IDownloadService)) != null) {
hal.ProvideLink(Defines.FilesResource.Guid, Defines.DownloadResource.Name, file => new { href = $"{Defines.DOWNLOAD_PATH}" });
}
}
}
}

Просмотреть файл

@ -41,7 +41,7 @@ namespace Microsoft.IIS.Administration.WebServer.Logging
Site site = logId.SiteId == null ? null : SiteHelper.GetSite(logId.SiteId.Value);
if (logId.SiteId != null && site == null) {
return new StatusCodeResult((int)HttpStatusCode.NotFound);
return NotFound();
}
return LoggingHelper.ToJsonModel(site, logId.Path);

Просмотреть файл

@ -6,11 +6,11 @@ namespace Microsoft.IIS.Administration.WebServer.Transactions
{
using AspNetCore.Mvc;
using Core;
using Core.Http;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
public class TransactionsController : ApiController
public class TransactionsController : ApiBaseController
{
[HttpGet]
[ResourceInfo(Name = Defines.TransactionsName)]

Просмотреть файл

@ -4,11 +4,12 @@
namespace Microsoft.IIS.Administration
{
using AspNetCore.Builder;
using AspNetCore.Routing;
using Core;
using Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using Core;
using AspNetCore.Builder;
using System.Linq;
using System.Reflection;
@ -44,14 +45,30 @@ namespace Microsoft.IIS.Administration
_modules.Add(module);
}
public void InitiateModules(IRouteBuilder routes, IApplicationBuilder applicationBuilder)
public void ConfigureModules(IServiceCollection services)
{
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
//
// Configure modules
foreach (IModule module in _modules) {
var sa = module as IServiceCollectionAccessor;
if (sa != null) {
sa.Use(services);
}
}
}
public void StartModules(IRouteBuilder routes, IApplicationBuilder applicationBuilder)
{
if (routes == null) {
throw new ArgumentNullException("routes");
throw new ArgumentNullException(nameof(routes));
}
if (applicationBuilder == null) {
throw new ArgumentNullException("applicationBuilder");
throw new ArgumentNullException(nameof(applicationBuilder));
}
_routeBuilder = routes;
@ -63,7 +80,7 @@ namespace Microsoft.IIS.Administration
//
// Start modules
foreach (IModule module in this._modules) {
foreach (IModule module in _modules) {
module.Start();
}

Просмотреть файл

@ -8,6 +8,7 @@ namespace Microsoft.IIS.Administration
using AspNetCore.Mvc.Filters;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
class HeadTransform
@ -35,7 +36,7 @@ namespace Microsoft.IIS.Administration
try {
await _next(context);
if (context.Response.StatusCode == StatusCodes.Status404NotFound && !context.Items.ContainsKey(FOUND_ACTION)) {
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound && !context.Items.ContainsKey(FOUND_ACTION)) {
context.Request.Method = "GET";
context.Response.StatusCode = status;

Просмотреть файл

@ -68,6 +68,7 @@ namespace Microsoft.IIS.Administration
ModuleConfig modConfig = new ModuleConfig(HostingEnvironment.GetConfigPath("modules.json"));
ModuleLoader loader = new ModuleLoader(HostingEnvironment);
LoadPlugins(loader, modConfig.Modules);
AdminHost.Instance.ConfigureModules(services);
//
// CORS
@ -80,13 +81,12 @@ namespace Microsoft.IIS.Administration
//
// Authentication
services.AddAuthentication();
services.AddAuthorization(o =>
{
services.AddAuthorization(o => {
o.AddPolicy("AccessToken", p => p.RequireAuthenticatedUser().RequireClaim(Core.Security.ClaimTypes.AccessToken));
o.AddPolicy("Administrators", p => p.RequireAuthenticatedUser().RequireRole("Administrators"));
o.AddPolicy("AdministrativeGroup", p => p.RequireAuthenticatedUser().RequireAssertion(authContext =>
o.AddPolicy("AdministrativeGroup", p => p.RequireAuthenticatedUser().RequireAssertion(authContext =>
IsUserInAdministrators(authContext, Configuration)
));
});
@ -98,8 +98,12 @@ namespace Microsoft.IIS.Administration
services.AddAntiforgery(o =>
{
o.RequireSsl = true;
o.CookieName = o.FormFieldName = Core.Http.HeaderNames.XSRF_TOKEN;
o.CookieName = o.FormFieldName = HeaderNames.XSRF_TOKEN;
});
//
// Caching
services.AddMemoryCache();
//
// MVC
@ -215,7 +219,7 @@ namespace Microsoft.IIS.Administration
// Add MVC
//
app.UseMvc(routes => {
AdminHost.Instance.InitiateModules(routes, app);
AdminHost.Instance.StartModules(routes, app);
InitiateFeatures(routes);
// Ensure routes meant to be extended do not block child routes

Просмотреть файл

@ -8,6 +8,10 @@
"name": "Microsoft.IIS.Administration.Certificates",
"enabled": true
},
{
"name": "Microsoft.IIS.Administration.Files",
"enabled": true
},
{
"name": "Microsoft.IIS.Administration.WebServer",
"enabled": true

Просмотреть файл

@ -20,6 +20,7 @@
"Microsoft.Extensions.Configuration": "1.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
"Microsoft.Extensions.Configuration.Binder": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",