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
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_d12c64a03e"
}
49 changes: 49 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,55 @@ 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 CreateSnapshotAsync()
{
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 -1;
seanmcc-msft marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}