using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; #if NETFX_CORE using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; using Windows.Storage.Streams; using BestHTTP.PlatformSupport.IO; using FileStream = BestHTTP.PlatformSupport.IO.FileStream; #elif UNITY_WP8 using Cryptography = BestHTTP.PlatformSupport.Cryptography; #else using Cryptography = System.Security.Cryptography; using FileStream = System.IO.FileStream; #endif namespace BestHTTP.Extensions { public static class Extensions { #region ASCII Encoding (These are required because Windows Phone doesn't supports the Encoding.ASCII class.) /// /// On WP8 platform there are no ASCII encoding. /// public static string AsciiToString(this byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.Length); foreach (byte b in bytes) sb.Append(b <= 0x7f ? (char)b : '?'); return sb.ToString(); } /// /// On WP8 platform there are no ASCII encoding. /// public static byte[] GetASCIIBytes(this string str) { byte[] result = new byte[str.Length]; for (int i = 0; i < str.Length; ++i) { char ch = str[i]; result[i] = (byte)((ch < (char)0x80) ? ch : '?'); } return result; } public static void SendAsASCII(this BinaryWriter stream, string str) { for (int i = 0; i < str.Length; ++i) { char ch = str[i]; stream.Write((byte)((ch < (char)0x80) ? ch : '?')); } } #endregion #region FileSystem WriteLine function support public static void WriteLine(this FileStream fs) { fs.Write(HTTPRequest.EOL, 0, 2); } public static void WriteLine(this FileStream fs, string line) { var buff = line.GetASCIIBytes(); fs.Write(buff, 0, buff.Length); fs.WriteLine(); } public static void WriteLine(this FileStream fs, string format, params object[] values) { var buff = string.Format(format, values).GetASCIIBytes(); fs.Write(buff, 0, buff.Length); fs.WriteLine(); } #endregion #region Other Extensions public static string GetRequestPathAndQueryURL(this Uri uri) { string requestPathAndQuery = uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped); // http://forum.unity3d.com/threads/best-http-released.200006/page-26#post-2723250 if (string.IsNullOrEmpty(requestPathAndQuery)) requestPathAndQuery = "/"; return requestPathAndQuery; } public static string[] FindOption(this string str, string option) { //s-maxage=2678400, must-revalidate, max-age=0 string[] options = str.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); option = option.ToLower(); for (int i = 0; i < options.Length; ++i) if (options[i].Contains(option)) return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); return null; } public static void WriteArray(this Stream stream, byte[] array) { stream.Write(array, 0, array.Length); } /// /// Returns true if the Uri's host is a valid IPv4 or IPv6 address. /// public static bool IsHostIsAnIPAddress(this Uri uri) { if (uri == null) return false; return IsIpV4AddressValid(uri.Host) || IsIpV6AddressValid(uri.Host); } // Original idea from: https://www.code4copy.com/csharp/c-validate-ip-address-string/ // Working regex: https://www.regular-expressions.info/ip.html private static readonly System.Text.RegularExpressions.Regex validIpV4AddressRegex = new System.Text.RegularExpressions.Regex("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b", System.Text.RegularExpressions.RegexOptions.IgnoreCase); /// /// Validates an IPv4 address. /// public static bool IsIpV4AddressValid(string address) { if (!string.IsNullOrEmpty(address)) return validIpV4AddressRegex.IsMatch(address.Trim()); return false; } /// /// Validates an IPv6 address. /// public static bool IsIpV6AddressValid(string address) { #if !NETFX_CORE if (!string.IsNullOrEmpty(address)) { System.Net.IPAddress ip; if (System.Net.IPAddress.TryParse(address, out ip)) return ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6; } #endif return false; } #endregion #region String Conversions public static int ToInt32(this string str, int defaultValue = default(int)) { if (str == null) return defaultValue; try { return int.Parse(str); } catch { return defaultValue; } } public static long ToInt64(this string str, long defaultValue = default(long)) { if (str == null) return defaultValue; try { return long.Parse(str); } catch { return defaultValue; } } public static DateTime ToDateTime(this string str, DateTime defaultValue = default(DateTime)) { if (str == null) return defaultValue; try { DateTime.TryParse(str, out defaultValue); return defaultValue.ToUniversalTime(); } catch { return defaultValue; } } public static string ToStrOrEmpty(this string str) { if (str == null) return String.Empty; return str; } #endregion #region MD5 Hashing public static string CalculateMD5Hash(this string input) { return input.GetASCIIBytes().CalculateMD5Hash(); } public static string CalculateMD5Hash(this byte[] input) { #if NETFX_CORE var alg = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5); IBuffer buff = CryptographicBuffer.CreateFromByteArray(input); var hashed = alg.HashData(buff); var res = CryptographicBuffer.EncodeToHexString(hashed); return res; #else var hash = Cryptography.MD5.Create().ComputeHash(input); var sb = new StringBuilder(); foreach (var b in hash) sb.Append(b.ToString("x2")); return sb.ToString(); #endif } #endregion #region Efficient String Parsing Helpers internal static string Read(this string str, ref int pos, char block, bool needResult = true) { return str.Read(ref pos, (ch) => ch != block, needResult); } internal static string Read(this string str, ref int pos, Func block, bool needResult = true) { if (pos >= str.Length) return string.Empty; str.SkipWhiteSpace(ref pos); int startPos = pos; while (pos < str.Length && block(str[pos])) pos++; string result = needResult ? str.Substring(startPos, pos - startPos) : null; // set position to the next char pos++; return result; } internal static string ReadPossibleQuotedText(this string str, ref int pos) { string result = string.Empty; if (str == null) return result; // It's a quoted text? if (str[pos] == '\"') { // Skip the starting quote str.Read(ref pos, '\"', false); // Read the text until the ending quote result = str.Read(ref pos, '\"'); // Next option str.Read(ref pos, ',', false); } else // It's not a quoted text, so we will read until the next option result = str.Read(ref pos, (ch) => ch != ',' && ch != ';'); return result; } internal static void SkipWhiteSpace(this string str, ref int pos) { if (pos >= str.Length) return; while (pos < str.Length && char.IsWhiteSpace(str[pos])) pos++; } internal static string TrimAndLower(this string str) { if (str == null) return null; char[] buffer = new char[str.Length]; int length = 0; for (int i = 0; i < str.Length; ++i) { char ch = str[i]; if (!char.IsWhiteSpace(ch) && !char.IsControl(ch)) buffer[length++] = char.ToLowerInvariant(ch); } return new string(buffer, 0, length); } internal static char? Peek(this string str, int pos) { if (pos < 0 || pos >= str.Length) return null; return str[pos]; } #endregion #region Specialized String Parsers //public, max-age=2592000 internal static List ParseOptionalHeader(this string str) { List result = new List(); if (str == null) return result; int idx = 0; // process the rest of the text while (idx < str.Length) { // Read key string key = str.Read(ref idx, (ch) => ch != '=' && ch != ',').TrimAndLower(); HeaderValue qp = new HeaderValue(key); if (str[idx - 1] == '=') qp.Value = str.ReadPossibleQuotedText(ref idx); result.Add(qp); } return result; } //deflate, gzip, x-gzip, identity, *;q=0 internal static List ParseQualityParams(this string str) { List result = new List(); if (str == null) return result; int idx = 0; while (idx < str.Length) { string key = str.Read(ref idx, (ch) => ch != ',' && ch != ';').TrimAndLower(); HeaderValue qp = new HeaderValue(key); if (str[idx - 1] == ';') { str.Read(ref idx, '=', false); qp.Value = str.Read(ref idx, ','); } result.Add(qp); } return result; } #endregion #region Buffer Filling /// /// Will fill the entire buffer from the stream. Will throw an exception when the underlying stream is closed. /// public static void ReadBuffer(this Stream stream, byte[] buffer) { int count = 0; do { int read = stream.Read(buffer, count, buffer.Length - count); if (read <= 0) throw ExceptionHelper.ServerClosedTCPStream(); count += read; } while (count < buffer.Length); } #endregion #region MemoryStream public static void WriteAll(this MemoryStream ms, byte[] buffer) { ms.Write(buffer, 0, buffer.Length); } public static void WriteString(this MemoryStream ms, string str) { byte[] buffer = Encoding.UTF8.GetBytes(str); ms.WriteAll(buffer); } public static void WriteLine(this MemoryStream ms) { ms.WriteAll(HTTPRequest.EOL); } public static void WriteLine(this MemoryStream ms, string str) { ms.WriteString(str); ms.WriteLine(); } #endregion } public static class ExceptionHelper { public static Exception ServerClosedTCPStream() { return new Exception("TCP Stream closed unexpectedly by the remote server"); } } }