diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 93b803f9c..2283c35f4 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2023.1.1", + "version": "2023.1.2", "commands": [ "jb" ] diff --git a/Directory.Build.props b/Directory.Build.props index 7456286d1..656c5aa85 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -53,7 +53,7 @@ - + @@ -78,7 +78,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers diff --git a/Minio.Examples/Cases/CopyObject.cs b/Minio.Examples/Cases/CopyObject.cs index bab796872..7720b1e94 100644 --- a/Minio.Examples/Cases/CopyObject.cs +++ b/Minio.Examples/Cases/CopyObject.cs @@ -33,9 +33,10 @@ internal static class CopyObject { Console.WriteLine("Running example for API: CopyObjectAsync"); var metaData = new Dictionary - { - { "Test-Metadata", "Test Test" } - }; + (StringComparer.Ordinal) + { + { "Test-Metadata", "Test Test" } + }; // Optionally pass copy conditions var cpSrcArgs = new CopySourceObjectArgs() .WithBucket(fromBucketName) diff --git a/Minio.Examples/Cases/CopyObjectMetadata.cs b/Minio.Examples/Cases/CopyObjectMetadata.cs index 81a9449ff..dba4524d5 100644 --- a/Minio.Examples/Cases/CopyObjectMetadata.cs +++ b/Minio.Examples/Cases/CopyObjectMetadata.cs @@ -37,10 +37,11 @@ internal static class CopyObjectMetadata // set custom metadata var metadata = new Dictionary - { - { "Content-Type", "application/css" }, - { "Mynewkey", "my-new-value" } - }; + (StringComparer.Ordinal) + { + { "Content-Type", "application/css" }, + { "Mynewkey", "my-new-value" } + }; var copySourceObjectArgs = new CopySourceObjectArgs() .WithBucket(fromBucketName) diff --git a/Minio.Examples/Cases/CopyObjectReplaceTags.cs b/Minio.Examples/Cases/CopyObjectReplaceTags.cs index c91957af7..c2f97e7d8 100644 --- a/Minio.Examples/Cases/CopyObjectReplaceTags.cs +++ b/Minio.Examples/Cases/CopyObjectReplaceTags.cs @@ -31,9 +31,10 @@ internal static class CopyObjectReplaceTags { Console.WriteLine("Running example for API: CopyObjectAsync with Tags"); var tags = new Dictionary - { - { "Test-TagKey", "Test-TagValue" } - }; + (StringComparer.Ordinal) + { + { "Test-TagKey", "Test-TagValue" } + }; var tagObj = Tagging.GetObjectTags(tags); var cpSrcArgs = new CopySourceObjectArgs() .WithBucket(fromBucketName) diff --git a/Minio.Examples/Cases/PresignedGetObject.cs b/Minio.Examples/Cases/PresignedGetObject.cs index 888c2a95a..62c0c8fec 100644 --- a/Minio.Examples/Cases/PresignedGetObject.cs +++ b/Minio.Examples/Cases/PresignedGetObject.cs @@ -24,7 +24,8 @@ public static class PresignedGetObject { if (client is null) throw new ArgumentNullException(nameof(client)); - var reqParams = new Dictionary { { "response-content-type", "application/json" } }; + var reqParams = new Dictionary(StringComparer.Ordinal) + { { "response-content-type", "application/json" } }; var args = new PresignedGetObjectArgs() .WithBucket(bucketName) .WithObject(objectName) diff --git a/Minio.Examples/Cases/PresignedPostPolicy.cs b/Minio.Examples/Cases/PresignedPostPolicy.cs index 3c6afb2d0..e2dab27e4 100644 --- a/Minio.Examples/Cases/PresignedPostPolicy.cs +++ b/Minio.Examples/Cases/PresignedPostPolicy.cs @@ -44,7 +44,7 @@ public static class PresignedPostPolicy var tuple = await client.PresignedPostPolicyAsync(form).ConfigureAwait(false); var curlCommand = "curl -k --insecure -X POST"; - foreach (var pair in tuple.Item2) curlCommand = curlCommand + $" -F {pair.Key}={pair.Value}"; + foreach (var pair in tuple.Item2) curlCommand += $" -F {pair.Key}={pair.Value}"; curlCommand = curlCommand + " -F file=@/etc/issue " + tuple.Item1 + bucketName + "/"; } } \ No newline at end of file diff --git a/Minio.Examples/Cases/PutObject.cs b/Minio.Examples/Cases/PutObject.cs index 10a4b4754..b8e97b536 100644 --- a/Minio.Examples/Cases/PutObject.cs +++ b/Minio.Examples/Cases/PutObject.cs @@ -38,9 +38,10 @@ internal static class PutObject var fileInfo = new FileInfo(fileName); var metaData = new Dictionary - { - { "Test-Metadata", "Test Test" } - }; + (StringComparer.Ordinal) + { + { "Test-Metadata", "Test Test" } + }; var args = new PutObjectArgs() .WithBucket(bucketName) .WithObject(objectName) diff --git a/Minio.Examples/Cases/PutObjectWithTags.cs b/Minio.Examples/Cases/PutObjectWithTags.cs index 4e97a9d80..7d8bc4934 100644 --- a/Minio.Examples/Cases/PutObjectWithTags.cs +++ b/Minio.Examples/Cases/PutObjectWithTags.cs @@ -34,9 +34,10 @@ internal static class PutObjectWithTags { Console.WriteLine("Running example for API: PutObjectAsync with Tags"); var tags = new Dictionary - { - { "Test-TagKey", "Test-TagValue" } - }; + (StringComparer.Ordinal) + { + { "Test-TagKey", "Test-TagValue" } + }; var args = new PutObjectArgs() .WithBucket(bucketName) .WithObject(objectName) diff --git a/Minio.Examples/Cases/RetryPolicyObject.cs b/Minio.Examples/Cases/RetryPolicyObject.cs index 34c627e93..545e251a3 100644 --- a/Minio.Examples/Cases/RetryPolicyObject.cs +++ b/Minio.Examples/Cases/RetryPolicyObject.cs @@ -43,7 +43,8 @@ public static PolicyBuilder CreatePolicyBuilder() { return Policy .Handle() - .Or(ex => ex.Message.StartsWith("Unsuccessful response from server")); + .Or(ex => + ex.Message.StartsWith("Unsuccessful response from server", StringComparison.InvariantCulture)); } public static AsyncPolicy GetDefaultRetryPolicy() @@ -62,7 +63,7 @@ public static AsyncPolicy GetDefaultRetryPolicy() i => CalcBackoff(i, retryInterval, maxRetryInterval)); } - public static RetryPolicyHandlingDelegate AsRetryDelegate(this AsyncPolicy policy) + public static RetryPolicyHandler AsRetryDelegate(this AsyncPolicy policy) { return policy is null ? null diff --git a/Minio.Examples/Program.cs b/Minio.Examples/Program.cs index 31e9960ef..6f394d5c0 100644 --- a/Minio.Examples/Program.cs +++ b/Minio.Examples/Program.cs @@ -182,7 +182,8 @@ await CopyObjectMetadata.Run(minioClient, bucketName, objectName, destBucketName var sses3 = new SSES3(); // Uncomment to specify SSE-KMS encryption option - var sseKms = new SSEKMS("kms-key", new Dictionary { { "kms-context", "somevalue" } }); + var sseKms = new SSEKMS("kms-key", + new Dictionary(StringComparer.Ordinal) { { "kms-context", "somevalue" } }); // Upload encrypted object var putFileName1 = CreateFile(1 * UNIT_MB); diff --git a/Minio.Functional.Tests/FunctionalTest.cs b/Minio.Functional.Tests/FunctionalTest.cs index cc61e5cea..618547b97 100644 --- a/Minio.Functional.Tests/FunctionalTest.cs +++ b/Minio.Functional.Tests/FunctionalTest.cs @@ -195,14 +195,15 @@ public static class FunctionalTest private static string Bash(string cmd) { var Replacements = new Dictionary - { - { "$", "\\$" }, { "(", "\\(" }, - { ")", "\\)" }, { "{", "\\{" }, - { "}", "\\}" }, { "[", "\\[" }, - { "]", "\\]" }, { "@", "\\@" }, - { "%", "\\%" }, { "&", "\\&" }, - { "#", "\\#" }, { "+", "\\+" } - }; + (StringComparer.Ordinal) + { + { "$", "\\$" }, { "(", "\\(" }, + { ")", "\\)" }, { "{", "\\{" }, + { "}", "\\}" }, { "[", "\\[" }, + { "]", "\\]" }, { "@", "\\@" }, + { "%", "\\%" }, { "&", "\\&" }, + { "#", "\\#" }, { "+", "\\+" } + }; foreach (var toReplace in Replacements.Keys) cmd = cmd.Replace(toReplace, Replacements[toReplace]); var cmdNoReturn = cmd + " >/dev/null 2>&1"; @@ -271,10 +272,10 @@ public static string GetRandomName(int length = 5) return "minio-dotnet-example-" + result; } - internal static void GenerateRandomFile(string fileName) + internal static void GenerateRandom500MB_File(string fileName) { using var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); - var fileSize = 3L * 1024 * 1024 * 1024; + var fileSize = 500L * 1024 * 1024; var segments = fileSize / 10000; var last_seg = fileSize % 10000; using var br = new BinaryWriter(fs); @@ -302,7 +303,7 @@ public static string GetFilePath(string fileName) return $"{path}/{fileName}"; } - internal static async Task RunCoreTests(MinioClient minioClient) + internal static Task RunCoreTests(MinioClient minioClient) { ConcurrentBag coreTestsTasks = new() { @@ -332,8 +333,7 @@ internal static async Task RunCoreTests(MinioClient minioClient) GetBucketPolicy_Test1(minioClient) }; - await Utils.RunInParallel(coreTestsTasks, async (task, _) => { await task.ConfigureAwait(false); }) - .ConfigureAwait(false); + return Utils.RunInParallel(coreTestsTasks, async (task, _) => { await task.ConfigureAwait(false); }); } internal static async Task BucketExists_Test(MinioClient minio) @@ -347,9 +347,10 @@ internal static async Task BucketExists_Test(MinioClient minio) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; try { @@ -387,9 +388,10 @@ internal static async Task RemoveBucket_Test1(MinioClient minio) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; var found = false; try @@ -422,9 +424,10 @@ internal static async Task RemoveBucket_Test2(MinioClient minio) var bucketName = GetRandomName(20); var objectName = GetRandomName(20); var forceFlagHeader = new Dictionary - { - { "x-minio-force-delete", "true" } - }; + (StringComparer.Ordinal) + { + { "x-minio-force-delete", "true" } + }; var beArgs = new BucketExistsArgs() .WithBucket(bucketName); @@ -444,10 +447,11 @@ internal static async Task RemoveBucket_Test2(MinioClient minio) await Task.Delay(1000).ConfigureAwait(false); var args = new Dictionary - { - { "bucketName", bucketName }, - { "x-minio-force-delete", "true" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "x-minio-force-delete", "true" } + }; var found = false; @@ -482,10 +486,11 @@ internal static async Task ListBuckets_Test(MinioClient minio) var noOfBuckets = 5; var args = new Dictionary - { - { "bucketNameSuffix", bucketNameSuffix }, - { "noOfBuckets", noOfBuckets.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketNameSuffix", bucketNameSuffix }, + { "noOfBuckets", noOfBuckets.ToString() } + }; try { @@ -695,13 +700,14 @@ internal static async Task PutGetStatEncryptedObject_Test1(MinioClient minio) var contentType = "application/octet-stream"; var tempFileName = "tempFile-" + GetRandomName(); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Putobject with SSE-C encryption. @@ -786,13 +792,14 @@ internal static async Task PutGetStatEncryptedObject_Test2(MinioClient minio) var contentType = "application/octet-stream"; var tempFileName = "tempFile-" + GetRandomName(); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "6MB" }, - { "size", "6MB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "6MB" }, + { "size", "6MB" } + }; try { // Test multipart Put with SSE-C encryption @@ -878,13 +885,14 @@ internal static async Task PutGetStatEncryptedObject_Test3(MinioClient minio) var contentType = "application/octet-stream"; var tempFileName = "tempFile-" + GetRandomName(); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "6MB" }, - { "size", "6MB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "6MB" }, + { "size", "6MB" } + }; try { // Test multipart Put/Get/Stat with SSE-S3 encryption @@ -1049,13 +1057,14 @@ internal static async Task StatObject_Test1(MinioClient minio) var contentType = "gzip"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { @@ -1084,11 +1093,12 @@ internal static async Task FPutObject_Test1(MinioClient minio) var objectName = GetRandomObjectName(10); var fileName = CreateFile(6 * MB, dataFile6MB); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileName", fileName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileName", fileName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -1122,11 +1132,12 @@ internal static async Task FPutObject_Test2(MinioClient minio) var objectName = GetRandomObjectName(10); var fileName = CreateFile(10 * KB, dataFile10KB); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileName", fileName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileName", fileName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -1162,10 +1173,11 @@ internal static async Task RemoveObject_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName } + }; try { using (var filestream = rsg.GenerateStreamFromSeed(1 * KB)) @@ -1204,10 +1216,11 @@ internal static async Task RemoveObjects_Test2(MinioClient minio) var objectName = GetRandomObjectName(6); var objectsList = new List(); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectNames", "[" + objectName + "0..." + objectName + "50]" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectNames", "[" + objectName + "0..." + objectName + "50]" } + }; try { var count = 50; @@ -1245,10 +1258,11 @@ internal static async Task RemoveObjects_Test3(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(6); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectNames", "[" + objectName + "0..." + objectName + "50]" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectNames", "[" + objectName + "0..." + objectName + "50]" } + }; try { var count = 50; @@ -1344,11 +1358,12 @@ internal static async Task PresignedPostPolicy_Test1(MinioClient minio) formPolicy.SetUserMetadata(metadataKey, metadataValue); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "expiresOn", expiresOn.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresOn", expiresOn.ToString() } + }; // File to be uploaded var size = 10 * KB; @@ -1413,10 +1428,11 @@ internal static async Task RemoveIncompleteUpload_Test(MinioClient minio) var objectName = GetRandomObjectName(10); var contentType = "csv"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -1479,11 +1495,12 @@ internal static async Task SelectObjectContent_Test(MinioClient minio) var objectName = GetRandomObjectName(10); var outFileName = "outFileName-SelectObjectContent_Test"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileName", outFileName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileName", outFileName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -1533,8 +1550,9 @@ internal static async Task SelectObjectContent_Test(MinioClient minio) var resp = await minio.SelectObjectContentAsync(selArgs).ConfigureAwait(false); using var streamReader = new StreamReader(resp.Payload); var output = await streamReader.ReadToEndAsync().ConfigureAwait(false); - var csvStringNoWS = Regex.Replace(csvString.ToString(), @"\s+", ""); - var outputNoWS = Regex.Replace(output, @"\s+", ""); + var csvStringNoWS = + Regex.Replace(csvString.ToString(), @"\s+", "", RegexOptions.None, TimeSpan.FromHours(1)); + var outputNoWS = Regex.Replace(output, @"\s+", "", RegexOptions.None, TimeSpan.FromHours(1)); #if NETFRAMEWORK using var md5 = MD5.Create(); @@ -1582,9 +1600,10 @@ internal static async Task BucketEncryptionsAsync_Test1(MinioClient minio) var startTime = DateTime.Now; var bucketName = GetRandomName(15); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -1699,10 +1718,11 @@ internal static async Task LegalHoldStatusAsync_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName } + }; try { await Setup_WithLock_Test(minio, bucketName).ConfigureAwait(false); @@ -1807,15 +1827,17 @@ internal static async Task BucketTagsAsync_Test1(MinioClient minio) var startTime = DateTime.Now; var bucketName = GetRandomName(15); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; var tags = new Dictionary - { - { "key1", "value1" }, - { "key2", "value2" }, - { "key3", "value3" } - }; + (StringComparer.Ordinal) + { + { "key1", "value1" }, + { "key2", "value2" }, + { "key3", "value3" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -1933,17 +1955,19 @@ internal static async Task ObjectTagsAsync_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileSize", size.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileSize", size.ToString() } + }; var tags = new Dictionary - { - { "key1", "value1" }, - { "key2", "value2" }, - { "key3", "value3" } - }; + (StringComparer.Ordinal) + { + { "key1", "value1" }, + { "key2", "value2" }, + { "key3", "value3" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -2088,11 +2112,12 @@ internal static async Task ObjectVersioningAsync_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileSize", size.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileSize", size.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -2193,9 +2218,10 @@ internal static async Task ObjectLockConfigurationAsync_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; var setLockNotImplemented = false; var getLockNotImplemented = false; @@ -2360,10 +2386,11 @@ internal static async Task ObjectRetentionAsync_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName } + }; try { @@ -2564,10 +2591,11 @@ internal static async Task GetObjectS3Zip_Test1(MinioClient minio) var objectName = GetRandomObjectName(15) + ".zip"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -2582,9 +2610,10 @@ internal static async Task GetObjectS3Zip_Test1(MinioClient minio) await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); var extractHeader = new Dictionary - { - { "x-minio-extract", "true" } - }; + (StringComparer.Ordinal) + { + { "x-minio-extract", "true" } + }; // GeObject api test var r = new Random(); @@ -2672,12 +2701,13 @@ internal static async Task ListenBucketNotificationsAsync_Test1(MinioClient mini var objectName = GetRandomName(10); var contentType = "application/octet-stream"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "size", "1KB" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -2777,7 +2807,7 @@ internal static async Task ListenBucketNotificationsAsync_Test1(MinioClient mini // Check if endPoint is AWS static bool isAWS(string endPoint) { - var rgx = new Regex("^s3\\.?.*\\.amazonaws\\.com", RegexOptions.IgnoreCase); + var rgx = new Regex("^s3\\.?.*\\.amazonaws\\.com", RegexOptions.IgnoreCase, TimeSpan.FromHours(1)); var matches = rgx.Matches(endPoint); return matches.Count > 0; } @@ -2818,11 +2848,12 @@ internal static async Task ListenBucketNotificationsAsync_Test2(MinioClient mini var bucketName = GetRandomName(15); var contentType = "application/json"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "contentType", contentType }, - { "size", "16B" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "contentType", contentType }, + { "size", "16B" } + }; try { @@ -2920,12 +2951,13 @@ internal static async Task ListenBucketNotificationsAsync_Test3(MinioClient mini var suffix = ".json"; var contentType = "application/json"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "contentType", contentType }, - { "suffix", suffix }, - { "size", "16B" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "contentType", contentType }, + { "suffix", suffix }, + { "size", "16B" } + }; try { @@ -3026,10 +3058,11 @@ internal static async Task MakeBucket_Test1(MinioClient minio) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName }, - { "region", "us-east-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "region", "us-east-1" } + }; try { @@ -3064,10 +3097,11 @@ internal static async Task MakeBucket_Test2(MinioClient minio, bool aws = false) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName }, - { "region", "us-east-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "region", "us-east-1" } + }; var testType = "Test whether make bucket passes when bucketname has a period."; try @@ -3104,10 +3138,11 @@ internal static async Task MakeBucket_Test3(MinioClient minio, bool aws = false) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName }, - { "region", "eu-central-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "region", "eu-central-1" } + }; try { await minio.MakeBucketAsync(mbArgs).ConfigureAwait(false); @@ -3142,10 +3177,11 @@ internal static async Task MakeBucket_Test4(MinioClient minio, bool aws = false) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName }, - { "region", "us-west-2" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "region", "us-west-2" } + }; try { await minio.MakeBucketAsync(mbArgs).ConfigureAwait(false); @@ -3173,10 +3209,11 @@ internal static async Task MakeBucket_Test5(MinioClient minio) var startTime = DateTime.Now; string bucketName = null; var args = new Dictionary - { - { "bucketName", bucketName }, - { "region", "us-east-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "region", "us-east-1" } + }; try { @@ -3208,10 +3245,11 @@ internal static async Task MakeBucketLock_Test1(MinioClient minio) var rbArgs = new RemoveBucketArgs() .WithBucket(bucketName); var args = new Dictionary - { - { "bucketName", bucketName }, - { "region", "us-east-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "region", "us-east-1" } + }; try { @@ -3245,12 +3283,13 @@ internal static async Task PutObject_Test1(MinioClient minio) var contentType = "application/octet-stream"; var size = 1 * MB; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "size", "1MB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "size", "1MB" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -3282,13 +3321,14 @@ internal static async Task PutObject_Test2(MinioClient minio) var objectName = GetRandomObjectName(10); var contentType = "binary/octet-stream"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "size", "6MB" } - }; - try + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "size", "6MB" } + }; + try { await Setup_Test(minio, bucketName).ConfigureAwait(false); await PutObject_Tester(minio, bucketName, objectName, null, contentType, 0, null, @@ -3315,12 +3355,13 @@ internal static async Task PutObject_Test3(MinioClient minio) var objectName = GetRandomObjectName(10); var contentType = "custom-contenttype"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "size", "1MB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "size", "1MB" } + }; try { @@ -3352,18 +3393,20 @@ internal static async Task PutObject_Test4(MinioClient minio) var fileName = CreateFile(1, dataFile1B); var contentType = "custom/contenttype"; var metaData = new Dictionary - { - { "customheader", "minio dotnet" } - }; + (StringComparer.Ordinal) + { + { "customheader", "minio dotnet" } + }; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "1B" }, - { "size", "1B" }, - { "metaData", "customheader:minio-dotnet" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "1B" }, + { "size", "1B" }, + { "metaData", "customheader:minio-dotnet" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -3401,12 +3444,13 @@ internal static async Task PutObject_Test5(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "data", "1B" }, - { "size", "1B" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "data", "1B" }, + { "size", "1B" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -3436,13 +3480,14 @@ internal static async Task PutObject_Test7(MinioClient minio) var objectName = GetRandomObjectName(10); var contentType = "application/octet-stream"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "10KB" }, - { "size", "-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "10KB" }, + { "size", "-1" } + }; try { // Putobject call with unknown stream size. See if PutObjectAsync call succeeds @@ -3489,13 +3534,14 @@ internal static async Task PutObject_Test8(MinioClient minio) var objectName = GetRandomObjectName(10); var contentType = "application/octet-stream"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "data", "0B" }, - { "size", "-1" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "data", "0B" }, + { "size", "-1" } + }; try { // Putobject call where unknown stream sent 0 bytes. @@ -3548,14 +3594,15 @@ internal static async Task CopyObject_Test1(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-CopyObject_Test1"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -3618,14 +3665,15 @@ internal static async Task CopyObject_Test2(MinioClient minio) var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test CopyConditions where matching ETag is not found @@ -3706,14 +3754,15 @@ internal static async Task CopyObject_Test3(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-CopyObject_Test3"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test CopyConditions where matching ETag is found @@ -3783,13 +3832,14 @@ internal static async Task CopyObject_Test4(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-CopyObject_Test4"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test if objectName is defaulted to source objectName @@ -3856,14 +3906,15 @@ internal static async Task CopyObject_Test5(MinioClient minio) var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "6MB" }, - { "size", "6MB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "6MB" }, + { "size", "6MB" } + }; try { // Test if multi-part copy upload for large files works as expected. @@ -3933,14 +3984,15 @@ internal static async Task CopyObject_Test6(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-CopyObject_Test6"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test CopyConditions where matching ETag is found @@ -4012,14 +4064,15 @@ internal static async Task CopyObject_Test7(MinioClient minio) var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test CopyConditions where matching ETag is found @@ -4090,15 +4143,16 @@ internal static async Task CopyObject_Test8(MinioClient minio) var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" }, - { "copyconditions", "x-amz-metadata-directive:REPLACE" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" }, + { "copyconditions", "x-amz-metadata-directive:REPLACE" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4110,7 +4164,8 @@ internal static async Task CopyObject_Test8(MinioClient minio) .WithObject(objectName) .WithStreamData(filestream) .WithObjectSize(filestream.Length) - .WithHeaders(new Dictionary { { "Orig", "orig-val with spaces" } }); + .WithHeaders(new Dictionary(StringComparer.Ordinal) + { { "Orig", "orig-val with spaces" } }); await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); } @@ -4126,11 +4181,12 @@ internal static async Task CopyObject_Test8(MinioClient minio) // set custom metadata var customMetadata = new Dictionary - { - { "Content-Type", "application/css" }, - { "Mynewkey", "test test" }, - { "Orig", "orig-valwithoutspaces" } - }; + (StringComparer.Ordinal) + { + { "Content-Type", "application/css" }, + { "Mynewkey", "test test" }, + { "Orig", "orig-valwithoutspaces" } + }; var copySourceObjectArgs = new CopySourceObjectArgs() .WithBucket(bucketName) .WithObject(objectName) @@ -4178,12 +4234,13 @@ internal static async Task CopyObject_Test9(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-CopyObject_Test9"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4199,9 +4256,10 @@ internal static async Task CopyObject_Test9(MinioClient minio) await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); var putTags = new Dictionary - { - { "key1", "PutObjectTags" } - }; + (StringComparer.Ordinal) + { + { "key1", "PutObjectTags" } + }; var setObjectTagsArgs = new SetObjectTagsArgs() .WithBucket(bucketName) .WithObject(objectName) @@ -4210,9 +4268,10 @@ internal static async Task CopyObject_Test9(MinioClient minio) } var copyTags = new Dictionary - { - { "key1", "CopyObjectTags" } - }; + (StringComparer.Ordinal) + { + { "key1", "CopyObjectTags" } + }; var copySourceObjectArgs = new CopySourceObjectArgs() .WithBucket(bucketName) .WithObject(objectName); @@ -4266,14 +4325,15 @@ internal static async Task EncryptedCopyObject_Test1(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-EncryptedCopyObject_Test1"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test Copy with SSE-C -> SSE-C encryption @@ -4349,14 +4409,15 @@ internal static async Task EncryptedCopyObject_Test2(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-EncryptedCopyObject_Test2"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test Copy of SSE-C encrypted object to unencrypted on destination side @@ -4429,7 +4490,7 @@ internal static async Task EncryptedCopyObject_Test3(MinioClient minio) var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); var outFileName = "outFileName-EncryptedCopyObject_Test3"; - var args = new Dictionary + var args = new Dictionary(StringComparer.Ordinal) { { "bucketName", bucketName }, { "objectName", objectName }, @@ -4504,14 +4565,15 @@ internal static async Task EncryptedCopyObject_Test4(MinioClient minio) var destObjectName = GetRandomName(10); var outFileName = "outFileName-EncryptedCopyObject_Test4"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "destBucketName", destBucketName }, - { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "destBucketName", destBucketName }, + { "destObjectName", destObjectName }, + { "data", "1KB" }, + { "size", "1KB" } + }; try { // Test Copy of SSE-S3 encrypted object to SSE-S3 on destination side @@ -4578,11 +4640,12 @@ internal static async Task GetObject_Test1(MinioClient minio) string contentType = null; var tempFileName = "tempFile-" + GetRandomName(); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4646,11 +4709,12 @@ internal static async Task GetObject_Test2(MinioClient minio) var objectName = GetRandomObjectName(10); var fileName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileName", fileName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileName", fileName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4722,25 +4786,27 @@ internal static async Task GetObject_3_OffsetLength_Tests(MinioClient minio) var tempFileName = "tempFile-" + GetRandomName(); var tempSource = "tempSourceFile-" + GetRandomName(); var offsetLengthTests = new Dictionary> - { - // list is {offset, length} values - { "GetObject_Test3", new List { 14, 20 } }, - { "GetObject_Test4", new List { 30, 0 } }, - { "GetObject_Test5", new List { 0, 25 } } - }; + (StringComparer.Ordinal) + { + // list is {offset, length} values + { "GetObject_Test3", new List { 14, 20 } }, + { "GetObject_Test4", new List { 30, 0 } }, + { "GetObject_Test5", new List { 0, 25 } } + }; foreach (var test in offsetLengthTests) { var testName = test.Key; var offsetToStartFrom = test.Value[0]; var lengthToBeRead = test.Value[1]; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType }, - { "offset", offsetToStartFrom.ToString() }, - { "length", lengthToBeRead.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType }, + { "offset", offsetToStartFrom.ToString() }, + { "length", lengthToBeRead.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4845,17 +4911,17 @@ internal static async Task GetObject_AsyncCallback_Test1(MinioClient minio) var fileName = GetRandomName(10); var destFileName = GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "contentType", contentType } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "contentType", contentType } + }; try { - // Create a large local file - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) GenerateRandomFile(fileName); - else Bash("truncate -s 2G " + fileName); + // Create a local 500MB file + GenerateRandom500MB_File(fileName); // Create the bucket await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4876,8 +4942,9 @@ internal static async Task GetObject_AsyncCallback_Test1(MinioClient minio) var callbackAsync = async (Stream stream, CancellationToken cancellationToken) => { - using var dest = new FileStream(destFileName, FileMode.Create, FileAccess.Write); + var dest = new FileStream(destFileName, FileMode.Create, FileAccess.Write); await stream.CopyToAsync(dest, cancellationToken).ConfigureAwait(false); + await dest.DisposeAsync(); }; var getObjectArgs = new GetObjectArgs() @@ -4920,11 +4987,12 @@ internal static async Task FGetObject_Test1(MinioClient minio) var objectName = GetRandomObjectName(10); var outFileName = "outFileName-FGetObject_Test1"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "fileName", outFileName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "fileName", outFileName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -4970,12 +5038,13 @@ internal static async Task ListObjects_Test1(MinioClient minio) var prefix = "minix"; var objectName = prefix + GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "prefix", prefix }, - { "recursive", "false" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "prefix", prefix }, + { "recursive", "false" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5010,9 +5079,10 @@ internal static async Task ListObjects_Test2(MinioClient minio) var startTime = DateTime.Now; var bucketName = GetRandomName(15); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5043,12 +5113,13 @@ internal static async Task ListObjects_Test3(MinioClient minio) var prefix = "minix"; var objectName = prefix + "/" + GetRandomName(10) + "/suffix"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "prefix", prefix }, - { "recursive", "true" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "prefix", prefix }, + { "recursive", "true" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5084,11 +5155,12 @@ internal static async Task ListObjects_Test4(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "recursive", "false" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "recursive", "false" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5125,11 +5197,12 @@ internal static async Task ListObjects_Test5(MinioClient minio) var objectNamePrefix = GetRandomName(10); var numObjects = 100; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectNamePrefix }, - { "recursive", "false" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectNamePrefix }, + { "recursive", "false" } + }; var objectNames = new List(); try { @@ -5174,12 +5247,13 @@ internal static async Task ListObjects_Test6(MinioClient minio) var objectNamePrefix = GetRandomName(10); var numObjects = 1015; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectNamePrefix }, - { "recursive", "false" } - }; - var objectNamesSet = new HashSet(); + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectNamePrefix }, + { "recursive", "false" } + }; + var objectNamesSet = new HashSet(StringComparer.Ordinal); try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5241,13 +5315,14 @@ internal static async Task ListObjectVersions_Test1(MinioClient minio) var prefix = "minix"; var objectName = prefix + GetRandomName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "prefix", prefix }, - { "recursive", "false" }, - { "versions", "true" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "prefix", prefix }, + { "recursive", "false" }, + { "versions", "true" } + }; var objectVersions = new List>(); try { @@ -5356,11 +5431,12 @@ internal static async Task PresignedGetObject_Test1(MinioClient minio) var downloadFile = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "expiresInt", expiresInt.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5417,11 +5493,12 @@ internal static async Task PresignedGetObject_Test2(MinioClient minio) var objectName = GetRandomObjectName(10); var expiresInt = 0; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "expiresInt", expiresInt.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5483,16 +5560,17 @@ internal static async Task PresignedGetObject_Test3(MinioClient minio) var reqDate = DateTime.UtcNow.AddSeconds(-50); var downloadFile = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "expiresInt", expiresInt.ToString() }, + (StringComparer.Ordinal) { - "reqParams", - "response-content-type:application/json,response-content-disposition:attachment;filename= MyDoc u m e nt.json ;" - }, - { "reqDate", reqDate.ToString() } - }; + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString() }, + { + "reqParams", + "response-content-type:application/json,response-content-disposition:attachment;filename= MyDoc u m e nt.json ;" + }, + { "reqDate", reqDate.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5512,10 +5590,11 @@ internal static async Task PresignedGetObject_Test3(MinioClient minio) .WithObject(objectName); var stats = await minio.StatObjectAsync(statObjectArgs).ConfigureAwait(false); var reqParams = new Dictionary - { - ["response-content-type"] = "application/json", - ["response-content-disposition"] = "attachment;filename= MyDoc u m e nt.json ;" - }; + (StringComparer.Ordinal) + { + ["response-content-type"] = "application/json", + ["response-content-disposition"] = "attachment;filename= MyDoc u m e nt.json ;" + }; var preArgs = new PresignedGetObjectArgs() .WithBucket(bucketName) .WithObject(objectName) @@ -5529,10 +5608,11 @@ internal static async Task PresignedGetObject_Test3(MinioClient minio) throw new ArgumentNullException(nameof(response.Content), "Unable to download via presigned URL"); Assert.IsTrue(response.Content.Headers.GetValues("Content-Type") - .Contains(reqParams["response-content-type"])); + .Contains(reqParams["response-content-type"], StringComparer.Ordinal)); Assert.IsTrue(response.Content.Headers.GetValues("Content-Disposition") - .Contains(reqParams["response-content-disposition"])); - Assert.IsTrue(response.Content.Headers.GetValues("Content-Length").Contains(stats.Size.ToString())); + .Contains(reqParams["response-content-disposition"], StringComparer.Ordinal)); + Assert.IsTrue(response.Content.Headers.GetValues("Content-Length") + .Contains(stats.Size.ToString(), StringComparer.Ordinal)); using (var fs = new FileStream(downloadFile, FileMode.CreateNew)) { @@ -5576,11 +5656,12 @@ internal static async Task PresignedPutObject_Test1(MinioClient minio) var fileName = CreateFile(10 * KB, dataFile10KB); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "expiresInt", expiresInt.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5626,11 +5707,12 @@ internal static async Task PresignedPutObject_Test2(MinioClient minio) var expiresInt = 0; var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectName", objectName }, - { "expiresInt", expiresInt.ToString() } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString() } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5688,10 +5770,11 @@ internal static async Task ListIncompleteUpload_Test1(MinioClient minio) var objectName = GetRandomObjectName(10); var contentType = "gzip"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "recursive", "true" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "recursive", "true" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5752,11 +5835,12 @@ internal static async Task ListIncompleteUpload_Test2(MinioClient minio) var objectName = prefix + GetRandomName(10); var contentType = "gzip"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "prefix", prefix }, - { "recursive", "false" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "prefix", prefix }, + { "recursive", "false" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5813,11 +5897,12 @@ internal static async Task ListIncompleteUpload_Test3(MinioClient minio) var objectName = prefix + "/" + GetRandomName(10) + "/suffix"; var contentType = "gzip"; var args = new Dictionary - { - { "bucketName", bucketName }, - { "prefix", prefix }, - { "recursive", "true" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "prefix", prefix }, + { "recursive", "true" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5881,11 +5966,12 @@ internal static async Task SetBucketPolicy_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName }, - { "objectPrefix", objectName.Substring(5) }, - { "policyType", "readonly" } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectPrefix", objectName.Substring(5) }, + { "policyType", "readonly" } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5937,9 +6023,10 @@ internal static async Task GetBucketPolicy_Test1(MinioClient minio) var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -5994,9 +6081,10 @@ internal static async Task BucketLifecycleAsync_Test1(MinioClient minio) var startTime = DateTime.Now; var bucketName = GetRandomName(15); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); @@ -6120,9 +6208,10 @@ internal static async Task BucketLifecycleAsync_Test2(MinioClient minio) var startTime = DateTime.Now; var bucketName = GetRandomName(15); var args = new Dictionary - { - { "bucketName", bucketName } - }; + (StringComparer.Ordinal) + { + { "bucketName", bucketName } + }; try { await Setup_Test(minio, bucketName).ConfigureAwait(false); diff --git a/Minio.Functional.Tests/Minio.Functional.Tests.csproj b/Minio.Functional.Tests/Minio.Functional.Tests.csproj index 2bae62590..88f08443f 100644 --- a/Minio.Functional.Tests/Minio.Functional.Tests.csproj +++ b/Minio.Functional.Tests/Minio.Functional.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/Minio.Tests/EndpointTest.cs b/Minio.Tests/EndpointTest.cs index 4ca83579f..e3929d786 100644 --- a/Minio.Tests/EndpointTest.cs +++ b/Minio.Tests/EndpointTest.cs @@ -127,13 +127,14 @@ public void TestGetEndpointURL() public void TestIfIPIsValid() { var testIPDict = new Dictionary - { - { "192.168.1", false }, - { "192.168.1.1", true }, - { "192.168.1.1.1", false }, - { "-192.168.1.1", false }, - { "260.192.1.1", false } - }; + (StringComparer.Ordinal) + { + { "192.168.1", false }, + { "192.168.1.1", true }, + { "192.168.1.1.1", false }, + { "-192.168.1.1", false }, + { "260.192.1.1", false } + }; foreach (var testCase in testIPDict) Assert.AreEqual(S3utils.IsValidIP(testCase.Key), testCase.Value); } @@ -142,19 +143,20 @@ public void TestIfIPIsValid() public void TestIfDomainIsValid() { var testDomainDict = new Dictionary - { - { "%$$$", false }, - { "s3.amazonaws.com", true }, - { "s3.cn-north-1.amazonaws.com.cn", true }, - { "s3.amazonaws.com_", false }, - { "s3.amz.test.com", true }, - { "s3.%%", false }, - { "localhost", true }, - { "-localhost", false }, - { "", false }, - { "\n \t", false }, - { " ", false } - }; + (StringComparer.Ordinal) + { + { "%$$$", false }, + { "s3.amazonaws.com", true }, + { "s3.cn-north-1.amazonaws.com.cn", true }, + { "s3.amazonaws.com_", false }, + { "s3.amz.test.com", true }, + { "s3.%%", false }, + { "localhost", true }, + { "-localhost", false }, + { "", false }, + { "\n \t", false }, + { " ", false } + }; foreach (var testCase in testDomainDict) Assert.AreEqual(RequestUtil.IsValidEndpoint(testCase.Key), testCase.Value); @@ -164,16 +166,17 @@ public void TestIfDomainIsValid() public void TestIsAmazonEndpoint() { var testAmazonDict = new Dictionary - { - { "192.168.1.1", false }, - { "storage.googleapis.com", false }, - { "s3.amazonaws.com", true }, - { "amazons3.amazonaws.com", false }, - { "-192.168.1.1", false }, - { "260.192.1.1", false }, - { "https://s3.amazonaws.com", false }, - { "s3.cn-north-1.amazonaws.com.cn", true } - }; + (StringComparer.Ordinal) + { + { "192.168.1.1", false }, + { "storage.googleapis.com", false }, + { "s3.amazonaws.com", true }, + { "amazons3.amazonaws.com", false }, + { "-192.168.1.1", false }, + { "260.192.1.1", false }, + { "https://s3.amazonaws.com", false }, + { "s3.cn-north-1.amazonaws.com.cn", true } + }; foreach (var testCase in testAmazonDict) { @@ -186,16 +189,17 @@ public void TestIsAmazonEndpoint() public void TestIsAmazonChinaEndpoint() { var testAmazonDict = new Dictionary - { - { "192.168.1.1", false }, - { "storage.googleapis.com", false }, - { "s3.amazonaws.com", false }, - { "amazons3.amazonaws.com", false }, - { "-192.168.1.1", false }, - { "260.192.1.1", false }, - { "https://s3.amazonaws.com", false }, - { "s3.cn-north-1.amazonaws.com.cn", true } - }; + (StringComparer.Ordinal) + { + { "192.168.1.1", false }, + { "storage.googleapis.com", false }, + { "s3.amazonaws.com", false }, + { "amazons3.amazonaws.com", false }, + { "-192.168.1.1", false }, + { "260.192.1.1", false }, + { "https://s3.amazonaws.com", false }, + { "s3.cn-north-1.amazonaws.com.cn", true } + }; foreach (var testCase in testAmazonDict) { diff --git a/Minio.Tests/Minio.Tests.csproj b/Minio.Tests/Minio.Tests.csproj index 93206a714..c60294a7d 100644 --- a/Minio.Tests/Minio.Tests.csproj +++ b/Minio.Tests/Minio.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/Minio.Tests/OperationsTest.cs b/Minio.Tests/OperationsTest.cs index c213d6e5f..70c76ede2 100644 --- a/Minio.Tests/OperationsTest.cs +++ b/Minio.Tests/OperationsTest.cs @@ -97,9 +97,10 @@ public async Task PresignedGetObjectWithHeaders() var objectName = "object-name"; var reqParams = new Dictionary - { - { "Response-Content-Disposition", "attachment; filename=\"filename.jpg\"" } - }; + (StringComparer.Ordinal) + { + { "Response-Content-Disposition", "attachment; filename=\"filename.jpg\"" } + }; var bktExistArgs = new BucketExistsArgs() .WithBucket(bucket); diff --git a/Minio.sln b/Minio.sln index e8863072d..af8e26e91 100644 --- a/Minio.sln +++ b/Minio.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject Docs\API.md = Docs\API.md Directory.Build.props = Directory.Build.props + .config\dotnet-tools.json = .config\dotnet-tools.json .github\workflows\minio-dotnet-linux.yml = .github\workflows\minio-dotnet-linux.yml .github\workflows\minio-dotnet-windows.yml = .github\workflows\minio-dotnet-windows.yml README.md = README.md diff --git a/Minio/AWSS3Endpoints.cs b/Minio/AWSS3Endpoints.cs index 203d5219c..8c9c6c387 100644 --- a/Minio/AWSS3Endpoints.cs +++ b/Minio/AWSS3Endpoints.cs @@ -29,7 +29,7 @@ public sealed class AWSS3Endpoints private AWSS3Endpoints() { - endpoints = new ConcurrentDictionary(); + endpoints = new ConcurrentDictionary(StringComparer.Ordinal); // ap-northeast-1 endpoints.TryAdd("ap-northeast-1", "s3-ap-northeast-1.amazonaws.com"); // ap-northeast-2 diff --git a/Minio/ApiEndpoints/ObjectOperations.cs b/Minio/ApiEndpoints/ObjectOperations.cs index bcfc6a7ff..085935111 100644 --- a/Minio/ApiEndpoints/ObjectOperations.cs +++ b/Minio/ApiEndpoints/ObjectOperations.cs @@ -263,7 +263,7 @@ public async Task PresignedPutObjectAsync(PresignedPutObjectArgs args) var requestMessageBuilder = await CreateRequest(HttpMethod.Put, args.BucketName, args.ObjectName, args.Headers, // contentType - Convert.ToString(args.GetType()), // metaData + Convert.ToString(args.GetType(), CultureInfo.InvariantCulture), // metaData Utils.ObjectToByteArray(args.RequestBody)).ConfigureAwait(false); var authenticator = new V4Authenticator(Secure, AccessKey, SecretKey, Region, SessionToken); @@ -712,9 +712,9 @@ public async Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancell cpReqArgs.Validate(); Dictionary newMeta; if (args.ReplaceMetadataDirective) - newMeta = new Dictionary(args.Headers); + newMeta = new Dictionary(args.Headers, StringComparer.Ordinal); else - newMeta = new Dictionary(args.SourceObjectInfo.MetaData); + newMeta = new Dictionary(args.SourceObjectInfo.MetaData, StringComparer.Ordinal); if (args.SourceObject.SSE is not null && args.SourceObject.SSE is SSECopy) args.SourceObject.SSE.Marshal(newMeta); args.SSE?.Marshal(newMeta); @@ -756,7 +756,7 @@ public async Task StatObjectAsync(StatObjectArgs args, CancellationT using var response = await ExecuteTaskAsync(NoErrorHandlers, requestMessageBuilder, cancellationToken: cancellationToken) .ConfigureAwait(false); - var responseHeaders = new Dictionary(); + var responseHeaders = new Dictionary(StringComparer.Ordinal); foreach (var param in response.Headers.ToList()) responseHeaders.Add(param.Key, param.Value); var statResponse = new StatObjectResponse(response.StatusCode, response.Content, response.Headers, args); @@ -932,11 +932,11 @@ await ExecuteTaskAsync(NoErrorHandlers, requestMessageBuilder, cancellationToken partCondition.byteRangeEnd = partCondition.byteRangeStart + (long)partSize - 1; else partCondition.byteRangeEnd = partCondition.byteRangeStart + (long)lastPartSize - 1; - var queryMap = new Dictionary(); + var queryMap = new Dictionary(StringComparer.Ordinal); if (!string.IsNullOrEmpty(uploadId) && partNumber > 0) { queryMap.Add("uploadId", uploadId); - queryMap.Add("partNumber", partNumber.ToString()); + queryMap.Add("partNumber", partNumber.ToString(CultureInfo.InvariantCulture)); } if (args.SourceObject.SSE is not null && args.SourceObject.SSE is SSECopy) @@ -1320,7 +1320,7 @@ await ExecuteTaskAsync(NoErrorHandlers, requestMessageBuilder, cancellationToken double lastPartSize = multiPartInfo.lastPartSize; var totalParts = new Part[(int)partCount]; - var sseHeaders = new Dictionary(); + var sseHeaders = new Dictionary(StringComparer.Ordinal); sseDest?.Marshal(sseHeaders); // No need to resume upload since this is a Server-side copy. Just initiate a new upload. @@ -1340,20 +1340,21 @@ await ExecuteTaskAsync(NoErrorHandlers, requestMessageBuilder, cancellationToken else partCondition.byteRangeEnd = partCondition.byteRangeStart + (long)lastPartSize - 1; - var queryMap = new Dictionary(); + var queryMap = new Dictionary(StringComparer.Ordinal); if (!string.IsNullOrEmpty(uploadId) && partNumber > 0) { queryMap.Add("uploadId", uploadId); - queryMap.Add("partNumber", partNumber.ToString()); + queryMap.Add("partNumber", partNumber.ToString(CultureInfo.InvariantCulture)); } var customHeader = new Dictionary - { + (StringComparer.Ordinal) { - "x-amz-copy-source-range", - "bytes=" + partCondition.byteRangeStart + "-" + partCondition.byteRangeEnd - } - }; + { + "x-amz-copy-source-range", + "bytes=" + partCondition.byteRangeStart + "-" + partCondition.byteRangeEnd + } + }; if (sseSrc is not null && sseSrc is SSECopy) sseSrc.Marshal(customHeader); sseDest?.Marshal(customHeader); diff --git a/Minio/BucketRegionCache.cs b/Minio/BucketRegionCache.cs index 2dc347670..f56ad7214 100644 --- a/Minio/BucketRegionCache.cs +++ b/Minio/BucketRegionCache.cs @@ -31,7 +31,7 @@ public sealed class BucketRegionCache private BucketRegionCache() { - regionMap = new ConcurrentDictionary(); + regionMap = new ConcurrentDictionary(StringComparer.Ordinal); } public static BucketRegionCache Instance => lazy.Value; diff --git a/Minio/Credentials/AssumeRoleBaseProvider.cs b/Minio/Credentials/AssumeRoleBaseProvider.cs index 5549a883e..23ae8a4c0 100644 --- a/Minio/Credentials/AssumeRoleBaseProvider.cs +++ b/Minio/Credentials/AssumeRoleBaseProvider.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System.Globalization; using System.Net; using System.Text; using CommunityToolkit.HighPerformance; @@ -26,8 +27,8 @@ namespace Minio.Credentials; public abstract class AssumeRoleBaseProvider : IClientProvider where T : AssumeRoleBaseProvider { - internal readonly IEnumerable NoErrorHandlers = - Enumerable.Empty(); + internal readonly IEnumerable NoErrorHandlers = + Enumerable.Empty(); protected AssumeRoleBaseProvider(MinioClient client) { @@ -136,7 +137,7 @@ internal virtual async Task BuildRequest() internal virtual AccessCredentials ParseResponse(HttpResponseMessage response) { - var content = Convert.ToString(response.Content); + var content = Convert.ToString(response.Content, CultureInfo.InvariantCulture); if (string.IsNullOrEmpty(content) || !HttpStatusCode.OK.Equals(response.StatusCode)) throw new ArgumentNullException(nameof(response), "Unable to generate credentials. Response error."); diff --git a/Minio/Credentials/CertificateIdentityProvider.cs b/Minio/Credentials/CertificateIdentityProvider.cs index f2466ddb0..aa36f929e 100644 --- a/Minio/Credentials/CertificateIdentityProvider.cs +++ b/Minio/Credentials/CertificateIdentityProvider.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System.Globalization; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -142,14 +143,14 @@ public CertificateIdentityProvider WithCertificate(X509Certificate2 cert = null) public CertificateIdentityProvider Build() { - if (string.IsNullOrEmpty(DurationInSeconds.ToString())) + if (string.IsNullOrEmpty(DurationInSeconds.ToString(CultureInfo.InvariantCulture))) DurationInSeconds = DEFAULT_DURATION_IN_SECONDS; var builder = new UriBuilder(StsEndpoint); var query = HttpUtility.ParseQueryString(builder.Query); query["Action"] = "AssumeRoleWithCertificate"; query["Version"] = "2011-06-15"; - query["DurationInSeconds"] = DurationInSeconds.ToString(); + query["DurationInSeconds"] = DurationInSeconds.ToString(CultureInfo.InvariantCulture); builder.Query = query.ToString(); PostEndpoint = builder.ToString(); diff --git a/Minio/Credentials/IAMAWSProvider.cs b/Minio/Credentials/IAMAWSProvider.cs index ead5d1308..9331cf625 100644 --- a/Minio/Credentials/IAMAWSProvider.cs +++ b/Minio/Credentials/IAMAWSProvider.cs @@ -150,7 +150,7 @@ public async Task GetAccessCredentials(Uri url) requestBuilder.AddQueryParameter("location", ""); using var response = - await Minio_Client.ExecuteTaskAsync(Enumerable.Empty(), requestBuilder) + await Minio_Client.ExecuteTaskAsync(Enumerable.Empty(), requestBuilder) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(response.Content) || !HttpStatusCode.OK.Equals(response.StatusCode)) @@ -181,7 +181,7 @@ public async Task GetIamRoleNameAsync(Uri url) requestBuilder.AddQueryParameter("location", ""); using var response = - await Minio_Client.ExecuteTaskAsync(Enumerable.Empty(), requestBuilder) + await Minio_Client.ExecuteTaskAsync(Enumerable.Empty(), requestBuilder) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(response.Content) || diff --git a/Minio/Credentials/WebIdentityClientGrantsProvider.cs b/Minio/Credentials/WebIdentityClientGrantsProvider.cs index 6cd0d1570..3a79396f7 100644 --- a/Minio/Credentials/WebIdentityClientGrantsProvider.cs +++ b/Minio/Credentials/WebIdentityClientGrantsProvider.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System.Globalization; using System.Net; using System.Text; using CommunityToolkit.HighPerformance; @@ -61,7 +62,7 @@ internal override AccessCredentials ParseResponse(HttpResponseMessage response) // Stream receiveStream = response.Content.ReadAsStreamAsync(); // StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8); // txtBlock.Text = readStream.ReadToEnd(); - var content = Convert.ToString(response.Content); + var content = Convert.ToString(response.Content, CultureInfo.InvariantCulture); if (string.IsNullOrWhiteSpace(content) || !HttpStatusCode.OK.Equals(response.StatusCode)) throw new ArgumentNullException(nameof(response), "Unable to get credentials. Response error."); diff --git a/Minio/Credentials/WebIdentityProvider.cs b/Minio/Credentials/WebIdentityProvider.cs index cf899eaa4..6ad0f50ab 100644 --- a/Minio/Credentials/WebIdentityProvider.cs +++ b/Minio/Credentials/WebIdentityProvider.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System.Globalization; using System.Text; using System.Xml.Serialization; using CommunityToolkit.HighPerformance; @@ -73,7 +74,8 @@ internal override AccessCredentials ParseResponse(HttpResponseMessage response) { Validate(); var credentials = base.ParseResponse(response); - using var stream = Encoding.UTF8.GetBytes(Convert.ToString(response.Content)).AsMemory().AsStream(); + using var stream = Encoding.UTF8.GetBytes(Convert.ToString(response.Content, CultureInfo.InvariantCulture)) + .AsMemory().AsStream(); return Utils.DeserializeXml(stream); } } \ No newline at end of file diff --git a/Minio/DataModel/BucketArgs.cs b/Minio/DataModel/BucketArgs.cs index 50a897bad..44f9bc7da 100644 --- a/Minio/DataModel/BucketArgs.cs +++ b/Minio/DataModel/BucketArgs.cs @@ -25,7 +25,7 @@ public abstract class BucketArgs : Args internal string BucketName { get; set; } - internal IDictionary Headers { get; set; } = new Dictionary(); + internal IDictionary Headers { get; set; } = new Dictionary(StringComparer.Ordinal); public T WithBucket(string bucket) { @@ -36,7 +36,7 @@ public T WithBucket(string bucket) public virtual T WithHeaders(IDictionary headers) { if (headers is null || headers.Count <= 0) return (T)this; - Headers ??= new Dictionary(); + Headers ??= new Dictionary(StringComparer.Ordinal); foreach (var key in headers.Keys) { Headers.Remove(key); diff --git a/Minio/DataModel/BucketOperationsArgs.cs b/Minio/DataModel/BucketOperationsArgs.cs index 5213732ad..2e4577d23 100644 --- a/Minio/DataModel/BucketOperationsArgs.cs +++ b/Minio/DataModel/BucketOperationsArgs.cs @@ -250,7 +250,7 @@ public SetPolicyArgs() internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) { if (string.IsNullOrEmpty(PolicyJsonString)) - new MinioException("SetPolicyArgs needs the policy to be set to the right JSON contents."); + throw new MinioException("SetPolicyArgs needs the policy to be set to the right JSON contents."); requestMessageBuilder.AddQueryParameter("policy", ""); requestMessageBuilder.AddJsonBody(PolicyJsonString); @@ -347,8 +347,8 @@ internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuild public class ListenBucketNotificationsArgs : BucketArgs { - internal readonly IEnumerable NoErrorHandlers = - Enumerable.Empty(); + internal readonly IEnumerable NoErrorHandlers = + Enumerable.Empty(); public ListenBucketNotificationsArgs() { @@ -375,7 +375,7 @@ public override string ToString() { if (!string.IsNullOrEmpty(eventsAsStr)) eventsAsStr += ", "; - eventsAsStr += eventType.value; + eventsAsStr += eventType.Value; } return string.Join("\n", str, string.Format("Events= [{0}]", eventsAsStr), string.Format("Prefix= {0}", Prefix), @@ -391,7 +391,7 @@ public ListenBucketNotificationsArgs WithNotificationObserver(IObserver public class CopyConditions { - private readonly Dictionary copyConditions = new(); + private readonly Dictionary copyConditions = new(StringComparer.Ordinal); internal long byteRangeEnd = -1; internal long byteRangeStart; diff --git a/Minio/DataModel/MinioNotification.cs b/Minio/DataModel/MinioNotification.cs index ffbaae185..f63a21bc8 100644 --- a/Minio/DataModel/MinioNotification.cs +++ b/Minio/DataModel/MinioNotification.cs @@ -15,6 +15,7 @@ */ using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Text.Json.Serialization; @@ -43,6 +44,9 @@ public MinioNotificationRaw(string json) public class MinioNotification { public string Err { get; set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] public Collection Records { get; set; } } @@ -59,9 +63,14 @@ public class NotificationEvent [JsonPropertyName("eventVersion")] public string EventVersion { get; set; } [JsonPropertyName("requestParameters")] + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] public Dictionary RequestParameters { get; set; } - [JsonPropertyName("responseElements")] public Dictionary ResponseElements { get; set; } + [JsonPropertyName("responseElements")] + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary ResponseElements { get; set; } [JsonPropertyName("s3")] public EventMeta S3 { get; set; } @@ -102,7 +111,10 @@ public class ObjectMeta [JsonPropertyName("size")] public int Size { get; set; } - [JsonPropertyName("userMetadata")] public Dictionary UserMetadata { get; set; } + [JsonPropertyName("userMetadata")] + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary UserMetadata { get; set; } [JsonPropertyName("versionId")] public string VersionId { get; set; } } diff --git a/Minio/DataModel/Notification/BucketNotification.cs b/Minio/DataModel/Notification/BucketNotification.cs index f74d851a6..5ba38d017 100644 --- a/Minio/DataModel/Notification/BucketNotification.cs +++ b/Minio/DataModel/Notification/BucketNotification.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Diagnostics.CodeAnalysis; using System.Xml; using System.Xml.Serialization; @@ -26,13 +27,6 @@ namespace Minio.DataModel; [XmlRoot(ElementName = "NotificationConfiguration", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] public class BucketNotification { - [XmlElement("CloudFunctionConfiguration")] - public List LambdaConfigs; - - [XmlElement("TopicConfiguration")] public List TopicConfigs; - [XmlElement("QueueConfiguration")] public List QueueConfigs; - - public BucketNotification() { LambdaConfigs = new List(); @@ -40,6 +34,21 @@ public BucketNotification() QueueConfigs = new List(); } + [XmlElement("CloudFunctionConfiguration")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", + Justification = "Needs to be concrete type for XML deserialization")] + public List LambdaConfigs { get; set; } + + [XmlElement("TopicConfiguration")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", + Justification = "Needs to be concrete type for XML deserialization")] + public List TopicConfigs { get; set; } + + [XmlElement("QueueConfiguration")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", + Justification = "Needs to be concrete type for XML deserialization")] + public List QueueConfigs { get; set; } + public string Name { get; set; } /// diff --git a/Minio/DataModel/Notification/EventType.cs b/Minio/DataModel/Notification/EventType.cs index b77f08128..0e52d39ea 100644 --- a/Minio/DataModel/Notification/EventType.cs +++ b/Minio/DataModel/Notification/EventType.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Globalization; using System.Xml.Serialization; namespace Minio.DataModel; @@ -42,20 +43,20 @@ public sealed class EventType public static readonly EventType ObjectRemovedDeleteMarkerCreated = new("s3:ObjectRemoved:DeleteMarkerCreated"); public static readonly EventType ReducedRedundancyLostObject = new("s3:ReducedRedundancyLostObject"); - [XmlText] public string value; - private EventType() { - value = null; + Value = null; } public EventType(string value) { - this.value = value; + Value = value; } + [XmlText] public string Value { get; set; } + public override string ToString() { - return string.Format("EventType= {0}", value); + return string.Format(CultureInfo.InvariantCulture, "EventType= {0}", Value); } } \ No newline at end of file diff --git a/Minio/DataModel/Notification/LambdaConfig.cs b/Minio/DataModel/Notification/LambdaConfig.cs index c05c01b4d..f3a1f7803 100644 --- a/Minio/DataModel/Notification/LambdaConfig.cs +++ b/Minio/DataModel/Notification/LambdaConfig.cs @@ -54,6 +54,6 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Lambda.GetHashCode(); + return StringComparer.Ordinal.GetHashCode(Lambda); } } \ No newline at end of file diff --git a/Minio/DataModel/Notification/NotificationConfiguration.cs b/Minio/DataModel/Notification/NotificationConfiguration.cs index bb4e1a629..66153eadb 100644 --- a/Minio/DataModel/Notification/NotificationConfiguration.cs +++ b/Minio/DataModel/Notification/NotificationConfiguration.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Diagnostics.CodeAnalysis; using System.Xml.Serialization; namespace Minio.DataModel; @@ -24,10 +25,6 @@ namespace Minio.DataModel; /// public class NotificationConfiguration { - [XmlElement] public string Id { get; set; } - [XmlElement("Event")] public List Events { get; set; } - [XmlElement("Filter")] public Filter Filter; - public NotificationConfiguration() { Arn = null; @@ -44,6 +41,13 @@ public NotificationConfiguration(Arn arn) Arn = arn; } + [XmlElement] public string Id { get; set; } + + [XmlElement("Event")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Using Range functions in code")] + public List Events { get; set; } + + [XmlElement("Filter")] public Filter Filter { get; set; } private Arn Arn { get; } diff --git a/Minio/DataModel/Notification/QueueConfig.cs b/Minio/DataModel/Notification/QueueConfig.cs index a7820d9a9..db5ec4aeb 100644 --- a/Minio/DataModel/Notification/QueueConfig.cs +++ b/Minio/DataModel/Notification/QueueConfig.cs @@ -51,6 +51,6 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Queue.GetHashCode(); + return StringComparer.Ordinal.GetHashCode(Queue); } } \ No newline at end of file diff --git a/Minio/DataModel/Notification/TopicConfig.cs b/Minio/DataModel/Notification/TopicConfig.cs index d9eb05efb..ad29f74ba 100644 --- a/Minio/DataModel/Notification/TopicConfig.cs +++ b/Minio/DataModel/Notification/TopicConfig.cs @@ -57,6 +57,6 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Topic.GetHashCode(); + return StringComparer.Ordinal.GetHashCode(Topic); } } \ No newline at end of file diff --git a/Minio/DataModel/ObjectOperationsArgs.cs b/Minio/DataModel/ObjectOperationsArgs.cs index 1e7dd87c3..ef143d818 100644 --- a/Minio/DataModel/ObjectOperationsArgs.cs +++ b/Minio/DataModel/ObjectOperationsArgs.cs @@ -175,7 +175,7 @@ internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuild requestMessageBuilder.AddQueryParameter("delimiter", Delimiter); requestMessageBuilder.AddQueryParameter("key-marker", KeyMarker); requestMessageBuilder.AddQueryParameter("upload-id-marker", UploadIdMarker); - requestMessageBuilder.AddQueryParameter("max-uploads", MAX_UPLOAD_COUNT.ToString()); + requestMessageBuilder.AddQueryParameter("max-uploads", MAX_UPLOAD_COUNT.ToString(CultureInfo.InvariantCulture)); return requestMessageBuilder; } } @@ -260,7 +260,7 @@ internal override void Validate() private void Populate() { - Headers ??= new Dictionary(); + Headers ??= new Dictionary(StringComparer.Ordinal); if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) SSE.Marshal(Headers); if (OffsetLengthSet) { @@ -324,7 +324,7 @@ protected new void Validate() ObjectName = Policy.Key; } - if (string.IsNullOrEmpty(Expiration.ToString())) + if (string.IsNullOrEmpty(Expiration.ToString(CultureInfo.InvariantCulture))) throw new InvalidOperationException("For the " + nameof(Policy) + " expiration should be set"); } @@ -536,7 +536,7 @@ internal override void Validate() private void Populate() { - Headers ??= new Dictionary(); + Headers ??= new Dictionary(StringComparer.Ordinal); if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) SSE.Marshal(Headers); if (OffsetLengthSet) @@ -716,7 +716,7 @@ internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuild deleteObjectsRequest = new XElement("Delete", objects, new XElement("Quiet", true)); - requestMessageBuilder.AddXmlBody(Convert.ToString(deleteObjectsRequest)); + requestMessageBuilder.AddXmlBody(Convert.ToString(deleteObjectsRequest, CultureInfo.InvariantCulture)); } else { @@ -727,11 +727,12 @@ internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuild deleteObjectsRequest = new XElement("Delete", objects, new XElement("Quiet", true)); - requestMessageBuilder.AddXmlBody(Convert.ToString(deleteObjectsRequest)); + requestMessageBuilder.AddXmlBody(Convert.ToString(deleteObjectsRequest, CultureInfo.InvariantCulture)); } requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", - Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(Convert.ToString(deleteObjectsRequest)))); + Utils.GetMD5SumStr( + Encoding.UTF8.GetBytes(Convert.ToString(deleteObjectsRequest, CultureInfo.InvariantCulture)))); return requestMessageBuilder; } @@ -919,7 +920,7 @@ public CopySourceObjectArgs() { RequestMethod = HttpMethod.Put; CopyOperationConditions = new CopyConditions(); - Headers = new Dictionary(); + Headers = new Dictionary(StringComparer.Ordinal); } internal string CopySourceObjectPath { get; set; } @@ -942,7 +943,7 @@ internal class CopyObjectRequestArgs : ObjectWriteArgs internal CopyObjectRequestArgs() { RequestMethod = HttpMethod.Put; - Headers = new Dictionary(); + Headers = new Dictionary(StringComparer.Ordinal); CopyOperationObjectType = typeof(CopyObjectResult); } @@ -960,14 +961,14 @@ internal CopyObjectRequestArgs() internal CopyObjectRequestArgs WithQueryMap(IDictionary queryMap) { - QueryMap = new Dictionary(queryMap); + QueryMap = new Dictionary(queryMap, StringComparer.Ordinal); return this; } internal CopyObjectRequestArgs WithPartCondition(CopyConditions partCondition) { CopyCondition = partCondition.Clone(); - Headers ??= new Dictionary(); + Headers ??= new Dictionary(StringComparer.Ordinal); Headers["x-amz-copy-source-range"] = "bytes=" + partCondition.byteRangeStart + "-" + partCondition.byteRangeEnd; return this; @@ -996,7 +997,7 @@ public CopyObjectRequestArgs WithCopyObjectSource(CopySourceObjectArgs cs) SourceObject.ObjectName = cs.ObjectName; SourceObject.VersionId = cs.VersionId; SourceObject.SSE = cs.SSE; - SourceObject.Headers = new Dictionary(cs.Headers); + SourceObject.Headers = new Dictionary(cs.Headers, StringComparer.Ordinal); SourceObject.MatchETag = cs.MatchETag; SourceObject.ModifiedSince = cs.ModifiedSince; SourceObject.NotMatchETag = cs.NotMatchETag; @@ -1099,9 +1100,9 @@ internal void Populate() ObjectName = string.IsNullOrEmpty(ObjectName) ? SourceObject.ObjectName : ObjectName; // Opting for concat as Headers may have byte range info .etc. if (!ReplaceMetadataDirective && SourceObjectInfo.MetaData is not null) - Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key) - .ToDictionary(item => item.Key, item => item.First().Value); - else if (ReplaceMetadataDirective) Headers ??= new Dictionary(); + Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); + else if (ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); } } @@ -1155,7 +1156,7 @@ private void Populate() if (string.IsNullOrEmpty(ObjectName)) ObjectName = SourceObject.ObjectName; if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) { - Headers = new Dictionary(); + Headers = new Dictionary(StringComparer.Ordinal); SSE.Marshal(Headers); } @@ -1166,7 +1167,7 @@ private void Populate() WithReplaceMetadataDirective(copyReplaceMeta); } - Headers ??= new Dictionary(); + Headers ??= new Dictionary(StringComparer.Ordinal); if (ReplaceMetadataDirective) { if (Headers is not null) @@ -1184,9 +1185,9 @@ private void Populate() Headers = Headers .Concat(SourceObjectInfo.MetaData) - .GroupBy(item => item.Key) + .GroupBy(item => item.Key, StringComparer.Ordinal) .ToDictionary(item => item.Key, item => - item.Last().Value); + item.Last().Value, StringComparer.Ordinal); } if (Headers is not null) @@ -1269,9 +1270,10 @@ internal CopyObjectArgs WithCopyObjectSourceStats(ObjectStat info) SourceObjectInfo = info; if (info.MetaData is not null && !ReplaceMetadataDirective) { - SourceObject.Headers ??= new Dictionary(); - SourceObject.Headers = SourceObject.Headers.Concat(info.MetaData).GroupBy(item => item.Key) - .ToDictionary(item => item.Key, item => item.First().Value); + SourceObject.Headers ??= new Dictionary(StringComparer.Ordinal); + SourceObject.Headers = SourceObject.Headers.Concat(info.MetaData) + .GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); } return this; @@ -1434,8 +1436,8 @@ internal MultipartCopyUploadArgs(CopyObjectArgs args) SourceObjectInfo = args.SourceObjectInfo; // Header part if (!args.ReplaceMetadataDirective) - Headers = new Dictionary(args.SourceObjectInfo.MetaData); - else if (args.ReplaceMetadataDirective) Headers ??= new Dictionary(); + Headers = new Dictionary(args.SourceObjectInfo.MetaData, StringComparer.Ordinal); + else if (args.ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); if (Headers is not null) { var newKVList = new List>(); @@ -1553,9 +1555,9 @@ private void Populate() { //Concat as Headers may have byte range info .etc. if (!ReplaceMetadataDirective && SourceObjectInfo.MetaData?.Count > 0) - Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key) - .ToDictionary(item => item.Key, item => item.First().Value); - else if (ReplaceMetadataDirective) Headers ??= new Dictionary(); + Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); + else if (ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); if (Headers is not null) { var newKVList = new List>(); @@ -1676,12 +1678,12 @@ internal CompleteMultipartUploadArgs(MultipartCopyUploadArgs args) RequestMethod = HttpMethod.Post; BucketName = args.BucketName; ObjectName = args.ObjectName ?? args.SourceObject.ObjectName; - Headers = new Dictionary(); + Headers = new Dictionary(StringComparer.Ordinal); SSE = args.SSE; SSE?.Marshal(args.Headers); if (args.Headers?.Count > 0) - Headers = Headers.Concat(args.Headers).GroupBy(item => item.Key) - .ToDictionary(item => item.Key, item => item.First().Value); + Headers = Headers.Concat(args.Headers).GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); } internal string UploadId { get; set; } diff --git a/Minio/DataModel/ObjectOperationsResponse.cs b/Minio/DataModel/ObjectOperationsResponse.cs index 38ce7cb32..1325baad9 100644 --- a/Minio/DataModel/ObjectOperationsResponse.cs +++ b/Minio/DataModel/ObjectOperationsResponse.cs @@ -198,10 +198,6 @@ internal NewMultipartUploadResponse(HttpStatusCode statusCode, string responseCo public class PutObjectResponse : GenericResponse { - public string Etag; - public string ObjectName; - public long Size; - public PutObjectResponse(HttpStatusCode statusCode, string responseContent, IDictionary responseHeaders, long size, string name) : base(statusCode, responseContent) @@ -218,4 +214,8 @@ public class PutObjectResponse : GenericResponse Size = size; ObjectName = name; } + + public string Etag { get; set; } + public string ObjectName { get; set; } + public long Size { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/ObjectStat.cs b/Minio/DataModel/ObjectStat.cs index 2c6c89b02..5cf1228c4 100644 --- a/Minio/DataModel/ObjectStat.cs +++ b/Minio/DataModel/ObjectStat.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text.RegularExpressions; using Minio.DataModel.ObjectLock; @@ -33,10 +34,18 @@ private ObjectStat() public DateTime LastModified { get; private set; } public string ETag { get; private set; } public string ContentType { get; private set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] public Dictionary MetaData { get; } + public string VersionId { get; private set; } public bool DeleteMarker { get; private set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] public Dictionary ExtraHeaders { get; } + public uint? TaggingCount { get; private set; } public string ArchiveStatus { get; private set; } public DateTime? Expires { get; private set; } @@ -61,7 +70,7 @@ public static ObjectStat FromResponseHeaders(string objectName, IDictionary= 0) + if (int.TryParse(paramValue, NumberStyles.Integer, CultureInfo.InvariantCulture, + out var tagCount) && tagCount >= 0) objInfo.TaggingCount = (uint)tagCount; break; case "x-amz-expiration": @@ -91,10 +101,11 @@ public static ObjectStat FromResponseHeaders(string objectName, IDictionary /// Dictionary of policy data - public IDictionary FormData { get; } = new Dictionary(); + public IDictionary FormData { get; } = new Dictionary(StringComparer.Ordinal); public DateTime Expiration { get; set; } public string Key { get; private set; } @@ -131,7 +132,10 @@ public void SetContentLength(long contentLength) if (contentLength <= 0) throw new ArgumentException("Negative Content length", nameof(contentLength)); Conditions.Add(new List<(string, string, string)> - { ("content-length-range", contentLength.ToString(), contentLength.ToString()) }); + { + ("content-length-range", contentLength.ToString(CultureInfo.InvariantCulture), + contentLength.ToString(CultureInfo.InvariantCulture)) + }); } /// @@ -147,7 +151,10 @@ public void SetContentRange(long startRange, long endRange) throw new ArgumentException("Start range is greater than end range", nameof(startRange)); Conditions.Add(new List<(string, string, string)> - { ("content-length-range", startRange.ToString(), endRange.ToString()) }); + { + ("content-length-range", startRange.ToString(CultureInfo.InvariantCulture), + endRange.ToString(CultureInfo.InvariantCulture)) + }); } /// @@ -312,8 +319,6 @@ public bool IsKeySet() /// true if expiration is set public bool IsExpirationSet() { - if (!string.IsNullOrEmpty(Expiration.ToString())) return true; - - return false; + return !string.IsNullOrEmpty(Expiration.ToString(CultureInfo.InvariantCulture)); } } \ No newline at end of file diff --git a/Minio/DataModel/Select/CSVFileHeaderInfo.cs b/Minio/DataModel/Select/CSVFileHeaderInfo.cs index 6ab00ad56..fc0e0ecf8 100644 --- a/Minio/DataModel/Select/CSVFileHeaderInfo.cs +++ b/Minio/DataModel/Select/CSVFileHeaderInfo.cs @@ -26,8 +26,6 @@ public sealed class CSVFileHeaderInfo public static readonly CSVFileHeaderInfo Ignore = new("IGNORE"); public static readonly CSVFileHeaderInfo Use = new("USE"); - [XmlText] public string HeaderInfo; - public CSVFileHeaderInfo() { } @@ -36,4 +34,6 @@ public CSVFileHeaderInfo(string value) { HeaderInfo = value; } + + [XmlText] public string HeaderInfo { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/Select/CSVQuoteFields.cs b/Minio/DataModel/Select/CSVQuoteFields.cs index e6030f19a..752abb7f3 100644 --- a/Minio/DataModel/Select/CSVQuoteFields.cs +++ b/Minio/DataModel/Select/CSVQuoteFields.cs @@ -25,8 +25,6 @@ public sealed class CSVQuoteFields public static readonly CSVQuoteFields Always = new("Always"); public static readonly CSVQuoteFields AsNeeded = new("AsNeeded"); - [XmlText] public string QuoteFields; - public CSVQuoteFields(string value) { QuoteFields = value; @@ -35,4 +33,6 @@ public CSVQuoteFields(string value) public CSVQuoteFields() { } + + [XmlText] public string QuoteFields { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/Select/JSONType.cs b/Minio/DataModel/Select/JSONType.cs index 3acae8126..da99715c0 100644 --- a/Minio/DataModel/Select/JSONType.cs +++ b/Minio/DataModel/Select/JSONType.cs @@ -25,8 +25,6 @@ public sealed class JSONType public static readonly JSONType Document = new("DOCUMENT"); public static readonly JSONType Lines = new("LINES"); - [XmlText] public string Type; - public JSONType() { } @@ -35,4 +33,6 @@ public JSONType(string value) { Type = value; } + + [XmlText] public string Type { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/Select/QueryExpressionType.cs b/Minio/DataModel/Select/QueryExpressionType.cs index 36988fbe2..d2b5f30cb 100644 --- a/Minio/DataModel/Select/QueryExpressionType.cs +++ b/Minio/DataModel/Select/QueryExpressionType.cs @@ -24,8 +24,6 @@ public sealed class QueryExpressionType // Constants for compression types under select API. public static readonly QueryExpressionType SQL = new("SQL"); - [XmlText] public string ExpressionType; - public QueryExpressionType() { } @@ -34,4 +32,6 @@ public QueryExpressionType(string value) { ExpressionType = value; } + + [XmlText] public string ExpressionType { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/Select/SelectCompressionType.cs b/Minio/DataModel/Select/SelectCompressionType.cs index 165704640..4184c86ab 100644 --- a/Minio/DataModel/Select/SelectCompressionType.cs +++ b/Minio/DataModel/Select/SelectCompressionType.cs @@ -26,8 +26,6 @@ public sealed class SelectCompressionType public static readonly SelectCompressionType GZIP = new("GZIP"); public static readonly SelectCompressionType BZIP = new("BZIP2"); - [XmlText] public string CompressionType; - public SelectCompressionType() { } @@ -36,4 +34,6 @@ public SelectCompressionType(string value) { CompressionType = value; } + + [XmlText] public string CompressionType { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/Select/SelectObjectType.cs b/Minio/DataModel/Select/SelectObjectType.cs index eba150229..1ad9c7c08 100644 --- a/Minio/DataModel/Select/SelectObjectType.cs +++ b/Minio/DataModel/Select/SelectObjectType.cs @@ -23,14 +23,14 @@ public sealed class SelectObjectType public static readonly SelectObjectType JSON = new("JSON"); public static readonly SelectObjectType Parquet = new("Parquet"); - public string Type; - public SelectObjectType() { } - public SelectObjectType(string value) + public SelectObjectType(string type) { - Type = value; + Type = type; } + + public string Type { get; set; } } \ No newline at end of file diff --git a/Minio/DataModel/Select/SelectResponseStream.cs b/Minio/DataModel/Select/SelectResponseStream.cs index 5e886a279..4eeb0b8f2 100644 --- a/Minio/DataModel/Select/SelectResponseStream.cs +++ b/Minio/DataModel/Select/SelectResponseStream.cs @@ -203,7 +203,7 @@ private void Start() protected IDictionary ExtractHeaders(Span data) { - var headerMap = new Dictionary(); + var headerMap = new Dictionary(StringComparer.Ordinal); var offset = 0; while (offset < data.Length) diff --git a/Minio/DataModel/ServerSideEncryption.cs b/Minio/DataModel/ServerSideEncryption.cs index 425af6652..7eeddaadf 100644 --- a/Minio/DataModel/ServerSideEncryption.cs +++ b/Minio/DataModel/ServerSideEncryption.cs @@ -22,9 +22,11 @@ namespace Minio.DataModel; // Type of Server-side encryption public enum EncryptionType { +#pragma warning disable CA1707 // Identifiers should not contain underscores SSE_C, SSE_S3, SSE_KMS +#pragma warning restore CA1707 // Identifiers should not contain underscores } /// @@ -45,13 +47,13 @@ public interface IServerSideEncryption public class SSEC : IServerSideEncryption { // secret AES-256 Key - protected byte[] key; + protected byte[] Key; public SSEC(byte[] key) { if (key is null || key.Length != 32) throw new ArgumentException("Secret key needs to be a 256 bit AES Key", nameof(key)); - this.key = key; + Key = key; } public EncryptionType GetEncryptionType() @@ -63,9 +65,9 @@ public virtual void Marshal(IDictionary headers) { if (headers is null) throw new ArgumentNullException(nameof(headers)); - var md5SumStr = Utils.GetMD5SumStr(key); + var md5SumStr = Utils.GetMD5SumStr(Key); headers.Add("X-Amz-Server-Side-Encryption-Customer-Algorithm", "AES256"); - headers.Add("X-Amz-Server-Side-Encryption-Customer-Key", Convert.ToBase64String(key)); + headers.Add("X-Amz-Server-Side-Encryption-Customer-Key", Convert.ToBase64String(Key)); headers.Add("X-Amz-Server-Side-Encryption-Customer-Key-Md5", md5SumStr); } } @@ -83,15 +85,15 @@ public override void Marshal(IDictionary headers) { if (headers is null) throw new ArgumentNullException(nameof(headers)); - var md5SumStr = Utils.GetMD5SumStr(key); + var md5SumStr = Utils.GetMD5SumStr(Key); headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm", "AES256"); - headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key", Convert.ToBase64String(key)); + headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key", Convert.ToBase64String(Key)); headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5", md5SumStr); } public SSEC CloneToSSEC() { - return new SSEC(key); + return new SSEC(Key); } } @@ -118,19 +120,19 @@ public virtual void Marshal(IDictionary headers) /// public class SSEKMS : IServerSideEncryption { - protected IDictionary context; - - // Specifies the customer master key(CMK).Cannot be null - protected string key; - public SSEKMS(string key, IDictionary context = null) { if (string.IsNullOrEmpty(key)) throw new ArgumentException("KMS Key cannot be empty", nameof(key)); - this.key = key; - this.context = context; + Key = key; + Context = context; } + protected IDictionary Context { get; set; } + + // Specifies the customer master key(CMK).Cannot be null + protected string Key { get; set; } + public EncryptionType GetEncryptionType() { return EncryptionType.SSE_KMS; @@ -140,9 +142,9 @@ public void Marshal(IDictionary headers) { if (headers is null) throw new ArgumentNullException(nameof(headers)); - headers.Add(Constants.SSEKMSKeyId, key); + headers.Add(Constants.SSEKMSKeyId, Key); headers.Add(Constants.SSEGenericHeader, "aws:kms"); - if (context is not null) headers.Add(Constants.SSEKMSContext, MarshalContext()); + if (Context is not null) headers.Add(Constants.SSEKMSContext, MarshalContext()); } /// @@ -155,8 +157,8 @@ private string MarshalContext() sb.Append('{'); var i = 0; - var len = context.Count; - foreach (var pair in context) + var len = Context.Count; + foreach (var pair in Context) { sb.Append('"').Append(pair.Key).Append('"'); sb.Append(':'); diff --git a/Minio/DataModel/Tags/Tagging.cs b/Minio/DataModel/Tags/Tagging.cs index ee99db29a..09a1c863a 100644 --- a/Minio/DataModel/Tags/Tagging.cs +++ b/Minio/DataModel/Tags/Tagging.cs @@ -69,7 +69,7 @@ public Tagging(IDictionary tags, bool isObjects) get { if (TaggingSet is null || TaggingSet.Tag.Count == 0) return null; - var tagMap = new Dictionary(); + var tagMap = new Dictionary(StringComparer.Ordinal); foreach (var tag in TaggingSet.Tag) tagMap[tag.Key] = tag.Value; return tagMap; } diff --git a/Minio/DataModel/Tracing/ResponseToLog.cs b/Minio/DataModel/Tracing/ResponseToLog.cs index 90129db02..52deed996 100644 --- a/Minio/DataModel/Tracing/ResponseToLog.cs +++ b/Minio/DataModel/Tracing/ResponseToLog.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Diagnostics.CodeAnalysis; using System.Net; namespace Minio.DataModel.Tracing; @@ -21,7 +22,11 @@ namespace Minio.DataModel.Tracing; public sealed class ResponseToLog { public string Content { get; internal set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] public Dictionary Headers { get; internal set; } + public HttpStatusCode StatusCode { get; internal set; } public Uri ResponseUri { get; internal set; } public double DurationMs { get; internal set; } diff --git a/Minio/Helper/BuilderUtil.cs b/Minio/Helper/BuilderUtil.cs index 2036aead6..01567234e 100644 --- a/Minio/Helper/BuilderUtil.cs +++ b/Minio/Helper/BuilderUtil.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Globalization; using System.Net; namespace Minio.Helper; @@ -85,7 +86,7 @@ private static bool IsValidSmallInt(string val) private static bool IsValidOctetVal(string val) { const byte uLimit = 255; - return byte.Parse(val) <= uLimit; + return byte.Parse(val, NumberStyles.Integer, CultureInfo.InvariantCulture) <= uLimit; } private static bool IsValidIPv4(string ip) @@ -96,8 +97,7 @@ private static bool IsValidIPv4(string ip) if (octetsStr.Length != 4) return false; var isValidSmallInt = Array.TrueForAll(octetsStr, IsValidSmallInt); if (!isValidSmallInt) return false; - var isValidOctet = Array.TrueForAll(octetsStr, IsValidOctetVal); - return isValidOctet; + return Array.TrueForAll(octetsStr, IsValidOctetVal); } private static bool IsValidIP(string host) @@ -119,7 +119,8 @@ public static bool IsValidHostnameOrIPAddress(string host) { try { - var port = int.Parse(host.Substring(posColon + 1, host.Length - posColon - 1)); + var port = int.Parse(host.Substring(posColon + 1, host.Length - posColon - 1), + CultureInfo.InvariantCulture); } catch (FormatException) { diff --git a/Minio/Helper/RequestUtil.cs b/Minio/Helper/RequestUtil.cs index 6f8916ade..2769b05fc 100644 --- a/Minio/Helper/RequestUtil.cs +++ b/Minio/Helper/RequestUtil.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Globalization; using System.Text.RegularExpressions; using System.Web; using Minio.Exceptions; @@ -62,7 +63,7 @@ internal static Uri GetEndpointURL(string endPoint, bool secure) } var scheme = secure ? "https" : "http"; - var endpointURL = string.Format("{0}://{1}", scheme, host); + var endpointURL = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, host); return new Uri(endpointURL, UriKind.Absolute); } @@ -71,7 +72,7 @@ internal static Uri TryCreateUri(string endpoint, bool secure) var scheme = secure ? HttpUtility.UrlEncode("https") : HttpUtility.UrlEncode("http"); // This is the actual url pointed to for all HTTP requests - var endpointURL = string.Format("{0}://{1}", scheme, endpoint); + var endpointURL = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, endpoint); Uri uri; try { @@ -120,7 +121,8 @@ internal static bool IsValidEndpoint(string endpoint) { if (label.Length < 1 || label.Length > 63) return false; - var validLabel = new Regex("^[a-zA-Z0-9]([A-Za-z0-9-_]*[a-zA-Z0-9])?$"); + var validLabel = new Regex("^[a-zA-Z0-9]([A-Za-z0-9-_]*[a-zA-Z0-9])?$", RegexOptions.ExplicitCapture, + TimeSpan.FromHours(1)); if (!validLabel.IsMatch(label)) return false; } diff --git a/Minio/Helper/S3utils.cs b/Minio/Helper/S3utils.cs index 392d4a9bd..b6ac27109 100644 --- a/Minio/Helper/S3utils.cs +++ b/Minio/Helper/S3utils.cs @@ -20,12 +20,13 @@ namespace Minio.Helper; internal static class S3utils { - internal static readonly Regex TrimWhitespaceRegex = new("\\s+"); + internal static readonly Regex TrimWhitespaceRegex = new("\\s+", RegexOptions.None, TimeSpan.FromHours(1)); internal static bool IsAmazonEndPoint(string endpoint) { if (IsAmazonChinaEndPoint(endpoint)) return true; - var rgx = new Regex("^s3[.-]?(.*?)\\.amazonaws\\.com$", RegexOptions.IgnoreCase); + var rgx = new Regex("^s3[.-]?(.*?)\\.amazonaws\\.com$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, + TimeSpan.FromHours(1)); var matches = rgx.Matches(endpoint); return matches.Count > 0; } diff --git a/Minio/Helper/Utils.cs b/Minio/Helper/Utils.cs index aa9557099..ca3029f50 100644 --- a/Minio/Helper/Utils.cs +++ b/Minio/Helper/Utils.cs @@ -37,10 +37,11 @@ public static class Utils { // We support '.' with bucket names but we fallback to using path // style requests instead for such buckets. - private static readonly Regex validBucketName = new("^[a-z0-9][a-z0-9\\.\\-]{1,61}[a-z0-9]$"); + private static readonly Regex validBucketName = + new("^[a-z0-9][a-z0-9\\.\\-]{1,61}[a-z0-9]$", RegexOptions.None, TimeSpan.FromHours(1)); // Invalid bucket name with double dot. - private static readonly Regex invalidDotBucketName = new("`/./."); + private static readonly Regex invalidDotBucketName = new("`/./.", RegexOptions.None, TimeSpan.FromHours(1)); private static readonly Lazy> _contentTypeMap = new(AddContentTypeMappings); @@ -196,10 +197,10 @@ internal static bool IsSupersetOf(IList l1, IList l2) if (l1 is null) return false; - return !l2.Except(l1).Any(); + return !l2.Except(l1, StringComparer.Ordinal).Any(); } - public static async Task RunInParallel(IEnumerable source, + public static Task RunInParallel(IEnumerable source, Func body) { var maxNoOfParallelProcesses = 4; @@ -208,15 +209,15 @@ internal static bool IsSupersetOf(IList l1, IList l2) { MaxDegreeOfParallelism = maxNoOfParallelProcesses }; - await Parallel.ForEachAsync(source, parallelOptions, body); + return Parallel.ForEachAsync(source, parallelOptions, body); #else - await Task.WhenAll(Partitioner.Create(source).GetPartitions(maxNoOfParallelProcesses) + return Task.WhenAll(Partitioner.Create(source).GetPartitions(maxNoOfParallelProcesses) .Select(partition => Task.Run(async delegate { using (partition) { while (partition.MoveNext()) - await body(partition.Current, new CancellationToken()); + await body(partition.Current, new CancellationToken()).ConfigureAwait(false); } } ))); @@ -274,7 +275,9 @@ public static bool IsValidExpiry(int expiryInt) internal static string GetMD5SumStr(ReadOnlySpan key) { #if NETSTANDARD +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms using var md5 = MD5.Create(); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms var hashedBytes = md5.ComputeHash(key.ToArray()); #else ReadOnlySpan hashedBytes = MD5.HashData(key); @@ -868,7 +871,7 @@ public static string MarshalXML(object obj, string nmspc) } finally { - xw?.Close(); + xw.Close(); } return str; @@ -888,13 +891,14 @@ public static string RemoveNamespaceInXML(string config) var patternToReplace = @"<\w+\s+\w+:nil=""true""(\s+xmlns:\w+=""http://www.w3.org/2001/XMLSchema-instance"")?\s*/>"; var patternToMatch = @"<\w+\s+xmlns=""http://s3.amazonaws.com/doc/2006-03-01/""\s*>"; - if (Regex.Match(config, patternToMatch, regexOptions).Success) + if (Regex.Match(config, patternToMatch, regexOptions, TimeSpan.FromHours(1)).Success) patternToReplace = @"xmlns=""http://s3.amazonaws.com/doc/2006-03-01/""\s*"; return Regex.Replace( config, patternToReplace, string.Empty, - regexOptions + regexOptions, + TimeSpan.FromHours(1) ); } @@ -907,18 +911,21 @@ public static Uri GetBaseUrl(string endpoint) { if (string.IsNullOrEmpty(endpoint)) throw new ArgumentException( - string.Format("{0} is the value of the endpoint. It can't be null or empty.", endpoint), + string.Format(CultureInfo.InvariantCulture, + "{0} is the value of the endpoint. It can't be null or empty.", endpoint), nameof(endpoint)); if (endpoint.EndsWith("/", StringComparison.OrdinalIgnoreCase)) endpoint = endpoint.Substring(0, endpoint.Length - 1); if (!endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !BuilderUtil.IsValidHostnameOrIPAddress(endpoint)) - throw new InvalidEndpointException(string.Format("{0} is invalid hostname.", endpoint), "endpoint"); + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0} is invalid hostname.", endpoint), "endpoint"); string conn_url; if (endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase)) throw new InvalidEndpointException( - string.Format("{0} the value of the endpoint has the scheme (http/https) in it.", endpoint), + string.Format(CultureInfo.InvariantCulture, + "{0} the value of the endpoint has the scheme (http/https) in it.", endpoint), "endpoint"); var enable_https = Environment.GetEnvironmentVariable("ENABLE_HTTPS"); @@ -927,7 +934,8 @@ public static Uri GetBaseUrl(string endpoint) var url = new Uri(conn_url); var hostnameOfUri = url.Authority; if (!string.IsNullOrWhiteSpace(hostnameOfUri) && !BuilderUtil.IsValidHostnameOrIPAddress(hostnameOfUri)) - throw new InvalidEndpointException(string.Format("{0}, {1} is invalid hostname.", endpoint, hostnameOfUri), + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0}, {1} is invalid hostname.", endpoint, hostnameOfUri), "endpoint"); return url; diff --git a/Minio/HttpRequestMessageBuilder.cs b/Minio/HttpRequestMessageBuilder.cs index 62eb4258e..67ae4ebab 100644 --- a/Minio/HttpRequestMessageBuilder.cs +++ b/Minio/HttpRequestMessageBuilder.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +using System.Globalization; using System.Net.Http.Headers; using System.Text; using System.Web; @@ -98,7 +99,7 @@ public HttpRequestMessage Request break; case "content-length": - request.Content.Headers.ContentLength = Convert.ToInt32(val); + request.Content.Headers.ContentLength = Convert.ToInt32(val, CultureInfo.InvariantCulture); break; case "content-md5": request.Content.Headers.ContentMD5 = Convert.FromBase64String(val); diff --git a/Minio/IMinioClient.cs b/Minio/IMinioClient.cs index 6e0ec7f34..038af1696 100644 --- a/Minio/IMinioClient.cs +++ b/Minio/IMinioClient.cs @@ -118,6 +118,6 @@ public interface IMinioClient : IDisposable void SetTraceOn(IRequestLogger logger = null); Task SetVersioningAsync(SetVersioningArgs args, CancellationToken cancellationToken = default); Task StatObjectAsync(StatObjectArgs args, CancellationToken cancellationToken = default); - Task WrapperGetAsync(string url); - Task WrapperPutAsync(string url, StreamContent strm); + Task WrapperGetAsync(Uri uri); + Task WrapperPutAsync(Uri uri, StreamContent strm); } \ No newline at end of file diff --git a/Minio/Minio.csproj b/Minio/Minio.csproj index fa4371a86..94f7f4db3 100644 --- a/Minio/Minio.csproj +++ b/Minio/Minio.csproj @@ -12,13 +12,13 @@ - + - - + + diff --git a/Minio/MinioClient.cs b/Minio/MinioClient.cs index e96d84547..8bc6eb075 100644 --- a/Minio/MinioClient.cs +++ b/Minio/MinioClient.cs @@ -35,14 +35,14 @@ public partial class MinioClient : IMinioClient /// /// Default error handling delegate /// - private readonly ApiResponseErrorHandlingDelegate _defaultErrorHandlingDelegate = response => + private readonly ApiResponseErrorHandler _defaultErrorHandlingDelegate = response => { if (response.StatusCode < HttpStatusCode.OK || response.StatusCode >= HttpStatusCode.BadRequest) ParseError(response); }; - internal readonly IEnumerable NoErrorHandlers = - Enumerable.Empty(); + internal readonly IEnumerable NoErrorHandlers = + Enumerable.Empty(); private string CustomUserAgent = string.Empty; private bool disposedValue; @@ -60,7 +60,7 @@ public partial class MinioClient : IMinioClient internal int RequestTimeout; // Handler for task retry policy - internal RetryPolicyHandlingDelegate RetryPolicyHandler; + internal RetryPolicyHandler RetryPolicyHandler; // Enables HTTP tracing if set to true private bool trace; @@ -121,17 +121,17 @@ private static string SystemUserAgent /// /// Runs httpClient's GetAsync method /// - public Task WrapperGetAsync(string url) + public Task WrapperGetAsync(Uri uri) { - return HttpClient.GetAsync(url); + return HttpClient.GetAsync(uri); } /// /// Runs httpClient's PutObjectAsync method /// - public Task WrapperPutAsync(string url, StreamContent strm) + public Task WrapperPutAsync(Uri uri, StreamContent strm) { - return Task.Run(async () => await HttpClient.PutAsync(url, strm).ConfigureAwait(false)); + return Task.Run(async () => await HttpClient.PutAsync(uri, strm).ConfigureAwait(false)); } /// @@ -325,7 +325,7 @@ private void ArgsCheck(Args args) var resource = string.Empty; var usePathStyle = false; - if (bucketName is not null && S3utils.IsAmazonEndPoint(BaseUrl)) + if (!string.IsNullOrEmpty(bucketName) && S3utils.IsAmazonEndPoint(BaseUrl)) { if (method == HttpMethod.Put && objectName is null && resourcePath is null) // use path style for make bucket to workaround "AuthorizationHeaderMalformed" error from s3.amazonaws.com @@ -333,7 +333,7 @@ private void ArgsCheck(Args args) else if (resourcePath?.Contains("location") == true) // use path style for location query usePathStyle = true; - else if (bucketName?.Contains('.') == true && Secure) + else if (bucketName.Contains('.') && Secure) // use path style where '.' in bucketName causes SSL certificate validation error usePathStyle = true; @@ -379,7 +379,7 @@ private void ArgsCheck(Args args) /// Optional cancellation token to cancel the operation /// ResponseResult internal Task ExecuteTaskAsync( - IEnumerable errorHandlers, + IEnumerable errorHandlers, HttpRequestMessageBuilder requestMessageBuilder, bool isSts = false, CancellationToken cancellationToken = default) @@ -398,7 +398,7 @@ private void ArgsCheck(Args args) } private async Task ExecuteTaskCoreAsync( - IEnumerable errorHandlers, + IEnumerable errorHandlers, HttpRequestMessageBuilder requestMessageBuilder, bool isSts = false, CancellationToken cancellationToken = default) @@ -610,12 +610,14 @@ private static void ParseErrorFromContent(ResponseResult response) if (response.StatusCode.Equals(HttpStatusCode.NotImplemented) && errResponse.Code.Equals("NotImplemented", StringComparison.OrdinalIgnoreCase)) +#pragma warning disable MA0025 // Implement the functionality instead of throwing NotImplementedException throw new NotImplementedException(errResponse.Message); +#pragma warning restore MA0025 // Implement the functionality instead of throwing NotImplementedException if (response.StatusCode.Equals(HttpStatusCode.BadRequest) && errResponse.Code.Equals("InvalidRequest", StringComparison.OrdinalIgnoreCase)) { - var legalHold = new Dictionary { { "legal-hold", "" } }; + var legalHold = new Dictionary(StringComparer.Ordinal) { { "legal-hold", "" } }; if (response.Request.RequestUri.Query.Contains("legalHold")) throw new MissingObjectLockConfigurationException(errResponse.BucketName, errResponse.Message); } @@ -645,7 +647,7 @@ private static void ParseErrorFromContent(ResponseResult response) /// /// /// - private void HandleIfErrorResponse(ResponseResult response, IEnumerable handlers, + private void HandleIfErrorResponse(ResponseResult response, IEnumerable handlers, DateTime startTime) { // Logs Response if HTTP tracing is enabled @@ -681,7 +683,7 @@ private void LogRequest(HttpRequestMessage request, ResponseResult response, dou { Name = parameter.Key, Value = parameter.Value, - Type = parameter.GetType().ToString() + Type = typeof(KeyValuePair>).ToString() }), // ToString() here to have the method as a nice string otherwise it will just show the enum value Method = request.Method.ToString(), @@ -693,7 +695,8 @@ private void LogRequest(HttpRequestMessage request, ResponseResult response, dou { StatusCode = response.StatusCode, Content = response.Content, - Headers = response.Headers.ToDictionary(o => o.Key, o => string.Join(Environment.NewLine, o.Value)), + Headers = response.Headers.ToDictionary(o => o.Key, o => string.Join(Environment.NewLine, o.Value), + StringComparer.Ordinal), // The Uri that actually responded (could be different from the requestUri if a redirection occurred) ResponseUri = response.Request.RequestUri, ErrorMessage = response.ErrorMessage, @@ -723,7 +726,7 @@ protected virtual void Dispose(bool disposing) } } -internal delegate void ApiResponseErrorHandlingDelegate(ResponseResult response); +internal delegate void ApiResponseErrorHandler(ResponseResult response); -public delegate Task RetryPolicyHandlingDelegate( +public delegate Task RetryPolicyHandler( Func> executeRequestCallback); \ No newline at end of file diff --git a/Minio/MinioClientExtensions.cs b/Minio/MinioClientExtensions.cs index 9b1546ec4..813c1ba6f 100644 --- a/Minio/MinioClientExtensions.cs +++ b/Minio/MinioClientExtensions.cs @@ -1,4 +1,6 @@ -using System.Net; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Net; using Minio.Credentials; using Minio.DataModel; using Minio.Exceptions; @@ -22,7 +24,8 @@ public static MinioClient WithEndpoint(this MinioClient minioClient, string endp if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); if (port < 1 || port > 65535) - throw new ArgumentException(string.Format("Port {0} is not a number between 1 and 65535", port), + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, "Port {0} is not a number between 1 and 65535", port), nameof(port)); return minioClient.WithEndpoint(endpoint + ":" + port); } @@ -41,7 +44,8 @@ public static MinioClient WithRegion(this MinioClient minioClient, string region if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); if (string.IsNullOrEmpty(region)) - throw new ArgumentException(string.Format("{0} the region value can't be null or empty.", region), + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, "{0} the region value can't be null or empty.", region), nameof(region)); minioClient.Region = region; @@ -127,7 +131,7 @@ public static MinioClient WithTimeout(this MinioClient minioClient, int timeout) /// Delegate that will wrap execution of http client requests. /// public static MinioClient WithRetryPolicy(this MinioClient minioClient, - RetryPolicyHandlingDelegate retryPolicyHandler) + RetryPolicyHandler retryPolicyHandler) { if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); @@ -205,32 +209,58 @@ public static MinioClient Build(this MinioClient minioClient) var scheme = minioClient.Secure ? Utils.UrlEncode("https") : Utils.UrlEncode("http"); if (!minioClient.BaseUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - minioClient.Endpoint = string.Format("{0}://{1}", scheme, host); + minioClient.Endpoint = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, host); else minioClient.Endpoint = host; + var httpClientHandler = new HttpClientHandler { Proxy = minioClient.Proxy }; minioClient.HttpClient ??= minioClient.Proxy is null ? new HttpClient() - : new HttpClient(new HttpClientHandler { Proxy = minioClient.Proxy }); + : new HttpClient(httpClientHandler); minioClient.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", minioClient.FullUserAgent); + minioClient.HttpClient.Timeout = TimeSpan.FromMinutes(30); return minioClient; } + [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", + Justification = "This is done in the interface. String is provided here for convenience")] + public static Task WrapperGetAsync(this IMinioClient minioClient, string url) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + return minioClient.WrapperGetAsync(new Uri(url)); + } + + /// + /// Runs httpClient's PutObjectAsync method + /// + [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", + Justification = "This is done in the interface. String is provided here for convenience")] + public static Task WrapperPutAsync(this IMinioClient minioClient, string url, StreamContent strm) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + return minioClient.WrapperPutAsync(new Uri(url), strm); + } + internal static Uri GetBaseUrl(string endpoint) { if (string.IsNullOrEmpty(endpoint)) throw new ArgumentException( - string.Format("{0} is the value of the endpoint. It can't be null or empty.", endpoint), + string.Format(CultureInfo.InvariantCulture, + "{0} is the value of the endpoint. It can't be null or empty.", endpoint), nameof(endpoint)); if (endpoint.EndsWith("/", StringComparison.OrdinalIgnoreCase)) endpoint = endpoint.Substring(0, endpoint.Length - 1); if (!BuilderUtil.IsValidHostnameOrIPAddress(endpoint)) - throw new InvalidEndpointException(string.Format("{0} is invalid hostname.", endpoint), "endpoint"); + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0} is invalid hostname.", endpoint), "endpoint"); string conn_url; if (endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase)) throw new InvalidEndpointException( - string.Format("{0} the value of the endpoint has the scheme (http/https) in it.", endpoint), + string.Format(CultureInfo.InvariantCulture, + "{0} the value of the endpoint has the scheme (http/https) in it.", endpoint), "endpoint"); var enable_https = Environment.GetEnvironmentVariable("ENABLE_HTTPS"); @@ -239,7 +269,8 @@ internal static Uri GetBaseUrl(string endpoint) var url = new Uri(conn_url); var hostnameOfUri = url.Authority; if (!string.IsNullOrEmpty(hostnameOfUri) && !BuilderUtil.IsValidHostnameOrIPAddress(hostnameOfUri)) - throw new InvalidEndpointException(string.Format("{0}, {1} is invalid hostname.", endpoint, hostnameOfUri), + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0}, {1} is invalid hostname.", endpoint, hostnameOfUri), "endpoint"); return url; diff --git a/Minio/Regions.cs b/Minio/Regions.cs index 79f24bfe6..757f761dc 100644 --- a/Minio/Regions.cs +++ b/Minio/Regions.cs @@ -22,9 +22,10 @@ namespace Minio; public static class Regions { private static readonly Regex endpointRegex = new(@"s3[.\-](.*?)\.amazonaws\.com$", - RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.RightToLeft); + RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.RightToLeft, + TimeSpan.FromHours(1)); - private static readonly ConcurrentDictionary cache = new(); + private static readonly ConcurrentDictionary cache = new(StringComparer.Ordinal); /// /// Get corresponding region for input host. diff --git a/Minio/ResponseResult.cs b/Minio/ResponseResult.cs index 70157a1d5..0c636607e 100644 --- a/Minio/ResponseResult.cs +++ b/Minio/ResponseResult.cs @@ -22,7 +22,7 @@ namespace Minio; public class ResponseResult : IDisposable { - private readonly Dictionary _headers = new(); + private readonly Dictionary _headers = new(StringComparer.Ordinal); private string _content; private ReadOnlyMemory _contentBytes; @@ -104,7 +104,7 @@ public string Content { get { - if (Response is null) return new Dictionary(); + if (Response is null) return new Dictionary(StringComparer.Ordinal); if (!_headers.Any()) { diff --git a/Minio/V4Authenticator.cs b/Minio/V4Authenticator.cs index 35f5c5d77..dfe285af7 100644 --- a/Minio/V4Authenticator.cs +++ b/Minio/V4Authenticator.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -332,7 +333,7 @@ public string PresignPostSignature(string region, DateTime signingDate, string p // Return presigned url. var signedUri = new UriBuilder(presignUri) { Query = $"{requestQuery}{headers}&X-Amz-Signature={signature}" }; if (signedUri.Uri.IsDefaultPort) signedUri.Port = -1; - return Convert.ToString(signedUri); + return Convert.ToString(signedUri, CultureInfo.InvariantCulture); } /// @@ -390,7 +391,7 @@ private static string GetCanonicalHost(Uri url) // METHOD canonicalStringList.AddLast(requestBuilder.Method.ToString()); - var queryParamsDict = new Dictionary(); + var queryParamsDict = new Dictionary(StringComparer.Ordinal); if (requestBuilder.QueryParameters is not null) foreach (var kvp in requestBuilder.QueryParameters) queryParamsDict[kvp.Key] = Uri.EscapeDataString(kvp.Value); @@ -405,7 +406,7 @@ private static string GetCanonicalHost(Uri url) { if (sb1.Length > 0) sb1.Append('&'); - sb1.AppendFormat("{0}={1}", p, queryParamsDict[p]); + sb1.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}", p, queryParamsDict[p]); } queryParams = sb1.ToString(); @@ -556,7 +557,9 @@ private void SetContentSha256(HttpRequestMessageBuilder requestBuilder, bool isS ReadOnlySpan bytes = Encoding.UTF8.GetBytes(requestBuilder.Content.ToString()); #if NETSTANDARD +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms using var md5 = MD5.Create(); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms var hash = md5.ComputeHash(bytes.ToArray()); #else ReadOnlySpan hash = MD5.HashData(bytes);