mirror of
https://github.com/FiloSottile/age.git
synced 2026-04-11 07:16:56 +00:00
159 lines
3.2 KiB
Go
159 lines
3.2 KiB
Go
// Copyright 2019 Google LLC
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file or at
|
|
// https://developers.google.com/open-source/licenses/bsd
|
|
|
|
package format
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
type newlineWriter struct {
|
|
dst io.Writer
|
|
written int
|
|
}
|
|
|
|
func (w *newlineWriter) Write(p []byte) (n int, err error) {
|
|
for len(p) > 0 {
|
|
remainingInLine := columnsPerLine - (w.written % columnsPerLine)
|
|
if remainingInLine == columnsPerLine && w.written != 0 {
|
|
if _, err := w.dst.Write([]byte("\n")); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
toWrite := remainingInLine
|
|
if toWrite > len(p) {
|
|
toWrite = len(p)
|
|
}
|
|
nn, err := w.dst.Write(p[:toWrite])
|
|
n += nn
|
|
w.written += nn
|
|
p = p[nn:]
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
type CloserFunc func() error
|
|
|
|
func (f CloserFunc) Close() error { return f() }
|
|
|
|
type nopCloser struct {
|
|
io.Writer
|
|
}
|
|
|
|
func (nopCloser) Close() error { return nil }
|
|
|
|
func NopCloser(w io.Writer) io.WriteCloser { return nopCloser{w} }
|
|
|
|
var endOfArmor = []byte("--- end of file ---\n")
|
|
|
|
func ArmoredWriter(dst io.Writer) io.WriteCloser {
|
|
// TODO: write a test with aligned and misaligned sizes, and 8 and 10 steps.
|
|
w := base64.NewEncoder(b64, &newlineWriter{dst: dst})
|
|
return struct {
|
|
io.Writer
|
|
io.Closer
|
|
}{
|
|
Writer: w,
|
|
Closer: CloserFunc(func() error {
|
|
if err := w.Close(); err != nil {
|
|
return err
|
|
}
|
|
if _, err := dst.Write([]byte("\n")); err != nil {
|
|
return err
|
|
}
|
|
_, err := dst.Write(endOfArmor)
|
|
return err
|
|
}),
|
|
}
|
|
}
|
|
|
|
type armoredReader struct {
|
|
r *bufio.Reader
|
|
unread []byte // backed by buf
|
|
buf [bytesPerLine]byte
|
|
err error
|
|
}
|
|
|
|
func ArmoredReader(r io.Reader) io.Reader {
|
|
return &armoredReader{r: bufio.NewReader(r)}
|
|
}
|
|
|
|
func (r *armoredReader) Read(p []byte) (int, error) {
|
|
if len(r.unread) > 0 {
|
|
n := copy(p, r.unread)
|
|
r.unread = r.unread[n:]
|
|
return n, nil
|
|
}
|
|
if r.err != nil {
|
|
return 0, r.err
|
|
}
|
|
|
|
getLine := func() ([]byte, error) {
|
|
line, err := r.r.ReadBytes('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = errors.New("invalid input")
|
|
}
|
|
return nil, err
|
|
}
|
|
// Unconditionally accept CRLF because the line ending context of the
|
|
// header is lost at the ArmoredReader caller. =(
|
|
if bytes.HasSuffix(line, []byte("\r\n")) {
|
|
line[len(line)-2] = '\n'
|
|
line = line[:len(line)-1]
|
|
}
|
|
return line, nil
|
|
}
|
|
|
|
line, err := getLine()
|
|
if err != nil {
|
|
return 0, r.setErr(err)
|
|
}
|
|
if bytes.Equal(line, endOfArmor) {
|
|
return 0, r.setErr(io.EOF)
|
|
}
|
|
line = bytes.TrimSuffix(line, []byte("\n"))
|
|
if bytes.Contains(line, []byte("\r")) {
|
|
return 0, r.setErr(errors.New("invalid input"))
|
|
}
|
|
if len(line) > columnsPerLine {
|
|
return 0, r.setErr(errors.New("invalid input"))
|
|
}
|
|
r.unread = r.buf[:]
|
|
n, err := b64.Decode(r.unread, line)
|
|
if err != nil {
|
|
return 0, r.setErr(err)
|
|
}
|
|
r.unread = r.unread[:n]
|
|
|
|
if n < bytesPerLine {
|
|
line, err := getLine()
|
|
if err != nil {
|
|
return 0, r.setErr(err)
|
|
}
|
|
if !bytes.Equal(line, endOfArmor) {
|
|
return 0, r.setErr(errors.New("invalid input"))
|
|
}
|
|
r.err = io.EOF
|
|
}
|
|
|
|
nn := copy(p, r.unread)
|
|
r.unread = r.unread[nn:]
|
|
return nn, nil
|
|
}
|
|
|
|
func (r *armoredReader) setErr(err error) error {
|
|
r.err = err
|
|
return err
|
|
}
|