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:
Shaun Walker 2022-08-30 07:21:52 -04:00
parent d6bb802892
commit 075748d697
17 changed files with 199 additions and 43 deletions

View File

@ -329,7 +329,7 @@
}
}
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
if (PagePathIsDeleted(page.Path, page.SiteId, _pageList))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
return;
@ -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)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
@page "/files/{**path}"
@namespace Oqtane.Pages
@model Oqtane.Pages.FilesModel

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

View File

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

View File

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

View File

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

View 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),

View File

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

View File

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

View File

@ -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);
@ -361,7 +367,8 @@ 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("\\", "/");