diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor
index 863db4f6..f3802045 100644
--- a/Oqtane.Client/Modules/Admin/Pages/Add.razor
+++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor
@@ -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)
{
diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor
index 3a1f4a67..36578432 100644
--- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor
@@ -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;
diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx
index 71c3f04f..25b893b7 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx
@@ -237,4 +237,7 @@
Meta:
+
+ The page name {0} is reserved. Please enter a different name for your page.
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx
index 5dc82a55..dec99529 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx
@@ -270,4 +270,7 @@
Meta:
+
+ The page name {0} is reserved. Please enter a different name for your page.
+
\ No newline at end of file
diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs
index 8ae32116..5bdede15 100644
--- a/Oqtane.Server/Controllers/FolderController.cs
+++ b/Oqtane.Server/Controllers/FolderController.cs
@@ -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);
- }
}
}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index bd878f4e..5f17f40c 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -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)))
diff --git a/Oqtane.Server/Infrastructure/TenantManager.cs b/Oqtane.Server/Infrastructure/TenantManager.cs
index 8ebcc20d..f34bd3ca 100644
--- a/Oqtane.Server/Infrastructure/TenantManager.cs
+++ b/Oqtane.Server/Infrastructure/TenantManager.cs
@@ -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);
}
diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs
index cdefb7c7..0b26df1d 100644
--- a/Oqtane.Server/Infrastructure/UpgradeManager.cs
+++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs
@@ -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();
+ var folderRepository = scope.ServiceProvider.GetRequiredService();
+ 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}");
+ }
+ }
+
}
}
diff --git a/Oqtane.Server/Pages/Files.cshtml b/Oqtane.Server/Pages/Files.cshtml
new file mode 100644
index 00000000..ea77fc5f
--- /dev/null
+++ b/Oqtane.Server/Pages/Files.cshtml
@@ -0,0 +1,3 @@
+@page "/files/{**path}"
+@namespace Oqtane.Pages
+@model Oqtane.Pages.FilesModel
diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs
new file mode 100644
index 00000000..b446c4d9
--- /dev/null
+++ b/Oqtane.Server/Pages/Files.cshtml.cs
@@ -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));
+ }
+ }
+}
diff --git a/Oqtane.Server/Repository/AliasRepository.cs b/Oqtane.Server/Repository/AliasRepository.cs
index c2d8d690..cbe3bd9c 100644
--- a/Oqtane.Server/Repository/AliasRepository.cs
+++ b/Oqtane.Server/Repository/AliasRepository.cs
@@ -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;
diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs
index dba0d491..6dbb92fd 100644
--- a/Oqtane.Server/Repository/FileRepository.cs
+++ b/Oqtane.Server/Repository/FileRepository.cs
@@ -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 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);
}
}
}
diff --git a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs
index adfe8f89..0da50f09 100644
--- a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs
+++ b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs
@@ -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);
diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs
index ded32453..0db7c251 100644
--- a/Oqtane.Server/Repository/SiteRepository.cs
+++ b/Oqtane.Server/Repository/SiteRepository.cs
@@ -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
{
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
{
new Permission(PermissionNames.Browse, RoleNames.Admin, true),
diff --git a/Oqtane.Server/Repository/UserRepository.cs b/Oqtane.Server/Repository/UserRepository.cs
index 7f72585c..6c9e31d7 100644
--- a/Oqtane.Server/Repository/UserRepository.cs
+++ b/Oqtane.Server/Repository/UserRepository.cs
@@ -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,
diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs
index 926b7705..aabe2ef6 100644
--- a/Oqtane.Shared/Shared/Constants.cs
+++ b/Oqtane.Shared/Shared/Constants.cs
@@ -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";
-
}
}
diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs
index db3b439c..8717c6b2 100644
--- a/Oqtane.Shared/Shared/Utilities.cs
+++ b/Oqtane.Shared/Shared/Utilities.cs
@@ -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("\\", "/");