Repository: quic-go/quic-go
Pull request #4476 (2024-05-03 10:52:00)

http3: return an io.EOF when parsing a capsule fails on the first byte

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/quic-go/quic-go/pull/4476?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 84.89%. Comparing base [(`0d565fc`)](https://app.codecov.io/gh/quic-go/quic-go/commit/0d565fc384067b4f629ab9b5a661550b733ffbf4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) to head [(`9600472`)](https://app.codecov.io/gh/quic-go/quic-go/pull/4476?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4476      +/-   ##
==========================================
+ Coverage   84.88%   84.89%   +0.02%     
==========================================
  Files         154      154              
  Lines       14647    14656       +9     
==========================================
+ Hits        12432    12442      +10     
+ Misses       1702     1701       -1     
  Partials      513      513              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/quic-go/quic-go/pull/4476?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


Patch:
diff --git a/http3/capsule.go b/http3/capsule.go
index 7bdcd4e5..69d4037a 100644
--- a/http3/capsule.go
+++ b/http3/capsule.go
@@ -21,13 +21,29 @@ func (r *exactReader) Read(b []byte) (int, error) {
 	return n, err
 }
 
+type countingByteReader struct {
+	io.ByteReader
+	Read int
+}
+
+func (r *countingByteReader) ReadByte() (byte, error) {
+	b, err := r.ByteReader.ReadByte()
+	if err == nil {
+		r.Read++
+	}
+	return b, err
+}
+
 // ParseCapsule parses the header of a Capsule.
 // It returns an io.LimitedReader that can be used to read the Capsule value.
 // The Capsule value must be read entirely (i.e. until the io.EOF) before using r again.
 func ParseCapsule(r quicvarint.Reader) (CapsuleType, io.Reader, error) {
-	ct, err := quicvarint.Read(r)
+	cbr := countingByteReader{ByteReader: r}
+	ct, err := quicvarint.Read(&cbr)
 	if err != nil {
-		if err == io.EOF {
+		// If an io.EOF is returned without consuming any bytes, return it unmodified.
+		// Otherwise, return an io.ErrUnexpectedEOF.
+		if err == io.EOF && cbr.Read > 0 {
 			return 0, nil, io.ErrUnexpectedEOF
 		}
 		return 0, nil, err


Test patch:
diff --git a/http3/capsule_test.go b/http3/capsule_test.go
index 4920e887..3e7e25e6 100644
--- a/http3/capsule_test.go
+++ b/http3/capsule_test.go
@@ -26,7 +26,7 @@ var _ = Describe("Capsule", func() {
 
 	It("writes capsules", func() {
 		var buf bytes.Buffer
-		WriteCapsule(&buf, 1337, []byte("foobar"))
+		Expect(WriteCapsule(&buf, 1337, []byte("foobar"))).To(Succeed())
 
 		ct, r, err := ParseCapsule(&buf)
 		Expect(err).ToNot(HaveOccurred())
@@ -44,7 +44,11 @@ var _ = Describe("Capsule", func() {
 		for i := range b {
 			ct, r, err := ParseCapsule(bytes.NewReader(b[:i]))
 			if err != nil {
-				Expect(err).To(MatchError(io.ErrUnexpectedEOF))
+				if i == 0 {
+					Expect(err).To(MatchError(io.EOF))
+				} else {
+					Expect(err).To(MatchError(io.ErrUnexpectedEOF))
+				}
 				continue
 			}
 			Expect(ct).To(BeEquivalentTo(1337))