user_agent_parser 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of user_agent_parser might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 11927dac3bd4e88a9a7be095093a664fd96b8111
4
- data.tar.gz: 21b998a0a2f0016ad4e75b6e8678e855369dadee
2
+ SHA256:
3
+ metadata.gz: bd4b7259791194e0dfdd38e5a9fe5f0316f055eb94d21578fa542a8913c6834b
4
+ data.tar.gz: 1682d5ac3c9ff1e10b896846f215e7c487d83a58ded86ebbed073213156699f8
5
5
  SHA512:
6
- metadata.gz: 9e374cd5e96daca43ef27002052edb5e1101d35c77a1f18dda370b1c28702709a86a9a2b36d830162b9a068a5259ac2d3d2a1532ae2efaf294d4a955224bcfd6
7
- data.tar.gz: 020cef494b4307e0921978e213c7c243cbb70c075f17a6f238e95a40dfd999d38c896abae12d17cf1afd834fea9f3be533d308f08e607127b07be1b82a1ed5b5
6
+ metadata.gz: f0306cab9fe8e6213f339a061a5683cea62de9a8f06ea5c96e756da014d0cfcffb90d82ff8be66da7c83726fe18ac0161f6eea817e4c211a1ebcb632538bbb38
7
+ data.tar.gz: be503613c609542a66750d169d0f7a52caa36baa12aeca16739984476edc50a58479d7e537cc3b572c5845d4f13ecbaf5f642953b2ac0b3bf72b532ed110d770
@@ -8,7 +8,7 @@ require 'user_agent_parser/cli'
8
8
 
9
9
  options = {}
10
10
 
11
- optparse = OptionParser.new do|opts|
11
+ optparse = OptionParser.new do |opts|
12
12
  opts.on('--family', 'Print family only') do
13
13
  options[:family] = true
14
14
  end
@@ -20,7 +20,7 @@ module UserAgentParser
20
20
  minor
21
21
  elsif @options[:os]
22
22
  @user_agent.os.to_s
23
- elsif format = @options[:format]
23
+ elsif (format = @options[:format])
24
24
  format
25
25
  .gsub('%f', @user_agent.family)
26
26
  .gsub('%n', @user_agent.name)
@@ -47,8 +47,8 @@ module UserAgentParser
47
47
  @version ||= @user_agent.version
48
48
  end
49
49
 
50
- def with_version(&block)
51
- block.call(version) if version
50
+ def with_version
51
+ yield(version) if version
52
52
  end
53
53
  end
54
54
  end
@@ -25,5 +25,13 @@ module UserAgentParser
25
25
  end
26
26
 
27
27
  alias == eql?
28
+
29
+ def to_h
30
+ {
31
+ family: family,
32
+ model: model,
33
+ brand: brand
34
+ }
35
+ end
28
36
  end
29
37
  end
@@ -13,9 +13,7 @@ module UserAgentParser
13
13
 
14
14
  def to_s
15
15
  string = family
16
- unless version.nil?
17
- string += " #{version}"
18
- end
16
+ string += " #{version}" unless version.nil?
19
17
  string
20
18
  end
21
19
 
@@ -30,5 +28,12 @@ module UserAgentParser
30
28
  end
31
29
 
32
30
  alias == eql?
31
+
32
+ def to_h
33
+ {
34
+ version: version.to_h,
35
+ family: family
36
+ }
37
+ end
33
38
  end
34
39
  end
@@ -3,8 +3,25 @@
3
3
  require 'yaml'
4
4
 
5
5
  module UserAgentParser
6
-
7
6
  class Parser
7
+ FAMILY_REPLACEMENT_KEYS = %w[
8
+ family_replacement
9
+ v1_replacement
10
+ v2_replacement
11
+ v3_replacement
12
+ v4_replacement
13
+ ].freeze
14
+
15
+ OS_REPLACEMENT_KEYS = %w[
16
+ os_replacement
17
+ os_v1_replacement
18
+ os_v2_replacement
19
+ os_v3_replacement
20
+ os_v4_replacement
21
+ ].freeze
22
+
23
+ private_constant :FAMILY_REPLACEMENT_KEYS, :OS_REPLACEMENT_KEYS
24
+
8
25
  attr_reader :patterns_path
9
26
 
10
27
  def initialize(options = {})
@@ -18,15 +35,15 @@ module UserAgentParser
18
35
  parse_ua(user_agent, os, device)
19
36
  end
20
37
 
21
- private
38
+ private
22
39
 
23
40
  def load_patterns(path)
24
41
  yml = YAML.load_file(path)
25
42
 
26
43
  # Parse all the regexs
27
- yml.each_pair do |type, patterns|
44
+ yml.each_pair do |_type, patterns|
28
45
  patterns.each do |pattern|
29
- pattern['regex'] = Regexp.new(pattern['regex'], pattern['regex_flag'] == 'i')
46
+ pattern[:regex] = Regexp.new(pattern['regex'], pattern['regex_flag'] == 'i')
30
47
  end
31
48
  end
32
49
 
@@ -63,69 +80,36 @@ module UserAgentParser
63
80
  end
64
81
  end
65
82
 
66
- def first_pattern_match(patterns, value)
67
- patterns.each do |pattern|
68
- if match = pattern['regex'].match(value)
69
- return [pattern, match]
83
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
84
+ def first_pattern_match(patterns, value)
85
+ patterns.each do |pattern|
86
+ if pattern[:regex].match?(value)
87
+ return [pattern, pattern[:regex].match(value)]
88
+ end
70
89
  end
90
+ nil
91
+ end
92
+ else
93
+ def first_pattern_match(patterns, value)
94
+ patterns.each do |pattern|
95
+ if (match = pattern[:regex].match(value))
96
+ return [pattern, match]
97
+ end
98
+ end
99
+ nil
71
100
  end
72
- nil
73
101
  end
74
102
 
75
103
  def user_agent_from_pattern_match(pattern, match, os = nil, device = nil)
76
- family, v1, v2, v3, v4 = match[1], match[2], match[3], match[4], match[5]
77
-
78
- if pattern['family_replacement']
79
- family = pattern['family_replacement'].sub('$1', family || '')
80
- end
81
-
82
- if pattern['v1_replacement']
83
- v1 = pattern['v1_replacement'].sub('$2', v1 || '')
84
- end
85
-
86
- if pattern['v2_replacement']
87
- v2 = pattern['v2_replacement'].sub('$3', v2 || '')
88
- end
89
-
90
- if pattern['v3_replacement']
91
- v3 = pattern['v3_replacement'].sub('$4', v3 || '')
92
- end
104
+ family, *versions = from_pattern_match(FAMILY_REPLACEMENT_KEYS, pattern, match)
93
105
 
94
- if pattern['v4_replacement']
95
- v4 = pattern['v4_replacement'].sub('$5', v4 || '')
96
- end
97
-
98
- version = version_from_segments(v1, v2, v3, v4)
99
-
100
- UserAgent.new(family, version, os, device)
106
+ UserAgent.new(family, version_from_segments(*versions), os, device)
101
107
  end
102
108
 
103
109
  def os_from_pattern_match(pattern, match)
104
- os, v1, v2, v3, v4 = match[1], match[2], match[3], match[4], match[5]
105
-
106
- if pattern['os_replacement']
107
- os = pattern['os_replacement'].sub('$1', os || '')
108
- end
109
-
110
- if pattern['os_v1_replacement']
111
- v1 = pattern['os_v1_replacement'].sub('$2', v1 || '')
112
- end
113
-
114
- if pattern['os_v2_replacement']
115
- v2 = pattern['os_v2_replacement'].sub('$3', v2 || '')
116
- end
117
-
118
- if pattern['os_v3_replacement']
119
- v3 = pattern['os_v3_replacement'].sub('$4', v3 || '')
120
- end
121
-
122
- if pattern['os_v4_replacement']
123
- v4 = pattern['os_v4_replacement'].sub('$5', v4 || '')
124
- end
110
+ os, *versions = from_pattern_match(OS_REPLACEMENT_KEYS, pattern, match)
125
111
 
126
- version = version_from_segments(v1, v2, v3, v4)
127
-
128
- OperatingSystem.new(os, version)
112
+ OperatingSystem.new(os, version_from_segments(*versions))
129
113
  end
130
114
 
131
115
  def device_from_pattern_match(pattern, match)
@@ -135,26 +119,49 @@ module UserAgentParser
135
119
 
136
120
  if pattern['device_replacement']
137
121
  family = pattern['device_replacement']
138
- match.each_with_index { |m,i| family = family.sub("$#{i}", m) }
122
+ match.each_with_index { |m, i| family = family.sub("$#{i}", m) }
139
123
  end
140
- if pattern["model_replacement"]
141
- model = pattern["model_replacement"]
142
- match.each_with_index { |m,i| model = model.sub("$#{i}", m) }
124
+ if pattern['model_replacement']
125
+ model = pattern['model_replacement']
126
+ match.each_with_index { |m, i| model = model.sub("$#{i}", m) }
143
127
  end
144
- if pattern["brand_replacement"]
145
- brand = pattern["brand_replacement"]
146
- match.each_with_index { |m,i| brand = brand.sub("$#{i}", m) }
128
+ if pattern['brand_replacement']
129
+ brand = pattern['brand_replacement']
130
+ match.each_with_index { |m, i| brand = brand.sub("$#{i}", m) }
147
131
  brand.strip!
148
132
  end
149
133
 
150
- model.strip! unless model.nil?
134
+ model&.strip!
151
135
 
152
136
  Device.new(family.strip, model, brand)
153
137
  end
154
138
 
139
+ # Maps replacement keys to their values
140
+ def from_pattern_match(keys, pattern, match)
141
+ keys.each_with_index.map do |key, idx|
142
+ # Check if there is any replacement specified
143
+ if pattern[key]
144
+ interpolate(pattern[key], match)
145
+ else
146
+ # No replacement defined, just return correct match group
147
+ match[idx + 1]
148
+ end
149
+ end
150
+ end
151
+
152
+ # Interpolates a string with data from matches if specified
153
+ def interpolate(replacement, match)
154
+ group_idx = replacement.index('$')
155
+ return replacement if group_idx.nil?
156
+
157
+ group_nbr = replacement[group_idx + 1]
158
+ replacement.sub("$#{group_nbr}", match[group_nbr.to_i])
159
+ end
160
+
155
161
  def version_from_segments(*segments)
156
- segments = segments.compact
157
- segments.empty? ? nil : Version.new(*segments)
162
+ return if segments.all?(&:nil?)
163
+
164
+ Version.new(*segments)
158
165
  end
159
166
  end
160
167
  end
@@ -34,5 +34,14 @@ module UserAgentParser
34
34
  end
35
35
 
36
36
  alias == eql?
37
+
38
+ def to_h
39
+ {
40
+ device: device.to_h,
41
+ family: family,
42
+ os: os.to_h,
43
+ version: version.to_h
44
+ }
45
+ end
37
46
  end
38
47
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module UserAgentParser
4
4
  class Version
5
-
6
5
  # Private: Regex used to split string version string into major, minor,
7
6
  # patch, and patch_minor.
8
7
  SEGMENTS_REGEX = /\d+\-\d+|\d+[a-zA-Z]+$|\d+|[A-Za-z][0-9A-Za-z-]*$/
@@ -11,10 +10,12 @@ module UserAgentParser
11
10
  alias to_s version
12
11
 
13
12
  def initialize(*args)
13
+ # If only one string argument is given, assume a complete version string
14
+ # and attempt to parse it
14
15
  if args.length == 1 && args.first.is_a?(String)
15
16
  @version = args.first.to_s.strip
16
17
  else
17
- @segments = args.map(&:to_s).map(&:strip)
18
+ @segments = args.compact.map(&:to_s).map(&:strip)
18
19
  @version = segments.join('.')
19
20
  end
20
21
  end
@@ -50,5 +51,15 @@ module UserAgentParser
50
51
  def segments
51
52
  @segments ||= version.scan(SEGMENTS_REGEX)
52
53
  end
54
+
55
+ def to_h
56
+ {
57
+ version: version,
58
+ major: major,
59
+ minor: minor,
60
+ patch: patch,
61
+ patch_minor: patch_minor
62
+ }
63
+ end
53
64
  end
54
65
  end
@@ -29,7 +29,7 @@ user_agent_parsers:
29
29
  # Pingdom
30
30
  - regex: '(Pingdom.com_bot_version_)(\d+)\.(\d+)'
31
31
  family_replacement: 'PingdomBot'
32
- # 'Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PingdomTMS/0.8.5 Safari/534.34'
32
+ # 'Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PingdomTMS/0.8.5 Safari/534.34'
33
33
  - regex: '(PingdomTMS)/(\d+)\.(\d+)\.(\d+)'
34
34
  family_replacement: 'PingdomBot'
35
35
 
@@ -37,6 +37,13 @@ user_agent_parsers:
37
37
  - regex: '(NewRelicPinger)/(\d+)\.(\d+)'
38
38
  family_replacement: 'NewRelicPingerBot'
39
39
 
40
+ # Tableau
41
+ - regex: '(Tableau)/(\d+)\.(\d+)'
42
+ family_replacement: 'Tableau'
43
+
44
+ # Salesforce
45
+ - regex: '(Salesforce)(?:.)\/(\d+)\.(\d?)'
46
+
40
47
  #StatusCake
41
48
  - regex: '(\(StatusCake\))'
42
49
  family_replacement: 'StatusCakeBot'
@@ -53,6 +60,10 @@ user_agent_parsers:
53
60
  - regex: 'via ggpht.com GoogleImageProxy'
54
61
  family_replacement: 'GmailImageProxy'
55
62
 
63
+ # Yahoo
64
+ - regex: 'YahooMailProxy; https://help.yahoo.com/kb/yahoo-mail-proxy-SLN28749.html'
65
+ family_replacement: 'YahooMailProxy'
66
+
56
67
  # Twitter
57
68
  - regex: '(Twitterbot)/(\d+)\.(\d+)'
58
69
  family_replacement: 'TwitterBot'
@@ -66,15 +77,22 @@ user_agent_parsers:
66
77
  - regex: '(MSIE) (\d+)\.(\d+)([a-z]\d?)?;.* MSIECrawler'
67
78
  family_replacement: 'MSIECrawler'
68
79
 
80
+ # DAVdroid
81
+ - regex: '(DAVdroid)/(\d+)\.(\d+)(?:\.(\d+))?'
82
+
69
83
  # Downloader ...
70
- - regex: '(Google-HTTP-Java-Client|Apache-HttpClient|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp)(?:[ /](\d+)(?:\.(\d+)(?:\.(\d+))?)?)?'
84
+ - regex: '(Google-HTTP-Java-Client|Apache-HttpClient|Go-http-client|scalaj-http|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp|aihttp|reqwest)(?:[ /](\d+)(?:\.(\d+)(?:\.(\d+))?)?)?'
71
85
 
72
86
  # Pinterestbot
73
87
  - regex: '(Pinterest(?:bot)?)/(\d+)(?:\.(\d+)(?:\.(\d+))?)?[;\s\(]+\+https://www.pinterest.com/bot.html'
74
88
  family_replacement: 'Pinterestbot'
75
89
 
76
90
  # Bots
77
- - regex: '(1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]+-Agent|AdsBot-Google(?:-[a-z]+)?|altavista|AppEngine-Google|archive.*?\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]+)*|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader(?: [A-Za-z]+)*|boitho.com-dc|BotSeer|\b\w*favicon\w*\b|\bYeti(?:-[a-z]+)?|Catchpoint(?: bot)?|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher)?|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]+-)?Googlebot(?:-[a-zA-Z]+)?|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile)?|IconSurf|IlTrovatore(?:-Setaccio)?|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]+Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .*? Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media *)?|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]*|^NING|Nutch[^/]*|Nymesis|ObjectsSearch|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|TwitterBot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]+|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s)? Link Sleuth|Xerka [A-z]+Bot|yacy(?:bot)?|Yahoo[a-z]*Seeker|Yahoo! Slurp|Yandex\w+|YodaoBot(?:-[A-z]+)?|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?'
91
+ - regex: '(1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]+-Agent|AdsBot-Google(?:-[a-z]+)?|altavista|AppEngine-Google|archive.*?\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]+)*|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader(?: [A-Za-z]+)*|boitho.com-dc|BotSeer|BUbiNG|\b\w*favicon\w*\b|\bYeti(?:-[a-z]+)?|Catchpoint(?: bot)?|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher)?|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]+-)?Googlebot(?:-[a-zA-Z]+)?|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile)?|IconSurf|IlTrovatore(?:-Setaccio)?|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]+Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .*? Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media *)?|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]*|^NING|Nutch[^/]*|Nymesis|ObjectsSearch|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|TwitterBot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]+|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s)? Link Sleuth|Xerka [A-z]+Bot|yacy(?:bot)?|Yahoo[a-z]*Seeker|Yahoo! Slurp|Yandex\w+|YodaoBot(?:-[A-z]+)?|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?'
92
+
93
+ # AWS S3 Clients
94
+ # must come before "Bots General matcher" to catch "boto"/"boto3" before "bot"
95
+ - regex: '\b(Boto3?|JetS3t|aws-(?:cli|sdk-(?:cpp|go|java|nodejs|ruby2?))|s3fs)/(\d+)\.(\d+)(?:\.(\d+))?'
78
96
 
79
97
  # Bots General matcher 'name/0.0'
80
98
  - regex: '(?:\/[A-Za-z0-9\.]+)? *([A-Za-z0-9 \-_\!\[\]:]*(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]*))/(\d+)(?:\.(\d+)(?:\.(\d+))?)?'
@@ -88,8 +106,8 @@ user_agent_parsers:
88
106
  # See os_parsers if you want to target a specific TV
89
107
  - regex: '(HbbTV)/(\d+)\.(\d+)\.(\d+) \('
90
108
 
91
- # must go before Firefox to catch Chimera/SeaMonkey/Camino
92
- - regex: '(Chimera|SeaMonkey|Camino)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*)?'
109
+ # must go before Firefox to catch Chimera/SeaMonkey/Camino/Waterfox
110
+ - regex: '(Chimera|SeaMonkey|Camino|Waterfox)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*)?'
93
111
 
94
112
  # Social Networks
95
113
  # Facebook
@@ -98,6 +116,18 @@ user_agent_parsers:
98
116
  # Pinterest
99
117
  - regex: '\[(Pinterest)/[^\]]+\]'
100
118
  - regex: '(Pinterest)(?: for Android(?: Tablet)?)?/(\d+)(?:\.(\d+)(?:\.(\d+))?)?'
119
+ # Instagram app
120
+ - regex: 'Mozilla.*Mobile.*(Instagram).(\d+)\.(\d+)\.(\d+)'
121
+ # Flipbaord app
122
+ - regex: 'Mozilla.*Mobile.*(Flipboard).(\d+)\.(\d+)\.(\d+)'
123
+ # Flipbaord-briefing app
124
+ - regex: 'Mozilla.*Mobile.*(Flipboard-Briefing).(\d+)\.(\d+)\.(\d+)'
125
+ # Onefootball app
126
+ - regex: 'Mozilla.*Mobile.*(Onefootball)\/Android.(\d+)\.(\d+)\.(\d+)'
127
+
128
+ # Basilisk
129
+ - regex: '(Firefox)/(\d+)\.(\d+) Basilisk/(\d+)'
130
+ family_replacement: 'Basilisk'
101
131
 
102
132
  # Pale Moon
103
133
  - regex: '(PaleMoon)/(\d+)\.(\d+)\.?(\d+)?'
@@ -263,7 +293,7 @@ user_agent_parsers:
263
293
 
264
294
  # Seznam.cz browser (based on WebKit)
265
295
  - regex: '(SznProhlizec)/(\d+)\.(\d+)(?:\.(\d+))?'
266
- family_replacement: 'Seznam.cz'
296
+ family_replacement: 'Seznam prohlížeč'
267
297
 
268
298
  # Coc Coc browser, based on Chrome (used in Vietnam)
269
299
  - regex: '(coc_coc_browser)/(\d+)\.(\d+)(?:\.(\d+))?'
@@ -283,6 +313,8 @@ user_agent_parsers:
283
313
  - regex: '(Crosswalk)/(\d+)\.(\d+)\.(\d+)\.(\d+)'
284
314
 
285
315
  # Chrome Mobile
316
+ - regex: 'Version/.+(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+)'
317
+ family_replacement: 'Chrome Mobile WebView'
286
318
  - regex: '; wv\).+(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+)'
287
319
  family_replacement: 'Chrome Mobile WebView'
288
320
  - regex: '(CrMo)/(\d+)\.(\d+)\.(\d+)\.(\d+)'
@@ -335,6 +367,17 @@ user_agent_parsers:
335
367
  # Podcast catcher Applications using iTunes
336
368
  - regex: '(PodCruncher|Downcast)[ /]?(\d+)\.?(\d+)?\.?(\d+)?\.?(\d+)?'
337
369
 
370
+ # Box Notes https://www.box.com/resources/downloads
371
+ # Must be before Electron
372
+ - regex: ' (BoxNotes)/(\d+)\.(\d+)\.(\d+)'
373
+
374
+ # Whale
375
+ - regex: '(Whale)/(\d+)\.(\d+)\.(\d+)\.(\d+) Mobile(?:[ /]|$)'
376
+ family_replacement: 'Whale'
377
+
378
+ - regex: '(Whale)/(\d+)\.(\d+)\.(\d+)'
379
+ family_replacement: 'Whale'
380
+
338
381
  #### END SPECIAL CASES TOP ####
339
382
 
340
383
  #### MAIN CASES - this catches > 50% of all browsers ####
@@ -350,7 +393,7 @@ user_agent_parsers:
350
393
  family_replacement: 'HipChat Desktop Client'
351
394
 
352
395
  # Browser/major_version.minor_version.beta_version
353
- - regex: '\b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave|MacOutlook|Electron)/(\d+)\.(\d+)\.(\d+)'
396
+ - regex: '\b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave|MacOutlook|Electron|OktaMobile)/(\d+)\.(\d+)\.(\d+)'
354
397
 
355
398
  # Outlook 2007
356
399
  - regex: 'Microsoft Office Outlook 12\.\d+\.\d+|MSOffice 12'
@@ -415,15 +458,19 @@ user_agent_parsers:
415
458
 
416
459
  # Headless Chrome
417
460
  # https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
418
- # Currently only available on Linux
419
- - regex: 'HeadlessChrome'
420
- family_replacement: 'HeadlessChrome'
461
+ - regex: '(HeadlessChrome)(?:/(\d+)\.(\d+)\.(\d+))?'
462
+
463
+ # Evolution Mail CardDav/CalDav integration
464
+ - regex: '(Evolution)/(\d+)\.(\d+)\.(\d+\.\d+)'
465
+
466
+ # Roundcube Mail CardDav plugin
467
+ - regex: '(RCM CardDAV plugin)/(\d+)\.(\d+)\.(\d+(?:-dev)?)'
421
468
 
422
469
  # Browser/major_version.minor_version
423
470
  - regex: '(bingbot|Bolt|AdobeAIR|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE Browser|mDolphin|qutebrowser|Otter|QupZilla|MailBar|kmail2|YahooMobileMail|ExchangeWebServices|ExchangeServicesClient|Dragon|Outlook-iOS-Android)/(\d+)\.(\d+)(?:\.(\d+))?'
424
471
 
425
472
  # Chrome/Chromium/major_version.minor_version
426
- - regex: '(Chromium|Chrome)/(\d+)\.(\d+)(?:\.(\d+))?'
473
+ - regex: '(Chromium|Chrome)/(\d+)\.(\d+)(?:\.(\d+))?(?:\.(\d+))?'
427
474
 
428
475
  ##########
429
476
  # IE Mobile needs to happen before Android to catch cases such as:
@@ -463,7 +510,7 @@ user_agent_parsers:
463
510
  family_replacement: 'FancyMusic'
464
511
  - regex: 'EspnDownloadManager'
465
512
  family_replacement: 'ESPN'
466
- - regex: '(ESPN) Radio (\d+)\.(\d+)\.?(\d+)? ?[rv:]?(\d+)? '
513
+ - regex: '(ESPN) Radio (\d+)\.(\d+)\.?(\d+)? ?(?:rv:(\d+))? '
467
514
  - regex: '(podracer|jPodder) v ?(\d+)\.(\d+)\.?(\d+)?'
468
515
  - regex: '(ZDM)/(\d+)\.(\d+)[; ]?'
469
516
  - regex: '(Zune|BeyondPod) (\d+)\.?(\d+)?[\);]'
@@ -521,6 +568,15 @@ user_agent_parsers:
521
568
  - regex: '(MSIE) (\d+)\.(\d+).*XBLWP7'
522
569
  family_replacement: 'IE Large Screen'
523
570
 
571
+ # Nextcloud desktop sync client
572
+ - regex: '(Nextcloud)'
573
+
574
+ # Generic mirall client
575
+ - regex: '(mirall)/(\d+)\.(\d+)\.(\d+)'
576
+
577
+ # Nextcloud/Owncloud android client
578
+ - regex: '(ownCloud-android)/(\d+)\.(\d+)\.(\d+)'
579
+ family_replacement: 'Owncloud'
524
580
 
525
581
  #### END MAIN CASES ####
526
582
 
@@ -552,6 +608,8 @@ user_agent_parsers:
552
608
  # @note: iOS / OSX Applications
553
609
  - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?.*[ +]Safari'
554
610
  family_replacement: 'Mobile Safari'
611
+ - regex: '(iPod|iPod touch|iPhone|iPad);.*CPU.*OS[ +](\d+)_(\d+)(?:_(\d+))?.* AppleNews\/\d+\.\d+\.\d+?'
612
+ family_replacement: 'Mobile Safari UI/WKWebView'
555
613
  - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?'
556
614
  family_replacement: 'Mobile Safari UI/WKWebView'
557
615
  - regex: '(iPod|iPod touch|iPhone|iPad);.*CPU.*OS[ +](\d+)_(\d+)(?:_(\d+))?.*Mobile.*[ +]Safari'
@@ -563,6 +621,11 @@ user_agent_parsers:
563
621
  - regex: '(iPod|iPhone|iPad)'
564
622
  family_replacement: 'Mobile Safari UI/WKWebView'
565
623
 
624
+ ##########################
625
+ # Outlook on iOS >= 2.62.0
626
+ ##########################
627
+ - regex: '(Outlook-iOS)/\d+\.\d+\.prod\.iphone \((\d+)\.(\d+)\.(\d+)\)'
628
+
566
629
  - regex: '(AvantGo) (\d+).(\d+)'
567
630
 
568
631
  - regex: '(OneBrowser)/(\d+).(\d+)'
@@ -653,8 +716,21 @@ user_agent_parsers:
653
716
  - regex: '(Phantom)/V(\d+)\.(\d+)'
654
717
  family_replacement: 'Phantom Browser'
655
718
 
656
- - regex: 'Trident(.*)rv.(\d+)\.(\d+)'
719
+ - regex: '(Trident)/(7|8)\.(0)'
720
+ family_replacement: 'IE'
721
+ v1_replacement: '11'
722
+
723
+ - regex: '(Trident)/(6)\.(0)'
724
+ family_replacement: 'IE'
725
+ v1_replacement: '10'
726
+
727
+ - regex: '(Trident)/(5)\.(0)'
657
728
  family_replacement: 'IE'
729
+ v1_replacement: '9'
730
+
731
+ - regex: '(Trident)/(4)\.(0)'
732
+ family_replacement: 'IE'
733
+ v1_replacement: '8'
658
734
 
659
735
  # Espial
660
736
  - regex: '(Espial)/(\d+)(?:\.(\d+))?(?:\.(\d+))?'
@@ -678,10 +754,16 @@ user_agent_parsers:
678
754
  family_replacement: 'Python Requests'
679
755
 
680
756
  # headless user-agents
681
- - regex: '\b(Windows-Update-Agent|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|Wget|OpenBSD ftp|jupdate)(?:[ /](\d+)(?:\.(\d+)(?:\.(\d+))?)?)?'
757
+ - regex: '\b(Windows-Update-Agent|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|PycURL|Wget|aria2|Axel|OpenBSD ftp|lftp|jupdate|insomnia)(?:[ /](\d+)(?:\.(\d+)(?:\.(\d+))?)?)?'
682
758
 
683
759
  - regex: '(Java)[/ ]{0,1}\d+\.(\d+)\.(\d+)[_-]*([a-zA-Z0-9]+)*'
684
760
 
761
+ # Cloud Storage Clients
762
+ - regex: '^(Cyberduck)/(\d+)\.(\d+)\.(\d+)(?:\.\d+)?'
763
+ - regex: '^(S3 Browser) (\d+)-(\d+)-(\d+)(?:\s*http://s3browser\.com)?'
764
+ # rclone - rsync for cloud storage - https://rclone.org/
765
+ - regex: '^(rclone)/v(\d+)\.(\d+)'
766
+
685
767
  # Roku Digital-Video-Players https://www.roku.com/
686
768
  - regex: '^(Roku)/DVP-(\d+)\.(\d+)'
687
769
 
@@ -689,6 +771,8 @@ user_agent_parsers:
689
771
  - regex: '(Kurio)\/(\d+)\.(\d+)\.(\d+)'
690
772
  family_replacement: 'Kurio App'
691
773
 
774
+ # Box Drive and Box Sync https://www.box.com/resources/downloads
775
+ - regex: '^(Box(?: Sync)?)/(\d+)\.(\d+)\.(\d+)'
692
776
 
693
777
  os_parsers:
694
778
  ##########
@@ -767,7 +851,7 @@ os_parsers:
767
851
  # Android
768
852
  # can actually detect rooted android os. do we care?
769
853
  ##########
770
- - regex: '(Android)[ \-/](\d+)\.(\d+)(?:[.\-]([a-z0-9]+))?'
854
+ - regex: '(Android)[ \-/](\d+)\.?(\d+)?(?:[.\-]([a-z0-9]+))?'
771
855
 
772
856
  - regex: '(Android) Donut'
773
857
  os_v1_replacement: '1'
@@ -799,6 +883,10 @@ os_parsers:
799
883
  - regex: '^(JUC).*; ?U; ?(?:Android)?(\d+)\.(\d+)(?:[\.\-]([a-z0-9]+))?'
800
884
  os_replacement: 'Android'
801
885
 
886
+ # Salesforce
887
+ - regex: '(android)\s(?:mobile\/)(\d+)\.(\d+)\.?(\d+)?'
888
+ os_replacement: 'Android'
889
+
802
890
  ##########
803
891
  # Kindle Android
804
892
  ##########
@@ -824,51 +912,75 @@ os_parsers:
824
912
  os_replacement: 'Windows Mobile'
825
913
 
826
914
  - regex: '(Windows (?:NT 5\.2|NT 5\.1))'
827
- os_replacement: 'Windows XP'
915
+ os_replacement: 'Windows'
916
+ os_v1_replacement: 'XP'
828
917
 
829
918
  - regex: '(Windows NT 6\.1)'
830
- os_replacement: 'Windows 7'
919
+ os_replacement: 'Windows'
920
+ os_v1_replacement: '7'
831
921
 
832
922
  - regex: '(Windows NT 6\.0)'
833
- os_replacement: 'Windows Vista'
923
+ os_replacement: 'Windows'
924
+ os_v1_replacement: 'Vista'
834
925
 
835
926
  - regex: '(Win 9x 4\.90)'
836
- os_replacement: 'Windows ME'
927
+ os_replacement: 'Windows'
928
+ os_v1_replacement: 'ME'
837
929
 
838
930
  - regex: '(Windows 98|Windows XP|Windows ME|Windows 95|Windows CE|Windows 7|Windows NT 4\.0|Windows Vista|Windows 2000|Windows 3.1)'
839
931
 
840
932
  - regex: '(Windows NT 6\.2; ARM;)'
841
- os_replacement: 'Windows RT'
933
+ os_replacement: 'Windows'
934
+ os_v1_replacement: 'RT'
935
+
842
936
  - regex: '(Windows NT 6\.2)'
843
- os_replacement: 'Windows 8'
937
+ os_replacement: 'Windows'
938
+ os_v1_replacement: '8'
844
939
 
845
940
  - regex: '(Windows NT 6\.3; ARM;)'
846
- os_replacement: 'Windows RT 8.1'
941
+ os_replacement: 'Windows'
942
+ os_v1_replacement: 'RT 8.1'
943
+
847
944
  - regex: '(Windows NT 6\.3)'
848
- os_replacement: 'Windows 8.1'
945
+ os_replacement: 'Windows'
946
+ os_v1_replacement: '8.1'
849
947
 
850
948
  - regex: '(Windows NT 6\.4)'
851
- os_replacement: 'Windows 10'
949
+ os_replacement: 'Windows'
950
+ os_v1_replacement: '10'
951
+
852
952
  - regex: '(Windows NT 10\.0)'
853
- os_replacement: 'Windows 10'
953
+ os_replacement: 'Windows'
954
+ os_v1_replacement: '10'
854
955
 
855
956
  - regex: '(Windows NT 5\.0)'
856
- os_replacement: 'Windows 2000'
957
+ os_replacement: 'Windows'
958
+ os_v1_replacement: '2000'
857
959
 
858
960
  - regex: '(WinNT4.0)'
859
- os_replacement: 'Windows NT 4.0'
961
+ os_replacement: 'Windows'
962
+ os_v1_replacement: 'NT 4.0'
860
963
 
861
964
  - regex: '(Windows ?CE)'
862
- os_replacement: 'Windows CE'
965
+ os_replacement: 'Windows'
966
+ os_v1_replacement: 'CE'
863
967
 
864
968
  - regex: 'Win ?(95|98|3.1|NT|ME|2000)'
865
- os_replacement: 'Windows $1'
969
+ os_replacement: 'Windows'
970
+ os_v1_replacement: '$1'
866
971
 
867
972
  - regex: 'Win16'
868
- os_replacement: 'Windows 3.1'
973
+ os_replacement: 'Windows'
974
+ os_v1_replacement: '3.1'
869
975
 
870
976
  - regex: 'Win32'
871
- os_replacement: 'Windows 95'
977
+ os_replacement: 'Windows'
978
+ os_v1_replacement: '95'
979
+
980
+ # Box apps (Drive, Sync, Notes) on Windows https://www.box.com/resources/downloads
981
+ - regex: '^Box.*Windows/([\d.]+);'
982
+ os_replacement: 'Windows'
983
+ os_v1_replacement: '$1'
872
984
 
873
985
  ##########
874
986
  # Tizen OS from Samsung
@@ -883,6 +995,11 @@ os_parsers:
883
995
  ##########
884
996
  - regex: '((?:Mac[ +]?|; )OS[ +]X)[\s+/](?:(\d+)[_.](\d+)(?:[_.](\d+))?|Mach-O)'
885
997
  os_replacement: 'Mac OS X'
998
+ - regex: '(\w+\s+Mac OS X\s+\w+\s+(\d+).(\d+).(\d+).*)'
999
+ os_replacement: 'Mac OS X'
1000
+ os_v1_replacement: '$2'
1001
+ os_v2_replacement: '$3'
1002
+ os_v3_replacement: '$4'
886
1003
  # Leopard
887
1004
  - regex: ' (Dar)(win)/(9).(\d+).*\((?:i386|x86_64|Power Macintosh)\)'
888
1005
  os_replacement: 'Mac OS X'
@@ -920,6 +1037,10 @@ os_parsers:
920
1037
  # ios devices spoof (mac os x), so including intel/ppc prefixes
921
1038
  - regex: '(?:PPC|Intel) (Mac OS X)'
922
1039
 
1040
+ # Box Drive and Box Sync on Mac OS X use OSX version numbers, not Darwin
1041
+ - regex: '^Box.*;(Darwin)/(10)\.(1\d)(?:\.(\d+))?'
1042
+ os_replacement: 'Mac OS X'
1043
+
923
1044
  ##########
924
1045
  # iOS
925
1046
  # http://en.wikipedia.org/wiki/IOS_version_history
@@ -979,9 +1100,29 @@ os_parsers:
979
1100
  os_replacement: 'Mac OS X'
980
1101
  os_v1_replacement: '10'
981
1102
  os_v2_replacement: '11'
1103
+ - regex: 'CFNetwork/7.* Darwin/15\.4\.\d+'
1104
+ os_replacement: 'iOS'
1105
+ os_v1_replacement: '9'
1106
+ os_v2_replacement: '3'
1107
+ os_v3_replacement: '1'
1108
+ - regex: 'CFNetwork/7.* Darwin/15\.5\.\d+'
1109
+ os_replacement: 'iOS'
1110
+ os_v1_replacement: '9'
1111
+ os_v2_replacement: '3'
1112
+ os_v3_replacement: '2'
1113
+ - regex: 'CFNetwork/7.* Darwin/15\.6\.\d+'
1114
+ os_replacement: 'iOS'
1115
+ os_v1_replacement: '9'
1116
+ os_v2_replacement: '3'
1117
+ os_v3_replacement: '5'
982
1118
  - regex: '(CF)(Network)/758\.(\d)'
983
1119
  os_replacement: 'iOS'
984
1120
  os_v1_replacement: '9'
1121
+ - regex: 'CFNetwork/808\.3 Darwin/16\.3\.\d+'
1122
+ os_replacement: 'iOS'
1123
+ os_v1_replacement: '10'
1124
+ os_v2_replacement: '2'
1125
+ os_v3_replacement: '1'
985
1126
  - regex: '(CF)(Network)/808\.(\d)'
986
1127
  os_replacement: 'iOS'
987
1128
  os_v1_replacement: '10'
@@ -990,6 +1131,10 @@ os_parsers:
990
1131
  # CFNetwork macOS Apps (must be before CFNetwork iOS Apps
991
1132
  # @ref: https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history
992
1133
  ##########
1134
+ - regex: 'CFNetwork/.* Darwin/17\.\d+.*\(x86_64\)'
1135
+ os_replacement: 'Mac OS X'
1136
+ os_v1_replacement: '10'
1137
+ os_v2_replacement: '13'
993
1138
  - regex: 'CFNetwork/.* Darwin/16\.\d+.*\(x86_64\)'
994
1139
  os_replacement: 'Mac OS X'
995
1140
  os_v1_replacement: '10'
@@ -1025,14 +1170,76 @@ os_parsers:
1025
1170
  os_replacement: 'iOS'
1026
1171
  os_v1_replacement: '9'
1027
1172
  os_v2_replacement: '0'
1173
+ - regex: 'CFNetwork/8.* Darwin/16\.5\.\d+'
1174
+ os_replacement: 'iOS'
1175
+ os_v1_replacement: '10'
1176
+ os_v2_replacement: '3'
1177
+ - regex: 'CFNetwork/8.* Darwin/16\.6\.\d+'
1178
+ os_replacement: 'iOS'
1179
+ os_v1_replacement: '10'
1180
+ os_v2_replacement: '3'
1181
+ os_v3_replacement: '2'
1182
+ - regex: 'CFNetwork/8.* Darwin/16\.7\.\d+'
1183
+ os_replacement: 'iOS'
1184
+ os_v1_replacement: '10'
1185
+ os_v2_replacement: '3'
1186
+ os_v3_replacement: '3'
1028
1187
  - regex: 'CFNetwork/8.* Darwin/(16)\.\d+'
1029
1188
  os_replacement: 'iOS'
1030
1189
  os_v1_replacement: '10'
1190
+ - regex: 'CFNetwork/8.* Darwin/17\.0\.\d+'
1191
+ os_replacement: 'iOS'
1192
+ os_v1_replacement: '11'
1193
+ os_v2_replacement: '0'
1194
+ - regex: 'CFNetwork/8.* Darwin/17\.2\.\d+'
1195
+ os_replacement: 'iOS'
1196
+ os_v1_replacement: '11'
1197
+ os_v2_replacement: '1'
1198
+ - regex: 'CFNetwork/8.* Darwin/17\.3\.\d+'
1199
+ os_replacement: 'iOS'
1200
+ os_v1_replacement: '11'
1201
+ os_v2_replacement: '2'
1202
+ - regex: 'CFNetwork/8.* Darwin/17\.4\.\d+'
1203
+ os_replacement: 'iOS'
1204
+ os_v1_replacement: '11'
1205
+ os_v2_replacement: '2'
1206
+ os_v3_replacement: '6'
1207
+ - regex: 'CFNetwork/8.* Darwin/17\.5\.\d+'
1208
+ os_replacement: 'iOS'
1209
+ os_v1_replacement: '11'
1210
+ os_v2_replacement: '3'
1211
+ - regex: 'CFNetwork/9.* Darwin/17\.6\.\d+'
1212
+ os_replacement: 'iOS'
1213
+ os_v1_replacement: '11'
1214
+ os_v2_replacement: '4'
1215
+ - regex: 'CFNetwork/9.* Darwin/17\.7\.\d+'
1216
+ os_replacement: 'iOS'
1217
+ os_v1_replacement: '11'
1218
+ os_v2_replacement: '4'
1219
+ os_v3_replacement: '1'
1220
+ - regex: 'CFNetwork/8.* Darwin/(17)\.\d+'
1221
+ os_replacement: 'iOS'
1222
+ os_v1_replacement: '11'
1223
+ - regex: 'CFNetwork/9.* Darwin/18\.0\.\d+'
1224
+ os_replacement: 'iOS'
1225
+ os_v1_replacement: '12'
1226
+ os_v2_replacement: '0'
1227
+ - regex: 'CFNetwork/9.* Darwin/(18)\.\d+'
1228
+ os_replacement: 'iOS'
1229
+ os_v1_replacement: '12'
1230
+ - regex: 'CFNetwork/.* Darwin/'
1231
+ os_replacement: 'iOS'
1232
+
1031
1233
  # iOS Apps
1032
1234
  - regex: '\b(iOS[ /]|iOS; |iPhone(?:/| v|[ _]OS[/,]|; | OS : |\d,\d/|\d,\d; )|iPad/)(\d{1,2})[_\.](\d{1,2})(?:[_\.](\d+))?'
1033
1235
  os_replacement: 'iOS'
1034
1236
  - regex: '\((iOS);'
1035
1237
 
1238
+ ##########################
1239
+ # Outlook on iOS >= 2.62.0
1240
+ ##########################
1241
+ - regex: 'Outlook-(iOS)/\d+\.\d+\.prod\.iphone'
1242
+
1036
1243
  ##########
1037
1244
  # Apple TV
1038
1245
  ##########
@@ -1189,6 +1396,13 @@ os_parsers:
1189
1396
  - regex: '(Linux)(?:[ /](\d+)\.(\d+)(?:\.(\d+))?)?'
1190
1397
  - regex: 'SunOS'
1191
1398
  os_replacement: 'Solaris'
1399
+ # Wget/x.x.x (linux-gnu)
1400
+ - regex: '\(linux-gnu\)'
1401
+ os_replacement: 'Linux'
1402
+ - regex: '\(x86_64-redhat-linux-gnu\)'
1403
+ os_replacement: 'Red Hat'
1404
+ - regex: '\((freebsd)(\d+)\.(\d+)\)'
1405
+ os_replacement: 'FreeBSD'
1192
1406
 
1193
1407
  # Roku Digital-Video-Players https://www.roku.com/
1194
1408
  - regex: '^(Roku)/DVP-(\d+)\.(\d+)'
@@ -2098,7 +2312,7 @@ device_parsers:
2098
2312
  device_replacement: '$1'
2099
2313
  brand_replacement: 'Google'
2100
2314
  model_replacement: '$1'
2101
- - regex: '; *(Pixel \w+) Build'
2315
+ - regex: '; *(Pixel.*) Build'
2102
2316
  device_replacement: '$1'
2103
2317
  brand_replacement: 'Google'
2104
2318
  model_replacement: '$1'
@@ -2374,7 +2588,7 @@ device_parsers:
2374
2588
  device_replacement: '$1 $2'
2375
2589
  brand_replacement: 'iBall'
2376
2590
  model_replacement: '$1 $2'
2377
- - regex: '; *(IBall)(?:[ _]([^;/]+)|) Build'
2591
+ - regex: '; *(IBall)(?:[ _]([^;/]+))? Build'
2378
2592
  regex_flag: 'i'
2379
2593
  device_replacement: '$1 $2'
2380
2594
  brand_replacement: 'iBall'
@@ -3449,6 +3663,10 @@ device_parsers:
3449
3663
  device_replacement: '$1'
3450
3664
  brand_replacement: 'RCA'
3451
3665
  model_replacement: '$1'
3666
+ - regex: '; RCA (\w+) Build/'
3667
+ device_replacement: 'RCA $1'
3668
+ brand_replacement: 'RCA'
3669
+ model_replacement: '$1'
3452
3670
 
3453
3671
  #########
3454
3672
  # Rockchip
@@ -4088,7 +4306,7 @@ device_parsers:
4088
4306
  # XiaoMi
4089
4307
  # @ref: http://www.xiaomi.com/event/buyphone
4090
4308
  #########
4091
- - regex: '; *((MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) (Build|MIUI)/'
4309
+ - regex: '; *((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) (Build|MIUI)/'
4092
4310
  device_replacement: 'XiaoMi $1'
4093
4311
  brand_replacement: 'XiaoMi'
4094
4312
  model_replacement: '$1'
@@ -4550,6 +4768,18 @@ device_parsers:
4550
4768
  device_replacement: '$1'
4551
4769
  brand_replacement: 'Apple'
4552
4770
  model_replacement: '$1'
4771
+ - regex: '(Apple Watch)(?:;| Simulator;)'
4772
+ device_replacement: '$1'
4773
+ brand_replacement: 'Apple'
4774
+ model_replacement: '$1'
4775
+ - regex: '(HomePod)(?:;| Simulator;)'
4776
+ device_replacement: '$1'
4777
+ brand_replacement: 'Apple'
4778
+ model_replacement: '$1'
4779
+ - regex: 'iPhone'
4780
+ device_replacement: 'iPhone'
4781
+ brand_replacement: 'Apple'
4782
+ model_replacement: 'iPhone'
4553
4783
  # @note: desktop applications show device info
4554
4784
  - regex: 'CFNetwork/.* Darwin/\d.*\(((?:Mac|iMac|PowerMac|PowerBook)[^\d]*)(\d+)(?:,|%2C)(\d+)'
4555
4785
  device_replacement: '$1$2,$3'
@@ -4567,6 +4797,14 @@ device_parsers:
4567
4797
  brand_replacement: 'Apple'
4568
4798
  model_replacement: 'iOS-Device'
4569
4799
 
4800
+ ##########################
4801
+ # Outlook on iOS >= 2.62.0
4802
+ ##########################
4803
+ - regex: 'Outlook-(iOS)/\d+\.\d+\.prod\.iphone'
4804
+ brand_replacement: 'Apple'
4805
+ device_replacement: 'iPhone'
4806
+ model_replacement: 'iPhone'
4807
+
4570
4808
  ##########
4571
4809
  # Acer
4572
4810
  ##########
@@ -4717,7 +4955,7 @@ device_parsers:
4717
4955
  ##########
4718
4956
  # LGE NetCast TV
4719
4957
  ##########
4720
- - regex: 'LGE; (?:Media\/)?([^;]*);[^;]*;[^;]*;?\); "?LG NetCast(\.TV|\.Media|)-\d+'
4958
+ - regex: 'LGE; (?:Media\/)?([^;]*);[^;]*;[^;]*;?\); "?LG NetCast(\.TV|\.Media)?-\d+'
4721
4959
  device_replacement: 'NetCast$2'
4722
4960
  brand_replacement: 'LG'
4723
4961
  model_replacement: '$1'
@@ -4988,7 +5226,7 @@ device_parsers:
4988
5226
  ##########
4989
5227
  # Spiders (this is hack...)
4990
5228
  ##########
4991
- - regex: '(bot|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.*/\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google Web Preview|Qwantify)'
5229
+ - regex: '(bot|BUbiNG|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Daum|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.*/\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google Web Preview|Qwantify|Yeti)'
4992
5230
  regex_flag: 'i'
4993
5231
  device_replacement: 'Spider'
4994
5232
  brand_replacement: 'Spider'
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: user_agent_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Lucas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-02 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: coveralls
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '='
18
- - !ruby/object:Gem::Version
19
- version: 0.8.21
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - '='
25
- - !ruby/object:Gem::Version
26
- version: 0.8.21
11
+ date: 2018-10-10 00:00:00.000000000 Z
12
+ dependencies: []
27
13
  description: A simple, comprehensive Ruby gem for parsing user agent strings with
28
14
  the help of BrowserScope's UA database
29
15
  email: t@toolmantim.com
@@ -54,7 +40,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
40
  requirements:
55
41
  - - ">="
56
42
  - !ruby/object:Gem::Version
57
- version: 1.8.7
43
+ version: '2.3'
58
44
  required_rubygems_version: !ruby/object:Gem::Requirement
59
45
  requirements:
60
46
  - - ">="
@@ -62,7 +48,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
48
  version: '0'
63
49
  requirements: []
64
50
  rubyforge_project:
65
- rubygems_version: 2.6.12
51
+ rubygems_version: 2.7.4
66
52
  signing_key:
67
53
  specification_version: 4
68
54
  summary: A simple, comprehensive Ruby gem for parsing user agent strings with the