From 07208d172091671c99fa7fe3d75ea063af1b535e Mon Sep 17 00:00:00 2001 From: JinmingHu Date: Fri, 4 Sep 2020 14:46:24 +0800 Subject: [PATCH] [Storage Files Service] File sas (#568) * File sas * rebase on lastest master --- .../azure/storage/blobs/blob_sas_builder.hpp | 20 +- .../storage/common/storage_credential.hpp | 4 + .../azure-storage-files-shares/CMakeLists.txt | 3 + .../files/shares/share_sas_builder.hpp | 240 ++++++++++++++++++ .../src/share_sas_builder.cpp | 145 +++++++++++ .../test/share_sas_test.cpp | 221 ++++++++++++++++ 6 files changed, 620 insertions(+), 13 deletions(-) create mode 100644 sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_sas_builder.hpp create mode 100644 sdk/storage/azure-storage-files-shares/src/share_sas_builder.cpp create mode 100644 sdk/storage/azure-storage-files-shares/test/share_sas_test.cpp diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp index 4b5869fa7..2769a0d50 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp @@ -182,7 +182,7 @@ namespace Azure { namespace Storage { namespace Blobs { * shared access signature, and the service version to use when handling requests made with this * shared access signature. */ - std::string Version = c_ApiVersion; + std::string Version = Details::c_defaultSasVersion; /** * @brief The optional signed protocol field specifies the protocol permitted for a @@ -271,8 +271,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Sets the permissions for the blob container SAS. * - * @param - * permissions The allowed permissions. + * @param permissions The allowed permissions. */ void SetPermissions(BlobContainerSasPermissions permissions) { @@ -282,8 +281,7 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Sets the permissions for the blob SAS. * - * @param permissions The - * allowed permissions. + * @param permissions The allowed permissions. */ void SetPermissions(BlobSasPermissions permissions); @@ -291,10 +289,8 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Uses the SharedKeyCredential to sign this shared access signature, to produce * the proper SAS query parameters for authentication requests. * - * @param credential - * The storage account's shared key credential. - * @return The SAS query parameters used for - * authenticating requests. + * @param credential The storage account's shared key credential. + * @return The SAS query parameters used for authenticating requests. */ std::string ToSasQueryParameters(const SharedKeyCredential& credential); @@ -302,11 +298,9 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Uses an account's user delegation key to sign this shared access signature, to * produce the proper SAS query parameters for authentication requests. * - * @param - * credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey. + * @param credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey. * @param accountName The name of the storage account. - * @return The SAS query parameters - * used for authenticating requests. + * @return The SAS query parameters used for authenticating requests. */ std::string ToSasQueryParameters( const UserDelegationKey& userDelegationKey, diff --git a/sdk/storage/azure-storage-common/inc/azure/storage/common/storage_credential.hpp b/sdk/storage/azure-storage-common/inc/azure/storage/common/storage_credential.hpp index ab419d98d..7751be1cd 100644 --- a/sdk/storage/azure-storage-common/inc/azure/storage/common/storage_credential.hpp +++ b/sdk/storage/azure-storage-common/inc/azure/storage/common/storage_credential.hpp @@ -16,6 +16,9 @@ namespace Azure { namespace Storage { namespace Blobs { struct BlobSasBuilder; } + namespace Files { namespace Shares { + struct ShareSasBuilder; + }} // namespace Files::Shares struct SharedKeyCredential { @@ -35,6 +38,7 @@ namespace Azure { namespace Storage { private: friend class SharedKeyPolicy; friend struct Blobs::BlobSasBuilder; + friend struct Files::Shares::ShareSasBuilder; friend struct AccountSasBuilder; std::string GetAccountKey() const { diff --git a/sdk/storage/azure-storage-files-shares/CMakeLists.txt b/sdk/storage/azure-storage-files-shares/CMakeLists.txt index 3a1699596..01cd6f723 100644 --- a/sdk/storage/azure-storage-files-shares/CMakeLists.txt +++ b/sdk/storage/azure-storage-files-shares/CMakeLists.txt @@ -12,6 +12,7 @@ set (AZURE_STORAGE_SHARES_HEADER inc/azure/storage/files/shares/share_file_client.hpp inc/azure/storage/files/shares/share_options.hpp inc/azure/storage/files/shares/share_responses.hpp + inc/azure/storage/files/shares/share_sas_builder.hpp inc/azure/storage/files/shares/share_service_client.hpp inc/azure/storage/files/shares/shares.hpp ) @@ -20,6 +21,7 @@ set (AZURE_STORAGE_SHARES_SOURCE src/share_client.cpp src/share_directory_client.cpp src/share_file_client.cpp + src/share_sas_builder.cpp src/share_service_client.cpp ) @@ -47,6 +49,7 @@ target_sources( test/share_directory_client_test.hpp test/share_file_client_test.cpp test/share_file_client_test.hpp + test/share_sas_test.cpp test/share_service_client_test.cpp test/share_service_client_test.hpp ) diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_sas_builder.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_sas_builder.hpp new file mode 100644 index 000000000..e34bd7b4a --- /dev/null +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_sas_builder.hpp @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "azure/core/nullable.hpp" +#include "azure/storage/common/account_sas_builder.hpp" +#include "azure/storage/common/constants.hpp" + +namespace Azure { namespace Storage { namespace Files { namespace Shares { + + /** + * @brief Specifies which resources are accessible via the shared access signature. + */ + enum class ShareSasResource + { + /** + * @brief Grants access to the content and metadata of the file. + */ + Share, + + /** + * @brief Grants access to the content and metadata of any file in the share, and to the list of + * directories and files in the share. + */ + File, + }; + + /** + * @brief The list of permissions that can be set for a file share's access policy. + */ + enum class ShareSasPermissions + { + /** + * @brief Indicates that Read is permitted. + */ + Read = 1, + + /** + * @brief Indicates that Write is permitted. + */ + Write = 2, + + /** + * @brief Indicates that Delete is permitted. + */ + Delete = 4, + + /** + * @brief Indicates that List is permitted. + */ + List = 8, + + /** + * @brief Indicates that Create is permitted. + */ + Create = 16, + + /** + * @beirf Indicates that all permissions are set. + */ + All = ~0, + }; + + inline ShareSasPermissions operator|(ShareSasPermissions lhs, ShareSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline ShareSasPermissions operator&(ShareSasPermissions lhs, ShareSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + std::string ShareSasPermissionsToString(ShareSasPermissions permissions); + + /** + * @brief The list of permissions that can be set for a share file's access policy. + */ + enum class ShareFileSasPermissions + { + /** + * @brief Indicates that Read is permitted. + */ + Read = 1, + + /** + * @brief Indicates that Write is permitted. + */ + Write = 2, + + /** + * @brief Indicates that Delete is permitted. + */ + + Delete = 4, + + /** + * @brief Indicates that Create is permitted. + */ + Create = 8, + + /** + * @beirf Indicates that all permissions are set. + */ + All = ~0, + }; + + inline ShareFileSasPermissions operator|(ShareFileSasPermissions lhs, ShareFileSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline ShareFileSasPermissions operator&(ShareFileSasPermissions lhs, ShareFileSasPermissions rhs) + { + using type = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /** + * @brief ShareSasBuilder is used to generate a Shared Access Signature (SAS) for an Azure + * Storage share or file. + */ + struct ShareSasBuilder + { + /** + * @brief The storage service version to use to authenticate requests made with this + * shared access signature, and the service version to use when handling requests made with this + * shared access signature. + */ + std::string Version = Azure::Storage::Details::c_defaultSasVersion; + + /** + * @brief The optional signed protocol field specifies the protocol permitted for a + * request made with the SAS. + */ + SasProtocol Protocol; + + /** + * @brief Optionally specify the time at which the shared access signature becomes + * valid. + */ + Azure::Core::Nullable StartsOn; + + /** + * @brief The time at which the shared access signature becomes invalid. This field must + * be omitted if it has been specified in an associated stored access policy. + */ + std::string ExpiresOn; + + /** + * @brief Specifies an IP address or a range of IP addresses from which to accept + * requests. If the IP address from which the request originates does not match the IP address + * or address range specified on the SAS token, the request is not authenticated. When + * specifying a range of IP addresses, note that the range is inclusive. + */ + Azure::Core::Nullable IPRange; + + /** + * @brief An optional unique value up to 64 characters in length that correlates to an + * access policy specified for the share. + */ + std::string Identifier; + + /** + * @brief The name of the file share being made accessible. + */ + std::string ShareName; + + /** + * @brief The name of the share file being made accessible, or empty for a share SAS.. + */ + std::string FilePath; + + /** + * @brief Specifies which resources are accessible via the shared access signature. + */ + ShareSasResource Resource; + + /** + * @brief Override the value returned for Cache-Control response header.. + */ + std::string CacheControl; + + /** + * @brief Override the value returned for Content-Disposition response header.. + */ + std::string ContentDisposition; + + /** + * @brief Override the value returned for Content-Encoding response header.. + */ + std::string ContentEncoding; + + /** + * @brief Override the value returned for Content-Language response header.. + */ + std::string ContentLanguage; + + /** + * @brief Override the value returned for Content-Type response header.. + */ + std::string ContentType; + + /** + * @brief Sets the permissions for the share SAS. + * + * @param permissions The allowed permissions. + */ + void SetPermissions(ShareSasPermissions permissions) + { + Permissions = ShareSasPermissionsToString(permissions); + } + + /** + * @brief Sets the permissions for the share SAS. + * + * @param permissions The allowed permissions. + */ + void SetPermissions(ShareFileSasPermissions permissions); + + /** + * @brief Uses the SharedKeyCredential to sign this shared access signature, to produce + * the proper SAS query parameters for authentication requests. + * + * @param credential The storage account's shared key credential. + * @return The SAS query parameters used for authenticating requests. + */ + std::string ToSasQueryParameters(const SharedKeyCredential& credential); + + private: + std::string Permissions; + }; + +}}}} // namespace Azure::Storage::Files::Shares diff --git a/sdk/storage/azure-storage-files-shares/src/share_sas_builder.cpp b/sdk/storage/azure-storage-files-shares/src/share_sas_builder.cpp new file mode 100644 index 000000000..633ac78de --- /dev/null +++ b/sdk/storage/azure-storage-files-shares/src/share_sas_builder.cpp @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/storage/files/shares/share_sas_builder.hpp" +#include "azure/core/http/http.hpp" +#include "azure/storage/common/crypt.hpp" + +namespace Azure { namespace Storage { namespace Files { namespace Shares { + + namespace { + std::string ShareSasResourceToString(ShareSasResource resource) + { + if (resource == ShareSasResource::Share) + { + return "s"; + } + else if (resource == ShareSasResource::File) + { + return "f"; + } + else + { + throw std::runtime_error("unknown ShareSasResource value"); + } + } + } // namespace + + std::string ShareSasPermissionsToString(ShareSasPermissions permissions) + { + std::string permissions_str; + // The order matters + if ((permissions & ShareSasPermissions::Read) == ShareSasPermissions::Read) + { + permissions_str += "r"; + } + if ((permissions & ShareSasPermissions::Create) == ShareSasPermissions::Create) + { + permissions_str += "c"; + } + if ((permissions & ShareSasPermissions::Write) == ShareSasPermissions::Write) + { + permissions_str += "w"; + } + if ((permissions & ShareSasPermissions::Delete) == ShareSasPermissions::Delete) + { + permissions_str += "d"; + } + if ((permissions & ShareSasPermissions::List) == ShareSasPermissions::List) + { + permissions_str += "l"; + } + return permissions_str; + } + + void ShareSasBuilder::SetPermissions(ShareFileSasPermissions permissions) + { + Permissions.clear(); + // The order matters + if ((permissions & ShareFileSasPermissions::Read) == ShareFileSasPermissions::Read) + { + Permissions += "r"; + } + if ((permissions & ShareFileSasPermissions::Create) == ShareFileSasPermissions::Create) + { + Permissions += "c"; + } + if ((permissions & ShareFileSasPermissions::Write) == ShareFileSasPermissions::Write) + { + Permissions += "w"; + } + if ((permissions & ShareFileSasPermissions::Delete) == ShareFileSasPermissions::Delete) + { + Permissions += "d"; + } + } + + std::string ShareSasBuilder::ToSasQueryParameters(const SharedKeyCredential& credential) + { + std::string canonicalName = "/file/" + credential.AccountName + "/" + ShareName; + if (Resource == ShareSasResource::File) + { + canonicalName += "/" + FilePath; + } + std::string protocol = SasProtocolToString(Protocol); + std::string resource = ShareSasResourceToString(Resource); + + std::string stringToSign = Permissions + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "") + + "\n" + ExpiresOn + "\n" + canonicalName + "\n" + Identifier + "\n" + + (IPRange.HasValue() ? IPRange.GetValue() : "") + "\n" + protocol + "\n" + Version + "\n" + + CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + + "\n" + ContentType; + + std::string signature + = Base64Encode(Details::HmacSha256(stringToSign, Base64Decode(credential.GetAccountKey()))); + + Azure::Core::Http::Url builder; + builder.AppendQuery("sv", Version); + builder.AppendQuery("spr", protocol); + if (StartsOn.HasValue()) + { + builder.AppendQuery("st", StartsOn.GetValue()); + } + if (!ExpiresOn.empty()) + { + builder.AppendQuery("se", ExpiresOn); + } + if (IPRange.HasValue()) + { + builder.AppendQuery("sip", IPRange.GetValue()); + } + if (!Identifier.empty()) + { + builder.AppendQuery("si", Identifier); + } + builder.AppendQuery("sr", resource); + if (!Permissions.empty()) + { + builder.AppendQuery("sp", Permissions); + } + builder.AppendQuery("sig", signature, true); + if (!CacheControl.empty()) + { + builder.AppendQuery("rscc", CacheControl); + } + if (!ContentDisposition.empty()) + { + builder.AppendQuery("rscd", ContentDisposition); + } + if (!ContentEncoding.empty()) + { + builder.AppendQuery("rsce", ContentEncoding); + } + if (!ContentLanguage.empty()) + { + builder.AppendQuery("rscl", ContentLanguage); + } + if (!ContentType.empty()) + { + builder.AppendQuery("rsct", ContentType); + } + + return builder.GetAbsoluteUrl(); + } + +}}}} // namespace Azure::Storage::Files::Shares diff --git a/sdk/storage/azure-storage-files-shares/test/share_sas_test.cpp b/sdk/storage/azure-storage-files-shares/test/share_sas_test.cpp new file mode 100644 index 000000000..25c087f11 --- /dev/null +++ b/sdk/storage/azure-storage-files-shares/test/share_sas_test.cpp @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "share_client_test.hpp" + +#include "azure/storage/files/shares/share_sas_builder.hpp" + +namespace Azure { namespace Storage { namespace Test { + + TEST_F(FileShareClientTest, FileSasTest) + { + std::string fileName = RandomString(); + Files::Shares::ShareSasBuilder fileSasBuilder; + fileSasBuilder.Protocol = SasProtocol::HttpsAndHtttp; + fileSasBuilder.StartsOn = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + fileSasBuilder.ExpiresOn + = ToIso8601(std::chrono::system_clock::now() + std::chrono::minutes(60)); + fileSasBuilder.ShareName = m_shareName; + fileSasBuilder.FilePath = fileName; + fileSasBuilder.Resource = Files::Shares::ShareSasResource::File; + + Files::Shares::ShareSasBuilder shareSasBuilder = fileSasBuilder; + shareSasBuilder.FilePath.clear(); + shareSasBuilder.Resource = Files::Shares::ShareSasResource::Share; + + auto keyCredential + = Details::ParseConnectionString(StandardStorageConnectionString()).KeyCredential; + auto accountName = keyCredential->AccountName; + auto fileServiceClient0 = Files::Shares::ServiceClient::CreateFromConnectionString( + StandardStorageConnectionString()); + auto shareClient0 = fileServiceClient0.GetShareClient(m_shareName); + auto fileClient0 = shareClient0.GetFileClient(fileName); + + std::string shareUri = shareClient0.GetUri(); + std::string fileUri = fileClient0.GetUri(); + + auto verifyFileRead = [&](const std::string& sas) { + int64_t fileSize = 512; + fileClient0.Create(fileSize); + auto fileClient = Files::Shares::FileClient(fileUri + sas); + auto downloadedContent = fileClient.Download(); + EXPECT_EQ( + ReadBodyStream(downloadedContent->BodyStream).size(), static_cast(fileSize)); + }; + + auto verifyFileCreate = [&](const std::string& sas) { + int64_t fileSize = 512; + auto fileClient = Files::Shares::FileClient(fileUri + sas); + EXPECT_NO_THROW(fileClient.Create(fileSize)); + }; + + auto verifyFileWrite = [&](const std::string& sas) { + int64_t fileSize = 512; + fileClient0.Create(fileSize); + auto fileClient = Files::Shares::FileClient(fileUri + sas); + std::string fileContent = "a"; + EXPECT_NO_THROW(fileClient.UploadFrom( + reinterpret_cast(fileContent.data()), fileContent.size())); + }; + + auto verifyFileDelete = [&](const std::string& sas) { + int64_t fileSize = 512; + fileClient0.Create(fileSize); + auto fileClient = Files::Shares::FileClient(fileUri + sas); + EXPECT_NO_THROW(fileClient.Delete()); + }; + + auto verifyFileList = [&](const std::string& sas) { + auto shareClient = Files::Shares::ShareClient(shareUri + sas); + EXPECT_NO_THROW(shareClient.ListFilesAndDirectoriesSegment()); + }; + + for (auto permissions : + {Files::Shares::ShareSasPermissions::Read, + Files::Shares::ShareSasPermissions::Write, + Files::Shares::ShareSasPermissions::Delete, + Files::Shares::ShareSasPermissions::List, + Files::Shares::ShareSasPermissions::Create, + Files::Shares::ShareSasPermissions::All}) + { + shareSasBuilder.SetPermissions(permissions); + auto sasToken = shareSasBuilder.ToSasQueryParameters(*keyCredential); + + if ((permissions & Files::Shares::ShareSasPermissions::Read) + == Files::Shares::ShareSasPermissions::Read) + { + verifyFileRead(sasToken); + } + if ((permissions & Files::Shares::ShareSasPermissions::Write) + == Files::Shares::ShareSasPermissions::Write) + { + verifyFileWrite(sasToken); + } + if ((permissions & Files::Shares::ShareSasPermissions::Delete) + == Files::Shares::ShareSasPermissions::Delete) + { + verifyFileDelete(sasToken); + } + if ((permissions & Files::Shares::ShareSasPermissions::List) + == Files::Shares::ShareSasPermissions::List) + { + verifyFileList(sasToken); + } + if ((permissions & Files::Shares::ShareSasPermissions::Create) + == Files::Shares::ShareSasPermissions::Create) + { + verifyFileCreate(sasToken); + } + } + + for (auto permissions : + {Files::Shares::ShareFileSasPermissions::Read, + Files::Shares::ShareFileSasPermissions::Write, + Files::Shares::ShareFileSasPermissions::Delete, + Files::Shares::ShareFileSasPermissions::Create}) + { + fileSasBuilder.SetPermissions(permissions); + auto sasToken = fileSasBuilder.ToSasQueryParameters(*keyCredential); + + if ((permissions & Files::Shares::ShareFileSasPermissions::Read) + == Files::Shares::ShareFileSasPermissions::Read) + { + verifyFileRead(sasToken); + } + if ((permissions & Files::Shares::ShareFileSasPermissions::Write) + == Files::Shares::ShareFileSasPermissions::Write) + { + verifyFileWrite(sasToken); + } + if ((permissions & Files::Shares::ShareFileSasPermissions::Delete) + == Files::Shares::ShareFileSasPermissions::Delete) + { + verifyFileDelete(sasToken); + } + if ((permissions & Files::Shares::ShareFileSasPermissions::Create) + == Files::Shares::ShareFileSasPermissions::Create) + { + verifyFileCreate(sasToken); + } + } + + fileSasBuilder.SetPermissions(Files::Shares::ShareFileSasPermissions::All); + // Expires + { + Files::Shares::ShareSasBuilder builder2 = fileSasBuilder; + builder2.StartsOn = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + builder2.ExpiresOn = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(1)); + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_THROW(verifyFileRead(sasToken), StorageError); + } + + // Without start time + { + Files::Shares::ShareSasBuilder builder2 = fileSasBuilder; + builder2.StartsOn.Reset(); + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_NO_THROW(verifyFileRead(sasToken)); + } + + // IP + { + Files::Shares::ShareSasBuilder builder2 = fileSasBuilder; + builder2.IPRange = "0.0.0.0-0.0.0.1"; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_THROW(verifyFileRead(sasToken), StorageError); + + builder2.IPRange = "0.0.0.0-255.255.255.255"; + sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_NO_THROW(verifyFileRead(sasToken)); + } + + // Identifier + { + Files::Shares::SignedIdentifier identifier; + identifier.Id = RandomString(64); + identifier.Policy.Start + = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(5)); + identifier.Policy.Expiry + = ToIso8601(std::chrono::system_clock::now() + std::chrono::minutes(60)); + identifier.Policy.Permission + = Files::Shares::ShareSasPermissionsToString(Files::Shares::ShareSasPermissions::Read); + m_shareClient->SetAccessPolicy({identifier}); + + Files::Shares::ShareSasBuilder builder2 = fileSasBuilder; + builder2.StartsOn.Reset(); + builder2.ExpiresOn.clear(); + builder2.SetPermissions(static_cast(0)); + builder2.Identifier = identifier.Id; + + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + EXPECT_NO_THROW(verifyFileRead(sasToken)); + } + + // response headers override + { + Files::Shares::FileShareHttpHeaders headers; + headers.ContentType = "application/x-binary"; + headers.ContentLanguage = "en-US"; + headers.ContentDisposition = "attachment"; + headers.CacheControl = "no-cache"; + headers.ContentEncoding = "identify"; + + Files::Shares::ShareSasBuilder builder2 = fileSasBuilder; + builder2.ContentType = "application/x-binary"; + builder2.ContentLanguage = "en-US"; + builder2.ContentDisposition = "attachment"; + builder2.CacheControl = "no-cache"; + builder2.ContentEncoding = "identify"; + auto sasToken = builder2.ToSasQueryParameters(*keyCredential); + auto fileClient = Files::Shares::FileClient(fileUri + sasToken); + fileClient0.Create(0); + auto p = fileClient.GetProperties(); + EXPECT_EQ(p->HttpHeaders.ContentType, headers.ContentType); + EXPECT_EQ(p->HttpHeaders.ContentLanguage, headers.ContentLanguage); + EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition); + EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl); + EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding); + } + } + +}}} // namespace Azure::Storage::Test