128 lines
4.4 KiB
C#
128 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.Primitives;
|
|
using System.Buffers;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace Shared.Engine
|
|
{
|
|
public static class AccsDbInvk
|
|
{
|
|
static readonly ThreadLocal<StringBuilder> sb = new(() => new StringBuilder(PoolInvk.rentChunk));
|
|
|
|
public static string Args(string uri, HttpContext httpContext)
|
|
{
|
|
StringBuilder args = sb.Value;
|
|
args.Clear();
|
|
|
|
args.Append(uri);
|
|
char split = uri.Contains('?') ? '&' : '?';
|
|
|
|
if (httpContext.Request.Query.ContainsKey("account_email") && !uri.Contains("account_email=", StringComparison.OrdinalIgnoreCase))
|
|
AppendParam(ref split, args, "account_email", httpContext.Request.Query["account_email"]);
|
|
|
|
if (httpContext.Request.Query.ContainsKey("uid") && !uri.Contains("uid=", StringComparison.OrdinalIgnoreCase))
|
|
AppendParam(ref split, args, "uid", httpContext.Request.Query["uid"]);
|
|
|
|
if (httpContext.Request.Query.ContainsKey("token") && !uri.Contains("token=", StringComparison.OrdinalIgnoreCase))
|
|
AppendParam(ref split, args, "token", httpContext.Request.Query["token"]);
|
|
|
|
if (httpContext.Request.Query.ContainsKey("box_mac") && !uri.Contains("box_mac=", StringComparison.OrdinalIgnoreCase))
|
|
AppendParam(ref split, args, "box_mac", httpContext.Request.Query["box_mac"]);
|
|
|
|
if (httpContext.Request.Query.ContainsKey("nws_id") && !uri.Contains("nws_id=", StringComparison.OrdinalIgnoreCase))
|
|
AppendParam(ref split, args, "nws_id", httpContext.Request.Query["nws_id"]);
|
|
|
|
if (args.Length == 0)
|
|
return uri;
|
|
|
|
return args.ToString();
|
|
}
|
|
|
|
|
|
static void AppendParam(ref char split, StringBuilder sb, string key, in StringValues values)
|
|
{
|
|
if (values.Count == 0)
|
|
return;
|
|
|
|
string s = values[0];
|
|
if (string.IsNullOrEmpty(s))
|
|
return;
|
|
|
|
ReadOnlySpan<char> span = s.AsSpan().Trim();
|
|
|
|
sb.Append(split);
|
|
if (split == '?')
|
|
split = '&';
|
|
|
|
sb.Append(key);
|
|
sb.Append('=');
|
|
|
|
AppendUrlEncodedLowerInvariant(sb, span);
|
|
}
|
|
|
|
static void AppendUrlEncodedLowerInvariant(StringBuilder sb, ReadOnlySpan<char> value)
|
|
{
|
|
Span<byte> utf8 = stackalloc byte[4]; // максимум 4 байта на один Rune (UTF-8)
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
{
|
|
char ch = char.ToLowerInvariant(value[i]);
|
|
|
|
if (!IsAllowedChar(ch))
|
|
continue;
|
|
|
|
if (IsUnreserved(ch))
|
|
{
|
|
sb.Append(ch);
|
|
continue;
|
|
}
|
|
|
|
if (ch == ' ')
|
|
{
|
|
sb.Append("%20");
|
|
continue;
|
|
}
|
|
|
|
// Корректно декодируем один Unicode scalar (1 или 2 char UTF-16)
|
|
ReadOnlySpan<char> tail = value.Slice(i);
|
|
OperationStatus status = Rune.DecodeFromUtf16(tail, out Rune rune, out int consumed);
|
|
|
|
if (status != OperationStatus.Done || consumed <= 0)
|
|
{
|
|
// fallback: кодируем текущий char как есть
|
|
rune = new Rune(value[i]);
|
|
consumed = 1;
|
|
}
|
|
|
|
int len = rune.EncodeToUtf8(utf8);
|
|
AppendPercentEncodedBytes(sb, utf8.Slice(0, len));
|
|
|
|
i += consumed - 1; // consumed = 1 или 2
|
|
}
|
|
}
|
|
|
|
static bool IsUnreserved(char c)
|
|
=> (c >= 'a' && c <= 'z')
|
|
|| (c >= '0' && c <= '9')
|
|
|| c == '-' || c == '_' || c == '.';
|
|
|
|
static bool IsAllowedChar(char c)
|
|
=> (c >= 'a' && c <= 'z')
|
|
|| (c >= '0' && c <= '9')
|
|
|| c == '@' || c == '.' || c == '-' || c == '_' || c == '=';
|
|
|
|
static readonly string encodedHex = "0123456789ABCDEF";
|
|
|
|
static void AppendPercentEncodedBytes(StringBuilder sb, ReadOnlySpan<byte> bytes)
|
|
{
|
|
foreach (byte b in bytes)
|
|
{
|
|
sb.Append('%');
|
|
sb.Append(encodedHex[b >> 4]);
|
|
sb.Append(encodedHex[b & 0x0F]);
|
|
}
|
|
}
|
|
}
|
|
}
|