* Add HTTP proxy support in forward proxy Signed-off-by: noisyfox <timemanager.rick@gmail.com> * Typo * Throw exception if forward proxy not supported.
232 lines
7.3 KiB
C#
232 lines
7.3 KiB
C#
using System;
|
|
using System.Text;
|
|
|
|
namespace Shadowsocks.Util.Sockets
|
|
{
|
|
public class LineReader
|
|
{
|
|
private readonly WrappedSocket _socket;
|
|
private readonly Func<string, object, bool> _onLineRead;
|
|
private readonly Action<Exception, object> _onException;
|
|
private readonly Action<byte[], int, int, object> _onFinish;
|
|
private readonly Encoding _encoding;
|
|
// private readonly string _delimiter;
|
|
private readonly byte[] _delimiterBytes;
|
|
private readonly int[] _delimiterSearchCharTable;
|
|
private readonly int[] _delimiterSearchOffsetTable;
|
|
private readonly object _state;
|
|
|
|
private readonly byte[] _lineBuffer;
|
|
|
|
private int _bufferIndex;
|
|
|
|
public LineReader(WrappedSocket socket, Func<string, object, bool> onLineRead, Action<Exception, object> onException,
|
|
Action<byte[], int, int, object> onFinish, Encoding encoding, string delimiter, int maxLineBytes, object state)
|
|
{
|
|
if (socket == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(socket));
|
|
}
|
|
if (onLineRead == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(onLineRead));
|
|
}
|
|
if (encoding == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(encoding));
|
|
}
|
|
if (delimiter == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(delimiter));
|
|
}
|
|
|
|
_socket = socket;
|
|
_onLineRead = onLineRead;
|
|
_onException = onException;
|
|
_onFinish = onFinish;
|
|
_encoding = encoding;
|
|
// _delimiter = delimiter;
|
|
_state = state;
|
|
|
|
// decode delimiter
|
|
_delimiterBytes = encoding.GetBytes(delimiter);
|
|
|
|
if (_delimiterBytes.Length == 0)
|
|
{
|
|
throw new ArgumentException("Too short!", nameof(delimiter));
|
|
}
|
|
|
|
if (maxLineBytes < _delimiterBytes.Length)
|
|
{
|
|
throw new ArgumentException("Too small!", nameof(maxLineBytes));
|
|
}
|
|
|
|
_delimiterSearchCharTable = MakeCharTable(_delimiterBytes);
|
|
_delimiterSearchOffsetTable = MakeOffsetTable(_delimiterBytes);
|
|
|
|
_lineBuffer = new byte[maxLineBytes];
|
|
|
|
// start reading
|
|
socket.BeginReceive(_lineBuffer, 0, maxLineBytes, 0, ReceiveCallback, 0);
|
|
}
|
|
|
|
private void ReceiveCallback(IAsyncResult ar)
|
|
{
|
|
int length = (int)ar.AsyncState;
|
|
try
|
|
{
|
|
var bytesRead = _socket.EndReceive(ar);
|
|
|
|
if (bytesRead == 0)
|
|
{
|
|
OnFinish(length);
|
|
return;
|
|
}
|
|
|
|
length += bytesRead;
|
|
|
|
int i;
|
|
while ((i = IndexOf(_lineBuffer, _bufferIndex, length, _delimiterBytes, _delimiterSearchOffsetTable,
|
|
_delimiterSearchCharTable)) != -1)
|
|
{
|
|
var decodeLen = i - _bufferIndex;
|
|
string line = _encoding.GetString(_lineBuffer, _bufferIndex, decodeLen);
|
|
|
|
_bufferIndex = i + _delimiterBytes.Length;
|
|
length -= decodeLen;
|
|
length -= _delimiterBytes.Length;
|
|
|
|
var stop = _onLineRead(line, _state);
|
|
if (stop)
|
|
{
|
|
OnFinish(length);
|
|
return;
|
|
}
|
|
}
|
|
if (length == _lineBuffer.Length)
|
|
{
|
|
OnException(new IndexOutOfRangeException("LineBuffer full! Try increace maxLineBytes!"));
|
|
OnFinish(length);
|
|
|
|
return;
|
|
}
|
|
|
|
if (_bufferIndex > 0)
|
|
{
|
|
Buffer.BlockCopy(_lineBuffer, _bufferIndex, _lineBuffer, 0, length);
|
|
_bufferIndex = 0;
|
|
}
|
|
|
|
_socket.BeginReceive(_lineBuffer, length, _lineBuffer.Length - length, 0, ReceiveCallback, length);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
OnException(ex);
|
|
OnFinish(length);
|
|
}
|
|
}
|
|
|
|
private void OnException(Exception ex)
|
|
{
|
|
_onException?.Invoke(ex, _state);
|
|
}
|
|
|
|
private void OnFinish(int length)
|
|
{
|
|
_onFinish?.Invoke(_lineBuffer, _bufferIndex, length, _state);
|
|
}
|
|
|
|
#region Boyer-Moore string search
|
|
|
|
public static int IndexOf(byte[] haystack, int index, int length, byte[] needle, int[] offsetTable, int[] charTable)
|
|
{
|
|
var end = index + length;
|
|
for (int i = needle.Length - 1 + index, j; i < end;)
|
|
{
|
|
for (j = needle.Length - 1; needle[j] == haystack[i]; --i, --j)
|
|
{
|
|
if (j == 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
// i += needle.length - j; // For naive method
|
|
i += Math.Max(offsetTable[needle.Length - 1 - j], charTable[haystack[i]]);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Makes the jump table based on the mismatched character information.
|
|
*/
|
|
private static int[] MakeCharTable(byte[] needle)
|
|
{
|
|
const int ALPHABET_SIZE = 256;
|
|
int[] table = new int[ALPHABET_SIZE];
|
|
for (int i = 0; i < table.Length; ++i)
|
|
{
|
|
table[i] = needle.Length;
|
|
}
|
|
for (int i = 0; i < needle.Length - 1; ++i)
|
|
{
|
|
table[needle[i]] = needle.Length - 1 - i;
|
|
}
|
|
return table;
|
|
}
|
|
|
|
/**
|
|
* Makes the jump table based on the scan offset which mismatch occurs.
|
|
*/
|
|
private static int[] MakeOffsetTable(byte[] needle)
|
|
{
|
|
int[] table = new int[needle.Length];
|
|
int lastPrefixPosition = needle.Length;
|
|
for (int i = needle.Length - 1; i >= 0; --i)
|
|
{
|
|
if (IsPrefix(needle, i + 1))
|
|
{
|
|
lastPrefixPosition = i + 1;
|
|
}
|
|
table[needle.Length - 1 - i] = lastPrefixPosition - i + needle.Length - 1;
|
|
}
|
|
for (int i = 0; i < needle.Length - 1; ++i)
|
|
{
|
|
int slen = SuffixLength(needle, i);
|
|
table[slen] = needle.Length - 1 - i + slen;
|
|
}
|
|
return table;
|
|
}
|
|
|
|
/**
|
|
* Is needle[p:end] a prefix of needle?
|
|
*/
|
|
private static bool IsPrefix(byte[] needle, int p)
|
|
{
|
|
for (int i = p, j = 0; i < needle.Length; ++i, ++j)
|
|
{
|
|
if (needle[i] != needle[j])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum length of the substring ends at p and is a suffix.
|
|
*/
|
|
private static int SuffixLength(byte[] needle, int p)
|
|
{
|
|
int len = 0;
|
|
for (int i = p, j = needle.Length - 1;
|
|
i >= 0 && needle[i] == needle[j]; --i, --j)
|
|
{
|
|
len += 1;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|