| // 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[:]) |
| |
| return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine) |
| } |
| |
| // 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 |
| } |