File: uri.go

package info (click to toggle)
golang-github-smallstep-crypto 0.63.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 3,800 kB
  • sloc: sh: 66; makefile: 50
file content (222 lines) | stat: -rw-r--r-- 5,286 bytes parent folder | download | duplicates (3)
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
}