WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 6596145

Browse files
committed
armor: don't leave an empty line before the footer
Closes #264 Fixes #263
1 parent 7a262e1 commit 6596145

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-16
lines changed

armor/armor.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const (
2828

2929
type armoredWriter struct {
3030
started, closed bool
31-
encoder io.WriteCloser
31+
encoder *format.WrappedBase64Encoder
3232
dst io.Writer
3333
}
3434

@@ -50,15 +50,20 @@ func (a *armoredWriter) Close() error {
5050
if err := a.encoder.Close(); err != nil {
5151
return err
5252
}
53-
_, err := io.WriteString(a.dst, "\n"+Footer+"\n")
53+
footer := Footer + "\n"
54+
if !a.encoder.LastLineIsEmpty() {
55+
footer = "\n" + footer
56+
}
57+
_, err := io.WriteString(a.dst, footer)
5458
return err
5559
}
5660

5761
func NewWriter(dst io.Writer) io.WriteCloser {
5862
// TODO: write a test with aligned and misaligned sizes, and 8 and 10 steps.
59-
return &armoredWriter{dst: dst,
60-
encoder: base64.NewEncoder(base64.StdEncoding.Strict(),
61-
format.NewlineWriter(dst))}
63+
return &armoredWriter{
64+
dst: dst,
65+
encoder: format.NewWrappedBase64Encoder(base64.StdEncoding, dst),
66+
}
6267
}
6368

6469
type armoredReader struct {

armor/armor_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package armor_test
88

99
import (
1010
"bytes"
11+
"crypto/rand"
1112
"encoding/pem"
1213
"fmt"
1314
"io"
@@ -18,6 +19,7 @@ import (
1819

1920
"filippo.io/age"
2021
"filippo.io/age/armor"
22+
"filippo.io/age/internal/format"
2123
)
2224

2325
func ExampleNewWriter() {
@@ -87,9 +89,15 @@ kB/RRusYjn+KVJ+KTioxj0THtzZPXcjFKuQ1
8789
}
8890

8991
func TestArmor(t *testing.T) {
92+
t.Run("PartialLine", func(t *testing.T) { testArmor(t, 611) })
93+
t.Run("FullLine", func(t *testing.T) { testArmor(t, 10*format.BytesPerLine) })
94+
}
95+
96+
func testArmor(t *testing.T, size int) {
9097
buf := &bytes.Buffer{}
9198
w := armor.NewWriter(buf)
92-
plain := make([]byte, 611)
99+
plain := make([]byte, size)
100+
rand.Read(plain)
93101
if _, err := w.Write(plain); err != nil {
94102
t.Fatal(err)
95103
}
@@ -101,9 +109,18 @@ func TestArmor(t *testing.T) {
101109
if block == nil {
102110
t.Fatal("PEM decoding failed")
103111
}
112+
if len(block.Headers) != 0 {
113+
t.Error("unexpected headers")
114+
}
115+
if block.Type != "AGE ENCRYPTED FILE" {
116+
t.Errorf("unexpected type %q", block.Type)
117+
}
104118
if !bytes.Equal(block.Bytes, plain) {
105119
t.Error("PEM decoded value doesn't match")
106120
}
121+
if !bytes.Equal(buf.Bytes(), pem.EncodeToMemory(block)) {
122+
t.Error("PEM re-encoded value doesn't match")
123+
}
107124

108125
r := armor.NewReader(buf)
109126
out, err := ioutil.ReadAll(r)

internal/format/format.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,40 @@ func DecodeString(s string) ([]byte, error) {
4343
var EncodeToString = b64.EncodeToString
4444

4545
const ColumnsPerLine = 64
46+
4647
const BytesPerLine = ColumnsPerLine / 4 * 3
4748

48-
// NewlineWriter returns a Writer that writes to dst, inserting an LF character
49-
// every ColumnsPerLine bytes. It does not insert a newline neither at the
50-
// beginning nor at the end of the stream, but it ensures the last line is
51-
// shorter than ColumnsPerLine, which means it might be empty.
52-
func NewlineWriter(dst io.Writer) io.Writer {
53-
return &newlineWriter{dst: dst}
49+
// NewWrappedBase64Encoder returns a WrappedBase64Encoder that writes to dst.
50+
func NewWrappedBase64Encoder(enc *base64.Encoding, dst io.Writer) *WrappedBase64Encoder {
51+
w := &WrappedBase64Encoder{dst: dst}
52+
w.enc = base64.NewEncoder(enc, WriterFunc(w.writeWrapped))
53+
return w
5454
}
5555

56-
type newlineWriter struct {
56+
type WriterFunc func(p []byte) (int, error)
57+
58+
func (f WriterFunc) Write(p []byte) (int, error) { return f(p) }
59+
60+
// WrappedBase64Encoder is a standard base64 encoder that inserts an LF
61+
// character every ColumnsPerLine bytes. It does not insert a newline neither at
62+
// the beginning nor at the end of the stream, but it ensures the last line is
63+
// shorter than ColumnsPerLine, which means it might be empty.
64+
type WrappedBase64Encoder struct {
65+
enc io.WriteCloser
5766
dst io.Writer
5867
written int
5968
buf bytes.Buffer
6069
}
6170

62-
func (w *newlineWriter) Write(p []byte) (int, error) {
71+
func (w *WrappedBase64Encoder) Write(p []byte) (int, error) { return w.enc.Write(p) }
72+
73+
func (w *WrappedBase64Encoder) Close() error {
74+
return w.enc.Close()
75+
}
76+
77+
func (w *WrappedBase64Encoder) writeWrapped(p []byte) (int, error) {
6378
if w.buf.Len() != 0 {
64-
panic("age: internal error: non-empty newlineWriter.buf")
79+
panic("age: internal error: non-empty WrappedBase64Encoder.buf")
6580
}
6681
for len(p) > 0 {
6782
toWrite := ColumnsPerLine - (w.written % ColumnsPerLine)
@@ -84,9 +99,18 @@ func (w *newlineWriter) Write(p []byte) (int, error) {
8499
return len(p), nil
85100
}
86101

102+
// LastLineIsEmpty returns whether the last output line was empty, either
103+
// because no input was written, or because a multiple of BytesPerLine was.
104+
//
105+
// Calling LastLineIsEmpty before Close is meaningless.
106+
func (w *WrappedBase64Encoder) LastLineIsEmpty() bool {
107+
return w.written%ColumnsPerLine == 0
108+
}
109+
87110
const intro = "age-encryption.org/v1\n"
88111

89112
var recipientPrefix = []byte("->")
113+
90114
var footerPrefix = []byte("---")
91115

92116
func (r *Stanza) Marshal(w io.Writer) error {
@@ -101,7 +125,7 @@ func (r *Stanza) Marshal(w io.Writer) error {
101125
if _, err := io.WriteString(w, "\n"); err != nil {
102126
return err
103127
}
104-
ww := base64.NewEncoder(b64, NewlineWriter(w))
128+
ww := NewWrappedBase64Encoder(b64, w)
105129
if _, err := ww.Write(r.Body); err != nil {
106130
return err
107131
}

0 commit comments

Comments
 (0)