diff --git a/armor/armor.go b/armor/armor.go index cc4dd7ac..17f224da 100644 --- a/armor/armor.go +++ b/armor/armor.go @@ -102,8 +102,11 @@ func (r *armoredReader) Read(p []byte) (int, error) { } getLine := func() ([]byte, error) { - line, err := r.r.ReadBytes('\n') - if err == io.EOF && len(line) == 0 { + // ReadSlice bounds the line to the bufio buffer, unlike ReadBytes. + line, err := r.r.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, errors.New("column limit exceeded") + } else if err == io.EOF && len(line) == 0 { return nil, io.ErrUnexpectedEOF } else if err != nil && err != io.EOF { return nil, err diff --git a/armor/armor_test.go b/armor/armor_test.go index ca25e2b5..d7ac1f76 100644 --- a/armor/armor_test.go +++ b/armor/armor_test.go @@ -116,6 +116,39 @@ func TestWriterLifecycle(t *testing.T) { }) } +type endless byte + +func (e endless) Read(p []byte) (int, error) { + for i := range p { + p[i] = byte(e) + } + return len(p), nil +} + +type countingReader struct { + r io.Reader + n int +} + +func (c *countingReader) Read(p []byte) (int, error) { + n, err := c.r.Read(p) + c.n += n + return n, err +} + +func TestLongLine(t *testing.T) { + for _, prefix := range []string{"", armor.Header + "\n"} { + cr := &countingReader{r: io.MultiReader(strings.NewReader(prefix), + io.LimitReader(endless('A'), 64<<20))} + if _, err := io.ReadAll(armor.NewReader(cr)); err == nil { + t.Errorf("prefix %q: expected error", prefix) + } + if cr.n > 1<<20 { + t.Errorf("prefix %q: read %d bytes before failing", prefix, cr.n) + } + } +} + func testArmor(t *testing.T, size int) { buf := &bytes.Buffer{} w := armor.NewWriter(buf)