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  }