From 4366159c50148bcf832b7e0d22ff6849f516477b Mon Sep 17 00:00:00 2001 From: JinmingHu Date: Mon, 15 Jun 2020 02:31:36 -0700 Subject: [PATCH] Add more APIs in storage sdk (#169) * Add xml serializer/deserializer * Add blob service client, add undelete, set access tier, start copy, abort copy, create snapshot, stage block from uri * add support for conditional headers * support range download * Change the helper functions order to alphabetical so that we always get the same result in different environemnts. * use c++ casting * Add support for append blob and page blob * remove redundant ; added by auto-generator * Remove BlobType in BlockBlob::Upload * Rename blob_client_options.hpp->blob_options.hpp * Remove API version in request options --- sdk/storage/CMakeLists.txt | 19 +- sdk/storage/inc/blobs/append_blob_client.hpp | 54 + sdk/storage/inc/blobs/blob.hpp | 3 + sdk/storage/inc/blobs/blob_client.hpp | 32 +- sdk/storage/inc/blobs/blob_client_options.hpp | 88 - .../inc/blobs/blob_container_client.hpp | 16 +- .../blobs/blob_container_client_options.hpp | 51 - sdk/storage/inc/blobs/blob_options.hpp | 353 ++ sdk/storage/inc/blobs/blob_service_client.hpp | 51 + sdk/storage/inc/blobs/block_blob_client.hpp | 18 +- .../internal/protocol/blob_rest_client.hpp | 3858 ++++++++++++++--- sdk/storage/inc/blobs/page_blob_client.hpp | 79 + sdk/storage/inc/common/xml_wrapper.hpp | 62 + sdk/storage/src/blobs/append_blob_client.cpp | 93 + sdk/storage/src/blobs/blob_client.cpp | 115 +- .../src/blobs/blob_container_client.cpp | 15 +- sdk/storage/src/blobs/blob_service_client.cpp | 154 + sdk/storage/src/blobs/block_blob_client.cpp | 60 +- sdk/storage/src/blobs/page_blob_client.cpp | 178 + sdk/storage/src/common/crypt.cpp | 56 +- sdk/storage/src/common/xml_wrapper.cpp | 177 + 21 files changed, 4803 insertions(+), 729 deletions(-) create mode 100644 sdk/storage/inc/blobs/append_blob_client.hpp delete mode 100644 sdk/storage/inc/blobs/blob_client_options.hpp delete mode 100644 sdk/storage/inc/blobs/blob_container_client_options.hpp create mode 100644 sdk/storage/inc/blobs/blob_options.hpp create mode 100644 sdk/storage/inc/blobs/blob_service_client.hpp create mode 100644 sdk/storage/inc/blobs/page_blob_client.hpp create mode 100644 sdk/storage/inc/common/xml_wrapper.hpp create mode 100644 sdk/storage/src/blobs/append_blob_client.cpp create mode 100644 sdk/storage/src/blobs/blob_service_client.cpp create mode 100644 sdk/storage/src/blobs/page_blob_client.cpp create mode 100644 sdk/storage/src/common/xml_wrapper.cpp diff --git a/sdk/storage/CMakeLists.txt b/sdk/storage/CMakeLists.txt index f21952a52..51b0d8627 100644 --- a/sdk/storage/CMakeLists.txt +++ b/sdk/storage/CMakeLists.txt @@ -16,12 +16,15 @@ set (AZURE_STORAGE_BLOB_HEADER inc/common/common_headers_request_policy.hpp inc/common/shared_key_policy.hpp inc/common/crypt.hpp + inc/common/xml_wrapper.hpp inc/blobs/blob.hpp + inc/blobs/blob_service_client.hpp + inc/blobs/blob_container_client.hpp inc/blobs/blob_client.hpp inc/blobs/block_blob_client.hpp - inc/blobs/blob_container_client.hpp - inc/blobs/blob_client_options.hpp - inc/blobs/blob_container_client_options.hpp + inc/blobs/page_blob_client.hpp + inc/blobs/append_blob_client.hpp + inc/blobs/blob_options.hpp inc/blobs/internal/protocol/blob_rest_client.hpp ) @@ -31,14 +34,18 @@ set (AZURE_STORAGE_DATALAKE_HEADER ) set (AZURE_STORAGE_BLOB_SOURCE - src/blobs/blob_client.cpp - src/blobs/block_blob_client.cpp - src/blobs/blob_container_client.cpp src/common/storage_credential.cpp src/common/storage_url_builder.cpp src/common/common_headers_request_policy.cpp src/common/shared_key_policy.cpp src/common/crypt.cpp + src/common/xml_wrapper.cpp + src/blobs/blob_service_client.cpp + src/blobs/blob_container_client.cpp + src/blobs/blob_client.cpp + src/blobs/block_blob_client.cpp + src/blobs/page_blob_client.cpp + src/blobs/append_blob_client.cpp ) set (AZURE_STORAGE_DATALAKE_SOURCE diff --git a/sdk/storage/inc/blobs/append_blob_client.hpp b/sdk/storage/inc/blobs/append_blob_client.hpp new file mode 100644 index 000000000..f4264110f --- /dev/null +++ b/sdk/storage/inc/blobs/append_blob_client.hpp @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "blob_options.hpp" +#include "blobs/blob_client.hpp" +#include "common/storage_credential.hpp" +#include "internal/protocol/blob_rest_client.hpp" + +#include + +namespace Azure { namespace Storage { namespace Blobs { + + class AppendBlobClient : public BlobClient { + public: + // connection string + static AppendBlobClient CreateFromConnectionString( + const std::string& connectionString, + const std::string& containerName, + const std::string& blobName, + const AppendBlobClientOptions& options = AppendBlobClientOptions()); + + // shared key auth + explicit AppendBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const AppendBlobClientOptions& options = AppendBlobClientOptions()); + + // token auth + explicit AppendBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const AppendBlobClientOptions& options = AppendBlobClientOptions()); + + // anonymous/SAS/customized pipeline auth + explicit AppendBlobClient( + const std::string& blobUri, + const AppendBlobClientOptions& options = AppendBlobClientOptions()); + + AppendBlobClient WithSnapshot(const std::string& snapshot) const; + + BlobContentInfo Create(const CreateAppendBlobOptions& options = CreateAppendBlobOptions()); + + BlobAppendInfo AppendBlock( + // TODO: We don't have BodyStream for now. + std::vector content, + const AppendBlockOptions& options = AppendBlockOptions()); + + private: + explicit AppendBlobClient(BlobClient blobClient); + }; + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/blob.hpp b/sdk/storage/inc/blobs/blob.hpp index 9c3206a5e..71bfcc703 100644 --- a/sdk/storage/inc/blobs/blob.hpp +++ b/sdk/storage/inc/blobs/blob.hpp @@ -3,6 +3,9 @@ #pragma once +#include "blobs/append_blob_client.hpp" #include "blobs/blob_client.hpp" #include "blobs/blob_container_client.hpp" +#include "blobs/blob_service_client.hpp" #include "blobs/block_blob_client.hpp" +#include "blobs/page_blob_client.hpp" diff --git a/sdk/storage/inc/blobs/blob_client.hpp b/sdk/storage/inc/blobs/blob_client.hpp index 87e6551d4..dee4a3d26 100644 --- a/sdk/storage/inc/blobs/blob_client.hpp +++ b/sdk/storage/inc/blobs/blob_client.hpp @@ -3,7 +3,7 @@ #pragma once -#include "blob_client_options.hpp" +#include "blob_options.hpp" #include "common/storage_credential.hpp" #include "common/storage_url_builder.hpp" #include "internal/protocol/blob_rest_client.hpp" @@ -40,21 +40,39 @@ namespace Azure { namespace Storage { namespace Blobs { const std::string& blobUri, const BlobClientOptions& options = BlobClientOptions()); - BlobClient WithSnapshot(const std::string& snapshot); + BlobClient WithSnapshot(const std::string& snapshot) const; BlobProperties GetProperties( - const GetBlobPropertiesOptions& options = GetBlobPropertiesOptions()); + const GetBlobPropertiesOptions& options = GetBlobPropertiesOptions()) const; - BlobInfo SetHttpHeaders(const SetBlobHttpHeadersOptions& options = SetBlobHttpHeadersOptions()); + BlobInfo SetHttpHeaders( + const SetBlobHttpHeadersOptions& options = SetBlobHttpHeadersOptions()) const; BlobInfo SetMetadata( std::map metadata, - const SetBlobMetadataOptions& options = SetBlobMetadataOptions()); + const SetBlobMetadataOptions& options = SetBlobMetadataOptions()) const; + + BasicResponse SetAccessTier( + AccessTier Tier, + const SetAccessTierOptions& options = SetAccessTierOptions()) const; + + BlobCopyInfo StartCopyFromUri( + const std::string& sourceUri, + const StartCopyFromUriOptions& options = StartCopyFromUriOptions()) const; + + BasicResponse AbortCopyFromUri( + const std::string& copyId, + const AbortCopyFromUriOptions& options = AbortCopyFromUriOptions()) const; FlattenedDownloadProperties Download( - const DownloadBlobOptions& options = DownloadBlobOptions()); + const DownloadBlobOptions& options = DownloadBlobOptions()) const; - BasicResponse Delete(const DeleteBlobOptions& options = DeleteBlobOptions()); + BlobSnapshotInfo CreateSnapshot( + const CreateSnapshotOptions& options = CreateSnapshotOptions()) const; + + BasicResponse Delete(const DeleteBlobOptions& options = DeleteBlobOptions()) const; + + BasicResponse Undelete(const UndeleteBlobOptions& options = UndeleteBlobOptions()) const; protected: UrlBuilder m_blobUrl; diff --git a/sdk/storage/inc/blobs/blob_client_options.hpp b/sdk/storage/inc/blobs/blob_client_options.hpp deleted file mode 100644 index bba2e2822..000000000 --- a/sdk/storage/inc/blobs/blob_client_options.hpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#pragma once - -#include "internal/protocol/blob_rest_client.hpp" - -#include -#include -#include - -namespace Azure { namespace Storage { namespace Blobs { - - struct BlobClientOptions - { - std::vector> policies; - }; - - struct BlockBlobClientOptions : public BlobClientOptions - { - }; - - struct GetBlobPropertiesOptions - { - Azure::Core::Context Context; - }; - - struct SetBlobHttpHeadersOptions - { - Azure::Core::Context Context; - std::string ContentType; - std::string ContentEncoding; - std::string ContentLanguage; - std::string ContentMD5; - std::string CacheControl; - std::string ContentDisposition; - }; - - struct SetBlobMetadataOptions - { - Azure::Core::Context Context; - }; - - struct DownloadBlobOptions - { - Azure::Core::Context Context; - uint64_t Offset = std::numeric_limits::max(); - uint64_t Length = 0; - }; - - struct DeleteBlobOptions - { - Azure::Core::Context Context; - DeleteSnapshotsOption DeleteSnapshots = DeleteSnapshotsOption::None; - }; - - struct UploadBlobOptions - { - Azure::Core::Context Context; - std::string ContentMD5; - std::string ContentCRC64; - BlobHttpHeaders Properties; - std::map Metadata; - AccessTier Tier = AccessTier::Unknown; - }; - - struct StageBlockOptions - { - Azure::Core::Context Context; - std::string ContentMD5; - std::string ContentCRC64; - }; - - struct CommitBlockListOptions - { - Azure::Core::Context Context; - BlobHttpHeaders Properties; - std::map Metadata; - AccessTier Tier = AccessTier::Unknown; - }; - - struct GetBlockListOptions - { - Azure::Core::Context Context; - BlockListTypeOption ListType = BlockListTypeOption::All; - }; - -}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/blob_container_client.hpp b/sdk/storage/inc/blobs/blob_container_client.hpp index 5c71fc9ad..35aab6b96 100644 --- a/sdk/storage/inc/blobs/blob_container_client.hpp +++ b/sdk/storage/inc/blobs/blob_container_client.hpp @@ -3,7 +3,7 @@ #pragma once -#include "blob_container_client_options.hpp" +#include "blob_options.hpp" #include "blobs/blob_client.hpp" #include "common/storage_credential.hpp" #include "common/storage_url_builder.hpp" @@ -40,21 +40,23 @@ namespace Azure { namespace Storage { namespace Blobs { const std::string& containerUri, const BlobContainerClientOptions& options = BlobContainerClientOptions()); - BlobClient GetBlobClient(const std::string& blobName); + BlobClient GetBlobClient(const std::string& blobName) const; BlobContainerInfo Create( - const CreateBlobContainerOptions& options = CreateBlobContainerOptions()); + const CreateBlobContainerOptions& options = CreateBlobContainerOptions()) const; - BasicResponse Delete(const DeleteBlobContainerOptions& options = DeleteBlobContainerOptions()); + BasicResponse Delete( + const DeleteBlobContainerOptions& options = DeleteBlobContainerOptions()) const; BlobContainerProperties GetProperties( - const GetBlobContainerPropertiesOptions& options = GetBlobContainerPropertiesOptions()); + const GetBlobContainerPropertiesOptions& options + = GetBlobContainerPropertiesOptions()) const; BlobContainerInfo SetMetadata( std::map metadata, - SetBlobContainerMetadataOptions options = SetBlobContainerMetadataOptions()); + SetBlobContainerMetadataOptions options = SetBlobContainerMetadataOptions()) const; - BlobsFlatSegment ListBlobs(const ListBlobsOptions& options = ListBlobsOptions()); + BlobsFlatSegment ListBlobs(const ListBlobsOptions& options = ListBlobsOptions()) const; private: UrlBuilder m_ContainerUri; diff --git a/sdk/storage/inc/blobs/blob_container_client_options.hpp b/sdk/storage/inc/blobs/blob_container_client_options.hpp deleted file mode 100644 index 1fb85a732..000000000 --- a/sdk/storage/inc/blobs/blob_container_client_options.hpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#pragma once - -#include "internal/protocol/blob_rest_client.hpp" - -#include -#include -#include - -namespace Azure { namespace Storage { namespace Blobs { - - struct BlobContainerClientOptions - { - std::vector> policies; - }; - - struct CreateBlobContainerOptions - { - Azure::Core::Context Context; - PublicAccessType AccessType = PublicAccessType::Private; - std::map Metadata; - }; - - struct DeleteBlobContainerOptions - { - Azure::Core::Context Context; - }; - - struct GetBlobContainerPropertiesOptions - { - Azure::Core::Context Context; - }; - - struct SetBlobContainerMetadataOptions - { - Azure::Core::Context Context; - }; - - struct ListBlobsOptions - { - Azure::Core::Context Context; - std::string Prefix; - std::string Delimiter; - std::string Marker; - int MaxResults = 0; - std::vector Include; - }; - -}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/blob_options.hpp b/sdk/storage/inc/blobs/blob_options.hpp new file mode 100644 index 000000000..e6c4be05d --- /dev/null +++ b/sdk/storage/inc/blobs/blob_options.hpp @@ -0,0 +1,353 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "internal/protocol/blob_rest_client.hpp" + +#include +#include +#include + +namespace Azure { namespace Storage { namespace Blobs { + + struct BlobServiceClientOptions + { + std::vector> policies; + }; + + struct ListBlobContainersOptions + { + Azure::Core::Context Context; + std::string Prefix; + std::string Marker; + int MaxResults = 0; + std::vector Include; + }; + + struct GetUserDelegationKeyOptions + { + Azure::Core::Context Context; + std::string StartsOn; + }; + + struct BlobContainerClientOptions + { + std::vector> policies; + }; + + struct CreateBlobContainerOptions + { + Azure::Core::Context Context; + PublicAccessType AccessType = PublicAccessType::Private; + std::map Metadata; + }; + + struct DeleteBlobContainerOptions + { + Azure::Core::Context Context; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + }; + + struct GetBlobContainerPropertiesOptions + { + Azure::Core::Context Context; + }; + + struct SetBlobContainerMetadataOptions + { + Azure::Core::Context Context; + std::string IfModifiedSince; + }; + + struct ListBlobsOptions + { + Azure::Core::Context Context; + std::string Prefix; + std::string Delimiter; + std::string Marker; + int MaxResults = 0; + std::vector Include; + }; + + struct BlobClientOptions + { + std::vector> policies; + }; + + struct BlockBlobClientOptions : public BlobClientOptions + { + }; + + struct AppendBlobClientOptions : public BlobClientOptions + { + }; + + struct PageBlobClientOptions : public BlobClientOptions + { + }; + + struct GetBlobPropertiesOptions + { + Azure::Core::Context Context; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct SetBlobHttpHeadersOptions + { + Azure::Core::Context Context; + std::string ContentType; + std::string ContentEncoding; + std::string ContentLanguage; + std::string ContentMD5; + std::string CacheControl; + std::string ContentDisposition; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct SetBlobMetadataOptions + { + Azure::Core::Context Context; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct SetAccessTierOptions + { + Azure::Core::Context Context; + Blobs::RehydratePriority RehydratePriority = Blobs::RehydratePriority::Unknown; + }; + + struct StartCopyFromUriOptions + { + Azure::Core::Context Context; + std::map Metadata; + std::string LeaseId; + std::string SourceLeaseId; + AccessTier Tier = AccessTier::Unknown; + Blobs::RehydratePriority RehydratePriority = Blobs::RehydratePriority::Unknown; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + std::string SourceIfModifiedSince; + std::string SourceIfUnmodifiedSince; + std::string SourceIfMatch; + std::string SourceIfNoneMatch; + }; + + struct AbortCopyFromUriOptions + { + Azure::Core::Context Context; + std::string LeaseId; + }; + + struct DownloadBlobOptions + { + Azure::Core::Context Context; + uint64_t Offset = std::numeric_limits::max(); // max means the whole blob + uint64_t Length = 0; // 0 means till the end + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct CreateSnapshotOptions + { + Azure::Core::Context Context; + std::map Metadata; + std::string LeaseId; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct DeleteBlobOptions + { + Azure::Core::Context Context; + DeleteSnapshotsOption DeleteSnapshots = DeleteSnapshotsOption::None; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct UndeleteBlobOptions + { + Azure::Core::Context Context; + }; + + struct UploadBlobOptions + { + Azure::Core::Context Context; + std::string ContentMD5; + std::string ContentCRC64; + BlobHttpHeaders Properties; + std::map Metadata; + AccessTier Tier = AccessTier::Unknown; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct StageBlockOptions + { + Azure::Core::Context Context; + std::string ContentMD5; + std::string ContentCRC64; + }; + + struct StageBlockFromUriOptions + { + Azure::Core::Context Context; + uint64_t SourceOffset = std::numeric_limits::max(); + uint64_t SourceLength = 0; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + std::string SourceIfModifiedSince; + std::string SourceIfUnmodifiedSince; + std::string SourceIfMatch; + std::string SourceIfNoneMatch; + }; + + struct CommitBlockListOptions + { + Azure::Core::Context Context; + BlobHttpHeaders Properties; + std::map Metadata; + AccessTier Tier = AccessTier::Unknown; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct GetBlockListOptions + { + Azure::Core::Context Context; + BlockListTypeOption ListType = BlockListTypeOption::All; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct CreateAppendBlobOptions + { + Azure::Core::Context Context; + BlobHttpHeaders Properties; + std::map Metadata; + AccessTier Tier = AccessTier::Unknown; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct AppendBlockOptions + { + Azure::Core::Context Context; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + uint64_t MaxSize = std::numeric_limits::max(); + uint64_t AppendPosition = std::numeric_limits::max(); + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct CreatePageBlobOptions + { + Azure::Core::Context Context; + uint64_t SequenceNumber = 0; + BlobHttpHeaders Properties; + std::map Metadata; + AccessTier Tier = AccessTier::Unknown; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct UploadPagesOptions + { + Azure::Core::Context Context; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct UploadPagesFromUriOptions + { + Azure::Core::Context Context; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct ClearPagesOptions + { + Azure::Core::Context Context; + std::string LeaseId; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct ResizePageBlobOptions + { + Azure::Core::Context Context; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct GetPageRangesOptions + { + Azure::Core::Context Context; + std::string PreviousSnapshot; + std::string PreviousSnapshotUrl; + uint64_t Offset = 0; + uint64_t Length = 0; + std::string LeaseId; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + + struct IncrementalCopyPageBlobOptions + { + Azure::Core::Context Context; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/blob_service_client.hpp b/sdk/storage/inc/blobs/blob_service_client.hpp new file mode 100644 index 000000000..21296f134 --- /dev/null +++ b/sdk/storage/inc/blobs/blob_service_client.hpp @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "blob_options.hpp" +#include "common/storage_credential.hpp" +#include "common/storage_url_builder.hpp" +#include "internal/protocol/blob_rest_client.hpp" + +#include +#include + +namespace Azure { namespace Storage { namespace Blobs { + + class BlobServiceClient { + public: + // connection string + static BlobServiceClient CreateFromConnectionString( + const std::string& connectionString, + const BlobServiceClientOptions& options = BlobServiceClientOptions()); + + // shared key auth + explicit BlobServiceClient( + const std::string& serviceUri, + std::shared_ptr credential, + const BlobServiceClientOptions& options = BlobServiceClientOptions()); + + // token auth + explicit BlobServiceClient( + const std::string& serviceUri, + std::shared_ptr credential, + const BlobServiceClientOptions& options = BlobServiceClientOptions()); + + // anonymous/SAS/customized pipeline auth + explicit BlobServiceClient( + const std::string& serviceUri, + const BlobServiceClientOptions& options = BlobServiceClientOptions()); + + ListContainersSegment ListBlobContainersSegment( + const ListBlobContainersOptions& options = ListBlobContainersOptions()) const; + + UserDelegationKey GetUserDelegationKey( + const std::string& expiresOn, + const GetUserDelegationKeyOptions& options = GetUserDelegationKeyOptions()) const; + + protected: + UrlBuilder m_serviceUrl; + std::shared_ptr m_pipeline; + }; +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/block_blob_client.hpp b/sdk/storage/inc/blobs/block_blob_client.hpp index 5b91cc25d..613055dbd 100644 --- a/sdk/storage/inc/blobs/block_blob_client.hpp +++ b/sdk/storage/inc/blobs/block_blob_client.hpp @@ -3,7 +3,7 @@ #pragma once -#include "blob_client_options.hpp" +#include "blob_options.hpp" #include "blobs/blob_client.hpp" #include "common/storage_credential.hpp" #include "internal/protocol/blob_rest_client.hpp" @@ -39,24 +39,30 @@ namespace Azure { namespace Storage { namespace Blobs { const std::string& blobUri, const BlockBlobClientOptions& options = BlockBlobClientOptions()); - BlockBlobClient WithSnapshot(const std::string& snapshot); + BlockBlobClient WithSnapshot(const std::string& snapshot) const; BlobContentInfo Upload( // TODO: We don't have BodyStream for now. std::vector content, - const UploadBlobOptions& options = UploadBlobOptions()); + const UploadBlobOptions& options = UploadBlobOptions()) const; BlockInfo StageBlock( const std::string& blockId, // TODO: We don't have BodyStream for now. std::vector content, - const StageBlockOptions& options = StageBlockOptions()); + const StageBlockOptions& options = StageBlockOptions()) const; + + BlockInfo StageBlockFromUri( + const std::string& blockId, + const std::string& sourceUri, + const StageBlockFromUriOptions& options = StageBlockFromUriOptions()) const; BlobContentInfo CommitBlockList( const std::vector>& blockIds, - const CommitBlockListOptions& options = CommitBlockListOptions()); + const CommitBlockListOptions& options = CommitBlockListOptions()) const; - BlobBlockListInfo GetBlockList(const GetBlockListOptions& options = GetBlockListOptions()); + BlobBlockListInfo GetBlockList( + const GetBlockListOptions& options = GetBlockListOptions()) const; private: explicit BlockBlobClient(BlobClient blobClient); diff --git a/sdk/storage/inc/blobs/internal/protocol/blob_rest_client.hpp b/sdk/storage/inc/blobs/internal/protocol/blob_rest_client.hpp index 3afd2f50a..87b00de68 100644 --- a/sdk/storage/inc/blobs/internal/protocol/blob_rest_client.hpp +++ b/sdk/storage/inc/blobs/internal/protocol/blob_rest_client.hpp @@ -4,21 +4,21 @@ #pragma once +#include "common/storage_common.hpp" +#include "common/xml_wrapper.hpp" +#include "context.hpp" +#include "http/http.hpp" +#include "http/pipeline.hpp" + +#include #include +#include #include #include #include #include #include -namespace libXML2 { -#include "libxml/tree.h" -} - -#include "context.hpp" -#include "http/http.hpp" -#include "http/pipeline.hpp" - namespace Azure { namespace Storage { namespace Blobs { enum class AccessTier { @@ -172,6 +172,22 @@ namespace Azure { namespace Storage { namespace Blobs { std::string ClientRequestId; }; // struct BasicResponse + struct BlobAppendInfo + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string ETag; + std::string LastModified; + std::string ContentMD5; + std::string ContentCRC64; + uint64_t AppendOffset = std::numeric_limits::max(); + uint64_t CommittedBlockCount = std::numeric_limits::max(); + bool ServerEncrypted = true; + std::string EncryptionKeySHA256; + }; // struct BlobAppendInfo + enum class BlobArchiveStatus { Unknown, @@ -348,6 +364,19 @@ namespace Azure { namespace Storage { namespace Blobs { throw std::runtime_error("cannot convert " + blob_lease_status + " to BlobLeaseStatus"); } + struct BlobSnapshotInfo + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string Snapshot; + std::string ETag; + std::string LastModified; + bool ServerEncrypted = true; + std::string EncryptionKeySHA256; + }; // struct BlobSnapshotInfo + enum class BlobType { Unknown, @@ -486,6 +515,45 @@ namespace Azure { namespace Storage { namespace Blobs { throw std::runtime_error("cannot convert " + block_type + " to BlockType"); } + enum class CopyStatus + { + Unknown, + Success, + Pending, + }; // enum class CopyStatus + + inline std::string CopyStatusToString(const CopyStatus& copy_status) + { + switch (copy_status) + { + case CopyStatus::Unknown: + return ""; + case CopyStatus::Success: + return "success"; + case CopyStatus::Pending: + return "pending"; + default: + return std::string(); + } + } + + inline CopyStatus CopyStatusFromString(const std::string& copy_status) + { + if (copy_status == "") + { + return CopyStatus::Unknown; + } + if (copy_status == "success") + { + return CopyStatus::Success; + } + if (copy_status == "pending") + { + return CopyStatus::Pending; + } + throw std::runtime_error("cannot convert " + copy_status + " to CopyStatus"); + } + enum class DeleteSnapshotsOption { None, @@ -620,6 +688,45 @@ namespace Azure { namespace Storage { namespace Blobs { "cannot convert " + list_blobs_include_item + " to ListBlobsIncludeItem"); } + struct PageBlobInfo + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string ETag; + std::string LastModified; + uint64_t SequenceNumber = 0; + }; // struct PageBlobInfo + + struct PageInfo + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string ETag; + std::string LastModified; + std::string ContentMD5; + std::string ContentCRC64; + uint64_t SequenceNumber = 0; + bool ServerEncrypted = true; + std::string EncryptionKeySHA256; + }; // struct PageInfo + + struct PageRangesInfo + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string ETag; + std::string LastModified; + uint64_t BlobContentLength = 0; + std::vector> PageRange; + std::vector> ClearRange; + }; // struct PageRangesInfo + enum class PublicAccessType { Container, @@ -659,6 +766,60 @@ namespace Azure { namespace Storage { namespace Blobs { throw std::runtime_error("cannot convert " + public_access_type + " to PublicAccessType"); } + enum class RehydratePriority + { + Unknown, + High, + Standard, + }; // enum class RehydratePriority + + inline std::string RehydratePriorityToString(const RehydratePriority& rehydrate_priority) + { + switch (rehydrate_priority) + { + case RehydratePriority::Unknown: + return ""; + case RehydratePriority::High: + return "High"; + case RehydratePriority::Standard: + return "Standard"; + default: + return std::string(); + } + } + + inline RehydratePriority RehydratePriorityFromString(const std::string& rehydrate_priority) + { + if (rehydrate_priority == "") + { + return RehydratePriority::Unknown; + } + if (rehydrate_priority == "High") + { + return RehydratePriority::High; + } + if (rehydrate_priority == "Standard") + { + return RehydratePriority::Standard; + } + throw std::runtime_error("cannot convert " + rehydrate_priority + " to RehydratePriority"); + } + + struct UserDelegationKey + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string SignedObjectId; + std::string SignedTenantId; + std::string SignedStartsOn; + std::string SignedExpiresOn; + std::string SignedService; + std::string SignedVersion; + std::string Value; + }; // struct UserDelegationKey + struct BlobBlockListInfo { std::string RequestId; @@ -704,6 +865,18 @@ namespace Azure { namespace Storage { namespace Blobs { BlobLeaseStatus LeaseStatus = BlobLeaseStatus::Unlocked; }; // struct BlobContainerProperties + struct BlobCopyInfo + { + std::string RequestId; + std::string Date; + std::string Version; + std::string ClientRequestId; + std::string ETag; + std::string LastModified; + std::string CopyId; + Blobs::CopyStatus CopyStatus = Blobs::CopyStatus::Unknown; + }; // struct BlobCopyInfo + struct BlobItem { std::string Name; @@ -817,7 +990,6 @@ namespace Azure { namespace Storage { namespace Blobs { public: struct ListBlobContainersOptions { - std::string Version; std::string Prefix; std::string Marker; int MaxResults = 0; @@ -830,14 +1002,7 @@ namespace Azure { namespace Storage { namespace Blobs { { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); request.AddHeader("Content-Length", "0"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); request.AddQueryParameter("comp", "list"); if (!options.Prefix.empty()) { @@ -871,6 +1036,10 @@ namespace Azure { namespace Storage { namespace Blobs { { throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); } + XmlReader reader( + reinterpret_cast(http_response.GetBodyBuffer().data()), + http_response.GetBodyBuffer().size()); + response = ListContainersSegmentFromXml(reader); response.Version = http_response.GetHeaders().at("x-ms-version"); response.Date = http_response.GetHeaders().at("Date"); response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); @@ -880,114 +1049,6 @@ namespace Azure { namespace Storage { namespace Blobs { { response.ClientRequestId = response_clientrequestid_iterator->second; } - // TODO: Think about how to initialize - // xmlInitParser(); - // TODO: Think about how to free doc on exception - - // TODO: Think about how to hanlde xml > 2GB - using namespace libXML2; - xmlDoc* doc = xmlReadMemory( - reinterpret_cast(http_response.GetBodyBuffer().data()), - int(http_response.GetBodyBuffer().size()), - nullptr, - nullptr, - 0); - if (doc == nullptr) - throw std::runtime_error("failed to parse response xml"); - - xmlNode* root = xmlDocGetRootElement(doc); - if (root == nullptr - || std::string(reinterpret_cast(root->name)) != "EnumerationResults") - throw std::runtime_error("failed to parse response xml"); - - enum - { - start_tag, - attribute, - content, - end_tag, - }; - - auto parse_xml_callback - = [&response, blob_container_item = BlobContainerItem(), in_metadata = false]( - const std::string& name, int type, const std::string& value) mutable { - if (type == start_tag && name == "Metadata") - in_metadata = true; - else if (type == end_tag && name == "Metadata") - in_metadata = false; - else if (type == content && in_metadata) - blob_container_item.Metadata.emplace(name, value); - else if (type == attribute && name == "ServiceEndpoint") - response.ServiceEndpoint = value; - else if (type == content && name == "Prefix") - response.Prefix = value; - else if (type == content && name == "Marker") - response.Marker = value; - else if (type == content && name == "MaxResults") - response.MaxResults = std::stoi(value); - else if (type == content && name == "NextMarker") - response.NextMarker = value; - else if (type == start_tag && name == "Container") - blob_container_item = BlobContainerItem(); - else if (type == end_tag && name == "Container") - response.BlobContainerItems.emplace_back(std::move(blob_container_item)); - else if (type == content && name == "Name") - blob_container_item.Name = value; - else if (type == content && name == "Last-Modified") - blob_container_item.LastModified = value; - else if (type == content && name == "Etag") - blob_container_item.ETag = value; - else if (type == content && name == "LeaseStatus") - blob_container_item.LeaseStatus = BlobLeaseStatusFromString(value); - else if (type == content && name == "LeaseState") - blob_container_item.LeaseState = BlobLeaseStateFromString(value); - else if (type == content && name == "LeaseDuration") - blob_container_item.LeaseDuration = value; - else if (type == content && name == "PublicAccess") - blob_container_item.AccessType = PublicAccessTypeFromString(value); - else if (type == content && name == "HasImmutabilityPolicy") - blob_container_item.HasImmutabilityPolicy = value == "true"; - else if (type == content && name == "HasLegalHold") - blob_container_item.HasLegalHold = value == "true"; - }; - - std::function parse_xml; - parse_xml = [&parse_xml, &parse_xml_callback](xmlNode* node) { - if (!(node->type == XML_ELEMENT_NODE || node->type == XML_ATTRIBUTE_NODE)) - return; - - std::string node_name(reinterpret_cast(node->name)); - parse_xml_callback(node_name, start_tag, ""); - - for (xmlAttr* prop = node->properties; prop; prop = prop->next) - { - std::string prop_name(reinterpret_cast(prop->name)); - std::string prop_value(reinterpret_cast(prop->children->content)); - parse_xml_callback(prop_name, attribute, prop_value); - } - - bool has_child_element = false; - for (xmlNode* child = node->children; child; child = child->next) - { - has_child_element |= child->type == XML_ELEMENT_NODE; - parse_xml(child); - } - - if (!has_child_element && node->children) - { - std::string node_content(reinterpret_cast(node->children->content)); - parse_xml_callback(node_name, content, node_content); - } - - parse_xml_callback(node_name, end_tag, ""); - }; - - parse_xml(root); - - xmlFreeDoc(doc); - - // TODO: Think about how to cleanup - // xmlCleanupParser(); return response; } @@ -1002,13 +1063,510 @@ namespace Azure { namespace Storage { namespace Blobs { return ListBlobContainersParseResponse(*response); } + struct GetUserDelegationKeyOptions + { + std::string StartsOn; + std::string ExpiresOn; + }; // struct GetUserDelegationKeyOptions + + static Azure::Core::Http::Request GetUserDelegationKeyConstructRequest( + const std::string& url, + const GetUserDelegationKeyOptions& options) + { + XmlWriter writer; + GetUserDelegationKeyOptionsToXml(writer, options); + std::string xml_body = writer.GetDocument(); + std::vector body_buffer(xml_body.begin(), xml_body.end()); + uint64_t body_buffer_length = body_buffer.size(); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Post, url, std::move(body_buffer)); + request.AddHeader("Content-Length", std::to_string(body_buffer_length)); + request.AddQueryParameter("restype", "service"); + request.AddQueryParameter("comp", "userdelegationkey"); + request.AddHeader("x-ms-version", "2019-07-07"); + unused(options); + return request; + } + + static UserDelegationKey GetUserDelegationKeyParseResponse( + Azure::Core::Http::Response& http_response) + { + UserDelegationKey response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 200)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + XmlReader reader( + reinterpret_cast(http_response.GetBodyBuffer().data()), + http_response.GetBodyBuffer().size()); + response = UserDelegationKeyFromXml(reader); + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + return response; + } + + static UserDelegationKey GetUserDelegationKey( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const GetUserDelegationKeyOptions& options) + { + auto request = GetUserDelegationKeyConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return GetUserDelegationKeyParseResponse(*response); + } + + private: + static ListContainersSegment ListContainersSegmentFromXml(XmlReader& reader) + { + ListContainersSegment ret; + enum class XmlTagName + { + k_EnumerationResults, + k_Prefix, + k_Marker, + k_NextMarker, + k_MaxResults, + k_Containers, + k_Container, + k_Unknown, + }; + std::vector 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, "EnumerationResults") == 0) + { + path.emplace_back(XmlTagName::k_EnumerationResults); + } + else if (std::strcmp(node.Name, "Prefix") == 0) + { + path.emplace_back(XmlTagName::k_Prefix); + } + else if (std::strcmp(node.Name, "Marker") == 0) + { + path.emplace_back(XmlTagName::k_Marker); + } + else if (std::strcmp(node.Name, "NextMarker") == 0) + { + path.emplace_back(XmlTagName::k_NextMarker); + } + else if (std::strcmp(node.Name, "MaxResults") == 0) + { + path.emplace_back(XmlTagName::k_MaxResults); + } + else if (std::strcmp(node.Name, "Containers") == 0) + { + path.emplace_back(XmlTagName::k_Containers); + } + else if (std::strcmp(node.Name, "Container") == 0) + { + path.emplace_back(XmlTagName::k_Container); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 3 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Containers && path[2] == XmlTagName::k_Container) + { + ret.BlobContainerItems.emplace_back(BlobContainerItemFromXml(reader)); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Prefix) + { + ret.Prefix = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Marker) + { + ret.Marker = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_NextMarker) + { + ret.NextMarker = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_MaxResults) + { + ret.MaxResults = std::stoi(node.Value); + } + } + else if (node.Type == XmlNodeType::Attribute) + { + if (path.size() == 1 && path[0] == XmlTagName::k_EnumerationResults + && std::strcmp(node.Name, "ServiceEndpoint") == 0) + { + ret.ServiceEndpoint = node.Value; + } + } + } + return ret; + } + + static UserDelegationKey UserDelegationKeyFromXml(XmlReader& reader) + { + UserDelegationKey ret; + enum class XmlTagName + { + k_UserDelegationKey, + k_SignedOid, + k_SignedTid, + k_SignedStart, + k_SignedExpiry, + k_SignedService, + k_SignedVersion, + k_Value, + k_Unknown, + }; + std::vector 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, "UserDelegationKey") == 0) + { + path.emplace_back(XmlTagName::k_UserDelegationKey); + } + else if (std::strcmp(node.Name, "SignedOid") == 0) + { + path.emplace_back(XmlTagName::k_SignedOid); + } + else if (std::strcmp(node.Name, "SignedTid") == 0) + { + path.emplace_back(XmlTagName::k_SignedTid); + } + else if (std::strcmp(node.Name, "SignedStart") == 0) + { + path.emplace_back(XmlTagName::k_SignedStart); + } + else if (std::strcmp(node.Name, "SignedExpiry") == 0) + { + path.emplace_back(XmlTagName::k_SignedExpiry); + } + else if (std::strcmp(node.Name, "SignedService") == 0) + { + path.emplace_back(XmlTagName::k_SignedService); + } + else if (std::strcmp(node.Name, "SignedVersion") == 0) + { + path.emplace_back(XmlTagName::k_SignedVersion); + } + else if (std::strcmp(node.Name, "Value") == 0) + { + path.emplace_back(XmlTagName::k_Value); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_SignedOid) + { + ret.SignedObjectId = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_SignedTid) + { + ret.SignedTenantId = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_SignedStart) + { + ret.SignedStartsOn = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_SignedExpiry) + { + ret.SignedExpiresOn = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_SignedService) + { + ret.SignedService = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_SignedVersion) + { + ret.SignedVersion = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_UserDelegationKey + && path[1] == XmlTagName::k_Value) + { + ret.Value = node.Value; + } + } + } + return ret; + } + + static BlobContainerItem BlobContainerItemFromXml(XmlReader& reader) + { + BlobContainerItem ret; + enum class XmlTagName + { + k_Name, + k_Properties, + k_Etag, + k_LastModified, + k_PublicAccess, + k_HasImmutabilityPolicy, + k_HasLegalHold, + k_LeaseStatus, + k_LeaseState, + k_LeaseDuration, + k_Metadata, + k_Unknown, + }; + std::vector 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, "Name") == 0) + { + path.emplace_back(XmlTagName::k_Name); + } + else if (std::strcmp(node.Name, "Properties") == 0) + { + path.emplace_back(XmlTagName::k_Properties); + } + else if (std::strcmp(node.Name, "Etag") == 0) + { + path.emplace_back(XmlTagName::k_Etag); + } + else if (std::strcmp(node.Name, "Last-Modified") == 0) + { + path.emplace_back(XmlTagName::k_LastModified); + } + else if (std::strcmp(node.Name, "PublicAccess") == 0) + { + path.emplace_back(XmlTagName::k_PublicAccess); + } + else if (std::strcmp(node.Name, "HasImmutabilityPolicy") == 0) + { + path.emplace_back(XmlTagName::k_HasImmutabilityPolicy); + } + else if (std::strcmp(node.Name, "HasLegalHold") == 0) + { + path.emplace_back(XmlTagName::k_HasLegalHold); + } + else if (std::strcmp(node.Name, "LeaseStatus") == 0) + { + path.emplace_back(XmlTagName::k_LeaseStatus); + } + else if (std::strcmp(node.Name, "LeaseState") == 0) + { + path.emplace_back(XmlTagName::k_LeaseState); + } + else if (std::strcmp(node.Name, "LeaseDuration") == 0) + { + path.emplace_back(XmlTagName::k_LeaseDuration); + } + else if (std::strcmp(node.Name, "Metadata") == 0) + { + path.emplace_back(XmlTagName::k_Metadata); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 1 && path[0] == XmlTagName::k_Metadata) + { + ret.Metadata = MetadataFromXml(reader); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 1 && path[0] == XmlTagName::k_Name) + { + ret.Name = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_Etag) + { + ret.ETag = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LastModified) + { + ret.LastModified = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_PublicAccess) + { + ret.AccessType = PublicAccessTypeFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_HasImmutabilityPolicy) + { + ret.HasImmutabilityPolicy = std::strcmp(node.Value, "true") == 0; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_HasLegalHold) + { + ret.HasLegalHold = std::strcmp(node.Value, "true") == 0; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LeaseStatus) + { + ret.LeaseStatus = BlobLeaseStatusFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LeaseState) + { + ret.LeaseState = BlobLeaseStateFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LeaseDuration) + { + ret.LeaseDuration = node.Value; + } + } + } + return ret; + } + + static std::map MetadataFromXml(XmlReader& reader) + { + std::map ret; + int depth = 0; + std::string key; + while (true) + { + auto node = reader.Read(); + if (node.Type == XmlNodeType::End) + { + break; + } + else if (node.Type == XmlNodeType::StartTag) + { + if (depth++ == 0) + { + key = node.Name; + } + } + else if (node.Type == XmlNodeType::EndTag) + { + if (depth-- == 0) + { + break; + } + } + else if (depth == 1 && node.Type == XmlNodeType::Text) + { + ret.emplace(std::move(key), std::string(node.Value)); + } + } + return ret; + } + + static void GetUserDelegationKeyOptionsToXml( + XmlWriter& writer, + const GetUserDelegationKeyOptions& options) + { + writer.Write(XmlNode{XmlNodeType::StartTag, "KeyInfo"}); + 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::EndTag}); + writer.Write(XmlNode{XmlNodeType::End}); + } + }; // class Service class Container { public: struct CreateOptions { - std::string Version; PublicAccessType AccessType = PublicAccessType::Private; std::map Metadata; }; // struct CreateOptions @@ -1020,14 +1578,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("restype", "container"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); for (const auto& pair : options.Metadata) { request.AddHeader("x-ms-meta-" + pair.first, pair.second); @@ -1077,7 +1628,8 @@ namespace Azure { namespace Storage { namespace Blobs { struct DeleteOptions { - std::string Version; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; }; // struct DeleteOptions static Azure::Core::Http::Request DeleteConstructRequest( @@ -1087,13 +1639,14 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("restype", "container"); - if (!options.Version.empty()) + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.IfModifiedSince.empty()) { - request.AddHeader("x-ms-version", options.Version); + request.AddHeader("If-Modified-Since", options.IfModifiedSince); } - else + if (!options.IfUnmodifiedSince.empty()) { - request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); } return request; } @@ -1133,7 +1686,6 @@ namespace Azure { namespace Storage { namespace Blobs { struct GetPropertiesOptions { - std::string Version; std::string EncryptionKey; std::string EncryptionKeySHA256; std::string EncryptionAlgorithm; @@ -1146,14 +1698,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("restype", "container"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); if (!options.EncryptionKey.empty()) { request.AddHeader("x-ms-encryption-key", options.EncryptionKey); @@ -1232,8 +1777,8 @@ namespace Azure { namespace Storage { namespace Blobs { struct SetMetadataOptions { - std::string Version; std::map Metadata; + std::string IfModifiedSince; }; // struct SetMetadataOptions static Azure::Core::Http::Request SetMetadataConstructRequest( @@ -1244,18 +1789,15 @@ namespace Azure { namespace Storage { namespace Blobs { request.AddHeader("Content-Length", "0"); request.AddQueryParameter("restype", "container"); request.AddQueryParameter("comp", "metadata"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); for (const auto& pair : options.Metadata) { request.AddHeader("x-ms-meta-" + pair.first, pair.second); } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } return request; } @@ -1296,7 +1838,6 @@ namespace Azure { namespace Storage { namespace Blobs { struct ListBlobsOptions { - std::string Version; std::string Prefix; std::string Delimiter; std::string Marker; @@ -1310,14 +1851,7 @@ namespace Azure { namespace Storage { namespace Blobs { { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); request.AddHeader("Content-Length", "0"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); request.AddQueryParameter("restype", "container"); request.AddQueryParameter("comp", "list"); if (!options.Prefix.empty()) @@ -1362,6 +1896,10 @@ namespace Azure { namespace Storage { namespace Blobs { { throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); } + XmlReader reader( + reinterpret_cast(http_response.GetBodyBuffer().data()), + http_response.GetBodyBuffer().size()); + response = BlobsFlatSegmentFromXml(reader); response.Version = http_response.GetHeaders().at("x-ms-version"); response.Date = http_response.GetHeaders().at("Date"); response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); @@ -1371,142 +1909,6 @@ namespace Azure { namespace Storage { namespace Blobs { { response.ClientRequestId = response_clientrequestid_iterator->second; } - // TODO: Think about how to initialize - // xmlInitParser(); - // TODO: Think about how to free doc on exception - - // TODO: Think about how to hanlde xml > 2GB - using namespace libXML2; - xmlDoc* doc = xmlReadMemory( - reinterpret_cast(http_response.GetBodyBuffer().data()), - int(http_response.GetBodyBuffer().size()), - nullptr, - nullptr, - 0); - if (doc == nullptr) - throw std::runtime_error("failed to parse response xml"); - - xmlNode* root = xmlDocGetRootElement(doc); - if (root == nullptr - || std::string(reinterpret_cast(root->name)) != "EnumerationResults") - throw std::runtime_error("failed to parse response xml"); - - enum - { - start_tag, - attribute, - content, - end_tag, - }; - - auto parse_xml_callback - = [&response, blob_item = BlobItem(), in_metadata = false]( - const std::string& name, int type, const std::string& value) mutable { - if (type == start_tag && name == "Metadata") - in_metadata = true; - else if (type == end_tag && name == "Metadata") - in_metadata = false; - else if (type == content && in_metadata) - blob_item.Metadata.emplace(name, value); - else if (type == attribute && name == "ServiceEndpoint") - response.ServiceEndpoint = value; - else if (type == attribute && name == "ContainerName") - response.Container = value; - else if (type == content && name == "Prefix") - response.Prefix = value; - else if (type == content && name == "Marker") - response.Marker = value; - else if (type == content && name == "MaxResults") - response.MaxResults = std::stoi(value); - else if (type == content && name == "Delimiter") - response.Delimiter = value; - else if (type == content && name == "NextMarker") - response.NextMarker = value; - else if (type == start_tag && name == "Blob") - blob_item = BlobItem(); - else if (type == end_tag && name == "Blob") - response.BlobItems.emplace_back(std::move(blob_item)); - else if (type == content && name == "Name") - blob_item.Name = value; - else if (type == content && name == "Deleted") - blob_item.Deleted = value == "true"; - else if (type == content && name == "Snapshot") - blob_item.Snapshot = value; - else if (type == content && name == "Creation-Time") - blob_item.CreationTime = value; - else if (type == content && name == "Last-Modified") - blob_item.LastModified = value; - else if (type == content && name == "Etag") - blob_item.ETag = value; - else if (type == content && name == "Content-Length") - blob_item.ContentLength = std::stoull(value); - else if (type == content && name == "BlobType") - blob_item.BlobType = BlobTypeFromString(value); - else if (type == content && name == "AccessTier") - blob_item.Tier = AccessTierFromString(value); - else if (type == content && name == "AccessTierInferred") - blob_item.AccessTierInferred = value == "true"; - else if (type == content && name == "LeaseStatus") - blob_item.LeaseStatus = BlobLeaseStatusFromString(value); - else if (type == content && name == "LeaseState") - blob_item.LeaseState = BlobLeaseStateFromString(value); - else if (type == content && name == "LeaseDuration") - blob_item.LeaseDuration = value; - else if (type == content && name == "ServerEncrypted") - blob_item.ServerEncrypted = value == "true"; - else if (type == content && name == "CustomerProvidedKeySha256") - blob_item.EncryptionKeySHA256 = value; - else if (type == content && name == "Content-Type") - blob_item.Properties.ContentType = value; - else if (type == content && name == "Content-Encoding") - blob_item.Properties.ContentEncoding = value; - else if (type == content && name == "Content-Language") - blob_item.Properties.ContentLanguage = value; - else if (type == content && name == "Content-MD5") - blob_item.Properties.ContentMD5 = value; - else if (type == content && name == "Cache-Control") - blob_item.Properties.CacheControl = value; - else if (type == content && name == "Content-Disposition") - blob_item.Properties.ContentDisposition = value; - }; - - std::function parse_xml; - parse_xml = [&parse_xml, &parse_xml_callback](xmlNode* node) { - if (!(node->type == XML_ELEMENT_NODE || node->type == XML_ATTRIBUTE_NODE)) - return; - - std::string node_name(reinterpret_cast(node->name)); - parse_xml_callback(node_name, start_tag, ""); - - for (xmlAttr* prop = node->properties; prop; prop = prop->next) - { - std::string prop_name(reinterpret_cast(prop->name)); - std::string prop_value(reinterpret_cast(prop->children->content)); - parse_xml_callback(prop_name, attribute, prop_value); - } - - bool has_child_element = false; - for (xmlNode* child = node->children; child; child = child->next) - { - has_child_element |= child->type == XML_ELEMENT_NODE; - parse_xml(child); - } - - if (!has_child_element && node->children) - { - std::string node_content(reinterpret_cast(node->children->content)); - parse_xml_callback(node_name, content, node_content); - } - - parse_xml_callback(node_name, end_tag, ""); - }; - - parse_xml(root); - - xmlFreeDoc(doc); - - // TODO: Think about how to cleanup - // xmlCleanupParser(); return response; } @@ -1521,17 +1923,464 @@ namespace Azure { namespace Storage { namespace Blobs { return ListBlobsParseResponse(*response); } + private: + static BlobsFlatSegment BlobsFlatSegmentFromXml(XmlReader& reader) + { + BlobsFlatSegment ret; + enum class XmlTagName + { + k_EnumerationResults, + k_Prefix, + k_Marker, + k_NextMarker, + k_MaxResults, + k_Delimiter, + k_Blobs, + k_Blob, + k_Unknown, + }; + std::vector 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, "EnumerationResults") == 0) + { + path.emplace_back(XmlTagName::k_EnumerationResults); + } + else if (std::strcmp(node.Name, "Prefix") == 0) + { + path.emplace_back(XmlTagName::k_Prefix); + } + else if (std::strcmp(node.Name, "Marker") == 0) + { + path.emplace_back(XmlTagName::k_Marker); + } + else if (std::strcmp(node.Name, "NextMarker") == 0) + { + path.emplace_back(XmlTagName::k_NextMarker); + } + else if (std::strcmp(node.Name, "MaxResults") == 0) + { + path.emplace_back(XmlTagName::k_MaxResults); + } + else if (std::strcmp(node.Name, "Delimiter") == 0) + { + path.emplace_back(XmlTagName::k_Delimiter); + } + else if (std::strcmp(node.Name, "Blobs") == 0) + { + path.emplace_back(XmlTagName::k_Blobs); + } + else if (std::strcmp(node.Name, "Blob") == 0) + { + path.emplace_back(XmlTagName::k_Blob); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 3 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Blobs && path[2] == XmlTagName::k_Blob) + { + ret.BlobItems.emplace_back(BlobItemFromXml(reader)); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Prefix) + { + ret.Prefix = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Marker) + { + ret.Marker = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_NextMarker) + { + ret.NextMarker = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_MaxResults) + { + ret.MaxResults = std::stoi(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_EnumerationResults + && path[1] == XmlTagName::k_Delimiter) + { + ret.Delimiter = node.Value; + } + } + else if (node.Type == XmlNodeType::Attribute) + { + if (path.size() == 1 && path[0] == XmlTagName::k_EnumerationResults + && std::strcmp(node.Name, "ServiceEndpoint") == 0) + { + ret.ServiceEndpoint = node.Value; + } + else if ( + path.size() == 1 && path[0] == XmlTagName::k_EnumerationResults + && std::strcmp(node.Name, "ContainerName") == 0) + { + ret.Container = node.Value; + } + } + } + return ret; + } + + static BlobItem BlobItemFromXml(XmlReader& reader) + { + BlobItem ret; + enum class XmlTagName + { + k_Name, + k_Deleted, + k_Snapshot, + k_Properties, + k_ContentType, + k_ContentEncoding, + k_ContentLanguage, + k_ContentMD5, + k_CacheControl, + k_ContentDisposition, + k_CreationTime, + k_LastModified, + k_Etag, + k_ContentLength, + k_BlobType, + k_AccessTier, + k_AccessTierInferred, + k_LeaseStatus, + k_LeaseState, + k_LeaseDuration, + k_ServerEncrypted, + k_EncryptionKeySHA256, + k_Metadata, + k_Unknown, + }; + std::vector 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, "Name") == 0) + { + path.emplace_back(XmlTagName::k_Name); + } + else if (std::strcmp(node.Name, "Deleted") == 0) + { + path.emplace_back(XmlTagName::k_Deleted); + } + else if (std::strcmp(node.Name, "Snapshot") == 0) + { + path.emplace_back(XmlTagName::k_Snapshot); + } + else if (std::strcmp(node.Name, "Properties") == 0) + { + path.emplace_back(XmlTagName::k_Properties); + } + else if (std::strcmp(node.Name, "Content-Type") == 0) + { + path.emplace_back(XmlTagName::k_ContentType); + } + else if (std::strcmp(node.Name, "Content-Encoding") == 0) + { + path.emplace_back(XmlTagName::k_ContentEncoding); + } + else if (std::strcmp(node.Name, "Content-Language") == 0) + { + path.emplace_back(XmlTagName::k_ContentLanguage); + } + else if (std::strcmp(node.Name, "Content-MD5") == 0) + { + path.emplace_back(XmlTagName::k_ContentMD5); + } + else if (std::strcmp(node.Name, "Cache-Control") == 0) + { + path.emplace_back(XmlTagName::k_CacheControl); + } + else if (std::strcmp(node.Name, "Content-Disposition") == 0) + { + path.emplace_back(XmlTagName::k_ContentDisposition); + } + else if (std::strcmp(node.Name, "Creation-Time") == 0) + { + path.emplace_back(XmlTagName::k_CreationTime); + } + else if (std::strcmp(node.Name, "Last-Modified") == 0) + { + path.emplace_back(XmlTagName::k_LastModified); + } + else if (std::strcmp(node.Name, "Etag") == 0) + { + path.emplace_back(XmlTagName::k_Etag); + } + else if (std::strcmp(node.Name, "Content-Length") == 0) + { + path.emplace_back(XmlTagName::k_ContentLength); + } + else if (std::strcmp(node.Name, "BlobType") == 0) + { + path.emplace_back(XmlTagName::k_BlobType); + } + else if (std::strcmp(node.Name, "AccessTier") == 0) + { + path.emplace_back(XmlTagName::k_AccessTier); + } + else if (std::strcmp(node.Name, "AccessTierInferred") == 0) + { + path.emplace_back(XmlTagName::k_AccessTierInferred); + } + else if (std::strcmp(node.Name, "LeaseStatus") == 0) + { + path.emplace_back(XmlTagName::k_LeaseStatus); + } + else if (std::strcmp(node.Name, "LeaseState") == 0) + { + path.emplace_back(XmlTagName::k_LeaseState); + } + else if (std::strcmp(node.Name, "LeaseDuration") == 0) + { + path.emplace_back(XmlTagName::k_LeaseDuration); + } + else if (std::strcmp(node.Name, "ServerEncrypted") == 0) + { + path.emplace_back(XmlTagName::k_ServerEncrypted); + } + else if (std::strcmp(node.Name, "EncryptionKeySHA256") == 0) + { + path.emplace_back(XmlTagName::k_EncryptionKeySHA256); + } + else if (std::strcmp(node.Name, "Metadata") == 0) + { + path.emplace_back(XmlTagName::k_Metadata); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 1 && path[0] == XmlTagName::k_Metadata) + { + ret.Metadata = MetadataFromXml(reader); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 1 && path[0] == XmlTagName::k_Name) + { + ret.Name = node.Value; + } + else if (path.size() == 1 && path[0] == XmlTagName::k_Deleted) + { + ret.Deleted = std::strcmp(node.Value, "true") == 0; + } + else if (path.size() == 1 && path[0] == XmlTagName::k_Snapshot) + { + ret.Snapshot = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ContentType) + { + ret.Properties.ContentType = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ContentEncoding) + { + ret.Properties.ContentEncoding = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ContentLanguage) + { + ret.Properties.ContentLanguage = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ContentMD5) + { + ret.Properties.ContentMD5 = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CacheControl) + { + ret.Properties.CacheControl = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ContentDisposition) + { + ret.Properties.ContentDisposition = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CreationTime) + { + ret.CreationTime = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LastModified) + { + ret.LastModified = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_Etag) + { + ret.ETag = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ContentLength) + { + ret.ContentLength = std::stoull(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_BlobType) + { + ret.BlobType = BlobTypeFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_AccessTier) + { + ret.Tier = AccessTierFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_AccessTierInferred) + { + ret.AccessTierInferred = std::strcmp(node.Value, "true") == 0; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LeaseStatus) + { + ret.LeaseStatus = BlobLeaseStatusFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LeaseState) + { + ret.LeaseState = BlobLeaseStateFromString(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_LeaseDuration) + { + ret.LeaseDuration = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ServerEncrypted) + { + ret.ServerEncrypted = std::strcmp(node.Value, "true") == 0; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_EncryptionKeySHA256) + { + ret.EncryptionKeySHA256 = node.Value; + } + } + } + return ret; + } + + static std::map MetadataFromXml(XmlReader& reader) + { + std::map ret; + int depth = 0; + std::string key; + while (true) + { + auto node = reader.Read(); + if (node.Type == XmlNodeType::End) + { + break; + } + else if (node.Type == XmlNodeType::StartTag) + { + if (depth++ == 0) + { + key = node.Name; + } + } + else if (node.Type == XmlNodeType::EndTag) + { + if (depth-- == 0) + { + break; + } + } + else if (depth == 1 && node.Type == XmlNodeType::Text) + { + ret.emplace(std::move(key), std::string(node.Value)); + } + } + return ret; + } + }; // class Container class Blob { public: struct DownloadOptions { - std::string Version; std::pair Range; std::string EncryptionKey; std::string EncryptionKeySHA256; std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct DownloadOptions static Azure::Core::Http::Request DownloadConstructRequest( @@ -1540,15 +2389,16 @@ namespace Azure { namespace Storage { namespace Blobs { { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); request.AddHeader("Content-Length", "0"); - if (!options.Version.empty()) + request.AddHeader("x-ms-version", "2019-07-07"); + if (options.Range.first == std::numeric_limits::max()) { - request.AddHeader("x-ms-version", options.Version); + // do nothing + } + else if (options.Range.second == std::numeric_limits::max()) + { + request.AddHeader("x-ms-range", "bytes=" + std::to_string(options.Range.first) + "-"); } else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } - if (options.Range.first <= options.Range.second) { request.AddHeader( "x-ms-range", @@ -1567,6 +2417,22 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } return request; } @@ -1704,8 +2570,11 @@ namespace Azure { namespace Storage { namespace Blobs { struct DeleteOptions { - std::string Version; DeleteSnapshotsOption DeleteSnapshots = DeleteSnapshotsOption::None; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct DeleteOptions static Azure::Core::Http::Request DeleteConstructRequest( @@ -1714,13 +2583,27 @@ namespace Azure { namespace Storage { namespace Blobs { { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, url); request.AddHeader("Content-Length", "0"); - if (!options.Version.empty()) + request.AddHeader("x-ms-version", "2019-07-07"); + auto options_deletesnapshots_str = DeleteSnapshotsOptionToString(options.DeleteSnapshots); + if (!options_deletesnapshots_str.empty()) { - request.AddHeader("x-ms-version", options.Version); + request.AddHeader("x-ms-delete-snapshots", options_deletesnapshots_str); } - else + if (!options.IfModifiedSince.empty()) { - request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); } return request; } @@ -1758,9 +2641,61 @@ namespace Azure { namespace Storage { namespace Blobs { return DeleteParseResponse(*response); } + struct UndeleteOptions + { + }; // struct UndeleteOptions + + static Azure::Core::Http::Request UndeleteConstructRequest( + const std::string& url, + const UndeleteOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddHeader("x-ms-version", "2019-07-07"); + request.AddQueryParameter("comp", "undelete"); + unused(options); + return request; + } + + static BasicResponse UndeleteParseResponse(Azure::Core::Http::Response& http_response) + { + BasicResponse response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 200)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + return response; + } + + static BasicResponse Undelete( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const UndeleteOptions& options) + { + auto request = UndeleteConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return UndeleteParseResponse(*response); + } + struct GetPropertiesOptions { - std::string Version; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct GetPropertiesOptions static Azure::Core::Http::Request GetPropertiesConstructRequest( @@ -1769,13 +2704,22 @@ namespace Azure { namespace Storage { namespace Blobs { { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, url); request.AddHeader("Content-Length", "0"); - if (!options.Version.empty()) + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.IfModifiedSince.empty()) { - request.AddHeader("x-ms-version", options.Version); + request.AddHeader("If-Modified-Since", options.IfModifiedSince); } - else + if (!options.IfUnmodifiedSince.empty()) { - request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); } return request; } @@ -1917,7 +2861,6 @@ namespace Azure { namespace Storage { namespace Blobs { struct SetHttpHeadersOptions { - std::string Version; std::string ContentType; std::string ContentEncoding; std::string ContentLanguage; @@ -1927,6 +2870,10 @@ namespace Azure { namespace Storage { namespace Blobs { std::string EncryptionKey; std::string EncryptionKeySHA256; std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct SetHttpHeadersOptions static Azure::Core::Http::Request SetHttpHeadersConstructRequest( @@ -1936,14 +2883,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "properties"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); if (!options.ContentType.empty()) { request.AddHeader("x-ms-blob-content-type", options.ContentType); @@ -1980,6 +2920,22 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } return request; } @@ -2026,11 +2982,14 @@ namespace Azure { namespace Storage { namespace Blobs { struct SetMetadataOptions { - std::string Version; std::map Metadata; std::string EncryptionKey; std::string EncryptionKeySHA256; std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct SetMetadataOptions static Azure::Core::Http::Request SetMetadataConstructRequest( @@ -2040,14 +2999,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); request.AddHeader("Content-Length", "0"); request.AddQueryParameter("comp", "metadata"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); for (const auto& pair : options.Metadata) { request.AddHeader("x-ms-meta-" + pair.first, pair.second); @@ -2064,6 +3016,22 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } return request; } @@ -2102,18 +3070,359 @@ namespace Azure { namespace Storage { namespace Blobs { return SetMetadataParseResponse(*response); } + struct SetAccessTierOptions + { + AccessTier Tier; + Blobs::RehydratePriority RehydratePriority = Blobs::RehydratePriority::Unknown; + }; // struct SetAccessTierOptions + + static Azure::Core::Http::Request SetAccessTierConstructRequest( + const std::string& url, + const SetAccessTierOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "tier"); + request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("x-ms-access-tier", AccessTierToString(options.Tier)); + auto options_rehydratepriority_str = RehydratePriorityToString(options.RehydratePriority); + if (!options_rehydratepriority_str.empty()) + { + request.AddHeader("x-ms-rehydrate-priority", options_rehydratepriority_str); + } + return request; + } + + static BasicResponse SetAccessTierParseResponse(Azure::Core::Http::Response& http_response) + { + BasicResponse response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 200 || http_status_code == 202)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + return response; + } + + static BasicResponse SetAccessTier( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const SetAccessTierOptions& options) + { + auto request = SetAccessTierConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return SetAccessTierParseResponse(*response); + } + + struct StartCopyFromUriOptions + { + std::map Metadata; + std::string SourceUri; + std::string LeaseId; + std::string SourceLeaseId; + AccessTier Tier = AccessTier::Unknown; + Blobs::RehydratePriority RehydratePriority = Blobs::RehydratePriority::Unknown; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + std::string SourceIfModifiedSince; + std::string SourceIfUnmodifiedSince; + std::string SourceIfMatch; + std::string SourceIfNoneMatch; + }; // struct StartCopyFromUriOptions + + static Azure::Core::Http::Request StartCopyFromUriConstructRequest( + const std::string& url, + const StartCopyFromUriOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddHeader("x-ms-version", "2019-07-07"); + for (const auto& pair : options.Metadata) + { + request.AddHeader("x-ms-meta-" + pair.first, pair.second); + } + request.AddHeader("x-ms-copy-source", options.SourceUri); + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.SourceLeaseId.empty()) + { + request.AddHeader("x-ms-source-lease-id", options.SourceLeaseId); + } + auto options_tier_str = AccessTierToString(options.Tier); + if (!options_tier_str.empty()) + { + request.AddHeader("x-ms-access-tier", options_tier_str); + } + auto options_rehydratepriority_str = RehydratePriorityToString(options.RehydratePriority); + if (!options_rehydratepriority_str.empty()) + { + request.AddHeader("x-ms-rehydrate-priority", options_rehydratepriority_str); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + if (!options.SourceIfModifiedSince.empty()) + { + request.AddHeader("x-ms-source-if-modified-since", options.SourceIfModifiedSince); + } + if (!options.SourceIfUnmodifiedSince.empty()) + { + request.AddHeader("x-ms-source-if-unmodified-since", options.SourceIfUnmodifiedSince); + } + if (!options.SourceIfMatch.empty()) + { + request.AddHeader("x-ms-source-if-match", options.SourceIfMatch); + } + if (!options.SourceIfNoneMatch.empty()) + { + request.AddHeader("x-ms-source-if-none-match", options.SourceIfNoneMatch); + } + return request; + } + + static BlobCopyInfo StartCopyFromUriParseResponse(Azure::Core::Http::Response& http_response) + { + BlobCopyInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 202)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + response.CopyId = http_response.GetHeaders().at("x-ms-copy-id"); + response.CopyStatus + = CopyStatusFromString(http_response.GetHeaders().at("x-ms-copy-status")); + return response; + } + + static BlobCopyInfo StartCopyFromUri( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const StartCopyFromUriOptions& options) + { + auto request = StartCopyFromUriConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return StartCopyFromUriParseResponse(*response); + } + + struct AbortCopyFromUriOptions + { + std::string CopyId; + std::string LeaseId; + }; // struct AbortCopyFromUriOptions + + static Azure::Core::Http::Request AbortCopyFromUriConstructRequest( + const std::string& url, + const AbortCopyFromUriOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddHeader("x-ms-version", "2019-07-07"); + request.AddQueryParameter("comp", "copy"); + request.AddQueryParameter("copyid", options.CopyId); + request.AddHeader("x-ms-copy-action", "abort"); + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + return request; + } + + static BasicResponse AbortCopyFromUriParseResponse(Azure::Core::Http::Response& http_response) + { + BasicResponse response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 204)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + return response; + } + + static BasicResponse AbortCopyFromUri( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const AbortCopyFromUriOptions& options) + { + auto request = AbortCopyFromUriConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return AbortCopyFromUriParseResponse(*response); + } + + struct CreateSnapshotOptions + { + std::map Metadata; + std::string LeaseId; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct CreateSnapshotOptions + + static Azure::Core::Http::Request CreateSnapshotConstructRequest( + const std::string& url, + const CreateSnapshotOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "snapshot"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + for (const auto& pair : options.Metadata) + { + request.AddHeader("x-ms-meta-" + pair.first, pair.second); + } + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static BlobSnapshotInfo CreateSnapshotParseResponse( + Azure::Core::Http::Response& http_response) + { + BlobSnapshotInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + response.Snapshot = http_response.GetHeaders().at("x-ms-snapshot"); + return response; + } + + static BlobSnapshotInfo CreateSnapshot( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const CreateSnapshotOptions& options) + { + auto request = CreateSnapshotConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return CreateSnapshotParseResponse(*response); + } + + private: }; // class Blob class BlockBlob { public: struct UploadOptions { - std::string Version; std::vector* BodyBuffer = nullptr; Azure::Core::Http::BodyStream* BodyStream = nullptr; std::string ContentMD5; std::string ContentCRC64; - Blobs::BlobType BlobType = Blobs::BlobType::Unknown; BlobHttpHeaders Properties; std::map Metadata; std::string LeaseId; @@ -2121,6 +3430,10 @@ namespace Azure { namespace Storage { namespace Blobs { std::string EncryptionKey; std::string EncryptionKeySHA256; std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct UploadOptions static Azure::Core::Http::Request UploadConstructRequest( @@ -2130,14 +3443,7 @@ namespace Azure { namespace Storage { namespace Blobs { auto request = Azure::Core::Http::Request( Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer); request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size())); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); if (!options.EncryptionKey.empty()) { request.AddHeader("x-ms-encryption-key", options.EncryptionKey); @@ -2190,12 +3496,28 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddHeader("x-ms-lease-id", options.LeaseId); } - request.AddHeader("x-ms-blob-type", BlobTypeToString(options.BlobType)); + request.AddHeader("x-ms-blob-type", "BlockBlob"); auto options_tier_str = AccessTierToString(options.Tier); if (!options_tier_str.empty()) { request.AddHeader("x-ms-access-tier", options_tier_str); } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } return request; } @@ -2258,7 +3580,6 @@ namespace Azure { namespace Storage { namespace Blobs { struct StageBlockOptions { - std::string Version; std::vector* BodyBuffer = nullptr; Azure::Core::Http::BodyStream* BodyStream = nullptr; std::string BlockId; @@ -2279,14 +3600,7 @@ namespace Azure { namespace Storage { namespace Blobs { request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size())); request.AddQueryParameter("comp", "block"); request.AddQueryParameter("blockid", options.BlockId); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); if (!options.ContentMD5.empty()) { request.AddHeader("Content-MD5", options.ContentMD5); @@ -2369,9 +3683,152 @@ namespace Azure { namespace Storage { namespace Blobs { return StageBlockParseResponse(*response); } + struct StageBlockFromUriOptions + { + std::string BlockId; + std::string SourceUri; + std::pair SourceRange; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string SourceIfModifiedSince; + std::string SourceIfUnmodifiedSince; + std::string SourceIfMatch; + std::string SourceIfNoneMatch; + }; // struct StageBlockFromUriOptions + + static Azure::Core::Http::Request StageBlockFromUriConstructRequest( + const std::string& url, + const StageBlockFromUriOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "block"); + request.AddQueryParameter("blockid", options.BlockId); + request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("x-ms-copy-source", options.SourceUri); + if (options.SourceRange.first + == std::numeric_limits::max()) + { + // do nothing + } + else if ( + options.SourceRange.second + == std::numeric_limits::max()) + { + request.AddHeader( + "x-ms-source_range", "bytes=" + std::to_string(options.SourceRange.first) + "-"); + } + else + { + request.AddHeader( + "x-ms-source_range", + "bytes=" + std::to_string(options.SourceRange.first) + "-" + + std::to_string(options.SourceRange.second)); + } + if (!options.ContentMD5.empty()) + { + request.AddHeader("x-ms-source-content-md5", options.ContentMD5); + } + if (!options.ContentCRC64.empty()) + { + request.AddHeader("x-ms-source-content-crc64", options.ContentCRC64); + } + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.SourceIfModifiedSince.empty()) + { + request.AddHeader("x-ms-source-if-modified-since", options.SourceIfModifiedSince); + } + if (!options.SourceIfUnmodifiedSince.empty()) + { + request.AddHeader("x-ms-source-if-unmodified-since", options.SourceIfUnmodifiedSince); + } + if (!options.SourceIfMatch.empty()) + { + request.AddHeader("x-ms-source-if-match", options.SourceIfMatch); + } + if (!options.SourceIfNoneMatch.empty()) + { + request.AddHeader("x-ms-source-if-none-match", options.SourceIfNoneMatch); + } + return request; + } + + static BlockInfo StageBlockFromUriParseResponse(Azure::Core::Http::Response& http_response) + { + BlockInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + auto response_contentmd5_iterator = http_response.GetHeaders().find("Content-MD5"); + if (response_contentmd5_iterator != http_response.GetHeaders().end()) + { + response.ContentMD5 = response_contentmd5_iterator->second; + } + auto response_contentcrc64_iterator = http_response.GetHeaders().find("x-ms-content-crc64"); + if (response_contentcrc64_iterator != http_response.GetHeaders().end()) + { + response.ContentCRC64 = response_contentcrc64_iterator->second; + } + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static BlockInfo StageBlockFromUri( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const StageBlockFromUriOptions& options) + { + auto request = StageBlockFromUriConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return StageBlockFromUriParseResponse(*response); + } + struct CommitBlockListOptions { - std::string Version; std::vector> BlockList; BlobHttpHeaders Properties; std::map Metadata; @@ -2379,6 +3836,10 @@ namespace Azure { namespace Storage { namespace Blobs { std::string EncryptionKey; std::string EncryptionKeySHA256; std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; AccessTier Tier = AccessTier::Unknown; }; // struct CommitBlockListOptions @@ -2386,61 +3847,16 @@ namespace Azure { namespace Storage { namespace Blobs { const std::string& url, const CommitBlockListOptions& options) { - // TODO: Think about how to initialize - // xmlInitParser(); - // TODO: Think about how to free doc on exception - using namespace libXML2; - xmlDocPtr doc = xmlNewDoc(BAD_CAST("1.0")); - xmlNodePtr block_list_node = xmlNewNode(nullptr, BAD_CAST("BlockList")); - xmlDocSetRootElement(doc, block_list_node); - - for (const auto& block : options.BlockList) - { - const char* tag_name = nullptr; - if (block.first == BlockType::Uncommitted) - tag_name = "Uncommitted"; - else if (block.first == BlockType::Committed) - tag_name = "Committed"; - else if (block.first == BlockType::Latest) - tag_name = "Latest"; - else - throw std::runtime_error("unexpected block type"); - - xmlNewChild(block_list_node, nullptr, BAD_CAST(tag_name), BAD_CAST(block.second.data())); - } - - xmlChar* xml_dump; - int xml_dump_size; - xmlDocDumpMemory(doc, &xml_dump, &xml_dump_size); - xmlFreeDoc(doc); - if (xml_dump == nullptr) - { - throw std::runtime_error("failed to allocate memory when building xml body"); - } - - // TODO: Think about how to free memory - // xmlFree(xml_dump); - - // TODO: Think about how to cleanup - // xmlCleanupParser(); - - // TODO: Think about how to avoid copy - std::vector body_buffer( - static_cast(xml_dump), - static_cast(xml_dump) + xml_dump_size); - // TODO: Set Content-MD5 or x-ms-content-crc64 header - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, body_buffer); - request.AddHeader("Content-Length", std::to_string(body_buffer.size())); + XmlWriter writer; + CommitBlockListOptionsToXml(writer, options); + std::string xml_body = writer.GetDocument(); + std::vector body_buffer(xml_body.begin(), xml_body.end()); + uint64_t body_buffer_length = body_buffer.size(); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Put, url, std::move(body_buffer)); + request.AddHeader("Content-Length", std::to_string(body_buffer_length)); request.AddQueryParameter("comp", "blocklist"); - if (!options.Version.empty()) - { - request.AddHeader("x-ms-version", options.Version); - } - else - { - request.AddHeader("x-ms-version", "2019-07-07"); - } + request.AddHeader("x-ms-version", "2019-07-07"); if (!options.Properties.ContentType.empty()) { request.AddHeader("x-ms-blob-content-type", options.Properties.ContentType); @@ -2490,6 +3906,22 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddHeader("x-ms-access-tier", options_tier_str); } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } return request; } @@ -2543,8 +3975,11 @@ namespace Azure { namespace Storage { namespace Blobs { struct GetBlockListOptions { - std::string Version; BlockListTypeOption ListType = BlockListTypeOption::All; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; }; // struct GetBlockListOptions static Azure::Core::Http::Request GetBlockListConstructRequest( @@ -2559,13 +3994,22 @@ namespace Azure { namespace Storage { namespace Blobs { { request.AddQueryParameter("blocklisttype", block_list_type_option); } - if (!options.Version.empty()) + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.IfModifiedSince.empty()) { - request.AddHeader("x-ms-version", options.Version); + request.AddHeader("If-Modified-Since", options.IfModifiedSince); } - else + if (!options.IfUnmodifiedSince.empty()) { - request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); } return request; } @@ -2580,6 +4024,842 @@ namespace Azure { namespace Storage { namespace Blobs { { throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); } + XmlReader reader( + reinterpret_cast(http_response.GetBodyBuffer().data()), + http_response.GetBodyBuffer().size()); + response = BlobBlockListInfoFromXml(reader); + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + response.ContentType = http_response.GetHeaders().at("Content-Type"); + response.ContentLength + = std::stoull(http_response.GetHeaders().at("x-ms-blob-content-length")); + return response; + } + + static BlobBlockListInfo GetBlockList( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const GetBlockListOptions& options) + { + auto request = GetBlockListConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return GetBlockListParseResponse(*response); + } + + private: + static BlobBlockListInfo BlobBlockListInfoFromXml(XmlReader& reader) + { + BlobBlockListInfo ret; + enum class XmlTagName + { + k_BlockList, + k_CommittedBlocks, + k_Block, + k_UncommittedBlocks, + k_Unknown, + }; + std::vector 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, "BlockList") == 0) + { + path.emplace_back(XmlTagName::k_BlockList); + } + else if (std::strcmp(node.Name, "CommittedBlocks") == 0) + { + path.emplace_back(XmlTagName::k_CommittedBlocks); + } + else if (std::strcmp(node.Name, "Block") == 0) + { + path.emplace_back(XmlTagName::k_Block); + } + else if (std::strcmp(node.Name, "UncommittedBlocks") == 0) + { + path.emplace_back(XmlTagName::k_UncommittedBlocks); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 3 && path[0] == XmlTagName::k_BlockList + && path[1] == XmlTagName::k_CommittedBlocks && path[2] == XmlTagName::k_Block) + { + ret.CommittedBlocks.emplace_back(BlobBlockFromXml(reader)); + path.pop_back(); + } + else if ( + path.size() == 3 && path[0] == XmlTagName::k_BlockList + && path[1] == XmlTagName::k_UncommittedBlocks && path[2] == XmlTagName::k_Block) + { + ret.UncommittedBlocks.emplace_back(BlobBlockFromXml(reader)); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + } + } + return ret; + } + + static BlobBlock BlobBlockFromXml(XmlReader& reader) + { + BlobBlock ret; + enum class XmlTagName + { + k_Name, + k_Size, + k_Unknown, + }; + std::vector 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, "Name") == 0) + { + path.emplace_back(XmlTagName::k_Name); + } + else if (std::strcmp(node.Name, "Size") == 0) + { + path.emplace_back(XmlTagName::k_Size); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + } + else if (node.Type == XmlNodeType::Text) + { + if (path.size() == 1 && path[0] == XmlTagName::k_Name) + { + ret.Name = node.Value; + } + else if (path.size() == 1 && path[0] == XmlTagName::k_Size) + { + ret.Size = std::stoull(node.Value); + } + } + } + return ret; + } + + static void CommitBlockListOptionsToXml( + XmlWriter& writer, + const CommitBlockListOptions& options) + { + writer.Write(XmlNode{XmlNodeType::StartTag, "BlockList"}); + for (const auto& i : options.BlockList) + { + writer.Write( + XmlNode{XmlNodeType::StartTag, BlockTypeToString(i.first).data(), i.second.data()}); + } + writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::End}); + } + + }; // class BlockBlob + + class PageBlob { + public: + struct CreateOptions + { + uint64_t BlobContentLength; + uint64_t SequenceNumber = 0; + BlobHttpHeaders Properties; + std::map Metadata; + std::string LeaseId; + AccessTier Tier = AccessTier::Unknown; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct CreateOptions + + static Azure::Core::Http::Request CreateConstructRequest( + const std::string& url, + const CreateOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.Properties.ContentType.empty()) + { + request.AddHeader("x-ms-blob-content-type", options.Properties.ContentType); + } + if (!options.Properties.ContentEncoding.empty()) + { + request.AddHeader("x-ms-blob-content-encoding", options.Properties.ContentEncoding); + } + if (!options.Properties.ContentLanguage.empty()) + { + request.AddHeader("x-ms-blob-content-language", options.Properties.ContentLanguage); + } + if (!options.Properties.CacheControl.empty()) + { + request.AddHeader("x-ms-blob-cache-control", options.Properties.CacheControl); + } + if (!options.Properties.ContentMD5.empty()) + { + request.AddHeader("x-ms-blob-content-md5", options.Properties.ContentMD5); + } + if (!options.Properties.ContentDisposition.empty()) + { + request.AddHeader("x-ms-blob-content-disposition", options.Properties.ContentDisposition); + } + for (const auto& pair : options.Metadata) + { + request.AddHeader("x-ms-meta-" + pair.first, pair.second); + } + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + request.AddHeader("x-ms-blob-type", "PageBlob"); + request.AddHeader("x-ms-blob-content-length", std::to_string(options.BlobContentLength)); + request.AddHeader("x-ms-blob-sequence-number", std::to_string(options.SequenceNumber)); + auto options_tier_str = AccessTierToString(options.Tier); + if (!options_tier_str.empty()) + { + request.AddHeader("x-ms-access-tier", options_tier_str); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static BlobContentInfo CreateParseResponse(Azure::Core::Http::Response& http_response) + { + BlobContentInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_contentmd5_iterator = http_response.GetHeaders().find("Content-MD5"); + if (response_contentmd5_iterator != http_response.GetHeaders().end()) + { + response.ContentMD5 = response_contentmd5_iterator->second; + } + auto response_contentcrc64_iterator = http_response.GetHeaders().find("x-ms-content-crc64"); + if (response_contentcrc64_iterator != http_response.GetHeaders().end()) + { + response.ContentCRC64 = response_contentcrc64_iterator->second; + } + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static BlobContentInfo Create( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const CreateOptions& options) + { + auto request = CreateConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return CreateParseResponse(*response); + } + + struct UploadPagesOptions + { + std::vector* BodyBuffer = nullptr; + Azure::Core::Http::BodyStream* BodyStream = nullptr; + std::pair Range; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct UploadPagesOptions + + static Azure::Core::Http::Request UploadPagesConstructRequest( + const std::string& url, + const UploadPagesOptions& options) + { + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer); + request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size())); + request.AddQueryParameter("comp", "page"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (options.Range.first == std::numeric_limits::max()) + { + // do nothing + } + else if (options.Range.second == std::numeric_limits::max()) + { + request.AddHeader("x-ms-range", "bytes=" + std::to_string(options.Range.first) + "-"); + } + else + { + request.AddHeader( + "x-ms-range", + "bytes=" + std::to_string(options.Range.first) + "-" + + std::to_string(options.Range.second)); + } + if (!options.ContentMD5.empty()) + { + request.AddHeader("Content-MD5", options.ContentMD5); + } + if (!options.ContentCRC64.empty()) + { + request.AddHeader("x-ms-content-crc64", options.ContentCRC64); + } + request.AddHeader("x-ms-page-write", "update"); + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static PageInfo UploadPagesParseResponse(Azure::Core::Http::Response& http_response) + { + PageInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_contentmd5_iterator = http_response.GetHeaders().find("Content-MD5"); + if (response_contentmd5_iterator != http_response.GetHeaders().end()) + { + response.ContentMD5 = response_contentmd5_iterator->second; + } + auto response_contentcrc64_iterator = http_response.GetHeaders().find("x-ms-content-crc64"); + if (response_contentcrc64_iterator != http_response.GetHeaders().end()) + { + response.ContentCRC64 = response_contentcrc64_iterator->second; + } + auto response_sequencenumber_iterator + = http_response.GetHeaders().find("x-ms-blob-sequence-number"); + if (response_sequencenumber_iterator != http_response.GetHeaders().end()) + { + response.SequenceNumber = std::stoull(response_sequencenumber_iterator->second); + } + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static PageInfo UploadPages( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const UploadPagesOptions& options) + { + auto request = UploadPagesConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return UploadPagesParseResponse(*response); + } + + struct UploadPagesFromUriOptions + { + std::string SourceUri; + std::pair SourceRange; + std::pair Range; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct UploadPagesFromUriOptions + + static Azure::Core::Http::Request UploadPagesFromUriConstructRequest( + const std::string& url, + const UploadPagesFromUriOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "page"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (options.Range.first == std::numeric_limits::max()) + { + // do nothing + } + else if (options.Range.second == std::numeric_limits::max()) + { + request.AddHeader("x-ms-range", "bytes=" + std::to_string(options.Range.first) + "-"); + } + else + { + request.AddHeader( + "x-ms-range", + "bytes=" + std::to_string(options.Range.first) + "-" + + std::to_string(options.Range.second)); + } + request.AddHeader("x-ms-copy-source", options.SourceUri); + if (options.SourceRange.first + == std::numeric_limits::max()) + { + // do nothing + } + else if ( + options.SourceRange.second + == std::numeric_limits::max()) + { + request.AddHeader( + "x-ms-source-range", "bytes=" + std::to_string(options.SourceRange.first) + "-"); + } + else + { + request.AddHeader( + "x-ms-source-range", + "bytes=" + std::to_string(options.SourceRange.first) + "-" + + std::to_string(options.SourceRange.second)); + } + if (!options.ContentMD5.empty()) + { + request.AddHeader("x-ms-source-content-md5", options.ContentMD5); + } + if (!options.ContentCRC64.empty()) + { + request.AddHeader("x-ms-source-content-crc64", options.ContentCRC64); + } + request.AddHeader("x-ms-page-write", "update"); + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static PageInfo UploadPagesFromUriParseResponse(Azure::Core::Http::Response& http_response) + { + PageInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_contentmd5_iterator = http_response.GetHeaders().find("Content-MD5"); + if (response_contentmd5_iterator != http_response.GetHeaders().end()) + { + response.ContentMD5 = response_contentmd5_iterator->second; + } + auto response_contentcrc64_iterator = http_response.GetHeaders().find("x-ms-content-crc64"); + if (response_contentcrc64_iterator != http_response.GetHeaders().end()) + { + response.ContentCRC64 = response_contentcrc64_iterator->second; + } + auto response_sequencenumber_iterator + = http_response.GetHeaders().find("x-ms-blob-sequence-number"); + if (response_sequencenumber_iterator != http_response.GetHeaders().end()) + { + response.SequenceNumber = std::stoull(response_sequencenumber_iterator->second); + } + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static PageInfo UploadPagesFromUri( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const UploadPagesFromUriOptions& options) + { + auto request = UploadPagesFromUriConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return UploadPagesFromUriParseResponse(*response); + } + + struct ClearPagesOptions + { + std::pair Range; + std::string LeaseId; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct ClearPagesOptions + + static Azure::Core::Http::Request ClearPagesConstructRequest( + const std::string& url, + const ClearPagesOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "page"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (options.Range.first == std::numeric_limits::max()) + { + // do nothing + } + else if (options.Range.second == std::numeric_limits::max()) + { + request.AddHeader("x-ms-range", "bytes=" + std::to_string(options.Range.first) + "-"); + } + else + { + request.AddHeader( + "x-ms-range", + "bytes=" + std::to_string(options.Range.first) + "-" + + std::to_string(options.Range.second)); + } + request.AddHeader("x-ms-page-write", "clear"); + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static PageInfo ClearPagesParseResponse(Azure::Core::Http::Response& http_response) + { + PageInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_sequencenumber_iterator + = http_response.GetHeaders().find("x-ms-blob-sequence-number"); + if (response_sequencenumber_iterator != http_response.GetHeaders().end()) + { + response.SequenceNumber = std::stoull(response_sequencenumber_iterator->second); + } + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static PageInfo ClearPages( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const ClearPagesOptions& options) + { + auto request = ClearPagesConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return ClearPagesParseResponse(*response); + } + + struct ResizeOptions + { + uint64_t BlobContentLength; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct ResizeOptions + + static Azure::Core::Http::Request ResizeConstructRequest( + const std::string& url, + const ResizeOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "properties"); + request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("x-ms-blob-content-length", std::to_string(options.BlobContentLength)); + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static PageBlobInfo ResizeParseResponse(Azure::Core::Http::Response& http_response) + { + PageBlobInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 200)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } response.Version = http_response.GetHeaders().at("x-ms-version"); response.Date = http_response.GetHeaders().at("Date"); response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); @@ -2591,115 +4871,663 @@ namespace Azure { namespace Storage { namespace Blobs { } response.ETag = http_response.GetHeaders().at("ETag"); response.LastModified = http_response.GetHeaders().at("Last-Modified"); - response.ContentType = http_response.GetHeaders().at("Content-Type"); - response.ContentLength - = std::stoull(http_response.GetHeaders().at("x-ms-blob-content-length")); - // TODO: Think about how to initialize - // xmlInitParser(); - // TODO: Think about how to free doc on exception - - // TODO: Think about how to hanlde xml > 2GB - using namespace libXML2; - xmlDoc* doc = xmlReadMemory( - reinterpret_cast(http_response.GetBodyBuffer().data()), - int(http_response.GetBodyBuffer().size()), - nullptr, - nullptr, - 0); - if (doc == nullptr) - throw std::runtime_error("failed to parse response xml"); - - xmlNode* root = xmlDocGetRootElement(doc); - if (root == nullptr - || std::string(reinterpret_cast(root->name)) != "BlockList") - throw std::runtime_error("failed to parse response xml"); - - enum + auto response_sequencenumber_iterator + = http_response.GetHeaders().find("x-ms-blob-sequence-number"); + if (response_sequencenumber_iterator != http_response.GetHeaders().end()) { - start_tag, - attribute, - content, - end_tag, - }; - - auto parse_xml_callback - = [&response, - blob_block = BlobBlock(), - in_committed_block = false, - in_uncommitted_block - = false](const std::string& name, int type, const std::string& value) mutable { - if (type == start_tag && name == "CommittedBlocks") - in_committed_block = true; - else if (type == end_tag && name == "CommittedBlocks") - in_committed_block = false; - else if (type == start_tag && name == "UncommittedBlocks") - in_uncommitted_block = true; - else if (type == end_tag && name == "UncommittedBlocks") - in_uncommitted_block = false; - else if (type == start_tag && name == "Block") - blob_block = BlobBlock(); - else if (type == end_tag && name == "Block" && in_committed_block) - response.CommittedBlocks.emplace_back(std::move(blob_block)); - else if (type == end_tag && name == "Block" && in_uncommitted_block) - response.UncommittedBlocks.emplace_back(std::move(blob_block)); - else if (type == content && name == "Name") - blob_block.Name = value; - else if (type == content && name == "Size") - blob_block.Size = std::stoull(value); - }; - - std::function parse_xml; - parse_xml = [&parse_xml, &parse_xml_callback](xmlNode* node) { - if (!(node->type == XML_ELEMENT_NODE || node->type == XML_ATTRIBUTE_NODE)) - return; - - std::string node_name(reinterpret_cast(node->name)); - parse_xml_callback(node_name, start_tag, ""); - - for (xmlAttr* prop = node->properties; prop; prop = prop->next) - { - std::string prop_name(reinterpret_cast(prop->name)); - std::string prop_value(reinterpret_cast(prop->children->content)); - parse_xml_callback(prop_name, attribute, prop_value); - } - - bool has_child_element = false; - for (xmlNode* child = node->children; child; child = child->next) - { - has_child_element |= child->type == XML_ELEMENT_NODE; - parse_xml(child); - } - - if (!has_child_element && node->children) - { - std::string node_content(reinterpret_cast(node->children->content)); - parse_xml_callback(node_name, content, node_content); - } - - parse_xml_callback(node_name, end_tag, ""); - }; - - parse_xml(root); - - xmlFreeDoc(doc); - - // TODO: Think about how to cleanup - // xmlCleanupParser(); + response.SequenceNumber = std::stoull(response_sequencenumber_iterator->second); + } return response; } - static BlobBlockListInfo GetBlockList( + static PageBlobInfo Resize( Azure::Core::Context context, Azure::Core::Http::HttpPipeline& pipeline, const std::string& url, - const GetBlockListOptions& options) + const ResizeOptions& options) { - auto request = GetBlockListConstructRequest(url, options); + auto request = ResizeConstructRequest(url, options); auto response = pipeline.Send(context, request); - return GetBlockListParseResponse(*response); + return ResizeParseResponse(*response); } - }; // class BlockBlob + struct GetPageRangesOptions + { + std::string PreviousSnapshot; + std::string PreviousSnapshotUrl; + std::pair Range; + std::string LeaseId; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct GetPageRangesOptions + + static Azure::Core::Http::Request GetPageRangesConstructRequest( + const std::string& url, + const GetPageRangesOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "pagelist"); + if (!options.PreviousSnapshot.empty()) + { + request.AddQueryParameter("prevsnapshot", options.PreviousSnapshot); + } + request.AddHeader("x-ms-version", "2019-07-07"); + if (options.Range.first == std::numeric_limits::max()) + { + // do nothing + } + else if (options.Range.second == std::numeric_limits::max()) + { + request.AddHeader("x-ms-range", "bytes=" + std::to_string(options.Range.first) + "-"); + } + else + { + request.AddHeader( + "x-ms-range", + "bytes=" + std::to_string(options.Range.first) + "-" + + std::to_string(options.Range.second)); + } + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (!options.PreviousSnapshotUrl.empty()) + { + request.AddHeader("x-ms-previous-snapshot-url", options.PreviousSnapshotUrl); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static PageRangesInfo GetPageRangesParseResponse(Azure::Core::Http::Response& http_response) + { + PageRangesInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 200)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + XmlReader reader( + reinterpret_cast(http_response.GetBodyBuffer().data()), + http_response.GetBodyBuffer().size()); + response = PageRangesInfoFromXml(reader); + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + response.BlobContentLength + = std::stoull(http_response.GetHeaders().at("x-ms-blob-content-length")); + return response; + } + + static PageRangesInfo GetPageRanges( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const GetPageRangesOptions& options) + { + auto request = GetPageRangesConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return GetPageRangesParseResponse(*response); + } + + struct CopyIncrementalOptions + { + std::string CopySource; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct CopyIncrementalOptions + + static Azure::Core::Http::Request CopyIncrementalConstructRequest( + const std::string& url, + const CopyIncrementalOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddQueryParameter("comp", "incrementalcopy"); + request.AddHeader("x-ms-version", "2019-07-07"); + request.AddHeader("x-ms-copy-source", options.CopySource); + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static BlobCopyInfo CopyIncrementalParseResponse(Azure::Core::Http::Response& http_response) + { + BlobCopyInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 202)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + response.CopyId = http_response.GetHeaders().at("x-ms-copy-id"); + response.CopyStatus + = CopyStatusFromString(http_response.GetHeaders().at("x-ms-copy-status")); + return response; + } + + static BlobCopyInfo CopyIncremental( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const CopyIncrementalOptions& options) + { + auto request = CopyIncrementalConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return CopyIncrementalParseResponse(*response); + } + + private: + static PageRangesInfo PageRangesInfoFromXml(XmlReader& reader) + { + PageRangesInfo ret; + enum class XmlTagName + { + k_PageList, + k_PageRange, + k_ClearRange, + k_Unknown, + }; + std::vector 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, "PageList") == 0) + { + path.emplace_back(XmlTagName::k_PageList); + } + else if (std::strcmp(node.Name, "PageRange") == 0) + { + path.emplace_back(XmlTagName::k_PageRange); + } + else if (std::strcmp(node.Name, "ClearRange") == 0) + { + path.emplace_back(XmlTagName::k_ClearRange); + } + else + { + path.emplace_back(XmlTagName::k_Unknown); + } + if (path.size() == 2 && path[0] == XmlTagName::k_PageList + && path[1] == XmlTagName::k_PageRange) + { + ret.PageRange.emplace_back(PageRangeFromXml(reader)); + path.pop_back(); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_PageList + && path[1] == XmlTagName::k_ClearRange) + { + ret.ClearRange.emplace_back(ClearRangeFromXml(reader)); + path.pop_back(); + } + } + else if (node.Type == XmlNodeType::Text) + { + } + } + return ret; + } + + static std::pair ClearRangeFromXml(XmlReader& reader) + { + int depth = 0; + bool is_start = false; + bool is_end = false; + uint64_t start; + uint64_t end; + while (true) + { + auto node = reader.Read(); + if (node.Type == XmlNodeType::End) + { + break; + } + else if (node.Type == XmlNodeType::StartTag && strcmp(node.Name, "Start") == 0) + { + is_start = true; + } + else if (node.Type == XmlNodeType::StartTag && strcmp(node.Name, "End") == 0) + { + is_end = true; + } + else if (node.Type == XmlNodeType::EndTag) + { + is_start = false; + is_end = false; + if (depth-- == 0) + { + break; + } + } + else if (depth == 1 && node.Type == XmlNodeType::Text) + { + if (is_start) + { + start = std::stoull(node.Name); + } + else if (is_end) + { + end = std::stoull(node.Name); + } + } + } + return std::make_pair(start, end); + } + + static std::pair PageRangeFromXml(XmlReader& reader) + { + int depth = 0; + bool is_start = false; + bool is_end = false; + uint64_t start; + uint64_t end; + while (true) + { + auto node = reader.Read(); + if (node.Type == XmlNodeType::End) + { + break; + } + else if (node.Type == XmlNodeType::StartTag && strcmp(node.Name, "Start") == 0) + { + is_start = true; + } + else if (node.Type == XmlNodeType::StartTag && strcmp(node.Name, "End") == 0) + { + is_end = true; + } + else if (node.Type == XmlNodeType::EndTag) + { + is_start = false; + is_end = false; + if (depth-- == 0) + { + break; + } + } + else if (depth == 1 && node.Type == XmlNodeType::Text) + { + if (is_start) + { + start = std::stoull(node.Name); + } + else if (is_end) + { + end = std::stoull(node.Name); + } + } + } + return std::make_pair(start, end); + } + + }; // class PageBlob + + class AppendBlob { + public: + struct CreateOptions + { + BlobHttpHeaders Properties; + std::map Metadata; + std::string LeaseId; + AccessTier Tier = AccessTier::Unknown; + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct CreateOptions + + static Azure::Core::Http::Request CreateConstructRequest( + const std::string& url, + const CreateOptions& options) + { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url); + request.AddHeader("Content-Length", "0"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.Properties.ContentType.empty()) + { + request.AddHeader("x-ms-blob-content-type", options.Properties.ContentType); + } + if (!options.Properties.ContentEncoding.empty()) + { + request.AddHeader("x-ms-blob-content-encoding", options.Properties.ContentEncoding); + } + if (!options.Properties.ContentLanguage.empty()) + { + request.AddHeader("x-ms-blob-content-language", options.Properties.ContentLanguage); + } + if (!options.Properties.CacheControl.empty()) + { + request.AddHeader("x-ms-blob-cache-control", options.Properties.CacheControl); + } + if (!options.Properties.ContentMD5.empty()) + { + request.AddHeader("x-ms-blob-content-md5", options.Properties.ContentMD5); + } + if (!options.Properties.ContentDisposition.empty()) + { + request.AddHeader("x-ms-blob-content-disposition", options.Properties.ContentDisposition); + } + for (const auto& pair : options.Metadata) + { + request.AddHeader("x-ms-meta-" + pair.first, pair.second); + } + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + request.AddHeader("x-ms-blob-type", "AppendBlob"); + auto options_tier_str = AccessTierToString(options.Tier); + if (!options_tier_str.empty()) + { + request.AddHeader("x-ms-access-tier", options_tier_str); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static BlobContentInfo CreateParseResponse(Azure::Core::Http::Response& http_response) + { + BlobContentInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_contentmd5_iterator = http_response.GetHeaders().find("Content-MD5"); + if (response_contentmd5_iterator != http_response.GetHeaders().end()) + { + response.ContentMD5 = response_contentmd5_iterator->second; + } + auto response_contentcrc64_iterator = http_response.GetHeaders().find("x-ms-content-crc64"); + if (response_contentcrc64_iterator != http_response.GetHeaders().end()) + { + response.ContentCRC64 = response_contentcrc64_iterator->second; + } + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static BlobContentInfo Create( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const CreateOptions& options) + { + auto request = CreateConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return CreateParseResponse(*response); + } + + struct AppendBlockOptions + { + std::vector* BodyBuffer = nullptr; + Azure::Core::Http::BodyStream* BodyStream = nullptr; + std::string ContentMD5; + std::string ContentCRC64; + std::string LeaseId; + uint64_t MaxSize = std::numeric_limits::max(); + uint64_t AppendPosition = std::numeric_limits::max(); + std::string EncryptionKey; + std::string EncryptionKeySHA256; + std::string EncryptionAlgorithm; + std::string IfModifiedSince; + std::string IfUnmodifiedSince; + std::string IfMatch; + std::string IfNoneMatch; + }; // struct AppendBlockOptions + + static Azure::Core::Http::Request AppendBlockConstructRequest( + const std::string& url, + const AppendBlockOptions& options) + { + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer); + request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size())); + request.AddQueryParameter("comp", "appendblock"); + request.AddHeader("x-ms-version", "2019-07-07"); + if (!options.ContentMD5.empty()) + { + request.AddHeader("Content-MD5", options.ContentMD5); + } + if (!options.ContentCRC64.empty()) + { + request.AddHeader("x-ms-content-crc64", options.ContentCRC64); + } + if (!options.LeaseId.empty()) + { + request.AddHeader("x-ms-lease-id", options.LeaseId); + } + if (options.MaxSize != std::numeric_limits::max()) + { + request.AddHeader("x-ms-blob-condition-maxsize", std::to_string(options.MaxSize)); + } + if (options.AppendPosition != std::numeric_limits::max()) + { + request.AddHeader( + "x-ms-blob-condition-appendpos", std::to_string(options.AppendPosition)); + } + if (!options.EncryptionKey.empty()) + { + request.AddHeader("x-ms-encryption-key", options.EncryptionKey); + } + if (!options.EncryptionKeySHA256.empty()) + { + request.AddHeader("x-ms-encryption-key-sha256", options.EncryptionKeySHA256); + } + if (!options.EncryptionAlgorithm.empty()) + { + request.AddHeader("x-ms-encryption-algorithm", options.EncryptionAlgorithm); + } + if (!options.IfModifiedSince.empty()) + { + request.AddHeader("If-Modified-Since", options.IfModifiedSince); + } + if (!options.IfUnmodifiedSince.empty()) + { + request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince); + } + if (!options.IfMatch.empty()) + { + request.AddHeader("If-Match", options.IfMatch); + } + if (!options.IfNoneMatch.empty()) + { + request.AddHeader("If-None-Match", options.IfNoneMatch); + } + return request; + } + + static BlobAppendInfo AppendBlockParseResponse(Azure::Core::Http::Response& http_response) + { + BlobAppendInfo response; + auto http_status_code + = static_cast::type>( + http_response.GetStatusCode()); + if (!(http_status_code == 201)) + { + throw std::runtime_error("HTTP status code " + std::to_string(http_status_code)); + } + response.Version = http_response.GetHeaders().at("x-ms-version"); + response.Date = http_response.GetHeaders().at("Date"); + response.RequestId = http_response.GetHeaders().at("x-ms-request-id"); + auto response_clientrequestid_iterator + = http_response.GetHeaders().find("x-ms-client-request-id"); + if (response_clientrequestid_iterator != http_response.GetHeaders().end()) + { + response.ClientRequestId = response_clientrequestid_iterator->second; + } + response.ETag = http_response.GetHeaders().at("ETag"); + response.LastModified = http_response.GetHeaders().at("Last-Modified"); + auto response_contentmd5_iterator = http_response.GetHeaders().find("Content-MD5"); + if (response_contentmd5_iterator != http_response.GetHeaders().end()) + { + response.ContentMD5 = response_contentmd5_iterator->second; + } + auto response_contentcrc64_iterator = http_response.GetHeaders().find("x-ms-content-crc64"); + if (response_contentcrc64_iterator != http_response.GetHeaders().end()) + { + response.ContentCRC64 = response_contentcrc64_iterator->second; + } + response.AppendOffset + = std::stoull(http_response.GetHeaders().at("x-ms-blob-append-offset")); + response.CommittedBlockCount + = std::stoull(http_response.GetHeaders().at("x-ms-blob-committed-block-count")); + auto response_serverencrypted_iterator + = http_response.GetHeaders().find("x-ms-server-encrypted"); + if (response_serverencrypted_iterator != http_response.GetHeaders().end()) + { + response.ServerEncrypted = response_serverencrypted_iterator->second == "true"; + } + auto response_encryptionkeysha256_iterator + = http_response.GetHeaders().find("x-ms-encryption-key-sha256"); + if (response_encryptionkeysha256_iterator != http_response.GetHeaders().end()) + { + response.EncryptionKeySHA256 = response_encryptionkeysha256_iterator->second; + } + return response; + } + + static BlobAppendInfo AppendBlock( + Azure::Core::Context context, + Azure::Core::Http::HttpPipeline& pipeline, + const std::string& url, + const AppendBlockOptions& options) + { + auto request = AppendBlockConstructRequest(url, options); + auto response = pipeline.Send(context, request); + return AppendBlockParseResponse(*response); + } + + private: + }; // class AppendBlob }; // class BlobRestClient }}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/blobs/page_blob_client.hpp b/sdk/storage/inc/blobs/page_blob_client.hpp new file mode 100644 index 000000000..6543d3855 --- /dev/null +++ b/sdk/storage/inc/blobs/page_blob_client.hpp @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "blob_options.hpp" +#include "blobs/blob_client.hpp" +#include "common/storage_credential.hpp" +#include "internal/protocol/blob_rest_client.hpp" + +#include + +namespace Azure { namespace Storage { namespace Blobs { + + class PageBlobClient : public BlobClient { + public: + // connection string + static PageBlobClient CreateFromConnectionString( + const std::string& connectionString, + const std::string& containerName, + const std::string& blobName, + const PageBlobClientOptions& options = PageBlobClientOptions()); + + // shared key auth + explicit PageBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const PageBlobClientOptions& options = PageBlobClientOptions()); + + // token auth + explicit PageBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const PageBlobClientOptions& options = PageBlobClientOptions()); + + // anonymous/SAS/customized pipeline auth + explicit PageBlobClient( + const std::string& blobUri, + const PageBlobClientOptions& options = PageBlobClientOptions()); + + PageBlobClient WithSnapshot(const std::string& snapshot) const; + + BlobContentInfo Create( + uint64_t blobContentLength, + const CreatePageBlobOptions& options = CreatePageBlobOptions()); + + PageInfo UploadPages( + // TODO: We don't have BodyStream for now. + std::vector content, + uint64_t offset, + const UploadPagesOptions& options = UploadPagesOptions()); + + PageInfo UploadPagesFromUri( + std::string sourceUri, + uint64_t sourceOffset, + uint64_t sourceLength, + uint64_t destinationoffset, + const UploadPagesFromUriOptions& options = UploadPagesFromUriOptions()); + + PageInfo ClearPages( + uint64_t offset, + uint64_t length, + const ClearPagesOptions& options = ClearPagesOptions()); + + PageBlobInfo Resize( + uint64_t blobContentLength, + const ResizePageBlobOptions& options = ResizePageBlobOptions()); + + PageRangesInfo GetPageRanges(const GetPageRangesOptions& options = GetPageRangesOptions()); + + BlobCopyInfo StartCopyIncremental( + const std::string& sourceUri, + const IncrementalCopyPageBlobOptions& options = IncrementalCopyPageBlobOptions()); + + private: + explicit PageBlobClient(BlobClient blobClient); + }; + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/inc/common/xml_wrapper.hpp b/sdk/storage/inc/common/xml_wrapper.hpp new file mode 100644 index 000000000..829de9ee8 --- /dev/null +++ b/sdk/storage/inc/common/xml_wrapper.hpp @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +struct _xmlTextReader; +struct _xmlTextWriter; +struct _xmlBuffer; + +namespace Azure { namespace Storage { + + enum class XmlNodeType + { + StartTag, + EndTag, + SelfClosingTag, + Text, + Attribute, + End, + }; + + struct XmlNode + { + explicit XmlNode(XmlNodeType type, const char* name = nullptr, const char* value = nullptr) + : Type(type), Name(name), Value(value) + { + } + XmlNodeType Type; + const char* Name; + const char* Value; + }; + + class XmlReader { + public: + explicit XmlReader(const char* data, std::size_t length); + ~XmlReader(); + + XmlNode Read(); + + private: + _xmlTextReader* m_reader = nullptr; + bool m_readingAttributes = false; + }; + + class XmlWriter { + public: + explicit XmlWriter(); + ~XmlWriter(); + + void Write(XmlNode node); + + std::string GetDocument(); + + private: + _xmlBuffer* m_buffer = nullptr; + _xmlTextWriter* m_writer = nullptr; + }; + +}} // namespace Azure::Storage diff --git a/sdk/storage/src/blobs/append_blob_client.cpp b/sdk/storage/src/blobs/append_blob_client.cpp new file mode 100644 index 000000000..d0dd30202 --- /dev/null +++ b/sdk/storage/src/blobs/append_blob_client.cpp @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "blobs/append_blob_client.hpp" + +#include "common/storage_common.hpp" + +namespace Azure { namespace Storage { namespace Blobs { + + AppendBlobClient AppendBlobClient::CreateFromConnectionString( + const std::string& connectionString, + const std::string& containerName, + const std::string& blobName, + const AppendBlobClientOptions& options) + { + AppendBlobClient newClient( + BlobClient::CreateFromConnectionString(connectionString, containerName, blobName, options)); + return newClient; + } + + AppendBlobClient::AppendBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const AppendBlobClientOptions& options) + : BlobClient(blobUri, std::move(credential), options) + { + } + + AppendBlobClient::AppendBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const AppendBlobClientOptions& options) + : BlobClient(blobUri, std::move(credential), options) + { + } + + AppendBlobClient::AppendBlobClient( + const std::string& blobUri, + const AppendBlobClientOptions& options) + : BlobClient(blobUri, options) + { + } + + AppendBlobClient::AppendBlobClient(BlobClient blobClient) : BlobClient(std::move(blobClient)) {} + + AppendBlobClient AppendBlobClient::WithSnapshot(const std::string& snapshot) const + { + AppendBlobClient newClient(*this); + if (snapshot.empty()) + { + newClient.m_blobUrl.RemoveQuery("snapshot"); + } + else + { + newClient.m_blobUrl.AppendQuery("snapshot", snapshot); + } + return newClient; + } + + BlobContentInfo AppendBlobClient::Create(const CreateAppendBlobOptions& options) + { + BlobRestClient::AppendBlob::CreateOptions protocolLayerOptions; + protocolLayerOptions.Properties = options.Properties; + protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.Tier = options.Tier; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::AppendBlob::Create( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + BlobAppendInfo AppendBlobClient::AppendBlock( + std::vector content, + const AppendBlockOptions& options) + { + BlobRestClient::AppendBlob::AppendBlockOptions protocolLayerOptions; + protocolLayerOptions.BodyBuffer = &content; + protocolLayerOptions.ContentMD5 = options.ContentMD5; + protocolLayerOptions.ContentCRC64 = options.ContentCRC64; + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.MaxSize = options.MaxSize; + protocolLayerOptions.AppendPosition = options.AppendPosition; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::AppendBlob::AppendBlock( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/blobs/blob_client.cpp b/sdk/storage/src/blobs/blob_client.cpp index 4cf723f3a..c1475e9c6 100644 --- a/sdk/storage/src/blobs/blob_client.cpp +++ b/sdk/storage/src/blobs/blob_client.cpp @@ -123,48 +123,62 @@ namespace Azure { namespace Storage { namespace Blobs { m_pipeline = std::make_shared(policies); } - BlobClient BlobClient::WithSnapshot(const std::string& snapshot) + BlobClient BlobClient::WithSnapshot(const std::string& snapshot) const { BlobClient newClient(*this); if (snapshot.empty()) { - m_blobUrl.RemoveQuery("snapshot"); + newClient.m_blobUrl.RemoveQuery("snapshot"); } else { - m_blobUrl.AppendQuery("snapshot", snapshot); + newClient.m_blobUrl.AppendQuery("snapshot", snapshot); } return newClient; } - FlattenedDownloadProperties BlobClient::Download(const DownloadBlobOptions& options) + FlattenedDownloadProperties BlobClient::Download(const DownloadBlobOptions& options) const { BlobRestClient::Blob::DownloadOptions protocolLayerOptions; if (options.Offset != std::numeric_limits::max()) { - protocolLayerOptions.Range - = std::make_pair(options.Offset, options.Offset + options.Length - 1); + if (options.Length == 0) + { + protocolLayerOptions.Range + = std::make_pair(options.Offset, std::numeric_limits::max()); + } + else + { + protocolLayerOptions.Range + = std::make_pair(options.Offset, options.Offset + options.Length - 1); + } } else { protocolLayerOptions.Range = std::make_pair(std::numeric_limits::max(), uint64_t(0)); } + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::Blob::Download( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } - BlobProperties BlobClient::GetProperties(const GetBlobPropertiesOptions& options) + BlobProperties BlobClient::GetProperties(const GetBlobPropertiesOptions& options) const { - unused(options); - BlobRestClient::Blob::GetPropertiesOptions protocolLayerOptions; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::Blob::GetProperties( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } - BlobInfo BlobClient::SetHttpHeaders(const SetBlobHttpHeadersOptions& options) + BlobInfo BlobClient::SetHttpHeaders(const SetBlobHttpHeadersOptions& options) const { BlobRestClient::Blob::SetHttpHeadersOptions protocolLayerOptions; protocolLayerOptions.ContentType = options.ContentType; @@ -173,27 +187,102 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.ContentMD5 = options.ContentMD5; protocolLayerOptions.CacheControl = options.CacheControl; protocolLayerOptions.ContentDisposition = options.ContentDisposition; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::Blob::SetHttpHeaders( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } BlobInfo BlobClient::SetMetadata( std::map metadata, - const SetBlobMetadataOptions& options) + const SetBlobMetadataOptions& options) const { - unused(options); BlobRestClient::Blob::SetMetadataOptions protocolLayerOptions; protocolLayerOptions.Metadata = std::move(metadata); + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::Blob::SetMetadata( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } - BasicResponse BlobClient::Delete(const DeleteBlobOptions& options) + BasicResponse BlobClient::SetAccessTier(AccessTier Tier, const SetAccessTierOptions& options) + const + { + BlobRestClient::Blob::SetAccessTierOptions protocolLayerOptions; + protocolLayerOptions.Tier = Tier; + protocolLayerOptions.RehydratePriority = options.RehydratePriority; + return BlobRestClient::Blob::SetAccessTier( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + BlobCopyInfo BlobClient::StartCopyFromUri( + const std::string& sourceUri, + const StartCopyFromUriOptions& options) const + { + BlobRestClient::Blob::StartCopyFromUriOptions protocolLayerOptions; + protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.SourceUri = sourceUri; + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.SourceLeaseId = options.SourceLeaseId; + protocolLayerOptions.Tier = options.Tier; + protocolLayerOptions.RehydratePriority = options.RehydratePriority; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + protocolLayerOptions.SourceIfModifiedSince = options.SourceIfModifiedSince; + protocolLayerOptions.SourceIfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.SourceIfMatch = options.SourceIfMatch; + protocolLayerOptions.SourceIfNoneMatch = options.SourceIfNoneMatch; + return BlobRestClient::Blob::StartCopyFromUri( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + BasicResponse BlobClient::AbortCopyFromUri( + const std::string& copyId, + const AbortCopyFromUriOptions& options) const + { + BlobRestClient::Blob::AbortCopyFromUriOptions protocolLayerOptions; + protocolLayerOptions.CopyId = copyId; + protocolLayerOptions.LeaseId = options.LeaseId; + return BlobRestClient::Blob::AbortCopyFromUri( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + BlobSnapshotInfo BlobClient::CreateSnapshot(const CreateSnapshotOptions& options) const + { + BlobRestClient::Blob::CreateSnapshotOptions protocolLayerOptions; + protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::Blob::CreateSnapshot( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + BasicResponse BlobClient::Delete(const DeleteBlobOptions& options) const { BlobRestClient::Blob::DeleteOptions protocolLayerOptions; protocolLayerOptions.DeleteSnapshots = options.DeleteSnapshots; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::Blob::Delete( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } + BasicResponse BlobClient::Undelete(const UndeleteBlobOptions& options) const + { + BlobRestClient::Blob::UndeleteOptions protocolLayerOptions; + return BlobRestClient::Blob::Undelete( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + }}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/blobs/blob_container_client.cpp b/sdk/storage/src/blobs/blob_container_client.cpp index 0c3191384..b1488c336 100644 --- a/sdk/storage/src/blobs/blob_container_client.cpp +++ b/sdk/storage/src/blobs/blob_container_client.cpp @@ -123,7 +123,7 @@ namespace Azure { namespace Storage { namespace Blobs { m_pipeline = std::make_shared(policies); } - BlobContainerInfo BlobContainerClient::Create(const CreateBlobContainerOptions& options) + BlobContainerInfo BlobContainerClient::Create(const CreateBlobContainerOptions& options) const { BlobRestClient::Container::CreateOptions protocolLayerOptions; protocolLayerOptions.AccessType = options.AccessType; @@ -132,16 +132,17 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_ContainerUri.to_string(), protocolLayerOptions); } - BasicResponse BlobContainerClient::Delete(const DeleteBlobContainerOptions& options) + BasicResponse BlobContainerClient::Delete(const DeleteBlobContainerOptions& options) const { - unused(options); BlobRestClient::Container::DeleteOptions protocolLayerOptions; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; return BlobRestClient::Container::Delete( options.Context, *m_pipeline, m_ContainerUri.to_string(), protocolLayerOptions); } BlobContainerProperties BlobContainerClient::GetProperties( - const GetBlobContainerPropertiesOptions& options) + const GetBlobContainerPropertiesOptions& options) const { unused(options); BlobRestClient::Container::GetPropertiesOptions protocolLayerOptions; @@ -151,16 +152,16 @@ namespace Azure { namespace Storage { namespace Blobs { BlobContainerInfo BlobContainerClient::SetMetadata( std::map metadata, - SetBlobContainerMetadataOptions options) + SetBlobContainerMetadataOptions options) const { - unused(options); BlobRestClient::Container::SetMetadataOptions protocolLayerOptions; protocolLayerOptions.Metadata = metadata; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; return BlobRestClient::Container::SetMetadata( options.Context, *m_pipeline, m_ContainerUri.to_string(), protocolLayerOptions); } - BlobsFlatSegment BlobContainerClient::ListBlobs(const ListBlobsOptions& options) + BlobsFlatSegment BlobContainerClient::ListBlobs(const ListBlobsOptions& options) const { BlobRestClient::Container::ListBlobsOptions protocolLayerOptions; protocolLayerOptions.Prefix = options.Prefix; diff --git a/sdk/storage/src/blobs/blob_service_client.cpp b/sdk/storage/src/blobs/blob_service_client.cpp new file mode 100644 index 000000000..7c36cade0 --- /dev/null +++ b/sdk/storage/src/blobs/blob_service_client.cpp @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "blobs/blob_service_client.hpp" + +#include "common/common_headers_request_policy.hpp" +#include "common/shared_key_policy.hpp" +#include "common/storage_common.hpp" +#include "http/curl/curl.hpp" + +namespace Azure { namespace Storage { namespace Blobs { + + BlobServiceClient BlobServiceClient::CreateFromConnectionString( + const std::string& connectionString, + const BlobServiceClientOptions& options) + { + auto parsedConnectionString = ParseConnectionString(connectionString); + + std::string accountName; + std::string accountKey; + std::string blobEndpoint; + std::string EndpointSuffix; + std::string defaultEndpointsProtocol = ".core.windows.net"; + + auto ite = parsedConnectionString.find("AccountName"); + if (ite != parsedConnectionString.end()) + { + accountName = ite->second; + } + ite = parsedConnectionString.find("AccountKey"); + if (ite != parsedConnectionString.end()) + { + accountKey = ite->second; + } + ite = parsedConnectionString.find("BlobEndpoint"); + if (ite != parsedConnectionString.end()) + { + blobEndpoint = ite->second; + } + ite = parsedConnectionString.find("EndpointSuffix"); + if (ite != parsedConnectionString.end()) + { + EndpointSuffix = ite->second; + } + ite = parsedConnectionString.find("DefaultEndpointsProtocol"); + if (ite != parsedConnectionString.end()) + { + defaultEndpointsProtocol = ite->second; + } + + UrlBuilder builder; + builder.SetScheme(defaultEndpointsProtocol); + if (!blobEndpoint.empty()) + { + builder = UrlBuilder(blobEndpoint); + } + else if (!accountName.empty()) + { + builder.SetHost(accountName + ".blob." + EndpointSuffix); + } + else + { + throw std::runtime_error("invalid connection string"); + } + + auto credential = std::make_shared(accountName, accountKey); + + return BlobServiceClient(builder.to_string(), credential, options); + } + + BlobServiceClient::BlobServiceClient( + const std::string& serviceUri, + std::shared_ptr credential, + const BlobServiceClientOptions& options) + : m_serviceUrl(serviceUri) + { + std::vector> policies; + for (const auto& p : options.policies) + { + policies.emplace_back(std::unique_ptr(p->Clone())); + } + policies.emplace_back(std::make_unique()); + policies.emplace_back(std::make_unique(credential)); + policies.emplace_back(std::make_unique( + std::make_shared())); + m_pipeline = std::make_shared(policies); + } + + BlobServiceClient::BlobServiceClient( + const std::string& serviceUri, + std::shared_ptr credential, + const BlobServiceClientOptions& options) + : m_serviceUrl(serviceUri) + { + std::vector> policies; + for (const auto& p : options.policies) + { + policies.emplace_back(std::unique_ptr(p->Clone())); + } + policies.emplace_back(std::make_unique()); + // not implemented yet + unused(credential); + policies.emplace_back(std::make_unique( + std::make_shared())); + m_pipeline = std::make_shared(policies); + } + + BlobServiceClient::BlobServiceClient( + const std::string& serviceUri, + const BlobServiceClientOptions& options) + : m_serviceUrl(serviceUri) + { + std::vector> policies; + for (const auto& p : options.policies) + { + policies.emplace_back(std::unique_ptr(p->Clone())); + } + policies.emplace_back(std::make_unique()); + policies.emplace_back(std::make_unique( + std::make_shared())); + m_pipeline = std::make_shared(policies); + } + + ListContainersSegment BlobServiceClient::ListBlobContainersSegment( + const ListBlobContainersOptions& options) const + { + BlobRestClient::Service::ListBlobContainersOptions protocolLayerOptions; + protocolLayerOptions.Prefix = options.Prefix; + protocolLayerOptions.Marker = options.Marker; + protocolLayerOptions.MaxResults = options.MaxResults; + protocolLayerOptions.IncludeMetadata = ListBlobContainersIncludeOption::None; + for (auto i : options.Include) + { + if (i == ListBlobContainersIncludeOption::Metadata) + { + protocolLayerOptions.IncludeMetadata = i; + } + } + return BlobRestClient::Service::ListBlobContainers( + options.Context, *m_pipeline, m_serviceUrl.to_string(), protocolLayerOptions); + } + + UserDelegationKey BlobServiceClient::GetUserDelegationKey( + const std::string& expiresOn, + const GetUserDelegationKeyOptions& options) const + { + BlobRestClient::Service::GetUserDelegationKeyOptions protocolLayerOptions; + protocolLayerOptions.StartsOn = options.StartsOn; + protocolLayerOptions.ExpiresOn = expiresOn; + return BlobRestClient::Service::GetUserDelegationKey( + options.Context, *m_pipeline, m_serviceUrl.to_string(), protocolLayerOptions); + } + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/blobs/block_blob_client.cpp b/sdk/storage/src/blobs/block_blob_client.cpp index d5f2ddcc3..7f14a9bb1 100644 --- a/sdk/storage/src/blobs/block_blob_client.cpp +++ b/sdk/storage/src/blobs/block_blob_client.cpp @@ -43,7 +43,7 @@ namespace Azure { namespace Storage { namespace Blobs { BlockBlobClient::BlockBlobClient(BlobClient blobClient) : BlobClient(std::move(blobClient)) {} - BlockBlobClient BlockBlobClient::WithSnapshot(const std::string& snapshot) + BlockBlobClient BlockBlobClient::WithSnapshot(const std::string& snapshot) const { BlockBlobClient newClient(*this); if (snapshot.empty()) @@ -60,16 +60,19 @@ namespace Azure { namespace Storage { namespace Blobs { BlobContentInfo BlockBlobClient::Upload( // TODO: We don't have BodyStream for now. std::vector content, - const UploadBlobOptions& options) + const UploadBlobOptions& options) const { BlobRestClient::BlockBlob::UploadOptions protocolLayerOptions; protocolLayerOptions.BodyBuffer = &content; protocolLayerOptions.ContentMD5 = options.ContentMD5; protocolLayerOptions.ContentCRC64 = options.ContentCRC64; - protocolLayerOptions.BlobType = BlobType::BlockBlob; protocolLayerOptions.Properties = options.Properties; protocolLayerOptions.Metadata = options.Metadata; protocolLayerOptions.Tier = options.Tier; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::BlockBlob::Upload( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } @@ -78,7 +81,7 @@ namespace Azure { namespace Storage { namespace Blobs { const std::string& blockId, // TODO: We don't have BodyStream for now. std::vector content, - const StageBlockOptions& options) + const StageBlockOptions& options) const { BlobRestClient::BlockBlob::StageBlockOptions protocolLayerOptions; protocolLayerOptions.BodyBuffer = &content; @@ -89,23 +92,68 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } + BlockInfo BlockBlobClient::StageBlockFromUri( + const std::string& blockId, + const std::string& sourceUri, + const StageBlockFromUriOptions& options) const + { + BlobRestClient::BlockBlob::StageBlockFromUriOptions protocolLayerOptions; + protocolLayerOptions.BlockId = blockId; + protocolLayerOptions.SourceUri = sourceUri; + if (options.SourceOffset != std::numeric_limits::max()) + { + if (options.SourceLength == 0) + { + protocolLayerOptions.SourceRange = std::make_pair( + options.SourceOffset, std::numeric_limits::max()); + } + else + { + protocolLayerOptions.SourceRange + = std::make_pair(options.SourceOffset, options.SourceOffset + options.SourceLength - 1); + } + } + else + { + protocolLayerOptions.SourceRange + = std::make_pair(std::numeric_limits::max(), uint64_t(0)); + } + protocolLayerOptions.ContentMD5 = options.ContentMD5; + protocolLayerOptions.ContentCRC64 = options.ContentCRC64; + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.SourceIfModifiedSince = options.SourceIfModifiedSince; + protocolLayerOptions.SourceIfUnmodifiedSince = options.SourceIfUnmodifiedSince; + protocolLayerOptions.SourceIfMatch = options.SourceIfMatch; + protocolLayerOptions.SourceIfNoneMatch = options.SourceIfNoneMatch; + return BlobRestClient::BlockBlob::StageBlockFromUri( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + BlobContentInfo BlockBlobClient::CommitBlockList( const std::vector>& blockIds, - const CommitBlockListOptions& options) + const CommitBlockListOptions& options) const { BlobRestClient::BlockBlob::CommitBlockListOptions protocolLayerOptions; protocolLayerOptions.BlockList = blockIds; protocolLayerOptions.Properties = options.Properties; protocolLayerOptions.Metadata = options.Metadata; protocolLayerOptions.Tier = options.Tier; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::BlockBlob::CommitBlockList( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } - BlobBlockListInfo BlockBlobClient::GetBlockList(const GetBlockListOptions& options) + BlobBlockListInfo BlockBlobClient::GetBlockList(const GetBlockListOptions& options) const { BlobRestClient::BlockBlob::GetBlockListOptions protocolLayerOptions; protocolLayerOptions.ListType = options.ListType; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; return BlobRestClient::BlockBlob::GetBlockList( options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); } diff --git a/sdk/storage/src/blobs/page_blob_client.cpp b/sdk/storage/src/blobs/page_blob_client.cpp new file mode 100644 index 000000000..832a1fb02 --- /dev/null +++ b/sdk/storage/src/blobs/page_blob_client.cpp @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "blobs/page_blob_client.hpp" + +#include "common/storage_common.hpp" + +namespace Azure { namespace Storage { namespace Blobs { + + PageBlobClient PageBlobClient::CreateFromConnectionString( + const std::string& connectionString, + const std::string& containerName, + const std::string& blobName, + const PageBlobClientOptions& options) + { + PageBlobClient newClient( + BlobClient::CreateFromConnectionString(connectionString, containerName, blobName, options)); + return newClient; + } + + PageBlobClient::PageBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const PageBlobClientOptions& options) + : BlobClient(blobUri, std::move(credential), options) + { + } + + PageBlobClient::PageBlobClient( + const std::string& blobUri, + std::shared_ptr credential, + const PageBlobClientOptions& options) + : BlobClient(blobUri, std::move(credential), options) + { + } + + PageBlobClient::PageBlobClient(const std::string& blobUri, const PageBlobClientOptions& options) + : BlobClient(blobUri, options) + { + } + + PageBlobClient::PageBlobClient(BlobClient blobClient) : BlobClient(std::move(blobClient)) {} + + PageBlobClient PageBlobClient::WithSnapshot(const std::string& snapshot) const + { + PageBlobClient newClient(*this); + if (snapshot.empty()) + { + newClient.m_blobUrl.RemoveQuery("snapshot"); + } + else + { + newClient.m_blobUrl.AppendQuery("snapshot", snapshot); + } + return newClient; + } + + BlobContentInfo PageBlobClient::Create( + uint64_t blobContentLength, + const CreatePageBlobOptions& options) + { + BlobRestClient::PageBlob::CreateOptions protocolLayerOptions; + protocolLayerOptions.BlobContentLength = blobContentLength; + protocolLayerOptions.SequenceNumber = options.SequenceNumber; + protocolLayerOptions.Properties = options.Properties; + protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.Tier = options.Tier; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::Create( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + PageInfo PageBlobClient::UploadPages( + std::vector content, + uint64_t offset, + const UploadPagesOptions& options) + { + BlobRestClient::PageBlob::UploadPagesOptions protocolLayerOptions; + protocolLayerOptions.BodyBuffer = &content; + protocolLayerOptions.Range = std::make_pair(offset, offset + content.size() - 1); + protocolLayerOptions.ContentMD5 = options.ContentMD5; + protocolLayerOptions.ContentCRC64 = options.ContentCRC64; + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::UploadPages( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + PageInfo PageBlobClient::UploadPagesFromUri( + std::string sourceUri, + uint64_t sourceOffset, + uint64_t sourceLength, + uint64_t destinationoffset, + const UploadPagesFromUriOptions& options) + { + BlobRestClient::PageBlob::UploadPagesFromUriOptions protocolLayerOptions; + protocolLayerOptions.SourceUri = sourceUri; + protocolLayerOptions.SourceRange + = std::make_pair(sourceOffset, sourceOffset + sourceLength - 1); + protocolLayerOptions.Range + = std::make_pair(destinationoffset, destinationoffset + sourceLength - 1); + protocolLayerOptions.ContentMD5 = options.ContentMD5; + protocolLayerOptions.ContentCRC64 = options.ContentCRC64; + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::UploadPagesFromUri( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + PageInfo PageBlobClient::ClearPages( + uint64_t offset, + uint64_t length, + const ClearPagesOptions& options) + { + BlobRestClient::PageBlob::ClearPagesOptions protocolLayerOptions; + protocolLayerOptions.Range = std::make_pair(offset, offset + length - 1); + protocolLayerOptions.LeaseId = options.LeaseId; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::ClearPages( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + PageBlobInfo PageBlobClient::Resize( + uint64_t blobContentLength, + const ResizePageBlobOptions& options) + { + BlobRestClient::PageBlob::ResizeOptions protocolLayerOptions; + protocolLayerOptions.BlobContentLength = blobContentLength; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::Resize( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + PageRangesInfo PageBlobClient::GetPageRanges(const GetPageRangesOptions& options) + { + BlobRestClient::PageBlob::GetPageRangesOptions protocolLayerOptions; + protocolLayerOptions.PreviousSnapshot = options.PreviousSnapshot; + protocolLayerOptions.PreviousSnapshotUrl = options.PreviousSnapshotUrl; + protocolLayerOptions.Range + = std::make_pair(options.Offset, options.Offset + options.Length - 1); + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::GetPageRanges( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + + BlobCopyInfo PageBlobClient::StartCopyIncremental( + const std::string& sourceUri, + const IncrementalCopyPageBlobOptions& options) + { + BlobRestClient::PageBlob::CopyIncrementalOptions protocolLayerOptions; + protocolLayerOptions.CopySource = sourceUri; + protocolLayerOptions.IfModifiedSince = options.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince; + protocolLayerOptions.IfMatch = options.IfMatch; + protocolLayerOptions.IfNoneMatch = options.IfNoneMatch; + return BlobRestClient::PageBlob::CopyIncremental( + options.Context, *m_pipeline, m_blobUrl.to_string(), protocolLayerOptions); + } + +}}} // namespace Azure::Storage::Blobs diff --git a/sdk/storage/src/common/crypt.cpp b/sdk/storage/src/common/crypt.cpp index da0141d2b..503a68945 100644 --- a/sdk/storage/src/common/crypt.cpp +++ b/sdk/storage/src/common/crypt.cpp @@ -31,7 +31,7 @@ namespace Azure { namespace Storage { AlgorithmProviderInstance() { NTSTATUS status = BCryptOpenAlgorithmProvider( - &Handle, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG); + &Handle, BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG); if (!BCRYPT_SUCCESS(status)) { throw std::runtime_error("BCryptOpenAlgorithmProvider failed"); @@ -41,7 +41,7 @@ namespace Azure { namespace Storage { status = BCryptGetProperty( Handle, BCRYPT_OBJECT_LENGTH, - (PBYTE)&objectLength, + reinterpret_cast(&objectLength), sizeof(objectLength), &dataLength, 0); @@ -52,7 +52,12 @@ namespace Azure { namespace Storage { ContextSize = objectLength; DWORD hashLength = 0; status = BCryptGetProperty( - Handle, BCRYPT_HASH_LENGTH, (PBYTE)&hashLength, sizeof(hashLength), &dataLength, 0); + Handle, + BCRYPT_HASH_LENGTH, + reinterpret_cast(&hashLength), + sizeof(hashLength), + &dataLength, + 0); if (!BCRYPT_SUCCESS(status)) { throw std::runtime_error("BCryptGetProperty failed"); @@ -72,17 +77,21 @@ namespace Azure { namespace Storage { NTSTATUS status = BCryptCreateHash( AlgorithmProvider.Handle, &hashHandle, - (PUCHAR)context.data(), - (ULONG)context.size(), - (PUCHAR)key.data(), - (ULONG)key.length(), + reinterpret_cast(&context[0]), + static_cast(context.size()), + reinterpret_cast(const_cast(&key[0])), + static_cast(key.length()), 0); if (!BCRYPT_SUCCESS(status)) { throw std::runtime_error("BCryptCreateHash failed"); } - status = BCryptHashData(hashHandle, (PBYTE)text.data(), (ULONG)text.length(), 0); + status = BCryptHashData( + hashHandle, + reinterpret_cast(const_cast(&text[0])), + static_cast(text.length()), + 0); if (!BCRYPT_SUCCESS(status)) { throw std::runtime_error("BCryptHashData failed"); @@ -90,7 +99,8 @@ namespace Azure { namespace Storage { std::string hash; hash.resize(AlgorithmProvider.HashLength); - status = BCryptFinishHash(hashHandle, (PUCHAR)hash.data(), (ULONG)hash.length(), 0); + status = BCryptFinishHash( + hashHandle, reinterpret_cast(&hash[0]), static_cast(hash.length()), 0); if (!BCRYPT_SUCCESS(status)) { throw std::runtime_error("BCryptFinishHash failed"); @@ -105,15 +115,15 @@ namespace Azure { namespace Storage { { std::string encoded; // According to RFC 4648, the encoded length should be ceiling(n / 3) * 4 - DWORD encodedLength = DWORD((text.length() + 2) / 3 * 4); + DWORD encodedLength = static_cast((text.length() + 2) / 3 * 4); encoded.resize(encodedLength); CryptBinaryToStringA( - (BYTE*)text.data(), - (DWORD)text.length(), + reinterpret_cast(text.data()), + static_cast(text.length()), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, - (LPSTR)encoded.data(), - (DWORD*)&encodedLength); + static_cast(&encoded[0]), + &encodedLength); return encoded; } @@ -128,12 +138,12 @@ namespace Azure { namespace Storage { CryptStringToBinaryA( text.data(), - (DWORD)text.length(), + static_cast(text.length()), CRYPT_STRING_BASE64 | CRYPT_STRING_STRICT, - (BYTE*)decoded.data(), + reinterpret_cast(&decoded[0]), &decodedLength, - NULL, - NULL); + nullptr, + nullptr); decoded.resize(decodedLength); return decoded; } @@ -147,10 +157,10 @@ namespace Azure { namespace Storage { HMAC( EVP_sha256(), key.data(), - (int)key.length(), - (const unsigned char*)text.data(), + static_cast(key.length()), + reinterpret_cast(text.data()), text.length(), - (unsigned char*)&hash[0], + reinterpret_cast(&hash[0]), &hashLength); return std::string(hash, hashLength); @@ -161,7 +171,7 @@ namespace Azure { namespace Storage { BIO* bio = BIO_new(BIO_s_mem()); bio = BIO_push(BIO_new(BIO_f_base64()), bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); - BIO_write(bio, text.data(), (int)text.length()); + BIO_write(bio, text.data(), static_cast(text.length())); BIO_flush(bio); BUF_MEM* bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); @@ -182,7 +192,7 @@ namespace Azure { namespace Storage { BIO* bio = BIO_new_mem_buf(text.data(), -1); bio = BIO_push(BIO_new(BIO_f_base64()), bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); - int decodedLength = BIO_read(bio, &decoded[0], (int)text.length()); + int decodedLength = BIO_read(bio, &decoded[0], static_cast(text.length())); BIO_free_all(bio); decoded.resize(decodedLength); diff --git a/sdk/storage/src/common/xml_wrapper.cpp b/sdk/storage/src/common/xml_wrapper.cpp new file mode 100644 index 000000000..52693f05d --- /dev/null +++ b/sdk/storage/src/common/xml_wrapper.cpp @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "common/xml_wrapper.hpp" + +#include "libxml/xmlreader.h" +#include "libxml/xmlwriter.h" + +#include +#include + +namespace Azure { namespace Storage { + + struct XmlGlobalInitializer + { + XmlGlobalInitializer() { xmlInitParser(); } + ~XmlGlobalInitializer() { xmlCleanupParser(); } + }; + + static void XmlGlobalInitialize() { static XmlGlobalInitializer globalInitializer; } + + XmlReader::XmlReader(const char* data, std::size_t length) + { + XmlGlobalInitialize(); + + if (length > static_cast(std::numeric_limits::max())) + { + throw std::runtime_error("xml data too big"); + } + + m_reader = xmlReaderForMemory(data, static_cast(length), nullptr, nullptr, 0); + if (!m_reader) + { + throw std::runtime_error("failed to parse xml"); + } + } + + XmlReader::~XmlReader() { xmlFreeTextReader(m_reader); } + + XmlNode XmlReader::Read() + { + if (m_readingAttributes) + { + int ret = xmlTextReaderMoveToNextAttribute(m_reader); + if (ret == 1) + { + const char* name = reinterpret_cast(xmlTextReaderName(m_reader)); + const char* value = reinterpret_cast(xmlTextReaderValue(m_reader)); + return XmlNode{XmlNodeType::Attribute, name, value}; + } + else if (ret == 0) + { + m_readingAttributes = false; + } + else + { + throw std::runtime_error("failed to parse xml"); + } + } + + int ret = xmlTextReaderRead(m_reader); + if (ret == 0) + { + return XmlNode{XmlNodeType::End}; + } + if (ret != 1) + { + throw std::runtime_error("failed to parse xml"); + } + + int type = xmlTextReaderNodeType(m_reader); + bool is_empty = xmlTextReaderIsEmptyElement(m_reader) == 1; + bool has_value = xmlTextReaderHasValue(m_reader) == 1; + bool has_attributes = xmlTextReaderHasAttributes(m_reader) == 1; + + const char* name = reinterpret_cast(xmlTextReaderName(m_reader)); + const char* value = reinterpret_cast(xmlTextReaderValue(m_reader)); + + if (has_attributes) + { + m_readingAttributes = true; + } + + if (type == XML_READER_TYPE_ELEMENT && is_empty) + { + return XmlNode{XmlNodeType::SelfClosingTag, name}; + } + else if (type == XML_READER_TYPE_ELEMENT) + { + return XmlNode{XmlNodeType::StartTag, name}; + } + else if (type == XML_READER_TYPE_END_ELEMENT) + { + return XmlNode{XmlNodeType::EndTag, name}; + } + else if (type == XML_READER_TYPE_TEXT) + { + if (has_value) + { + return XmlNode{XmlNodeType::Text, nullptr, value}; + } + } + else if (type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE) + { + // silently ignore + } + else + { + throw std::runtime_error("unknown type " + std::to_string(type) + " while parsing xml"); + } + + return Read(); + } + + XmlWriter::XmlWriter() + { + XmlGlobalInitialize(); + m_buffer = xmlBufferCreate(); + m_writer = xmlNewTextWriterMemory(m_buffer, 0); + xmlTextWriterStartDocument(m_writer, nullptr, nullptr, nullptr); + } + + XmlWriter::~XmlWriter() + { + xmlFreeTextWriter(m_writer); + xmlBufferFree(m_buffer); + } + + void XmlWriter::Write(XmlNode node) + { + if (node.Type == XmlNodeType::StartTag) + { + if (!node.Value) + { + xmlTextWriterStartElement(m_writer, BAD_CAST(node.Name)); + } + else + { + xmlTextWriterWriteElement(m_writer, BAD_CAST(node.Name), BAD_CAST(node.Value)); + } + } + else if (node.Type == XmlNodeType::EndTag) + { + xmlTextWriterEndElement(m_writer); + } + else if (node.Type == XmlNodeType::SelfClosingTag) + { + xmlTextWriterStartElement(m_writer, BAD_CAST(node.Name)); + xmlTextWriterEndElement(m_writer); + } + else if (node.Type == XmlNodeType::Text) + { + xmlTextWriterWriteString(m_writer, BAD_CAST(node.Value)); + } + else if (node.Type == XmlNodeType::Attribute) + { + xmlTextWriterWriteAttribute(m_writer, BAD_CAST(node.Name), BAD_CAST(node.Value)); + } + else if (node.Type == XmlNodeType::End) + { + xmlTextWriterEndDocument(m_writer); + } + else + { + throw std::runtime_error( + "unsupported XmlNode type " + + std::to_string(static_cast::type>(node.Type))); + } + } + + std::string XmlWriter::GetDocument() + { + xmlTextWriterFlush(m_writer); + return std::string(reinterpret_cast(m_buffer->content), m_buffer->use); + } + +}} // namespace Azure::Storage