|
|
@ -10,61 +10,74 @@ namespace Apewer.Web |
|
|
|
public class StaticController : ApiController |
|
|
|
{ |
|
|
|
|
|
|
|
/// <summary>允许服务器端包含(Server Side Include)。</summary>
|
|
|
|
/// <remarks>默认值:允许。</remarks>
|
|
|
|
protected bool AllowSSI { get; set; } = true; |
|
|
|
|
|
|
|
/// <summary>当执行目录且没有默认文档时,枚举子目录和子文件。</summary>
|
|
|
|
/// <remarks>默认值:不允许。</remarks>
|
|
|
|
protected bool AllowEnumerate { get; set; } = false; |
|
|
|
|
|
|
|
/// <summary>获取已解析的站点根目录。</summary>
|
|
|
|
protected string Root { get => _root?.Value; } |
|
|
|
|
|
|
|
List<string> PathSegments; |
|
|
|
#region 初始化
|
|
|
|
|
|
|
|
Func<StaticController, JumpStatement> _initializer = null; |
|
|
|
Class<string> _root = null; |
|
|
|
|
|
|
|
/// <summary></summary>
|
|
|
|
public StaticController() : base((c) => { ((StaticController)c).Initialize(); return false; }) { } |
|
|
|
|
|
|
|
/// <summary></summary>
|
|
|
|
public StaticController(Func<StaticController, JumpStatement> initializer) : base((c) => { ((StaticController)c).Initialize(); return false; }) |
|
|
|
{ |
|
|
|
_initializer = initializer; |
|
|
|
} |
|
|
|
|
|
|
|
void Initialize() |
|
|
|
{ |
|
|
|
if (Request == null || Request.Url == null) return; |
|
|
|
var absolute = Request.Url.AbsolutePath; |
|
|
|
var split = absolute == null ? new string[0] : absolute.Split('/', '\\'); |
|
|
|
PathSegments = new List<string>(split.Length); |
|
|
|
foreach (var item in split) |
|
|
|
|
|
|
|
if (_initializer != null) |
|
|
|
{ |
|
|
|
var trim = TextUtility.Trim(TextUtility.DecodeUrl(item)); |
|
|
|
if (string.IsNullOrEmpty(trim)) continue; |
|
|
|
if (trim == "." || trim == "..") continue; |
|
|
|
PathSegments.Add(trim); |
|
|
|
var jump = _initializer.Invoke(this); |
|
|
|
if (jump == JumpStatement.Break) return; |
|
|
|
} |
|
|
|
absolute = TextUtility.Join("/", PathSegments); |
|
|
|
|
|
|
|
var path = MapPath(absolute); |
|
|
|
if (PathSegments.Count < 1) Directory(GetRoot()); |
|
|
|
else if (IsBlocked(PathSegments[0])) Respond404(path); |
|
|
|
else if (StorageUtility.FileExists(path)) File(path); |
|
|
|
else if (StorageUtility.DirectoryExists(path)) Directory(path); |
|
|
|
else Respond404(path); |
|
|
|
} |
|
|
|
|
|
|
|
#region virtual
|
|
|
|
// 获取相对路径
|
|
|
|
var urlPath = GetUrlPath(); |
|
|
|
if (string.IsNullOrEmpty(urlPath)) return; |
|
|
|
|
|
|
|
/// <summary>响应 404 状态。</summary>
|
|
|
|
/// <remarks>默认:设置状态为 404,不输出内容。</remarks>
|
|
|
|
protected virtual void Respond404(string path) |
|
|
|
{ |
|
|
|
Response.Model = new ApiStatusModel(404); |
|
|
|
// 内置的处理程序
|
|
|
|
ApiUtility.StopReturn(Response); |
|
|
|
Execute(urlPath); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>响应 403 状态。</summary>
|
|
|
|
/// <remarks>默认:设置状态为 403,不输出内容。</remarks>
|
|
|
|
protected virtual void Respond403(string path) |
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region 自定义
|
|
|
|
|
|
|
|
/// <summary>允许服务器端包含(Server Side Include)。</summary>
|
|
|
|
/// <remarks>默认值:允许。</remarks>
|
|
|
|
protected bool AllowSSI { get; set; } = true; |
|
|
|
|
|
|
|
/// <summary>允许 GetRoot 方法访问应用程序目录。</summary>
|
|
|
|
/// <remarks>默认值:不允许。</remarks>
|
|
|
|
protected bool AllowApplicationPath { get; set; } = false; |
|
|
|
|
|
|
|
/// <summary>获取已解析的站点根目录。</summary>
|
|
|
|
protected string Root { get => _root?.Value; } |
|
|
|
|
|
|
|
/// <summary>执行路径。</summary>
|
|
|
|
protected virtual void Execute(string urlPath) |
|
|
|
{ |
|
|
|
Response.Model = new ApiStatusModel(403); |
|
|
|
// 自定义处理
|
|
|
|
var model = GetModel(urlPath); |
|
|
|
if (model != null) |
|
|
|
{ |
|
|
|
Response.Model = model; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 自定义处理
|
|
|
|
var result = GetResult(urlPath); |
|
|
|
if (result != null) |
|
|
|
{ |
|
|
|
Response.Model = result; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
InternalExecute(urlPath); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>获取此静态站点的目录。</summary>
|
|
|
@ -111,156 +124,365 @@ namespace Apewer.Web |
|
|
|
return @static; |
|
|
|
} |
|
|
|
|
|
|
|
_root = new Class<string>(app); |
|
|
|
return app; |
|
|
|
if (AllowApplicationPath) |
|
|
|
{ |
|
|
|
_root = new Class<string>(app); |
|
|
|
return app; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
_root = new Class<string>(null); |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>获取 URL 路径。</summary>
|
|
|
|
protected virtual string GetUrlPath() => Request.Url.AbsolutePath; |
|
|
|
|
|
|
|
/// <summary>获取响应结果。</summary>
|
|
|
|
protected virtual IActionResult GetResult(string urlPath) => null; |
|
|
|
|
|
|
|
/// <summary>获取响应结果。</summary>
|
|
|
|
protected virtual ApiModel GetModel(string urlPath) => null; |
|
|
|
|
|
|
|
/// <summary>加载 Web.zip 文件。</summary>
|
|
|
|
protected virtual Dictionary<string, byte[]> LoadWebZip() |
|
|
|
{ |
|
|
|
var names = new string[] { "Web.zip", "web.zip" }; |
|
|
|
|
|
|
|
foreach (var name in names) |
|
|
|
{ |
|
|
|
var path = StorageUtility.CombinePath(RuntimeUtility.ApplicationPath, name); |
|
|
|
if (StorageUtility.FileExists(path)) |
|
|
|
{ |
|
|
|
var data = StorageUtility.ReadFile(path); |
|
|
|
var dict = BytesUtility.FromZip(data); |
|
|
|
return dict; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>从扩展名获取内容类型。</summary>
|
|
|
|
protected virtual string ContentType(string extension) => NetworkUtility.Mime(extension); |
|
|
|
|
|
|
|
/// <summary>从扩展名和文件路径获取过期时间。</summary>
|
|
|
|
/// <remarks>默认值:0,不缓存。</remarks>
|
|
|
|
protected virtual int Expires(string extension, string path) => 0; |
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region 开放方法
|
|
|
|
|
|
|
|
/// <summary>响应重定向。</summary>
|
|
|
|
protected virtual void RespondRedirect(string urlPath, string location) => Response.Model = new ApiRedirectModel(location); |
|
|
|
|
|
|
|
/// <summary>响应 400 状态。</summary>
|
|
|
|
/// <remarks>默认:设置状态为 400,不输出内容。</remarks>
|
|
|
|
protected virtual void Respond400(string path, string reason) => Response.Model = new ApiStatusModel(400); |
|
|
|
|
|
|
|
/// <summary>响应 404 状态。</summary>
|
|
|
|
/// <remarks>默认:设置状态为 404,不输出内容。</remarks>
|
|
|
|
protected virtual void Respond404(string urlPath) => Response.Model = new ApiStatusModel(404); |
|
|
|
|
|
|
|
/// <summary>响应 403 状态。</summary>
|
|
|
|
/// <remarks>默认:设置状态为 403,不输出内容。</remarks>
|
|
|
|
protected virtual void Respond403(string urlPath) => Response.Model = new ApiStatusModel(403); |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region 内置的执行程序
|
|
|
|
|
|
|
|
const bool WipeBom = true; |
|
|
|
|
|
|
|
// 默认文档的文件名
|
|
|
|
static string[] DefaultFileNames = new string[] { "index.html", "index.htm", "default.html", "default.htm" }; |
|
|
|
|
|
|
|
// 文件名中不允许出现的字符
|
|
|
|
static char[] InvalidFileNameChars = Path.GetInvalidFileNameChars(); |
|
|
|
|
|
|
|
/// <summary>已解析到本地文本路径,执行此路径。</summary>
|
|
|
|
/// <remarks>默认:输出文件内容,文件不存在时输出 404 状态。</remarks>
|
|
|
|
protected virtual void File(string path) |
|
|
|
// Zip 文件的缓存
|
|
|
|
static Dictionary<Type, Dictionary<string, byte[]>> WebZips = new Dictionary<Type, Dictionary<string, byte[]>>(); |
|
|
|
|
|
|
|
// 解析 URL 的路径,获取本地路径。
|
|
|
|
string MapPath(string urlPath) |
|
|
|
{ |
|
|
|
if (!System.IO.File.Exists(path)) |
|
|
|
var path = GetRoot(); |
|
|
|
if (path.IsEmpty()) return ""; |
|
|
|
if (string.IsNullOrEmpty(urlPath)) return path; |
|
|
|
|
|
|
|
foreach (var split in urlPath.Split('/', '\\')) |
|
|
|
{ |
|
|
|
Respond404(path); |
|
|
|
var segment = split.ToTrim(); |
|
|
|
if (segment == null || segment == "" || segment == "." || segment == "..") continue; |
|
|
|
path = StorageUtility.CombinePath(path, segment); |
|
|
|
} |
|
|
|
return path; |
|
|
|
} |
|
|
|
|
|
|
|
void InternalExecute(string urlPath) |
|
|
|
{ |
|
|
|
if (urlPath.IsEmpty()) |
|
|
|
{ |
|
|
|
Respond400(urlPath, "请求的 URL 路径无效。"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 获取文件扩展名。
|
|
|
|
var ext = Path.GetExtension(path).Lower(); |
|
|
|
if (ext.Length > 1 && ext.StartsWith(".")) ext = ext.Substring(1); |
|
|
|
// 检查路径
|
|
|
|
var urlIsDir = urlPath.EndsWith("/") || urlPath.EndsWith("\\"); |
|
|
|
var urlExt = null as string; |
|
|
|
var urlIsHtml = false; |
|
|
|
var segments = new List<string>(); |
|
|
|
{ |
|
|
|
var split = urlPath == null ? new string[0] : urlPath.Split('/', '\\'); |
|
|
|
segments.Capacity = split.Length; |
|
|
|
foreach (var item in split) |
|
|
|
{ |
|
|
|
var segment = TextUtility.DecodeUrl(item).Trim(); |
|
|
|
if (string.IsNullOrEmpty(segment)) continue; |
|
|
|
if (segment == "." || segment == "..") continue; |
|
|
|
|
|
|
|
// 检查无效字符
|
|
|
|
foreach (var c in segment) |
|
|
|
{ |
|
|
|
if (Array.IndexOf(InvalidFileNameChars, c) > -1) |
|
|
|
{ |
|
|
|
Respond400(urlPath, $"请求的路径中含有无效字符。"); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
segments.Add(segment); |
|
|
|
} |
|
|
|
|
|
|
|
// 重置 UrlPath
|
|
|
|
urlPath = "/" + segments.Join("/"); |
|
|
|
if (segments.Count > 0) |
|
|
|
{ |
|
|
|
// 请求筛选
|
|
|
|
if (IsBlocked(segments[0])) |
|
|
|
{ |
|
|
|
Respond404(urlPath); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
urlExt = segments[segments.Count - 1].ToLower().Split('.').Last(); |
|
|
|
urlIsHtml = urlExt == "html" || urlExt == "htm" || urlExt == "shtml"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 本地存储路径
|
|
|
|
var storagePath = MapPath(urlPath); |
|
|
|
|
|
|
|
// 按扩展名获取缓存过期时间。
|
|
|
|
var expires = Expires(ext, path); |
|
|
|
// 本地文件
|
|
|
|
if (StorageUtility.FileExists(storagePath)) |
|
|
|
{ |
|
|
|
ExecuteFile(storagePath, urlPath, urlExt, urlIsHtml); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 按扩展名获取 Content-Type。
|
|
|
|
var type = ContentType(ext); |
|
|
|
if (string.IsNullOrEmpty(type)) type = NetworkUtility.Mime(ext); |
|
|
|
// 目录
|
|
|
|
if (StorageUtility.DirectoryExists(storagePath)) |
|
|
|
{ |
|
|
|
var filePath = SearchDefaultFile(storagePath); |
|
|
|
if (!string.IsNullOrEmpty(filePath)) |
|
|
|
{ |
|
|
|
if (urlIsDir) |
|
|
|
{ |
|
|
|
ExecuteFile(filePath, urlPath, urlExt, urlIsHtml); |
|
|
|
return; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
var location = urlPath + "/"; |
|
|
|
RespondRedirect(urlPath, location); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Server Side Includes
|
|
|
|
if (AllowSSI && ext == "html" || ext == "htm" || ext == "shtml") |
|
|
|
// WebZip
|
|
|
|
var webZip = GetWebZip(); |
|
|
|
if (webZip != null) |
|
|
|
{ |
|
|
|
var html = ReadWithSSI(path); |
|
|
|
var bytes = html.Bytes(); |
|
|
|
// 文件
|
|
|
|
{ |
|
|
|
if (webZip.TryGetValue(urlPath, out var data)) |
|
|
|
{ |
|
|
|
ExecuteFile(data, urlExt, urlIsHtml); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 尝试匹配默认文档
|
|
|
|
foreach (var defaultName in DefaultFileNames) |
|
|
|
{ |
|
|
|
var defaultPath = TextUtility.AssureEnds(urlPath, "/") + defaultName; |
|
|
|
if (webZip.TryGetValue(defaultPath, out var data)) |
|
|
|
{ |
|
|
|
if (urlIsDir) |
|
|
|
{ |
|
|
|
ExecuteFile(data, defaultName, true); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
var location = TextUtility.AssureEnds(urlPath, "/"); |
|
|
|
RespondRedirect(urlPath, location); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var model = new ApiBytesModel(); |
|
|
|
if (expires > 0) model.Expires = expires; |
|
|
|
model.ContentType = type; |
|
|
|
model.Bytes = bytes; |
|
|
|
// 未知
|
|
|
|
Respond404(urlPath); |
|
|
|
} |
|
|
|
|
|
|
|
void ExecuteFile(string storagePath, string urlPath, string urlExt, bool urlIsHtml) |
|
|
|
{ |
|
|
|
var contentType = ContentType(urlExt); |
|
|
|
if (urlIsHtml) |
|
|
|
{ |
|
|
|
var text = ReadTextFile(urlPath, 0); |
|
|
|
var model = new ApiTextModel(text, contentType); |
|
|
|
Response.Model = model; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
var stream = StorageUtility.OpenFile(path, true); |
|
|
|
|
|
|
|
var model = new ApiStreamModel(); |
|
|
|
if (expires > 0) model.Expires = expires; |
|
|
|
model.ContentType = type; |
|
|
|
model.AutoDispose = true; |
|
|
|
model.Stream = stream; |
|
|
|
try |
|
|
|
{ |
|
|
|
var stream = StorageUtility.OpenFile(storagePath, true); |
|
|
|
var model = new ApiStreamModel(); |
|
|
|
model.ContentType = contentType; |
|
|
|
model.AutoDispose = true; |
|
|
|
model.Stream = stream; |
|
|
|
Response.Model = model; |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
Respond400(storagePath, $"<{ex.GetType().Name}> {ex.Message}"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ExecuteFile(byte[] data, string urlExt, bool urlIsHtml) |
|
|
|
{ |
|
|
|
var contentType = ContentType(urlExt); |
|
|
|
if (urlIsHtml) |
|
|
|
{ |
|
|
|
var text = data.Text(); |
|
|
|
text = ServerSideIncludes(text, 0); |
|
|
|
var model = new ApiTextModel(text, contentType); |
|
|
|
Response.Model = model; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
var model = new ApiBytesModel(data, contentType); |
|
|
|
Response.Model = model; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>已解析到本地目录路径,执行此路径。</summary>
|
|
|
|
/// <remarks>默认:输出文件内容,文件不存在时输出 404 状态。</remarks>
|
|
|
|
protected virtual void Directory(string path) |
|
|
|
/// <summary>读取文本文件。</summary>
|
|
|
|
string ReadTextFile(string urlPath, int recursive) |
|
|
|
{ |
|
|
|
if (!System.IO.Directory.Exists(path)) |
|
|
|
// 主文件保留原有的 BOM
|
|
|
|
var wipeBom = recursive > 0; |
|
|
|
|
|
|
|
// 本地文件
|
|
|
|
var path = MapPath(urlPath); |
|
|
|
if (StorageUtility.FileExists(path)) |
|
|
|
{ |
|
|
|
Respond404(path); |
|
|
|
return; |
|
|
|
var data = StorageUtility.ReadFile(path, wipeBom); |
|
|
|
var text = data.Text(); |
|
|
|
text = ServerSideIncludes(text, recursive); |
|
|
|
return text; |
|
|
|
} |
|
|
|
|
|
|
|
var @default = Default(path); |
|
|
|
if (!string.IsNullOrEmpty(@default)) |
|
|
|
// Zip
|
|
|
|
var webZip = GetWebZip(); |
|
|
|
if (webZip != null) |
|
|
|
{ |
|
|
|
File(@default); |
|
|
|
return; |
|
|
|
if (webZip.TryGetValue(urlPath, out var data)) |
|
|
|
{ |
|
|
|
var text = data.Text(); |
|
|
|
text = ServerSideIncludes(text, recursive); |
|
|
|
return text; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (AllowEnumerate) Response.Data = ListChildren(path); |
|
|
|
else Respond403(path); |
|
|
|
// 文件不存在
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>在指定目录下搜索默认文件。</summary>
|
|
|
|
/// <returns>完整文件路径,当搜索失败时返回 NULL。</returns>
|
|
|
|
protected string Default(string directory) |
|
|
|
string SearchDefaultFile(string directoryPath) |
|
|
|
{ |
|
|
|
var subs = StorageUtility.GetSubFiles(directory); |
|
|
|
if (subs.Count < 0) return null; |
|
|
|
subs.Sort(); |
|
|
|
|
|
|
|
var names = new Dictionary<string, string>(subs.Count); |
|
|
|
foreach (var sub in subs) |
|
|
|
// 获取子文件路径
|
|
|
|
var filePaths = StorageUtility.GetSubFiles(directoryPath); |
|
|
|
if (filePaths.Count < 0) return null; |
|
|
|
filePaths.Sort(); |
|
|
|
|
|
|
|
// 获取文件名
|
|
|
|
var dict = new Dictionary<string, string>(filePaths.Count); |
|
|
|
foreach (var filePath in filePaths) |
|
|
|
{ |
|
|
|
var name = Path.GetFileName(sub); |
|
|
|
if (names.ContainsKey(name)) continue; |
|
|
|
names.Add(name, sub); |
|
|
|
var name = Path.GetFileName(filePath).ToLower(); |
|
|
|
if (dict.ContainsKey(name)) continue; |
|
|
|
dict.Add(name, filePath); |
|
|
|
} |
|
|
|
foreach (var sub in subs) |
|
|
|
|
|
|
|
// 按顺序获取
|
|
|
|
foreach (var defaultFileName in DefaultFileNames) |
|
|
|
{ |
|
|
|
var name = Path.GetFileName(sub); |
|
|
|
var lower = name.ToLower(); |
|
|
|
if (lower == name) continue; |
|
|
|
if (names.ContainsKey(lower)) continue; |
|
|
|
names.Add(lower, sub); |
|
|
|
if (dict.TryGetValue("index.html", out var physicalPath)) |
|
|
|
{ |
|
|
|
return physicalPath; |
|
|
|
} |
|
|
|
} |
|
|
|
if (names.ContainsKey("index.html")) return names["index.html"]; |
|
|
|
if (names.ContainsKey("index.htm")) return names["index.htm"]; |
|
|
|
if (names.ContainsKey("default.html")) return names["default.html"]; |
|
|
|
if (names.ContainsKey("default.htm")) return names["default.htm"]; |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region private
|
|
|
|
|
|
|
|
// 解析 URL 的路径,获取本地路径。
|
|
|
|
string MapPath(string urlPath) |
|
|
|
Dictionary<string, byte[]> GetWebZip() |
|
|
|
{ |
|
|
|
var path = GetRoot(); |
|
|
|
if (!string.IsNullOrEmpty(urlPath)) |
|
|
|
var instanceType = GetType(); |
|
|
|
lock (WebZips) |
|
|
|
{ |
|
|
|
foreach (var split in urlPath.Split('/')) |
|
|
|
if (WebZips.TryGetValue(instanceType, out var webZip)) |
|
|
|
{ |
|
|
|
return webZip; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
var seg = split.ToTrim(); |
|
|
|
if (string.IsNullOrEmpty(seg)) continue; |
|
|
|
if (seg == "." || seg == "..") continue; |
|
|
|
path = StorageUtility.CombinePath(path, seg); |
|
|
|
var dict = LoadWebZip(); |
|
|
|
if (dict != null) |
|
|
|
{ |
|
|
|
var temp = new Dictionary<string, byte[]>(dict.Count); |
|
|
|
foreach (var kvp in dict) |
|
|
|
{ |
|
|
|
if (kvp.Key.IsEmpty()) continue; |
|
|
|
var url = string.Join("/", kvp.Key.Split('/', '\\')); |
|
|
|
url = "/" + url.ToLower(); |
|
|
|
if (temp.ContainsKey(url)) continue; |
|
|
|
temp.Add(url, kvp.Value); |
|
|
|
} |
|
|
|
dict = temp; |
|
|
|
} |
|
|
|
WebZips.Add(instanceType, dict); |
|
|
|
} |
|
|
|
} |
|
|
|
return path; |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// Server Side Includes
|
|
|
|
string ReadWithSSI(string path, int recursive = 0) |
|
|
|
string ServerSideIncludes(string text, int recursive) |
|
|
|
{ |
|
|
|
if (recursive > 10) return ""; |
|
|
|
|
|
|
|
var input = StorageUtility.ReadFile(path, true); |
|
|
|
if (input == null || input.LongLength < 1) return ""; |
|
|
|
|
|
|
|
// 尝试解码。
|
|
|
|
var html = TextUtility.FromBytes(input); |
|
|
|
if (string.IsNullOrEmpty(html)) return ""; |
|
|
|
if (!AllowSSI) return text; |
|
|
|
if (recursive > 10) return text; |
|
|
|
if (string.IsNullOrEmpty(text)) return ""; |
|
|
|
|
|
|
|
// 按首尾截取。
|
|
|
|
const string left = "<!--"; |
|
|
|
const string right = "-->"; |
|
|
|
const string head = "#include virtual="; |
|
|
|
var sb = new StringBuilder(); |
|
|
|
var text = html; |
|
|
|
while (true) |
|
|
|
{ |
|
|
|
var offset = text.IndexOf(left); |
|
|
@ -288,9 +510,9 @@ namespace Apewer.Web |
|
|
|
{ |
|
|
|
temp = temp.Substring(head.Length); |
|
|
|
temp = temp.Replace("\"", ""); |
|
|
|
var subPath = MapPath(temp); |
|
|
|
var subText = ReadWithSSI(subPath, recursive + 1); |
|
|
|
if (subText != null && subText.Length > 0) sb.Append(subText); |
|
|
|
|
|
|
|
var includeText = ReadTextFile(temp, recursive + 1); |
|
|
|
if (includeText != null && includeText.Length > 0) sb.Append(includeText); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
@ -305,6 +527,10 @@ namespace Apewer.Web |
|
|
|
return output; |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region private
|
|
|
|
|
|
|
|
/// <summary>列出指定目录的子项。</summary>
|
|
|
|
Json ListChildren(string directory) |
|
|
|
{ |
|
|
@ -363,7 +589,8 @@ namespace Apewer.Web |
|
|
|
return array; |
|
|
|
} |
|
|
|
|
|
|
|
static bool IsBlocked(string segment) |
|
|
|
/// <summary>请求筛选:检查段内容是要被屏蔽。</summary>
|
|
|
|
bool IsBlocked(string segment) |
|
|
|
{ |
|
|
|
if (string.IsNullOrEmpty(segment)) return true; |
|
|
|
var lower = segment.ToLower(); |
|
|
|