You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
8.8 KiB
314 lines
8.8 KiB
using Apewer;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace Apewer.Internals
|
|
{
|
|
|
|
internal class UrlEncoding
|
|
{
|
|
|
|
public static string Encode(string plain)
|
|
{
|
|
return TextUtility.FromBytes(Encode(TextUtility.Bytes(plain)));
|
|
}
|
|
|
|
public static string Decode(string escaped)
|
|
{
|
|
return Decode(escaped, Encoding.UTF8);
|
|
}
|
|
|
|
private UrlEncoding() { }
|
|
|
|
#region encode static
|
|
|
|
/// <summary>对 URL 编码。</summary>
|
|
static byte[] Encode(byte[] bytes)
|
|
{
|
|
if (bytes == null) return null;
|
|
if (bytes.Length < 1) return BytesUtility.Empty;
|
|
|
|
var offset = 0;
|
|
var count = bytes.Length;
|
|
|
|
int num = 0;
|
|
int num2 = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
char c = (char)bytes[offset + i];
|
|
if (c == ' ')
|
|
{
|
|
num++;
|
|
}
|
|
else if (!IsUrlSafeChar(c))
|
|
{
|
|
num2++;
|
|
}
|
|
}
|
|
if (num == 0 && num2 == 0)
|
|
{
|
|
if (offset == 0 && bytes.Length == count)
|
|
{
|
|
return bytes;
|
|
}
|
|
byte[] array = new byte[count];
|
|
Buffer.BlockCopy(bytes, offset, array, 0, count);
|
|
return array;
|
|
}
|
|
byte[] array2 = new byte[count + num2 * 2];
|
|
int num3 = 0;
|
|
for (int j = 0; j < count; j++)
|
|
{
|
|
byte b = bytes[offset + j];
|
|
char c2 = (char)b;
|
|
if (IsUrlSafeChar(c2))
|
|
{
|
|
array2[num3++] = b;
|
|
}
|
|
else if (c2 == ' ')
|
|
{
|
|
array2[num3++] = 43;
|
|
}
|
|
else
|
|
{
|
|
array2[num3++] = 37;
|
|
array2[num3++] = (byte)IntToHex(b >> 4 & 0xF);
|
|
array2[num3++] = (byte)IntToHex(b & 0xF);
|
|
}
|
|
}
|
|
return array2;
|
|
}
|
|
|
|
static bool IsUrlSafeChar(char ch)
|
|
{
|
|
//const string lower = "abcdefghijklmnopqrstuvwxyz";
|
|
//const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
//const string numbers = "0123456789";
|
|
//const string symbols = "!()*-._";
|
|
|
|
if (ch >= 'a' && ch <= 'z') return true;
|
|
if (ch >= 'A' && ch <= 'Z') return true;
|
|
if (ch >= '0' && ch <= '9') return true;
|
|
switch (ch)
|
|
{
|
|
case '!':
|
|
case '(':
|
|
case ')':
|
|
case '*':
|
|
case '-':
|
|
case '.':
|
|
case '_':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static char IntToHex(int n)
|
|
{
|
|
if (n <= 9)
|
|
{
|
|
return (char)(n + 48);
|
|
}
|
|
return (char)(n - 10 + 97);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region decode instance
|
|
|
|
private int _bufferSize;
|
|
|
|
private int _numChars;
|
|
|
|
private char[] _charBuffer;
|
|
|
|
private int _numBytes;
|
|
|
|
private byte[] _byteBuffer;
|
|
|
|
private Encoding _encoding;
|
|
|
|
private void FlushBytes()
|
|
{
|
|
if (_numBytes > 0)
|
|
{
|
|
_numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
|
|
_numBytes = 0;
|
|
}
|
|
}
|
|
|
|
internal UrlEncoding(int bufferSize, Encoding encoding)
|
|
{
|
|
_bufferSize = bufferSize;
|
|
_encoding = encoding;
|
|
_charBuffer = new char[bufferSize];
|
|
}
|
|
|
|
internal void AddChar(char ch)
|
|
{
|
|
if (_numBytes > 0)
|
|
{
|
|
FlushBytes();
|
|
}
|
|
_charBuffer[_numChars++] = ch;
|
|
}
|
|
|
|
internal void AddByte(byte b)
|
|
{
|
|
if (_byteBuffer == null)
|
|
{
|
|
_byteBuffer = new byte[_bufferSize];
|
|
}
|
|
_byteBuffer[_numBytes++] = b;
|
|
}
|
|
|
|
internal string GetString()
|
|
{
|
|
if (_numBytes > 0)
|
|
{
|
|
FlushBytes();
|
|
}
|
|
if (_numChars > 0)
|
|
{
|
|
return new string(_charBuffer, 0, _numChars);
|
|
}
|
|
return string.Empty;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region decode static
|
|
|
|
static string Decode(string value, Encoding encoding)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return null;
|
|
}
|
|
int length = value.Length;
|
|
var urlDecoder = new UrlEncoding(length, encoding);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
char c = value[i];
|
|
switch (c)
|
|
{
|
|
case '+':
|
|
c = ' ';
|
|
goto default;
|
|
case '%':
|
|
if (i < length - 2)
|
|
{
|
|
if (value[i + 1] == 'u' && i < length - 5)
|
|
{
|
|
int num = HexToInt(value[i + 2]);
|
|
int num2 = HexToInt(value[i + 3]);
|
|
int num3 = HexToInt(value[i + 4]);
|
|
int num4 = HexToInt(value[i + 5]);
|
|
if (num >= 0 && num2 >= 0 && num3 >= 0 && num4 >= 0)
|
|
{
|
|
c = (char)(num << 12 | num2 << 8 | num3 << 4 | num4);
|
|
i += 5;
|
|
urlDecoder.AddChar(c);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int num5 = HexToInt(value[i + 1]);
|
|
int num6 = HexToInt(value[i + 2]);
|
|
if (num5 >= 0 && num6 >= 0)
|
|
{
|
|
byte b = (byte)(num5 << 4 | num6);
|
|
i += 2;
|
|
urlDecoder.AddByte(b);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
goto default;
|
|
default:
|
|
if ((c & 0xFF80) == 0)
|
|
{
|
|
urlDecoder.AddByte((byte)c);
|
|
}
|
|
else
|
|
{
|
|
urlDecoder.AddChar(c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return ValidateString(urlDecoder.GetString());
|
|
}
|
|
|
|
static int HexToInt(char h)
|
|
{
|
|
if (h >= '0' && h <= '9')
|
|
{
|
|
return h - 48;
|
|
}
|
|
if (h >= 'a' && h <= 'f')
|
|
{
|
|
return h - 97 + 10;
|
|
}
|
|
if (h >= 'A' && h <= 'F')
|
|
{
|
|
return h - 65 + 10;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static string ValidateString(string input)
|
|
{
|
|
var skipUtf16Validation = true;
|
|
|
|
if (!skipUtf16Validation && !string.IsNullOrEmpty(input))
|
|
{
|
|
int num = -1;
|
|
int num2 = 0;
|
|
while (num2 < input.Length)
|
|
{
|
|
if (!char.IsSurrogate(input[num2]))
|
|
{
|
|
num2++;
|
|
continue;
|
|
}
|
|
num = num2;
|
|
break;
|
|
}
|
|
if (num < 0)
|
|
{
|
|
return input;
|
|
}
|
|
char[] array = input.ToCharArray();
|
|
for (int i = num; i < array.Length; i++)
|
|
{
|
|
char c = array[i];
|
|
if (char.IsLowSurrogate(c))
|
|
{
|
|
array[i] = '�';
|
|
}
|
|
else if (char.IsHighSurrogate(c))
|
|
{
|
|
if (i + 1 < array.Length && char.IsLowSurrogate(array[i + 1]))
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
array[i] = '�';
|
|
}
|
|
}
|
|
}
|
|
return new string(array);
|
|
}
|
|
return input;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|