/*
* Copyright (c) Citrix Systems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1) Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2) Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
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;
namespace XenAPI
{
public partial class HTTP
{
#region Exceptions
[Serializable]
public class TooManyRedirectsException : Exception
{
private readonly int redirect;
private readonly Uri uri;
public TooManyRedirectsException(int redirect, Uri uri)
{
this.redirect = redirect;
this.uri = uri;
}
public TooManyRedirectsException() : base() { }
public TooManyRedirectsException(string message) : base(message) { }
public TooManyRedirectsException(string message, Exception exception) : base(message, exception) { }
protected TooManyRedirectsException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
redirect = info.GetInt32("redirect");
uri = (Uri)info.GetValue("uri", typeof(Uri));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("redirect", redirect);
info.AddValue("uri", uri, typeof(Uri));
base.GetObjectData(info, context);
}
}
[Serializable]
public class BadServerResponseException : Exception
{
public BadServerResponseException() : base() { }
public BadServerResponseException(string message) : base(message) { }
public BadServerResponseException(string message, Exception exception) : base(message, exception) { }
protected BadServerResponseException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
[Serializable]
public class CancelledException : Exception
{
public CancelledException() : base() { }
public CancelledException(string message) : base(message) { }
public CancelledException(string message, Exception exception) : base(message, exception) { }
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) { }
}
#endregion
public delegate bool FuncBool();
public delegate void UpdateProgressDelegate(int percent);
public delegate void DataCopiedDelegate(long bytes);
// Size of byte buffer used for GETs and PUTs
// (not the socket rx buffer)
public const int BUFFER_SIZE = 32 * 1024;
public const int MAX_REDIRECTS = 10;
public const int DEFAULT_HTTPS_PORT = 443;
public enum ProxyAuthenticationMethod
{
Basic = 0,
Digest = 1
}
///
/// The authentication scheme to use for authenticating to a proxy server.
/// Defaults to Digest.
///
public static ProxyAuthenticationMethod CurrentProxyAuthenticationMethod = ProxyAuthenticationMethod.Digest;
#region Helper functions
private static void WriteLine(String txt, Stream stream)
{
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(String.Format("{0}\r\n", txt));
stream.Write(bytes, 0, bytes.Length);
}
private static void WriteLine(Stream stream)
{
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();
while (true)
{
int b = stream.ReadByte();
if (b == -1)
throw new EndOfStreamException();
char c = Convert.ToChar(b);
result.Append(c);
if (c == '\n')
return result.ToString();
}
}
///
/// Read HTTP headers, doing any redirects as necessary
///
/// 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)
{
// read headers/fields
string line = ReadLine(stream), initialLine = line, transferEncodingField = null;
if (string.IsNullOrEmpty(initialLine)) // sanity check
return false;
if (headers == null)
headers = new List();
while (!string.IsNullOrWhiteSpace(line)) // IsNullOrWhiteSpace also checks for empty string
{
line = line.TrimEnd('\r', '\n');
headers.Add(line);
if (line == "Transfer-Encoding: Chunked")
transferEncodingField = line;
line = ReadLine(stream);
}
// read chunks
string entityBody = "";
if (!string.IsNullOrEmpty(transferEncodingField))
{
int lastChunkSize = -1;
do
{
// read chunk size
string chunkSizeStr = ReadLine(stream);
chunkSizeStr = chunkSizeStr.TrimEnd('\r', '\n');
int chunkSize = 0;
int.TryParse(chunkSizeStr, System.Globalization.NumberStyles.HexNumber,
System.Globalization.CultureInfo.InvariantCulture, out chunkSize);
// read number of bytes from the stream
int totalNumberOfBytesRead = 0;
int numberOfBytesRead;
byte[] bytes = new byte[chunkSize];
do
{
numberOfBytesRead = stream.Read(bytes, totalNumberOfBytesRead, chunkSize - totalNumberOfBytesRead);
totalNumberOfBytesRead += numberOfBytesRead;
} while (numberOfBytesRead > 0 && totalNumberOfBytesRead < chunkSize);
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);
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)
{
if (header.StartsWith("Location: "))
{
url = header.Substring(10);
break;
}
}
Uri redirect = new Uri(url.Trim());
stream.Close();
stream = ConnectStream(redirect, proxy, nodelay, timeout_ms);
return true; // headers need to be sent again
default:
stream.Close();
throw new BadServerResponseException(string.Format("Received error code {0} from the server", initialLine));
}
return false;
}
private static int getResultCode(string line)
{
string[] bits = line.Split(new char[] { ' ' });
return (bits.Length < 2 ? 0 : Int32.Parse(bits[1]));
}
public static bool UseSSL(Uri uri)
{
return uri.Scheme == "https" || uri.Port == DEFAULT_HTTPS_PORT;
}
private static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
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)
{
long bytesWritten = 0;
byte[] buffer = new byte[BUFFER_SIZE];
DateTime lastUpdate = DateTime.Now;
while (cancellingDelegate == null || !cancellingDelegate())
{
int bytesRead = inStream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
break;
outStream.Write(buffer, 0, bytesRead);
bytesWritten += bytesRead;
if (progressDelegate != null &&
DateTime.Now - lastUpdate > TimeSpan.FromMilliseconds(500))
{
progressDelegate(bytesWritten);
lastUpdate = DateTime.Now;
}
}
if (cancellingDelegate != null && cancellingDelegate())
throw new CancelledException();
if (progressDelegate != null)
progressDelegate(bytesWritten);
return bytesWritten;
}
///
/// Build a URI from a hostname, a path, and some query arguments
///
/// An even-length array, alternating argument names and values
///
///
public static Uri BuildUri(string hostname, string path, params object[] args)
{
// The last argument may be an object[] in its own right, in which case we need
// to flatten the array.
List