├── .circleci └── config.yml ├── .dockerignore ├── .gitignore ├── .gitmodules ├── .luacheckrc ├── Dockerfile ├── Makefile ├── README.md ├── lib └── resty │ ├── woothee.lua │ └── woothee │ ├── appliance.lua │ ├── browser.lua │ ├── crawler.lua │ ├── dataset.lua │ ├── misc.lua │ ├── mobile_phone.lua │ ├── os.lua │ └── util.lua ├── lua-resty-woothee-1.12.0-1.rockspec ├── maint ├── dataset_yaml2lua.lua └── testset_yaml2lua.lua ├── t ├── 00_compile.t ├── 01_version.t ├── 02_dataset.t ├── 03_is_crawler.t ├── 10_testfile_crawler.t ├── 11_testfile_crawler_google.t ├── 12_testfile_pc_windows.t ├── 13_testfile_pc_misc.t ├── 14_testfile_mobilephone_docomo.t ├── 15_testfile_mobilephone_au.t ├── 16_testfile_mobilephone_softbank.t ├── 17_testfile_mobilephone_willcom.t ├── 18_testfile_mobilephone_misc.t ├── 19_testfile_smartphone_ios.t ├── 20_testfile_smartphone_android.t ├── 21_testfile_smartphone_misc.t ├── 22_testfile_appliance.t ├── 23_testfile_pc_lowpriority.t ├── 24_testfile_misc.t ├── 25_testfile_crawler_nonmajor.t ├── lib │ └── debug.lua └── testfiles │ └── .gitkeep └── xt ├── lib └── debug.lua └── xx_debug.t /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | - image: docker:18.06.3-ce-git 6 | 7 | steps: 8 | - checkout 9 | - setup_remote_docker 10 | - run: | 11 | docker build -t lua-resty-woothee:$CIRCLE_SHA1 . 12 | docker run lua-resty-woothee:$CIRCLE_SHA1 sh -c 'make all' 13 | workflows: 14 | version: 2 15 | test: 16 | jobs: 17 | - test 18 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | debug* 2 | tmp* 3 | test.lua 4 | memo 5 | .envrc 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | t/servroot/ 2 | t/testfiles/*.t 3 | debug.log 4 | tmp 5 | /*.src.rock 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "woothee"] 2 | path = woothee 3 | url = https://github.com/woothee/woothee.git 4 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | ignore = {"113", "211", "231", "411", "421", "542", "631"} 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openresty/openresty:1.15.8.1-0-bionic 2 | 3 | RUN apt-get -q -y update \ 4 | && apt-get -q -y install perl libyaml-dev libyaml-perl \ 5 | && apt-get -q -y install git wget 6 | 7 | # cpan 8 | RUN wget http://xrl.us/cpanm 9 | RUN perl cpanm --notest Test::Nginx 10 | 11 | RUN ln -s /usr/local/openresty/luajit/bin/luajit /usr/local/openresty/luajit/bin/lua 12 | ENV PATH $PATH:/opt/openresty/nginx/sbin:/usr/local/openresty/luajit/bin 13 | 14 | # luarocks 15 | RUN luarocks install lyaml YAML_LIBDIR=/usr/lib/x86_64-linux-gnu/ 16 | RUN luarocks install etlua 17 | RUN luarocks install luacheck 18 | 19 | ADD . /code/ 20 | 21 | WORKDIR /code 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LUA_PATH="./lib/?.lua;;" 2 | 3 | all: test 4 | 5 | local-all: docker-build docker-run-test 6 | 7 | testsets: 8 | git submodule update --init 9 | 10 | testfiles: testsets 11 | LUA_PATH=$(LUA_PATH) lua maint/testset_yaml2lua.lua 12 | sync; sync; sync; 13 | 14 | lib/resty/woothee/dataset.lua: testfiles 15 | LUA_PATH=$(LUA_PATH) lua maint/dataset_yaml2lua.lua 16 | sync; sync; sync; 17 | 18 | test: lib/resty/woothee/dataset.lua 19 | luacheck --codes lib 20 | prove -Ilib 21 | 22 | docker-build: 23 | docker build -t local/lua-resty-woothee . 24 | 25 | docker-run-test: 26 | docker run local/lua-resty-woothee sh -c 'make all' 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/woothee/lua-resty-woothee.svg?style=svg)](https://circleci.com/gh/woothee/lua-resty-woothee) 2 | 3 | # Woothee lua resty 4 | 5 | The Lua-Openresty implementation of Project Woothee, which is multi-language user-agent strings parsers. 6 | 7 | https://github.com/woothee/woothee 8 | 9 | ## Installation 10 | 11 | ``` 12 | luarocks install lua-resty-woothee 13 | ``` 14 | 15 | 16 | ## Synopsis 17 | 18 | #### Basic Usage 19 | 20 | ```lua 21 | server { 22 | location /test { 23 | content_by_lua_block { 24 | local woothee = require "resty.woothee" 25 | 26 | -- parse 27 | local r = woothee.parse(ngx.var.http_user_agent) 28 | -- => {"name": "xxx", "category": "xxx", "os": "xxx", "version": "xxx", "vendor": "xxx"} 29 | 30 | -- crawler? 31 | local crawler = woothee.is_crawler(ngx.var.http_user_agent) 32 | -- => true 33 | 34 | ngx.header.content_type = "text/plain" 35 | ngx.say(r.name) 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | #### Include Nginx Log 42 | 43 | ```lua 44 | log_format woothee_format 45 | '$remote_addr - $remote_user [$time_local] ' 46 | '"$request" $status $body_bytes_sent ' 47 | '"$http_referer" "$http_user_agent" ' 48 | '"$x_wt_name" "$x_wt_category" "$x_wt_os" "$x_wt_version" "$x_wt_vendor" "$x_wt_os_version"' 49 | ; 50 | 51 | server { 52 | 53 | access_log /var/log/nginx/nginx-access-woothee.log woothee_format; 54 | 55 | # set nginx valiables 56 | set $x_wt_name '-'; 57 | set $x_wt_category '-'; 58 | set $x_wt_os '-'; 59 | set $x_wt_version '-'; 60 | set $x_wt_vendor '-'; 61 | set $x_wt_os_version '-'; 62 | 63 | location /test { 64 | content_by_lua_block { 65 | local woothee = require "resty.woothee" 66 | local r = woothee.parse(ngx.var.http_user_agent) 67 | -- set nginx valiables 68 | ngx.var.x_wt_name = r.name 69 | ngx.var.x_wt_category = r.category 70 | ngx.var.x_wt_os = r.os 71 | ngx.var.x_wt_version = r.version 72 | ngx.var.x_wt_vendor = r.vendor 73 | ngx.var.x_wt_os_version = r.os_version 74 | 75 | ngx.header.content_type = "text/plain" 76 | ngx.say(r.name) 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | #### Forward Backend Server 83 | 84 | ```lua 85 | server { 86 | 87 | # set nginx valiables 88 | set $x_wt_name '-'; 89 | set $x_wt_category '-'; 90 | set $x_wt_os '-'; 91 | set $x_wt_version '-'; 92 | set $x_wt_vendor '-'; 93 | set $x_wt_os_version '-'; 94 | 95 | location /test { 96 | rewrite_by_lua_block { 97 | local woothee = require "resty.woothee" 98 | local r = woothee.parse(ngx.var.http_user_agent) 99 | -- set nginx valiables 100 | ngx.var.x_wt_name = r.name 101 | ngx.var.x_wt_category = r.category 102 | ngx.var.x_wt_os = r.os 103 | ngx.var.x_wt_version = r.version 104 | ngx.var.x_wt_vendor = r.vendor 105 | ngx.var.x_wt_os_version = r.os_version 106 | } 107 | 108 | proxy_pass http://backend-server/; 109 | # proxy set header 110 | proxy_set_header X-WT-NAME $x_wt_name; 111 | proxy_set_header X-WT-CATEGORY $x_wt_category; 112 | proxy_set_header X-WT-OS $x_wt_os; 113 | proxy_set_header X-WT-VERSION $x_wt_version; 114 | proxy_set_header X-WT-VENDOR $x_wt_vendor; 115 | proxy_set_header X-WT-OS-VERSION $x_wt_os_version; 116 | } 117 | } 118 | ``` 119 | 120 | ## For Developer (on Docker) 121 | 122 | #### docker run & run test 123 | 124 | ``` 125 | make local-all 126 | ``` 127 | 128 | * * * * * 129 | 130 | ## Authors 131 | 132 | * TSUYOSHI TORII (toritori0318) 133 | 134 | ## License 135 | 136 | Copyright 2014- TSUYOSHI TORII (toritori0318) 137 | 138 | Licensed under the Apache License, Version 2.0 (the "License"); 139 | you may not use this file except in compliance with the License. 140 | You may obtain a copy of the License at 141 | 142 | http://www.apache.org/licenses/LICENSE-2.0 143 | 144 | Unless required by applicable law or agreed to in writing, software 145 | distributed under the License is distributed on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 147 | See the License for the specific language governing permissions and 148 | limitations under the License. 149 | -------------------------------------------------------------------------------- /lib/resty/woothee.lua: -------------------------------------------------------------------------------- 1 | local _M = { _VERSION = "1.12.0-1" } 2 | 3 | local dataset = require('resty.woothee.dataset') 4 | local browser = require('resty.woothee.browser') 5 | local os = require('resty.woothee.os') 6 | local mobile_phone = require('resty.woothee.mobile_phone') 7 | local crawler = require('resty.woothee.crawler') 8 | local appliance = require('resty.woothee.appliance') 9 | local misc = require('resty.woothee.misc') 10 | local util = require('resty.woothee.util') 11 | 12 | local function try_crawler(useragent, result) 13 | if crawler.challenge_google(useragent, result) then 14 | return true 15 | end 16 | 17 | if crawler.challenge_crawlers(useragent, result) then 18 | return true 19 | end 20 | 21 | return false 22 | end 23 | 24 | local function try_browser(useragent, result) 25 | if browser.challenge_MSIE(useragent, result) then 26 | return true 27 | end 28 | 29 | if browser.challenge_vivaldi(useragent, result) then 30 | return true 31 | end 32 | 33 | if browser.challenge_yandexbrowser(useragent, result) then 34 | return true 35 | end 36 | 37 | if browser.challenge_samsung(useragent, result) then 38 | return true 39 | end 40 | 41 | if browser.challenge_safari_chrome(useragent, result) then 42 | return true 43 | end 44 | 45 | if browser.challenge_firefox(useragent, result) then 46 | return true 47 | end 48 | 49 | if browser.challenge_opera(useragent, result) then 50 | return true 51 | end 52 | 53 | if browser.challenge_webview(useragent, result) then 54 | return true 55 | end 56 | 57 | return false 58 | end 59 | 60 | local function try_os(useragent, result) 61 | if os.challenge_windows(useragent, result) then 62 | return true 63 | end 64 | 65 | -- OSX PC and iOS devices (strict check) 66 | if os.challenge_osx(useragent, result) then 67 | return true 68 | end 69 | 70 | -- Linux PC and Android 71 | if os.challenge_linux(useragent, result) then 72 | return true 73 | end 74 | 75 | -- all useragents matches /(iPhone|iPad|iPod|Android|BlackBerry)/ 76 | if os.challenge_smart_phone(useragent, result) then 77 | return true 78 | end 79 | 80 | -- mobile phones like KDDI-.* 81 | if os.challenge_mobile_phone(useragent, result) then 82 | return true 83 | end 84 | 85 | -- Nintendo DSi/Wii with Opera 86 | if os.challenge_appliance(useragent, result) then 87 | return true 88 | end 89 | 90 | if os.challenge_misc(useragent, result) then 91 | return true 92 | end 93 | 94 | return false 95 | end 96 | 97 | local function try_mobile_phone(useragent, result) 98 | if mobile_phone.challenge_docomo(useragent, result) then 99 | return true 100 | end 101 | 102 | if mobile_phone.challenge_au(useragent, result) then 103 | return true 104 | end 105 | 106 | if mobile_phone.challenge_softbank(useragent, result) then 107 | return true 108 | end 109 | 110 | if mobile_phone.challenge_willcom(useragent, result) then 111 | return true 112 | end 113 | 114 | if mobile_phone.challenge_misc(useragent, result) then 115 | return true 116 | end 117 | 118 | return false 119 | end 120 | 121 | local function try_appliance(useragent, result) 122 | if appliance.challenge_playstation(useragent, result) then 123 | return true 124 | end 125 | 126 | if appliance.challenge_nintendo(useragent, result) then 127 | return true 128 | end 129 | 130 | if appliance.challenge_digitalTV(useragent, result) then 131 | return true 132 | end 133 | 134 | return false 135 | end 136 | 137 | local function try_misc(useragent, result) 138 | if misc.challenge_desktop_tools(useragent, result) then 139 | return true 140 | end 141 | 142 | return false 143 | end 144 | 145 | local function try_rare_cases(useragent, result) 146 | if misc.challenge_smart_phone_patterns(useragent, result) then 147 | return true 148 | end 149 | 150 | if browser.challenge_sleipnir(useragent, result) then 151 | return true 152 | end 153 | 154 | if misc.challenge_http_library(useragent, result) then 155 | return true 156 | end 157 | 158 | if misc.challenge_maybe_rss_reader(useragent, result) then 159 | return true 160 | end 161 | 162 | if crawler.challenge_maybe_crawler(useragent, result) then 163 | return true 164 | end 165 | 166 | return false 167 | end 168 | 169 | local function exec_parse(useragent) 170 | local result = {} 171 | 172 | if string.len(useragent) < 1 or useragent == '-' then 173 | return result; 174 | end 175 | 176 | if try_crawler(useragent, result) then 177 | return result; 178 | end 179 | 180 | if try_browser(useragent, result) then 181 | if try_os(useragent, result) then 182 | return result; 183 | else 184 | return result; 185 | end 186 | end 187 | 188 | if try_mobile_phone(useragent, result) then 189 | return result; 190 | end 191 | 192 | if try_appliance(useragent, result) then 193 | return result; 194 | end 195 | 196 | if try_misc(useragent, result) then 197 | return result; 198 | end 199 | 200 | -- browser unknown, check os only 201 | if try_os(useragent, result) then 202 | return result; 203 | end 204 | 205 | if try_rare_cases(useragent, result) then 206 | return result; 207 | end 208 | 209 | return result; 210 | end 211 | 212 | local function fill_result(result) 213 | if not result[dataset.ATTRIBUTE_NAME] then 214 | result[dataset.ATTRIBUTE_NAME] = dataset.VALUE_UNKNOWN; 215 | end 216 | if not result[dataset.ATTRIBUTE_CATEGORY] then 217 | result[dataset.ATTRIBUTE_CATEGORY] = dataset.VALUE_UNKNOWN; 218 | end 219 | if not result[dataset.ATTRIBUTE_OS] then 220 | result[dataset.ATTRIBUTE_OS] = dataset.VALUE_UNKNOWN; 221 | end 222 | if not result[dataset.ATTRIBUTE_OS_VERSION] then 223 | result[dataset.ATTRIBUTE_OS_VERSION] = dataset.VALUE_UNKNOWN; 224 | end 225 | if not result[dataset.ATTRIBUTE_VERSION] then 226 | result[dataset.ATTRIBUTE_VERSION] = dataset.VALUE_UNKNOWN; 227 | end 228 | if not result[dataset.ATTRIBUTE_VENDOR] then 229 | result[dataset.ATTRIBUTE_VENDOR] = dataset.VALUE_UNKNOWN; 230 | end 231 | return result 232 | end 233 | 234 | 235 | 236 | function _M.parse(useragent) 237 | useragent = useragent or '' 238 | return fill_result(exec_parse(useragent)) 239 | end 240 | 241 | function _M.is_crawler(useragent) 242 | useragent = useragent or '' 243 | if try_crawler(useragent, {}) then 244 | return true 245 | end 246 | 247 | return false 248 | end 249 | 250 | return _M 251 | -------------------------------------------------------------------------------- /lib/resty/woothee/appliance.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local util = require('resty.woothee.util') 4 | local dataset = require('resty.woothee.dataset') 5 | 6 | function _M.challenge_playstation(ua, result) 7 | local data, os_version, match, err = nil 8 | 9 | if string.find(ua, 'PSP (PlayStation Portable);', 1, true) then 10 | data = dataset.get('PSP') 11 | match, err = ngx.re.match(ua, [[PSP \(PlayStation Portable\); ([.0-9]+)\)]], "o") 12 | if match then 13 | os_version = match[1] 14 | end 15 | elseif string.find(ua, 'PlayStation Vita', 1, true) then 16 | data = dataset.get('PSVita') 17 | match, err = ngx.re.match(ua, [[PlayStation Vita ([.0-9]+)\)]], "o") 18 | if match then 19 | os_version = match[1] 20 | end 21 | elseif string.find(ua, 'PLAYSTATION 3 ', 1, true) or string.find(ua, 'PLAYSTATION 3;', 1, true) then 22 | data = dataset.get('PS3') 23 | match, err = ngx.re.match(ua, [[PLAYSTATION 3;? ([.0-9]+)\)]], "o") 24 | if match then 25 | os_version = match[1] 26 | end 27 | elseif string.find(ua, 'PlayStation 4 ', 1, true) then 28 | data = dataset.get('PS4') 29 | match, err = ngx.re.match(ua, [[PlayStation 4 ([.0-9]+)\)]], "o") 30 | if match then 31 | os_version = match[1] 32 | end 33 | end 34 | 35 | if not data then 36 | return false 37 | end 38 | 39 | util.update_map(result, data) 40 | if os_version then 41 | util.update_os_version(result, os_version); 42 | end 43 | return true 44 | end 45 | 46 | function _M.challenge_nintendo(ua, result) 47 | local data = nil 48 | 49 | if string.find(ua, 'Nintendo 3DS;', 1, true) then 50 | data = dataset.get('Nintendo3DS') 51 | elseif string.find(ua, 'Nintendo DSi;', 1, true) then 52 | data = dataset.get('NintendoDSi') 53 | elseif string.find(ua, 'Nintendo Wii;', 1, true) then 54 | data = dataset.get('NintendoWii') 55 | elseif string.find(ua, '(Nintendo WiiU)', 1, true) then 56 | data = dataset.get('NintendoWiiU') 57 | end 58 | 59 | if not data then 60 | return false 61 | end 62 | 63 | util.update_map(result, data) 64 | return true 65 | end 66 | 67 | function _M.challenge_digitalTV(ua, result) 68 | local data = nil 69 | 70 | if string.find(ua, 'InettvBrowser/', 1, true) then 71 | data = dataset.get('DigitalTV') 72 | end 73 | 74 | if not data then 75 | return false 76 | end 77 | 78 | util.update_map(result, data) 79 | return true 80 | end 81 | 82 | return _M 83 | -------------------------------------------------------------------------------- /lib/resty/woothee/browser.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local util = require('resty.woothee.util') 4 | local dataset = require('resty.woothee.dataset') 5 | 6 | function _M.challenge_MSIE(ua, result) 7 | if not string.find(ua, 'compatible; MSIE', 1, true) and not string.find(ua, 'Trident/', 1, true) and not string.find(ua, 'IEMobile/', 1, true) then 8 | return false; 9 | end 10 | 11 | local version = dataset.VALUE_UNKNOWN; 12 | local match, err = ngx.re.match(ua, [[MSIE ([.0-9]+);]], "o") 13 | if match then 14 | version = match[1] 15 | else 16 | local match1, err1 = ngx.re.match(ua, [[Trident/([.0-9]+);]], "o") 17 | local match2, err2 = ngx.re.match(ua, [[ rv:([.0-9]+)]], "o") 18 | if match1 and match2 then 19 | version = match2[1] 20 | else 21 | match, err = ngx.re.match(ua, [[IEMobile/([.0-9]+);]], "o") 22 | if match then 23 | version = match[1] 24 | end 25 | end 26 | end 27 | 28 | util.update_map(result, dataset.get('MSIE')) 29 | util.update_version(result, version) 30 | return true; 31 | end 32 | 33 | 34 | function _M.challenge_safari_chrome(ua, result) 35 | if not string.find(ua, 'Safari/', 1, true) then 36 | return false 37 | elseif string.find(ua, 'Chrome', 1, true) and string.find(ua, 'wv', 1, true) then 38 | return false 39 | elseif string.find(ua, 'Google Web Preview', 1, true) then 40 | -- really???? 41 | return false 42 | end 43 | 44 | local version = dataset.VALUE_UNKNOWN; 45 | local match, err = ngx.re.match(ua, [[(?:Edge|Edg|EdgiOS|EdgA)\/([.0-9]+)]], "o") 46 | if match then 47 | version = match[1] 48 | util.update_map(result, dataset.get('Edge')) 49 | util.update_version(result, version) 50 | return true 51 | end 52 | 53 | local match, err = ngx.re.match(ua, [[FxiOS\/([.0-9]+)]], "o") 54 | if match then 55 | version = match[1] 56 | util.update_map(result, dataset.get('Firefox')) 57 | util.update_version(result, version) 58 | return true 59 | end 60 | 61 | local match, err = ngx.re.match(ua, [[(?:Chrome|CrMo|CriOS)/([.0-9]+)]], "o") 62 | if match then 63 | local match_ob, err = ngx.re.match(ua, [[OPR/([.0-9]+)]], "o") 64 | if match_ob then 65 | -- Opera w/ blink 66 | version = match_ob[1] 67 | util.update_map(result, dataset.get('Opera')) 68 | util.update_version(result, version) 69 | return true 70 | end 71 | 72 | -- Chrome 73 | version = match[1] 74 | util.update_map(result, dataset.get('Chrome')) 75 | util.update_version(result, version) 76 | return true 77 | end 78 | 79 | local match, err = ngx.re.match(ua, [[GSA\/([.0-9]+)]], "o") 80 | if match then 81 | version = match[1] 82 | util.update_map(result, dataset.get('GSA')) 83 | util.update_version(result, version) 84 | return true 85 | end 86 | 87 | local match, err = ngx.re.match(ua, [[Version/([.0-9]+)]], "o") 88 | if match then 89 | version = match[1] 90 | end 91 | util.update_map(result, dataset.get('Safari')) 92 | util.update_version(result, version) 93 | return true; 94 | end 95 | 96 | 97 | function _M.challenge_firefox(ua, result) 98 | if not string.find(ua, 'Firefox/', 1, true) then 99 | return false 100 | end 101 | 102 | local version = dataset.VALUE_UNKNOWN 103 | local match, err = ngx.re.match(ua, [[Firefox/([.0-9]+)]], "o") 104 | if match then 105 | version = match[1] 106 | end 107 | 108 | util.update_map(result, dataset.get('Firefox')) 109 | util.update_version(result, version) 110 | return true 111 | end 112 | 113 | 114 | function _M.challenge_yandexbrowser(ua, result) 115 | if not string.find(ua, 'YaBrowser/', 1, true) then 116 | return false 117 | end 118 | 119 | local version = dataset.VALUE_UNKNOWN 120 | local match, err = ngx.re.match(ua, [[YaBrowser/(\d+\.\d+\.\d+\.\d+)]], "o") 121 | if match then 122 | version = match[1] 123 | end 124 | 125 | util.update_map(result, dataset.get('YaBrowser')) 126 | util.update_version(result, version) 127 | return true 128 | end 129 | 130 | 131 | function _M.challenge_opera(ua, result) 132 | if not string.find(ua, 'Opera', 1, true) then 133 | return false 134 | end 135 | 136 | local version = dataset.VALUE_UNKNOWN 137 | local match, err = ngx.re.match(ua, [[Version/([.0-9]+)]], "o") 138 | if match then 139 | version = match[1] 140 | else 141 | local match, err = ngx.re.match(ua, [[Opera[/ ]([.0-9]+)]], "o") 142 | if match then 143 | version = match[1] 144 | end 145 | end 146 | 147 | util.update_map(result, dataset.get('Opera')) 148 | util.update_version(result, version) 149 | return true; 150 | end 151 | 152 | function _M.challenge_webview(ua, result) 153 | local version = dataset.VALUE_UNKNOWN 154 | 155 | if string.find(ua, 'Chrome', 1, true) and string.find(ua, 'wv', 1, true) then 156 | local match, err = ngx.re.match(ua, [[Version/([.0-9]+)]], "o") 157 | if match then 158 | version = match[1] 159 | end 160 | util.update_map(result, dataset.get('Webview')) 161 | util.update_version(result, version) 162 | return true 163 | end 164 | 165 | local match, err = ngx.re.match(ua, [[iP(?:hone;|ad;|od) (.*)like Mac OS X]], "o") 166 | if match then 167 | if string.find(ua, 'Safari/', 1, true) then 168 | return false 169 | end 170 | 171 | local match, err = ngx.re.match(ua, [[Version/([.0-9]+)]], "o") 172 | if match then 173 | version = match[1] 174 | end 175 | util.update_map(result, dataset.get('Webview')) 176 | util.update_version(result, version) 177 | return true 178 | end 179 | 180 | return false 181 | end 182 | 183 | function _M.challenge_sleipnir(ua, result) 184 | if not string.find(ua, 'Sleipnir/', 1, true) then 185 | return false 186 | end 187 | 188 | local version = dataset.VALUE_UNKNOWN 189 | local match, err = ngx.re.match(ua, [[Sleipnir/([.0-9]+)]], "o") 190 | if match then 191 | version = match[1] 192 | end 193 | util.update_map(result, dataset.get('Sleipnir')) 194 | util.update_version(result, version) 195 | 196 | -- [[ 197 | -- Sleipnir's user-agent doesn't contain Windows version, so put 'Windows UNKNOWN Ver'. 198 | -- Sleipnir is IE component browser, so for Windows only. 199 | -- ]] 200 | 201 | local win = dataset.get('Win') 202 | util.update_category(result, win[dataset.KEY_CATEGORY]) 203 | util.update_os(result, win[dataset.KEY_NAME]) 204 | 205 | return true 206 | end 207 | 208 | function _M.challenge_vivaldi(ua, result) 209 | if not string.find(ua, 'Vivaldi/', 1, true) then 210 | return false 211 | end 212 | 213 | local version = dataset.VALUE_UNKNOWN 214 | local match, err = ngx.re.match(ua, [[Vivaldi/([.0-9]+)]], "o") 215 | if match then 216 | version = match[1] 217 | end 218 | util.update_map(result, dataset.get('Vivaldi')) 219 | util.update_version(result, version) 220 | 221 | return true 222 | end 223 | 224 | function _M.challenge_samsung(ua, result) 225 | if not string.find(ua, 'SamsungBrowser/', 1, true) then 226 | return false 227 | end 228 | 229 | local version = dataset.VALUE_UNKNOWN 230 | local match, err = ngx.re.match(ua, [[SamsungBrowser/([.0-9]+)]], "o") 231 | if match then 232 | version = match[1] 233 | end 234 | util.update_map(result, dataset.get('SamsungBrowser')) 235 | util.update_version(result, version) 236 | 237 | return true 238 | end 239 | 240 | return _M 241 | -------------------------------------------------------------------------------- /lib/resty/woothee/crawler.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local util = require('resty.woothee.util') 4 | local dataset = require('resty.woothee.dataset') 5 | 6 | function _M.challenge_google(ua, result) 7 | if not string.find(ua, 'Google', 1, true) then 8 | return false 9 | end 10 | 11 | if string.find(ua, 'compatible; Googlebot', 1, true) then 12 | if string.find(ua, 'compatible; Googlebot-Mobile', 1, true) then 13 | util.update_map(result, dataset.get('GoogleBotMobile')) 14 | return true 15 | else 16 | util.update_map(result, dataset.get('GoogleBot')) 17 | return true 18 | end 19 | end 20 | 21 | if string.find(ua, 'compatible; AdsBot-Google-Mobile;', 1, true) then 22 | util.update_map(result, dataset.get('AdsBotGoogleMobile')) 23 | return true 24 | end 25 | 26 | if ngx.re.match(ua, [[^AdsBot-Google]], "o") then 27 | util.update_map(result, dataset.get('AdsBotGoogle')) 28 | return true 29 | end 30 | 31 | if string.find(ua, 'Googlebot-Image/', 1, true) then 32 | util.update_map(result, dataset.get('GoogleBot')) 33 | return true 34 | end 35 | 36 | if string.find(ua, 'Mediapartners-Google', 1, true) then 37 | if string.find(ua, 'compatible; Mediapartners-Google', 1, true) or ua == 'Mediapartners-Google' then 38 | util.update_map(result, dataset.get('GoogleMediaPartners')) 39 | return true 40 | end 41 | end 42 | 43 | if string.find(ua, 'Feedfetcher-Google;', 1, true) then 44 | util.update_map(result, dataset.get('GoogleFeedFetcher')) 45 | return true 46 | end 47 | 48 | if string.find(ua, 'AppEngine-Google', 1, true) then 49 | util.update_map(result, dataset.get('GoogleAppEngine')) 50 | return true 51 | end 52 | 53 | if string.find(ua, 'Google Web Preview', 1, true) then 54 | util.update_map(result, dataset.get('GoogleWebPreview')) 55 | return true 56 | end 57 | 58 | return false 59 | end 60 | 61 | 62 | function _M.challenge_crawlers(ua, result) 63 | if string.find(ua, 'Yahoo', 1, true) or string.find(ua, 'help.yahoo.co.jp/help/jp/', 1, true) or string.find(ua, 'listing.yahoo.co.jp/support/faq/', 1, true) then 64 | if string.find(ua, 'compatible; Yahoo! Slurp', 1, true) then 65 | util.update_map(result, dataset.get('YahooSlurp')) 66 | return true 67 | end 68 | if string.find(ua, 'YahooFeedSeekerJp', 1, true) or string.find(ua, 'YahooFeedSeekerBetaJp', 1, true) then 69 | util.update_map(result, dataset.get('YahooJP')) 70 | return true 71 | end 72 | if string.find(ua, 'crawler (http://listing.yahoo.co.jp/support/faq/', 1, true) or string.find(ua, 'crawler (http://help.yahoo.co.jp/help/jp/', 1, true) then 73 | util.update_map(result, dataset.get('YahooJP')) 74 | return true 75 | end 76 | if string.find(ua, 'Y!J-BRZ/YATSHA crawler', 1, true) or string.find(ua, 'Y!J-BRY/YATSH crawler', 1, true) then 77 | util.update_map(result, dataset.get('YahooJP')) 78 | return true 79 | end 80 | if string.find(ua, 'Yahoo Pipes', 1, true) then 81 | util.update_map(result, dataset.get('YahooPipes')) 82 | return true 83 | end 84 | end 85 | 86 | if string.find(ua, 'msnbot', 1, true) then 87 | util.update_map(result, dataset.get('msnbot')) 88 | return true 89 | end 90 | 91 | if string.find(ua, 'bingbot', 1, true) then 92 | if string.find(ua, 'compatible; bingbot', 1, true) then 93 | util.update_map(result, dataset.get('bingbot')) 94 | return true 95 | end 96 | end 97 | 98 | if string.find(ua, 'BingPreview', 1, true) then 99 | util.update_map(result, dataset.get('BingPreview')) 100 | return true 101 | end 102 | 103 | if string.find(ua, 'Baidu', 1, true) then 104 | if string.find(ua, 'compatible; Baiduspider', 1, true) or string.find(ua, 'Baiduspider+', 1, true) or string.find(ua, 'Baiduspider-image+', 1, true) then 105 | util.update_map(result, dataset.get('Baiduspider')) 106 | return true 107 | end 108 | end 109 | 110 | if string.find(ua, 'Yeti', 1, true) then 111 | if string.find(ua, 'http://help.naver.com/robots', 1, true) or string.find(ua, 'http://help.naver.com/support/robots.html', 1, true) or string.find(ua, 'http://naver.me/bot', 1, true) then 112 | util.update_map(result, dataset.get('Yeti')) 113 | return true 114 | end 115 | end 116 | 117 | if string.find(ua, 'FeedBurner/', 1, true) then 118 | util.update_map(result, dataset.get('FeedBurner')) 119 | return true 120 | end 121 | 122 | if string.find(ua, 'facebookexternalhit', 1, true) then 123 | util.update_map(result, dataset.get('facebook')) 124 | return true 125 | end 126 | 127 | if string.find(ua, 'Twitterbot/', 1, true) then 128 | util.update_map(result, dataset.get('twitter')) 129 | return true 130 | end 131 | 132 | if string.find(ua, 'ichiro', 1, true) then 133 | if string.find(ua, 'http://help.goo.ne.jp/door/crawler.html', 1, true) or string.find(ua, 'compatible; ichiro/mobile goo;', 1, true) then 134 | util.update_map(result, dataset.get('goo')) 135 | return true 136 | end 137 | end 138 | 139 | if string.find(ua, 'gooblogsearch/', 1, true) then 140 | util.update_map(result, dataset.get('goo')) 141 | return true 142 | end 143 | 144 | if string.find(ua, 'Apple-PubSub', 1, true) then 145 | util.update_map(result, dataset.get('ApplePubSub')) 146 | return true 147 | end 148 | 149 | if string.find(ua, '(www.radian6.com/crawler)', 1, true) then 150 | util.update_map(result, dataset.get('radian6')) 151 | return true 152 | end 153 | 154 | if string.find(ua, 'Genieo/', 1, true) then 155 | util.update_map(result, dataset.get('Genieo')) 156 | return true 157 | end 158 | 159 | if string.find(ua, 'labs.topsy.com/butterfly/', 1, true) then 160 | util.update_map(result, dataset.get('topsyButterfly')) 161 | return true 162 | end 163 | 164 | if string.find(ua, 'rogerbot/1.0 (http://www.seomoz.org/dp/rogerbot', 1, true) then 165 | util.update_map(result, dataset.get('rogerbot')) 166 | return true 167 | end 168 | 169 | if string.find(ua, 'compatible; AhrefsBot/', 1, true) then 170 | util.update_map(result, dataset.get('AhrefsBot')) 171 | return true 172 | end 173 | 174 | if string.find(ua, 'livedoor FeedFetcher', 1, true) or string.find(ua, 'Fastladder FeedFetcher', 1, true) then 175 | util.update_map(result, dataset.get('livedoorFeedFetcher')) 176 | return true 177 | end 178 | 179 | if string.find(ua, 'Hatena ', 1, true) then 180 | if string.find(ua, 'Hatena Antenna', 1, true) or string.find(ua, 'Hatena Pagetitle Agent', 1, true) or string.find(ua, 'Hatena Diary RSS', 1, true) then 181 | util.update_map(result, dataset.get('Hatena')) 182 | return true 183 | end 184 | end 185 | 186 | if string.find(ua, 'mixi-check', 1, true) or string.find(ua, 'mixi-crawler', 1, true) or string.find(ua, 'mixi-news-crawler', 1, true) then 187 | util.update_map(result, dataset.get('mixi')) 188 | return true 189 | end 190 | 191 | if string.find(ua, 'Indy Library', 1, true) then 192 | if string.find(ua, 'compatible; Indy Library', 1, true) then 193 | util.update_map(result, dataset.get('IndyLibrary')) 194 | return true 195 | end 196 | end 197 | 198 | if string.find(ua, 'trendictionbot', 1, true) then 199 | util.update_map(result, dataset.get('trendictionbot')) 200 | return true 201 | end 202 | 203 | return false 204 | end 205 | 206 | 207 | function _M.challenge_maybe_crawler(ua, result) 208 | local data = nil 209 | 210 | if ngx.re.match(ua, [[(bot|crawler|spider)(?:[-_ ./;@()]|$)]], "io") then 211 | util.update_map(result, dataset.get('VariousCrawler')) 212 | return true 213 | elseif ngx.re.match(ua, [[^(?:Rome Client |UnwindFetchor/|ia_archiver |Summify |PostRank/)]], "o") or string.find(ua, 'ASP-Ranker Feed Crawler', 1, true) then 214 | util.update_map(result, dataset.get('VariousCrawler')) 215 | return true 216 | elseif ngx.re.match(ua, [[(feed|web) ?parser]], "io") then 217 | util.update_map(result, dataset.get('VariousCrawler')) 218 | return true 219 | elseif ngx.re.match(ua, [[watch ?dog]], "io") then 220 | util.update_map(result, dataset.get('VariousCrawler')) 221 | return true 222 | end 223 | 224 | return false 225 | end 226 | 227 | return _M 228 | -------------------------------------------------------------------------------- /lib/resty/woothee/dataset.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------- 2 | -- Auto Generate from main/dataset_yaml2lua.lua 3 | -------------------------------------------------- 4 | local _M = { } 5 | 6 | _M.KEY_LABEL = "label" 7 | _M.KEY_NAME = "name" 8 | _M.KEY_TYPE = "type" 9 | _M.KEY_CATEGORY = "category" 10 | _M.KEY_OS = "os" 11 | _M.KEY_OS_VERSION = "os_version" 12 | _M.KEY_VENDOR = "vendor" 13 | _M.KEY_VERSION = "version" 14 | _M.TYPE_BROWSER = "browser" 15 | _M.TYPE_OS = "os" 16 | _M.TYPE_FULL = "full" 17 | _M.CATEGORY_PC = "pc" 18 | _M.CATEGORY_SMARTPHONE = "smartphone" 19 | _M.CATEGORY_MOBILEPHONE = "mobilephone" 20 | _M.CATEGORY_CRAWLER = "crawler" 21 | _M.CATEGORY_APPLIANCE = "appliance" 22 | _M.CATEGORY_MISC = "misc" 23 | _M.ATTRIBUTE_NAME = "name" 24 | _M.ATTRIBUTE_CATEGORY = "category" 25 | _M.ATTRIBUTE_OS = "os" 26 | _M.ATTRIBUTE_OS_VERSION = "os_version" 27 | _M.ATTRIBUTE_VENDOR = "vendor" 28 | _M.ATTRIBUTE_VERSION = "version" 29 | _M.VALUE_UNKNOWN = "UNKNOWN" 30 | _M.CATEGORY_LIST = {"pc", "smartphone", "mobilephone", "crawler", "appliance", "misc", "UNKNOWN"} 31 | _M.ATTRIBUTE_LIST = {"name", "category", "os", "vendor", "version", "os_version"} 32 | 33 | local DATASET = {} 34 | 35 | 36 | local obj = { 37 | label='MSIE', 38 | name='Internet Explorer', 39 | type='browser' 40 | } 41 | obj["vendor"] = 'Microsoft' 42 | DATASET[obj.label] = obj 43 | 44 | local obj = { 45 | label='Edge', 46 | name='Edge', 47 | type='browser' 48 | } 49 | obj["vendor"] = 'Microsoft' 50 | DATASET[obj.label] = obj 51 | 52 | local obj = { 53 | label='Chrome', 54 | name='Chrome', 55 | type='browser' 56 | } 57 | obj["vendor"] = 'Google' 58 | DATASET[obj.label] = obj 59 | 60 | local obj = { 61 | label='Safari', 62 | name='Safari', 63 | type='browser' 64 | } 65 | obj["vendor"] = 'Apple' 66 | DATASET[obj.label] = obj 67 | 68 | local obj = { 69 | label='Firefox', 70 | name='Firefox', 71 | type='browser' 72 | } 73 | obj["vendor"] = 'Mozilla' 74 | DATASET[obj.label] = obj 75 | 76 | local obj = { 77 | label='Opera', 78 | name='Opera', 79 | type='browser' 80 | } 81 | obj["vendor"] = 'Opera' 82 | DATASET[obj.label] = obj 83 | 84 | local obj = { 85 | label='Vivaldi', 86 | name='Vivaldi', 87 | type='browser' 88 | } 89 | obj["vendor"] = 'Vivaldi Technologies' 90 | DATASET[obj.label] = obj 91 | 92 | local obj = { 93 | label='Sleipnir', 94 | name='Sleipnir', 95 | type='browser' 96 | } 97 | obj["vendor"] = 'Fenrir Inc.' 98 | DATASET[obj.label] = obj 99 | 100 | local obj = { 101 | label='Webview', 102 | name='Webview', 103 | type='browser' 104 | } 105 | obj["vendor"] = 'OS vendor' 106 | DATASET[obj.label] = obj 107 | 108 | local obj = { 109 | label='YaBrowser', 110 | name='Yandex Browser', 111 | type='browser' 112 | } 113 | obj["vendor"] = 'Yandex' 114 | DATASET[obj.label] = obj 115 | 116 | local obj = { 117 | label='Win', 118 | name='Windows UNKNOWN Ver', 119 | type='os' 120 | } 121 | obj["category"] = 'pc' 122 | DATASET[obj.label] = obj 123 | 124 | local obj = { 125 | label='Win10', 126 | name='Windows 10', 127 | type='os' 128 | } 129 | obj["category"] = 'pc' 130 | DATASET[obj.label] = obj 131 | 132 | local obj = { 133 | label='Win8.1', 134 | name='Windows 8.1', 135 | type='os' 136 | } 137 | obj["category"] = 'pc' 138 | DATASET[obj.label] = obj 139 | 140 | local obj = { 141 | label='Win8', 142 | name='Windows 8', 143 | type='os' 144 | } 145 | obj["category"] = 'pc' 146 | DATASET[obj.label] = obj 147 | 148 | local obj = { 149 | label='Win7', 150 | name='Windows 7', 151 | type='os' 152 | } 153 | obj["category"] = 'pc' 154 | DATASET[obj.label] = obj 155 | 156 | local obj = { 157 | label='WinVista', 158 | name='Windows Vista', 159 | type='os' 160 | } 161 | obj["category"] = 'pc' 162 | DATASET[obj.label] = obj 163 | 164 | local obj = { 165 | label='WinXP', 166 | name='Windows XP', 167 | type='os' 168 | } 169 | obj["category"] = 'pc' 170 | DATASET[obj.label] = obj 171 | 172 | local obj = { 173 | label='Win2000', 174 | name='Windows 2000', 175 | type='os' 176 | } 177 | obj["category"] = 'pc' 178 | DATASET[obj.label] = obj 179 | 180 | local obj = { 181 | label='WinNT4', 182 | name='Windows NT 4.0', 183 | type='os' 184 | } 185 | obj["category"] = 'pc' 186 | DATASET[obj.label] = obj 187 | 188 | local obj = { 189 | label='WinMe', 190 | name='Windows Me', 191 | type='os' 192 | } 193 | obj["category"] = 'pc' 194 | DATASET[obj.label] = obj 195 | 196 | local obj = { 197 | label='Win98', 198 | name='Windows 98', 199 | type='os' 200 | } 201 | obj["category"] = 'pc' 202 | DATASET[obj.label] = obj 203 | 204 | local obj = { 205 | label='Win95', 206 | name='Windows 95', 207 | type='os' 208 | } 209 | obj["category"] = 'pc' 210 | DATASET[obj.label] = obj 211 | 212 | local obj = { 213 | label='WinPhone', 214 | name='Windows Phone OS', 215 | type='os' 216 | } 217 | obj["category"] = 'smartphone' 218 | DATASET[obj.label] = obj 219 | 220 | local obj = { 221 | label='WinCE', 222 | name='Windows CE', 223 | type='os' 224 | } 225 | obj["category"] = 'smartphone' 226 | DATASET[obj.label] = obj 227 | 228 | local obj = { 229 | label='OSX', 230 | name='Mac OSX', 231 | type='os' 232 | } 233 | obj["category"] = 'pc' 234 | DATASET[obj.label] = obj 235 | 236 | local obj = { 237 | label='MacOS', 238 | name='Mac OS Classic', 239 | type='os' 240 | } 241 | obj["category"] = 'pc' 242 | DATASET[obj.label] = obj 243 | 244 | local obj = { 245 | label='Linux', 246 | name='Linux', 247 | type='os' 248 | } 249 | obj["category"] = 'pc' 250 | DATASET[obj.label] = obj 251 | 252 | local obj = { 253 | label='BSD', 254 | name='BSD', 255 | type='os' 256 | } 257 | obj["category"] = 'pc' 258 | DATASET[obj.label] = obj 259 | 260 | local obj = { 261 | label='ChromeOS', 262 | name='ChromeOS', 263 | type='os' 264 | } 265 | obj["category"] = 'pc' 266 | DATASET[obj.label] = obj 267 | 268 | local obj = { 269 | label='Android', 270 | name='Android', 271 | type='os' 272 | } 273 | obj["category"] = 'smartphone' 274 | DATASET[obj.label] = obj 275 | 276 | local obj = { 277 | label='iPhone', 278 | name='iPhone', 279 | type='os' 280 | } 281 | obj["category"] = 'smartphone' 282 | DATASET[obj.label] = obj 283 | 284 | local obj = { 285 | label='iPad', 286 | name='iPad', 287 | type='os' 288 | } 289 | obj["category"] = 'smartphone' 290 | DATASET[obj.label] = obj 291 | 292 | local obj = { 293 | label='iPod', 294 | name='iPod', 295 | type='os' 296 | } 297 | obj["category"] = 'smartphone' 298 | DATASET[obj.label] = obj 299 | 300 | local obj = { 301 | label='iOS', 302 | name='iOS', 303 | type='os' 304 | } 305 | obj["category"] = 'smartphone' 306 | DATASET[obj.label] = obj 307 | 308 | local obj = { 309 | label='FirefoxOS', 310 | name='Firefox OS', 311 | type='os' 312 | } 313 | obj["category"] = 'smartphone' 314 | DATASET[obj.label] = obj 315 | 316 | local obj = { 317 | label='BlackBerry', 318 | name='BlackBerry', 319 | type='os' 320 | } 321 | obj["category"] = 'smartphone' 322 | DATASET[obj.label] = obj 323 | 324 | local obj = { 325 | label='BlackBerry10', 326 | name='BlackBerry 10', 327 | type='os' 328 | } 329 | obj["category"] = 'smartphone' 330 | DATASET[obj.label] = obj 331 | 332 | local obj = { 333 | label='docomo', 334 | name='docomo', 335 | type='full' 336 | } 337 | obj["vendor"] = 'docomo' 338 | obj["category"] = 'mobilephone' 339 | obj["os"] = 'docomo' 340 | DATASET[obj.label] = obj 341 | 342 | local obj = { 343 | label='au', 344 | name='au by KDDI', 345 | type='full' 346 | } 347 | obj["vendor"] = 'au' 348 | obj["category"] = 'mobilephone' 349 | obj["os"] = 'au' 350 | DATASET[obj.label] = obj 351 | 352 | local obj = { 353 | label='SoftBank', 354 | name='SoftBank Mobile', 355 | type='full' 356 | } 357 | obj["vendor"] = 'SoftBank' 358 | obj["category"] = 'mobilephone' 359 | obj["os"] = 'SoftBank' 360 | DATASET[obj.label] = obj 361 | 362 | local obj = { 363 | label='willcom', 364 | name='WILLCOM', 365 | type='full' 366 | } 367 | obj["vendor"] = 'WILLCOM' 368 | obj["category"] = 'mobilephone' 369 | obj["os"] = 'WILLCOM' 370 | DATASET[obj.label] = obj 371 | 372 | local obj = { 373 | label='jig', 374 | name='jig browser', 375 | type='full' 376 | } 377 | obj["vendor"] = '' 378 | obj["category"] = 'mobilephone' 379 | obj["os"] = 'jig' 380 | DATASET[obj.label] = obj 381 | 382 | local obj = { 383 | label='emobile', 384 | name='emobile', 385 | type='full' 386 | } 387 | obj["vendor"] = '' 388 | obj["category"] = 'mobilephone' 389 | obj["os"] = 'emobile' 390 | DATASET[obj.label] = obj 391 | 392 | local obj = { 393 | label='SymbianOS', 394 | name='SymbianOS', 395 | type='full' 396 | } 397 | obj["vendor"] = '' 398 | obj["category"] = 'mobilephone' 399 | obj["os"] = 'SymbianOS' 400 | DATASET[obj.label] = obj 401 | 402 | local obj = { 403 | label='MobileTranscoder', 404 | name='Mobile Transcoder', 405 | type='full' 406 | } 407 | obj["vendor"] = '' 408 | obj["category"] = 'mobilephone' 409 | obj["os"] = 'Mobile Transcoder' 410 | DATASET[obj.label] = obj 411 | 412 | local obj = { 413 | label='Nintendo3DS', 414 | name='Nintendo 3DS', 415 | type='full' 416 | } 417 | obj["vendor"] = 'Nintendo' 418 | obj["category"] = 'appliance' 419 | obj["os"] = 'Nintendo 3DS' 420 | DATASET[obj.label] = obj 421 | 422 | local obj = { 423 | label='NintendoDSi', 424 | name='Nintendo DSi', 425 | type='full' 426 | } 427 | obj["vendor"] = 'Nintendo' 428 | obj["category"] = 'appliance' 429 | obj["os"] = 'Nintendo DSi' 430 | DATASET[obj.label] = obj 431 | 432 | local obj = { 433 | label='NintendoWii', 434 | name='Nintendo Wii', 435 | type='full' 436 | } 437 | obj["vendor"] = 'Nintendo' 438 | obj["category"] = 'appliance' 439 | obj["os"] = 'Nintendo Wii' 440 | DATASET[obj.label] = obj 441 | 442 | local obj = { 443 | label='NintendoWiiU', 444 | name='Nintendo Wii U', 445 | type='full' 446 | } 447 | obj["vendor"] = 'Nintendo' 448 | obj["category"] = 'appliance' 449 | obj["os"] = 'Nintendo Wii U' 450 | DATASET[obj.label] = obj 451 | 452 | local obj = { 453 | label='PSP', 454 | name='PlayStation Portable', 455 | type='full' 456 | } 457 | obj["vendor"] = 'Sony' 458 | obj["category"] = 'appliance' 459 | obj["os"] = 'PlayStation Portable' 460 | DATASET[obj.label] = obj 461 | 462 | local obj = { 463 | label='PSVita', 464 | name='PlayStation Vita', 465 | type='full' 466 | } 467 | obj["vendor"] = 'Sony' 468 | obj["category"] = 'appliance' 469 | obj["os"] = 'PlayStation Vita' 470 | DATASET[obj.label] = obj 471 | 472 | local obj = { 473 | label='PS3', 474 | name='PlayStation 3', 475 | type='full' 476 | } 477 | obj["vendor"] = 'Sony' 478 | obj["category"] = 'appliance' 479 | obj["os"] = 'PlayStation 3' 480 | DATASET[obj.label] = obj 481 | 482 | local obj = { 483 | label='PS4', 484 | name='PlayStation 4', 485 | type='full' 486 | } 487 | obj["vendor"] = 'Sony' 488 | obj["category"] = 'appliance' 489 | obj["os"] = 'PlayStation 4' 490 | DATASET[obj.label] = obj 491 | 492 | local obj = { 493 | label='Xbox360', 494 | name='Xbox 360', 495 | type='full' 496 | } 497 | obj["vendor"] = 'Microsoft' 498 | obj["category"] = 'appliance' 499 | obj["os"] = 'Xbox 360' 500 | DATASET[obj.label] = obj 501 | 502 | local obj = { 503 | label='XboxOne', 504 | name='Xbox One', 505 | type='full' 506 | } 507 | obj["vendor"] = 'Microsoft' 508 | obj["category"] = 'appliance' 509 | obj["os"] = 'Xbox One' 510 | DATASET[obj.label] = obj 511 | 512 | local obj = { 513 | label='DigitalTV', 514 | name='InternetTVBrowser', 515 | type='full' 516 | } 517 | obj["vendor"] = '' 518 | obj["category"] = 'appliance' 519 | obj["os"] = 'DigitalTV' 520 | DATASET[obj.label] = obj 521 | 522 | local obj = { 523 | label='SafariRSSReader', 524 | name='Safari RSSReader', 525 | type='full' 526 | } 527 | obj["vendor"] = 'Apple' 528 | obj["category"] = 'misc' 529 | DATASET[obj.label] = obj 530 | 531 | local obj = { 532 | label='GoogleDesktop', 533 | name='Google Desktop', 534 | type='full' 535 | } 536 | obj["vendor"] = 'Google' 537 | obj["category"] = 'misc' 538 | DATASET[obj.label] = obj 539 | 540 | local obj = { 541 | label='WindowsRSSReader', 542 | name='Windows RSSReader', 543 | type='full' 544 | } 545 | obj["vendor"] = 'Microsoft' 546 | obj["category"] = 'misc' 547 | DATASET[obj.label] = obj 548 | 549 | local obj = { 550 | label='VariousRSSReader', 551 | name='RSSReader', 552 | type='full' 553 | } 554 | obj["vendor"] = '' 555 | obj["category"] = 'misc' 556 | DATASET[obj.label] = obj 557 | 558 | local obj = { 559 | label='HTTPLibrary', 560 | name='HTTP Library', 561 | type='full' 562 | } 563 | obj["vendor"] = '' 564 | obj["category"] = 'misc' 565 | DATASET[obj.label] = obj 566 | 567 | local obj = { 568 | label='GoogleBot', 569 | name='Googlebot', 570 | type='full' 571 | } 572 | obj["vendor"] = '' 573 | obj["category"] = 'crawler' 574 | DATASET[obj.label] = obj 575 | 576 | local obj = { 577 | label='GoogleBotMobile', 578 | name='Googlebot Mobile', 579 | type='full' 580 | } 581 | obj["vendor"] = '' 582 | obj["category"] = 'crawler' 583 | DATASET[obj.label] = obj 584 | 585 | local obj = { 586 | label='GoogleMediaPartners', 587 | name='Google Mediapartners', 588 | type='full' 589 | } 590 | obj["vendor"] = '' 591 | obj["category"] = 'crawler' 592 | DATASET[obj.label] = obj 593 | 594 | local obj = { 595 | label='GoogleFeedFetcher', 596 | name='Google Feedfetcher', 597 | type='full' 598 | } 599 | obj["vendor"] = '' 600 | obj["category"] = 'crawler' 601 | DATASET[obj.label] = obj 602 | 603 | local obj = { 604 | label='GoogleAppEngine', 605 | name='Google AppEngine', 606 | type='full' 607 | } 608 | obj["vendor"] = '' 609 | obj["category"] = 'crawler' 610 | DATASET[obj.label] = obj 611 | 612 | local obj = { 613 | label='GoogleWebPreview', 614 | name='Google Web Preview', 615 | type='full' 616 | } 617 | obj["vendor"] = '' 618 | obj["category"] = 'crawler' 619 | DATASET[obj.label] = obj 620 | 621 | local obj = { 622 | label='YahooSlurp', 623 | name='Yahoo! Slurp', 624 | type='full' 625 | } 626 | obj["vendor"] = '' 627 | obj["category"] = 'crawler' 628 | DATASET[obj.label] = obj 629 | 630 | local obj = { 631 | label='YahooJP', 632 | name='Yahoo! Japan', 633 | type='full' 634 | } 635 | obj["vendor"] = '' 636 | obj["category"] = 'crawler' 637 | DATASET[obj.label] = obj 638 | 639 | local obj = { 640 | label='YahooPipes', 641 | name='Yahoo! Pipes', 642 | type='full' 643 | } 644 | obj["vendor"] = '' 645 | obj["category"] = 'crawler' 646 | DATASET[obj.label] = obj 647 | 648 | local obj = { 649 | label='Baiduspider', 650 | name='Baiduspider', 651 | type='full' 652 | } 653 | obj["vendor"] = '' 654 | obj["category"] = 'crawler' 655 | DATASET[obj.label] = obj 656 | 657 | local obj = { 658 | label='msnbot', 659 | name='msnbot', 660 | type='full' 661 | } 662 | obj["vendor"] = '' 663 | obj["category"] = 'crawler' 664 | DATASET[obj.label] = obj 665 | 666 | local obj = { 667 | label='bingbot', 668 | name='bingbot', 669 | type='full' 670 | } 671 | obj["vendor"] = '' 672 | obj["category"] = 'crawler' 673 | DATASET[obj.label] = obj 674 | 675 | local obj = { 676 | label='BingPreview', 677 | name='BingPreview', 678 | type='full' 679 | } 680 | obj["vendor"] = '' 681 | obj["category"] = 'crawler' 682 | DATASET[obj.label] = obj 683 | 684 | local obj = { 685 | label='Yeti', 686 | name='Naver Yeti', 687 | type='full' 688 | } 689 | obj["vendor"] = '' 690 | obj["category"] = 'crawler' 691 | DATASET[obj.label] = obj 692 | 693 | local obj = { 694 | label='FeedBurner', 695 | name='Google FeedBurner', 696 | type='full' 697 | } 698 | obj["vendor"] = '' 699 | obj["category"] = 'crawler' 700 | DATASET[obj.label] = obj 701 | 702 | local obj = { 703 | label='facebook', 704 | name='facebook', 705 | type='full' 706 | } 707 | obj["vendor"] = '' 708 | obj["category"] = 'crawler' 709 | DATASET[obj.label] = obj 710 | 711 | local obj = { 712 | label='twitter', 713 | name='twitter', 714 | type='full' 715 | } 716 | obj["vendor"] = '' 717 | obj["category"] = 'crawler' 718 | DATASET[obj.label] = obj 719 | 720 | local obj = { 721 | label='trendictionbot', 722 | name='trendiction', 723 | type='full' 724 | } 725 | obj["vendor"] = '' 726 | obj["category"] = 'crawler' 727 | DATASET[obj.label] = obj 728 | 729 | local obj = { 730 | label='mixi', 731 | name='mixi', 732 | type='full' 733 | } 734 | obj["vendor"] = '' 735 | obj["category"] = 'crawler' 736 | DATASET[obj.label] = obj 737 | 738 | local obj = { 739 | label='IndyLibrary', 740 | name='Indy Library', 741 | type='full' 742 | } 743 | obj["vendor"] = '' 744 | obj["category"] = 'crawler' 745 | DATASET[obj.label] = obj 746 | 747 | local obj = { 748 | label='ApplePubSub', 749 | name='Apple iCloud', 750 | type='full' 751 | } 752 | obj["vendor"] = '' 753 | obj["category"] = 'crawler' 754 | DATASET[obj.label] = obj 755 | 756 | local obj = { 757 | label='Genieo', 758 | name='Genieo Web Filter', 759 | type='full' 760 | } 761 | obj["vendor"] = '' 762 | obj["category"] = 'crawler' 763 | DATASET[obj.label] = obj 764 | 765 | local obj = { 766 | label='topsyButterfly', 767 | name='topsy Butterfly', 768 | type='full' 769 | } 770 | obj["vendor"] = '' 771 | obj["category"] = 'crawler' 772 | DATASET[obj.label] = obj 773 | 774 | local obj = { 775 | label='rogerbot', 776 | name='SeoMoz rogerbot', 777 | type='full' 778 | } 779 | obj["vendor"] = '' 780 | obj["category"] = 'crawler' 781 | DATASET[obj.label] = obj 782 | 783 | local obj = { 784 | label='AhrefsBot', 785 | name='ahref AhrefsBot', 786 | type='full' 787 | } 788 | obj["vendor"] = '' 789 | obj["category"] = 'crawler' 790 | DATASET[obj.label] = obj 791 | 792 | local obj = { 793 | label='radian6', 794 | name='salesforce radian6', 795 | type='full' 796 | } 797 | obj["vendor"] = '' 798 | obj["category"] = 'crawler' 799 | DATASET[obj.label] = obj 800 | 801 | local obj = { 802 | label='Hatena', 803 | name='Hatena', 804 | type='full' 805 | } 806 | obj["vendor"] = '' 807 | obj["category"] = 'crawler' 808 | DATASET[obj.label] = obj 809 | 810 | local obj = { 811 | label='goo', 812 | name='goo', 813 | type='full' 814 | } 815 | obj["vendor"] = '' 816 | obj["category"] = 'crawler' 817 | DATASET[obj.label] = obj 818 | 819 | local obj = { 820 | label='livedoorFeedFetcher', 821 | name='livedoor FeedFetcher', 822 | type='full' 823 | } 824 | obj["vendor"] = '' 825 | obj["category"] = 'crawler' 826 | DATASET[obj.label] = obj 827 | 828 | local obj = { 829 | label='VariousCrawler', 830 | name='misc crawler', 831 | type='full' 832 | } 833 | obj["vendor"] = '' 834 | obj["category"] = 'crawler' 835 | DATASET[obj.label] = obj 836 | 837 | function _M.get(label) 838 | return DATASET[label] 839 | end 840 | 841 | return _M 842 | -------------------------------------------------------------------------------- /lib/resty/woothee/misc.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local util = require('resty.woothee.util') 4 | local dataset = require('resty.woothee.dataset') 5 | 6 | function _M.challenge_desktop_tools(ua, result) 7 | local data = nil 8 | 9 | if string.find(ua, 'AppleSyndication/', 1, true) then 10 | data = dataset.get('SafariRSSReader') 11 | elseif string.find(ua, 'compatible; Google Desktop/', 1, true) then 12 | data = dataset.get('GoogleDesktop') 13 | elseif string.find(ua, 'Windows-RSS-Platform', 1, true) or string.find(ua, 'PLAYSTATION 3;', 1, true) then 14 | data = dataset.get('WindowsRSSReader') 15 | end 16 | 17 | if not data then 18 | return false 19 | end 20 | 21 | util.update_map(result, data) 22 | return true 23 | end 24 | 25 | 26 | function _M.challenge_smart_phone_patterns(ua, result) 27 | if string.find(ua, 'CFNetwork/', 1, true) then 28 | local data = dataset.get('iOS'); 29 | util.update_category(result, data[dataset.KEY_CATEGORY]) 30 | util.update_os(result, data[dataset.KEY_NAME]) 31 | return true 32 | end 33 | 34 | return false 35 | end 36 | 37 | 38 | function _M.challenge_http_library(ua, result) 39 | local data,version = nil 40 | 41 | if ngx.re.match(ua, [[^(?:Apache-HttpClient/|Jakarta Commons-HttpClient/|Java/)]], "o") or ngx.re.match(ua, [[[- ]HttpClient(/|$)]], "o") then 42 | data = dataset.get('HTTPLibrary') 43 | version = 'Java' 44 | elseif string.find(ua, 'Java(TM) 2 Runtime Environment,', 1, true) then 45 | data = dataset.get('HTTPLibrary') 46 | version = 'Java' 47 | elseif ngx.re.match(ua, [[^Wget]], "o") then 48 | data = dataset.get('HTTPLibrary') 49 | version = 'wget' 50 | elseif ngx.re.match(ua, [[^(?:libwww-perl|WWW-Mechanize|LWP::Simple|LWP |lwp-trivial)]], "o") then 51 | data = dataset.get('HTTPLibrary') 52 | version = 'perl' 53 | elseif ngx.re.match(ua, [[^(?:Ruby|feedzirra|Typhoeus)]], "o") then 54 | data = dataset.get('HTTPLibrary') 55 | version = 'ruby' 56 | elseif ngx.re.match(ua, [[^(?:Python-urllib\/|Twisted )]], "o") then 57 | data = dataset.get('HTTPLibrary') 58 | version = 'python' 59 | elseif ngx.re.match(ua, [[^(?:PHP|WordPress|CakePHP|PukiWiki|PECL::HTTP)(?:/| |$)]], "o") or ngx.re.match(ua, [[(?:PEAR |)HTTP_Request(?: class|2)]], "o") then 60 | data = dataset.get('HTTPLibrary') 61 | version = 'php' 62 | elseif string.find(ua, 'PEAR HTTP_Request class;', 1, true) then 63 | data = dataset.get('HTTPLibrary') 64 | version = 'php' 65 | elseif string.find(ua, 'curl/', 1, true) then 66 | data = dataset.get('HTTPLibrary') 67 | version = 'curl' 68 | end 69 | 70 | if not data then 71 | return false 72 | end 73 | 74 | util.update_map(result, data) 75 | util.update_version(result, version) 76 | return true 77 | end 78 | 79 | 80 | function _M.challenge_maybe_rss_reader(ua, result) 81 | local data = nil 82 | 83 | if ngx.re.match(ua, [[rss(?:reader|bar|[-_ /;()]|[ +]*/)]], "io") or ngx.re.match(ua, [[headline-reader]], "io") then 84 | data = dataset.get('VariousRSSReader') 85 | else 86 | if string.find(ua, 'cococ/', 1, true) then 87 | data = dataset.get('VariousRSSReader'); 88 | end 89 | end 90 | 91 | if not data then 92 | return false 93 | end 94 | 95 | util.update_map(result, data) 96 | return true 97 | end 98 | 99 | return _M 100 | -------------------------------------------------------------------------------- /lib/resty/woothee/mobile_phone.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local util = require('resty.woothee.util') 4 | local dataset = require('resty.woothee.dataset') 5 | 6 | function _M.challenge_docomo(ua, result) 7 | if not string.find(ua, 'DoCoMo', 1, true) and not string.find(ua, ';FOMA;', 1, true) then 8 | return false; 9 | end 10 | 11 | local version = dataset.VALUE_UNKNOWN; 12 | local match, err = ngx.re.match(ua, [[DoCoMo/[.0-9]+[ /]([^- /;()"']+)]], "o") 13 | if match then 14 | version = match[1]; 15 | else 16 | match, err = ngx.re.match(ua, [[\(([^;)]+);FOMA;]], "o") 17 | if match then 18 | version = match[1]; 19 | end 20 | end 21 | 22 | util.update_map(result, dataset.get('docomo')) 23 | util.update_version(result, version) 24 | return true; 25 | end 26 | 27 | 28 | function _M.challenge_au(ua, result) 29 | if not string.find(ua, 'KDDI-', 1, true) then 30 | return false 31 | end 32 | 33 | local version = dataset.VALUE_UNKNOWN; 34 | local match, err = ngx.re.match(ua, [[KDDI-([^- /;()"']+)]], "o") 35 | if match then 36 | version = match[1] 37 | end 38 | 39 | util.update_map(result, dataset.get('au')) 40 | util.update_version(result, version) 41 | return true 42 | end 43 | 44 | 45 | function _M.challenge_softbank(ua, result) 46 | if not string.find(ua, 'SoftBank', 1, true) and not string.find(ua, 'Vodafone', 1, true) and not string.find(ua, 'J-PHONE', 1, true) then 47 | return false 48 | end 49 | 50 | local version = dataset.VALUE_UNKNOWN; 51 | local match, err = ngx.re.match(ua, [[(?:SoftBank|Vodafone|J-PHONE)/[.0-9]+/([^ /;()]+)]], "o") 52 | if match then 53 | version = match[1] 54 | end 55 | 56 | util.update_map(result, dataset.get('SoftBank')) 57 | util.update_version(result, version) 58 | return true 59 | end 60 | 61 | 62 | function _M.challenge_willcom(ua, result) 63 | if not string.find(ua, 'WILLCOM', 1, true) and not string.find(ua, 'DDIPOCKET', 1, true) then 64 | return false 65 | end 66 | 67 | local version = dataset.VALUE_UNKNOWN; 68 | local match, err = ngx.re.match(ua, [[(?:WILLCOM|DDIPOCKET);[^/]+/([^ /;()]+)]], "o") 69 | if match then 70 | version = match[1] 71 | end 72 | 73 | util.update_map(result, dataset.get('willcom')) 74 | util.update_version(result, version) 75 | return true 76 | end 77 | 78 | function _M.challenge_misc(ua, result) 79 | if string.find(ua, 'jig browser', 1, true) then 80 | util.update_map(result, dataset.get('jig')) 81 | local match, err = ngx.re.match(ua, [[jig browser[^;]+; ([^);]+)]], "o") 82 | if match then 83 | util.update_version(result, match[1]) 84 | end 85 | return true 86 | end 87 | 88 | if string.find(ua, 'emobile/', 1, true) or string.find(ua, 'OpenBrowser', 1, true) or string.find(ua, 'Browser/Obigo-Browser', 1, true) then 89 | util.update_map(result, dataset.get('emobile')) 90 | return true 91 | end 92 | 93 | if string.find(ua, 'SymbianOS', 1, true) then 94 | util.update_map(result, dataset.get('SymbianOS')) 95 | return true 96 | end 97 | 98 | if string.find(ua, 'Hatena-Mobile-Gateway/', 1, true) then 99 | util.update_map(result, dataset.get('MobileTranscoder')) 100 | util.update_version(result, 'Hatena') 101 | return true 102 | end 103 | 104 | if string.find(ua, 'livedoor-Mobile-Gateway/', 1, true) then 105 | util.update_map(result, dataset.get('MobileTranscoder')) 106 | util.update_version(result, 'livedoor') 107 | return true 108 | end 109 | 110 | return false 111 | end 112 | 113 | return _M 114 | -------------------------------------------------------------------------------- /lib/resty/woothee/os.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local util = require('resty.woothee.util') 4 | local dataset = require('resty.woothee.dataset') 5 | 6 | function _M.challenge_windows(ua, result) 7 | if not string.find(ua, 'Windows', 1, true) then 8 | return false; 9 | end 10 | 11 | if string.find(ua, 'Xbox', 1, true) then 12 | local d = dataset.get("Xbox360") 13 | if string.find(ua, 'Xbox; Xbox One)', 1, true) then 14 | d = dataset.get("XboxOne") 15 | end 16 | util.update_map(result, d) 17 | return true 18 | end 19 | 20 | local data = dataset.get('Win'); 21 | local match, err = ngx.re.match(ua, 'Windows ([ .a-zA-Z0-9]+)[;)]', "o") 22 | if not match then 23 | -- Windows, but version unknown 24 | util.update_category(result, data[dataset.KEY_CATEGORY]); 25 | util.update_os(result, data[dataset.KEY_NAME]); 26 | return true 27 | end 28 | 29 | local version = match[1] 30 | if version == 'NT 10.0' then 31 | data = dataset.get('Win10') 32 | elseif version == 'NT 6.3' then 33 | data = dataset.get('Win8.1') 34 | elseif version == 'NT 6.2' then 35 | data = dataset.get('Win8') -- "NT 6.2; ARM;" means Windows RT, oh.... 36 | elseif version == 'NT 6.1' then 37 | data = dataset.get('Win7') 38 | elseif version == 'NT 6.0' then 39 | data = dataset.get('WinVista') 40 | elseif version == 'NT 5.1' then 41 | data = dataset.get('WinXP') 42 | elseif version == 'NT 5.0' then 43 | data = dataset.get('Win2000') 44 | elseif version == 'NT 4.0' then 45 | data = dataset.get('WinNT4') 46 | elseif version == '98' then 47 | data = dataset.get('Win98') 48 | elseif version == '95' then 49 | data = dataset.get('Win95') 50 | elseif version == 'CE' then 51 | data = dataset.get('WinCE') 52 | else 53 | match, err = ngx.re.match(version, [[^Phone(?: OS)? ([.0-9]+)]], "o") 54 | if match then 55 | data = dataset.get('WinPhone') 56 | version = match[1] 57 | end 58 | end 59 | 60 | util.update_category(result, data[dataset.KEY_CATEGORY]); 61 | util.update_os(result, data[dataset.KEY_NAME]); 62 | util.update_os_version(result, version); 63 | return true; 64 | end 65 | 66 | function _M.challenge_osx(ua, result) 67 | -- Opens a file in append mode 68 | 69 | if not string.find(ua, 'Mac OS X', 1, true) then 70 | return false 71 | end 72 | 73 | local dummy = nil 74 | local data = dataset.get('OSX'); 75 | local version = nil 76 | local match = nil 77 | 78 | if string.find(ua, 'like Mac OS X', 1, true) then 79 | if string.find(ua, 'iPhone;', 1, true) then 80 | data = dataset.get('iPhone') 81 | elseif string.find(ua, 'iPad;', 1, true) then 82 | data = dataset.get('iPad') 83 | elseif string.find(ua, 'iPod', 1, true) then 84 | data = dataset.get('iPod') 85 | end 86 | 87 | local match, err = ngx.re.match(ua, [[; CPU(?: iPhone)? OS (\d+_\d+(?:_\d+)?) like Mac OS X]], "o") 88 | if match and match[1] then 89 | version, dummy = string.gsub(match[1], "_", ".") 90 | end 91 | 92 | else 93 | local match, err = ngx.re.match(ua, [[Mac OS X (10[._]\d+(?:[._]\d+)?)(?:\)|;)]], "o") 94 | if match and match[1] then 95 | version, dummy = string.gsub(match[1], "_", ".") 96 | end 97 | end 98 | 99 | util.update_category(result, data[dataset.KEY_CATEGORY]); 100 | util.update_os(result, data[dataset.KEY_NAME]); 101 | if version then 102 | util.update_os_version(result, version); 103 | end 104 | return true 105 | end 106 | 107 | 108 | function _M.challenge_linux(ua, result) 109 | if not string.find(ua, 'Linux', 1, true) then 110 | return false 111 | end 112 | 113 | local data = dataset.get('Linux'); 114 | local os_version = nil 115 | 116 | if string.find(ua, 'Android', 1, true) then 117 | data = dataset.get('Android'); 118 | local match, err = ngx.re.match(ua, [[Android[- ](\d+(.\d+(?:.\d+)?)?)]], "o") 119 | if match then 120 | os_version = match[1] 121 | end 122 | end 123 | 124 | util.update_category(result, data[dataset.KEY_CATEGORY]); 125 | util.update_os(result, data[dataset.KEY_NAME]); 126 | if os_version then 127 | util.update_os_version(result, os_version); 128 | end 129 | return true 130 | end 131 | 132 | 133 | function _M.challenge_smart_phone(ua, result) 134 | local data, os_version, match, err = nil 135 | 136 | if string.find(ua, 'iPhone', 1, true) then 137 | data = dataset.get('iPhone') 138 | elseif string.find(ua, 'iPad', 1, true) then 139 | data = dataset.get('iPad') 140 | elseif string.find(ua, 'iPod', 1, true) then 141 | data = dataset.get('iPod') 142 | elseif string.find(ua, 'Android', 1, true) then 143 | data = dataset.get('Android') 144 | elseif string.find(ua, 'CFNetwork', 1, true) then 145 | data = dataset.get('iOS') 146 | elseif string.find(ua, 'BB10', 1, true) then 147 | data = dataset.get('BlackBerry10') 148 | match, err = ngx.re.match(ua, [[Version/([.0-9]+) ]], "o") 149 | if match then 150 | os_version = match[1] 151 | end 152 | elseif string.find(ua, 'BlackBerry', 1, true) then 153 | data = dataset.get('BlackBerry') 154 | match, err = ngx.re.match(ua, [[BlackBerry(?:\d+)/([.0-9]+) ]], "o") 155 | if match then 156 | os_version = match[1] 157 | end 158 | end 159 | 160 | if result[dataset.KEY_NAME] and result[dataset.KEY_NAME] == dataset.get('Firefox')[dataset.KEY_NAME] then 161 | -- Firefox OS specific pattern 162 | -- http://lawrencemandel.com/2012/07/27/decision-made-firefox-os-user-agent-string/ 163 | -- https://github.com/woothee/woothee/issues/2 164 | match, err = ngx.re.match(ua, [[^Mozilla/[.0-9]+ \((?:Mobile|Tablet);(?:.*;)? rv:([.0-9]+)\) Gecko/[.0-9]+ Firefox/[.0-9]+$]], "o") 165 | if match then 166 | data = dataset.get('FirefoxOS'); 167 | os_version = match[1] 168 | end 169 | end 170 | 171 | if not data then 172 | return false 173 | end 174 | 175 | util.update_category(result, data[dataset.KEY_CATEGORY]) 176 | util.update_os(result, data[dataset.KEY_NAME]) 177 | if os_version then 178 | util.update_os_version(result, os_version) 179 | end 180 | return true 181 | end 182 | 183 | function _M.challenge_mobile_phone(ua, result) 184 | if string.find(ua, 'KDDI-', 1, true) then 185 | local match, err = ngx.re.match(ua, [[KDDI-([^- /;()"']+)]], "o") 186 | if match then 187 | local term = match[1]; 188 | local data = dataset.get('au'); 189 | util.update_category(result, data[dataset.KEY_CATEGORY]) 190 | util.update_os(result, data[dataset.KEY_OS]) 191 | util.update_version(result, term) 192 | return true 193 | end 194 | elseif string.find(ua, 'WILLCOM', 1, true) or string.find(ua, 'DDIPOCKET', 1, true) then 195 | local match, err = ngx.re.match(ua, [[(?:WILLCOM|DDIPOCKET);[^/]+/([^ /;()]+)]], "o") 196 | if match then 197 | local term = match[1]; 198 | local data = dataset.get('willcom'); 199 | util.update_category(result, data[dataset.KEY_CATEGORY]) 200 | util.update_os(result, data[dataset.KEY_OS]) 201 | util.update_version(result, term) 202 | return true 203 | end 204 | elseif string.find(ua, 'SymbianOS', 1, true) then 205 | local data = dataset.get('SymbianOS'); 206 | util.update_category(result, data[dataset.KEY_CATEGORY]) 207 | util.update_os(result, data[dataset.KEY_OS]) 208 | return true 209 | elseif string.find(ua, 'Google Wireless Transcoder', 1, true) then 210 | util.update_map(result, dataset.get('MobileTranscoder')) 211 | util.update_version(result, 'Google') 212 | return true 213 | elseif string.find(ua, 'Naver Transcoder', 1, true) then 214 | util.update_map(result, dataset.get('MobileTranscoder')) 215 | util.update_version(result, 'Naver') 216 | return true 217 | end 218 | 219 | return false 220 | end 221 | 222 | function _M.challenge_appliance(ua, result) 223 | if string.find(ua, 'Nintendo DSi;', 1, true) then 224 | local data = dataset.get('NintendoDSi'); 225 | util.update_category(result, data[dataset.KEY_CATEGORY]) 226 | util.update_os(result, data[dataset.KEY_OS]) 227 | return true 228 | elseif string.find(ua, 'Nintendo Wii;', 1, true) then 229 | local data = dataset.get('NintendoWii'); 230 | util.update_category(result, data[dataset.KEY_CATEGORY]) 231 | util.update_os(result, data[dataset.KEY_OS]) 232 | return true 233 | end 234 | 235 | return false 236 | end 237 | 238 | function _M.challenge_misc(ua, result) 239 | local data, os_version, match, err = nil 240 | 241 | if string.find(ua, '(Win98;', 1, true) then 242 | data = dataset.get('Win98'); 243 | os_version = '98' 244 | elseif string.find(ua, 'Macintosh; U; PPC;', 1, true) then 245 | data = dataset.get('MacOS'); 246 | match, err = ngx.re.match(ua, [[rv:(\d+.\d+.\d+)]], "o") 247 | if match then 248 | os_version = match[1] 249 | end 250 | elseif string.find(ua, 'Mac_PowerPC', 1, true) then 251 | data = dataset.get('MacOS'); 252 | elseif string.find(ua, 'X11; FreeBSD ', 1, true) then 253 | data = dataset.get('BSD'); 254 | match, err = ngx.re.match(ua, [[FreeBSD ([^;\)]+);]], "o") 255 | if match then 256 | os_version = match[1] 257 | end 258 | elseif string.find(ua, 'X11; CrOS ', 1, true) then 259 | data = dataset.get('ChromeOS'); 260 | match, err = ngx.re.match(ua, [[CrOS ([^\)]+)\)]], "o") 261 | if match then 262 | os_version = match[1] 263 | end 264 | end 265 | 266 | if data then 267 | util.update_category(result, data[dataset.KEY_CATEGORY]) 268 | util.update_os(result, data[dataset.KEY_NAME]) 269 | if os_version then 270 | util.update_os_version(result, os_version); 271 | end 272 | return true 273 | end 274 | 275 | return false 276 | end 277 | 278 | return _M 279 | -------------------------------------------------------------------------------- /lib/resty/woothee/util.lua: -------------------------------------------------------------------------------- 1 | local _M = { } 2 | 3 | local dataset = require('resty.woothee.dataset') 4 | 5 | function _M.update_map(target, source) 6 | target = target or {} 7 | source = source or {} 8 | 9 | for key, _ in pairs(source) do 10 | if key == dataset.KEY_LABEL or key == dataset.KEY_TYPE then 11 | -- pass 12 | elseif source[key] and string.len(source[key]) > 0 then 13 | target[key] = source[key] or '' 14 | end 15 | end 16 | end 17 | 18 | function _M.update_category(target, category) 19 | target[dataset.ATTRIBUTE_CATEGORY] = category 20 | end 21 | 22 | function _M.update_version(target, version) 23 | target[dataset.ATTRIBUTE_VERSION] = version 24 | end 25 | 26 | function _M.update_os(target, os) 27 | target[dataset.ATTRIBUTE_OS] = os 28 | end 29 | 30 | function _M.update_os_version(target, version) 31 | target[dataset.ATTRIBUTE_OS_VERSION] = version 32 | end 33 | 34 | return _M 35 | -------------------------------------------------------------------------------- /lua-resty-woothee-1.12.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-woothee" 2 | version = "1.12.0-1" 3 | source = { 4 | url = "git://github.com/woothee/lua-resty-woothee", 5 | tag = "v1.12.0-1" 6 | } 7 | description = { 8 | summary = "Woothee(UA Parser) Lua-Openresty implementation", 9 | detailed = [[The Lua-Openresty implementation of Project Woothee, which is multi-language user-agent strings parsers.]], 10 | homepage = "https://github.com/toritori0318/lua-resty-woothee", 11 | license = "Apache License 2.0", 12 | maintainer = "toritori0318" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | ["resty.woothee"] = "lib/resty/woothee.lua", 21 | ["resty.woothee.appliance"] = "lib/resty/woothee/appliance.lua", 22 | ["resty.woothee.browser"] = "lib/resty/woothee/browser.lua", 23 | ["resty.woothee.crawler"] = "lib/resty/woothee/crawler.lua", 24 | ["resty.woothee.dataset"] = "lib/resty/woothee/dataset.lua", 25 | ["resty.woothee.misc"] = "lib/resty/woothee/misc.lua", 26 | ["resty.woothee.mobile_phone"] = "lib/resty/woothee/mobile_phone.lua", 27 | ["resty.woothee.os"] = "lib/resty/woothee/os.lua", 28 | ["resty.woothee.util"] = "lib/resty/woothee/util.lua" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /maint/dataset_yaml2lua.lua: -------------------------------------------------------------------------------- 1 | require "luarocks.loader" 2 | 3 | local lyaml = require('lyaml') 4 | local etlua = require('etlua') 5 | 6 | local dataset_fname = "woothee/dataset.yaml" 7 | local write_fname = "lib/resty/woothee/dataset.lua" 8 | 9 | local template = etlua.compile([[ 10 | -------------------------------------------------- 11 | -- Auto Generate from main/dataset_yaml2lua.lua 12 | -------------------------------------------------- 13 | local _M = { } 14 | 15 | _M.KEY_LABEL = "label" 16 | _M.KEY_NAME = "name" 17 | _M.KEY_TYPE = "type" 18 | _M.KEY_CATEGORY = "category" 19 | _M.KEY_OS = "os" 20 | _M.KEY_OS_VERSION = "os_version" 21 | _M.KEY_VENDOR = "vendor" 22 | _M.KEY_VERSION = "version" 23 | _M.TYPE_BROWSER = "browser" 24 | _M.TYPE_OS = "os" 25 | _M.TYPE_FULL = "full" 26 | _M.CATEGORY_PC = "pc" 27 | _M.CATEGORY_SMARTPHONE = "smartphone" 28 | _M.CATEGORY_MOBILEPHONE = "mobilephone" 29 | _M.CATEGORY_CRAWLER = "crawler" 30 | _M.CATEGORY_APPLIANCE = "appliance" 31 | _M.CATEGORY_MISC = "misc" 32 | _M.ATTRIBUTE_NAME = "name" 33 | _M.ATTRIBUTE_CATEGORY = "category" 34 | _M.ATTRIBUTE_OS = "os" 35 | _M.ATTRIBUTE_OS_VERSION = "os_version" 36 | _M.ATTRIBUTE_VENDOR = "vendor" 37 | _M.ATTRIBUTE_VERSION = "version" 38 | _M.VALUE_UNKNOWN = "UNKNOWN" 39 | _M.CATEGORY_LIST = {"pc", "smartphone", "mobilephone", "crawler", "appliance", "misc", "UNKNOWN"} 40 | _M.ATTRIBUTE_LIST = {"name", "category", "os", "vendor", "version", "os_version"} 41 | 42 | local DATASET = {} 43 | 44 | <% for i, dataset in ipairs(dataset_entries) do %> 45 | local obj = { 46 | label='<%= dataset.label -%>', 47 | name='<%= dataset.name -%>', 48 | type='<%= dataset.type -%>' 49 | } 50 | <% if dataset.type == 'browser' then -%> 51 | obj["vendor"] = '<%= dataset.vendor -%>' 52 | <% elseif dataset.type == 'os' then -%> 53 | obj["category"] = '<%= dataset.category -%>' 54 | <% elseif dataset.type == 'full' then -%> 55 | obj["vendor"] = '<%= dataset.vendor or "" -%>' 56 | obj["category"] = '<%= dataset.category -%>' 57 | <% if dataset.os then -%> 58 | obj["os"] = '<%= dataset.os -%>' 59 | <% end -%> 60 | <% else -%> 61 | @INVALID-TYPE@ 62 | <% end -%> 63 | DATASET[obj.label] = obj 64 | <% end -%> 65 | 66 | function _M.get(label) 67 | return DATASET[label] 68 | end 69 | 70 | return _M 71 | ]]) 72 | 73 | 74 | -- read_file 75 | local function read_file(fname) 76 | local data = '' 77 | -- slurp dataset.yml 78 | local f = io.open(fname, "r") 79 | for line in f:lines() do 80 | data = data .. line .. "\n" 81 | end 82 | f:close() 83 | return data 84 | end 85 | 86 | -- write file 87 | local function write_file(fname, data) 88 | local f = io.open(fname, "w") 89 | f:write(data) 90 | f:close() 91 | end 92 | 93 | -- main 94 | local slurp_dataset_yaml = read_file(dataset_fname) 95 | local dataset_entries = lyaml.load(slurp_dataset_yaml) 96 | local dataset_string = template({ 97 | dataset_entries = dataset_entries 98 | }) 99 | write_file(write_fname, dataset_string) 100 | -------------------------------------------------------------------------------- /maint/testset_yaml2lua.lua: -------------------------------------------------------------------------------- 1 | require "luarocks.loader" 2 | 3 | local lyaml = require('lyaml') 4 | local etlua = require('etlua') 5 | local dataset = require('resty.woothee.dataset'); 6 | 7 | local dataset_file = "./woothee/dataset.yaml" 8 | local lua_file = "./lib/resty/woothee/dataset.lua" 9 | 10 | local TESTSET_DIR = './woothee/testsets/' 11 | local TARGETS = { 12 | {'crawler.yaml','Crawler'},{'crawler_google.yaml','Crawler/Google'}, 13 | {'pc_windows.yaml', 'PC/Windows'},{'pc_misc.yaml', 'PC/Misc'}, 14 | {'mobilephone_docomo.yaml','MobilePhone/docomo'},{'mobilephone_au.yaml','MobilePhone/au'}, 15 | {'mobilephone_softbank.yaml','MobilePhone/softbank'},{'mobilephone_willcom.yaml','MobilePhone/willcom'}, 16 | {'mobilephone_misc.yaml','MobilePhone/misc'}, 17 | {'smartphone_ios.yaml','SmartPhone/ios'},{'smartphone_android.yaml','SmartPhone/android'}, 18 | {'smartphone_misc.yaml','SmartPhone/misc'}, 19 | {'appliance.yaml','Appliance'}, 20 | {'pc_lowpriority.yaml','PC/LowPriority'}, 21 | {'misc.yaml','Misc'}, 22 | {'crawler_nonmajor.yaml','Crawler/NonMajor'} 23 | } 24 | 25 | local template = etlua.compile([[ 26 | === TEST <%= no -%>: [<%= groupname -%>][<%= entry.target -%>] 27 | --- http_config eval: $::HttpConfig 28 | --- config 29 | location /t { 30 | content_by_lua ' 31 | local woothee = require "resty.woothee" 32 | local set_name = "<%= groupname -%>" 33 | local target = "<%= entry.target -%>" 34 | local r = woothee.parse(target) 35 | ngx.say(string.format("%s=%s expect:%s", "name", (r["name"]=="<%= entry["name"] -%>"), "<%= entry["name"] -%>" )) 36 | ngx.say(string.format("%s=%s expect:%s", "category", (r["category"]=="<%= entry["category"] -%>"), "<%= entry["category"] -%>" )) 37 | <% if entry["os"] then -%> 38 | ngx.say(string.format("%s=%s expect:%s", "os", (r["os"]=="<%= entry["os"] -%>"), "<%= entry["os"] -%>" )) 39 | <% end -%> 40 | <% if entry["os_version"] then -%> 41 | ngx.say(string.format("%s=%s expect:%s", "os_version", (r["os_version"]=="<%= entry["os_version"] -%>"), "<%= entry["os_version"] -%>" )) 42 | <% end -%> 43 | <% if entry["version"] then -%> 44 | ngx.say(string.format("%s=%s expect:%s", "version", (r["version"]=="<%= entry["version"] -%>"), "<%= entry["version"] -%>" )) 45 | <% end -%> 46 | <% if entry["vendor"] then -%> 47 | ngx.say(string.format("%s=%s expect:%s", "vendor", (r["vendor"]=="<%= entry["vendor"] -%>"), "<%= entry["vendor"] -%>" )) 48 | <% end -%> 49 | '; 50 | } 51 | --- request 52 | GET /t 53 | --- response_body 54 | name=true expect:<%= entry["name"] %> 55 | category=true expect:<%= entry["category"] %> 56 | <% if entry["os"] then -%> 57 | os=true expect:<%= entry["os"] %> 58 | <% end -%> 59 | <% if entry["os_version"] then -%> 60 | os_version=true expect:<%= entry["os_version"] %> 61 | <% end -%> 62 | <% if entry["version"] then -%> 63 | version=true expect:<%= entry["version"] %> 64 | <% end -%> 65 | <% if entry["vendor"] then -%> 66 | vendor=true expect:<%= entry["vendor"] %> 67 | <% end -%> 68 | --- no_error_log 69 | [error] 70 | ]]) 71 | 72 | 73 | 74 | 75 | -- read_file 76 | local function read_file(fname) 77 | local data = '' 78 | -- slurp dataset.yml 79 | local f = io.open(fname, "r") 80 | for line in f:lines() do 81 | data = data .. line .. "\n" 82 | end 83 | f:close() 84 | return data 85 | end 86 | 87 | -- write file 88 | local function write_file(fname, data) 89 | local f = io.open(fname, "w") 90 | f:write(data) 91 | f:close() 92 | end 93 | 94 | -- main 95 | local no = 0 96 | for i, target in ipairs(TARGETS) do 97 | local entry_fname= target[1] 98 | local groupname = target[2] 99 | 100 | -- read file 101 | local dataset_fname = TESTSET_DIR .. entry_fname 102 | local slurp_dataset_yaml = read_file(dataset_fname) 103 | 104 | local entry_string = '' 105 | local testset = lyaml.load(slurp_dataset_yaml) 106 | for i, entry in ipairs(testset) do 107 | no = no + 1 108 | 109 | -- render 110 | local render_string = template({ 111 | no = no, 112 | groupname = groupname, 113 | entry = entry, 114 | }) 115 | 116 | entry_string = entry_string .. render_string 117 | end 118 | 119 | -- write file 120 | local sub_entry_fname = string.gsub(entry_fname, ".yaml", "") 121 | local wfname = "t/testfiles/" .. sub_entry_fname .. ".t" 122 | write_file(wfname, entry_string) 123 | end 124 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | #no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: basic 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local woothee = require "resty.woothee" 31 | ngx.say("OK") 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body 37 | OK 38 | --- no_error_log 39 | [error] 40 | -------------------------------------------------------------------------------- /t/01_version.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | #no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: basic 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local woothee = require "resty.woothee" 31 | ngx.say(woothee._VERSION) 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body_like chop 37 | ^\d+\.\d+.\d+(\-\d+)?$ 38 | --- no_error_log 39 | [error] 40 | -------------------------------------------------------------------------------- /t/02_dataset.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | #no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: contains constants 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local dataset = require "resty.woothee.dataset" 31 | ngx.say(dataset.ATTRIBUTE_NAME) 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body 37 | name 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: contains list of attributes 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location /t { 45 | content_by_lua ' 46 | local dataset = require "resty.woothee.dataset" 47 | local compare_list = { 48 | dataset.ATTRIBUTE_NAME, dataset.ATTRIBUTE_CATEGORY, dataset.ATTRIBUTE_OS, 49 | dataset.ATTRIBUTE_VENDOR, dataset.ATTRIBUTE_VERSION, dataset.ATTRIBUTE_OS_VERSION 50 | } 51 | for i, cattr in ipairs(compare_list) do 52 | for i, dattr in ipairs(dataset.ATTRIBUTE_LIST) do 53 | if cattr and dattr and cattr == dattr then 54 | ngx.say(cattr) 55 | break 56 | end 57 | end 58 | end 59 | '; 60 | } 61 | --- request 62 | GET /t 63 | --- response_body 64 | name 65 | category 66 | os 67 | vendor 68 | version 69 | os_version 70 | --- no_error_log 71 | [error] 72 | 73 | === TEST 3: contains list of categories 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location /t { 77 | content_by_lua ' 78 | local dataset = require "resty.woothee.dataset" 79 | local compare_list = { 80 | dataset.CATEGORY_PC, dataset.CATEGORY_SMARTPHONE, dataset.CATEGORY_MOBILEPHONE, 81 | dataset.CATEGORY_CRAWLER, dataset.CATEGORY_APPLIANCE, dataset.CATEGORY_MISC, 82 | dataset.VALUE_UNKNOWN 83 | } 84 | for i, cattr in ipairs(compare_list) do 85 | for i, dattr in ipairs(dataset.CATEGORY_LIST) do 86 | if cattr and dattr and cattr == dattr then 87 | ngx.say(cattr) 88 | break 89 | end 90 | end 91 | end 92 | '; 93 | } 94 | --- request 95 | GET /t 96 | --- response_body 97 | pc 98 | smartphone 99 | mobilephone 100 | crawler 101 | appliance 102 | misc 103 | UNKNOWN 104 | --- no_error_log 105 | [error] 106 | 107 | === TEST 4: contains certain dataset 108 | --- http_config eval: $::HttpConfig 109 | --- config 110 | location /t { 111 | content_by_lua ' 112 | local dataset = require "resty.woothee.dataset" 113 | ngx.say("Googlebot = " .. dataset.get("GoogleBot").name) 114 | '; 115 | } 116 | --- request 117 | GET /t 118 | --- response_body 119 | Googlebot = Googlebot 120 | --- no_error_log 121 | [error] 122 | -------------------------------------------------------------------------------- /t/03_is_crawler.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | #no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: basic 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local woothee = require "resty.woothee" 31 | -- crawler true 32 | ngx.say(woothee.is_crawler("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")) 33 | -- crawler false 34 | ngx.say(woothee.is_crawler("Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3")) 35 | '; 36 | } 37 | --- request 38 | GET /t 39 | --- response_body 40 | true 41 | false 42 | --- no_error_log 43 | [error] 44 | -------------------------------------------------------------------------------- /t/10_testfile_crawler.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/crawler.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/11_testfile_crawler_google.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/crawler_google.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/12_testfile_pc_windows.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/pc_windows.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/13_testfile_pc_misc.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/pc_misc.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/14_testfile_mobilephone_docomo.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/mobilephone_docomo.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/15_testfile_mobilephone_au.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/mobilephone_au.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/16_testfile_mobilephone_softbank.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/mobilephone_softbank.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/17_testfile_mobilephone_willcom.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/mobilephone_willcom.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/18_testfile_mobilephone_misc.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/mobilephone_misc.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/19_testfile_smartphone_ios.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/smartphone_ios.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/20_testfile_smartphone_android.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/smartphone_android.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/21_testfile_smartphone_misc.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/smartphone_misc.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/22_testfile_appliance.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/appliance.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/23_testfile_pc_lowpriority.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/pc_lowpriority.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/24_testfile_misc.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/misc.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/25_testfile_crawler_nonmajor.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | my $fname = "t/testfiles/crawler_nonmajor.t"; 7 | open DATA, "< $fname" or die "Cannot open $fname: $!\n"; 8 | 9 | repeat_each(1); 10 | 11 | plan tests => repeat_each() * (3 * blocks()); 12 | 13 | my $pwd = cwd(); 14 | 15 | our $HttpConfig = qq{ 16 | lua_package_path "$pwd/lib/?.lua;;"; 17 | }; 18 | 19 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 20 | 21 | no_long_string(); 22 | #no_diff(); 23 | 24 | run_tests(); 25 | 26 | -------------------------------------------------------------------------------- /t/lib/debug.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | require "luarocks.loader" 3 | local inspect = require('inspect') 4 | 5 | function _M.output_logfile(message) 6 | message = inspect(message) 7 | local file = io.open("debug.log", "a") 8 | file:write(message .. "\n") 9 | file:close() 10 | end 11 | 12 | return _M 13 | -------------------------------------------------------------------------------- /t/testfiles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woothee/lua-resty-woothee/256e7ccbcc22d037936cd813e5964f41e5a5a885/t/testfiles/.gitkeep -------------------------------------------------------------------------------- /xt/lib/debug.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | require "luarocks.loader" 3 | local inspect = require('inspect') 4 | 5 | function _M.output_logfile(message) 6 | message = inspect(message) 7 | local file = io.open("debug.log", "a") 8 | file:write(message .. "\n") 9 | file:close() 10 | end 11 | 12 | return _M 13 | -------------------------------------------------------------------------------- /xt/xx_debug.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | #no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: [debug] 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local woothee = require "resty.woothee" 31 | local set_name = "Crawler" 32 | local target = "Mozilla/5.0 (Windows NT xxxxxxxxxxxxxx....)" 33 | -- local target = "Girls/2.0 (livedoor Co.,Ltd.; Peachy 2.1; iPhone; RSS Version 2.0; +http://girls.livedoor.com/)" 34 | local r = woothee.parse(target) 35 | local inspect = require("inspect") 36 | ngx.say("browser=" .. target) 37 | ngx.say(inspect(r)) 38 | '; 39 | } 40 | --- request 41 | GET /t 42 | --- response_body 43 | debug 44 | --- no_error_log 45 | [error] 46 | --------------------------------------------------------------------------------