xenadmin/XenOvfTransport/Http.cs

587 lines
22 KiB
C#

/* 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:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * 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.Collections.Specialized;
using System.Globalization;
using System.Threading;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace XenOvfTransport
{
public class Http
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public Action<XenOvfTranportEventArgs> UpdateHandler { get; set; }
private void OnUpdate(XenOvfTranportEventArgs e)
{
if (UpdateHandler != null)
UpdateHandler.Invoke(e);
}
internal enum Tag
{
Print = 0, Load = 1, HttpGet = 12, HttpPut = 13, Prompt = 3, Exit = 4,
Error = 14, OK = 5, Failed = 6, Chunk = 7, End = 8, Command = 9, Response = 10,
Blob = 11, Debug = 15, PrintStderr = 16
};
private TcpClient tcpClient = null;
private Uri ServerURI;
private const int OK = 200;
private const long KB = 1024;
private const long MB = (KB * 1024);
private const long GB = (MB * 1024);
public bool Cancel { get; set; }
public void Put(Stream readstream, Uri serverUri, string p2vUri, NameValueCollection headers, long offset, long filesize, bool isChunked)
{
Stream http = null;
try
{
http = DoHttp("PUT", serverUri, p2vUri, headers);
if (http == null)
{
log.Debug("Http.DoHTTP FAILED");
return;
}
if (isChunked)
{
SendChunkedData(http, readstream, offset, filesize);
}
else
{
SendData(http, readstream, p2vUri, filesize);
}
}
catch (ReConnectException)
{
throw;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
log.Error("OVF.Tools.Http.Put: Exception: {0}", ex);
}
finally
{
if (http != null)
http.Close();
http = null;
}
}
public void Get(string filename, Uri serverUri, string p2vUri)
{
try
{
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
Stream http = Connect(serverUri.Host, serverUri.Port);
String header = "GET " + p2vUri + " HTTP/1.0\r\n\r\n";
WriteLine(http, header);
String response = ReadLine(http);
int code = GetResultCode(response);
switch (code)
{
case 302:
string url = "";
while (true)
{
response = ReadLine(http);
if (response.StartsWith("Location: "))
url = response.Substring(10);
if (response.Equals("\r\n") || response.Equals("")) break;
}
Uri redirect = new Uri(url.Trim());
http.Close();
Get(filename, redirect, p2vUri);
break;
default:
http.Close();
return;
}
while (true)
{
response = ReadLine(http);
if (response.Equals("\r\n") || response.Equals("")) break;
}
// Stream should be positioned after the headers
}
}
catch (EndOfStreamException eof)
{
log.DebugFormat("Get::No Data: {0}", eof.Message);
}
catch (Exception ex)
{
//marshal_response(http, tag.Failed);
log.ErrorFormat("Get::Exception: {0}", ex.Message);
}
}
internal Stream DoHttp(String method, Uri serverUri, string p2vUri, NameValueCollection headers)
{
ServerURI = serverUri;
log.DebugFormat("Connect To: {0}:{1}", serverUri.Host, serverUri.Port);
Stream http = Connect(serverUri.Host, serverUri.Port);
String httprequest = string.Format("{0} {1} http:/1.0\r\n", method, p2vUri);
log.DebugFormat("Request: {0}", httprequest);
if (http == null)
{
throw new Exception("HTTP == NULL");
}
WriteLine(http, httprequest);
WriteMIMEHeaders(http, headers);
if (CheckResponse(http) != OK)
{
http.Close();
return null;
}
return http;
}
internal void WriteMIMEHeaders(Stream http, NameValueCollection headers)
{
StringBuilder request = new StringBuilder();
// Create the MIME Headers.
if (headers != null && headers.Count > 0)
{
foreach (string key in headers.Keys)
{
request.AppendFormat("{0}: {1}\r\n", key, headers[key]);
}
}
request.Append("\r\n");
//Log.Debug("HEADERS:\r\n{0}", request.ToString());
WriteBuffer(http, request.ToString());
//Log.Debug("MIME Headers Written");
}
internal int CheckResponse(Stream http)
{
int code = 0;
String response = ReadLine(http);
code = GetResultCode(response);
switch (code)
{
case 0:
{
log.Debug("No Return data at this time.");
code = 200;
break;
}
case 200:
{
log.Debug("200 OK");
break;
}
case 403:
{
log.Debug("403 FORBIDDEN, handler was not found");
break;
}
default:
{
log.DebugFormat("ERROR Returned: {0}", code);
break;
}
}
while (true)
{
response = ReadLine(http);
if (response.Equals("\r\n") || response.Equals(""))
break;
}
return code;
}
internal void SendTestData(Stream http)
{
byte[] testBuffer = new byte[KB];
byte x = 0x70;
byte y = 0;
for (int i = 0; i < 1024; i++)
{
if ((i % 10) == 0)
{
x = 0x70;
y = 0;
}
testBuffer[i] = (byte)(x + y++);
}
byte[] message = Encoding.UTF8.GetBytes(string.Format("{0}\r\n", testBuffer.Length));
http.Write(message, 0, message.Length);
http.Write(testBuffer, 0, testBuffer.Length);
message = Encoding.UTF8.GetBytes(string.Format("\r\n0\r\n"));
http.Write(message, 0, message.Length);
http.Flush();
log.DebugFormat("TestBuffer {0} bytes written", testBuffer.Length);
}
internal void SendFilesInDirectory(Stream http, string pathname)
{
byte[] message;
foreach (string file in Directory.GetFiles(pathname))
{
byte[] block = File.ReadAllBytes(file);
message = Encoding.UTF8.GetBytes(string.Format("{0}\r\n", block.Length));
http.Write(message, 0, message.Length);
http.Write(block, 0, block.Length);
message = Encoding.UTF8.GetBytes(string.Format("\r\n"));
http.Write(message, 0, message.Length);
log.DebugFormat("Block [{0}] written", file);
}
message = Encoding.UTF8.GetBytes(string.Format("0\r\n"));
http.Write(message, 0, message.Length);
http.Flush();
log.DebugFormat("Finished sending files in: {0}", pathname);
}
internal void SendChunkedData(Stream http, Stream filestream, long offset, long filesize)
{
byte Zero = 0x0;
byte[] sizeblock;
byte[] datablock = new byte[2 * MB];
byte[] endblock = Encoding.UTF8.GetBytes(string.Format("\r\n"));
byte[] finalblock = Encoding.UTF8.GetBytes(string.Format("0\r\n"));
List<byte> fullblock = new List<byte>();
long bytessent = offset;
int i = 0;
bool skipblock = false;
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileStart, "SendData Start", "Disk Copy", (ulong)offset, (ulong)filesize));
while (true)
{
// Form: chunked
// size\r\ndata\r\n\r\nsize\r\n\r\n0\r\n
// 1234
// asdfasdfasdfasdfsdfd
//
// 1234
// asdfasdfasdfasdfasdf
//
// 0
//
fullblock.Clear();
bool IsAllZeros = true;
int n = filestream.Read(datablock, 0, datablock.Length);
if (n <= 0) break;
sizeblock = Encoding.UTF8.GetBytes(string.Format("{0}\r\n", n));
try
{
for (int j = 0; j < datablock.Length; j++)
{
if (!datablock[j].Equals(Zero))
{
IsAllZeros = false;
break;
}
}
if (!IsAllZeros)
{
if (!skipblock)
{
fullblock.AddRange(sizeblock);
fullblock.AddRange(datablock);
fullblock.AddRange(endblock);
http.Write(fullblock.ToArray(), 0, fullblock.Count);
bytessent += n;
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileProgress, "SendData Start", "Disk Copy", (ulong)bytessent, (ulong)filesize));
Thread.Sleep(100);
}
else
{
Exception ex = new Exception("Skipped Empty");
throw new ReConnectException(string.Format("{0}", bytessent), ex);
}
}
else
{
bytessent += n;
skipblock = true;
}
}
catch (Exception ex)
{
log.DebugFormat("EXCEPTION: {0} : {1}", ex.GetType(), ex.Message);
throw new ReConnectException(string.Format("{0}", bytessent), ex);
}
string str1 = string.Format(">>> {0} <<< Block {1} : Total {2} : Full {3} Skipped: {4}\r",
i++,
n.ToString("N0", CultureInfo.InvariantCulture),
bytessent.ToString("N0", CultureInfo.InvariantCulture),
filesize.ToString("N0", CultureInfo.InvariantCulture),
skipblock);
log.Debug(Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(str1)));
Thread.Sleep(100);
}
http.Write(finalblock, 0, finalblock.Length);
http.Flush();
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileComplete, "SendData Completed", "Disk Copy", (ulong)bytessent, (ulong)filesize));
string str2 = string.Format("=== {0} === Total {1} : Full {2}\r",
i++,
bytessent.ToString("N0", CultureInfo.InvariantCulture),
filesize.ToString("N0", CultureInfo.InvariantCulture));
log.Debug(Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(str2)));
}
internal void SendData(Stream http, Stream filestream, string p2vUri, long filesize)
{
byte[] block = new byte[MB];
ulong p = 0;
int n = 0;
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileStart, "SendData Start", "Disk Copy", 0, (ulong)filesize));
while (true)
{
try
{
n = filestream.Read(block, 0, block.Length);
if (n <= 0) break;
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileProgress, "SendData Start", "Disk Copy", p, (ulong)filesize));
if (Cancel)
{
log.Warn("OVF.Tools.Http.SendData IMPORT CANCELED: resulting vm may be bad.");
throw new OperationCanceledException("Import canceled.");
}
http.Write(block, 0, n);
p += (ulong)n;
if (p >= (ulong)filesize)
break;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
log.ErrorFormat("OVF.Tools.Http.SendData FAILED {0}", ex.Message);
throw;
}
}
http.Flush();
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileComplete, "SendData Completed", "Disk Copy", p, (ulong)filesize));
}
internal string ReadLine(Stream stream)
{
StringBuilder messageData = new StringBuilder();
do
{
stream.ReadTimeout = 10000;
try
{
int i = stream.ReadByte();
if (i > 0)
{
char b = (char)i;
messageData.Append(b);
if (b == '\n') break;
}
}
catch
{
break;
}
} while (true);
return messageData.ToString();
}
internal void WriteLine(Stream stream, string line)
{
byte[] message = Encoding.UTF8.GetBytes(line);
stream.Write(message, 0, message.Length);
stream.Flush();
}
internal void WriteBuffer(Stream stream, string line)
{
byte[] message = Encoding.UTF8.GetBytes(line);
stream.Write(message, 0, message.Length);
}
internal int GetResultCode(string line)
{
string[] bits = line.Split(new char[] { ' ' });
if (bits.Length < 2) return 0;
return Int32.Parse(bits[1]);
}
internal void MarshalResponse(Stream stream, Tag t)
{
MarshalBaseTypes.MarshalInt(stream, 4 + 4);
MarshalTag(stream, Tag.Response);
MarshalTag(stream, t);
}
internal void MarshalTag(Stream stream, Tag tag)
{
MarshalBaseTypes.MarshalInt(stream, (int)tag);
}
internal bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// Do allow this client to communicate with unauthenticated servers.
return true;
}
internal Stream Connect(String hostname, int port)
{
//tcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
//tcpSocket.Blocking = true;
//tcpSocket.NoDelay = true;
//tcpSocket.LingerState = new LingerOption(true, 10);
//tcpSocket.SendTimeout = 50000;
//tcpSocket.Connect(hostname, port);
//NetworkStream stream = new NetworkStream(tcpSocket);
tcpClient = new TcpClient(hostname, port);
tcpClient.NoDelay = true;
tcpClient.LingerState = new LingerOption(true, 10);
tcpClient.SendTimeout = 50000;
NetworkStream stream = tcpClient.GetStream();
if (port == 443)
{
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(stream, false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
try
{
sslStream.AuthenticateAsClient("", null, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, true);
}
catch
{
if (tcpClient != null)
tcpClient.Close();
return null;
}
return sslStream;
}
return stream;
}
}
[Serializable]
public class ReConnectException : Exception
{
public ReConnectException()
: base()
{
}
public ReConnectException(string message)
: base(message)
{
}
public ReConnectException(string message, Exception ex)
: base(message, ex)
{
}
}
public class MarshalBaseTypes
{
public static uint UnmarshalInt32(Stream stream)
{
uint a = (uint)stream.ReadByte();
uint b = (uint)stream.ReadByte();
uint c = (uint)stream.ReadByte();
uint d = (uint)stream.ReadByte();
//Console.WriteLine("a = " + a + " b = " + b + " c = " + c + " d = " + d);
return (a << 0) | (b << 8) | (c << 16) | (d << 24);
}
public static void MarshalInt32(Stream stream, uint x)
{
uint mask = 0xff;
stream.WriteByte((byte)((x >> 0) & mask));
stream.WriteByte((byte)((x >> 8) & mask));
stream.WriteByte((byte)((x >> 16) & mask));
stream.WriteByte((byte)((x >> 24) & mask));
}
public static int UnmarshalInt(Stream stream)
{
return (int)UnmarshalInt32(stream);
}
public static void MarshalInt(Stream stream, int x)
{
MarshalInt32(stream, (uint)x);
}
public static byte[] Unmarshaln(Stream stream, uint n)
{
byte[] buffer = new byte[n];
int toread = (int)n;
int offset = 0;
while (toread > 0)
{
int nread = stream.Read(buffer, offset, toread);
offset = nread; toread -= nread;
}
return buffer;
}
public static string UnmarshalString(Stream stream)
{
uint length = UnmarshalInt32(stream);
byte[] buffer = Unmarshaln(stream, length);
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, (int)length)];
decoder.GetChars(buffer, 0, (int)length, chars, 0);
return new string(chars);
}
public static void MarshalString(Stream stream, string x)
{
MarshalInt(stream, x.Length);
char[] c = x.ToCharArray();
Encoder encoder = Encoding.UTF8.GetEncoder();
byte[] bytes = new byte[encoder.GetByteCount(c, 0, c.Length, true)];
encoder.GetBytes(c, 0, c.Length, bytes, 0, true);
stream.Write(bytes, 0, bytes.Length);
}
}
}