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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
package uri
import (
"bytes"
"encoding/hex"
"fmt"
"net/url"
"os"
"strconv"
"strings"
"unicode"
"github.com/pkg/errors"
)
// URI implements a parser for a URI format based on the the PKCS #11 URI Scheme
// defined in https://tools.ietf.org/html/rfc7512
//
// These URIs will be used to define the key names in a KMS.
type URI struct {
*url.URL
Values url.Values
}
// New creates a new URI from a scheme and key-value pairs.
func New(scheme string, values url.Values) *URI {
return &URI{
URL: &url.URL{
Scheme: scheme,
Opaque: strings.ReplaceAll(values.Encode(), "&", ";"),
},
Values: values,
}
}
// NewFile creates an uri for a file.
func NewFile(path string) *URI {
return &URI{
URL: &url.URL{
Scheme: "file",
Path: path,
},
}
}
// NewOpaque returns a uri with the given scheme and the given opaque.
func NewOpaque(scheme, opaque string) *URI {
return &URI{
URL: &url.URL{
Scheme: scheme,
Opaque: opaque,
},
}
}
// HasScheme returns true if the given uri has the given scheme, false otherwise.
func HasScheme(scheme, rawuri string) bool {
u, err := url.Parse(rawuri)
if err != nil {
return false
}
return strings.EqualFold(u.Scheme, scheme)
}
// Parse returns the URI for the given string or an error.
func Parse(rawuri string) (*URI, error) {
u, err := url.Parse(rawuri)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %s", rawuri)
}
if u.Scheme == "" {
return nil, errors.Errorf("error parsing %s: scheme is missing", rawuri)
}
// Starting with Go 1.17 url.ParseQuery returns an error using semicolon as
// separator.
v, err := url.ParseQuery(strings.ReplaceAll(u.Opaque, ";", "&"))
if err != nil {
return nil, errors.Wrapf(err, "error parsing %s", rawuri)
}
return &URI{
URL: u,
Values: v,
}, nil
}
// ParseWithScheme returns the URI for the given string only if it has the given
// scheme.
func ParseWithScheme(scheme, rawuri string) (*URI, error) {
u, err := Parse(rawuri)
if err != nil {
return nil, err
}
if !strings.EqualFold(u.Scheme, scheme) {
return nil, errors.Errorf("error parsing %s: scheme not expected", rawuri)
}
return u, nil
}
// String returns the string representation of the URI.
func (u *URI) String() string {
if len(u.Values) > 0 {
u.URL.Opaque = strings.ReplaceAll(u.Values.Encode(), "&", ";")
}
return u.URL.String()
}
// Has checks whether a given key is set.
func (u *URI) Has(key string) bool {
return u.Values.Has(key) || u.URL.Query().Has(key)
}
// Get returns the first value in the uri with the given key, it will return
// empty string if that field is not present.
func (u *URI) Get(key string) string {
v := u.Values.Get(key)
if v == "" {
v = u.URL.Query().Get(key)
}
return v
}
// GetBool returns true if a given key has the value "true". It returns false
// otherwise.
func (u *URI) GetBool(key string) bool {
v := u.Values.Get(key)
if v == "" {
v = u.URL.Query().Get(key)
}
return strings.EqualFold(v, "true")
}
// GetInt returns the first integer value in the URI with the given key. It
// returns nil if the field is not present or if the value can't be parsed
// as an integer.
func (u *URI) GetInt(key string) *int64 {
v := u.Values.Get(key)
if v == "" {
v = u.URL.Query().Get(key)
}
if v == "" {
return nil
}
if i, err := strconv.ParseInt(v, 10, 0); err == nil {
return &i
}
return nil
}
// GetEncoded returns the first value in the uri with the given key, it will
// return empty nil if that field is not present or is empty. If the return
// value is hex encoded it will decode it and return it.
func (u *URI) GetEncoded(key string) []byte {
v := u.Get(key)
if v == "" {
return nil
}
if len(v)%2 == 0 {
if b, err := hex.DecodeString(strings.TrimPrefix(v, "0x")); err == nil {
return b
}
}
return []byte(v)
}
// GetHexEncoded returns the first value in the uri with the given key. It
// returns nil if the field is not present or is empty. It will return an
// error if the the value is not properly hex encoded.
func (u *URI) GetHexEncoded(key string) ([]byte, error) {
v := u.Get(key)
if v == "" {
return nil, nil
}
b, err := hex.DecodeString(strings.TrimPrefix(v, "0x"))
if err != nil {
return nil, fmt.Errorf("failed decoding %q: %w", v, err)
}
return b, nil
}
// Pin returns the pin encoded in the url. It will read the pin from the
// pin-value or the pin-source attributes.
func (u *URI) Pin() string {
if value := u.Get("pin-value"); value != "" {
return value
}
if path := u.Get("pin-source"); path != "" {
if b, err := readFile(path); err == nil {
return string(bytes.TrimRightFunc(b, unicode.IsSpace))
}
}
return ""
}
// Read returns the raw content of the file in the given attribute key. This
// method will return nil if the key is missing.
func (u *URI) Read(key string) ([]byte, error) {
path := u.Get(key)
if path == "" {
return nil, nil
}
return readFile(path)
}
func readFile(path string) ([]byte, error) {
u, err := url.Parse(path)
if err == nil && (u.Scheme == "" || u.Scheme == "file") {
switch {
case u.Path != "":
path = u.Path
case u.Opaque != "":
path = u.Opaque
}
}
b, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "error reading %s", path)
}
return b, nil
}
|