@@ -35,17 +35,20 @@ CurlReadStreambuf::CurlReadStreambuf(
3535
3636bool CurlReadStreambuf::IsOpen () const { return download_.IsOpen (); }
3737
38- HttpResponse CurlReadStreambuf::Close () {
39- // TODO(#1736) - return StatusOr<> from here.
40- return download_.Close ().value ();
38+ void CurlReadStreambuf::Close () {
39+ auto response = download_.Close ();
40+ if (not response.ok ()) {
41+ status_ = std::move (response).status ();
42+ ReportError (status_);
43+ }
4144}
4245
4346CurlReadStreambuf::int_type CurlReadStreambuf::underflow () {
4447 if (not IsOpen ()) {
45- current_ios_buffer_. clear ();
46- current_ios_buffer_. push_back ( ' \0 ' );
47- char * data = ¤t_ios_buffer_[ 0 ];
48- setg (data, data + 1 , data + 1 );
48+ // The stream is closed, reading from a closed stream can happen if there is
49+ // no object to read from, or the object is empty. In that case just setup
50+ // an empty (but valid) region and verify the checksums.
51+ SetEmptyRegion ( );
4952 hash_validator_result_ = HashValidator::FinishAndCheck (
5053 __func__ + std::string (" mismatched hashes reading from closed stream" ),
5154 std::move (*hash_validator_));
@@ -55,13 +58,15 @@ CurlReadStreambuf::int_type CurlReadStreambuf::underflow() {
5558 current_ios_buffer_.reserve (target_buffer_size_);
5659 StatusOr<HttpResponse> response = download_.GetMore (current_ios_buffer_);
5760 if (not response.ok ()) {
58- return traits_type::eof ( );
61+ return ReportError ( std::move (response). status () );
5962 }
6063 for (auto const & kv : response->headers ) {
6164 hash_validator_->ProcessHeader (kv.first , kv.second );
65+ headers_.emplace (kv.first , kv.second );
6266 }
6367 if (response->status_code >= 300 ) {
64- return traits_type::eof ();
68+ return ReportError (
69+ Status (response->status_code , std::move (response->payload )));
6570 }
6671
6772 if (not current_ios_buffer_.empty ()) {
@@ -70,15 +75,41 @@ CurlReadStreambuf::int_type CurlReadStreambuf::underflow() {
7075 setg (data, data, data + current_ios_buffer_.size ());
7176 return traits_type::to_int_type (*data);
7277 }
73- current_ios_buffer_.push_back (' \0 ' );
74- char * data = ¤t_ios_buffer_[0 ];
75- setg (data, data + 1 , data + 1 );
78+
79+ // This is an actual EOF, there is no more data to download, create an
80+ // empty (but valid) region:
81+ SetEmptyRegion ();
82+ // Verify the checksums, and return the EOF character.
7683 hash_validator_result_ = HashValidator::FinishAndCheck (
7784 __func__ + std::string (" mismatched hashes at end of download" ),
7885 std::move (*hash_validator_));
7986 return traits_type::eof ();
8087}
8188
89+ CurlReadStreambuf::int_type CurlReadStreambuf::ReportError (Status status) {
90+ // The only way to report errors from a std::basic_streambuf<> (which this
91+ // class derives from) is to throw exceptions:
92+ // https://stackoverflow.com/questions/50716688/how-to-set-the-badbit-of-a-stream-by-a-customized-streambuf
93+ // but we need to be able to report errors when the application has disabled
94+ // exceptions via `-fno-exceptions` or a similar option. In that case we set
95+ // `status_`, and report the error as an EOF. This is obviously not ideal,
96+ // but it is the best we can do when the application disables the standard
97+ // mechanism to signal errors.
98+ status_ = std::move (status);
99+ #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
100+ internal::ThrowStatus (status_);
101+ #else
102+ return traits_type::eof ();
103+ #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
104+ }
105+
106+ void CurlReadStreambuf::SetEmptyRegion () {
107+ current_ios_buffer_.clear ();
108+ current_ios_buffer_.push_back (' \0 ' );
109+ char * data = ¤t_ios_buffer_[0 ];
110+ setg (data, data + 1 , data + 1 );
111+ }
112+
82113CurlWriteStreambuf::CurlWriteStreambuf (
83114 CurlUploadRequest&& upload, std::size_t max_buffer_size,
84115 std::unique_ptr<HashValidator> hash_validator)
0 commit comments