diff --git a/include/miniocpp/response.h b/include/miniocpp/response.h index 031af0e..bf89166 100644 --- a/include/miniocpp/response.h +++ b/include/miniocpp/response.h @@ -20,7 +20,10 @@ #include #include +#include +#include #include +#include #include #include "error.h" @@ -238,19 +241,21 @@ MINIO_S3_DERIVE_FROM_RESPONSE(DownloadObjectResponse) MINIO_S3_DERIVE_FROM_RESPONSE(GetObjectResponse) struct Item : public Response { - std::string etag; // except DeleteMarker - std::string name; + // etag and name stored in owning ListObjectsResponse's owned_. + std::string_view etag; // except DeleteMarker + std::string_view name; utils::UtcTime last_modified; - std::string owner_id; - std::string owner_name; + // Fields below point into owning ListObjectsResponse's xml_document memory. + std::string_view owner_id; + std::string_view owner_name; size_t size = 0; // except DeleteMarker - std::string storage_class; - bool is_latest = false; // except ListObjects V1/V2 - std::string version_id; // except ListObjects V1/V2 + std::string_view storage_class; + bool is_latest = false; // except ListObjects V1/V2 + std::string_view version_id; // except ListObjects V1/V2 std::map user_metadata; bool is_prefix = false; bool is_delete_marker = false; - std::string encoding_type; + std::string_view encoding_type; Item() = default; @@ -263,29 +268,34 @@ struct Item : public Response { struct ListObjectsResponse : public Response { // Common - std::string name; - std::string encoding_type; - std::string prefix; - std::string delimiter; + std::string_view name; + std::string_view encoding_type; + std::string_view prefix; + std::string_view delimiter; bool is_truncated; unsigned int max_keys; std::list contents; + // Owned XML document for zero-copy string_view backing. + std::shared_ptr doc_; + // Owned strings for non-XML-assigned string_view backing storage. + std::list owned_; + // ListObjectsV1 - std::string marker; - std::string next_marker; + std::string_view marker; + std::string_view next_marker; // ListObjectsV2 unsigned int key_count; - std::string start_after; - std::string continuation_token; - std::string next_continuation_token; + std::string_view start_after; + std::string_view continuation_token; + std::string_view next_continuation_token; // ListObjectVersions - std::string key_marker; - std::string next_key_marker; - std::string version_id_marker; - std::string next_version_id_marker; + std::string_view key_marker; + std::string_view next_key_marker; + std::string_view version_id_marker; + std::string_view next_version_id_marker; ListObjectsResponse() = default; @@ -293,8 +303,6 @@ struct ListObjectsResponse : public Response { explicit ListObjectsResponse(const Response& resp) : Response(resp) {} - ~ListObjectsResponse() = default; - static ListObjectsResponse ParseXML(std::string_view data, bool version); }; // struct ListObjectsResponse diff --git a/src/response.cc b/src/response.cc index b77f545..05a48fb 100644 --- a/src/response.cc +++ b/src/response.cc @@ -54,7 +54,7 @@ Response Response::ParseXML(std::string_view data, int status_code, resp.headers = headers; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { resp.err_ = error::Error("unable to parse XML; " + std::string(data)); return resp; @@ -91,7 +91,7 @@ ListBucketsResponse ListBucketsResponse::ParseXML(std::string_view data) { std::list buckets; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -119,7 +119,7 @@ CompleteMultipartUploadResponse CompleteMultipartUploadResponse::ParseXML( CompleteMultipartUploadResponse resp; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -160,17 +160,31 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, bool version) { ListObjectsResponse resp; - pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + auto xdoc = std::make_shared(); + pugi::xml_parse_result result = xdoc->load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } + resp.doc_ = std::move(xdoc); std::string xpath = version ? "/ListVersionsResult" : "/ListBucketResult"; - auto root = xdoc.select_node(xpath.c_str()); + auto root = resp.doc_->select_node(xpath.c_str()); pugi::xpath_node text; std::string value; + const char* raw = nullptr; + + // Helper to store an unescaped string or point into doc memory. + auto assign_unescaped = [&resp](std::string_view encoding_type, + const char* raw, + std::string_view& target) -> void { + if (encoding_type == "url") { + resp.owned_.emplace_back(curlpp::unescape(raw)); + target = resp.owned_.back(); + } else { + target = raw; + } + }; text = root.node().select_node("Name/text()"); resp.name = text.node().value(); @@ -179,8 +193,8 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, resp.encoding_type = text.node().value(); text = root.node().select_node("Prefix/text()"); - value = text.node().value(); - resp.prefix = (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + assign_unescaped(resp.encoding_type, raw, resp.prefix); text = root.node().select_node("Delimiter/text()"); resp.delimiter = text.node().value(); @@ -196,14 +210,12 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, // ListBucketResult V1 { text = root.node().select_node("Marker/text()"); - value = text.node().value(); - resp.marker = - (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + assign_unescaped(resp.encoding_type, raw, resp.marker); text = root.node().select_node("NextMarker/text()"); - value = text.node().value(); - resp.next_marker = - (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + assign_unescaped(resp.encoding_type, raw, resp.next_marker); } // ListBucketResult V2 @@ -214,9 +226,8 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, resp.key_count = static_cast(std::stoul(value)); text = root.node().select_node("StartAfter/text()"); - value = text.node().value(); - resp.start_after = - (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + assign_unescaped(resp.encoding_type, raw, resp.start_after); text = root.node().select_node("ContinuationToken/text()"); resp.continuation_token = text.node().value(); @@ -228,14 +239,12 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, // ListVersionsResult { text = root.node().select_node("KeyMarker/text()"); - value = text.node().value(); - resp.key_marker = - (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + assign_unescaped(resp.encoding_type, raw, resp.key_marker); text = root.node().select_node("NextKeyMarker/text()"); - value = text.node().value(); - resp.next_key_marker = - (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + assign_unescaped(resp.encoding_type, raw, resp.next_key_marker); text = root.node().select_node("VersionIdMarker/text()"); resp.version_id_marker = text.node().value(); @@ -249,18 +258,24 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, auto populate = [&resp = resp, &last_item = last_item]( std::list& items, pugi::xpath_node_set& contents, bool is_delete_marker) -> void { + const char* raw = nullptr; for (auto content : contents) { pugi::xpath_node text; std::string value; Item item; text = content.node().select_node("ETag/text()"); - item.etag = utils::Trim(text.node().value(), '"'); + resp.owned_.emplace_back(utils::Trim(text.node().value(), '"')); + item.etag = resp.owned_.back(); text = content.node().select_node("Key/text()"); - value = text.node().value(); - item.name = - (resp.encoding_type == "url") ? curlpp::unescape(value) : value; + raw = text.node().value(); + if (resp.encoding_type == "url") { + resp.owned_.emplace_back(curlpp::unescape(raw)); + item.name = resp.owned_.back(); + } else { + item.name = raw; + } text = content.node().select_node("LastModified/text()"); value = text.node().value(); @@ -303,17 +318,21 @@ ListObjectsResponse ListObjectsResponse::ParseXML(std::string_view data, populate(resp.contents, contents, false); // Only for ListObjectsV1. if (resp.is_truncated && resp.next_marker.empty()) { - resp.next_marker = last_item.name; + resp.owned_.emplace_back(last_item.name); + resp.next_marker = resp.owned_.back(); } auto common_prefixes = root.node().select_nodes("CommonPrefixes"); for (auto common_prefix : common_prefixes) { Item item; - text = common_prefix.node().select_node("Prefix/text()"); - value = text.node().value(); - item.name = (resp.encoding_type == "url") ? curlpp::unescape(value) : value; - + raw = text.node().value(); + if (resp.encoding_type == "url") { + resp.owned_.emplace_back(curlpp::unescape(raw)); + item.name = resp.owned_.back(); + } else { + item.name = raw; + } item.is_prefix = true; resp.contents.push_back(item); @@ -329,7 +348,7 @@ RemoveObjectsResponse RemoveObjectsResponse::ParseXML(std::string_view data) { RemoveObjectsResponse resp; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -386,7 +405,7 @@ GetBucketNotificationResponse GetBucketNotificationResponse::ParseXML( NotificationConfig config; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -481,7 +500,7 @@ GetBucketEncryptionResponse GetBucketEncryptionResponse::ParseXML( SseConfig config; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -503,7 +522,7 @@ GetBucketReplicationResponse GetBucketReplicationResponse::ParseXML( ReplicationConfig config; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -669,7 +688,7 @@ GetBucketLifecycleResponse GetBucketLifecycleResponse::ParseXML( LifecycleConfig config; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -796,7 +815,7 @@ GetBucketTagsResponse GetBucketTagsResponse::ParseXML(std::string_view data) { std::map map; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); } @@ -821,7 +840,7 @@ GetObjectTagsResponse GetObjectTagsResponse::ParseXML(std::string_view data) { std::map map; pugi::xml_document xdoc; - pugi::xml_parse_result result = xdoc.load_string(data.data()); + pugi::xml_parse_result result = xdoc.load_buffer(data.data(), data.size()); if (!result) { return error::make("unable to parse XML"); }