Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed string to sign sorting #45105

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
seanmcc-msft marked this conversation as resolved.
Show resolved Hide resolved
{
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,
};
vincenttran-msft marked this conversation as resolved.
Show resolved Hide resolved

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;
seanmcc-msft marked this conversation as resolved.
Show resolved Hide resolved
}
}

return 0;
}
}
}
}
Loading