diff --git a/sdk/storage/azure-storage-blobs/src/blob_batch_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_batch_client.cpp index 46abbc27e..ddbd4a38f 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_batch_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_batch_client.cpp @@ -239,8 +239,8 @@ namespace Azure { namespace Storage { namespace Blobs { requestBody += getBatchBoundary(); auto blobUrl = m_serviceUrl; - blobUrl.AppendPath(subrequest.ContainerName); - blobUrl.AppendPath(subrequest.BlobName); + blobUrl.AppendPath(Details::UrlEncodePath(subrequest.ContainerName)); + blobUrl.AppendPath(Details::UrlEncodePath(subrequest.BlobName)); BlobRestClient::Blob::DeleteBlobOptions protocolLayerOptions; protocolLayerOptions.DeleteSnapshots = subrequest.Options.DeleteSnapshots; protocolLayerOptions.IfModifiedSince = subrequest.Options.AccessConditions.IfModifiedSince; @@ -261,8 +261,8 @@ namespace Azure { namespace Storage { namespace Blobs { requestBody += getBatchBoundary(); auto blobUrl = m_serviceUrl; - blobUrl.AppendPath(subrequest.ContainerName); - blobUrl.AppendPath(subrequest.BlobName); + blobUrl.AppendPath(Details::UrlEncodePath(subrequest.ContainerName)); + blobUrl.AppendPath(Details::UrlEncodePath(subrequest.BlobName)); BlobRestClient::Blob::SetBlobAccessTierOptions protocolLayerOptions; protocolLayerOptions.Tier = subrequest.Tier; protocolLayerOptions.RehydratePriority = subrequest.Options.RehydratePriority; diff --git a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp index 53cd1dfe2..8e3355c8b 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp @@ -23,7 +23,7 @@ namespace Azure { namespace Storage { namespace Blobs { { auto parsedConnectionString = Details::ParseConnectionString(connectionString); auto containerUri = std::move(parsedConnectionString.BlobServiceUri); - containerUri.AppendPath(containerName); + containerUri.AppendPath(Details::UrlEncodePath(containerName)); if (parsedConnectionString.KeyCredential) { @@ -117,7 +117,7 @@ namespace Azure { namespace Storage { namespace Blobs { BlobClient BlobContainerClient::GetBlobClient(const std::string& blobName) const { auto blobUri = m_containerUrl; - blobUri.AppendPath(blobName); + blobUri.AppendPath(Details::UrlEncodePath(blobName)); return BlobClient(std::move(blobUri), m_pipeline, m_customerProvidedKey, m_encryptionScope); } diff --git a/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp b/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp index fb914a16b..d0885d230 100644 --- a/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT -#include "blob_container_client_test.hpp" -#include "azure/storage/blobs/blob_sas_builder.hpp" -#include "azure/storage/common/crypt.hpp" - #include #include +#include "azure/storage/blobs/blob_sas_builder.hpp" +#include "azure/storage/common/crypt.hpp" +#include "blob_container_client_test.hpp" + namespace Azure { namespace Storage { namespace Blobs { bool operator==( @@ -1048,4 +1048,39 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(BlobContainerClientTest, SpecialBlobName) + { + const std::string non_ascii_word = "\xE6\xB5\x8B\xE8\xAF\x95"; + const std::string encoded_non_ascii_word = "%E6%B5%8B%E8%AF%95"; + std::string baseBlobName = "a b c / !@#$%^&*() def" + non_ascii_word; + + { + std::string blobName = baseBlobName + RandomString(); + auto blobClient = m_blobContainerClient->GetAppendBlobClient(blobName); + EXPECT_NO_THROW(blobClient.Create()); + auto blobUrl = blobClient.GetUri(); + EXPECT_EQ( + blobUrl, + m_blobContainerClient->GetUri() + "/" + Storage::Details::UrlEncodePath(blobName)); + } + { + std::string blobName = baseBlobName + RandomString(); + auto blobClient = m_blobContainerClient->GetPageBlobClient(blobName); + EXPECT_NO_THROW(blobClient.Create(1024)); + auto blobUrl = blobClient.GetUri(); + EXPECT_EQ( + blobUrl, + m_blobContainerClient->GetUri() + "/" + Storage::Details::UrlEncodePath(blobName)); + } + { + std::string blobName = baseBlobName + RandomString(); + auto blobClient = m_blobContainerClient->GetBlockBlobClient(blobName); + EXPECT_NO_THROW(blobClient.UploadFrom(nullptr, 0)); + auto blobUrl = blobClient.GetUri(); + EXPECT_EQ( + blobUrl, + m_blobContainerClient->GetUri() + "/" + Storage::Details::UrlEncodePath(blobName)); + } + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-common/inc/azure/storage/common/crypt.hpp b/sdk/storage/azure-storage-common/inc/azure/storage/common/crypt.hpp index 6ff40ca1b..ff7f90011 100644 --- a/sdk/storage/azure-storage-common/inc/azure/storage/common/crypt.hpp +++ b/sdk/storage/azure-storage-common/inc/azure/storage/common/crypt.hpp @@ -64,5 +64,6 @@ namespace Azure { namespace Storage { std::string Sha256(const std::string& text); std::string HmacSha256(const std::string& text, const std::string& key); std::string UrlEncodeQueryParameter(const std::string& value); + std::string UrlEncodePath(const std::string& value); } // namespace Details }} // namespace Azure::Storage diff --git a/sdk/storage/azure-storage-common/src/crypt.cpp b/sdk/storage/azure-storage-common/src/crypt.cpp index e50747708..9a74ec2a2 100644 --- a/sdk/storage/azure-storage-common/src/crypt.cpp +++ b/sdk/storage/azure-storage-common/src/crypt.cpp @@ -23,15 +23,16 @@ namespace Azure { namespace Storage { namespace Details { + static const char* c_unreserved + = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; + static const char* c_subdelimiters = "!$&'()*+,;="; + const char* c_hex = "0123456789ABCDEF"; + std::string UrlEncodeQueryParameter(const std::string& value) { - static const char* unreserved - = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; - static const char* subdelimiters = "!$&'()*+,;="; - const static std::vector shouldEncodeTable = []() { std::string queryCharacters - = std::string(unreserved) + std::string(subdelimiters) + "%/:@?"; + = std::string(c_unreserved) + std::string(c_subdelimiters) + "%/:@?"; std::vector ret(256, true); for (char c : queryCharacters) @@ -48,7 +49,40 @@ namespace Azure { namespace Storage { return ret; }(); - const char* hex = "0123456789ABCDEF"; + std::string encoded; + for (char c : value) + { + unsigned char uc = c; + if (shouldEncodeTable[uc]) + { + encoded += '%'; + encoded += c_hex[(uc >> 4) & 0x0f]; + encoded += c_hex[uc & 0x0f]; + } + else + { + encoded += c; + } + } + return encoded; + } + + std::string UrlEncodePath(const std::string& value) + { + const static std::vector shouldEncodeTable = []() { + std::string pathCharacters + = std::string(c_unreserved) + std::string(c_subdelimiters) + "%/:@"; + + std::vector ret(256, true); + for (char c : pathCharacters) + { + ret[c] = false; + } + // we also encode % and + + ret['%'] = true; + ret['+'] = true; + return ret; + }(); std::string encoded; for (char c : value) @@ -57,8 +91,8 @@ namespace Azure { namespace Storage { if (shouldEncodeTable[uc]) { encoded += '%'; - encoded += hex[(uc >> 4) & 0x0f]; - encoded += hex[uc & 0x0f]; + encoded += c_hex[(uc >> 4) & 0x0f]; + encoded += c_hex[uc & 0x0f]; } else {