Skip to content

Commit

Permalink
Fixed string to sign sorting (#45105)
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmcc-msft authored Jul 25, 2024
1 parent 049389b commit 203f6cd
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 11 deletions.
2 changes: 1 addition & 1 deletion sdk/storage/Azure.Storage.Blobs/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/storage/Azure.Storage.Blobs",
"Tag": "net/storage/Azure.Storage.Blobs_be75b1430c"
"Tag": "net/storage/Azure.Storage.Blobs_14eb1d6279"
}
77 changes: 77 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5110,6 +5110,83 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
e => Assert.AreEqual("BlobNotFound", e.ErrorCode));
}

[RecordedTest]
public async Task SetMetadataAsync_Sort()
{
await using DisposingContainer test = await GetTestContainerAsync();
// Arrange
BlobBaseClient blob = await GetNewBlobClient(test.Container);
IDictionary<string, string> metadata = new Dictionary<string, string>() {
{ "a0", "a" },
{ "a1", "a" },
{ "a2", "a" },
{ "a3", "a" },
{ "a4", "a" },
{ "a5", "a" },
{ "a6", "a" },
{ "a7", "a" },
{ "a8", "a" },
{ "a9", "a" },
{ "_", "a" },
{ "_a", "a" },
{ "a_", "a" },
{ "__", "a" },
{ "_a_", "a" },
{ "b", "a" },
{ "c", "a" },
{ "y", "a" },
{ "z", "z_" },
{ "_z", "a" },
{ "_F", "a" },
{ "F", "a" },
{ "F_", "a" },
{ "_F_", "a" },
{ "__F", "a" },
{ "__a", "a" },
{ "a__", "a" }
};

// Act
Response<BlobInfo> response = await blob.SetMetadataAsync(metadata);

// Assert

// Ensure that we grab the whole ETag value from the service without removing the quotes
Assert.AreEqual(response.Value.ETag.ToString(), $"\"{response.GetRawResponse().Headers.ETag}\"");

// Ensure the value has been correctly set by doing a GetProperties call
Response<BlobProperties> getPropertiesResponse = await blob.GetPropertiesAsync();
AssertDictionaryEquality(metadata, getPropertiesResponse.Value.Metadata);
}

[RecordedTest]
public async Task SetMetadataAsync_Sort_InvalidMetadata()
{
await using DisposingContainer test = await GetTestContainerAsync();
// Arrange
BlobBaseClient blob = await GetNewBlobClient(test.Container);
IDictionary<string, string> metadata = new Dictionary<string, string>() {
{ "test", "val" },
{ "test-", "val" },
{ "test--", "val" },
{ "test-_", "val" },
{ "test_-", "val" },
{ "test__", "val" },
{ "test-a", "val" },
{ "test-_A", "val" },
{ "test_a", "val" },
{ "test_Z", "val" },
{ "test_a_", "val" },
{ "test_a-", "val" },
{ "test_a-_", "val" },
};

// Act
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
blob.SetMetadataAsync(metadata),
e => Assert.AreEqual(BlobErrorCode.InvalidMetadata.ToString(), e.ErrorCode));
}

[RecordedTest]
public async Task CreateSnapshotAsync()
{
Expand Down
1 change: 1 addition & 0 deletions sdk/storage/Azure.Storage.Common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Breaking Changes

### Bugs Fixed
- Fixed \[BUG\] Azure Blob Storage Client SDK No Longer Supports Globalization Invariant Mode for Account Key Authentication #45052

### Other Changes

Expand Down
1 change: 0 additions & 1 deletion sdk/storage/Azure.Storage.Common/src/Shared/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ internal static class Constants
public const string DisableExpectContinueHeaderEnvVar = "AZURE_STORAGE_DISABLE_EXPECT_CONTINUE_HEADER";

public const string DefaultScope = "/.default";
public const string EnUsCulture = "en-US";

/// <summary>
/// Storage Connection String constant values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ internal sealed class StorageSharedKeyPipelinePolicy : HttpPipelineSynchronousPo
private const bool IncludeXMsDate = true;

/// <summary>
/// CultureInfo used to sort headers in the string to sign.
/// Shared key credentials used to sign requests
/// </summary>
private static readonly CultureInfo s_cultureInfo = new CultureInfo(Constants.EnUsCulture, useUserOverride: false);
private readonly StorageSharedKeyCredential _credentials;

/// <summary>
/// Shared key credentials used to sign requests
/// Used to sort headers to build the string to sign.
/// </summary>
private readonly StorageSharedKeyCredential _credentials;
private static readonly HeaderComparer s_headerComparer = new HeaderComparer();

/// <summary>
/// Create a new SharedKeyPipelinePolicy
/// </summary>
/// <param name="credentials">SharedKeyCredentials to authenticate requests.</param>
public StorageSharedKeyPipelinePolicy(StorageSharedKeyCredential credentials)
=> _credentials = credentials;
{
_credentials = credentials;
}

/// <summary>
/// Sign the request using the shared key credentials.
Expand Down Expand Up @@ -117,10 +119,7 @@ private static void BuildCanonicalizedHeaders(StringBuilder stringBuilder, HttpM
}
}

headers.Sort(static (x, y) =>
{;
return string.Compare(x.Name, y.Name, s_cultureInfo, CompareOptions.IgnoreSymbols);
});
headers.Sort(s_headerComparer);

foreach (var header in headers)
{
Expand Down Expand Up @@ -162,5 +161,102 @@ private void BuildCanonicalizedResource(StringBuilder stringBuilder, Uri resourc
}
}
}

internal class HeaderComparer : IComparer<HttpHeader>
{
private static readonly HeaderStringComparer s_headerComparer = new HeaderStringComparer();

public int Compare(HttpHeader x, HttpHeader y)
{
return s_headerComparer.Compare(x.Name, y.Name);
}
}

internal class HeaderStringComparer : IComparer<string>
{
private static readonly int[] s_table_lv0 =
{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71c, 0x0, 0x71f, 0x721, 0x723, 0x725,
0x0, 0x0, 0x0, 0x72d, 0x803, 0x0, 0x0, 0x733, 0x0, 0xd03, 0xd1a, 0xd1c, 0xd1e,
0xd20, 0xd22, 0xd24, 0xd26, 0xd28, 0xd2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25, 0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51,
0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99, 0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9,
0x0, 0x0, 0x0, 0x743, 0x744, 0x748, 0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25,
0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51, 0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99,
0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9, 0x0, 0x74c, 0x0, 0x750, 0x0,
};

private static readonly int[] s_table_lv2 =
{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
};

private static readonly int[] s_table_lv4 =
{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8212, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
};

private static readonly int[][] s_tables = { s_table_lv0, s_table_lv2, s_table_lv4 };

public int Compare(string x, string y)
{
int currentLevel = 0;
int i = 0;
int j = 0;

while (currentLevel < s_tables.Length)
{
if (currentLevel == s_tables.Length - 1 && i != j)
{
return j - i;
}

int weight1 = i < x.Length ? s_tables[currentLevel][x[i]] : 0x1;
int weight2 = j < y.Length ? s_tables[currentLevel][y[j]] : 0x1;

if (weight1 == 0x1 && weight2 == 0x1)
{
i = 0;
j = 0;
currentLevel++;
}
else if (weight1 == weight2)
{
i++;
j++;
}
else if (weight1 == 0)
{
i++;
}
else if (weight2 == 0)
{
j++;
}
else
{
return weight1 - weight2;
}
}

return 0;
}
}
}
}

0 comments on commit 203f6cd

Please sign in to comment.