Internal changes

PiperOrigin-RevId: 977768025
diff --git a/snappy-c.cc b/snappy-c.cc
index 473a0b0..6da18d8 100644
--- a/snappy-c.cc
+++ b/snappy-c.cc
@@ -39,6 +39,9 @@
     return SNAPPY_BUFFER_TOO_SMALL;
   }
   snappy::RawCompress(input, input_length, compressed, compressed_length);
+  if (*compressed_length == 0) {
+    return SNAPPY_INVALID_INPUT;
+  }
   return SNAPPY_OK;
 }
 
diff --git a/snappy-c.h b/snappy-c.h
index 32aa0c6..e228785 100644
--- a/snappy-c.h
+++ b/snappy-c.h
@@ -59,6 +59,9 @@
  * <compressed_length> contains the true length of the compressed output,
  * and SNAPPY_OK is returned.
  *
+ * An <input_length> of 2^32 or more returns SNAPPY_INVALID_INPUT with
+ * <compressed_length> set to 0 and nothing written.
+ *
  * Example:
  *   size_t output_length = snappy_max_compressed_length(input_length);
  *   char* output = (char*)malloc(output_length);
diff --git a/snappy-test.cc b/snappy-test.cc
index aae6072..5c9d041 100644
--- a/snappy-test.cc
+++ b/snappy-test.cc
@@ -464,7 +464,8 @@
   // Make sure we're at the end-of-compressed-data point.  This means
   // if we call inflate with Z_FINISH we won't consume any input or
   // write any output
-  Bytef dummyin, dummyout;
+  Bytef dummyout;
+  Bytef dummyin = 0;
   uLongf dummylen = 0;
   if ( UncompressChunkOrAll(&dummyout, &dummylen, &dummyin, 0, Z_FINISH)
        != Z_OK ) {
diff --git a/snappy.cc b/snappy.cc
index 5bcd580..add72f8 100644
--- a/snappy.cc
+++ b/snappy.cc
@@ -1888,7 +1888,10 @@
   assert(options.level == 1 || options.level == 2);
   size_t written = 0;
   size_t N = reader->Available();
-  assert(N <= 0xFFFFFFFFu);
+  // The uncompressed length is a 32-bit varint in the stream format.
+  if (static_cast<uint64_t>(N) > std::numeric_limits<uint32_t>::max()) {
+    return 0;
+  }
   char ulength[Varint::kMax32];
   char* p = Varint::Encode32(ulength, N);
   writer->Append(ulength, p - ulength);
diff --git a/snappy.h b/snappy.h
index 3653739..1c44868 100644
--- a/snappy.h
+++ b/snappy.h
@@ -129,7 +129,7 @@
   // ------------------------------------------------------------------------
 
   // Compress the bytes read from "*reader" and append to "*writer". Return the
-  // number of bytes written.
+  // number of bytes written, or zero if "*reader" has 2^32 or more bytes.
   // First version is to preserve ABI.
   size_t Compress(Source* reader, Sink* writer);
   size_t Compress(Source* reader, Sink* writer,
@@ -154,7 +154,8 @@
   // ------------------------------------------------------------------------
 
   // Sets "*compressed" to the compressed version of "input[0..input_length-1]".
-  // Original contents of *compressed are lost.
+  // Original contents of *compressed are lost. Returns zero and writes nothing
+  // if "input_length" is 2^32 or more.
   //
   // REQUIRES: "input[]" is not an alias of "*compressed".
   // First version is to preserve ABI.
@@ -166,7 +167,8 @@
   // Same as `Compress` above but taking an `iovec` array as input. Note that
   // this function preprocesses the inputs to compute the sum of
   // `iov[0..iov_cnt-1].iov_len` before reading. To avoid this, use
-  // `RawCompressFromIOVec` below.
+  // `RawCompressFromIOVec` below. Returns zero and writes nothing if that sum
+  // is 2^32 or more.
   // First version is to preserve ABI.
   size_t CompressFromIOVec(const struct iovec* iov, size_t iov_cnt,
                            std::string* compressed);
@@ -207,6 +209,9 @@
   // Takes the data stored in "input[0..input_length]" and stores
   // it in the array pointed to by "compressed".
   //
+  // "*compressed_length" is set to the length of the compressed output, or to
+  // zero, with nothing written, if "input_length" is 2^32 or more.
+  //
   // "*compressed_length" is set to the length of the compressed output.
   //
   // Example:
@@ -228,7 +233,9 @@
 
   // Same as `RawCompress` above but taking an `iovec` array as input. Note that
   // `uncompressed_length` is the total number of bytes to be read from the
-  // elements of `iov` (_not_ the number of elements in `iov`).
+  // elements of `iov` (_not_ the number of elements in `iov`). Sets
+  // "*compressed_length" to zero and writes nothing if `uncompressed_length`
+  // is 2^32 or more.
   // First version is to preserve ABI.
   void RawCompressFromIOVec(const struct iovec* iov, size_t uncompressed_length,
                             char* compressed, size_t* compressed_length);
diff --git a/snappy_unittest.cc b/snappy_unittest.cc
index 8d83858..db60f1d 100644
--- a/snappy_unittest.cc
+++ b/snappy_unittest.cc
@@ -28,6 +28,7 @@
 
 #include <algorithm>
 #include <cinttypes>
+#include <cstdint>
 #include <cmath>
 #include <cstdlib>
 #include <random>
@@ -647,6 +648,70 @@
   EXPECT_EQ(a, b);
 }
 
+// An input of 2^32 bytes or more cannot be expressed by the stream format and
+// must be refused rather than compressed under a truncated length.
+#if SIZE_MAX > 0xFFFFFFFFu
+
+// Reports an arbitrary number of bytes available without materializing them.
+class OversizedSource : public Source {
+ public:
+  explicit OversizedSource(uint64_t total)
+      : left_(total), buf_(1 << 16, 'a') {}
+  size_t Available() const override { return static_cast<size_t>(left_); }
+  const char* Peek(size_t* len) override {
+    *len = static_cast<size_t>(std::min<uint64_t>(left_, buf_.size()));
+    return buf_.data();
+  }
+  void Skip(size_t n) override { left_ -= n; }
+
+ private:
+  uint64_t left_;
+  std::string buf_;
+};
+
+// Counts every appended byte and keeps the first few, which is where the
+// uncompressed-length varint lives.
+class CountingSink : public Sink {
+ public:
+  void Append(const char* data, size_t n) override {
+    for (size_t i = 0; i < n && head_.size() < 8; ++i) head_.push_back(data[i]);
+    total_ += n;
+  }
+
+  std::string head_;
+  uint64_t total_ = 0;
+};
+
+// Decodes the uncompressed-length varint a compressed stream starts with.
+uint32_t DeclaredLength(const std::string& stream) {
+  uint32_t result = 0;
+  int shift = 0;
+  for (size_t i = 0; i < stream.size(); ++i) {
+    const unsigned char c = static_cast<unsigned char>(stream[i]);
+    result |= static_cast<uint32_t>(c & 0x7f) << shift;
+    if (c < 128) break;
+    shift += 7;
+  }
+  return result;
+}
+
+TEST(Snappy, RefusesInputLongerThanTheFormatCanExpress) {
+  OversizedSource too_big(uint64_t{1} << 32);
+  CountingSink refused;
+  EXPECT_EQ(0u, Compress(&too_big, &refused));
+  EXPECT_EQ(0u, refused.total_);
+  EXPECT_TRUE(refused.head_.empty());
+
+  // One byte below that is the largest input the format can express, and it
+  // still compresses to a stream whose header names its real length.
+  OversizedSource largest((uint64_t{1} << 32) - 1);
+  CountingSink accepted;
+  EXPECT_GT(Compress(&largest, &accepted), 0u);
+  EXPECT_EQ(0xFFFFFFFFu, DeclaredLength(accepted.head_));
+}
+
+#endif  // SIZE_MAX > 0xFFFFFFFFu
+
 TEST(Snappy, FourByteOffset) {
   // The new compressor cannot generate four-byte offsets since
   // it chops up the input into 32KB pieces.  So we hand-emit the