parser.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. package uaparser
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "regexp"
  6. "sync"
  7. "sync/atomic"
  8. "sort"
  9. "time"
  10. "gopkg.in/yaml.v2"
  11. )
  12. type RegexesDefinitions struct {
  13. UA []*uaParser `yaml:"user_agent_parsers"`
  14. OS []*osParser `yaml:"os_parsers"`
  15. Device []*deviceParser `yaml:"device_parsers"`
  16. sync.RWMutex
  17. }
  18. type UserAgentSorter []*uaParser
  19. func (a UserAgentSorter) Len() int { return len(a) }
  20. func (a UserAgentSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  21. func (a UserAgentSorter) Less(i, j int) bool { return atomic.LoadUint64(&a[i].MatchesCount) > atomic.LoadUint64(&a[j].MatchesCount) }
  22. type uaParser struct {
  23. Reg *regexp.Regexp
  24. Expr string `yaml:"regex"`
  25. Flags string `yaml:"regex_flag"`
  26. FamilyReplacement string `yaml:"family_replacement"`
  27. V1Replacement string `yaml:"v1_replacement"`
  28. V2Replacement string `yaml:"v2_replacement"`
  29. V3Replacement string `yaml:"v3_replacement"`
  30. MatchesCount uint64
  31. }
  32. func (ua *uaParser) setDefaults() {
  33. if ua.FamilyReplacement == "" {
  34. ua.FamilyReplacement = "$1"
  35. }
  36. if ua.V1Replacement == "" {
  37. ua.V1Replacement = "$2"
  38. }
  39. if ua.V2Replacement == "" {
  40. ua.V2Replacement = "$3"
  41. }
  42. if ua.V3Replacement == "" {
  43. ua.V3Replacement = "$4"
  44. }
  45. }
  46. type OsSorter []*osParser
  47. func (a OsSorter) Len() int { return len(a) }
  48. func (a OsSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  49. func (a OsSorter) Less(i, j int) bool { return atomic.LoadUint64(&a[i].MatchesCount) > atomic.LoadUint64(&a[j].MatchesCount) }
  50. type osParser struct {
  51. Reg *regexp.Regexp
  52. Expr string `yaml:"regex"`
  53. Flags string `yaml:"regex_flag"`
  54. OSReplacement string `yaml:"os_replacement"`
  55. V1Replacement string `yaml:"os_v1_replacement"`
  56. V2Replacement string `yaml:"os_v2_replacement"`
  57. V3Replacement string `yaml:"os_v3_replacement"`
  58. V4Replacement string `yaml:"os_v4_replacement"`
  59. MatchesCount uint64
  60. }
  61. func (os *osParser) setDefaults() {
  62. if os.OSReplacement == "" {
  63. os.OSReplacement = "$1"
  64. }
  65. if os.V1Replacement == "" {
  66. os.V1Replacement = "$2"
  67. }
  68. if os.V2Replacement == "" {
  69. os.V2Replacement = "$3"
  70. }
  71. if os.V3Replacement == "" {
  72. os.V3Replacement = "$4"
  73. }
  74. if os.V4Replacement == "" {
  75. os.V4Replacement = "$5"
  76. }
  77. }
  78. type DeviceSorter []*deviceParser
  79. func (a DeviceSorter) Len() int { return len(a) }
  80. func (a DeviceSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  81. func (a DeviceSorter) Less(i, j int) bool { return atomic.LoadUint64(&a[i].MatchesCount) > atomic.LoadUint64(&a[j].MatchesCount) }
  82. type deviceParser struct {
  83. Reg *regexp.Regexp
  84. Expr string `yaml:"regex"`
  85. Flags string `yaml:"regex_flag"`
  86. DeviceReplacement string `yaml:"device_replacement"`
  87. BrandReplacement string `yaml:"brand_replacement"`
  88. ModelReplacement string `yaml:"model_replacement"`
  89. MatchesCount uint64
  90. }
  91. func (device *deviceParser) setDefaults() {
  92. if device.DeviceReplacement == "" {
  93. device.DeviceReplacement = "$1"
  94. }
  95. if device.ModelReplacement == "" {
  96. device.ModelReplacement = "$1"
  97. }
  98. }
  99. type Client struct {
  100. UserAgent *UserAgent
  101. Os *Os
  102. Device *Device
  103. }
  104. type Parser struct {
  105. RegexesDefinitions
  106. UserAgentMisses uint64
  107. OsMisses uint64
  108. DeviceMisses uint64
  109. Mode int
  110. UseSort bool
  111. debugMode bool
  112. }
  113. const (
  114. EOsLookUpMode = 1 /* 00000001 */
  115. EUserAgentLookUpMode = 2 /* 00000010 */
  116. EDeviceLookUpMode = 4 /* 00000100 */
  117. cMinMissesTreshold = 100000
  118. cDefaultMissesTreshold = 500000
  119. cDefaultMatchIdxNotOk = 20
  120. cDefaultSortOption = false
  121. )
  122. var (
  123. missesTreshold = uint64(500000)
  124. matchIdxNotOk = 20
  125. )
  126. func (parser *Parser) mustCompile() { // until we can use yaml.UnmarshalYAML with embedded pointer struct
  127. for _, p := range parser.UA {
  128. p.Reg = compileRegex(p.Flags, p.Expr)
  129. p.setDefaults()
  130. }
  131. for _, p := range parser.OS {
  132. p.Reg = compileRegex(p.Flags, p.Expr)
  133. p.setDefaults()
  134. }
  135. for _, p := range parser.Device {
  136. p.Reg = compileRegex(p.Flags, p.Expr)
  137. p.setDefaults()
  138. }
  139. }
  140. func NewWithOptions(regexFile string, mode, treshold, topCnt int, useSort, debugMode bool) (*Parser, error) {
  141. data, err := ioutil.ReadFile(regexFile)
  142. if nil != err {
  143. return nil, err
  144. }
  145. if topCnt >= 0 {
  146. matchIdxNotOk = topCnt
  147. }
  148. if treshold > cMinMissesTreshold {
  149. missesTreshold = uint64(treshold)
  150. }
  151. parser, err := NewFromBytes(data)
  152. if err != nil {
  153. return nil, err
  154. }
  155. parser.Mode = mode
  156. parser.UseSort = useSort
  157. parser.debugMode = debugMode
  158. return parser, nil
  159. }
  160. func New(regexFile string) (*Parser, error) {
  161. data, err := ioutil.ReadFile(regexFile)
  162. if nil != err {
  163. return nil, err
  164. }
  165. matchIdxNotOk = cDefaultMatchIdxNotOk
  166. missesTreshold = cDefaultMissesTreshold
  167. parser, err := NewFromBytes(data)
  168. if err != nil {
  169. return nil, err
  170. }
  171. return parser, nil
  172. }
  173. func NewFromSaved() *Parser {
  174. parser, err := NewFromBytes(definitionYaml)
  175. if err != nil {
  176. // if the YAML is malformed, it's a programmatic error inside what
  177. // we've statically-compiled in our binary. Panic!
  178. panic(err.Error())
  179. }
  180. return parser
  181. }
  182. func NewFromBytes(data []byte) (*Parser, error) {
  183. var definitions RegexesDefinitions
  184. if err := yaml.Unmarshal(data, &definitions); err != nil {
  185. return nil, err
  186. }
  187. parser := &Parser{definitions, 0, 0, 0, (EOsLookUpMode|EUserAgentLookUpMode|EDeviceLookUpMode), false, false}
  188. parser.mustCompile()
  189. return parser, nil
  190. }
  191. func (parser *Parser) Parse(line string) *Client {
  192. cli := new(Client)
  193. var wg sync.WaitGroup
  194. if EUserAgentLookUpMode & parser.Mode == EUserAgentLookUpMode {
  195. wg.Add(1)
  196. go func() {
  197. defer wg.Done()
  198. parser.RLock()
  199. cli.UserAgent = parser.ParseUserAgent(line)
  200. parser.RUnlock()
  201. }()
  202. }
  203. if EOsLookUpMode & parser.Mode == EOsLookUpMode {
  204. wg.Add(1)
  205. go func() {
  206. defer wg.Done()
  207. parser.RLock()
  208. cli.Os = parser.ParseOs(line)
  209. parser.RUnlock()
  210. }()
  211. }
  212. if EDeviceLookUpMode & parser.Mode == EDeviceLookUpMode {
  213. wg.Add(1)
  214. go func() {
  215. defer wg.Done()
  216. parser.RLock()
  217. cli.Device = parser.ParseDevice(line)
  218. parser.RUnlock()
  219. }()
  220. }
  221. wg.Wait()
  222. if parser.UseSort == true {
  223. checkAndSort(parser)
  224. }
  225. return cli
  226. }
  227. func (parser *Parser) ParseUserAgent(line string) *UserAgent {
  228. ua := new(UserAgent)
  229. foundIdx := -1
  230. found := false
  231. for i, uaPattern := range parser.UA {
  232. uaPattern.Match(line, ua)
  233. if len(ua.Family) > 0 {
  234. found = true
  235. foundIdx = i
  236. atomic.AddUint64(&uaPattern.MatchesCount, 1)
  237. break
  238. }
  239. }
  240. if !found {
  241. ua.Family = "Other"
  242. }
  243. if(foundIdx > matchIdxNotOk) {
  244. atomic.AddUint64(&parser.UserAgentMisses, 1)
  245. }
  246. return ua
  247. }
  248. func (parser *Parser) ParseOs(line string) *Os {
  249. os := new(Os)
  250. foundIdx := -1
  251. found := false
  252. for i, osPattern := range parser.OS {
  253. osPattern.Match(line, os)
  254. if len(os.Family) > 0 {
  255. found = true
  256. foundIdx = i
  257. atomic.AddUint64(&osPattern.MatchesCount, 1)
  258. break
  259. }
  260. }
  261. if !found {
  262. os.Family = "Other"
  263. }
  264. if(foundIdx > matchIdxNotOk) {
  265. atomic.AddUint64(&parser.OsMisses, 1)
  266. }
  267. return os
  268. }
  269. func (parser *Parser) ParseDevice(line string) *Device {
  270. dvc := new(Device)
  271. foundIdx := -1
  272. found := false
  273. for i, dvcPattern := range parser.Device {
  274. dvcPattern.Match(line, dvc)
  275. if len(dvc.Family) > 0 {
  276. found = true
  277. foundIdx = i
  278. atomic.AddUint64(&dvcPattern.MatchesCount, 1)
  279. break
  280. }
  281. }
  282. if !found {
  283. dvc.Family = "Other"
  284. }
  285. if(foundIdx > matchIdxNotOk) {
  286. atomic.AddUint64(&parser.DeviceMisses, 1)
  287. }
  288. return dvc
  289. }
  290. func checkAndSort(parser *Parser) {
  291. parser.Lock()
  292. if(atomic.LoadUint64(&parser.UserAgentMisses) >= missesTreshold) {
  293. if parser.debugMode {
  294. fmt.Printf("%s\tSorting UserAgents slice\n", time.Now());
  295. }
  296. parser.UserAgentMisses = 0
  297. sort.Sort(UserAgentSorter(parser.UA));
  298. }
  299. parser.Unlock()
  300. parser.Lock()
  301. if(atomic.LoadUint64(&parser.OsMisses) >= missesTreshold) {
  302. if parser.debugMode {
  303. fmt.Printf("%s\tSorting OS slice\n", time.Now());
  304. }
  305. parser.OsMisses = 0
  306. sort.Sort(OsSorter(parser.OS));
  307. }
  308. parser.Unlock()
  309. parser.Lock()
  310. if(atomic.LoadUint64(&parser.DeviceMisses) >= missesTreshold) {
  311. if parser.debugMode {
  312. fmt.Printf("%s\tSorting Device slice\n", time.Now());
  313. }
  314. parser.DeviceMisses = 0
  315. sort.Sort(DeviceSorter(parser.Device));
  316. }
  317. parser.Unlock()
  318. }
  319. func compileRegex(flags, expr string) *regexp.Regexp {
  320. if flags == "" {
  321. return regexp.MustCompile(expr)
  322. } else {
  323. return regexp.MustCompile(fmt.Sprintf("(?%s)%s", flags, expr))
  324. }
  325. }