/* 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.Cryptography.X509Certificates; using System.Security.Authentication; using System.Text; using DiscUtils; using XenOvf.Utilities; namespace XenOvfTransport { public class Http { public Action 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 rce) { throw rce; } catch (OperationCanceledException oce) { throw oce; } 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.Debug("Get::No Data: {0}", eof.Message); } catch (Exception ex) { //marshal_response(http, tag.Failed); Log.Error("Get::Exception: {0}", ex.Message); } } internal Stream DoHttp(String method, Uri serverUri, string p2vUri, NameValueCollection headers) { ServerURI = serverUri; Log.Debug("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.Debug("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.Debug("ERROR Returned: {0}", code); break; } } while (true) { response = ReadLine(http); if (response.Equals("\r\n") || response.Equals("")) break; //Log.Debug("RESPONSE: {0}", response); } 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.Debug("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.Debug("Block [{0}] written", file); } message = Encoding.UTF8.GetBytes(string.Format("0\r\n")); http.Write(message, 0, message.Length); http.Flush(); Log.Debug("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 fullblock = new List(); 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.Debug("EXCEPTION: {0} : {1}", ex.GetType().ToString(), ex.Message); throw new ReConnectException(string.Format("{0}", bytessent), ex); } Log.Bytes(Encoding.UTF8.GetBytes(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))); Thread.Sleep(100); } http.Write(finalblock, 0, finalblock.Length); http.Flush(); OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.FileComplete, "SendData Completed", "Disk Copy", (ulong)bytessent, (ulong)filesize)); Log.Bytes(Encoding.UTF8.GetBytes(string.Format("=== {0} === Total {1} : Full {2}\r", i++, bytessent.ToString("N0", CultureInfo.InvariantCulture), filesize.ToString("N0", CultureInfo.InvariantCulture)))); } 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.Warning("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 (VhdException vhdex) //{ // Log.Error("OVF.Tools.Http.SendData Vhd Exception: {0}", vhdex.Message); // throw vhdex; //} catch (OperationCanceledException oce) { throw oce; } catch (Exception ex) { Log.Error("OVF.Tools.Http.SendData FAILED {0}", ex.Message); throw ex; } } 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(""); } 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); } } }