github.com/lestrrat-go/jwx/v2@v2.0.21/jwt/internal/types/date.go (about) 1 package types 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 9 "github.com/lestrrat-go/jwx/v2/internal/json" 10 ) 11 12 const ( 13 DefaultPrecision uint32 = 0 // second level 14 MaxPrecision uint32 = 9 // nanosecond level 15 ) 16 17 var Pedantic uint32 18 var ParsePrecision = DefaultPrecision 19 var FormatPrecision = DefaultPrecision 20 21 // NumericDate represents the date format used in the 'nbf' claim 22 type NumericDate struct { 23 time.Time 24 } 25 26 func (n *NumericDate) Get() time.Time { 27 if n == nil { 28 return (time.Time{}).UTC() 29 } 30 return n.Time 31 } 32 33 func intToTime(v interface{}, t *time.Time) bool { 34 var n int64 35 switch x := v.(type) { 36 case int64: 37 n = x 38 case int32: 39 n = int64(x) 40 case int16: 41 n = int64(x) 42 case int8: 43 n = int64(x) 44 case int: 45 n = int64(x) 46 default: 47 return false 48 } 49 50 *t = time.Unix(n, 0) 51 return true 52 } 53 54 func parseNumericString(x string) (time.Time, error) { 55 var t time.Time // empty time for empty return value 56 57 // Only check for the escape hatch if it's the pedantic 58 // flag is off 59 if Pedantic != 1 { 60 // This is an escape hatch for non-conformant providers 61 // that gives us RFC3339 instead of epoch time 62 for _, r := range x { 63 // 0x30 = '0', 0x39 = '9', 0x2E = '.' 64 if (r >= 0x30 && r <= 0x39) || r == 0x2E { 65 continue 66 } 67 68 // if it got here, then it probably isn't epoch time 69 tv, err := time.Parse(time.RFC3339, x) 70 if err != nil { 71 return t, fmt.Errorf(`value is not number of seconds since the epoch, and attempt to parse it as RFC3339 timestamp failed: %w`, err) 72 } 73 return tv, nil 74 } 75 } 76 77 var fractional string 78 whole := x 79 if i := strings.IndexRune(x, '.'); i > 0 { 80 if ParsePrecision > 0 && len(x) > i+1 { 81 fractional = x[i+1:] // everything after the '.' 82 if int(ParsePrecision) < len(fractional) { 83 // Remove insignificant digits 84 fractional = fractional[:int(ParsePrecision)] 85 } 86 // Replace missing fractional diits with zeros 87 for len(fractional) < int(MaxPrecision) { 88 fractional = fractional + "0" 89 } 90 } 91 whole = x[:i] 92 } 93 n, err := strconv.ParseInt(whole, 10, 64) 94 if err != nil { 95 return t, fmt.Errorf(`failed to parse whole value %q: %w`, whole, err) 96 } 97 var nsecs int64 98 if fractional != "" { 99 v, err := strconv.ParseInt(fractional, 10, 64) 100 if err != nil { 101 return t, fmt.Errorf(`failed to parse fractional value %q: %w`, fractional, err) 102 } 103 nsecs = v 104 } 105 106 return time.Unix(n, nsecs).UTC(), nil 107 } 108 109 func (n *NumericDate) Accept(v interface{}) error { 110 var t time.Time 111 switch x := v.(type) { 112 case float32: 113 tv, err := parseNumericString(fmt.Sprintf(`%.9f`, x)) 114 if err != nil { 115 return fmt.Errorf(`failed to accept float32 %.9f: %w`, x, err) 116 } 117 t = tv 118 case float64: 119 tv, err := parseNumericString(fmt.Sprintf(`%.9f`, x)) 120 if err != nil { 121 return fmt.Errorf(`failed to accept float32 %.9f: %w`, x, err) 122 } 123 t = tv 124 case json.Number: 125 tv, err := parseNumericString(x.String()) 126 if err != nil { 127 return fmt.Errorf(`failed to accept json.Number %q: %w`, x.String(), err) 128 } 129 t = tv 130 case string: 131 tv, err := parseNumericString(x) 132 if err != nil { 133 return fmt.Errorf(`failed to accept string %q: %w`, x, err) 134 } 135 t = tv 136 case time.Time: 137 t = x 138 default: 139 if !intToTime(v, &t) { 140 return fmt.Errorf(`invalid type %T`, v) 141 } 142 } 143 n.Time = t.UTC() 144 return nil 145 } 146 147 func (n NumericDate) String() string { 148 if FormatPrecision == 0 { 149 return strconv.FormatInt(n.Unix(), 10) 150 } 151 152 // This is cheating,but it's better (easier) than doing floating point math 153 // We basically munge with strings after formatting an integer balue 154 // for nanoseconds since epoch 155 s := strconv.FormatInt(n.UnixNano(), 10) 156 for len(s) < int(MaxPrecision) { 157 s = "0" + s 158 } 159 160 slwhole := len(s) - int(MaxPrecision) 161 s = s[:slwhole] + "." + s[slwhole:slwhole+int(FormatPrecision)] 162 if s[0] == '.' { 163 s = "0" + s 164 } 165 166 return s 167 } 168 169 // MarshalJSON translates from internal representation to JSON NumericDate 170 // See https://tools.ietf.org/html/rfc7519#page-6 171 func (n *NumericDate) MarshalJSON() ([]byte, error) { 172 if n.IsZero() { 173 return json.Marshal(nil) 174 } 175 176 return json.Marshal(n.String()) 177 } 178 179 func (n *NumericDate) UnmarshalJSON(data []byte) error { 180 var v interface{} 181 if err := json.Unmarshal(data, &v); err != nil { 182 return fmt.Errorf(`failed to unmarshal date: %w`, err) 183 } 184 185 var n2 NumericDate 186 if err := n2.Accept(v); err != nil { 187 return fmt.Errorf(`invalid value for NumericDate: %w`, err) 188 } 189 *n = n2 190 return nil 191 }