1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package armor
6
7import (
8	"encoding/base64"
9	"io"
10)
11
12var armorHeaderSep = []byte(": ")
13var blockEnd = []byte("\n=")
14var newline = []byte("\n")
15var armorEndOfLineOut = []byte("-----\n")
16
17// writeSlices writes its arguments to the given Writer.
18func writeSlices(out io.Writer, slices ...[]byte) (err error) {
19	for _, s := range slices {
20		_, err = out.Write(s)
21		if err != nil {
22			return err
23		}
24	}
25	return
26}
27
28// lineBreaker breaks data across several lines, all of the same byte length
29// (except possibly the last). Lines are broken with a single '\n'.
30type lineBreaker struct {
31	lineLength  int
32	line        []byte
33	used        int
34	out         io.Writer
35	haveWritten bool
36}
37
38func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
39	return &lineBreaker{
40		lineLength: lineLength,
41		line:       make([]byte, lineLength),
42		used:       0,
43		out:        out,
44	}
45}
46
47func (l *lineBreaker) Write(b []byte) (n int, err error) {
48	n = len(b)
49
50	if n == 0 {
51		return
52	}
53
54	if l.used == 0 && l.haveWritten {
55		_, err = l.out.Write([]byte{'\n'})
56		if err != nil {
57			return
58		}
59	}
60
61	if l.used+len(b) < l.lineLength {
62		l.used += copy(l.line[l.used:], b)
63		return
64	}
65
66	l.haveWritten = true
67	_, err = l.out.Write(l.line[0:l.used])
68	if err != nil {
69		return
70	}
71	excess := l.lineLength - l.used
72	l.used = 0
73
74	_, err = l.out.Write(b[0:excess])
75	if err != nil {
76		return
77	}
78
79	_, err = l.Write(b[excess:])
80	return
81}
82
83func (l *lineBreaker) Close() (err error) {
84	if l.used > 0 {
85		_, err = l.out.Write(l.line[0:l.used])
86		if err != nil {
87			return
88		}
89	}
90
91	return
92}
93
94// encoding keeps track of a running CRC24 over the data which has been written
95// to it and outputs a OpenPGP checksum when closed, followed by an armor
96// trailer.
97//
98// It's built into a stack of io.Writers:
99//    encoding -> base64 encoder -> lineBreaker -> out
100type encoding struct {
101	out       io.Writer
102	breaker   *lineBreaker
103	b64       io.WriteCloser
104	crc       uint32
105	blockType []byte
106}
107
108func (e *encoding) Write(data []byte) (n int, err error) {
109	e.crc = crc24(e.crc, data)
110	return e.b64.Write(data)
111}
112
113func (e *encoding) Close() (err error) {
114	err = e.b64.Close()
115	if err != nil {
116		return
117	}
118	e.breaker.Close()
119
120	var checksumBytes [3]byte
121	checksumBytes[0] = byte(e.crc >> 16)
122	checksumBytes[1] = byte(e.crc >> 8)
123	checksumBytes[2] = byte(e.crc)
124
125	var b64ChecksumBytes [4]byte
126	base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
127
128	return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
129}
130
131// Encode returns a WriteCloser which will encode the data written to it in
132// OpenPGP armor.
133func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
134	bType := []byte(blockType)
135	err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
136	if err != nil {
137		return
138	}
139
140	for k, v := range headers {
141		err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
142		if err != nil {
143			return
144		}
145	}
146
147	_, err = out.Write(newline)
148	if err != nil {
149		return
150	}
151
152	e := &encoding{
153		out:       out,
154		breaker:   newLineBreaker(out, 64),
155		crc:       crc24Init,
156		blockType: bType,
157	}
158	e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
159	return e, nil
160}
161