Merge pull request #2384 from sbwalker/dev
made folder paths cross platform, introduced file handler for abstracting the serving of files, enabled url mapping for broken file links, resolved public folder deletion issue
This commit is contained in:
commit
68f5bf5759
|
@ -341,6 +341,12 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
Page child;
|
||||
switch (_insert)
|
||||
{
|
||||
|
|
|
@ -463,6 +463,12 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_insert != "=")
|
||||
{
|
||||
Page child;
|
||||
|
|
|
@ -237,4 +237,7 @@
|
|||
<data name="Meta.Text" xml:space="preserve">
|
||||
<value>Meta:</value>
|
||||
</data>
|
||||
<data name="Message.Page.Reserved" xml:space="preserve">
|
||||
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -270,4 +270,7 @@
|
|||
<data name="Meta.Text" xml:space="preserve">
|
||||
<value>Meta:</value>
|
||||
</data>
|
||||
<data name="Message.Page.Reserved" xml:space="preserve">
|
||||
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -18,15 +18,13 @@ namespace Oqtane.Controllers
|
|||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class FolderController : Controller
|
||||
{
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly IFolderRepository _folders;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public FolderController(IWebHostEnvironment environment, IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
|
||||
public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_environment = environment;
|
||||
_folders = folders;
|
||||
_userPermissions = userPermissions;
|
||||
_logger = logger;
|
||||
|
@ -78,10 +76,10 @@ namespace Oqtane.Controllers
|
|||
[HttpGet("{siteId}/{path}")]
|
||||
public Folder GetByPath(int siteId, string path)
|
||||
{
|
||||
var folderPath = WebUtility.UrlDecode(path);
|
||||
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
|
||||
var folderPath = WebUtility.UrlDecode(path).Replace("\\", "/");
|
||||
if (!folderPath.EndsWith("/"))
|
||||
{
|
||||
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
folderPath += "/";
|
||||
}
|
||||
Folder folder = _folders.GetFolder(siteId, folderPath);
|
||||
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.Permissions))
|
||||
|
@ -121,9 +119,9 @@ namespace Oqtane.Controllers
|
|||
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
|
||||
{
|
||||
Folder parent = _folders.GetFolder(folder.ParentId.Value);
|
||||
folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
|
||||
folder.Path = Utilities.UrlCombine(parent.Path, folder.Name);
|
||||
}
|
||||
folder.Path = Utilities.PathCombine(folder.Path, Path.DirectorySeparatorChar.ToString());
|
||||
folder.Path = folder.Path + "/";
|
||||
folder = _folders.AddFolder(folder);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
|
||||
}
|
||||
|
@ -162,14 +160,14 @@ namespace Oqtane.Controllers
|
|||
if (folder.ParentId != null)
|
||||
{
|
||||
Folder parent = _folders.GetFolder(folder.ParentId.Value);
|
||||
folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
|
||||
folder.Path = Utilities.UrlCombine(parent.Path, folder.Name);
|
||||
}
|
||||
folder.Path = Utilities.PathCombine(folder.Path, Path.DirectorySeparatorChar.ToString());
|
||||
folder.Path = folder.Path + "/";
|
||||
|
||||
Models.Folder _folder = _folders.GetFolder(id, false);
|
||||
if (_folder.Path != folder.Path && Directory.Exists(GetFolderPath(_folder)))
|
||||
Folder _folder = _folders.GetFolder(id, false);
|
||||
if (_folder.Path != folder.Path && Directory.Exists(_folders.GetFolderPath(_folder)))
|
||||
{
|
||||
Directory.Move(GetFolderPath(_folder), GetFolderPath(folder));
|
||||
Directory.Move(_folders.GetFolderPath(_folder), _folders.GetFolderPath(folder));
|
||||
}
|
||||
|
||||
folder = _folders.UpdateFolder(folder);
|
||||
|
@ -226,9 +224,9 @@ namespace Oqtane.Controllers
|
|||
var folder = _folders.GetFolder(id, false);
|
||||
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, id, PermissionNames.Edit))
|
||||
{
|
||||
if (Directory.Exists(GetFolderPath(folder)))
|
||||
if (Directory.Exists(_folders.GetFolderPath(folder)))
|
||||
{
|
||||
Directory.Delete(GetFolderPath(folder));
|
||||
Directory.Delete(_folders.GetFolderPath(folder));
|
||||
}
|
||||
_folders.DeleteFolder(id);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
|
||||
|
@ -239,10 +237,5 @@ namespace Oqtane.Controllers
|
|||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFolderPath(Folder folder)
|
||||
{
|
||||
return Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", _alias.TenantId.ToString(), "Sites", folder.SiteId.ToString(), folder.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -273,7 +273,7 @@ namespace Oqtane.Controllers
|
|||
}
|
||||
|
||||
// remove user folder for site
|
||||
var folder = _folders.GetFolder(SiteId, Utilities.PathCombine("Users", user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()));
|
||||
var folder = _folders.GetFolder(SiteId, $"Users{user.UserId}/");
|
||||
if (folder != null)
|
||||
{
|
||||
if (Directory.Exists(_folders.GetFolderPath(folder)))
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace Oqtane.Infrastructure
|
|||
// legacy support for client api requests which would include the alias as a path prefix ( ie. {alias}/api/[controller] )
|
||||
int aliasId;
|
||||
string[] segments = httpcontext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (segments.Length > 1 && (segments[1] == "api" || segments[1] == "pages") && int.TryParse(segments[0], out aliasId))
|
||||
if (segments.Length > 1 && Shared.Constants.ReservedRoutes.Contains(segments[1]) && int.TryParse(segments[0], out aliasId))
|
||||
{
|
||||
alias = _aliasRepository.GetAliases().ToList().FirstOrDefault(item => item.AliasId == aliasId);
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@ namespace Oqtane.Infrastructure
|
|||
case "3.1.4":
|
||||
Upgrade_3_1_4(tenant, scope);
|
||||
break;
|
||||
case "3.2.0":
|
||||
Upgrade_3_2_0(tenant, scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,5 +241,29 @@ namespace Oqtane.Infrastructure
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Upgrade_3_2_0(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
try
|
||||
{
|
||||
// convert folder paths cross platform format
|
||||
var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
var folderRepository = scope.ServiceProvider.GetRequiredService<IFolderRepository>();
|
||||
foreach (Site site in siteRepository.GetSites().ToList())
|
||||
{
|
||||
var folders = folderRepository.GetFolders(site.SiteId);
|
||||
foreach (Folder folder in folders)
|
||||
{
|
||||
folder.Path = folder.Path.Replace("\\", "/");
|
||||
folderRepository.UpdateFolder(folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
3
Oqtane.Server/Pages/Files.cshtml
Normal file
3
Oqtane.Server/Pages/Files.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
@page "/files/{**path}"
|
||||
@namespace Oqtane.Pages
|
||||
@model Oqtane.Pages.FilesModel
|
98
Oqtane.Server/Pages/Files.cshtml.cs
Normal file
98
Oqtane.Server/Pages/Files.cshtml.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class FilesModel : PageModel
|
||||
{
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly IFileRepository _files;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_environment = environment;
|
||||
_files = files;
|
||||
_userPermissions = userPermissions;
|
||||
_urlMappings = urlMappings;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
public IActionResult OnGet(string path)
|
||||
{
|
||||
path = path.Replace("\\", "/");
|
||||
var folderpath = "";
|
||||
var filename = "";
|
||||
|
||||
var segments = path.Split('/');
|
||||
if (segments.Length > 0)
|
||||
{
|
||||
filename = segments[segments.Length - 1].ToLower();
|
||||
if (segments.Length > 1)
|
||||
{
|
||||
folderpath = string.Join("/", segments, 0, segments.Length - 1).ToLower() + "/";
|
||||
}
|
||||
}
|
||||
|
||||
var file = _files.GetFile(_alias.SiteId, folderpath, filename);
|
||||
if (file != null)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
|
||||
{
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
return PhysicalFile(filepath, file.GetMimeType());
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt {SiteId} {Path}", _alias.SiteId, path);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// look for url mapping
|
||||
var urlMapping = _urlMappings.GetUrlMapping(_alias.SiteId, "files/" + folderpath + filename);
|
||||
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
{
|
||||
var url = urlMapping.MappedUrl;
|
||||
if (!url.StartsWith("http"))
|
||||
{
|
||||
var uri = new Uri(HttpContext.Request.GetEncodedUrl());
|
||||
url = uri.Scheme + "://" + uri.Authority + ((!string.IsNullOrEmpty(_alias.Path)) ? "/" + _alias.Path : "") + "/" + url;
|
||||
}
|
||||
return RedirectPermanent(url);
|
||||
}
|
||||
}
|
||||
|
||||
// broken link
|
||||
string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot\\images"), "error.png");
|
||||
return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ namespace Oqtane.Repository
|
|||
int start = segments.Length;
|
||||
for (int i = 0; i < segments.Length; i++)
|
||||
{
|
||||
if (segments[i] == "api" || segments[i] == "pages" || segments[i] == Constants.ModuleDelimiter)
|
||||
if (Constants.ReservedRoutes.Contains(segments[i]) || segments[i] == Constants.ModuleDelimiter)
|
||||
{
|
||||
start = i;
|
||||
break;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -82,6 +83,24 @@ namespace Oqtane.Repository
|
|||
return file;
|
||||
}
|
||||
|
||||
public File GetFile(int siteId, string folderPath, string fileName)
|
||||
{
|
||||
var file = _db.File.AsNoTracking()
|
||||
.Include(item => item.Folder)
|
||||
.FirstOrDefault(item => item.Folder.SiteId == siteId &&
|
||||
item.Folder.Path.ToLower() == folderPath &&
|
||||
item.Name.ToLower() == fileName);
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
IEnumerable<Permission> permissions = _permissions.GetPermissions(EntityNames.Folder, file.FolderId).ToList();
|
||||
file.Folder.Permissions = permissions.EncodePermissions();
|
||||
file.Url = GetFileUrl(file, _tenants.GetAlias());
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public void DeleteFile(int fileId)
|
||||
{
|
||||
File file = _db.File.Find(fileId);
|
||||
|
@ -105,17 +124,7 @@ namespace Oqtane.Repository
|
|||
|
||||
private string GetFileUrl(File file, Alias alias)
|
||||
{
|
||||
string url = "";
|
||||
switch (file.Folder.Type)
|
||||
{
|
||||
case FolderTypes.Private:
|
||||
url = Utilities.ContentUrl(alias, file.FileId);
|
||||
break;
|
||||
case FolderTypes.Public:
|
||||
url = alias.BaseUrl + Utilities.UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", file.Folder.SiteId.ToString(), file.Folder.Path) + file.Name;
|
||||
break;
|
||||
}
|
||||
return url;
|
||||
return Utilities.FileUrl(alias, file.Folder.Path, file.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
|
@ -10,6 +10,7 @@ namespace Oqtane.Repository
|
|||
File UpdateFile(File file);
|
||||
File GetFile(int fileId);
|
||||
File GetFile(int fileId, bool tracking);
|
||||
File GetFile(int siteId, string folderPath, string fileName);
|
||||
void DeleteFile(int fileId);
|
||||
string GetFilePath(int fileId);
|
||||
string GetFilePath(File file);
|
||||
|
|
|
@ -134,7 +134,7 @@ namespace Oqtane.Repository
|
|||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions()
|
||||
});
|
||||
_folderRepository.AddFolder(new Folder { SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Public", Type = FolderTypes.Public, Path = Utilities.PathCombine("Public", Path.DirectorySeparatorChar.ToString()), Order = 1, ImageSizes = "", Capacity = 0, IsSystem = false,
|
||||
_folderRepository.AddFolder(new Folder { SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Public", Type = FolderTypes.Public, Path = "Public/", Order = 1, ImageSizes = "", Capacity = 0, IsSystem = false,
|
||||
Permissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.Browse, RoleNames.Admin, true),
|
||||
|
@ -144,7 +144,7 @@ namespace Oqtane.Repository
|
|||
});
|
||||
_folderRepository.AddFolder(new Folder
|
||||
{
|
||||
SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Users", Type = FolderTypes.Private, Path = Utilities.PathCombine("Users",Path.DirectorySeparatorChar.ToString()), Order = 3, ImageSizes = "", Capacity = 0, IsSystem = true,
|
||||
SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Users", Type = FolderTypes.Private, Path = "Users/", Order = 3, ImageSizes = "", Capacity = 0, IsSystem = true,
|
||||
Permissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.Browse, RoleNames.Admin, true),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Extensions;
|
||||
|
@ -41,7 +40,7 @@ namespace Oqtane.Repository
|
|||
}
|
||||
|
||||
// add folder for user
|
||||
Folder folder = _folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString()));
|
||||
Folder folder = _folders.GetFolder(user.SiteId, "Users/");
|
||||
if (folder != null)
|
||||
{
|
||||
_folders.AddFolder(new Folder
|
||||
|
@ -50,7 +49,7 @@ namespace Oqtane.Repository
|
|||
ParentId = folder.FolderId,
|
||||
Name = "My Folder",
|
||||
Type = FolderTypes.Private,
|
||||
Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()),
|
||||
Path = $"Users/{user.UserId}/",
|
||||
Order = 1,
|
||||
ImageSizes = "",
|
||||
Capacity = Constants.UserFolderCapacity,
|
||||
|
|
|
@ -26,6 +26,8 @@ namespace Oqtane.Shared
|
|||
|
||||
[Obsolete("Use PaneNames.Admin")]
|
||||
public const string AdminPane = PaneNames.Admin;
|
||||
|
||||
public static readonly string[] ReservedRoutes = { "api", "pages", "files" };
|
||||
public const string ModuleDelimiter = "*";
|
||||
public const string UrlParametersDelimiter = "!";
|
||||
|
||||
|
@ -91,6 +93,5 @@ namespace Oqtane.Shared
|
|||
public static readonly string HttpContextSiteSettingsKey = "SiteSettings";
|
||||
|
||||
public static readonly string MauiUserAgent = "MAUI";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,12 @@ namespace Oqtane.Shared
|
|||
return $"{alias.BaseUrl}{aliasUrl}{Constants.ContentUrl}{fileId}{method}";
|
||||
}
|
||||
|
||||
public static string FileUrl(Alias alias, string folderpath, string filename)
|
||||
{
|
||||
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
|
||||
return $"{alias.BaseUrl}{aliasUrl}/files/{folderpath.Replace("\\", "/")}{filename}";
|
||||
}
|
||||
|
||||
public static string ImageUrl(Alias alias, int fileId, int width, int height, string mode)
|
||||
{
|
||||
return ImageUrl(alias, fileId, width, height, mode, "", "", 0, false);
|
||||
|
@ -362,6 +368,7 @@ namespace Oqtane.Shared
|
|||
|
||||
public static string UrlCombine(params string[] segments)
|
||||
{
|
||||
segments = segments.Where(item => !string.IsNullOrEmpty(item) && item != "/" && item != "\\").ToArray();
|
||||
for (int i = 1; i < segments.Length; i++)
|
||||
{
|
||||
segments[i] = segments[i].Replace("\\", "/");
|
||||
|
|
Loading…
Reference in New Issue
Block a user