encode.go 3.3 KB

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