Commit 1a2a9546 authored by Honfika's avatar Honfika

Header improvements (part 1)

parent 5497aa64
using System; using System;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
......
...@@ -10,6 +10,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -10,6 +10,11 @@ namespace Titanium.Web.Proxy.Extensions
return str.Equals(value, StringComparison.CurrentCultureIgnoreCase); return str.Equals(value, StringComparison.CurrentCultureIgnoreCase);
} }
internal static bool EqualsIgnoreCase(this ReadOnlySpan<char> str, ReadOnlySpan<char> value)
{
return str.Equals(value, StringComparison.CurrentCultureIgnoreCase);
}
internal static bool ContainsIgnoreCase(this string str, string value) internal static bool ContainsIgnoreCase(this string str, string value)
{ {
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0; return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0;
......
...@@ -16,6 +16,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,6 +16,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1"); private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1");
public static Encoding HeaderEncoding => defaultEncoding;
/// <summary> /// <summary>
/// Gets the character encoding of request/response from content-type header /// Gets the character encoding of request/response from content-type header
/// </summary> /// </summary>
......
...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy.Helpers
private static readonly byte[] newLine = ProxyConstants.NewLineBytes; private static readonly byte[] newLine = ProxyConstants.NewLineBytes;
private static readonly Encoding encoder = Encoding.ASCII; private static Encoding encoding => HttpHelper.HeaderEncoding;
internal HttpWriter(Stream stream, IBufferPool bufferPool) internal HttpWriter(Stream stream, IBufferPool bufferPool)
{ {
...@@ -50,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -50,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers
var buffer = bufferPool.GetBuffer(); var buffer = bufferPool.GetBuffer();
try try
{ {
int idx = encoder.GetBytes(value, 0, charCount, buffer, 0); int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
if (newLineChars > 0) if (newLineChars > 0)
{ {
Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars); Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
...@@ -67,7 +67,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -67,7 +67,7 @@ namespace Titanium.Web.Proxy.Helpers
else else
{ {
var buffer = new byte[charCount + newLineChars + 1]; var buffer = new byte[charCount + newLineChars + 1];
int idx = encoder.GetBytes(value, 0, charCount, buffer, 0); int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
if (newLineChars > 0) if (newLineChars > 0)
{ {
Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars); Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
......
...@@ -107,11 +107,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -107,11 +107,12 @@ namespace Titanium.Web.Proxy.Http
url = Request.RequestUri.GetOriginalPathAndQuery(); url = Request.RequestUri.GetOriginalPathAndQuery();
} }
var headerBuilder = new StringBuilder();
// prepare the request & headers // prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method, url, Request.HttpVersion), cancellationToken); headerBuilder.Append(Request.CreateRequestLine(Request.Method, url, Request.HttpVersion));
headerBuilder.Append(ProxyConstants.NewLine);
var headerBuilder = new StringBuilder();
// Send Authentication to Upstream proxy if needed // Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null if (!isTransparent && upstreamProxy != null
&& Connection.IsHttps == false && Connection.IsHttps == false
......
...@@ -158,7 +158,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -158,7 +158,7 @@ namespace Titanium.Web.Proxy.Http
sb.Append($"{CreateRequestLine(Method, RequestUriString, HttpVersion)}{ProxyConstants.NewLine}"); sb.Append($"{CreateRequestLine(Method, RequestUriString, HttpVersion)}{ProxyConstants.NewLine}");
foreach (var header in Headers) foreach (var header in Headers)
{ {
sb.Append($"{header.ToString()}{ProxyConstants.NewLine}"); sb.Append($"{header}{ProxyConstants.NewLine}");
} }
sb.Append(ProxyConstants.NewLine); sb.Append(ProxyConstants.NewLine);
...@@ -205,30 +205,38 @@ namespace Titanium.Web.Proxy.Http ...@@ -205,30 +205,38 @@ namespace Titanium.Web.Proxy.Http
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl, internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl,
out Version version) out Version version)
{ {
// break up the line into three components (method, remote URL & Http Version) int firstSpace = httpCmd.IndexOf(' ');
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); if (firstSpace == -1)
if (httpCmdSplit.Length < 2)
{ {
// does not contain at least 2 parts
throw new Exception("Invalid HTTP request line: " + httpCmd); throw new Exception("Invalid HTTP request line: " + httpCmd);
} }
int lastSpace = httpCmd.LastIndexOf(' ');
// break up the line into three components (method, remote URL & Http Version)
// Find the request Verb // Find the request Verb
httpMethod = httpCmdSplit[0]; httpMethod = httpCmd.Substring(0, firstSpace);
if (!isAllUpper(httpMethod)) if (!isAllUpper(httpMethod))
{ {
httpMethod = httpMethod.ToUpper(); httpMethod = httpMethod.ToUpper();
} }
httpUrl = httpCmdSplit[1];
// parse the HTTP version
version = HttpHeader.Version11; version = HttpHeader.Version11;
if (httpCmdSplit.Length == 3)
if (firstSpace == lastSpace)
{ {
string httpVersion = httpCmdSplit[2].Trim(); httpUrl = httpCmd.AsSpan(firstSpace + 1).ToString();
}
else
{
httpUrl = httpCmd.AsSpan(firstSpace + 1, lastSpace - firstSpace - 1).ToString();
// parse the HTTP version
var httpVersion = httpCmd.AsSpan(lastSpace + 1);
if (httpVersion.EqualsIgnoreCase("HTTP/1.0")) if (httpVersion.EqualsIgnoreCase("HTTP/1.0".AsSpan(0)))
{ {
version = HttpHeader.Version10; version = HttpHeader.Version10;
} }
......
...@@ -134,22 +134,39 @@ namespace Titanium.Web.Proxy.Http ...@@ -134,22 +134,39 @@ namespace Titanium.Web.Proxy.Http
internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode,
out string statusDescription) out string statusDescription)
{ {
var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3); int firstSpace = httpStatus.IndexOf(' ');
if (httpResult.Length <= 1) if (firstSpace == -1)
{ {
throw new Exception("Invalid HTTP status line: " + httpStatus); throw new Exception("Invalid HTTP status line: " + httpStatus);
} }
string httpVersion = httpResult[0]; var httpVersion = httpStatus.AsSpan(0, firstSpace);
version = HttpHeader.Version11; version = HttpHeader.Version11;
if (httpVersion.EqualsIgnoreCase("HTTP/1.0")) if (httpVersion.EqualsIgnoreCase("HTTP/1.0".AsSpan()))
{ {
version = HttpHeader.Version10; version = HttpHeader.Version10;
} }
statusCode = int.Parse(httpResult[1]); int secondSpace = httpStatus.IndexOf(' ', firstSpace + 1);
statusDescription = httpResult.Length > 2 ? httpResult[2] : string.Empty; if (secondSpace != -1)
{
#if NETSTANDARD2_1
statusCode = int.Parse(httpStatus.AsSpan(firstSpace + 1, secondSpace - firstSpace - 1));
#else
statusCode = int.Parse(httpStatus.AsSpan(firstSpace + 1, secondSpace - firstSpace - 1).ToString());
#endif
statusDescription = httpStatus.AsSpan(secondSpace + 1).ToString();
}
else
{
#if NETSTANDARD2_1
statusCode = int.Parse(httpStatus.AsSpan(firstSpace + 1));
#else
statusCode = int.Parse(httpStatus.AsSpan(firstSpace + 1).ToString());
#endif
statusDescription = string.Empty;
}
} }
} }
} }
...@@ -30,30 +30,35 @@ namespace Titanium.Web.Proxy ...@@ -30,30 +30,35 @@ namespace Titanium.Web.Proxy
try try
{ {
var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization); var headerObj = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null) if (headerObj == null)
{ {
session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Required"); session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Required");
return false; return false;
} }
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit); string header = headerObj.Value;
int firstSpace = header.IndexOf(' ');
if (headerValueParts.Length != 2) // header value should contain exactly 1 space
if (firstSpace == -1 || header.IndexOf(' ', firstSpace + 1) != -1)
{ {
// Return not authorized // Return not authorized
session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false; return false;
} }
var authenticationType = header.AsMemory(0, firstSpace);
var credentials = header.AsMemory(firstSpace + 1);
if (ProxyBasicAuthenticateFunc != null) if (ProxyBasicAuthenticateFunc != null)
{ {
return await authenticateUserBasic(session, headerValueParts); return await authenticateUserBasic(session, authenticationType, credentials);
} }
if (ProxySchemeAuthenticateFunc != null) if (ProxySchemeAuthenticateFunc != null)
{ {
var result = await ProxySchemeAuthenticateFunc(session, headerValueParts[0], headerValueParts[1]); var result = await ProxySchemeAuthenticateFunc(session, authenticationType.ToString(), credentials.ToString());
if (result.Result == ProxyAuthenticationResult.ContinuationNeeded) if (result.Result == ProxyAuthenticationResult.ContinuationNeeded)
{ {
...@@ -78,16 +83,16 @@ namespace Titanium.Web.Proxy ...@@ -78,16 +83,16 @@ namespace Titanium.Web.Proxy
} }
} }
private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, string[] headerValueParts) private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, ReadOnlyMemory<char> authenticationType, ReadOnlyMemory<char> credentials)
{ {
if (!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic)) if (!authenticationType.Span.EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic.AsSpan()))
{ {
// Return not authorized // Return not authorized
session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false; return false;
} }
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1])); string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(credentials.ToString()));
int colonIndex = decoded.IndexOf(':'); int colonIndex = decoded.IndexOf(':');
if (colonIndex == -1) if (colonIndex == -1)
{ {
......
...@@ -12,7 +12,6 @@ namespace Titanium.Web.Proxy.Shared ...@@ -12,7 +12,6 @@ namespace Titanium.Web.Proxy.Shared
{ {
internal static readonly char DotSplit = '.'; internal static readonly char DotSplit = '.';
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' }; internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' }; internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' }; internal static readonly char[] EqualSplit = { '=' };
......
...@@ -5,6 +5,7 @@ using System.Net.Sockets; ...@@ -5,6 +5,7 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.StreamExtended.BufferPool; using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network namespace Titanium.Web.Proxy.StreamExtended.Network
...@@ -21,7 +22,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -21,7 +22,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
private byte[] streamBuffer; private byte[] streamBuffer;
// default to UTF-8 // default to UTF-8
private static readonly Encoding encoding = Encoding.UTF8; private static Encoding encoding => HttpHelper.HeaderEncoding;
private static readonly bool networkStreamHack = true; private static readonly bool networkStreamHack = true;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment