1package faker
2
3import (
4 "fmt"
5 "reflect"
6 "time"
7
8 "github.com/go-faker/faker/v4/pkg/interfaces"
9)
10
11var (
12 browserTypes = []string{"chrome", "firefox", "internetExplorer", "msedge", "opera", "safari"}
13 windowsPlatformTokens = []string{"Windows NT 6.2", "Windows NT 6.1", "Windows NT 6.0", "Windows NT 5.2",
14 "Windows NT 5.1", "Windows NT 5.01", "Windows NT 5.0", "Windows NT 4.0", "Windows 98; Win 9x 4.90",
15 "Windows 98", "Windows 95", "Windows CE",
16 }
17 linuxProcessor = []string{"i686", "x86_64"}
18 macProcessor = []string{"Intel", "PPC", "U; Intel", "U; PPC"}
19 // langCodes contains language codes extracted from https://gist.github.com/JamieMason/3748498.
20 langCodes = []string{"af-ZA", "ar-AE", "ar-BH", "ar-DZ", "ar-EG", "ar-IQ", "ar-JO", "ar-KW", "ar-LB", "ar-LY",
21 "ar-MA", "ar-OM", "ar-QA", "ar-SA", "ar-SY", "ar-TN", "ar-YE", "be-BY", "bg-BG", "ca-ES", "cs-CZ", "cy-GB",
22 "da-DK", "de-AT", "de-DE", "de-CH", "de-LI", "de-LU", "dv-MV", "el-GR", "en-AU", "en-BZ", "en-CA", "en-GB",
23 "en-IE", "en-JM", "en-NZ", "en-PH", "en-TT", "en-US", "en-ZA", "en-ZW", "es-AR", "es-BO", "es-CL", "es-CO",
24 "es-CR", "es-DO", "es-EC", "es-ES", "es-GT", "es-HN", "es-MX", "es-NI", "es-PA", "es-PE", "es-PR", "es-PY",
25 "es-SV", "es-UY", "es-VE", "et-EE", "eu-ES", "fa-IR", "fi-FI", "fo-FO", "fr-BE", "fr-CA", "fr-FR", "fr-CH",
26 "fr-LU", "fr-MC", "gl-ES", "gu-IN", "he-IL", "hi-IN", "hr-BA", "hr-HR", "hu-HU", "hy-AM", "id-ID", "is-IS",
27 "it-CH", "it-IT", "ja-JP", "ka-GE", "kk-KZ", "kn-IN", "ko-KR", "ky-KG", "lt-LT", "lv-LV", "mi-NZ", "mk-MK",
28 "mn-MN", "mr-IN", "ms-BN", "ms-MY", "mt-MT", "nb-NO", "nl-BE", "nl-NL", "nn-NO", "ns-ZA", "pa-IN", "pl-PL",
29 "pt-BR", "pt-PT", "ro-RO", "ru-RU", "sa-IN", "se-FI", "se-NO", "se-SE", "sk-SK", "sl-SI", "sq-AL", "sv-FI",
30 "sv-SE", "sw-KE", "ta-IN", "te-IN", "th-TH", "tn-ZA", "tr-TR", "tt-RU", "uk-UA", "ur-PK", "vi-VN", "xh-ZA",
31 "zh-CN", "zh-HK", "zh-MO", "zh-SG", "zh-TW", "zu-ZA",
32 }
33)
34
35func GetUserAgent() UserAgenter {
36 ua := &UserAgent{}
37 return ua
38}
39
40type UserAgenter interface {
41 UserAgent(v reflect.Value) (interface{}, error)
42}
43
44// UserAgent implements logic loosely based on https://fakerphp.org/formatters/user-agent/.
45type UserAgent struct{}
46
47func (ua UserAgent) UserAgent(reflect.Value) (interface{}, error) {
48 browserType := randomElementFromSliceString(browserTypes)
49 switch browserType {
50 case "chrome":
51 return ua.chrome(), nil
52 case "firefox":
53 return ua.firefox(), nil
54 case "internetExplorer":
55 return ua.internetExplorer(), nil
56 case "msedge":
57 return ua.msEdge(), nil
58 case "opera":
59 return ua.opera(), nil
60 case "safari":
61 return ua.safari(), nil
62 }
63 return nil, fmt.Errorf(`unknown browser type: "%s"`, browserType)
64}
65
66// chrome generates a user agent string that resembles Google Chrome.
67// Example: "Mozilla/5.0 (X11; Linux i686) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/69.0.3460.334 Safari/532.2"
68func (ua UserAgent) chrome() string {
69 chromeVer := fmt.Sprintf("%d.0.%d.%d",
70 ua.intBetween(36, 127), ua.intBetween(0, 6000), ua.intBetween(0, 400),
71 )
72 platformToken := randomElementFromSliceString([]string{
73 ua.linuxPlatformToken(),
74 ua.macPlatformToken("_"),
75 ua.windowsPlatformToken(),
76 })
77 safariType := randomElementFromSliceString([]string{"Safari", "Mobile Safari"})
78 webKitVer := ua.webKitVersion(false)
79 return fmt.Sprintf("Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) Chrome/%s %s/%s",
80 platformToken, webKitVer, chromeVer, safariType, webKitVer,
81 )
82}
83
84// firefox generates a user agent string that resembles Firefox.
85// Example: "Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20130219 Firefox/58.0"
86func (ua UserAgent) firefox() string {
87 firefoxVer := fmt.Sprintf("%d.0", ua.intBetween(35, 129))
88 platformToken := randomElementFromSliceString([]string{
89 ua.linuxPlatformToken(),
90 ua.macPlatformToken("."),
91 ua.windowsPlatformToken(),
92 })
93 return fmt.Sprintf("Mozilla/5.0 (%s; rv:%s) Gecko/%s Firefox/%s",
94 platformToken, firefoxVer, ua.geckoVersion(), firefoxVer,
95 )
96}
97
98// internetExplorer generates a user agent string that resembles Internet Explorer
99// Example: "Mozilla/5.0 (compatible; MSIE 9.0; Windows 98; Win 9x 4.90; Trident/5.0)"
100func (ua UserAgent) internetExplorer() string {
101 return fmt.Sprintf("Mozilla/5.0 (compatible; MSIE %d.0; %s; Trident/%d.0)",
102 ua.intBetween(5, 11), ua.windowsPlatformToken(), ua.intBetween(3, 7),
103 )
104}
105
106// msEdge generates a user agent string that resembles MS Edge
107// Example: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/533.0 (KHTML, like Gecko) Chrome/86.0.4685.32 Safari/533.0 Edg/86.0.4685.32"
108func (ua UserAgent) msEdge() string {
109 webKitVer := ua.webKitVersion(false)
110 chromiumVer := fmt.Sprintf("%d.0.%d.%d", ua.intBetween(79, 99), ua.intBetween(4000, 4844), ua.intBetween(10, 99))
111 platformToken := randomElementFromSliceString([]string{
112 ua.linuxPlatformToken(),
113 ua.windowsPlatformToken(),
114 ua.macPlatformToken("_"),
115 })
116 return fmt.Sprintf("Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) Chrome/%s Safari/%s Edg/%s",
117 platformToken, webKitVer, chromiumVer, webKitVer, chromiumVer,
118 )
119}
120
121// opera generates a user agent string that resembles Opera.
122// Example: "Opera/8.40 (X11; Linux i686; zh-TW) Presto/2.12.181 Version/10.57"
123func (ua UserAgent) opera() string {
124 platformToken := randomElementFromSliceString([]string{
125 ua.linuxPlatformToken(),
126 ua.windowsPlatformToken(),
127 })
128 operaVer := fmt.Sprintf("%d.%d", ua.intBetween(8, 9), ua.intBetween(10, 99))
129 prestoVer := fmt.Sprintf("2.%d.%d", ua.intBetween(8, 12), ua.intBetween(160, 355))
130 ver := fmt.Sprintf("%d.%d", ua.intBetween(10, 12), ua.intBetween(0, 70))
131 return fmt.Sprintf("Opera/%s (%s; %s) Presto/%s Version/%s",
132 operaVer, platformToken, randomElementFromSliceString(langCodes), prestoVer, ver,
133 )
134}
135
136// safari generates a user agent string that resembles Safari.
137// Example: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_3; bg-BG) AppleWebKit/548.43.16 (KHTML, like Gecko) Version/7.5 Safari/548.43.16"
138func (ua UserAgent) safari() string {
139 webKitVer := ua.webKitVersion(true)
140 safariVer := fmt.Sprintf("%d.%d", ua.intBetween(4, 17), ua.intBetween(0, 5))
141 if ua.intBetween(0, 100) < 50 {
142 safariVer = fmt.Sprintf("%s.%d", safariVer, ua.intBetween(1, 20))
143 }
144 platform := randomElementFromSliceString([]string{
145 fmt.Sprintf("Windows; U; %s; %s", ua.windowsPlatformToken(), randomElementFromSliceString(langCodes)),
146 fmt.Sprintf("%s; %s", ua.macPlatformToken("_"), randomElementFromSliceString(langCodes)),
147 })
148
149 return fmt.Sprintf("Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) Version/%s Safari/%s",
150 platform, webKitVer, safariVer, webKitVer,
151 )
152}
153
154func (ua UserAgent) linuxPlatformToken() string {
155 return fmt.Sprintf("X11; Linux %s", randomElementFromSliceString(linuxProcessor))
156}
157
158func (ua UserAgent) macPlatformToken(sep string) string {
159 return fmt.Sprintf("Macintosh; %s Mac OS X %d%s%d%s%d",
160 randomElementFromSliceString(macProcessor), ua.intBetween(10, 17), sep, ua.intBetween(5, 8), sep, ua.intBetween(0, 9),
161 )
162}
163
164func (ua UserAgent) windowsPlatformToken() string {
165 return randomElementFromSliceString(windowsPlatformTokens)
166}
167
168// webKitVersion generates a random string that resembles an AppleWebKit version string.
169func (ua UserAgent) webKitVersion(withPatchVer bool) string {
170 majorVer := ua.intBetween(531, 618)
171 if withPatchVer {
172 return fmt.Sprintf("%d.%d.%d", majorVer, ua.intBetween(1, 50), ua.intBetween(1, 20))
173 }
174 return fmt.Sprintf("%d.%d", majorVer, ua.intBetween(0, 10))
175}
176
177// geckoVersion generates a random string that resembles a Gecko version string.
178func (ua UserAgent) geckoVersion() string {
179 const YYYYMMDD = "20060102"
180 minDate := time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC)
181 maxDate := time.Now()
182 randDuration := time.Duration(rand.Int63n(maxDate.Unix()-minDate.Unix())) * time.Second
183 return minDate.Add(randDuration).Format(YYYYMMDD)
184}
185
186// intBetween generates a random integer in the range [min, max].
187//
188//nolint:revive
189func (ua UserAgent) intBetween(min, max int) int {
190 boundary := interfaces.RandomIntegerBoundary{Start: min, End: max + 1}
191 return randomIntegerWithBoundary(boundary)
192}