using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Web; using CryptoExchange.Net.Logging; using CryptoExchange.Net.Objects; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace CryptoExchange.Net { /// /// Helper methods /// public static class ExtensionMethods { /// /// Add a parameter /// /// /// /// public static void AddParameter(this Dictionary parameters, string key, string value) { parameters.Add(key, value); } /// /// Add a parameter /// /// /// /// /// public static void AddParameter(this Dictionary parameters, string key, string value, JsonConverter converter) { parameters.Add(key, JsonConvert.SerializeObject(value, converter)); } /// /// Add a parameter /// /// /// /// public static void AddParameter(this Dictionary parameters, string key, object value) { parameters.Add(key, value); } /// /// Add a parameter /// /// /// /// /// public static void AddParameter(this Dictionary parameters, string key, object value, JsonConverter converter) { parameters.Add(key, JsonConvert.SerializeObject(value, converter)); } /// /// Add an optional parameter. Not added if value is null /// /// /// /// public static void AddOptionalParameter(this Dictionary parameters, string key, object? value) { if (value != null) parameters.Add(key, value); } /// /// Add an optional parameter. Not added if value is null /// /// /// /// /// public static void AddOptionalParameter(this Dictionary parameters, string key, object? value, JsonConverter converter) { if (value != null) parameters.Add(key, JsonConvert.SerializeObject(value, converter)); } /// /// Add an optional parameter. Not added if value is null /// /// /// /// public static void AddOptionalParameter(this Dictionary parameters, string key, string? value) { if (value != null) parameters.Add(key, value); } /// /// Add an optional parameter. Not added if value is null /// /// /// /// /// public static void AddOptionalParameter(this Dictionary parameters, string key, string? value, JsonConverter converter) { if (value != null) parameters.Add(key, JsonConvert.SerializeObject(value, converter)); } /// /// Create a query string of the specified parameters /// /// The parameters to use /// Whether or not the values should be url encoded /// How to serialize array parameters /// public static string CreateParamString(this Dictionary parameters, bool urlEncodeValues, ArrayParametersSerialization serializationType) { var uriString = string.Empty; var arraysParameters = parameters.Where(p => p.Value.GetType().IsArray).ToList(); foreach (var arrayEntry in arraysParameters) { if (serializationType == ArrayParametersSerialization.Array) uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={v}"))}&"; else { var array = (Array)arrayEntry.Value; uriString += string.Join("&", array.OfType().Select(a => $"{arrayEntry.Key}={Uri.EscapeDataString(a.ToString())}")); uriString += "&"; } } uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? Uri.EscapeDataString(s.Value.ToString()) : s.Value)}"))}"; uriString = uriString.TrimEnd('&'); return uriString; } /// /// Convert a dictionary to formdata string /// /// /// public static string ToFormData(this SortedDictionary parameters) { var formData = HttpUtility.ParseQueryString(string.Empty); foreach (var kvp in parameters) { if (kvp.Value.GetType().IsArray) { var array = (Array)kvp.Value; foreach (var value in array) formData.Add(kvp.Key, value.ToString()); } else formData.Add(kvp.Key, kvp.Value.ToString()); } return formData.ToString(); } /// /// Get the string the secure string is representing /// /// The source secure string /// public static string GetString(this SecureString source) { lock (source) { string result; var length = source.Length; var pointer = IntPtr.Zero; var chars = new char[length]; try { pointer = Marshal.SecureStringToBSTR(source); Marshal.Copy(pointer, chars, 0, length); result = string.Join("", chars); } finally { if (pointer != IntPtr.Zero) { Marshal.ZeroFreeBSTR(pointer); } } return result; } } /// /// Are 2 secure strings equal /// /// Source secure string /// Compare secure string /// True if equal by value public static bool IsEqualTo(this SecureString ss1, SecureString ss2) { IntPtr bstr1 = IntPtr.Zero; IntPtr bstr2 = IntPtr.Zero; try { bstr1 = Marshal.SecureStringToBSTR(ss1); bstr2 = Marshal.SecureStringToBSTR(ss2); int length1 = Marshal.ReadInt32(bstr1, -4); int length2 = Marshal.ReadInt32(bstr2, -4); if (length1 == length2) { for (int x = 0; x < length1; ++x) { byte b1 = Marshal.ReadByte(bstr1, x); byte b2 = Marshal.ReadByte(bstr2, x); if (b1 != b2) return false; } } else return false; return true; } finally { if (bstr2 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr2); if (bstr1 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr1); } } /// /// Create a secure string from a string /// /// /// public static SecureString ToSecureString(this string source) { var secureString = new SecureString(); foreach (var c in source) secureString.AppendChar(c); secureString.MakeReadOnly(); return secureString; } /// /// String to JToken /// /// /// /// public static JToken? ToJToken(this string stringData, Log? log = null) { if (string.IsNullOrEmpty(stringData)) return null; try { return JToken.Parse(stringData); } catch (JsonReaderException jre) { var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Data: {stringData}"; log?.Write(LogLevel.Error, info); if (log == null) Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | {info}"); return null; } catch (JsonSerializationException jse) { var info = $"Deserialize JsonSerializationException: {jse.Message}. Data: {stringData}"; log?.Write(LogLevel.Error, info); if (log == null) Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | {info}"); return null; } } /// /// Validates an int is one of the allowed values /// /// Value of the int /// Name of the parameter /// Allowed values public static void ValidateIntValues(this int value, string argumentName, params int[] allowedValues) { if (!allowedValues.Contains(value)) throw new ArgumentException( $"{value} not allowed for parameter {argumentName}, allowed values: {string.Join(", ", allowedValues)}", argumentName); } /// /// Validates an int is between two values /// /// The value of the int /// Name of the parameter /// Min value /// Max value public static void ValidateIntBetween(this int value, string argumentName, int minValue, int maxValue) { if (value < minValue || value > maxValue) throw new ArgumentException( $"{value} not allowed for parameter {argumentName}, min: {minValue}, max: {maxValue}", argumentName); } /// /// Validates a string is not null or empty /// /// The value of the string /// Name of the parameter public static void ValidateNotNull(this string value, string argumentName) { if (string.IsNullOrEmpty(value)) throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName); } /// /// Validates a string is null or not empty /// /// /// public static void ValidateNullOrNotEmpty(this string value, string argumentName) { if (value != null && string.IsNullOrEmpty(value)) throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName); } /// /// Validates an object is not null /// /// The value of the object /// Name of the parameter public static void ValidateNotNull(this object value, string argumentName) { if (value == null) throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName); } /// /// Validates a list is not null or empty /// /// The value of the object /// Name of the parameter public static void ValidateNotNull(this IEnumerable value, string argumentName) { if (value == null || !value.Any()) throw new ArgumentException($"No values provided for parameter {argumentName}", argumentName); } /// /// Format an exception and inner exception to a readable string /// /// /// public static string ToLogString(this Exception? exception) { var message = new StringBuilder(); var indent = 0; while (exception != null) { for (var i = 0; i < indent; i++) message.Append(' '); message.Append(exception.GetType().Name); message.Append(" - "); message.AppendLine(exception.Message); for (var i = 0; i < indent; i++) message.Append(' '); message.AppendLine(exception.StackTrace); indent += 2; exception = exception.InnerException; } return message.ToString(); } /// /// Append a base url with provided path /// /// /// /// public static string AppendPath(this string url, params string[] path) { if (!url.EndsWith("/")) url += "/"; foreach (var item in path) url += item.Trim('/') + "/"; return url.TrimEnd('/'); } /// /// Fill parameters in a path. Parameters are specified by '{}' and should be specified in occuring sequence /// /// The total path string /// The values to fill /// public static string FillPathParameters(this string path, params string[] values) { foreach (var value in values) { var index = path.IndexOf("{}", StringComparison.Ordinal); if (index >= 0) { path = path.Remove(index, 2); path = path.Insert(index, value); } } return path; } /// /// Create a new uri with the provided parameters as query /// /// /// /// public static Uri SetParameters(this Uri baseUri, SortedDictionary parameters) { var uriBuilder = new UriBuilder(); uriBuilder.Scheme = baseUri.Scheme; uriBuilder.Host = baseUri.Host; uriBuilder.Path = baseUri.AbsolutePath; var httpValueCollection = HttpUtility.ParseQueryString(string.Empty); foreach (var parameter in parameters) httpValueCollection.Add(parameter.Key, parameter.Value.ToString()); uriBuilder.Query = httpValueCollection.ToString(); return uriBuilder.Uri; } /// /// Create a new uri with the provided parameters as query /// /// /// /// public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable> parameters) { var uriBuilder = new UriBuilder(); uriBuilder.Scheme = baseUri.Scheme; uriBuilder.Host = baseUri.Host; uriBuilder.Path = baseUri.AbsolutePath; var httpValueCollection = HttpUtility.ParseQueryString(string.Empty); foreach (var parameter in parameters) httpValueCollection.Add(parameter.Key, parameter.Value.ToString()); uriBuilder.Query = httpValueCollection.ToString(); return uriBuilder.Uri; } /// /// Add parameter to URI /// /// /// /// /// public static Uri AddQueryParmeter(this Uri uri, string name, string value) { var httpValueCollection = HttpUtility.ParseQueryString(uri.Query); httpValueCollection.Remove(name); httpValueCollection.Add(name, value); var ub = new UriBuilder(uri); ub.Query = httpValueCollection.ToString(); return ub.Uri; } } }