1// Package bytesize provides human-friendly byte size parsing.
2package bytesize
3
4import (
5 "fmt"
6 "math"
7 "strconv"
8 "strings"
9)
10
11// unitMultipliers maps human-friendly unit suffixes to their byte values.
12var unitMultipliers = map[string]int64{
13 "B": 1,
14 "KB": 1 << 10, // 1024
15 "MB": 1 << 20, // 1048576
16 "GB": 1 << 30, // 1073741824
17 "TB": 1 << 40, // 1099511627776
18}
19
20// Parse parses a human-friendly byte size string.
21//
22// Supported units: B, KB, MB, GB, TB (case-insensitive).
23// The decimal prefix (1024-based) is used (KiB, MiB, etc.).
24//
25// Returns int64 to safely integrate with standard library functions like http.MaxBytesReader.
26// Maximum representable value is 9,223,372,036,854,775,807 bytes (~8 exabytes).
27//
28// Examples:
29//
30// Parse("512MB") // 536870912 bytes
31// Parse("1GB") // 1073741824 bytes
32// Parse("100KB") // 102400 bytes
33// Parse("1.5GB") // 1610612736 bytes
34func Parse(s string) (int64, error) {
35 s = strings.TrimSpace(s)
36
37 if s == "" {
38 return 0, fmt.Errorf("empty size string")
39 }
40
41 s = strings.ToUpper(s)
42
43 // Find the unit suffix - try longest first to avoid matching "B" before "KB"
44 units := []string{"TB", "GB", "MB", "KB", "B"}
45 var unit string
46 var valueStr string
47
48 for _, u := range units {
49 if strings.HasSuffix(s, u) {
50 unit = u
51 valueStr = strings.TrimSuffix(s, u)
52 break
53 }
54 }
55
56 if unit == "" {
57 return 0, fmt.Errorf("invalid size %q: missing unit (supported: B, KB, MB, GB, TB)", s)
58 }
59
60 valueStr = strings.TrimSpace(valueStr)
61 if valueStr == "" {
62 return 0, fmt.Errorf("invalid size %q: missing numeric value", s)
63 }
64
65 value, err := strconv.ParseFloat(valueStr, 64)
66 if err != nil {
67 return 0, fmt.Errorf("invalid size value %q in %q: %w", valueStr, s, err)
68 }
69
70 if value < 0 {
71 return 0, fmt.Errorf("invalid size %q: negative value not allowed", s)
72 }
73
74 multiplier := unitMultipliers[unit]
75 result := value * float64(multiplier)
76
77 // Check for overflow before converting to int64
78 if result > math.MaxInt64 {
79 return 0, fmt.Errorf("size %q exceeds maximum allowed value (9.2 EB)", s)
80 }
81
82 return int64(result), nil
83}