github.com/lestrrat-go/jwx/v2@v2.0.21/jwt/openid/birthdate.go (about) 1 package openid 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "math" 8 "regexp" 9 "strconv" 10 11 "github.com/lestrrat-go/jwx/v2/internal/json" 12 ) 13 14 // https://openid.net/specs/openid-connect-core-1_0.html 15 // 16 // End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format. 17 // The year MAY be 0000, indicating that it is omitted. To represent only the year, YYYY 18 // format is allowed. Note that depending on the underlying platform's date related function, 19 // providing just year can result in varying month and day, so the implementers need to 20 // take this factor into account to correctly process the dates. 21 22 type BirthdateClaim struct { 23 year *int 24 month *int 25 day *int 26 } 27 28 func (b BirthdateClaim) Year() int { 29 if b.year == nil { 30 return 0 31 } 32 return *(b.year) 33 } 34 35 func (b BirthdateClaim) Month() int { 36 if b.month == nil { 37 return 0 38 } 39 return *(b.month) 40 } 41 42 func (b BirthdateClaim) Day() int { 43 if b.day == nil { 44 return 0 45 } 46 return *(b.day) 47 } 48 49 func (b *BirthdateClaim) UnmarshalJSON(data []byte) error { 50 var s string 51 if err := json.Unmarshal(data, &s); err != nil { 52 return fmt.Errorf(`failed to unmarshal JSON string for birthdate claim: %w`, err) 53 } 54 55 if err := b.Accept(s); err != nil { 56 return fmt.Errorf(`failed to accept JSON value for birthdate claim: %w`, err) 57 } 58 return nil 59 } 60 61 var intSize int 62 63 func init() { 64 switch math.MaxInt { 65 case math.MaxInt16: 66 intSize = 16 67 case math.MaxInt32: 68 intSize = 32 69 case math.MaxInt64: 70 intSize = 64 71 } 72 } 73 74 func parseBirthdayInt(s string) int { 75 i, _ := strconv.ParseInt(s, 10, intSize) 76 return int(i) 77 } 78 79 var birthdateRx = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`) 80 81 // Accepts a value read from JSON, and converts it to a BirthdateClaim. 82 // This method DOES NOT verify the correctness of a date. 83 // Consumers should check for validity of dates such as Apr 31 et al 84 func (b *BirthdateClaim) Accept(v interface{}) error { 85 b.year = nil 86 b.month = nil 87 b.day = nil 88 switch v := v.(type) { 89 case *BirthdateClaim: 90 if ptr := v.year; ptr != nil { 91 year := *ptr 92 b.year = &year 93 } 94 if ptr := v.month; ptr != nil { 95 month := *ptr 96 b.month = &month 97 } 98 if ptr := v.day; ptr != nil { 99 day := *ptr 100 b.day = &day 101 } 102 return nil 103 case string: 104 // yeah, regexp is slow. PR's welcome 105 indices := birthdateRx.FindStringSubmatchIndex(v) 106 if indices == nil { 107 return fmt.Errorf(`invalid pattern for birthdate`) 108 } 109 var tmp BirthdateClaim 110 111 // Okay, this really isn't kosher, but we're doing this for 112 // the coverage game... Because birthdateRx already checked that 113 // the string contains 3 strings with consecutive decimal values 114 // we can assume that strconv.ParseInt always succeeds. 115 // strconv.ParseInt (and strconv.ParseUint that it uses internally) 116 // only returns range errors, so we should be safe. 117 year := parseBirthdayInt(v[indices[2]:indices[3]]) 118 if year <= 0 { 119 return fmt.Errorf(`failed to parse birthdate year`) 120 } 121 tmp.year = &year 122 123 month := parseBirthdayInt(v[indices[4]:indices[5]]) 124 if month <= 0 { 125 return fmt.Errorf(`failed to parse birthdate month`) 126 } 127 tmp.month = &month 128 129 day := parseBirthdayInt(v[indices[6]:indices[7]]) 130 if day <= 0 { 131 return fmt.Errorf(`failed to parse birthdate day`) 132 } 133 tmp.day = &day 134 135 *b = tmp 136 return nil 137 default: 138 return fmt.Errorf(`invalid type for birthdate: %T`, v) 139 } 140 } 141 142 func (b BirthdateClaim) encode(dst io.Writer) { 143 fmt.Fprintf(dst, "%04d-%02d-%02d", b.Year(), b.Month(), b.Day()) 144 } 145 146 func (b BirthdateClaim) String() string { 147 var buf bytes.Buffer 148 b.encode(&buf) 149 return buf.String() 150 } 151 152 func (b BirthdateClaim) MarshalText() ([]byte, error) { 153 var buf bytes.Buffer 154 b.encode(&buf) 155 return buf.Bytes(), nil 156 }