Added download capability for files.
This commit is contained in:
Родитель
29b3a2591f
Коммит
112032a8be
|
@ -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",
|
||||
|
|
Загрузка…
Ссылка в новой задаче