common.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
  2. // 🤖 Github Repository: https://github.com/gofiber/fiber
  3. // 📌 API Documentation: https://docs.gofiber.io
  4. package utils
  5. import (
  6. "crypto/rand"
  7. "encoding/base64"
  8. "fmt"
  9. "math"
  10. "net"
  11. "os"
  12. "reflect"
  13. "runtime"
  14. "slices"
  15. "strings"
  16. "github.com/google/uuid"
  17. )
  18. const (
  19. defaultSecureTokenLength = 32
  20. maxFastTokenEncodedLength = 43 // base64.RawURLEncoding.EncodedLen(32)
  21. )
  22. func readRandomOrPanic(dst []byte) {
  23. if _, err := rand.Read(dst); err != nil {
  24. // On supported Go versions (1.24+), crypto/rand.Read panics internally and
  25. // does not return errors. This check preserves explicit panic semantics if
  26. // the behavior changes or an alternate implementation is used in the future.
  27. // See: https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/crypto/rand/rand.go
  28. panic(fmt.Errorf("utils: failed to read random bytes for token: %w", err))
  29. }
  30. }
  31. // Lookup table for ASCII whitespace characters (true = whitespace, false = not whitespace)
  32. var whitespaceTable = [256]bool{
  33. '\t': true, // 9 - horizontal tab
  34. '\n': true, // 10 - line feed
  35. '\v': true, // 11 - vertical tab
  36. '\f': true, // 12 - form feed
  37. '\r': true, // 13 - carriage return
  38. ' ': true, // 32 - space
  39. }
  40. // UUIDv4 returns a Random (Version 4) UUID.
  41. // The strength of the UUIDs is based on the strength of the crypto/rand package.
  42. func UUIDv4() string {
  43. token, err := uuid.NewRandom()
  44. if err != nil {
  45. panic(fmt.Errorf("utils: failed to generate secure UUID: %w", err))
  46. }
  47. return token.String()
  48. }
  49. // UUID generates an universally unique identifier (UUID).
  50. // This is an alias for UUIDv4 for backward compatibility.
  51. func UUID() string {
  52. return UUIDv4()
  53. }
  54. // GenerateSecureToken generates a cryptographically secure random token encoded in base64.
  55. // It uses crypto/rand for randomness and base64.RawURLEncoding for URL-safe output.
  56. // If length is less than or equal to 0, it defaults to 32 bytes (256 bits of entropy).
  57. // Panics if the random source fails.
  58. func GenerateSecureToken(length int) string {
  59. if length <= 0 {
  60. length = defaultSecureTokenLength
  61. }
  62. if length == defaultSecureTokenLength {
  63. var randomBuf [defaultSecureTokenLength]byte
  64. src := randomBuf[:]
  65. readRandomOrPanic(src)
  66. var encoded [maxFastTokenEncodedLength]byte
  67. encodedLen := base64.RawURLEncoding.EncodedLen(length)
  68. base64.RawURLEncoding.Encode(encoded[:encodedLen], src)
  69. return string(encoded[:encodedLen])
  70. }
  71. bytes := make([]byte, length)
  72. readRandomOrPanic(bytes)
  73. return base64.RawURLEncoding.EncodeToString(bytes)
  74. }
  75. // SecureToken generates a secure token with 32 bytes of entropy.
  76. // Panics if the random source fails. See GenerateSecureToken for details.
  77. func SecureToken() string {
  78. return GenerateSecureToken(defaultSecureTokenLength)
  79. }
  80. // FunctionName returns function name
  81. func FunctionName(fn any) string {
  82. if fn == nil {
  83. return ""
  84. }
  85. v := reflect.ValueOf(fn)
  86. if v.Kind() == reflect.Func {
  87. if v.IsNil() {
  88. return ""
  89. }
  90. pc := v.Pointer()
  91. f := runtime.FuncForPC(pc)
  92. if f == nil {
  93. return ""
  94. }
  95. return f.Name()
  96. }
  97. return v.Type().String()
  98. }
  99. // GetArgument check if key is in arguments
  100. func GetArgument(arg string) bool {
  101. return slices.Contains(os.Args[1:], arg)
  102. }
  103. // IncrementIPRange Find available next IP address
  104. func IncrementIPRange(ip net.IP) {
  105. for j := len(ip) - 1; j >= 0; j-- {
  106. ip[j]++
  107. if ip[j] > 0 {
  108. break
  109. }
  110. }
  111. }
  112. // ConvertToBytes returns integer size of bytes from human-readable string, ex. 42kb, 42M
  113. // Returns 0 if string is unrecognized
  114. func ConvertToBytes(humanReadableString string) int {
  115. strLen := len(humanReadableString)
  116. if strLen == 0 {
  117. return 0
  118. }
  119. // Fast path for plain byte values (e.g. "42", "42B", "42b").
  120. var sizeFast uint64
  121. maxInt := uint64(math.MaxInt)
  122. i := 0
  123. for ; i < strLen; i++ {
  124. c := humanReadableString[i]
  125. if c < '0' || c > '9' {
  126. break
  127. }
  128. d := uint64(c - '0')
  129. if sizeFast > maxInt/10 || (sizeFast == maxInt/10 && d > maxInt%10) {
  130. sizeFast = maxInt
  131. } else if sizeFast < maxInt {
  132. sizeFast = sizeFast*10 + d
  133. }
  134. }
  135. if i > 0 {
  136. if i == strLen {
  137. return int(sizeFast)
  138. }
  139. if i+1 == strLen {
  140. last := humanReadableString[i]
  141. if last == 'b' || last == 'B' {
  142. return int(sizeFast)
  143. }
  144. }
  145. }
  146. // Find the last digit position by scanning backwards
  147. // Also identify the unit prefix position in the same pass
  148. lastNumberPos := -1
  149. unitPrefixPos := 0
  150. for i := strLen - 1; i >= 0; i-- {
  151. c := humanReadableString[i]
  152. if c >= '0' && c <= '9' {
  153. lastNumberPos = i
  154. break
  155. }
  156. // Track the first letter position (unit prefix) from the end
  157. if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
  158. unitPrefixPos = i
  159. }
  160. }
  161. // No digits found
  162. if lastNumberPos < 0 {
  163. return 0
  164. }
  165. numPart := humanReadableString[:lastNumberPos+1]
  166. var size float64
  167. if strings.IndexByte(numPart, '.') >= 0 {
  168. var err error
  169. size, err = ParseFloat64(numPart)
  170. if err != nil {
  171. return 0
  172. }
  173. } else {
  174. i64, err := ParseUint(numPart)
  175. if err != nil {
  176. return 0
  177. }
  178. size = float64(i64)
  179. }
  180. if unitPrefixPos > 0 {
  181. switch humanReadableString[unitPrefixPos] {
  182. case 'k', 'K':
  183. size *= 1e3
  184. case 'm', 'M':
  185. size *= 1e6
  186. case 'g', 'G':
  187. size *= 1e9
  188. case 't', 'T':
  189. size *= 1e12
  190. case 'p', 'P':
  191. size *= 1e15
  192. }
  193. }
  194. if size > float64(math.MaxInt) {
  195. return math.MaxInt
  196. }
  197. return int(size)
  198. }