1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
// Copyright 2019 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 tlog
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
"unicode/utf8"
)
// A Tree is a tree description, to be signed by a go.sum database server.
type Tree struct {
N int64
Hash Hash
}
// FormatTree formats a tree description for inclusion in a note.
//
// The encoded form is three lines, each ending in a newline (U+000A):
//
// go.sum database tree
// N
// Hash
//
// where N is in decimal and Hash is in base64.
//
// A future backwards-compatible encoding may add additional lines,
// which the parser can ignore.
// A future backwards-incompatible encoding would use a different
// first line (for example, "go.sum database tree v2").
func FormatTree(tree Tree) []byte {
return []byte(fmt.Sprintf("go.sum database tree\n%d\n%s\n", tree.N, tree.Hash))
}
var errMalformedTree = errors.New("malformed tree note")
var treePrefix = []byte("go.sum database tree\n")
// ParseTree parses a tree root description.
func ParseTree(text []byte) (tree Tree, err error) {
// The message looks like:
//
// go.sum database tree
// 2
// nND/nri/U0xuHUrYSy0HtMeal2vzD9V4k/BO79C+QeI=
//
// For forwards compatibility, extra text lines after the encoding are ignored.
if !bytes.HasPrefix(text, treePrefix) || bytes.Count(text, []byte("\n")) < 3 || len(text) > 1e6 {
return Tree{}, errMalformedTree
}
lines := strings.SplitN(string(text), "\n", 4)
n, err := strconv.ParseInt(lines[1], 10, 64)
if err != nil || n < 0 || lines[1] != strconv.FormatInt(n, 10) {
return Tree{}, errMalformedTree
}
h, err := base64.StdEncoding.DecodeString(lines[2])
if err != nil || len(h) != HashSize {
return Tree{}, errMalformedTree
}
var hash Hash
copy(hash[:], h)
return Tree{n, hash}, nil
}
var errMalformedRecord = errors.New("malformed record data")
// FormatRecord formats a record for serving to a client
// in a lookup response or data tile.
//
// The encoded form is the record ID as a single number,
// then the text of the record, and then a terminating blank line.
// Record text must be valid UTF-8 and must not contain any ASCII control
// characters (those below U+0020) other than newline (U+000A).
// It must end in a terminating newline and not contain any blank lines.
func FormatRecord(id int64, text []byte) (msg []byte, err error) {
if !isValidRecordText(text) {
return nil, errMalformedRecord
}
msg = []byte(fmt.Sprintf("%d\n", id))
msg = append(msg, text...)
msg = append(msg, '\n')
return msg, nil
}
// isValidRecordText reports whether text is syntactically valid record text.
func isValidRecordText(text []byte) bool {
var last rune
for i := 0; i < len(text); {
r, size := utf8.DecodeRune(text[i:])
if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 || last == '\n' && r == '\n' {
return false
}
i += size
last = r
}
if last != '\n' {
return false
}
return true
}
// ParseRecord parses a record description at the start of text,
// stopping immediately after the terminating blank line.
// It returns the record id, the record text, and the remainder of text.
func ParseRecord(msg []byte) (id int64, text, rest []byte, err error) {
// Leading record id.
i := bytes.IndexByte(msg, '\n')
if i < 0 {
return 0, nil, nil, errMalformedRecord
}
id, err = strconv.ParseInt(string(msg[:i]), 10, 64)
if err != nil {
return 0, nil, nil, errMalformedRecord
}
msg = msg[i+1:]
// Record text.
i = bytes.Index(msg, []byte("\n\n"))
if i < 0 {
return 0, nil, nil, errMalformedRecord
}
text, rest = msg[:i+1], msg[i+2:]
if !isValidRecordText(text) {
return 0, nil, nil, errMalformedRecord
}
return id, text, rest, nil
}
|