aboutsummaryrefslogtreecommitdiff
path: root/objects/properties.go
blob: 2e8c8acc89e3ec2c5d7d06ce37f8238ce25b8640 (plain)
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
package objects

import (
	"encoding/hex"
	"fmt"
	"net/url"
	"sort"
)

// Properties are mappings from strings to strings that are encoded as a restricted version of URL query strings
// (only the characters [a-zA-Z0-9.:_-] are allowed, values are ordered by their key.
// This ordering and this character restrictions guarantee reproducible serialization)
type Properties map[string]string

// escapePropertyString escapes all bytes not in [a-zA-Z0-9.,:_-] as %XX, where XX represents the hexadecimal value of the byte.
// Compatible with URL query strings
func escapePropertyString(s string) []byte {
	out := []byte{}
	esc := []byte("%XX")

	for _, b := range []byte(s) {
		if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '.' || b == ',' || b == ':' || b == '_' || b == '-' {
			out = append(out, b)
		} else {
			hex.Encode(esc[1:], []byte{b})
			out = append(out, esc...)
		}
	}

	return out
}

func (p Properties) MarshalText() ([]byte, error) { // Guaranteed to not fail, error is only here to satisfy encoding.TextMarshaler
	keys := make([]string, len(p))
	i := 0
	for k := range p {
		keys[i] = k
		i++
	}

	sort.Strings(keys)

	out := []byte{}

	first := true
	for _, k := range keys {
		if first {
			first = false
		} else {
			out = append(out, '&')
		}

		out = append(out, escapePropertyString(k)...)
		out = append(out, '=')
		out = append(out, escapePropertyString(p[k])...)
	}

	return out, nil
}

func (p Properties) UnmarshalText(text []byte) error {
	vals, err := url.ParseQuery(string(text))
	if err != nil {
		return err
	}

	for k, v := range vals {
		if len(v) != 1 {
			return fmt.Errorf("Got %d values for key %s, expected 1", len(v), k)
		}

		p[k] = v[0]
	}

	return nil
}

func (a Properties) Equals(b Properties) bool {
	for k, va := range a {
		vb, ok := b[k]
		if !ok || vb != va {
			return false
		}
	}

	for k := range b {
		_, ok := a[k]
		if !ok {
			return false
		}
	}

	return true
}