File: birthdate.go

package info (click to toggle)
golang-github-lestrrat-go-jwx 2.1.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,872 kB
  • sloc: sh: 222; makefile: 86; perl: 62
file content (155 lines) | stat: -rw-r--r-- 3,651 bytes parent folder | download
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
package openid

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"regexp"
	"strconv"

	"github.com/lestrrat-go/jwx/v2/internal/json"
)

// https://openid.net/specs/openid-connect-core-1_0.html
//
// End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format.
// The year MAY be 0000, indicating that it is omitted. To represent only the year, YYYY
// format is allowed. Note that depending on the underlying platform's date related function,
// providing just year can result in varying month and day, so the implementers need to
// take this factor into account to correctly process the dates.

type BirthdateClaim struct {
	year  *int
	month *int
	day   *int
}

func (b BirthdateClaim) Year() int {
	if b.year == nil {
		return 0
	}
	return *(b.year)
}

func (b BirthdateClaim) Month() int {
	if b.month == nil {
		return 0
	}
	return *(b.month)
}

func (b BirthdateClaim) Day() int {
	if b.day == nil {
		return 0
	}
	return *(b.day)
}

func (b *BirthdateClaim) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return fmt.Errorf(`failed to unmarshal JSON string for birthdate claim: %w`, err)
	}

	if err := b.Accept(s); err != nil {
		return fmt.Errorf(`failed to accept JSON value for birthdate claim: %w`, err)
	}
	return nil
}

var intSize int

func init() {
	intSize = 64
	if math.MaxInt == math.MaxInt32 {
		intSize = 32
	}
}

func parseBirthdayInt(s string) int {
	i, err := strconv.ParseInt(s, 10, intSize)
	if err != nil {
		return 0
	}
	return int(i)
}

var birthdateRx = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`)

// Accepts a value read from JSON, and converts it to a BirthdateClaim.
// This method DOES NOT verify the correctness of a date.
// Consumers should check for validity of dates such as Apr 31 et al
func (b *BirthdateClaim) Accept(v interface{}) error {
	b.year = nil
	b.month = nil
	b.day = nil
	switch v := v.(type) {
	case *BirthdateClaim:
		if ptr := v.year; ptr != nil {
			year := *ptr
			b.year = &year
		}
		if ptr := v.month; ptr != nil {
			month := *ptr
			b.month = &month
		}
		if ptr := v.day; ptr != nil {
			day := *ptr
			b.day = &day
		}
		return nil
	case string:
		// yeah, regexp is slow. PR's welcome
		indices := birthdateRx.FindStringSubmatchIndex(v)
		if indices == nil {
			return fmt.Errorf(`invalid pattern for birthdate`)
		}
		var tmp BirthdateClaim

		// Okay, this really isn't kosher, but we're doing this for
		// the coverage game... Because birthdateRx already checked that
		// the string contains 3 strings with consecutive decimal values
		// we can assume that strconv.ParseInt always succeeds.
		// strconv.ParseInt (and strconv.ParseUint that it uses internally)
		// only returns range errors, so we should be safe.
		year := parseBirthdayInt(v[indices[2]:indices[3]])
		if year <= 0 {
			return fmt.Errorf(`failed to parse birthdate year`)
		}
		tmp.year = &year

		month := parseBirthdayInt(v[indices[4]:indices[5]])
		if month <= 0 {
			return fmt.Errorf(`failed to parse birthdate month`)
		}
		tmp.month = &month

		day := parseBirthdayInt(v[indices[6]:indices[7]])
		if day <= 0 {
			return fmt.Errorf(`failed to parse birthdate day`)
		}
		tmp.day = &day

		*b = tmp
		return nil
	default:
		return fmt.Errorf(`invalid type for birthdate: %T`, v)
	}
}

func (b BirthdateClaim) encode(dst io.Writer) {
	fmt.Fprintf(dst, "%04d-%02d-%02d", b.Year(), b.Month(), b.Day())
}

func (b BirthdateClaim) String() string {
	var buf bytes.Buffer
	b.encode(&buf)
	return buf.String()
}

func (b BirthdateClaim) MarshalText() ([]byte, error) {
	var buf bytes.Buffer
	b.encode(&buf)
	return buf.Bytes(), nil
}