1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "google/cloud/storage/internal/object_requests.h"
16 #include "google/cloud/storage/internal/binary_data_as_debug_string.h"
17 #include "google/cloud/storage/internal/metadata_parser.h"
18 #include "google/cloud/storage/internal/object_acl_requests.h"
19 #include "google/cloud/storage/object_metadata.h"
20 #include "google/cloud/internal/format_time_point.h"
21 #include <nlohmann/json.hpp>
22 #include <cinttypes>
23 #include <sstream>
24 
25 namespace google {
26 namespace cloud {
27 namespace storage {
28 inline namespace STORAGE_CLIENT_NS {
29 namespace internal {
30 namespace {
31 /**
32  * Sets a string field in @p json when @p value is not empty.
33  *
34  * This simplifies the implementation of ToJsonString() because we repeat this
35  * check for many attributes.
36  */
SetIfNotEmpty(nlohmann::json & json,char const * key,std::string const & value)37 void SetIfNotEmpty(nlohmann::json& json, char const* key,
38                    std::string const& value) {
39   if (value.empty()) {
40     return;
41   }
42   json[key] = value;
43 }
44 }  // namespace
45 
FromJson(nlohmann::json const & json)46 StatusOr<ObjectMetadata> ObjectMetadataParser::FromJson(
47     nlohmann::json const& json) {
48   if (!json.is_object()) {
49     return Status(StatusCode::kInvalidArgument, __func__);
50   }
51   ObjectMetadata result{};
52   auto status = CommonMetadata<ObjectMetadata>::ParseFromJson(result, json);
53   if (!status.ok()) {
54     return status;
55   }
56 
57   if (json.count("acl") != 0) {
58     for (auto const& kv : json["acl"].items()) {
59       auto parsed = ObjectAccessControlParser::FromJson(kv.value());
60       if (!parsed.ok()) {
61         return std::move(parsed).status();
62       }
63       result.acl_.emplace_back(std::move(*parsed));
64     }
65   }
66 
67   result.bucket_ = json.value("bucket", "");
68   result.cache_control_ = json.value("cacheControl", "");
69   result.component_count_ = internal::ParseIntField(json, "componentCount");
70   result.content_disposition_ = json.value("contentDisposition", "");
71   result.content_encoding_ = json.value("contentEncoding", "");
72   result.content_language_ = json.value("contentLanguage", "");
73   result.content_type_ = json.value("contentType", "");
74   result.crc32c_ = json.value("crc32c", "");
75   if (json.count("customerEncryption") != 0) {
76     auto field = json["customerEncryption"];
77     CustomerEncryption e;
78     e.encryption_algorithm = field.value("encryptionAlgorithm", "");
79     e.key_sha256 = field.value("keySha256", "");
80     result.customer_encryption_ = std::move(e);
81   }
82   result.event_based_hold_ = internal::ParseBoolField(json, "eventBasedHold");
83   result.generation_ = internal::ParseLongField(json, "generation");
84   result.kms_key_name_ = json.value("kmsKeyName", "");
85   result.md5_hash_ = json.value("md5Hash", "");
86   result.media_link_ = json.value("mediaLink", "");
87   if (json.count("metadata") > 0) {
88     for (auto const& kv : json["metadata"].items()) {
89       result.metadata_.emplace(kv.key(), kv.value().get<std::string>());
90     }
91   }
92   result.retention_expiration_time_ =
93       internal::ParseTimestampField(json, "retentionExpirationTime");
94   result.size_ = internal::ParseUnsignedLongField(json, "size");
95   result.temporary_hold_ = internal::ParseBoolField(json, "temporaryHold");
96   result.time_deleted_ = internal::ParseTimestampField(json, "timeDeleted");
97   result.time_storage_class_updated_ =
98       internal::ParseTimestampField(json, "timeStorageClassUpdated");
99   if (json.count("customTime") == 0) {
100     result.custom_time_.reset();
101   } else {
102     result.custom_time_ = internal::ParseTimestampField(json, "customTime");
103   }
104   return result;
105 }
106 
FromString(std::string const & payload)107 StatusOr<ObjectMetadata> ObjectMetadataParser::FromString(
108     std::string const& payload) {
109   auto json = nlohmann::json::parse(payload, nullptr, false);
110   return FromJson(json);
111 }
112 
ObjectMetadataJsonForCompose(ObjectMetadata const & meta)113 nlohmann::json ObjectMetadataJsonForCompose(ObjectMetadata const& meta) {
114   nlohmann::json metadata_as_json({});
115   if (!meta.acl().empty()) {
116     for (ObjectAccessControl const& a : meta.acl()) {
117       nlohmann::json entry;
118       SetIfNotEmpty(entry, "entity", a.entity());
119       SetIfNotEmpty(entry, "role", a.role());
120       metadata_as_json["acl"].emplace_back(std::move(entry));
121     }
122   }
123 
124   SetIfNotEmpty(metadata_as_json, "cacheControl", meta.cache_control());
125   SetIfNotEmpty(metadata_as_json, "contentDisposition",
126                 meta.content_disposition());
127   SetIfNotEmpty(metadata_as_json, "contentEncoding", meta.content_encoding());
128   SetIfNotEmpty(metadata_as_json, "contentLanguage", meta.content_language());
129   SetIfNotEmpty(metadata_as_json, "contentType", meta.content_type());
130 
131   if (meta.event_based_hold()) {
132     metadata_as_json["eventBasedHold"] = true;
133   }
134 
135   SetIfNotEmpty(metadata_as_json, "name", meta.name());
136   SetIfNotEmpty(metadata_as_json, "storageClass", meta.storage_class());
137 
138   if (!meta.metadata().empty()) {
139     nlohmann::json meta_as_json;
140     for (auto const& kv : meta.metadata()) {
141       meta_as_json[kv.first] = kv.second;
142     }
143     metadata_as_json["metadata"] = std::move(meta_as_json);
144   }
145 
146   return metadata_as_json;
147 }
148 
ObjectMetadataJsonForCopy(ObjectMetadata const & meta)149 nlohmann::json ObjectMetadataJsonForCopy(ObjectMetadata const& meta) {
150   return ObjectMetadataJsonForCompose(meta);
151 }
152 
ObjectMetadataJsonForInsert(ObjectMetadata const & meta)153 nlohmann::json ObjectMetadataJsonForInsert(ObjectMetadata const& meta) {
154   auto json = ObjectMetadataJsonForCompose(meta);
155   SetIfNotEmpty(json, "crc32c", meta.crc32c());
156   SetIfNotEmpty(json, "md5Hash", meta.md5_hash());
157   return json;
158 }
159 
ObjectMetadataJsonForRewrite(ObjectMetadata const & meta)160 nlohmann::json ObjectMetadataJsonForRewrite(ObjectMetadata const& meta) {
161   return ObjectMetadataJsonForCompose(meta);
162 }
163 
ObjectMetadataJsonForUpdate(ObjectMetadata const & meta)164 nlohmann::json ObjectMetadataJsonForUpdate(ObjectMetadata const& meta) {
165   nlohmann::json metadata_as_json({});
166   if (!meta.acl().empty()) {
167     for (ObjectAccessControl const& a : meta.acl()) {
168       nlohmann::json entry;
169       SetIfNotEmpty(entry, "entity", a.entity());
170       SetIfNotEmpty(entry, "role", a.role());
171       metadata_as_json["acl"].emplace_back(std::move(entry));
172     }
173   }
174 
175   SetIfNotEmpty(metadata_as_json, "cacheControl", meta.cache_control());
176   SetIfNotEmpty(metadata_as_json, "contentDisposition",
177                 meta.content_disposition());
178   SetIfNotEmpty(metadata_as_json, "contentEncoding", meta.content_encoding());
179   SetIfNotEmpty(metadata_as_json, "contentLanguage", meta.content_language());
180   SetIfNotEmpty(metadata_as_json, "contentType", meta.content_type());
181 
182   metadata_as_json["eventBasedHold"] = meta.event_based_hold();
183 
184   if (!meta.metadata().empty()) {
185     nlohmann::json meta_as_json;
186     for (auto const& kv : meta.metadata()) {
187       meta_as_json[kv.first] = kv.second;
188     }
189     metadata_as_json["metadata"] = std::move(meta_as_json);
190   }
191 
192   if (meta.has_custom_time()) {
193     metadata_as_json["customTime"] =
194         google::cloud::internal::FormatRfc3339(meta.custom_time());
195   }
196 
197   return metadata_as_json;
198 }
199 
operator <<(std::ostream & os,ListObjectsRequest const & r)200 std::ostream& operator<<(std::ostream& os, ListObjectsRequest const& r) {
201   os << "ListObjectsRequest={bucket_name=" << r.bucket_name();
202   r.DumpOptions(os, ", ");
203   return os << "}";
204 }
205 
FromHttpResponse(std::string const & payload)206 StatusOr<ListObjectsResponse> ListObjectsResponse::FromHttpResponse(
207     std::string const& payload) {
208   auto json = nlohmann::json::parse(payload, nullptr, false);
209   if (!json.is_object()) {
210     return Status(StatusCode::kInvalidArgument, __func__);
211   }
212 
213   ListObjectsResponse result;
214   result.next_page_token = json.value("nextPageToken", "");
215 
216   for (auto const& kv : json["items"].items()) {
217     auto parsed = internal::ObjectMetadataParser::FromJson(kv.value());
218     if (!parsed.ok()) {
219       return std::move(parsed).status();
220     }
221     result.items.emplace_back(std::move(*parsed));
222   }
223 
224   for (auto const& prefix_iterator : json["prefixes"].items()) {
225     auto const& prefix = prefix_iterator.value();
226     if (!prefix.is_string()) {
227       return Status(StatusCode::kInternal,
228                     "List Objects Response's 'prefix' is not a string.");
229     }
230     result.prefixes.emplace_back(prefix.get<std::string>());
231   }
232 
233   return result;
234 }
235 
operator <<(std::ostream & os,ListObjectsResponse const & r)236 std::ostream& operator<<(std::ostream& os, ListObjectsResponse const& r) {
237   os << "ListObjectsResponse={next_page_token=" << r.next_page_token
238      << ", items={";
239   std::copy(r.items.begin(), r.items.end(),
240             std::ostream_iterator<ObjectMetadata>(os, "\n  "));
241   os << "}, prefixes={";
242   std::copy(r.prefixes.begin(), r.prefixes.end(),
243             std::ostream_iterator<std::string>(os, "\n "));
244   return os << "}}";
245 }
246 
operator <<(std::ostream & os,GetObjectMetadataRequest const & r)247 std::ostream& operator<<(std::ostream& os, GetObjectMetadataRequest const& r) {
248   os << "GetObjectMetadataRequest={bucket_name=" << r.bucket_name()
249      << ", object_name=" << r.object_name();
250   r.DumpOptions(os, ", ");
251   return os << "}";
252 }
253 
operator <<(std::ostream & os,InsertObjectMediaRequest const & r)254 std::ostream& operator<<(std::ostream& os, InsertObjectMediaRequest const& r) {
255   os << "InsertObjectMediaRequest={bucket_name=" << r.bucket_name()
256      << ", object_name=" << r.object_name();
257   r.DumpOptions(os, ", ");
258   std::size_t constexpr kMaxDumpSize = 1024;
259   if (r.contents().size() > kMaxDumpSize) {
260     os << ", contents[0..1024]=\n"
261        << BinaryDataAsDebugString(r.contents().data(), kMaxDumpSize);
262   } else {
263     os << ", contents=\n"
264        << BinaryDataAsDebugString(r.contents().data(), r.contents().size());
265   }
266   return os << "}";
267 }
268 
operator <<(std::ostream & os,CopyObjectRequest const & r)269 std::ostream& operator<<(std::ostream& os, CopyObjectRequest const& r) {
270   os << "CopyObjectRequest={destination_bucket=" << r.destination_bucket()
271      << ", destination_object=" << r.destination_object()
272      << ", source_bucket=" << r.source_bucket()
273      << ", source_object=" << r.source_object();
274   r.DumpOptions(os, ", ");
275   return os << "}";
276 }
277 
RequiresNoCache() const278 bool ReadObjectRangeRequest::RequiresNoCache() const {
279   if (HasOption<ReadRange>()) {
280     return true;
281   }
282   if (HasOption<ReadFromOffset>() && GetOption<ReadFromOffset>().value() != 0) {
283     return true;
284   }
285   return HasOption<ReadLast>();
286 }
287 
RequiresRangeHeader() const288 bool ReadObjectRangeRequest::RequiresRangeHeader() const {
289   return RequiresNoCache();
290 }
291 
RangeHeader() const292 std::string ReadObjectRangeRequest::RangeHeader() const {
293   if (HasOption<ReadRange>() && HasOption<ReadFromOffset>()) {
294     auto range = GetOption<ReadRange>().value();
295     auto offset = GetOption<ReadFromOffset>().value();
296     auto begin = (std::max)(range.begin, offset);
297     return "Range: bytes=" + std::to_string(begin) + "-" +
298            std::to_string(range.end - 1);
299   }
300   if (HasOption<ReadRange>()) {
301     auto range = GetOption<ReadRange>().value();
302     return "Range: bytes=" + std::to_string(range.begin) + "-" +
303            std::to_string(range.end - 1);
304   }
305   if (HasOption<ReadFromOffset>()) {
306     auto offset = GetOption<ReadFromOffset>().value();
307     if (offset != 0) {
308       return "Range: bytes=" + std::to_string(offset) + "-";
309     }
310   }
311   if (HasOption<ReadLast>()) {
312     auto last = GetOption<ReadLast>().value();
313     return "Range: bytes=-" + std::to_string(last);
314   }
315   return "";
316 }
317 
StartingByte() const318 std::int64_t ReadObjectRangeRequest::StartingByte() const {
319   std::int64_t result = 0;
320   if (HasOption<ReadRange>()) {
321     result = (std::max)(result, GetOption<ReadRange>().value().begin);
322   }
323   if (HasOption<ReadFromOffset>()) {
324     result = (std::max)(result, GetOption<ReadFromOffset>().value());
325   }
326   if (HasOption<ReadLast>()) {
327     // The value of `StartingByte()` is unknown if `ReadLast` is set
328     result = -1;
329   }
330   return result;
331 }
332 
operator <<(std::ostream & os,ReadObjectRangeRequest const & r)333 std::ostream& operator<<(std::ostream& os, ReadObjectRangeRequest const& r) {
334   os << "ReadObjectRangeRequest={bucket_name=" << r.bucket_name()
335      << ", object_name=" << r.object_name();
336   r.DumpOptions(os, ", ");
337   return os << "}";
338 }
339 
340 #include "google/cloud/internal/disable_msvc_crt_secure_warnings.inc"
FromHttpResponse(HttpResponse && response)341 ReadObjectRangeResponse ReadObjectRangeResponse::FromHttpResponse(
342     HttpResponse&& response) {
343   auto loc = response.headers.find(std::string("content-range"));
344   if (response.headers.end() == loc) {
345     google::cloud::internal::ThrowInvalidArgument(
346         "invalid http response for ReadObjectRange");
347   }
348 
349   std::string const& content_range_value = loc->second;
350   auto function = __func__;  // capture this function name, not the lambda's
351   auto raise_error = [&content_range_value, &function]() {
352     std::ostringstream os;
353     os << static_cast<char const*>(function)
354        << " invalid format for content-range header <" << content_range_value
355        << ">";
356     google::cloud::internal::ThrowInvalidArgument(os.str());
357   };
358   char const unit_descriptor[] = "bytes";
359   if (content_range_value.rfind(unit_descriptor, 0) != 0) {
360     raise_error();
361   }
362   char const* buffer = content_range_value.data();
363   auto size = content_range_value.size();
364   // skip the initial "bytes " string.
365   buffer += sizeof(unit_descriptor);
366 
367   if (size < 2) {
368     raise_error();
369   }
370 
371   if (buffer[0] == '*' && buffer[1] == '/') {
372     // The header is just the indication of size ('bytes */<size>'), parse that.
373     buffer += 2;
374     std::int64_t object_size;
375     auto count = std::sscanf(buffer, "%" PRId64, &object_size);
376     if (count != 1) {
377       raise_error();
378     }
379     return ReadObjectRangeResponse{std::move(response.payload), 0, 0,
380                                    object_size};
381   }
382 
383   std::int64_t first_byte;
384   std::int64_t last_byte;
385   std::int64_t object_size;
386   auto count = std::sscanf(buffer, "%" PRId64 "-%" PRId64 "/%" PRId64,
387                            &first_byte, &last_byte, &object_size);
388   if (count != 3) {
389     raise_error();
390   }
391 
392   return ReadObjectRangeResponse{std::move(response.payload), first_byte,
393                                  last_byte, object_size};
394 }
395 #include "google/cloud/internal/diagnostics_pop.inc"
396 
operator <<(std::ostream & os,ReadObjectRangeResponse const & r)397 std::ostream& operator<<(std::ostream& os, ReadObjectRangeResponse const& r) {
398   return os << "ReadObjectRangeResponse={range=" << r.first_byte << "-"
399             << r.last_byte << "/" << r.object_size << ", contents=\n"
400             << BinaryDataAsDebugString(r.contents.data(), r.contents.size())
401             << "}";
402 }
403 
operator <<(std::ostream & os,DeleteObjectRequest const & r)404 std::ostream& operator<<(std::ostream& os, DeleteObjectRequest const& r) {
405   os << "DeleteObjectRequest={bucket_name=" << r.bucket_name()
406      << ", object_name=" << r.object_name();
407   r.DumpOptions(os, ", ");
408   return os << "}";
409 }
410 
operator <<(std::ostream & os,UpdateObjectRequest const & r)411 std::ostream& operator<<(std::ostream& os, UpdateObjectRequest const& r) {
412   os << "UpdateObjectRequest={bucket_name=" << r.bucket_name()
413      << ", object_name=" << r.object_name() << ", metadata=" << r.metadata();
414   r.DumpOptions(os, ", ");
415   return os << "}";
416 }
417 
ComposeObjectRequest(std::string bucket_name,std::vector<ComposeSourceObject> source_objects,std::string destination_object_name)418 ComposeObjectRequest::ComposeObjectRequest(
419     std::string bucket_name, std::vector<ComposeSourceObject> source_objects,
420     std::string destination_object_name)
421     : GenericObjectRequest(std::move(bucket_name),
422                            std::move(destination_object_name)),
423       source_objects_(std::move(source_objects)) {}
424 
JsonPayload() const425 std::string ComposeObjectRequest::JsonPayload() const {
426   nlohmann::json compose_object_payload_json;
427   compose_object_payload_json["kind"] = "storage#composeRequest";
428   nlohmann::json destination_metadata_payload;
429   if (HasOption<WithObjectMetadata>()) {
430     destination_metadata_payload =
431         ObjectMetadataJsonForCompose(GetOption<WithObjectMetadata>().value());
432   }
433   if (!destination_metadata_payload.is_null()) {
434     compose_object_payload_json["destination"] = destination_metadata_payload;
435   }
436   nlohmann::json source_object_list;
437   for (auto const& source_object : source_objects_) {
438     nlohmann::json source_object_json;
439     source_object_json["name"] = source_object.object_name;
440     if (source_object.generation.has_value()) {
441       source_object_json["generation"] = source_object.generation.value();
442     }
443     if (source_object.if_generation_match.has_value()) {
444       source_object_json["ifGenerationMatch"] =
445           source_object.if_generation_match.value();
446     }
447     source_object_list.emplace_back(std::move(source_object_json));
448   }
449   compose_object_payload_json["sourceObjects"] = source_object_list;
450 
451   return compose_object_payload_json.dump();
452 }
453 
operator <<(std::ostream & os,ComposeObjectRequest const & r)454 std::ostream& operator<<(std::ostream& os, ComposeObjectRequest const& r) {
455   os << "ComposeObjectRequest={bucket_name=" << r.bucket_name()
456      << ", destination_object_name=" << r.object_name();
457   r.DumpOptions(os, ", ");
458   return os << ", payload=" << r.JsonPayload() << "}";
459 }
460 
PatchObjectRequest(std::string bucket_name,std::string object_name,ObjectMetadata const & original,ObjectMetadata const & updated)461 PatchObjectRequest::PatchObjectRequest(std::string bucket_name,
462                                        std::string object_name,
463                                        ObjectMetadata const& original,
464                                        ObjectMetadata const& updated)
465     : GenericObjectRequest(std::move(bucket_name), std::move(object_name)) {
466   // Compare each writeable field to build the patch.
467   ObjectMetadataPatchBuilder builder;
468 
469   if (original.acl() != updated.acl()) {
470     builder.SetAcl(updated.acl());
471   }
472   if (original.cache_control() != updated.cache_control()) {
473     builder.SetCacheControl(updated.cache_control());
474   }
475   if (original.content_disposition() != updated.content_disposition()) {
476     builder.SetContentDisposition(updated.content_disposition());
477   }
478   if (original.content_encoding() != updated.content_encoding()) {
479     builder.SetContentEncoding(updated.content_encoding());
480   }
481   if (original.content_language() != updated.content_language()) {
482     builder.SetContentLanguage(updated.content_language());
483   }
484   if (original.content_type() != updated.content_type()) {
485     builder.SetContentType(updated.content_type());
486   }
487   if (original.event_based_hold() != updated.event_based_hold()) {
488     builder.SetEventBasedHold(updated.event_based_hold());
489   }
490 
491   if (original.metadata() != updated.metadata()) {
492     if (updated.metadata().empty()) {
493       builder.ResetMetadata();
494     } else {
495       std::map<std::string, std::string> difference;
496       // Find the keys in the original map that are not in the new map. Using
497       // `std::set_difference()` works because, unlike `std::unordered_map` the
498       // `std::map` iterators return elements ordered by key:
499       std::set_difference(original.metadata().begin(),
500                           original.metadata().end(), updated.metadata().begin(),
501                           updated.metadata().end(),
502                           std::inserter(difference, difference.end()),
503                           // We want to compare just keys and ignore values, the
504                           // map class provides such a function, so use it:
505                           original.metadata().value_comp());
506       for (auto&& d : difference) {
507         builder.ResetMetadata(d.first);
508       }
509 
510       // Find the elements (comparing key and value) in the updated map that
511       // are not in the original map:
512       difference.clear();
513       std::set_difference(updated.metadata().begin(), updated.metadata().end(),
514                           original.metadata().begin(),
515                           original.metadata().end(),
516                           std::inserter(difference, difference.end()));
517       for (auto&& d : difference) {
518         builder.SetMetadata(d.first, d.second);
519       }
520     }
521   }
522 
523   if (original.temporary_hold() != updated.temporary_hold()) {
524     builder.SetTemporaryHold(updated.temporary_hold());
525   }
526 
527   payload_ = builder.BuildPatch();
528 }
529 
PatchObjectRequest(std::string bucket_name,std::string object_name,ObjectMetadataPatchBuilder const & patch)530 PatchObjectRequest::PatchObjectRequest(std::string bucket_name,
531                                        std::string object_name,
532                                        ObjectMetadataPatchBuilder const& patch)
533     : GenericObjectRequest(std::move(bucket_name), std::move(object_name)),
534       payload_(patch.BuildPatch()) {}
535 
operator <<(std::ostream & os,PatchObjectRequest const & r)536 std::ostream& operator<<(std::ostream& os, PatchObjectRequest const& r) {
537   os << "PatchObjectRequest={bucket_name=" << r.bucket_name()
538      << ", object_name=" << r.object_name();
539   r.DumpOptions(os, ", ");
540   return os << ", payload=" << r.payload() << "}";
541 }
542 
operator <<(std::ostream & os,RewriteObjectRequest const & r)543 std::ostream& operator<<(std::ostream& os, RewriteObjectRequest const& r) {
544   os << "RewriteObjectRequest={destination_bucket=" << r.destination_bucket()
545      << ", destination_object=" << r.destination_object()
546      << ", source_bucket=" << r.source_bucket()
547      << ", source_object=" << r.source_object()
548      << ", rewrite_token=" << r.rewrite_token();
549   r.DumpOptions(os, ", ");
550   return os << "}";
551 }
552 
FromHttpResponse(std::string const & payload)553 StatusOr<RewriteObjectResponse> RewriteObjectResponse::FromHttpResponse(
554     std::string const& payload) {
555   auto object = nlohmann::json::parse(payload, nullptr, false);
556   if (!object.is_object()) {
557     return Status(StatusCode::kInvalidArgument, __func__);
558   }
559 
560   RewriteObjectResponse result;
561   result.total_bytes_rewritten =
562       ParseUnsignedLongField(object, "totalBytesRewritten");
563   result.object_size = ParseUnsignedLongField(object, "objectSize");
564   result.done = object.value("done", false);
565   result.rewrite_token = object.value("rewriteToken", "");
566   if (object.count("resource") != 0) {
567     auto parsed = internal::ObjectMetadataParser::FromJson(object["resource"]);
568     if (!parsed.ok()) {
569       return std::move(parsed).status();
570     }
571     result.resource = std::move(*parsed);
572   }
573   return result;
574 }
575 
operator <<(std::ostream & os,RewriteObjectResponse const & r)576 std::ostream& operator<<(std::ostream& os, RewriteObjectResponse const& r) {
577   return os << "RewriteObjectResponse={total_bytes_rewritten="
578             << r.total_bytes_rewritten << ", object_size=" << r.object_size
579             << ", done=" << std::boolalpha << r.done
580             << ", rewrite_token=" << r.rewrite_token
581             << ", resource=" << r.resource << "}";
582 }
583 
operator <<(std::ostream & os,ResumableUploadRequest const & r)584 std::ostream& operator<<(std::ostream& os, ResumableUploadRequest const& r) {
585   os << "ResumableUploadRequest={bucket_name=" << r.bucket_name()
586      << ", object_name=" << r.object_name();
587   r.DumpOptions(os, ", ");
588   return os << "}";
589 }
590 
operator <<(std::ostream & os,DeleteResumableUploadRequest const & r)591 std::ostream& operator<<(std::ostream& os,
592                          DeleteResumableUploadRequest const& r) {
593   os << "DeleteResumableUploadRequest={upload_session_url="
594      << r.upload_session_url();
595   r.DumpOptions(os, ", ");
596   return os << "}";
597 }
598 
RangeHeader() const599 std::string UploadChunkRequest::RangeHeader() const {
600   std::ostringstream os;
601   os << "Content-Range: bytes ";
602   auto const size = payload_size();
603   if (size == 0) {
604     // This typically happens when the sender realizes too late that the
605     // previous chunk was really the last chunk (e.g. the file is exactly a
606     // multiple of the quantum, reading the last chunk from a file, or sending
607     // it as part of a stream that does not detect the EOF), the formatting of
608     // the range is special in this case.
609     os << "*";
610   } else {
611     os << range_begin() << "-" << range_begin() + size - 1;
612   }
613   if (!last_chunk_) {
614     os << "/*";
615   } else {
616     os << "/" << source_size();
617   }
618   return std::move(os).str();
619 }
620 
operator <<(std::ostream & os,UploadChunkRequest const & r)621 std::ostream& operator<<(std::ostream& os, UploadChunkRequest const& r) {
622   os << "UploadChunkRequest={upload_session_url=" << r.upload_session_url()
623      << ", range=<" << r.RangeHeader() << ">";
624   r.DumpOptions(os, ", ");
625   os << ", payload={";
626   auto constexpr kMaxOutputBytes = 128;
627   char const* sep = "";
628   for (auto const& b : r.payload()) {
629     os << sep << "{"
630        << BinaryDataAsDebugString(b.data(), b.size(), kMaxOutputBytes) << "}";
631     sep = ", ";
632   }
633   return os << "}}";
634 }
635 
operator <<(std::ostream & os,QueryResumableUploadRequest const & r)636 std::ostream& operator<<(std::ostream& os,
637                          QueryResumableUploadRequest const& r) {
638   os << "QueryResumableUploadRequest={upload_session_url="
639      << r.upload_session_url();
640   r.DumpOptions(os, ", ");
641   return os << "}";
642 }
643 
644 }  // namespace internal
645 }  // namespace STORAGE_CLIENT_NS
646 }  // namespace storage
647 }  // namespace cloud
648 }  // namespace google
649