lampac/Lampac/Engine/Middlewares/RequestInfo.cs
lampac-talks f843f04fd4 chore: initial commit 154.3
Signed-off-by: lampac-talks <lampac-talks@users.noreply.github.com>
2026-01-30 16:23:09 +03:00

275 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using Shared;
using Shared.Models;
using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class RequestInfo
{
#region RequestInfo
private readonly RequestDelegate _next;
IMemoryCache memoryCache;
public RequestInfo(RequestDelegate next, IMemoryCache mem)
{
_next = next;
memoryCache = mem;
}
#endregion
public Task Invoke(HttpContext httpContext)
{
bool IsWsRequest = httpContext.Request.Path.StartsWithSegments("/nws", StringComparison.OrdinalIgnoreCase) ||
httpContext.Request.Path.StartsWithSegments("/ws", StringComparison.OrdinalIgnoreCase);
#region stats
if (AppInit.conf.openstat.enable && !IsWsRequest)
{
var now = DateTime.UtcNow;
var counter = memoryCache.GetOrCreate($"stats:request:{now.Hour}:{now.Minute}", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
return new CounterRequestInfo();
});
Interlocked.Increment(ref counter.Value);
}
#endregion
bool IsLocalRequest = false;
string cf_country = null;
string clientIp = httpContext.Connection.RemoteIpAddress.ToString();
bool IsLocalIp = Shared.Engine.Utilities.IPNetwork.IsLocalIp(clientIp);
if (httpContext.Request.Headers.TryGetValue("localrequest", out StringValues _localpasswd) && _localpasswd.Count > 0)
{
if (!IsLocalIp && !AppInit.conf.BaseModule.allowExternalIpAccessToLocalRequest)
return httpContext.Response.WriteAsync("allowExternalIpAccessToLocalRequest false", httpContext.RequestAborted);
if (_localpasswd[0] != AppInit.rootPasswd)
return httpContext.Response.WriteAsync("error passwd", httpContext.RequestAborted);
IsLocalRequest = true;
if (httpContext.Request.Headers.TryGetValue("x-client-ip", out StringValues xip) && xip.Count > 0)
{
if (!string.IsNullOrEmpty(xip[0]))
clientIp = xip[0];
}
}
else if (AppInit.conf.real_ip_cf || AppInit.conf.listen.frontend == "cloudflare")
{
#region cloudflare
if (Program.cloudflare_ips != null && Program.cloudflare_ips.Count > 0)
{
try
{
var clientIPAddress = IPAddress.Parse(clientIp);
foreach (var cf in Program.cloudflare_ips)
{
if (new System.Net.IPNetwork(cf.prefix, cf.prefixLength).Contains(clientIPAddress))
{
if (httpContext.Request.Headers.TryGetValue("CF-Connecting-IP", out StringValues xip) && xip.Count > 0)
{
if (!string.IsNullOrEmpty(xip[0]))
clientIp = xip[0];
}
if (httpContext.Request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues xfp) && xfp.Count > 0)
{
if (!string.IsNullOrEmpty(xfp[0]))
{
if (xfp[0] == "http" || xfp[0] == "https")
httpContext.Request.Scheme = xfp;
}
}
if (httpContext.Request.Headers.TryGetValue("CF-IPCountry", out StringValues xcountry) && xcountry.Count > 0)
{
if (!string.IsNullOrEmpty(xcountry[0]))
cf_country = xcountry[0];
}
break;
}
}
}
catch { }
}
#endregion
}
// запрос с cloudflare, запрос не в админку
else if (httpContext.Request.Headers.ContainsKey("CF-Connecting-IP") && !httpContext.Request.Path.Value.StartsWith("/admin", StringComparison.OrdinalIgnoreCase))
{
// если не указан frontend и это не первоначальная установка, тогда выводим ошибку
if (string.IsNullOrEmpty(AppInit.conf.listen.frontend) && File.Exists("module/manifest.json"))
return httpContext.Response.WriteAsync(unknownFrontend, httpContext.RequestAborted);
}
var req = new RequestModel()
{
IsLocalRequest = IsLocalRequest,
IsLocalIp = IsLocalIp,
IP = clientIp,
Country = cf_country,
UserAgent = httpContext.Request.Headers.UserAgent
};
if (httpContext.Request.Headers.TryGetValue("X-Kit-AesGcm", out StringValues aesGcmKey) && aesGcmKey.Count > 0)
req.AesGcmKey = aesGcmKey;
#region Weblog Request
if (!IsLocalRequest && !IsWsRequest && AppInit.conf.weblog.enable)
{
if (AppInit.conf.WebSocket.type == "signalr")
{
if (AppInit.conf.BaseModule.ws && soks.weblog_clients.Count > 0)
soks.SendLog(builderLog(httpContext, req), "request");
}
else
{
if (AppInit.conf.BaseModule.nws && NativeWebSocket.weblog_clients.Count > 0)
NativeWebSocket.SendLog(builderLog(httpContext, req), "request");
}
}
#endregion
if (!string.IsNullOrEmpty(AppInit.conf.accsdb.domainId_pattern))
{
string uid = Regex.Match(httpContext.Request.Host.Host, AppInit.conf.accsdb.domainId_pattern).Groups[1].Value;
req.user = AppInit.conf.accsdb.findUser(uid);
req.user_uid = uid;
if (req.user == null)
return httpContext.Response.WriteAsync("user not found", httpContext.RequestAborted);
req.@params = AppInit.conf.accsdb.@params;
httpContext.Features.Set(req);
return _next(httpContext);
}
else
{
if (!IsWsRequest)
{
req.user = AppInit.conf.accsdb.findUser(httpContext, out string uid);
req.user_uid = uid;
if (req.user != null)
req.@params = AppInit.conf.accsdb.@params;
if (string.IsNullOrEmpty(req.user_uid))
req.user_uid = getuid(httpContext);
}
httpContext.Features.Set(req);
return _next(httpContext);
}
}
#region getuid
static readonly string[] uids = ["token", "account_email", "uid", "box_mac"];
static string getuid(HttpContext httpContext)
{
foreach (string id in uids)
{
if (httpContext.Request.Query.ContainsKey(id))
{
StringValues val = httpContext.Request.Query[id];
if (val.Count > 0 && IsValidUid(val[0]))
return val[0];
}
}
return null;
}
static bool IsValidUid(ReadOnlySpan<char> value)
{
if (value.IsEmpty)
return false;
foreach (char ch in value)
{
if
(
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
ch == '_' || ch == '+' || ch == '.' || ch == '-' || ch == '@' || ch == '='
)
{
continue;
}
return false;
}
return true;
}
#endregion
static string builderLog(HttpContext httpContext, RequestModel req)
{
var logBuilder = new System.Text.StringBuilder();
logBuilder.AppendLine($"{DateTime.Now}");
logBuilder.AppendLine($"IP: {req.IP} {req.Country}");
logBuilder.AppendLine($"URL: {AppInit.Host(httpContext)}{httpContext.Request.Path}{httpContext.Request.QueryString}\n");
foreach (var header in httpContext.Request.Headers)
logBuilder.AppendLine($"{header.Key}: {header.Value}");
return logBuilder.ToString();
}
static readonly string unknownFrontend = @"<!DOCTYPE html>
<html lang='ru'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>CloudFlare</title>
<link href='/control/npm/bootstrap.min.css' rel='stylesheet'>
</head>
<body>
<div class='container mt-5'>
<div class='card mt-4'>
<div class='card-body'>
<h5 class='card-title'>Укажите frontend для правильной обработки запроса</h5>
<br>
<p class='card-text'>Добавьте в init.conf следующий код:</p>
<pre style='background: #e9ecef; padding: 1em;'><code>""listen"": {
""frontend"": ""cloudflare""
}</code></pre>
<br>
<p class='card-text'>Либо отключите проверку CF-Connecting-IP:</p>
<pre style='background: #e9ecef; padding: 1em;'><code>""listen"": {
""frontend"": ""off""
}</code></pre>
<br>
<p class='card-text'>Так же параметр можно изменить в <a href='/admin' target='_blank'>админке</a>: Остальное, base, frontend</p>
</div>
</div>
</div>
</body>
</html>";
}
public class CounterRequestInfo
{
public int Value;
}
}