mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-25 06:16:37 +01:00
CP-17878: Reverting HTTP.cs as it's related to Proxy only
HTTP.cs changes were brought in by c9ed8a1f68
, but they should not be in this branch
Signed-off-by: Gabor Apati-Nagy <gabor.apati-nagy@citrix.com>
This commit is contained in:
parent
da2d7dcdd6
commit
e648868f6e
@ -36,7 +36,6 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
@ -107,18 +106,6 @@ namespace XenAPI
|
||||
protected CancelledException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ProxyServerAuthenticationException : Exception
|
||||
{
|
||||
public ProxyServerAuthenticationException() : base() { }
|
||||
|
||||
public ProxyServerAuthenticationException(string message) : base(message) { }
|
||||
|
||||
public ProxyServerAuthenticationException(string message, Exception exception) : base(message, exception) { }
|
||||
|
||||
protected ProxyServerAuthenticationException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
|
||||
public delegate bool FuncBool();
|
||||
public delegate void UpdateProgressDelegate(int percent);
|
||||
public delegate void DataCopiedDelegate(long bytes);
|
||||
@ -143,10 +130,6 @@ namespace XenAPI
|
||||
WriteLine("", stream);
|
||||
}
|
||||
|
||||
// Stream.ReadByte() is used because using StreamReader in its place causes the reading to become stuck,
|
||||
// as it seems the Stream object has trouble recognizing the end of the stream. This seems to be a common
|
||||
// problem, of which a common solution is to read each byte until an EndOfStreamException is thrown, as is
|
||||
// done here.
|
||||
private static string ReadLine(Stream stream)
|
||||
{
|
||||
System.Text.StringBuilder result = new StringBuilder();
|
||||
@ -167,81 +150,25 @@ namespace XenAPI
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <returns>True if a redirect has occurred - headers will need to be resent.</returns>
|
||||
private static bool ReadHttpHeaders(ref Stream stream, IWebProxy proxy, bool nodelay, int timeout_ms, List<string> headers = null)
|
||||
private static bool ReadHttpHeaders(ref Stream stream, IWebProxy proxy, bool nodelay, int timeout_ms)
|
||||
{
|
||||
// read headers/fields
|
||||
string line = ReadLine(stream), initialLine = line, transferEncodingField = null;
|
||||
if (string.IsNullOrEmpty(initialLine)) // sanity check
|
||||
return false;
|
||||
while (!string.IsNullOrWhiteSpace(line)) // IsNullOrWhiteSpace also checks for empty string
|
||||
{
|
||||
if (headers != null)
|
||||
{
|
||||
line = line.TrimEnd('\r', '\n');
|
||||
headers.Add(line);
|
||||
if (line == "Transfer-Encoding: Chunked")
|
||||
transferEncodingField = line;
|
||||
}
|
||||
line = ReadLine(stream);
|
||||
}
|
||||
string response = ReadLine(stream);
|
||||
int code = getResultCode(response);
|
||||
|
||||
// read chunks
|
||||
string entityBody = "";
|
||||
if (!string.IsNullOrEmpty(transferEncodingField))
|
||||
{
|
||||
int lastChunkSize = -1;
|
||||
do
|
||||
{
|
||||
string chunkSizeStr = ReadLine(stream);
|
||||
chunkSizeStr = chunkSizeStr.TrimEnd('\r', '\n');
|
||||
int chunkSize = int.Parse(chunkSizeStr, System.Globalization.NumberStyles.HexNumber);
|
||||
|
||||
byte[] bytes = new byte[chunkSize];
|
||||
stream.Read(bytes, 0, chunkSize);
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
string str = System.Text.Encoding.ASCII.GetString(bytes);
|
||||
string[] split = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
headers.AddRange(split);
|
||||
|
||||
entityBody += str;
|
||||
}
|
||||
|
||||
line = ReadLine(stream); // empty line in the end of chunk
|
||||
|
||||
lastChunkSize = chunkSize;
|
||||
}
|
||||
while (lastChunkSize != 0);
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
entityBody = entityBody.TrimEnd('\r', '\n');
|
||||
headers.Add(entityBody); // keep entityBody if it's needed for Digest authentication (when qop="auth-int")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: handle other transfer types, in case "Transfer-Encoding: Chunked" isn't used
|
||||
}
|
||||
|
||||
// handle server response
|
||||
int code = getResultCode(initialLine);
|
||||
switch (code)
|
||||
{
|
||||
case 407: // authentication error; caller must handle this case
|
||||
case 200:
|
||||
break;
|
||||
|
||||
case 302:
|
||||
string url = "";
|
||||
foreach (string header in headers)
|
||||
while (true)
|
||||
{
|
||||
if (header.StartsWith("Location: "))
|
||||
{
|
||||
url = header.Substring(10);
|
||||
response = ReadLine(stream);
|
||||
if (response.StartsWith("Location: "))
|
||||
url = response.Substring(10);
|
||||
if (response.Equals("\r\n") || response.Equals("\n") || response.Equals(""))
|
||||
break;
|
||||
}
|
||||
}
|
||||
Uri redirect = new Uri(url.Trim());
|
||||
stream.Close();
|
||||
@ -249,8 +176,19 @@ namespace XenAPI
|
||||
return true; // headers need to be sent again
|
||||
|
||||
default:
|
||||
if (response.EndsWith("\r\n"))
|
||||
response = response.Substring(0, response.Length - 2);
|
||||
else if (response.EndsWith("\n"))
|
||||
response = response.Substring(0, response.Length - 1);
|
||||
stream.Close();
|
||||
throw new BadServerResponseException(string.Format("Received error code {0} from the server", initialLine));
|
||||
throw new BadServerResponseException(string.Format("Received error code {0} from the server", response));
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
string line = ReadLine(stream);
|
||||
if (System.Text.RegularExpressions.Regex.Match(line, "^\\s*$").Success)
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -276,20 +214,6 @@ namespace XenAPI
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a secure MD5 hash of the given input string.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to hash.</param>
|
||||
/// <returns>The secure hash as a hex string.</returns>
|
||||
public static string MD5Hash(string str)
|
||||
{
|
||||
MD5 hasher = MD5.Create();
|
||||
ASCIIEncoding enc = new ASCIIEncoding();
|
||||
byte[] bytes = enc.GetBytes(str);
|
||||
byte[] hash = hasher.ComputeHash(bytes);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLower();
|
||||
}
|
||||
|
||||
public static long CopyStream(Stream inStream, Stream outStream,
|
||||
DataCopiedDelegate progressDelegate, FuncBool cancellingDelegate)
|
||||
{
|
||||
@ -375,12 +299,6 @@ namespace XenAPI
|
||||
return uri.Uri;
|
||||
}
|
||||
|
||||
private static string GetPartOrNull(string str, int partIndex)
|
||||
{
|
||||
string[] parts = str.Split(new char[] { ' ' }, partIndex + 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
return partIndex < parts.Length - 1 ? parts[partIndex] : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static NetworkStream ConnectSocket(Uri uri, bool nodelay, int timeout_ms)
|
||||
@ -430,13 +348,11 @@ namespace XenAPI
|
||||
if (useProxy)
|
||||
{
|
||||
string line = String.Format("CONNECT {0}:{1} HTTP/1.0", uri.Host, uri.Port);
|
||||
|
||||
WriteLine(line, stream);
|
||||
WriteLine(stream);
|
||||
|
||||
List<string> initialResponse = new List<string>();
|
||||
ReadHttpHeaders(ref stream, proxy, nodelay, timeout_ms, initialResponse);
|
||||
|
||||
AuthenticateProxy(ref stream, uri, proxy, nodelay, timeout_ms, initialResponse, line);
|
||||
ReadHttpHeaders(ref stream, proxy, nodelay, timeout_ms);
|
||||
}
|
||||
|
||||
if (UseSSL(uri))
|
||||
@ -457,157 +373,6 @@ namespace XenAPI
|
||||
}
|
||||
}
|
||||
|
||||
private static void AuthenticateProxy(ref Stream stream, Uri uri, IWebProxy proxy, bool nodelay, int timeout_ms, List<string> initialResponse, string header)
|
||||
{
|
||||
// perform authentication only if proxy requires it
|
||||
List<string> fields = initialResponse.FindAll(str => str.StartsWith("Proxy-Authenticate:"));
|
||||
if (fields.Count > 0)
|
||||
{
|
||||
// clean up (if initial server response specifies "Proxy-Connection: Close" then stream cannot be re-used)
|
||||
string field = initialResponse.Find(str => str.StartsWith("Proxy-Connection: Close"));
|
||||
if (!string.IsNullOrEmpty(field))
|
||||
{
|
||||
stream.Close();
|
||||
Uri proxyURI = proxy.GetProxy(uri);
|
||||
stream = ConnectSocket(proxyURI, nodelay, timeout_ms);
|
||||
}
|
||||
|
||||
if (proxy.Credentials == null)
|
||||
throw new BadServerResponseException(string.Format("Received error code {0} from the server", initialResponse[0]));
|
||||
NetworkCredential credentials = proxy.Credentials.GetCredential(uri, null);
|
||||
|
||||
string basicField = fields.Find(str => str.StartsWith("Proxy-Authenticate: Basic"));
|
||||
string digestField = fields.Find(str => str.StartsWith("Proxy-Authenticate: Digest"));
|
||||
if (!string.IsNullOrEmpty(basicField))
|
||||
{
|
||||
string authenticationFieldReply = String.Format("Proxy-Authorization: Basic {0}",
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials.UserName + ":" + credentials.Password)));
|
||||
WriteLine(header, stream);
|
||||
WriteLine(authenticationFieldReply, stream);
|
||||
WriteLine(stream);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(digestField))
|
||||
{
|
||||
string authenticationFieldReply = string.Format(
|
||||
"Proxy-Authorization: Digest username=\"{0}\", uri=\"{1}:{2}\"",
|
||||
credentials.UserName, uri.Host, uri.Port);
|
||||
|
||||
string directiveString = digestField.Substring(27, digestField.Length - 27);
|
||||
string[] directives = directiveString.Split(new string[] { ", ", "\"" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
string algorithm = null; // optional
|
||||
string opaque = null; // optional
|
||||
string qop = null; // optional
|
||||
string realm = null;
|
||||
string nonce = null;
|
||||
|
||||
for (int i = 0; i < directives.Length; ++i)
|
||||
{
|
||||
switch (directives[i])
|
||||
{
|
||||
case "stale=":
|
||||
if (directives[++i].ToLower() == "true")
|
||||
throw new ProxyServerAuthenticationException("Stale nonce in Digest authentication attempt.");
|
||||
break;
|
||||
case "realm=":
|
||||
authenticationFieldReply += string.Format(", {0}\"{1}\"", directives[i], directives[++i]);
|
||||
realm = directives[i];
|
||||
break;
|
||||
case "nonce=":
|
||||
authenticationFieldReply += string.Format(", {0}\"{1}\"", directives[i], directives[++i]);
|
||||
nonce = directives[i];
|
||||
break;
|
||||
case "opaque=":
|
||||
authenticationFieldReply += string.Format(", {0}\"{1}\"", directives[i], directives[++i]);
|
||||
opaque = directives[i];
|
||||
break;
|
||||
case "algorithm=":
|
||||
authenticationFieldReply += string.Format(", {0}\"{1}\"", directives[i], directives[++i]);
|
||||
algorithm = directives[i];
|
||||
break;
|
||||
case "qop=":
|
||||
List<string> qops = new List<string>(directives[++i].Split(new char[] { ',' }));
|
||||
if (qops.Count > 0)
|
||||
{
|
||||
if (qops.Contains("auth"))
|
||||
qop = "auth";
|
||||
else if (qops.Contains("auth-int"))
|
||||
qop = "auth-int";
|
||||
else
|
||||
throw new ProxyServerAuthenticationException(
|
||||
"Digest authentication's quality-of-protection directive of is not supported.");
|
||||
authenticationFieldReply += string.Format(", qop=\"{0}\"", qop);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string clientNonce = "X3nC3nt3r"; // todo: generate random string
|
||||
if (qop != null)
|
||||
authenticationFieldReply += string.Format(", cnonce=\"{0}\"", clientNonce);
|
||||
|
||||
string nonceCount = "00000001"; // todo: track nonces and their corresponding nonce counts
|
||||
if (qop != null)
|
||||
authenticationFieldReply += string.Format(", nc={0}", nonceCount);
|
||||
|
||||
string HA1 = "";
|
||||
string scratch = string.Format("{0}:{1}:{2}", credentials.UserName, realm, credentials.Password);
|
||||
if (algorithm == null || algorithm == "MD5")
|
||||
HA1 = MD5Hash(scratch);
|
||||
else
|
||||
HA1 = MD5Hash(string.Format("{0}:{1}:{2}", MD5Hash(scratch), nonce, clientNonce));
|
||||
|
||||
string HA2 = "";
|
||||
scratch = GetPartOrNull(header, 0);
|
||||
scratch = string.Format("{0}:{1}:{2}", scratch ?? "CONNECT", uri.Host, uri.Port);
|
||||
if (qop == null || qop == "auth")
|
||||
HA2 = MD5Hash(scratch);
|
||||
else
|
||||
{
|
||||
string entityBody = initialResponse[initialResponse.Count - 1]; // entity body should have been stored as last element of initialResponse
|
||||
string str = string.Format("{0}:{1}", scratch, MD5Hash(entityBody));
|
||||
HA2 = MD5Hash(str);
|
||||
}
|
||||
|
||||
string response = "";
|
||||
if (qop == null)
|
||||
response = MD5Hash(string.Format("{0}:{1}:{2}", HA1, nonce, HA2));
|
||||
else
|
||||
response = MD5Hash(string.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nonceCount, clientNonce, qop, HA2));
|
||||
|
||||
authenticationFieldReply += string.Format(", response=\"{0}\"", response);
|
||||
|
||||
WriteLine(header, stream);
|
||||
WriteLine(authenticationFieldReply, stream);
|
||||
WriteLine(stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
string authType = GetPartOrNull(fields[0], 1);
|
||||
throw new ProxyServerAuthenticationException(
|
||||
string.Format("Proxy server's {0} authentication method is not supported.", authType ?? "chosen"));
|
||||
}
|
||||
|
||||
// handle authentication attempt response
|
||||
List<string> authenticatedResponse = new List<string>();
|
||||
ReadHttpHeaders(ref stream, proxy, nodelay, timeout_ms, authenticatedResponse);
|
||||
if (authenticatedResponse.Count == 0)
|
||||
throw new BadServerResponseException("No response from the proxy server after authentication attempt.");
|
||||
switch (getResultCode(authenticatedResponse[0]))
|
||||
{
|
||||
case 200:
|
||||
break;
|
||||
case 407:
|
||||
throw new ProxyServerAuthenticationException("Proxy server denied access due to wrong credentials.");
|
||||
default:
|
||||
throw new BadServerResponseException(string.Format(
|
||||
"Received error code {0} from the server", authenticatedResponse[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream DO_HTTP(Uri uri, IWebProxy proxy, bool nodelay, int timeout_ms, params string[] headers)
|
||||
{
|
||||
Stream stream = ConnectStream(uri, proxy, nodelay, timeout_ms);
|
||||
|
Loading…
Reference in New Issue
Block a user