Skip to content

Commit a0a0fa2

Browse files
authored
init (#1)
* init * handle empty case
1 parent c930e9a commit a0a0fa2

File tree

9 files changed

+164
-166
lines changed

9 files changed

+164
-166
lines changed

.gitignore

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1-
*~
1+
# Editors
22
*#
3-
*.dat
3+
*~
4+
.idea
5+
.vscode
6+
7+
# OS
8+
.DS_Store
9+
10+
# Database
11+
*.dat

LICENCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) [year] [fullname]
3+
Copyright (c) 2023 Dailymotion
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,19 @@
1-
# Udger golang (format V3):
1+
# Udger Golang
22

3-
This package is a fork of github.com/udger/udger with the following changes:
4-
- Using golang regexp instead of github.com/glenn-brown/golang-pkg-pcre
5-
- Supporting partial UA parsing by constructor flags (device, browser, os)
3+
This package reads in memory all the database from [Udger](https://udger.com/) and lets you lookup for user agent's metadata. The parsing only relies on the golang standard library regex. Only the **Udger Data v3 - Sqlite3 format** is supported.
64

7-
# Usage:
5+
This package is a fork of https://github.com/yoavfeld/udger itself forked from https://github.com/udger/udger.
86

9-
```
10-
package main
11-
12-
import (
13-
"github.com/yoavfeld/udger"
14-
)
15-
16-
func main() {
17-
client, err := udger.New("udgerDBv3FilePath", &udger.Flags{Device: true})
18-
if err != nil {
19-
log.Fatal(err)
20-
}
21-
ua := "Mozilla/5.0 (Linux; Android 4.4.4; MI PAD Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Safari/537.36"
22-
res,err := client.Lookup(ua)
23-
if err != nil {
24-
log.Fatal(err)
25-
}
26-
}
7+
## Tests / Benchmarks
278

9+
To run tests and benchmarks you need to have the Udger database `udgerdb_v3.dat` located in this folder.
2810

11+
To run unit tests and run:
12+
```shell
13+
go tests ./...
2914
```
3015

31-
# Open issues:
32-
33-
- Browser version is not supported since using golang regexp. it require a fix in findData func to support findDataWithVersion func
16+
To run tests with benchmarks:
17+
```shell
18+
go test -bench=.
19+
```

example/main.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/dailymotion-oss/udger
2+
3+
go 1.20
4+
5+
require (
6+
github.com/mattn/go-sqlite3 v1.14.18
7+
github.com/smartystreets/goconvey v1.8.1
8+
)
9+
10+
require (
11+
github.com/gopherjs/gopherjs v1.17.2 // indirect
12+
github.com/jtolds/gls v4.20.0+incompatible // indirect
13+
github.com/smarty/assertions v1.15.0 // indirect
14+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
2+
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
3+
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
4+
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
5+
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
6+
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
7+
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
8+
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
9+
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
10+
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=

types.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ type Udger struct {
1818
Browsers map[int]Browser
1919
OS map[int]OS
2020
Devices map[int]Device
21-
Flags *Flags
2221
}
2322

2423
// Info is the struct returned by the Lookup(ua string) function, contains everything about the UA
@@ -28,7 +27,7 @@ type Info struct {
2827
Device Device `json:"device"`
2928
}
3029

31-
// Browser contains information about the browser type, engine and off course it's name
30+
// Browser contains information about the browser type, engine and off course its name
3231
type Browser struct {
3332
Name string `json:"name"`
3433
Family string `json:"family"`

udger.go

Lines changed: 58 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,14 @@ import (
88
"strings"
99
)
1010

11-
type Flags struct {
12-
browser bool
13-
Device bool
14-
os bool
15-
}
16-
17-
// New creates a new instance of Udger and load all the database in memory to allow fast lookup
18-
// you need to pass the sqlite database in parameter
19-
func New(dbPath string, flags *Flags) (*Udger, error) {
20-
if flags == nil {
21-
flags = &Flags{
22-
browser: true,
23-
Device: true,
24-
os: true,
25-
}
26-
}
11+
// New creates a new instance of Udger from the dbPath database loaded in memory for fast lookup.
12+
func New(dbPath string) (*Udger, error) {
2713
u := &Udger{
2814
Browsers: make(map[int]Browser),
2915
OS: make(map[int]OS),
3016
Devices: make(map[int]Device),
3117
browserTypes: make(map[int]string),
3218
browserOS: make(map[int]int),
33-
Flags: flags,
3419
}
3520
var err error
3621

@@ -52,66 +37,57 @@ func New(dbPath string, flags *Flags) (*Udger, error) {
5237
return u, nil
5338
}
5439

55-
// Lookup one user agent and return a Info struct who contains all the metadata possible for the UA.
40+
// Lookup returns all the metadata possible for the given user agent string ua.
5641
func (udger *Udger) Lookup(ua string) (*Info, error) {
5742
info := &Info{}
58-
f := udger.Flags
5943

60-
var browserID int
61-
if f.browser {
62-
browserID, version, err := udger.findDataWithVersion(ua, udger.rexBrowsers, true)
63-
if err != nil {
64-
return nil, err
65-
}
66-
info.Browser = udger.Browsers[browserID]
44+
browserID, browserVersion := udger.findDataWithVersion(ua, udger.rexBrowsers, true)
45+
if browser, found := udger.Browsers[browserID]; found {
46+
info.Browser = browser
6747
if info.Browser.Family != "" {
68-
info.Browser.Name = info.Browser.Family + " " + version
48+
info.Browser.Name = browser.Family + " " + browserVersion
6949
}
70-
info.Browser.Version = version
71-
info.Browser.Type = udger.browserTypes[info.Browser.typ]
50+
info.Browser.Version = browserVersion
51+
info.Browser.Type = udger.browserTypes[browser.typ]
52+
} else {
53+
info.Browser.typ = -1
7254
}
7355

74-
if f.os {
75-
if val, ok := udger.browserOS[browserID]; ok {
76-
info.OS = udger.OS[val]
77-
} else {
78-
osID, _, err := udger.findData(ua, udger.rexOS, false)
79-
if err != nil {
80-
return nil, err
81-
}
82-
info.OS = udger.OS[osID]
83-
}
56+
if val, ok := udger.browserOS[browserID]; ok {
57+
info.OS = udger.OS[val]
58+
} else {
59+
osID, _ := udger.findDataWithVersion(ua, udger.rexOS, false)
60+
info.OS = udger.OS[osID]
8461
}
8562

86-
if f.Device {
87-
deviceID, _, err := udger.findData(ua, udger.rexDevices, false)
88-
if err != nil {
89-
return nil, err
63+
deviceID, _ := udger.findDataWithVersion(ua, udger.rexDevices, false)
64+
65+
if val, ok := udger.Devices[deviceID]; ok {
66+
info.Device = val
67+
} else if info.Browser.typ == -1 { // empty
68+
// pass
69+
} else if info.Browser.typ == 3 { // if browser is mobile, we can guess it's a mobile
70+
info.Device = Device{
71+
Name: "Smartphone",
72+
Icon: "phone.png",
9073
}
91-
if val, ok := udger.Devices[deviceID]; ok {
92-
info.Device = val
93-
} else if info.Browser.typ == 3 { // if browser is mobile, we can guess its a mobile
94-
info.Device = Device{
95-
Name: "Smartphone",
96-
Icon: "phone.png",
97-
}
98-
} else if info.Browser.typ == 5 || info.Browser.typ == 10 || info.Browser.typ == 20 || info.Browser.typ == 50 {
99-
info.Device = Device{
100-
Name: "Other",
101-
Icon: "other.png",
102-
}
103-
} else {
104-
//nothing so personal computer
105-
info.Device = Device{
106-
Name: "Personal computer",
107-
Icon: "desktop.png",
108-
}
74+
} else if info.Browser.typ == 5 || info.Browser.typ == 10 || info.Browser.typ == 20 || info.Browser.typ == 50 {
75+
info.Device = Device{
76+
Name: "Other",
77+
Icon: "other.png",
78+
}
79+
} else {
80+
//nothing so personal computer
81+
info.Device = Device{
82+
Name: "Personal computer",
83+
Icon: "desktop.png",
10984
}
11085
}
11186
return info, nil
11287
}
11388

11489
func (udger *Udger) cleanRegex(r string) string {
90+
// removes single-line and case-insensitive modifiers
11591
if strings.HasSuffix(r, "/si") {
11692
r = r[:len(r)-3]
11793
}
@@ -122,47 +98,35 @@ func (udger *Udger) cleanRegex(r string) string {
12298
return r
12399
}
124100

125-
func (udger *Udger) findDataWithVersion(ua string, data []rexData, withVersion bool) (idx int, value string, err error) {
126-
defer func() {
127-
if r := recover(); r != nil {
128-
idx, value, err = udger.findData(ua, data, false)
129-
}
130-
}()
131-
idx, value, err = udger.findData(ua, data, withVersion)
132-
return idx, value, err
133-
}
134-
135-
func (udger *Udger) findData(ua string, data []rexData, withVersion bool) (idx int, value string, err error) {
101+
func (udger *Udger) findDataWithVersion(ua string, data []rexData, withVersion bool) (int, string) {
102+
index := -1
103+
version := ""
136104
for i := 0; i < len(data); i++ {
137105
r := data[i].RegexCompiled
138106
if !r.MatchString(ua) {
139107
continue
140108
}
141-
//TODO: implement with regexp lib for support browser version & name
142-
//if withVersion && matcher.Present(1) {
143-
// return data[i].ID, matcher.GroupString(1), nil
144-
//}
145-
return data[i].ID, "", nil
109+
index = data[i].ID
110+
if withVersion {
111+
sub := r.FindStringSubmatch(ua)
112+
if len(sub) >= 2 {
113+
version = sub[1]
114+
}
115+
}
116+
break
146117
}
147-
return -1, "", nil
118+
return index, version
148119
}
149120

150121
func (udger *Udger) init() error {
151-
f := udger.Flags
152-
if f.browser {
153-
if err := udger.initBrowsers(); err != nil {
154-
return err
155-
}
122+
if err := udger.initBrowsers(); err != nil {
123+
return err
156124
}
157-
if f.Device {
158-
if err := udger.initDevices(); err != nil {
159-
return err
160-
}
125+
if err := udger.initDevices(); err != nil {
126+
return err
161127
}
162-
if f.os {
163-
if err := udger.initOS(); err != nil {
164-
return err
165-
}
128+
if err := udger.initOS(); err != nil {
129+
return err
166130
}
167131
return nil
168132
}
@@ -176,6 +140,7 @@ func (udger *Udger) initBrowsers() error {
176140
var d rexData
177141
rows.Scan(&d.ID, &d.Regex)
178142
d.Regex = udger.cleanRegex(d.Regex)
143+
// set case-insensitive flag withing current group
179144
r, err := regexp.Compile("(?i)" + d.Regex)
180145
if err != nil {
181146
return err
@@ -185,6 +150,7 @@ func (udger *Udger) initBrowsers() error {
185150
}
186151
rows.Close()
187152

153+
// Chrome, Safari, Firefox, etc.
188154
rows, err = udger.db.Query("SELECT id, class_id, name,engine,vendor,icon FROM udger_client_list")
189155
if err != nil {
190156
return err
@@ -197,6 +163,7 @@ func (udger *Udger) initBrowsers() error {
197163
}
198164
rows.Close()
199165

166+
// browser, mobile, crawler, etc.
200167
rows, err = udger.db.Query("SELECT id, client_classification FROM udger_client_class")
201168
if err != nil {
202169
return err

0 commit comments

Comments
 (0)