]>
Commit | Line | Data |
---|---|---|
c680a8e1 RS |
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 | ||
5 | package armor | |
6 | ||
7 | import ( | |
8 | "encoding/base64" | |
9 | "io" | |
10 | ) | |
11 | ||
12 | var armorHeaderSep = []byte(": ") | |
13 | var blockEnd = []byte("\n=") | |
14 | var newline = []byte("\n") | |
15 | var armorEndOfLineOut = []byte("-----\n") | |
16 | ||
17 | // writeSlices writes its arguments to the given Writer. | |
18 | func 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'. | |
30 | type lineBreaker struct { | |
31 | lineLength int | |
32 | line []byte | |
33 | used int | |
34 | out io.Writer | |
35 | haveWritten bool | |
36 | } | |
37 | ||
38 | func 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 | ||
47 | func (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 | ||
83 | func (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 | |
100 | type encoding struct { | |
101 | out io.Writer | |
102 | breaker *lineBreaker | |
103 | b64 io.WriteCloser | |
104 | crc uint32 | |
105 | blockType []byte | |
106 | } | |
107 | ||
108 | func (e *encoding) Write(data []byte) (n int, err error) { | |
109 | e.crc = crc24(e.crc, data) | |
110 | return e.b64.Write(data) | |
111 | } | |
112 | ||
113 | func (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. | |
133 | func 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 | } |