stringid.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. // Package stringid provides helper functions for dealing with string identifiers
  2. package stringid
  3. import (
  4. cryptorand "crypto/rand"
  5. "encoding/hex"
  6. "fmt"
  7. "io"
  8. "math"
  9. "math/big"
  10. "math/rand"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "time"
  15. )
  16. const shortLen = 12
  17. var (
  18. validShortID = regexp.MustCompile("^[a-f0-9]{12}$")
  19. validHex = regexp.MustCompile(`^[a-f0-9]{64}$`)
  20. )
  21. // IsShortID determines if an arbitrary string *looks like* a short ID.
  22. func IsShortID(id string) bool {
  23. return validShortID.MatchString(id)
  24. }
  25. // TruncateID returns a shorthand version of a string identifier for convenience.
  26. // A collision with other shorthands is very unlikely, but possible.
  27. // In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
  28. // will need to use a longer prefix, or the full-length Id.
  29. func TruncateID(id string) string {
  30. if i := strings.IndexRune(id, ':'); i >= 0 {
  31. id = id[i+1:]
  32. }
  33. if len(id) > shortLen {
  34. id = id[:shortLen]
  35. }
  36. return id
  37. }
  38. func generateID(r io.Reader) string {
  39. b := make([]byte, 32)
  40. for {
  41. if _, err := io.ReadFull(r, b); err != nil {
  42. panic(err) // This shouldn't happen
  43. }
  44. id := hex.EncodeToString(b)
  45. // if we try to parse the truncated for as an int and we don't have
  46. // an error then the value is all numeric and causes issues when
  47. // used as a hostname. ref #3869
  48. if _, err := strconv.ParseInt(TruncateID(id), 10, 64); err == nil {
  49. continue
  50. }
  51. return id
  52. }
  53. }
  54. // GenerateRandomID returns a unique id.
  55. func GenerateRandomID() string {
  56. return generateID(cryptorand.Reader)
  57. }
  58. // GenerateNonCryptoID generates unique id without using cryptographically
  59. // secure sources of random.
  60. // It helps you to save entropy.
  61. func GenerateNonCryptoID() string {
  62. return generateID(readerFunc(rand.Read))
  63. }
  64. // ValidateID checks whether an ID string is a valid image ID.
  65. func ValidateID(id string) error {
  66. if ok := validHex.MatchString(id); !ok {
  67. return fmt.Errorf("image ID %q is invalid", id)
  68. }
  69. return nil
  70. }
  71. func init() {
  72. // safely set the seed globally so we generate random ids. Tries to use a
  73. // crypto seed before falling back to time.
  74. var seed int64
  75. if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil {
  76. // This should not happen, but worst-case fallback to time-based seed.
  77. seed = time.Now().UnixNano()
  78. } else {
  79. seed = cryptoseed.Int64()
  80. }
  81. rand.Seed(seed)
  82. }
  83. type readerFunc func(p []byte) (int, error)
  84. func (fn readerFunc) Read(p []byte) (int, error) {
  85. return fn(p)
  86. }