[Storage Blob Service] Get/SetBlobContainerAccessPolicy (#381)

* Get/SetBlobContainerAccessPolicy

* Remove ResetPermissions()
This commit is contained in:
JinmingHu 2020-08-04 09:53:34 +08:00 committed by GitHub
parent 18b02094ed
commit 8867c7abcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 517 additions and 19 deletions

View File

@ -203,6 +203,32 @@ namespace Azure { namespace Storage { namespace Blobs {
const std::string& delimiter,
const ListBlobsOptions& options = ListBlobsOptions()) const;
/**
* @brief Gets the permissions for this container. The permissions indicate whether
* container data may be accessed publicly.
*
* @param options Optional parameters to
* execute this function.
* @return A BlobContainerAccessPolicy describing the container's
* access policy.
*/
Azure::Core::Response<BlobContainerAccessPolicy> GetAccessPolicy(
const GetBlobContainerAccessPolicyOptions& options
= GetBlobContainerAccessPolicyOptions()) const;
/**
* @brief Sets the permissions for the specified container. The permissions indicate
* whether blob container data may be accessed publicly.
*
* @param options Optional
* parameters to execute this function.
* @return A BlobContainerInfo describing the
* updated container.
*/
Azure::Core::Response<BlobContainerInfo> SetAccessPolicy(
const SetBlobContainerAccessPolicyOptions& options
= SetBlobContainerAccessPolicyOptions()) const;
private:
UriBuilder m_containerUrl;
std::shared_ptr<Azure::Core::Http::HttpPipeline> m_pipeline;

View File

@ -306,6 +306,50 @@ namespace Azure { namespace Storage { namespace Blobs {
ListBlobsIncludeItem Include = ListBlobsIncludeItem::None;
};
/**
* @brief Optional parameters for BlobContainerClient::GetAccessPolicy.
*/
struct GetBlobContainerAccessPolicyOptions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
/**
* @brief Optional conditions that must be met to perform this operation.
*/
LeaseAccessConditions AccessConditions;
};
/**
* @brief Optional parameters for BlobContainerClient::SetAccessPolicy.
*/
struct SetBlobContainerAccessPolicyOptions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
/**
* @brief Specifies whether data in the container may be accessed publicly and the level
* of access.
*/
Azure::Core::Nullable<PublicAccessType> AccessType;
/**
* @brief Stored access policies that you can use to provide fine grained control over
* container permissions.
*/
std::vector<BlobSignedIdentifier> SignedIdentifiers;
/**
* @brief Optional conditions that must be met to perform this operation.
*/
ContainerAccessConditions AccessConditions;
};
/**
* @brief Blob client options used to initalize BlobClient.
*/

View File

@ -109,6 +109,8 @@ namespace Azure { namespace Storage { namespace Blobs {
static_cast<type>(lhs) & static_cast<type>(rhs));
}
std::string BlobContainerSasPermissionsToString(BlobContainerSasPermissions permissions);
/**
* @brief The list of permissions that can be set for a blob's access policy.
*/
@ -271,7 +273,10 @@ namespace Azure { namespace Storage { namespace Blobs {
* @param
* permissions The allowed permissions.
*/
void SetPermissions(BlobContainerSasPermissions permissions);
void SetPermissions(BlobContainerSasPermissions permissions)
{
Permissions = BlobContainerSasPermissionsToString(permissions);
}
/**
* @brief Sets the permissions for the blob SAS.

View File

@ -478,6 +478,14 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Nullable<int32_t> Days;
}; // struct BlobRetentionPolicy
struct BlobSignedIdentifier
{
std::string Id;
std::string StartsOn;
std::string ExpiresOn;
std::string Permissions;
}; // struct BlobSignedIdentifier
struct BlobSnapshotInfo
{
std::string Snapshot;
@ -1081,6 +1089,14 @@ namespace Azure { namespace Storage { namespace Blobs {
std::vector<BlobBlock> UncommittedBlocks;
}; // struct BlobBlockListInfo
struct BlobContainerAccessPolicy
{
PublicAccessType AccessType;
std::string ETag;
std::string LastModified;
std::vector<BlobSignedIdentifier> SignedIdentifiers;
}; // struct BlobContainerAccessPolicy
struct BlobContainerItem
{
std::string Name;
@ -3093,7 +3109,177 @@ namespace Azure { namespace Storage { namespace Blobs {
std::move(response), std::move(pHttpResponse));
}
struct GetAccessPolicyOptions
{
Azure::Core::Nullable<int32_t> Timeout;
Azure::Core::Nullable<std::string> LeaseId;
}; // struct GetAccessPolicyOptions
static Azure::Core::Response<BlobContainerAccessPolicy> GetAccessPolicy(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const GetAccessPolicyOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url);
request.AddHeader("x-ms-version", c_APIVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "acl");
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobContainerAccessPolicy response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
{
const auto& httpResponseBody = httpResponse.GetBody();
XmlReader reader(
reinterpret_cast<const char*>(httpResponseBody.data()), httpResponseBody.size());
response = BlobContainerAccessPolicyFromXml(reader);
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.AccessType
= PublicAccessTypeFromString(httpResponse.GetHeaders().at("x-ms-blob-public-access"));
return Azure::Core::Response<BlobContainerAccessPolicy>(
std::move(response), std::move(pHttpResponse));
}
struct SetAccessPolicyOptions
{
Azure::Core::Nullable<int32_t> Timeout;
Azure::Core::Nullable<PublicAccessType> AccessType;
Azure::Core::Nullable<std::string> LeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
std::vector<BlobSignedIdentifier> SignedIdentifiers;
}; // struct SetAccessPolicyOptions
static Azure::Core::Response<BlobContainerInfo> SetAccessPolicy(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const SetAccessPolicyOptions& options)
{
unused(options);
std::string xml_body;
{
XmlWriter writer;
SetAccessPolicyOptionsToXml(writer, options);
xml_body = writer.GetDocument();
writer.Write(XmlNode{XmlNodeType::End});
}
Azure::Core::Http::MemoryBodyStream xml_body_stream(
reinterpret_cast<const uint8_t*>(xml_body.data()), xml_body.length());
auto request
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, &xml_body_stream);
request.AddHeader("Content-Length", std::to_string(xml_body_stream.Length()));
request.AddHeader("x-ms-version", c_APIVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "acl");
if (options.AccessType.HasValue())
{
request.AddHeader(
"x-ms-blob-public-access", PublicAccessTypeToString(options.AccessType.GetValue()));
}
if (options.LeaseId.HasValue())
{
request.AddHeader("x-ms-lease-id", options.LeaseId.GetValue());
}
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobContainerInfo response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
return Azure::Core::Response<BlobContainerInfo>(
std::move(response), std::move(pHttpResponse));
}
private:
static BlobContainerAccessPolicy BlobContainerAccessPolicyFromXml(XmlReader& reader)
{
BlobContainerAccessPolicy ret;
enum class XmlTagName
{
k_SignedIdentifiers,
k_SignedIdentifier,
k_Unknown,
};
std::vector<XmlTagName> path;
while (true)
{
auto node = reader.Read();
if (node.Type == XmlNodeType::End)
{
break;
}
else if (node.Type == XmlNodeType::EndTag)
{
if (path.size() > 0)
{
path.pop_back();
}
else
{
break;
}
}
else if (node.Type == XmlNodeType::StartTag)
{
if (std::strcmp(node.Name, "SignedIdentifiers") == 0)
{
path.emplace_back(XmlTagName::k_SignedIdentifiers);
}
else if (std::strcmp(node.Name, "SignedIdentifier") == 0)
{
path.emplace_back(XmlTagName::k_SignedIdentifier);
}
else
{
path.emplace_back(XmlTagName::k_Unknown);
}
if (path.size() == 2 && path[0] == XmlTagName::k_SignedIdentifiers
&& path[1] == XmlTagName::k_SignedIdentifier)
{
ret.SignedIdentifiers.emplace_back(BlobSignedIdentifierFromXml(reader));
path.pop_back();
}
}
else if (node.Type == XmlNodeType::Text)
{
}
}
return ret;
}
static BlobsFlatSegment BlobsFlatSegmentFromXml(XmlReader& reader)
{
BlobsFlatSegment ret;
@ -3660,6 +3846,93 @@ namespace Azure { namespace Storage { namespace Blobs {
return ret;
}
static BlobSignedIdentifier BlobSignedIdentifierFromXml(XmlReader& reader)
{
BlobSignedIdentifier ret;
enum class XmlTagName
{
k_Id,
k_AccessPolicy,
k_Start,
k_Expiry,
k_Permission,
k_Unknown,
};
std::vector<XmlTagName> path;
while (true)
{
auto node = reader.Read();
if (node.Type == XmlNodeType::End)
{
break;
}
else if (node.Type == XmlNodeType::EndTag)
{
if (path.size() > 0)
{
path.pop_back();
}
else
{
break;
}
}
else if (node.Type == XmlNodeType::StartTag)
{
if (std::strcmp(node.Name, "Id") == 0)
{
path.emplace_back(XmlTagName::k_Id);
}
else if (std::strcmp(node.Name, "AccessPolicy") == 0)
{
path.emplace_back(XmlTagName::k_AccessPolicy);
}
else if (std::strcmp(node.Name, "Start") == 0)
{
path.emplace_back(XmlTagName::k_Start);
}
else if (std::strcmp(node.Name, "Expiry") == 0)
{
path.emplace_back(XmlTagName::k_Expiry);
}
else if (std::strcmp(node.Name, "Permission") == 0)
{
path.emplace_back(XmlTagName::k_Permission);
}
else
{
path.emplace_back(XmlTagName::k_Unknown);
}
}
else if (node.Type == XmlNodeType::Text)
{
if (path.size() == 1 && path[0] == XmlTagName::k_Id)
{
ret.Id = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_AccessPolicy
&& path[1] == XmlTagName::k_Start)
{
ret.StartsOn = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_AccessPolicy
&& path[1] == XmlTagName::k_Expiry)
{
ret.ExpiresOn = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_AccessPolicy
&& path[1] == XmlTagName::k_Permission)
{
ret.Permissions = node.Value;
}
}
}
return ret;
}
static std::map<std::string, std::string> MetadataFromXml(XmlReader& reader)
{
std::map<std::string, std::string> ret;
@ -3694,6 +3967,38 @@ namespace Azure { namespace Storage { namespace Blobs {
return ret;
}
static void SetAccessPolicyOptionsToXml(
XmlWriter& writer,
const SetAccessPolicyOptions& options)
{
writer.Write(XmlNode{XmlNodeType::StartTag, "SignedIdentifiers"});
for (const auto& i : options.SignedIdentifiers)
{
BlobSignedIdentifierToXml(writer, i);
}
writer.Write(XmlNode{XmlNodeType::EndTag});
}
static void BlobSignedIdentifierToXml(XmlWriter& writer, const BlobSignedIdentifier& options)
{
writer.Write(XmlNode{XmlNodeType::StartTag, "SignedIdentifier"});
writer.Write(XmlNode{XmlNodeType::StartTag, "Id"});
writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.Id.data()});
writer.Write(XmlNode{XmlNodeType::EndTag});
writer.Write(XmlNode{XmlNodeType::StartTag, "AccessPolicy"});
writer.Write(XmlNode{XmlNodeType::StartTag, "Start"});
writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.StartsOn.data()});
writer.Write(XmlNode{XmlNodeType::EndTag});
writer.Write(XmlNode{XmlNodeType::StartTag, "Expiry"});
writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.ExpiresOn.data()});
writer.Write(XmlNode{XmlNodeType::EndTag});
writer.Write(XmlNode{XmlNodeType::StartTag, "Permission"});
writer.Write(XmlNode{XmlNodeType::Text, nullptr, options.Permissions.data()});
writer.Write(XmlNode{XmlNodeType::EndTag});
writer.Write(XmlNode{XmlNodeType::EndTag});
writer.Write(XmlNode{XmlNodeType::EndTag});
}
}; // class Container
class Blob {

View File

@ -204,4 +204,26 @@ namespace Azure { namespace Storage { namespace Blobs {
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobContainerAccessPolicy> BlobContainerClient::GetAccessPolicy(
const GetBlobContainerAccessPolicyOptions& options) const
{
BlobRestClient::Container::GetAccessPolicyOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId;
return BlobRestClient::Container::GetAccessPolicy(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobContainerInfo> BlobContainerClient::SetAccessPolicy(
const SetBlobContainerAccessPolicyOptions& options) const
{
BlobRestClient::Container::SetAccessPolicyOptions protocolLayerOptions;
protocolLayerOptions.AccessType = options.AccessType;
protocolLayerOptions.SignedIdentifiers = options.SignedIdentifiers;
protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId;
protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince;
return BlobRestClient::Container::SetAccessPolicy(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
}}} // namespace Azure::Storage::Blobs

View File

@ -33,43 +33,44 @@ namespace Azure { namespace Storage { namespace Blobs {
}
} // namespace
void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions)
std::string BlobContainerSasPermissionsToString(BlobContainerSasPermissions permissions)
{
Permissions.clear();
std::string permissions_str;
// The order matters
if ((permissions & BlobContainerSasPermissions::Read) == BlobContainerSasPermissions::Read)
{
Permissions += "r";
permissions_str += "r";
}
if ((permissions & BlobContainerSasPermissions::Add) == BlobContainerSasPermissions::Add)
{
Permissions += "a";
permissions_str += "a";
}
if ((permissions & BlobContainerSasPermissions::Create) == BlobContainerSasPermissions::Create)
{
Permissions += "c";
permissions_str += "c";
}
if ((permissions & BlobContainerSasPermissions::Write) == BlobContainerSasPermissions::Write)
{
Permissions += "w";
permissions_str += "w";
}
if ((permissions & BlobContainerSasPermissions::Delete) == BlobContainerSasPermissions::Delete)
{
Permissions += "d";
permissions_str += "d";
}
if ((permissions & BlobContainerSasPermissions::DeleteVersion)
== BlobContainerSasPermissions::DeleteVersion)
{
Permissions += "x";
permissions_str += "x";
}
if ((permissions & BlobContainerSasPermissions::List) == BlobContainerSasPermissions::List)
{
Permissions += "l";
permissions_str += "l";
}
if ((permissions & BlobContainerSasPermissions::Tags) == BlobContainerSasPermissions::Tags)
{
Permissions += "t";
permissions_str += "t";
}
return permissions_str;
}
void BlobSasBuilder::SetPermissions(BlobSasPermissions permissions)
@ -132,7 +133,10 @@ namespace Azure { namespace Storage { namespace Blobs {
{
builder.AppendQuery("st", StartsOn.GetValue());
}
builder.AppendQuery("se", ExpiresOn);
if (!ExpiresOn.empty())
{
builder.AppendQuery("se", ExpiresOn);
}
if (IPRange.HasValue())
{
builder.AppendQuery("sip", IPRange.GetValue());
@ -142,7 +146,10 @@ namespace Azure { namespace Storage { namespace Blobs {
builder.AppendQuery("si", Identifier);
}
builder.AppendQuery("sr", resource);
builder.AppendQuery("sp", Permissions);
if (!Permissions.empty())
{
builder.AppendQuery("sp", Permissions);
}
builder.AppendQuery("sig", signature, true);
if (!CacheControl.empty())
{

View File

@ -4,6 +4,18 @@
#include "blob_container_client_test.hpp"
#include "blobs/blob_sas_builder.hpp"
namespace Azure { namespace Storage { namespace Blobs {
bool operator==(
const Azure::Storage::Blobs::BlobSignedIdentifier& lhs,
const Azure::Storage::Blobs::BlobSignedIdentifier& rhs)
{
return lhs.Id == rhs.Id && lhs.StartsOn == rhs.StartsOn && lhs.ExpiresOn == rhs.ExpiresOn
&& lhs.Permissions == rhs.Permissions;
}
}}} // namespace Azure::Storage::Blobs
namespace Azure { namespace Storage { namespace Test {
std::shared_ptr<Azure::Storage::Blobs::BlobContainerClient>
@ -225,4 +237,39 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_EQ(items, blobs);
}
TEST_F(BlobContainerClientTest, AccessControlList)
{
auto container_client = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString(
StandardStorageConnectionString(), LowercaseRandomString());
container_client.Create();
Blobs::SetBlobContainerAccessPolicyOptions options;
options.AccessType = Blobs::PublicAccessType::Blob;
Blobs::BlobSignedIdentifier identifier;
identifier.Id = RandomString(64);
identifier.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1), 7);
identifier.ExpiresOn = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(1), 7);
identifier.Permissions
= Blobs::BlobContainerSasPermissionsToString(Blobs::BlobContainerSasPermissions::Read);
options.SignedIdentifiers.emplace_back(identifier);
identifier.Id = RandomString(64);
identifier.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(2), 7);
identifier.ExpiresOn = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(2), 7);
identifier.Permissions
= Blobs::BlobContainerSasPermissionsToString(Blobs::BlobContainerSasPermissions::All);
options.SignedIdentifiers.emplace_back(identifier);
auto ret = container_client.SetAccessPolicy(options);
EXPECT_FALSE(ret->ETag.empty());
EXPECT_FALSE(ret->LastModified.empty());
auto ret2 = container_client.GetAccessPolicy();
EXPECT_EQ(ret2->ETag, ret->ETag);
EXPECT_EQ(ret2->LastModified, ret->LastModified);
EXPECT_EQ(ret2->AccessType, options.AccessType.GetValue());
EXPECT_EQ(ret2->SignedIdentifiers, options.SignedIdentifiers);
container_client.Delete();
}
}}} // namespace Azure::Storage::Test

View File

@ -340,7 +340,7 @@ namespace Azure { namespace Storage { namespace Test {
builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1));
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_THROW(verify_blob_create(sasToken), StorageError);
auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName);
EXPECT_THROW(verify_blob_create(sasToken2), StorageError);
}
@ -371,6 +371,29 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_NO_THROW(verify_blob_create(sasToken2));
}
// Identifier
{
Blobs::SetBlobContainerAccessPolicyOptions options;
options.AccessType = Blobs::PublicAccessType::Blob;
Blobs::BlobSignedIdentifier identifier;
identifier.Id = RandomString(64);
identifier.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5));
identifier.ExpiresOn = ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(60));
identifier.Permissions
= Blobs::BlobContainerSasPermissionsToString(Blobs::BlobContainerSasPermissions::Read);
options.SignedIdentifiers.emplace_back(identifier);
m_blobContainerClient->SetAccessPolicy(options);
Blobs::BlobSasBuilder builder2 = blobSasBuilder;
builder2.StartsOn.Reset();
builder2.ExpiresOn.clear();
builder2.SetPermissions(static_cast<Blobs::BlobContainerSasPermissions>(0));
builder2.Identifier = identifier.Id;
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_NO_THROW(verify_blob_read(sasToken));
}
// response headers override
{
Blobs::BlobHttpHeaders headers;

View File

@ -217,7 +217,9 @@ namespace Azure { namespace Storage { namespace Test {
return result;
}
std::string ToISO8601(const std::chrono::system_clock::time_point& time_point)
std::string ToISO8601(
const std::chrono::system_clock::time_point& time_point,
int numDecimalDigits)
{
std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(time_point);
struct tm ct;
@ -226,9 +228,24 @@ namespace Azure { namespace Storage { namespace Test {
#else
gmtime_r(&epoch_seconds, &ct);
#endif
char buff[64];
std::strftime(buff, sizeof(buff), "%Y-%m-%dT%H:%M:%SZ", &ct);
return std::string(buff);
std::string time_str;
time_str.resize(64);
std::strftime(&time_str[0], time_str.length(), "%Y-%m-%dT%H:%M:%S", &ct);
time_str = time_str.data();
if (numDecimalDigits != 0)
{
time_str += ".";
auto time_point_second = std::chrono::time_point_cast<std::chrono::seconds>(time_point);
auto decimal_part = time_point - time_point_second;
uint64_t num_nanoseconds
= std::chrono::duration_cast<std::chrono::nanoseconds>(decimal_part).count();
std::string decimal_part_str = std::to_string(num_nanoseconds);
decimal_part_str = std::string(9 - decimal_part_str.length(), '0') + decimal_part_str;
decimal_part_str.resize(numDecimalDigits);
time_str += decimal_part_str;
}
time_str += "Z";
return time_str;
}
std::string ToRFC1123(const std::chrono::system_clock::time_point& time_point)

View File

@ -61,7 +61,9 @@ namespace Azure { namespace Storage { namespace Test {
void DeleteFile(const std::string& filename);
std::string ToISO8601(const std::chrono::system_clock::time_point& time_point);
std::string ToISO8601(
const std::chrono::system_clock::time_point& time_point,
int numDecimalDigits = 0);
std::string ToRFC1123(const std::chrono::system_clock::time_point& time_point);
}}} // namespace Azure::Storage::Test