1 // Copyright 2019 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/client.h"
16 #include "google/cloud/storage/testing/canonical_errors.h"
17 #include "google/cloud/storage/testing/storage_integration_test.h"
18 #include "google/cloud/internal/getenv.h"
19 #include "google/cloud/log.h"
20 #include "google/cloud/testing_util/assert_ok.h"
21 #include "google/cloud/testing_util/capture_log_lines_backend.h"
22 #include "google/cloud/testing_util/expect_exception.h"
23 #include <gmock/gmock.h>
24 #include <regex>
25 
26 namespace google {
27 namespace cloud {
28 namespace storage {
29 inline namespace STORAGE_CLIENT_NS {
30 namespace {
31 
32 using ::testing::HasSubstr;
33 using ::testing::StartsWith;
34 
35 class ObjectHashIntegrationTest
36     : public google::cloud::storage::testing::StorageIntegrationTest {
37  protected:
SetUp()38   void SetUp() override {
39     bucket_name_ = google::cloud::internal::GetEnv(
40                        "GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME")
41                        .value_or("");
42     ASSERT_FALSE(bucket_name_.empty());
43   }
44 
45   std::string bucket_name_;
46 };
47 
48 /// @test Verify that MD5 hashes are disabled by default.
TEST_F(ObjectHashIntegrationTest,DefaultMD5HashXML)49 TEST_F(ObjectHashIntegrationTest, DefaultMD5HashXML) {
50   auto client_options = ClientOptions::CreateDefaultClientOptions();
51   ASSERT_STATUS_OK(client_options);
52   Client client((*client_options)
53                     .set_enable_raw_client_tracing(true)
54                     .set_enable_http_tracing(true));
55   auto object_name = MakeRandomObjectName();
56 
57   auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
58   auto id = LogSink::Instance().AddBackend(backend);
59   StatusOr<ObjectMetadata> insert_meta =
60       client.InsertObject(bucket_name_, object_name, LoremIpsum(),
61                           IfGenerationMatch(0), Fields(""));
62   ASSERT_STATUS_OK(insert_meta);
63 
64   LogSink::Instance().RemoveBackend(id);
65 
66   EXPECT_THAT(backend->ClearLogLines(),
67               Not(Contains(StartsWith("x-goog-hash: md5="))));
68 
69   auto status = client.DeleteObject(bucket_name_, object_name);
70   ASSERT_STATUS_OK(status);
71 }
72 
73 /// @test Verify that MD5 hashes are disabled by default.
TEST_F(ObjectHashIntegrationTest,DefaultMD5HashJSON)74 TEST_F(ObjectHashIntegrationTest, DefaultMD5HashJSON) {
75   auto client_options = ClientOptions::CreateDefaultClientOptions();
76   ASSERT_STATUS_OK(client_options);
77   Client client((*client_options)
78                     .set_enable_raw_client_tracing(true)
79                     .set_enable_http_tracing(true));
80   auto object_name = MakeRandomObjectName();
81 
82   auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
83   auto id = LogSink::Instance().AddBackend(backend);
84   StatusOr<ObjectMetadata> insert_meta = client.InsertObject(
85       bucket_name_, object_name, LoremIpsum(), IfGenerationMatch(0));
86   ASSERT_STATUS_OK(insert_meta);
87 
88   LogSink::Instance().RemoveBackend(id);
89 
90   // This is a big indirect, we detect if the upload changed to
91   // multipart/related, and if so, we assume the hash value is being used.
92   // Unfortunately I (@coryan) cannot think of a way to examine the upload
93   // contents.
94   EXPECT_THAT(
95       backend->ClearLogLines(),
96       Contains(StartsWith("content-type: multipart/related; boundary=")));
97 
98   if (insert_meta->has_metadata("x_emulator_upload")) {
99     // When running against the emulator, we have some more information to
100     // verify the right upload type and contents were sent.
101     EXPECT_EQ("multipart", insert_meta->metadata("x_emulator_upload"));
102     ASSERT_FALSE(insert_meta->has_metadata("x_emulator_md5"));
103   }
104 
105   auto status = client.DeleteObject(bucket_name_, object_name);
106   ASSERT_STATUS_OK(status);
107 }
108 
109 /// @test Verify that `DisableMD5Hash` actually disables the header.
TEST_F(ObjectHashIntegrationTest,DisableMD5HashXML)110 TEST_F(ObjectHashIntegrationTest, DisableMD5HashXML) {
111   auto client_options = ClientOptions::CreateDefaultClientOptions();
112   ASSERT_STATUS_OK(client_options);
113   Client client((*client_options)
114                     .set_enable_raw_client_tracing(true)
115                     .set_enable_http_tracing(true));
116   auto object_name = MakeRandomObjectName();
117 
118   auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
119   auto id = LogSink::Instance().AddBackend(backend);
120   StatusOr<ObjectMetadata> insert_meta = client.InsertObject(
121       bucket_name_, object_name, LoremIpsum(), IfGenerationMatch(0),
122       DisableMD5Hash(true), Fields(""));
123   ASSERT_STATUS_OK(insert_meta);
124 
125   LogSink::Instance().RemoveBackend(id);
126 
127   EXPECT_THAT(backend->ClearLogLines(),
128               Not(Contains(StartsWith("x-goog-hash: md5="))));
129 
130   auto status = client.DeleteObject(bucket_name_, object_name);
131   ASSERT_STATUS_OK(status);
132 }
133 
134 /// @test Verify that `DisableMD5Hash` actually disables the payload.
TEST_F(ObjectHashIntegrationTest,DisableMD5HashJSON)135 TEST_F(ObjectHashIntegrationTest, DisableMD5HashJSON) {
136   auto client_options = ClientOptions::CreateDefaultClientOptions();
137   ASSERT_STATUS_OK(client_options);
138   Client client((*client_options)
139                     .set_enable_raw_client_tracing(true)
140                     .set_enable_http_tracing(true));
141   auto object_name = MakeRandomObjectName();
142 
143   auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
144   auto id = LogSink::Instance().AddBackend(backend);
145   StatusOr<ObjectMetadata> insert_meta =
146       client.InsertObject(bucket_name_, object_name, LoremIpsum(),
147                           IfGenerationMatch(0), DisableMD5Hash(true));
148   ASSERT_STATUS_OK(insert_meta);
149 
150   LogSink::Instance().RemoveBackend(id);
151 
152   // This is a big indirect, we detect if the upload changed to
153   // multipart/related, and if so, we assume the hash value is being used.
154   // Unfortunately I (@coryan) cannot think of a way to examine the upload
155   // contents.
156   EXPECT_THAT(
157       backend->ClearLogLines(),
158       Contains(StartsWith("content-type: multipart/related; boundary=")));
159 
160   if (insert_meta->has_metadata("x_emulator_upload")) {
161     // When running against the emulator, we have some more information to
162     // verify the right upload type and contents were sent.
163     EXPECT_EQ("multipart", insert_meta->metadata("x_emulator_upload"));
164     ASSERT_FALSE(insert_meta->has_metadata("x_emulator_md5"));
165   }
166 
167   auto status = client.DeleteObject(bucket_name_, object_name);
168   ASSERT_STATUS_OK(status);
169 }
170 
171 /// @test Verify that MD5 hashes are disabled by default on downloads.
TEST_F(ObjectHashIntegrationTest,DefaultMD5StreamingReadXML)172 TEST_F(ObjectHashIntegrationTest, DefaultMD5StreamingReadXML) {
173   StatusOr<Client> client = MakeIntegrationTestClient();
174   ASSERT_STATUS_OK(client);
175 
176   auto object_name = MakeRandomObjectName();
177 
178   // Create an object and a stream to read it back.
179   StatusOr<ObjectMetadata> meta =
180       client->InsertObject(bucket_name_, object_name, LoremIpsum(),
181                            IfGenerationMatch(0), Projection::Full());
182   ASSERT_STATUS_OK(meta);
183 
184   auto stream = client->ReadObject(bucket_name_, object_name);
185   std::string actual(std::istreambuf_iterator<char>{stream}, {});
186   ASSERT_FALSE(stream.IsOpen());
187   ASSERT_FALSE(actual.empty());
188 
189   EXPECT_EQ(stream.received_hash(), stream.computed_hash());
190   EXPECT_THAT(stream.received_hash(), Not(HasSubstr(meta->md5_hash())));
191 
192   auto status = client->DeleteObject(bucket_name_, object_name);
193   ASSERT_STATUS_OK(status);
194 }
195 
196 /// @test Verify that MD5 hashes are disabled by default on downloads.
TEST_F(ObjectHashIntegrationTest,DefaultMD5StreamingReadJSON)197 TEST_F(ObjectHashIntegrationTest, DefaultMD5StreamingReadJSON) {
198   StatusOr<Client> client = MakeIntegrationTestClient();
199   ASSERT_STATUS_OK(client);
200 
201   auto object_name = MakeRandomObjectName();
202 
203   // Create an object and a stream to read it back.
204   StatusOr<ObjectMetadata> meta =
205       client->InsertObject(bucket_name_, object_name, LoremIpsum(),
206                            IfGenerationMatch(0), Projection::Full());
207   ASSERT_STATUS_OK(meta);
208 
209   auto stream = client->ReadObject(bucket_name_, object_name,
210                                    IfMetagenerationNotMatch(0));
211   std::string actual(std::istreambuf_iterator<char>{stream}, {});
212   ASSERT_FALSE(stream.IsOpen());
213   ASSERT_FALSE(actual.empty());
214 
215   EXPECT_EQ(stream.received_hash(), stream.computed_hash());
216   EXPECT_THAT(stream.received_hash(), Not(HasSubstr(meta->md5_hash())));
217 
218   auto status = client->DeleteObject(bucket_name_, object_name);
219   ASSERT_STATUS_OK(status);
220 }
221 
222 /// @test Verify that hashes and checksums can be disabled on downloads.
TEST_F(ObjectHashIntegrationTest,DisableHashesStreamingReadXML)223 TEST_F(ObjectHashIntegrationTest, DisableHashesStreamingReadXML) {
224   StatusOr<Client> client = MakeIntegrationTestClient();
225   ASSERT_STATUS_OK(client);
226 
227   auto object_name = MakeRandomObjectName();
228 
229   // Create an object and a stream to read it back.
230   StatusOr<ObjectMetadata> meta =
231       client->InsertObject(bucket_name_, object_name, LoremIpsum(),
232                            IfGenerationMatch(0), Projection::Full());
233   ASSERT_STATUS_OK(meta);
234 
235   auto stream =
236       client->ReadObject(bucket_name_, object_name, DisableMD5Hash(true),
237                          DisableCrc32cChecksum(true));
238   std::string actual(std::istreambuf_iterator<char>{stream}, {});
239   ASSERT_FALSE(stream.IsOpen());
240   ASSERT_FALSE(actual.empty());
241 
242   EXPECT_TRUE(stream.computed_hash().empty());
243   EXPECT_TRUE(stream.received_hash().empty());
244 
245   auto status = client->DeleteObject(bucket_name_, object_name);
246   ASSERT_STATUS_OK(status);
247 }
248 
249 /// @test Verify that hashes and checksums can be disabled on downloads.
TEST_F(ObjectHashIntegrationTest,DisableHashesStreamingReadJSON)250 TEST_F(ObjectHashIntegrationTest, DisableHashesStreamingReadJSON) {
251   StatusOr<Client> client = MakeIntegrationTestClient();
252   ASSERT_STATUS_OK(client);
253 
254   auto object_name = MakeRandomObjectName();
255 
256   // Create an object and a stream to read it back.
257   StatusOr<ObjectMetadata> meta =
258       client->InsertObject(bucket_name_, object_name, LoremIpsum(),
259                            IfGenerationMatch(0), Projection::Full());
260   ASSERT_STATUS_OK(meta);
261 
262   auto stream = client->ReadObject(
263       bucket_name_, object_name, DisableMD5Hash(true),
264       DisableCrc32cChecksum(true), IfMetagenerationNotMatch(0));
265   std::string actual(std::istreambuf_iterator<char>{stream}, {});
266   ASSERT_FALSE(stream.IsOpen());
267   ASSERT_FALSE(actual.empty());
268 
269   EXPECT_TRUE(stream.computed_hash().empty());
270   EXPECT_TRUE(stream.received_hash().empty());
271 
272   auto status = client->DeleteObject(bucket_name_, object_name);
273   ASSERT_STATUS_OK(status);
274 }
275 
276 /// @test Verify that MD5 hashes are disabled by default on uploads.
TEST_F(ObjectHashIntegrationTest,DefaultMD5StreamingWriteJSON)277 TEST_F(ObjectHashIntegrationTest, DefaultMD5StreamingWriteJSON) {
278   StatusOr<Client> client = MakeIntegrationTestClient();
279   ASSERT_STATUS_OK(client);
280 
281   auto object_name = MakeRandomObjectName();
282 
283   // Create the object, but only if it does not exist already.
284   auto os =
285       client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0));
286   os.exceptions(std::ios_base::failbit);
287   // We will construct the expected response while streaming the data up.
288   std::ostringstream expected;
289   WriteRandomLines(os, expected);
290 
291   auto expected_md5hash = ComputeMD5Hash(expected.str());
292 
293   os.Close();
294   ObjectMetadata meta = os.metadata().value();
295   EXPECT_EQ(os.received_hash(), os.computed_hash());
296   EXPECT_THAT(os.received_hash(), Not(HasSubstr(expected_md5hash)));
297 
298   auto status = client->DeleteObject(bucket_name_, object_name);
299   ASSERT_STATUS_OK(status);
300 }
301 
302 /// @test Verify MD5 hash value before upload.
TEST_F(ObjectHashIntegrationTest,VerifyValidMD5StreamingWriteJSON)303 TEST_F(ObjectHashIntegrationTest, VerifyValidMD5StreamingWriteJSON) {
304   StatusOr<Client> client = MakeIntegrationTestClient();
305   ASSERT_STATUS_OK(client);
306 
307   auto object_name = MakeRandomObjectName();
308 
309   std::string expected = LoremIpsum();
310   auto expected_md5hash = ComputeMD5Hash(expected);
311 
312   // Create the object, but only if it does not exist already.
313   auto os = client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
314                                 MD5HashValue(expected_md5hash));
315 
316   os.exceptions(std::ios_base::failbit);
317   os << expected;
318   os.Close();
319 
320   ObjectMetadata meta = os.metadata().value();
321   EXPECT_EQ(os.received_hash(), os.computed_hash());
322   EXPECT_THAT(os.received_hash(), Not(HasSubstr(expected_md5hash)));
323 
324   auto status = client->DeleteObject(bucket_name_, object_name);
325   ASSERT_STATUS_OK(status);
326 }
327 
328 /// @test Verify invalid MD5 hash value before upload.
TEST_F(ObjectHashIntegrationTest,InvalidMD5StreamingWriteJSON)329 TEST_F(ObjectHashIntegrationTest, InvalidMD5StreamingWriteJSON) {
330   StatusOr<Client> client = MakeIntegrationTestClient();
331   ASSERT_STATUS_OK(client);
332 
333   auto object_name = MakeRandomObjectName();
334 
335   std::string expected = LoremIpsum();
336 
337   // Create the object, but only if it does not exist already. Dummy MD5HasValue
338   // is passed during WriteObject.
339   auto os = client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
340                                 MD5HashValue("AAAAAAAAAA+AAAAAAAAAAA=="));
341 
342   os.exceptions(std::ios_base::failbit);
343   os << expected;
344   os.Close();
345 
346   EXPECT_TRUE(os.bad());
347   EXPECT_FALSE(os.metadata().status().ok());
348 }
349 
350 /// @test Verify MD5 hashes before upload.
TEST_F(ObjectHashIntegrationTest,InvalidMD5StreamingWriteXML)351 TEST_F(ObjectHashIntegrationTest, InvalidMD5StreamingWriteXML) {
352   StatusOr<Client> client = MakeIntegrationTestClient();
353   ASSERT_STATUS_OK(client);
354 
355   auto object_name = MakeRandomObjectName();
356 
357   std::string expected = LoremIpsum();
358 
359   // Create the object, but only if it does not exist already. Dummy MD5HasValue
360   // is passed during WriteObject.
361   auto os = client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
362                                 Projection::Full(),
363                                 MD5HashValue("AAAAAAAAAA+AAAAAAAAAAA=="));
364   os.exceptions(std::ios_base::failbit);
365   os << expected;
366   os.Close();
367 
368   EXPECT_TRUE(os.bad());
369   EXPECT_FALSE(os.metadata().status().ok());
370 }
371 
372 /// @test Verify that hashes and checksums can be disabled in uploads.
TEST_F(ObjectHashIntegrationTest,DisableHashesStreamingWriteJSON)373 TEST_F(ObjectHashIntegrationTest, DisableHashesStreamingWriteJSON) {
374   StatusOr<Client> client = MakeIntegrationTestClient();
375   ASSERT_STATUS_OK(client);
376 
377   auto object_name = MakeRandomObjectName();
378 
379   // Create the object, but only if it does not exist already.
380   auto os =
381       client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
382                           DisableMD5Hash(true), DisableCrc32cChecksum(true));
383   os.exceptions(std::ios_base::failbit);
384   // We will construct the expected response while streaming the data up.
385   std::ostringstream expected;
386   WriteRandomLines(os, expected);
387 
388   os.Close();
389   ObjectMetadata meta = os.metadata().value();
390   EXPECT_TRUE(os.received_hash().empty());
391   EXPECT_TRUE(os.computed_hash().empty());
392 
393   auto status = client->DeleteObject(bucket_name_, object_name);
394   ASSERT_STATUS_OK(status);
395 }
396 
397 /// @test Verify that MD5 hash mismatches are reported by default on downloads.
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadXML)398 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadXML) {
399   // This test is disabled when not using the emulator as it relies on the
400   // emulator to inject faults.
401   if (!UsingEmulator()) GTEST_SKIP();
402   StatusOr<Client> client = MakeIntegrationTestClient();
403   ASSERT_STATUS_OK(client);
404 
405   auto object_name = MakeRandomObjectName();
406 
407   // Create an object and a stream to read it back.
408   StatusOr<ObjectMetadata> meta = client->InsertObject(
409       bucket_name_, object_name, LoremIpsum(), EnableMD5Hash(),
410       IfGenerationMatch(0), Projection::Full());
411   ASSERT_STATUS_OK(meta);
412 
413   auto stream = client->ReadObject(
414       bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
415       CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
416 
417 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
418   EXPECT_THROW(
419       try {
420         std::string actual(std::istreambuf_iterator<char>{stream},
421                            std::istreambuf_iterator<char>{});
422       } catch (HashMismatchError const& ex) {
423         EXPECT_NE(ex.received_hash(), ex.computed_hash());
424         EXPECT_THAT(ex.what(), HasSubstr("mismatched hashes"));
425         throw;
426       },
427       HashMismatchError);
428 #else
429   std::string actual(std::istreambuf_iterator<char>{stream}, {});
430   EXPECT_NE(stream.received_hash(), stream.computed_hash());
431   EXPECT_EQ(stream.received_hash(), meta->md5_hash());
432   EXPECT_FALSE(stream.status().ok());
433 #endif  // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
434 
435   auto status = client->DeleteObject(bucket_name_, object_name);
436   EXPECT_STATUS_OK(status);
437 }
438 
439 /// @test Verify that MD5 hash mismatches are reported by default on downloads.
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadJSON)440 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadJSON) {
441   // This test is disabled when not using the emulator as it relies on the
442   // emulator to inject faults.
443   if (!UsingEmulator()) GTEST_SKIP();
444   StatusOr<Client> client = MakeIntegrationTestClient();
445   ASSERT_STATUS_OK(client);
446 
447   auto object_name = MakeRandomObjectName();
448 
449   // Create an object and a stream to read it back.
450   StatusOr<ObjectMetadata> meta = client->InsertObject(
451       bucket_name_, object_name, LoremIpsum(), EnableMD5Hash(),
452       IfGenerationMatch(0), Projection::Full());
453   ASSERT_STATUS_OK(meta);
454 
455   auto stream = client->ReadObject(
456       bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
457       IfMetagenerationNotMatch(0),
458       CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
459 
460 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
461   EXPECT_THROW(
462       try {
463         std::string actual(std::istreambuf_iterator<char>{stream},
464                            std::istreambuf_iterator<char>{});
465       } catch (HashMismatchError const& ex) {
466         EXPECT_NE(ex.received_hash(), ex.computed_hash());
467         EXPECT_THAT(ex.what(), HasSubstr("mismatched hashes"));
468         throw;
469       },
470       HashMismatchError);
471 #else
472   std::string actual(std::istreambuf_iterator<char>{stream}, {});
473   EXPECT_FALSE(stream.received_hash().empty());
474   EXPECT_FALSE(stream.computed_hash().empty());
475   EXPECT_NE(stream.received_hash(), stream.computed_hash());
476 #endif  // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
477 
478   auto status = client->DeleteObject(bucket_name_, object_name);
479   EXPECT_STATUS_OK(status);
480 }
481 
482 /// @test Verify that MD5 hash mismatches are reported when using .read().
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadXMLRead)483 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadXMLRead) {
484   // This test is disabled when not using the emulator as it relies on the
485   // emulator to inject faults.
486   if (!UsingEmulator()) GTEST_SKIP();
487   StatusOr<Client> client = MakeIntegrationTestClient();
488   ASSERT_STATUS_OK(client);
489 
490   auto object_name = MakeRandomObjectName();
491   auto contents = MakeRandomData(1024 * 1024);
492 
493   // Create an object and a stream to read it back.
494   StatusOr<ObjectMetadata> meta =
495       client->InsertObject(bucket_name_, object_name, contents, EnableMD5Hash(),
496                            IfGenerationMatch(0), Projection::Full());
497   ASSERT_STATUS_OK(meta);
498 
499   auto stream = client->ReadObject(
500       bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
501       CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
502 
503   // Create a buffer large enough to hold the results and read pas EOF.
504   std::vector<char> buffer(2 * contents.size());
505   stream.read(buffer.data(), buffer.size());
506 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
507   EXPECT_TRUE(stream.bad());
508 #endif  // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
509   EXPECT_EQ(StatusCode::kDataLoss, stream.status().code());
510   EXPECT_NE(stream.received_hash(), stream.computed_hash());
511   EXPECT_EQ(stream.received_hash(), meta->md5_hash());
512 
513   auto status = client->DeleteObject(bucket_name_, object_name);
514   EXPECT_STATUS_OK(status);
515 }
516 
517 /// @test Verify that MD5 hash mismatches are reported when using .read().
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadJSONRead)518 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadJSONRead) {
519   // This test is disabled when not using the emulator as it relies on the
520   // emulator to inject faults.
521   if (!UsingEmulator()) GTEST_SKIP();
522   StatusOr<Client> client = MakeIntegrationTestClient();
523   ASSERT_STATUS_OK(client);
524 
525   auto object_name = MakeRandomObjectName();
526   auto contents = MakeRandomData(1024 * 1024);
527 
528   // Create an object and a stream to read it back.
529   StatusOr<ObjectMetadata> meta =
530       client->InsertObject(bucket_name_, object_name, contents, EnableMD5Hash(),
531                            IfGenerationMatch(0), Projection::Full());
532   ASSERT_STATUS_OK(meta);
533 
534   auto stream = client->ReadObject(
535       bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
536       IfMetagenerationNotMatch(0),
537       CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
538 
539   // Create a buffer large enough to hold the results and read pas EOF.
540   std::vector<char> buffer(2 * contents.size());
541   stream.read(buffer.data(), buffer.size());
542 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
543   EXPECT_TRUE(stream.bad());
544 #endif  // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
545   EXPECT_EQ(StatusCode::kDataLoss, stream.status().code());
546   EXPECT_NE(stream.received_hash(), stream.computed_hash());
547   EXPECT_EQ(stream.received_hash(), meta->md5_hash());
548 
549   auto status = client->DeleteObject(bucket_name_, object_name);
550   EXPECT_STATUS_OK(status);
551 }
552 
553 /// @test Verify that MD5 hash mismatches are reported by default on downloads.
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingWriteJSON)554 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingWriteJSON) {
555   // This test is disabled when not using the emulator as it relies on the
556   // emulator to inject faults.
557   if (!UsingEmulator()) GTEST_SKIP();
558   StatusOr<Client> client = MakeIntegrationTestClient();
559   ASSERT_STATUS_OK(client);
560 
561   auto object_name = MakeRandomObjectName();
562 
563   // Create a stream to upload an object.
564   ObjectWriteStream stream = client->WriteObject(
565       bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
566       IfGenerationMatch(0),
567       CustomHeader("x-goog-emulator-instructions", "inject-upload-data-error"));
568   stream << LoremIpsum() << "\n";
569   stream << LoremIpsum();
570 
571   stream.Close();
572   EXPECT_TRUE(stream.bad());
573   EXPECT_STATUS_OK(stream.metadata());
574   EXPECT_NE(stream.received_hash(), stream.computed_hash());
575 
576   auto status = client->DeleteObject(bucket_name_, object_name);
577   EXPECT_STATUS_OK(status);
578 }
579 
580 }  // anonymous namespace
581 }  // namespace STORAGE_CLIENT_NS
582 }  // namespace storage
583 }  // namespace cloud
584 }  // namespace google
585