From e648868f6ea860a73f497da620d6cd0777c925c4 Mon Sep 17 00:00:00 2001 From: Gabor Apati-Nagy Date: Wed, 14 Sep 2016 10:56:53 +0100 Subject: [PATCH] CP-17878: Reverting HTTP.cs as it's related to Proxy only HTTP.cs changes were brought in by c9ed8a1f688dec19cafc41a06e4167ec82999ab7, but they should not be in this branch Signed-off-by: Gabor Apati-Nagy --- XenModel/XenAPI/HTTP.cs | 279 ++++------------------------------------ 1 file changed, 22 insertions(+), 257 deletions(-) diff --git a/XenModel/XenAPI/HTTP.cs b/XenModel/XenAPI/HTTP.cs index f984c4584..ecd1c1631 100644 --- a/XenModel/XenAPI/HTTP.cs +++ b/XenModel/XenAPI/HTTP.cs @@ -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 /// /// /// True if a redirect has occurred - headers will need to be resent. - private static bool ReadHttpHeaders(ref Stream stream, IWebProxy proxy, bool nodelay, int timeout_ms, List 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; } - /// - /// Returns a secure MD5 hash of the given input string. - /// - /// The string to hash. - /// The secure hash as a hex string. - 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 initialResponse = new List(); - 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 initialResponse, string header) - { - // perform authentication only if proxy requires it - List 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 qops = new List(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 authenticatedResponse = new List(); - 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);