user_agent_parser 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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