/******************************************************************************* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"). You may not use * this file except in compliance with the License. A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * ***************************************************************************** * __ _ _ ___ * ( )( \/\/ )/ __) * /__\ \ / \__ \ * (_)(_) \/\/ (___/ * * AWS SDK for .NET */ using Amazon.Runtime.Internal.Util; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using System.Linq; using System.Net; using Amazon.Runtime.Internal; using Amazon.Runtime; using Amazon.Util.Internal; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; #if AWS_ASYNC_API using System.Threading.Tasks; #endif #if NETSTANDARD using System.Net.Http; using System.Runtime.InteropServices; #endif namespace Amazon.Util { /// /// This class defines utilities and constants that can be used by /// all the client libraries of the SDK. /// public static partial class AWSSDKUtils { #region Internal Constants internal const string DefaultRegion = "us-east-1"; internal const string DefaultGovRegion = "us-gov-west-1"; private const char WindowsDirectorySeparatorChar = '\\'; private const char WindowsAltDirectorySeparatorChar = '/'; private const char WindowsVolumeSeparatorChar = ':'; private const char SlashChar = '/'; private const string Slash = "/"; private const string EncodedSlash = "%2F"; internal const int DefaultMaxRetry = 3; private const int DefaultConnectionLimit = 50; private const int DefaultMaxIdleTime = 50 * 1000; // 50 seconds private const int MaxIsSetMethodsCacheSize = 50; public static readonly DateTime EPOCH_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public const int DefaultBufferSize = 8192; // Default value of progress update interval for streaming is 100KB. public const long DefaultProgressUpdateInterval = 102400; internal static Dictionary RFCEncodingSchemes = new Dictionary { { 3986, ValidUrlCharacters }, { 1738, ValidUrlCharactersRFC1738 } }; internal const string S3Accelerate = "s3-accelerate"; internal const string S3Control = "s3-control"; private const int DefaultMarshallerVersion = 2; private static readonly string _userAgent = InternalSDKUtils.BuildUserAgentString(string.Empty); #endregion #region Public Constants /// /// The user agent string header /// public const string UserAgentHeader = "User-Agent"; /// /// The Set of accepted and valid Url characters per RFC3986. /// Characters outside of this set will be encoded. /// public const string ValidUrlCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; /// /// The Set of accepted and valid Url characters per RFC1738. /// Characters outside of this set will be encoded. /// public const string ValidUrlCharactersRFC1738 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."; /// /// The set of accepted and valid Url path characters per RFC3986. /// private static string ValidPathCharacters = DetermineValidPathCharacters(); /// /// The set of characters which are not to be encoded as part of the X-Amzn-Trace-Id header values /// public const string ValidTraceIdHeaderValueCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=;:+&[]{}\"',"; // Checks which path characters should not be encoded // This set will be different for .NET 4 and .NET 4.5, as // per http://msdn.microsoft.com/en-us/library/hh367887%28v=vs.110%29.aspx private static string DetermineValidPathCharacters() { const string basePathCharacters = "/:'()!*[]$"; var sb = new StringBuilder(); foreach (var c in basePathCharacters) { var escaped = Uri.EscapeUriString(c.ToString()); if (escaped.Length == 1 && escaped[0] == c) sb.Append(c); } return sb.ToString(); } /// /// The string representing Url Encoded Content in HTTP requests /// public const string UrlEncodedContent = "application/x-www-form-urlencoded; charset=utf-8"; /// /// The GMT Date Format string. Used when parsing date objects /// public const string GMTDateFormat = "ddd, dd MMM yyyy HH:mm:ss \\G\\M\\T"; /// /// The ISO8601Date Format string. Used when parsing date objects /// public const string ISO8601DateFormat = "yyyy-MM-dd\\THH:mm:ss.fff\\Z"; /// /// The ISO8601Date Format string. Used when parsing date objects /// public const string ISO8601DateFormatNoMS = "yyyy-MM-dd\\THH:mm:ss\\Z"; /// /// The ISO8601 Basic date/time format string. Used when parsing date objects /// public const string ISO8601BasicDateTimeFormat = "yyyyMMddTHHmmssZ"; /// /// The ISO8601 basic date format. Used during AWS4 signature computation. /// public const string ISO8601BasicDateFormat = "yyyyMMdd"; /// /// The RFC822Date Format string. Used when parsing date objects /// public const string RFC822DateFormat = "ddd, dd MMM yyyy HH:mm:ss \\G\\M\\T"; #endregion #region Internal Methods /// /// Returns an extension of a path. /// This has the same behavior as System.IO.Path.GetExtension, but does not /// check the path for invalid characters. /// /// /// public static string GetExtension(string path) { if (path == null) return null; int length = path.Length; int index = length; while (--index >= 0) { char ch = path[index]; if (ch == '.') { if (index != length - 1) return path.Substring(index, length - index); else return string.Empty; } else if (IsPathSeparator(ch)) break; } return string.Empty; } // Checks if the character is one \ / : private static bool IsPathSeparator(char ch) { return (ch == '\\' || ch == '/' || ch == ':'); } /* * Determines the string to be signed based on the input parameters for * AWS Signature Version 2 */ internal static string CalculateStringToSignV2(ParameterCollection parameterCollection, string serviceUrl) { StringBuilder data = new StringBuilder("POST\n", 512); var sortedParameters = parameterCollection.GetSortedParametersList(); Uri endpoint = new Uri(serviceUrl); data.Append(endpoint.Host); data.Append("\n"); string uri = endpoint.AbsolutePath; if (uri == null || uri.Length == 0) { uri = "/"; } data.Append(AWSSDKUtils.UrlEncode(uri, true)); data.Append("\n"); foreach (KeyValuePair pair in sortedParameters) { if (pair.Value != null) { data.Append(AWSSDKUtils.UrlEncode(pair.Key, false)); data.Append("="); data.Append(AWSSDKUtils.UrlEncode(pair.Value, false)); data.Append("&"); } } string result = data.ToString(); return result.Remove(result.Length - 1); } /** * Convert request parameters to Url encoded query string */ internal static string GetParametersAsString(IRequest request) { return GetParametersAsString(request.ParameterCollection); } /** * Convert Dictionary of parameters to Url encoded query string */ internal static string GetParametersAsString(ParameterCollection parameterCollection) { var sortedParameters = parameterCollection.GetSortedParametersList(); StringBuilder data = new StringBuilder(512); foreach (var kvp in sortedParameters) { var key = kvp.Key; var value = kvp.Value; if (value != null) { data.Append(key); data.Append('='); data.Append(AWSSDKUtils.UrlEncode(value, false)); data.Append('&'); } } string result = data.ToString(); if (result.Length == 0) return string.Empty; return result.Remove(result.Length - 1); } /// /// Returns the canonicalized resource path for the service endpoint with single URL encoded path segments. /// /// Endpoint URL for the request /// Resource path for the request /// /// If resourcePath begins or ends with slash, the resulting canonicalized /// path will follow suit. /// /// Canonicalized resource path for the endpoint [Obsolete("Use CanonicalizeResourcePathV2 instead")] public static string CanonicalizeResourcePath(Uri endpoint, string resourcePath) { // This overload is kept for backward compatibility in existing code bases. return CanonicalizeResourcePath(endpoint, resourcePath, false, null, DefaultMarshallerVersion); } /// /// Returns the canonicalized resource path for the service endpoint /// /// Endpoint URL for the request /// Resource path for the request /// If true pre URL encode path segments if necessary. /// S3 is currently the only service that does not expect pre URL encoded segments. /// /// If resourcePath begins or ends with slash, the resulting canonicalized /// path will follow suit. /// /// Canonicalized resource path for the endpoint [Obsolete("Use CanonicalizeResourcePathV2 instead")] public static string CanonicalizeResourcePath(Uri endpoint, string resourcePath, bool detectPreEncode) { // This overload is kept for backward compatibility in existing code bases. return CanonicalizeResourcePath(endpoint, resourcePath, detectPreEncode, null, DefaultMarshallerVersion); } /// /// Returns the canonicalized resource path for the service endpoint /// /// Endpoint URL for the request /// Resource path for the request /// If true pre URL encode path segments if necessary. /// S3 is currently the only service that does not expect pre URL encoded segments. /// Dictionary of key/value parameters containing the values for the ResourcePath key replacements /// The version of the marshaller that constructed the request object. /// /// If resourcePath begins or ends with slash, the resulting canonicalized /// path will follow suit. /// /// Canonicalized resource path for the endpoint [Obsolete("Use CanonicalizeResourcePathV2 instead")] public static string CanonicalizeResourcePath(Uri endpoint, string resourcePath, bool detectPreEncode, IDictionary pathResources, int marshallerVersion) { if (endpoint != null) { var path = endpoint.AbsolutePath; if (string.IsNullOrEmpty(path) || string.Equals(path, Slash, StringComparison.Ordinal)) path = string.Empty; if (!string.IsNullOrEmpty(resourcePath) && resourcePath.StartsWith(Slash, StringComparison.Ordinal)) resourcePath = resourcePath.Substring(1); if (!string.IsNullOrEmpty(resourcePath)) path = path + Slash + resourcePath; resourcePath = path; } if (string.IsNullOrEmpty(resourcePath)) return Slash; IEnumerable encodedSegments = AWSSDKUtils.SplitResourcePathIntoSegments(resourcePath, pathResources); var pathWasPreEncoded = false; if (detectPreEncode) { if (endpoint == null) throw new ArgumentNullException(nameof(endpoint), "A non-null endpoint is necessary to decide whether or not to pre URL encode."); // S3 is a special case. For S3 skip the pre encode. // For everything else URL pre encode the resource path segments. if (!S3Uri.IsS3Uri(endpoint)) { encodedSegments = encodedSegments.Select(segment => UrlEncode(segment, true).Replace(Slash, EncodedSlash)); pathWasPreEncoded = true; } } var canonicalizedResourcePath = AWSSDKUtils.JoinResourcePathSegments(encodedSegments, false); // Get the logger each time (it's cached) because we shouldn't store it in a static variable. Logger.GetLogger(typeof(AWSSDKUtils)).DebugFormat("{0} encoded {1}{2} for canonicalization: {3}", pathWasPreEncoded ? "Double" : "Single", resourcePath, endpoint == null ? "" : " with endpoint " + endpoint.AbsoluteUri, canonicalizedResourcePath); return canonicalizedResourcePath; } /// /// Returns the canonicalized resource path for the service endpoint. /// /// Endpoint URL for the request. /// Resource path for the request. /// If true will URL-encode path segments including "/". "S3" is currently the only service that does not expect pre URL-encoded segments. /// Dictionary of key/value parameters containing the values for the ResourcePath key replacements. /// If resourcePath begins or ends with slash, the resulting canonicalized path will follow suit. /// Canonicalized resource path for the endpoint. public static string CanonicalizeResourcePathV2(Uri endpoint, string resourcePath, bool encode, IDictionary pathResources) { if (endpoint != null) { var path = endpoint.AbsolutePath; if (string.IsNullOrEmpty(path) || string.Equals(path, Slash, StringComparison.Ordinal)) path = string.Empty; if (!string.IsNullOrEmpty(resourcePath) && resourcePath.StartsWith(Slash, StringComparison.Ordinal)) resourcePath = resourcePath.Substring(1); if (!string.IsNullOrEmpty(resourcePath)) path = path + Slash + resourcePath; resourcePath = path; } if (string.IsNullOrEmpty(resourcePath)) return Slash; IEnumerable encodedSegments = AWSSDKUtils.SplitResourcePathIntoSegments(resourcePath, pathResources); var pathWasPreEncoded = false; if (encode) { if (endpoint == null) throw new ArgumentNullException(nameof(endpoint), "A non-null endpoint is necessary to decide whether or not to pre URL encode."); encodedSegments = encodedSegments.Select(segment => UrlEncode(segment, true).Replace(Slash, EncodedSlash)); pathWasPreEncoded = true; } var canonicalizedResourcePath = AWSSDKUtils.JoinResourcePathSegments(encodedSegments, false); // Get the logger each time (it's cached) because we shouldn't store it in a static variable. Logger.GetLogger(typeof(AWSSDKUtils)).DebugFormat("{0} encoded {1}{2} for canonicalization: {3}", pathWasPreEncoded ? "Double" : "Single", resourcePath, endpoint == null ? "" : " with endpoint " + endpoint.AbsoluteUri, canonicalizedResourcePath); return canonicalizedResourcePath; } /// /// Splits the resourcePath at / into segments then resolves any keys with the path resource values. Greedy /// key values will be split into multiple segments at each /. /// /// The patterned resourcePath /// The key/value lookup for the patterned resourcePath /// A list of path segments where all keys in the resourcePath have been resolved to one or more path segment values public static IEnumerable SplitResourcePathIntoSegments(string resourcePath, IDictionary pathResources) { var splitChars = new char[] { SlashChar }; var pathSegments = resourcePath.Split(splitChars, StringSplitOptions.None); if(pathResources == null || pathResources.Count == 0) { return pathSegments; } //Otherwise there are key/values that need to be resolved var resolvedSegments = new List(); foreach(var segment in pathSegments) { if (!pathResources.ContainsKey(segment)) { resolvedSegments.Add(segment); continue; } //Determine if the path is greedy. If greedy the segment will be split at each / into multiple segments. if (segment.EndsWith("+}", StringComparison.Ordinal)) { resolvedSegments.AddRange(pathResources[segment].Split(splitChars, StringSplitOptions.None)); } else { resolvedSegments.Add(pathResources[segment]); } } return resolvedSegments; } /// /// Joins all path segments with the / character and encodes each segment before joining. /// /// The segments of a URL path split at each / character /// If the path property is specified, /// the accepted path characters {/+:} are not encoded. /// A joined URL with encoded segments public static string JoinResourcePathSegments(IEnumerable pathSegments, bool path) { // Encode for canonicalization pathSegments = pathSegments.Select(segment => UrlEncode(segment, path)); if (path) { pathSegments = pathSegments.Select(segment => segment.Replace(Slash, EncodedSlash)); } // join the encoded segments with / return string.Join(Slash, pathSegments.ToArray()); } /// /// Takes a patterned resource path and resolves it using the key/value path resources into /// a segmented encoded URL. /// /// The patterned resourcePath /// The key/value lookup for the patterned resourcePath /// A segmented encoded URL public static string ResolveResourcePath(string resourcePath, IDictionary pathResources) { return ResolveResourcePath(resourcePath, pathResources, true); } /// /// Takes a patterned resource path and resolves it using the key/value path resources into /// a segmented encoded URL. /// /// The patterned resourcePath /// The key/value lookup for the patterned resourcePath /// If true valid path characters {/+:} are not encoded /// A segmented encoded URL public static string ResolveResourcePath(string resourcePath, IDictionary pathResources, bool skipEncodingValidPathChars) { if (string.IsNullOrEmpty(resourcePath)) { return resourcePath; } return JoinResourcePathSegments(SplitResourcePathIntoSegments(resourcePath, pathResources), skipEncodingValidPathChars); } /// /// Returns a new string created by joining each of the strings in the /// specified list together, with a comma between them. /// /// The list of strings to join into a single, comma delimited /// string list. /// A new string created by joining each of the strings in the /// specified list together, with a comma between strings. public static String Join(List strings) { StringBuilder result = new StringBuilder(); Boolean first = true; foreach (String s in strings) { if (!first) result.Append(", "); result.Append(s); first = false; } return result.ToString(); } /// /// Attempt to infer the region for a service request based on the endpoint /// /// Endpoint to the service to be called /// /// Region parsed from the endpoint; DefaultRegion (or DefaultGovRegion) /// if it cannot be determined/is not explicit /// public static string DetermineRegion(string url) { var regionEndpoint = RegionFinder.Instance.FindRegion(url); return regionEndpoint?.RegionName; } /// /// Attempt to infer the service name for a request (in short form, eg 'iam') from the /// service endpoint. /// /// Endpoint to the service to be called /// /// Short-form name of the service parsed from the endpoint; empty string if it cannot /// be determined /// public static string DetermineService(string url) { int delimIndex = url.IndexOf("//", StringComparison.Ordinal); if (delimIndex >= 0) url = url.Substring(delimIndex + 2); string[] urlParts = url.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); if (urlParts == null || urlParts.Length == 0) return string.Empty; string servicePart = urlParts[0]; int hyphenated = servicePart.IndexOf('-'); string service; if (hyphenated < 0) { service = servicePart; } else { service = servicePart.Substring(0, hyphenated); } // Check for SQS : return "sqs" incase service is determined to be "queue" as per the URL. if (service.Equals("queue")) { return "sqs"; } else { return service; } } /// /// Utility method for converting Unix epoch seconds to DateTime structure. /// /// The number of seconds since January 1, 1970. /// Converted DateTime structure public static DateTime ConvertFromUnixEpochSeconds(int seconds) { return new DateTime(seconds * 10000000L + EPOCH_START.Ticks, DateTimeKind.Utc).ToLocalTime(); } /// /// Utility method for converting Unix epoch milliseconds to DateTime structure. /// /// The number of milliseconds since January 1, 1970. /// Converted DateTime structure public static DateTime ConvertFromUnixEpochMilliseconds(long milliseconds) { return new DateTime(milliseconds * 10000L + EPOCH_START.Ticks, DateTimeKind.Utc).ToLocalTime(); } public static int ConvertToUnixEpochSeconds(DateTime dateTime) { return Convert.ToInt32(GetTimeSpanInTicks(dateTime).TotalSeconds); } public static string ConvertToUnixEpochSecondsString(DateTime dateTime) { return Convert.ToInt64(GetTimeSpanInTicks(dateTime).TotalSeconds).ToString(CultureInfo.InvariantCulture); } [Obsolete("This method isn't named correctly: it returns seconds instead of milliseconds. Use ConvertToUnixEpochSecondsDouble instead.", false)] public static double ConvertToUnixEpochMilliSeconds(DateTime dateTime) { return ConvertToUnixEpochSecondsDouble(dateTime); } public static double ConvertToUnixEpochSecondsDouble(DateTime dateTime) { return Math.Round(GetTimeSpanInTicks(dateTime).TotalMilliseconds, 0) / 1000.0; } public static TimeSpan GetTimeSpanInTicks(DateTime dateTime) { return new TimeSpan(dateTime.ToUniversalTime().Ticks - EPOCH_START.Ticks); } public static long ConvertDateTimetoMilliseconds(DateTime dateTime) { return ConvertTimeSpanToMilliseconds(GetTimeSpanInTicks(dateTime)); } public static long ConvertTimeSpanToMilliseconds(TimeSpan timeSpan) { return timeSpan.Ticks / TimeSpan.TicksPerMillisecond; } /// /// Helper function to format a byte array into string /// /// The data blob to process /// If true, returns hex digits in lower case form /// String version of the data public static string ToHex(byte[] data, bool lowercase) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < data.Length; i++) { sb.Append(data[i].ToString(lowercase ? "x2" : "X2", CultureInfo.InvariantCulture)); } return sb.ToString(); } /// /// Calls a specific EventHandler in a background thread /// /// /// /// public static void InvokeInBackground(EventHandler handler, T args, object sender) where T : EventArgs { if (handler == null) return; var list = handler.GetInvocationList(); foreach (var call in list) { var eventHandler = ((EventHandler)call); if (eventHandler != null) { Dispatcher.Dispatch(() => eventHandler(sender, args)); } } } private static BackgroundInvoker _dispatcher; private static BackgroundInvoker Dispatcher { get { if (_dispatcher == null) { _dispatcher = new BackgroundInvoker(); } return _dispatcher; } } /// /// Parses a query string of a URL and returns the parameters as a string-to-string dictionary. /// /// /// public static Dictionary ParseQueryParameters(string url) { Dictionary parameters = new Dictionary(); if (!string.IsNullOrEmpty(url)) { int queryIndex = url.IndexOf('?'); if (queryIndex >= 0) { string queryString = url.Substring(queryIndex + 1); string[] kvps = queryString.Split(new char[] { '&' }, StringSplitOptions.None); foreach (string kvp in kvps) { if (string.IsNullOrEmpty(kvp)) continue; string[] nameValuePair = kvp.Split(new char[] { '=' }, 2); string name = nameValuePair[0]; string value = nameValuePair.Length == 1 ? null : nameValuePair[1]; parameters[name] = value; } } } return parameters; } internal static bool AreEqual(object[] itemsA, object[] itemsB) { if (itemsA == null || itemsB == null) return (itemsA == itemsB); if (itemsA.Length != itemsB.Length) return false; var length = itemsA.Length; for (int i = 0; i < length; i++) { var a = itemsA[i]; var b = itemsB[i]; if (!AreEqual(a, b)) return false; } return true; } internal static bool AreEqual(object a, object b) { if (a == null || b == null) return (a == b); if (object.ReferenceEquals(a, b)) return true; return (a.Equals(b)); } internal static bool DictionariesAreEqual(Dictionary a, Dictionary b) { if (a == null || b == null) return (a == b); if (object.ReferenceEquals(a, b)) return true; return a.Count == b.Count && !a.Except(b).Any(); } /// /// Utility method for converting a string to a MemoryStream. /// /// /// public static MemoryStream GenerateMemoryStreamFromString(string s) { MemoryStream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); stream.Position = 0; return stream; } /// /// Utility method for copy the contents of the source stream to the destination stream. /// /// /// public static void CopyStream(Stream source, Stream destination) { CopyStream(source, destination, DefaultBufferSize); } /// /// Utility method for copy the contents of the source stream to the destination stream. /// /// /// /// public static void CopyStream(Stream source, Stream destination, int bufferSize) { if (source == null) throw new ArgumentNullException("source"); if (destination == null) throw new ArgumentNullException("destination"); if (bufferSize <= 0) throw new ArgumentOutOfRangeException("bufferSize"); byte[] array = new byte[bufferSize]; int count; while ((count = source.Read(array, 0, array.Length)) != 0) { destination.Write(array, 0, count); } } #endregion #region Public Methods and Properties /// /// Formats the current date as a GMT timestamp /// /// A GMT formatted string representation /// of the current date and time /// public static string FormattedCurrentTimestampGMT { get { #pragma warning disable CS0618 // Type or member is obsolete DateTime dateTime = AWSSDKUtils.CorrectedUtcNow; #pragma warning restore CS0618 // Type or member is obsolete return dateTime.ToString(GMTDateFormat, CultureInfo.InvariantCulture); } } /// /// Formats the current date as ISO 8601 timestamp /// /// An ISO 8601 formatted string representation /// of the current date and time /// public static string FormattedCurrentTimestampISO8601 { get { return GetFormattedTimestampISO8601(0); } } /// /// Gets the ISO8601 formatted timestamp that is minutesFromNow /// in the future. /// /// The number of minutes from the current instant /// for which the timestamp is needed. /// The ISO8601 formatted future timestamp. public static string GetFormattedTimestampISO8601(int minutesFromNow) { #pragma warning disable CS0618 // Type or member is obsolete return GetFormattedTimestampISO8601(AWSSDKUtils.CorrectedUtcNow.AddMinutes(minutesFromNow)); #pragma warning restore CS0618 // Type or member is obsolete } internal static string GetFormattedTimestampISO8601(IClientConfig config) { return GetFormattedTimestampISO8601(config.CorrectedUtcNow); } private static string GetFormattedTimestampISO8601(DateTime dateTime) { DateTime formatted = new DateTime( dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, DateTimeKind.Local ); return formatted.ToString( AWSSDKUtils.ISO8601DateFormat, CultureInfo.InvariantCulture ); } /// /// Formats the current date as ISO 8601 timestamp /// /// An ISO 8601 formatted string representation /// of the current date and time /// public static string FormattedCurrentTimestampRFC822 { get { return GetFormattedTimestampRFC822(0); } } /// /// Gets the RFC822 formatted timestamp that is minutesFromNow /// in the future. /// /// The number of minutes from the current instant /// for which the timestamp is needed. /// The ISO8601 formatted future timestamp. public static string GetFormattedTimestampRFC822(int minutesFromNow) { #pragma warning disable CS0612 // Type or member is obsolete DateTime dateTime = AWSSDKUtils.CorrectedUtcNow.AddMinutes(minutesFromNow); #pragma warning restore CS0612 // Type or member is obsolete return dateTime.ToString(AWSSDKUtils.RFC822DateFormat, CultureInfo.InvariantCulture); } /// /// Determines whether the given string is an absolute path to a root. /// /// The hypothetical absolute path. /// True if is an absolute path. public static bool IsAbsolutePath(string path) { return IsWindows() ? !IsPartiallyQualifiedForWindows(path) : Path.IsPathRooted(path); } private static bool IsWindows() { #if NETSTANDARD return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endif return true; } #region The code in this region has been minimally adapted from Microsoft's PathInternal.Windows.cs class as of 11/19/2019. The logic remains the same. /// /// Returns true if the path specified is relative to the current drive or working directory. /// Returns false if the path is fixed to a specific drive or UNC path. This method does no /// validation of the path (URIs will be returned as relative as a result). /// /// /// Handles paths that use the alternate directory separator. It is a frequent mistake to /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. /// "C:a" is drive relative- meaning that it will be resolved against the current directory /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory /// will not be used to modify the path). /// private static bool IsPartiallyQualifiedForWindows(string path) { if (path.Length < 2) { // It isn't fixed, it must be relative. There is no way to specify a fixed // path with one character (or less). return true; } if (IsWindowsDirectorySeparator(path[0])) { // There is no valid way to specify a relative path with two initial slashes or // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ return !(path[1] == '?' || IsWindowsDirectorySeparator(path[1])); } // The only way to specify a fixed path that doesn't begin with two slashes // is the drive, colon, slash format- i.e. C:\ return !((path.Length >= 3) && (path[1] == WindowsVolumeSeparatorChar) && IsWindowsDirectorySeparator(path[2]) // To match old behavior we'll check the drive character for validity as the path is technically // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. && IsValidWindowsDriveChar(path[0])); } /// /// True if the given character is a directory separator. /// private static bool IsWindowsDirectorySeparator(char c) { return c == WindowsDirectorySeparatorChar || c == WindowsAltDirectorySeparatorChar; } /// /// Returns true if the given character is a valid drive letter /// private static bool IsValidWindowsDriveChar(char value) { return (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'); } #endregion The code in this region has been minimally adapted from Microsoft's PathInternal.Windows.cs class as of 11/19/2019. The logic remains the same. /// /// URL encodes a string per RFC3986. If the path property is specified, /// the accepted path characters {/+:} are not encoded. /// /// The string to encode /// Whether the string is a URL path or not /// The encoded string public static string UrlEncode(string data, bool path) { return UrlEncode(3986, data, path); } /// /// URL encodes a string per the specified RFC. If the path property is specified, /// the accepted path characters {/+:} are not encoded. /// /// RFC number determing safe characters /// The string to encode /// Whether the string is a URL path or not /// The encoded string /// /// Currently recognised RFC versions are 1738 (Dec '94) and 3986 (Jan '05). /// If the specified RFC is not recognised, 3986 is used by default. /// public static string UrlEncode(int rfcNumber, string data, bool path) { StringBuilder encoded = new StringBuilder(data.Length * 2); string validUrlCharacters; if (!RFCEncodingSchemes.TryGetValue(rfcNumber, out validUrlCharacters)) validUrlCharacters = ValidUrlCharacters; string unreservedChars = String.Concat(validUrlCharacters, (path ? ValidPathCharacters : "")); foreach (char symbol in System.Text.Encoding.UTF8.GetBytes(data)) { if (unreservedChars.IndexOf(symbol) != -1) { encoded.Append(symbol); } else { encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol)); } } return encoded.ToString(); } internal static string UrlEncodeSlash(string data) { if (string.IsNullOrEmpty(data)) { return data; } return data.Replace("/", EncodedSlash); } /// /// Percent encodes the X-Amzn-Trace-Id header value skipping any characters within the /// ValidTraceIdHeaderValueCharacters character set. /// /// The X-Amzn-Trace-Id header value to encode. /// An encoded X-Amzn-Trace-Id header value. internal static string EncodeTraceIdHeaderValue(string value) { var encoded = new StringBuilder(value.Length * 2); foreach (char symbol in System.Text.Encoding.UTF8.GetBytes(value)) { if (ValidTraceIdHeaderValueCharacters.IndexOf(symbol) != -1) { encoded.Append(symbol); } else { encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol)); } } return encoded.ToString(); } /// /// URL encodes a string per the specified RFC with the exception of preserving the encoding of previously encoded slashes. /// If the path property is specified, the accepted path characters {/+:} are not encoded. /// /// The string to encode /// Whether the string is a URL path or not /// The encoded string with any previously encoded %2F preserved [Obsolete("This method is not supported in AWSSDK 3.5")] public static string ProtectEncodedSlashUrlEncode(string data, bool path) { if (string.IsNullOrEmpty(data)) { return data; } var index = 0; var sb = new StringBuilder(); var findIndex = data.IndexOf(EncodedSlash, index, StringComparison.OrdinalIgnoreCase); while (findIndex != -1) { sb.Append(UrlEncode(data.Substring(index, findIndex - index), path)); sb.Append(EncodedSlash); index = findIndex + EncodedSlash.Length; findIndex = data.IndexOf(EncodedSlash, index, StringComparison.OrdinalIgnoreCase); } //If encoded slash was not found return the original data if(index == 0) { return UrlEncode(data, path); } if(data.Length > index) { sb.Append(UrlEncode(data.Substring(index), path)); } return sb.ToString(); } /// /// Generates an MD5 Digest for the stream specified /// /// The Stream for which the MD5 Digest needs /// to be computed. /// A string representation of the hash with base64 encoding /// public static string GenerateMD5ChecksumForStream(Stream input) { string hash = null; if (!input.CanSeek) throw new InvalidOperationException("Input stream must be seekable"); // Use an MD5 instance to compute the hash for the stream byte[] hashed = CryptoUtilFactory.CryptoInstance.ComputeMD5Hash(input); // Convert the hash to a Base64 Encoded string and return it hash = Convert.ToBase64String(hashed); // Now that the hash has been generated, reset the stream to its origin so that the stream's data can be processed input.Position = 0; return hash; } /// /// Generates an MD5 Digest for the string-based content /// /// The content for which the MD5 Digest needs /// to be computed. /// /// Whether the returned checksum should be /// base64 encoded. /// /// A string representation of the hash with or w/o base64 encoding /// public static string GenerateChecksumForContent(string content, bool fBase64Encode) { // Convert the input string to a byte array and compute the hash. return GenerateChecksumForBytes(Encoding.UTF8.GetBytes(content), fBase64Encode); } /// /// Generates an MD5 Digest for the given byte array /// /// The content for which the MD5 Digest needs /// to be computed. /// /// Whether the returned checksum should be /// base64 encoded. /// /// A string representation of the hash with or w/o base64 encoding /// public static string GenerateChecksumForBytes(byte[] content, bool fBase64Encode) { var hashed = content != null ? CryptoUtilFactory.CryptoInstance.ComputeMD5Hash(content) : CryptoUtilFactory.CryptoInstance.ComputeMD5Hash(ArrayEx.Empty()); if (fBase64Encode) { // Convert the hash to a Base64 Encoded string and return it return Convert.ToBase64String(hashed); } else { return BitConverter.ToString(hashed).Replace("-", String.Empty); } } public static void Sleep(TimeSpan ts) { Sleep((int)ts.TotalMilliseconds); } /// /// Convert bytes to a hex string /// /// Bytes to convert. /// Hexadecimal string representing the byte array. public static string BytesToHexString(byte[] value) { string hex = BitConverter.ToString(value); hex = hex.Replace("-", string.Empty); return hex; } /// /// Convert a hex string to bytes /// /// Hexadecimal string /// Byte array corresponding to the hex string. public static byte[] HexStringToBytes(string hex) { if (string.IsNullOrEmpty(hex) || hex.Length % 2 == 1) throw new ArgumentOutOfRangeException("hex"); int count = 0; byte[] buffer = new byte[hex.Length / 2]; for (int i = 0; i < hex.Length; i += 2) { string sub = hex.Substring(i, 2); byte b = Convert.ToByte(sub, 16); buffer[count] = b; count++; } return buffer; } /// /// Returns DateTime.UtcNow + ManualClockCorrection when /// is set. /// This value should be used instead of DateTime.UtcNow to factor in manual clock correction /// [Obsolete("This property does not account for endpoint specific clock skew. Use CorrectClockSkew.GetCorrectedUtcNowForEndpoint() instead.")] public static DateTime CorrectedUtcNow { get { var now = AWSConfigs.utcNowSource(); if (AWSConfigs.ManualClockCorrection.HasValue) now += AWSConfigs.ManualClockCorrection.Value; return now; } } /// /// Returns true if the string has any bidirectional control characters. /// /// /// public static bool HasBidiControlCharacters(string input) { if (string.IsNullOrEmpty(input)) return false; foreach (var c in input) { if (IsBidiControlChar(c)) return true; } return false; } private static bool IsBidiControlChar(char c) { // check general range if (c < '\u200E' || c > '\u202E') return false; // check specific characters return ( c == '\u200E' || // LRM c == '\u200F' || // RLM c == '\u202A' || // LRE c == '\u202B' || // RLE c == '\u202C' || // PDF c == '\u202D' || // LRO c == '\u202E' // RLO ); } public static string DownloadStringContent(Uri uri) { return DownloadStringContent(uri, TimeSpan.Zero, null); } public static string DownloadStringContent(Uri uri, TimeSpan timeout) { return DownloadStringContent(uri, timeout, null); } public static string DownloadStringContent(Uri uri, IWebProxy proxy) { return DownloadStringContent(uri, TimeSpan.Zero, proxy); } public static string DownloadStringContent(Uri uri, TimeSpan timeout, IWebProxy proxy) { #if NETSTANDARD using (var client = CreateClient(uri, timeout, proxy, null)) { return AsyncHelpers.RunSync(() => { return client.GetStringAsync(uri); }); } #else var request = CreateClient(uri, timeout, proxy, null); using (var response = request.GetResponse() as HttpWebResponse) using (var reader = new StreamReader(response.GetResponseStream())) { return reader.ReadToEnd(); } #endif } /// /// Executes an HTTP request and returns the response as a string. This method /// throws WebException and HttpRequestException. In the event HttpRequestException /// is thrown the StatusCode is sent as user defined data on the exception under /// the key "StatusCode". /// /// The URI to make the request to /// The request type: GET, PUT, POST /// null or the content to send with the request /// Timeout for the request /// Proxy for the request /// null or any headers to send with the request /// The response as a string. public static string ExecuteHttpRequest(Uri uri, string requestType, string content, TimeSpan timeout, IWebProxy proxy, IDictionary headers) { #if NETSTANDARD using (var client = CreateClient(uri, timeout, proxy, headers)) { var response = AsyncHelpers.RunSync(() => { var requestMessage = new HttpRequestMessage(new HttpMethod(requestType), uri); if(!string.IsNullOrEmpty(content)) { requestMessage.Content = new StringContent(content); } return client.SendAsync(requestMessage); }); try { response.EnsureSuccessStatusCode(); } catch(HttpRequestException e) { var httpRequestException = new HttpRequestException(e.Message, e); httpRequestException.Data.Add(nameof(response.StatusCode), response.StatusCode); response.Dispose(); throw httpRequestException; } try { return AsyncHelpers.RunSync(() => { return response.Content.ReadAsStringAsync(); }); } finally { response.Dispose(); } } #else var request = CreateClient(uri, timeout, proxy, headers); request.Method = requestType; request.ContentLength = 0; if (!string.IsNullOrEmpty(content)) { var contentBytes = Encoding.UTF8.GetBytes(content); request.ContentLength = contentBytes.Length; using (var requestStream = request.GetRequestStream()) { requestStream.Write(contentBytes, 0, contentBytes.Length); } } using (var response = request.GetResponse() as HttpWebResponse) { using (var reader = new StreamReader(response.GetResponseStream())) { return reader.ReadToEnd(); } } #endif } #if NETSTANDARD private static HttpClient CreateClient(Uri uri, TimeSpan timeout, IWebProxy proxy, IDictionary headers) { var client = new HttpClient(new System.Net.Http.HttpClientHandler() { Proxy = proxy }); if (timeout > TimeSpan.Zero) { client.Timeout = timeout; } //DefaultRequestHeaders should not be used if we reuse the HttpClient. It is currently created for each request. client.DefaultRequestHeaders.TryAddWithoutValidation(UserAgentHeader, _userAgent); if(headers != null) { foreach(var nameValue in headers) { client.DefaultRequestHeaders.TryAddWithoutValidation(nameValue.Key, nameValue.Value); } } return client; } #else private static HttpWebRequest CreateClient(Uri uri, TimeSpan timeout, IWebProxy proxy, IDictionary headers) { HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest; request.Proxy = proxy ?? WebRequest.DefaultWebProxy; if (timeout > TimeSpan.Zero) { request.Timeout = (int)timeout.TotalMilliseconds; } request.UserAgent = _userAgent; if (headers != null) { foreach (var header in headers) { request.Headers.Add(header.Key, header.Value); } } return request; } #endif public static Stream OpenStream(Uri uri) { return OpenStream(uri, null); } public static Stream OpenStream(Uri uri, IWebProxy proxy) { #if NETSTANDARD using (var client = new System.Net.Http.HttpClient(new System.Net.Http.HttpClientHandler() { Proxy = proxy })) { var task = client.GetStreamAsync(uri); return task.Result; } #else HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest; request.Proxy = proxy ?? WebRequest.DefaultWebProxy; var asynResult = request.BeginGetResponse(null, null); HttpWebResponse response = request.EndGetResponse(asynResult) as HttpWebResponse; return response.GetResponseStream(); #endif } /// /// Utility method that accepts a string and replaces white spaces with a space. /// /// /// public static string CompressSpaces(string data) { if (data == null) { return null; } if (data.Length == 0) { return string.Empty; } var stringBuilder = new StringBuilder(); var isWhiteSpace = false; foreach (var character in data) { if (!isWhiteSpace | !(isWhiteSpace = char.IsWhiteSpace(character))) { stringBuilder.Append(isWhiteSpace ? ' ' : character); } } return stringBuilder.ToString(); } /// /// Runs a process with the below input parameters. /// /// /// [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] public static ProcessExecutionResult RunProcess(ProcessStartInfo processStartInfo) { using (var process = new Process { StartInfo = processStartInfo }) { var logger = Logger.GetLogger(typeof(AWSSDKUtils)); logger.InfoFormat("Starting a process with the following ProcessInfo: UseShellExecute - {0} RedirectStandardError - {1}, RedirectStandardOutput - {2}, CreateNoWindow - {3}", processStartInfo.UseShellExecute, processStartInfo.RedirectStandardError, processStartInfo.RedirectStandardOutput, processStartInfo.CreateNoWindow); process.Start(); logger.InfoFormat("Process started"); string standardOutput = null; var thread = new Thread(() => standardOutput = process.StandardOutput.ReadToEnd()); thread.Start(); var standardError = process.StandardError.ReadToEnd(); thread.Join(); process.WaitForExit(); return new ProcessExecutionResult { ExitCode = process.ExitCode, StandardError = standardError, StandardOutput = standardOutput }; } } #if AWS_ASYNC_API [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] public static async Task RunProcessAsync(ProcessStartInfo processStartInfo) { var logger = Logger.GetLogger(typeof(AWSSDKUtils)); using (var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true }) { var tcs = new TaskCompletionSource(); process.Exited += (s, ea) => tcs.SetResult(null); logger.InfoFormat("Starting a process with the following ProcessInfo: UseShellExecute - {0} RedirectStandardError - {1}, RedirectStandardOutput - {2}, CreateNoWindow - {3}", processStartInfo.UseShellExecute, processStartInfo.RedirectStandardError, processStartInfo.RedirectStandardOutput, processStartInfo.CreateNoWindow); process.Start(); logger.InfoFormat("Process started"); var standardErrorTask = process.StandardError.ReadToEndAsync(); var standardOutputTask = process.StandardOutput.ReadToEndAsync(); await Task.WhenAll(tcs.Task, standardErrorTask, standardOutputTask).ConfigureAwait(false); return new ProcessExecutionResult { ExitCode = process.ExitCode, StandardError = standardErrorTask.Result, StandardOutput = standardOutputTask.Result }; } } #endif /// /// This method allows to check whether a property of an object returned by a service call /// is set. This method is needed to discriminate whether a field is not set (not present in /// the service response) or if it is set to the default value for its type. Using this /// method is not required for nullable properties (non-ValueType and Nullable) because /// they will be simply set to null when not included in the service response. /// This method can also be used on objects used as part of service requests. /// This method doesn't support objects that are part of the S3 service model. /// /// An object that is used in an AWS service request or is /// returned as part of an AWS service response. /// The name of the property of awsServiceObject to check. /// True if the property is set, otherwise false. public static bool IsPropertySet(object awsServiceObject, string propertyName) { var type = awsServiceObject.GetType(); var nameSpace = type.Namespace; if (!nameSpace.StartsWith("Amazon.", StringComparison.Ordinal) || !nameSpace.EndsWith(".Model", StringComparison.Ordinal)) { throw new ArgumentException("IsPropertySet can be used only on Amazon Model classes"); } else if (nameSpace == "Amazon.S3.Model") { throw new ArgumentException("IsPropertySet doesn't support S3"); } var key = new IsSetMethodsCacheKey(type, propertyName); //We cache the result of GetIsPropertySetMethodInfo even if it is null var method = IsSetMethodsCache.GetOrAdd(key, (k) => k.Type.GetMethod("IsSet" + k.PropertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], new ParameterModifier[0])); if (method == null) { throw new ArgumentException("Could not find an IsSet method for property " + key); } object result = method.Invoke(awsServiceObject, new object[0]); if (!(result is bool)) { throw new ArgumentException("The IsSet method for property " + key + " didn't return a bool"); } return (bool)result; } #endregion #region Private Methods, Static Fields and Classes private static LruCache IsSetMethodsCache = new LruCache(MaxIsSetMethodsCacheSize); private class IsSetMethodsCacheKey { public readonly Type Type; public readonly string PropertyName; public IsSetMethodsCacheKey(Type type, string propertyName) { Type = type; PropertyName = propertyName; } public override bool Equals(object other) { var otherKey = other as IsSetMethodsCacheKey; if (otherKey == null) { return false; } return Type == otherKey.Type && PropertyName == otherKey.PropertyName; } public override int GetHashCode() { return Type.GetHashCode() ^ PropertyName.GetHashCode(); } public override string ToString() { return Type.FullName + "." + PropertyName; } } #endregion } public class JitteredDelay { private TimeSpan _maxDelay; private TimeSpan _variance; private TimeSpan _baseIncrement; private Random _rand = null; private int _count = 0; public JitteredDelay(TimeSpan baseIncrement, TimeSpan variance) : this(baseIncrement, variance, new TimeSpan(0, 0, 30)) { } public JitteredDelay(TimeSpan baseIncrement, TimeSpan variance, TimeSpan maxDelay) { _baseIncrement = baseIncrement; _variance = variance; _maxDelay = maxDelay; _rand = new System.Random(); } public TimeSpan GetRetryDelay(int attemptCount) { long ticks = (_baseIncrement.Ticks * (long)Math.Pow(2, attemptCount) + (long)(_rand.NextDouble() * _variance.Ticks)); return new TimeSpan(ticks); } public TimeSpan Next() { long nextTick = GetRetryDelay(_count + 1).Ticks; if (nextTick < _maxDelay.Ticks) { _count++; } else { nextTick = _maxDelay.Ticks; } return new TimeSpan(nextTick); } public void Reset() { _count = 0; } } }