2017-03-15 21:27:35 -04:00
|
|
|
// Copyright 2010 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package armor
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
|
|
|
var armorHeaderSep = []byte(": ")
|
|
|
|
var blockEnd = []byte("\n=")
|
|
|
|
var newline = []byte("\n")
|
|
|
|
var armorEndOfLineOut = []byte("-----\n")
|
|
|
|
|
|
|
|
// writeSlices writes its arguments to the given Writer.
|
|
|
|
func writeSlices(out io.Writer, slices ...[]byte) (err error) {
|
|
|
|
for _, s := range slices {
|
|
|
|
_, err = out.Write(s)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// lineBreaker breaks data across several lines, all of the same byte length
|
|
|
|
// (except possibly the last). Lines are broken with a single '\n'.
|
|
|
|
type lineBreaker struct {
|
|
|
|
lineLength int
|
|
|
|
line []byte
|
|
|
|
used int
|
|
|
|
out io.Writer
|
|
|
|
haveWritten bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
|
|
|
|
return &lineBreaker{
|
|
|
|
lineLength: lineLength,
|
|
|
|
line: make([]byte, lineLength),
|
|
|
|
used: 0,
|
|
|
|
out: out,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *lineBreaker) Write(b []byte) (n int, err error) {
|
|
|
|
n = len(b)
|
|
|
|
|
|
|
|
if n == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if l.used == 0 && l.haveWritten {
|
|
|
|
_, err = l.out.Write([]byte{'\n'})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if l.used+len(b) < l.lineLength {
|
|
|
|
l.used += copy(l.line[l.used:], b)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
l.haveWritten = true
|
|
|
|
_, err = l.out.Write(l.line[0:l.used])
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
excess := l.lineLength - l.used
|
|
|
|
l.used = 0
|
|
|
|
|
|
|
|
_, err = l.out.Write(b[0:excess])
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = l.Write(b[excess:])
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *lineBreaker) Close() (err error) {
|
|
|
|
if l.used > 0 {
|
|
|
|
_, err = l.out.Write(l.line[0:l.used])
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// encoding keeps track of a running CRC24 over the data which has been written
|
|
|
|
// to it and outputs a OpenPGP checksum when closed, followed by an armor
|
|
|
|
// trailer.
|
|
|
|
//
|
|
|
|
// It's built into a stack of io.Writers:
|
|
|
|
// encoding -> base64 encoder -> lineBreaker -> out
|
|
|
|
type encoding struct {
|
|
|
|
out io.Writer
|
|
|
|
breaker *lineBreaker
|
|
|
|
b64 io.WriteCloser
|
|
|
|
crc uint32
|
|
|
|
blockType []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *encoding) Write(data []byte) (n int, err error) {
|
|
|
|
e.crc = crc24(e.crc, data)
|
|
|
|
return e.b64.Write(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *encoding) Close() (err error) {
|
|
|
|
err = e.b64.Close()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
e.breaker.Close()
|
|
|
|
|
|
|
|
var checksumBytes [3]byte
|
|
|
|
checksumBytes[0] = byte(e.crc >> 16)
|
|
|
|
checksumBytes[1] = byte(e.crc >> 8)
|
|
|
|
checksumBytes[2] = byte(e.crc)
|
|
|
|
|
|
|
|
var b64ChecksumBytes [4]byte
|
|
|
|
base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
|
|
|
|
|
2017-06-13 20:43:43 -04:00
|
|
|
return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine, []byte{'\n'})
|
2017-03-15 21:27:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Encode returns a WriteCloser which will encode the data written to it in
|
|
|
|
// OpenPGP armor.
|
|
|
|
func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
|
|
|
|
bType := []byte(blockType)
|
|
|
|
err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range headers {
|
|
|
|
err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = out.Write(newline)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
e := &encoding{
|
|
|
|
out: out,
|
|
|
|
breaker: newLineBreaker(out, 64),
|
|
|
|
crc: crc24Init,
|
|
|
|
blockType: bType,
|
|
|
|
}
|
|
|
|
e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
|
|
|
|
return e, nil
|
|
|
|
}
|