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
}
|