├── .clang-format ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── UaParser.cpp ├── UaParser.h ├── UaParser.vcxproj ├── UaParserTest.cpp ├── UaParserTest.vcxproj ├── benchmarks ├── README.md ├── UaParserBench.cpp └── useragents.txt ├── internal ├── AlternativeExpander.cpp ├── AlternativeExpander.h ├── MakeUnique.h ├── Pattern.cpp ├── Pattern.h ├── README.md ├── ReplaceTemplate.cpp ├── ReplaceTemplate.h ├── SnippetIndex.cpp ├── SnippetIndex.h ├── SnippetMapping.h ├── StringUtils.h └── StringView.h ├── packages.config ├── test_device_type_mobile.yaml └── uap-cpp.sln /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | --- 3 | Language: Cpp 4 | AccessModifierOffset: -1 5 | ConstructorInitializerIndentWidth: 4 6 | AlignEscapedNewlinesLeft: true 7 | AlignAfterOpenBracket: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortIfStatementsOnASingleLine: false 13 | AllowShortLoopsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AlwaysBreakAfterDefinitionReturnType: false 16 | AlwaysBreakTemplateDeclarations: true 17 | AlwaysBreakBeforeMultilineStrings: true 18 | BreakBeforeBinaryOperators: false 19 | BreakBeforeTernaryOperators: true 20 | BreakConstructorInitializersBeforeComma: false 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | ColumnLimit: 80 24 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 25 | ConstructorInitializerIndentWidth: 4 26 | DerivePointerAlignment: false 27 | ExperimentalAutoDetectBinPacking: false 28 | IndentCaseLabels: true 29 | IndentWrappedFunctionNames: false 30 | IndentFunctionDeclarationAfterType: false 31 | MaxEmptyLinesToKeep: 1 32 | KeepEmptyLinesAtTheStartOfBlocks: false 33 | NamespaceIndentation: None 34 | PenaltyBreakBeforeFirstCallParameter: 1 35 | PenaltyBreakComment: 300 36 | PenaltyBreakString: 1000 37 | PenaltyBreakFirstLessLess: 120 38 | PenaltyExcessCharacter: 1000000 39 | PenaltyReturnTypeOnItsOwnLine: 200 40 | PointerAlignment: Left 41 | SpacesBeforeTrailingComments: 2 42 | Cpp11BracedListStyle: true 43 | Standard: Cpp11 44 | IndentWidth: 2 45 | TabWidth: 8 46 | UseTab: Never 47 | BreakBeforeBraces: Attach 48 | SpacesInParentheses: false 49 | SpacesInSquareBrackets: false 50 | SpacesInAngles: false 51 | SpaceInEmptyParentheses: false 52 | SpacesInCStyleCastParentheses: false 53 | SpaceAfterCStyleCast: false 54 | SpacesInContainerLiterals: true 55 | SpaceBeforeAssignmentOperators: true 56 | ContinuationIndentWidth: 4 57 | CommentPragmas: '^ IWYU pragma:' 58 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 59 | SpaceBeforeParens: ControlStatements 60 | DisableFormat: false 61 | ... 62 | 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### C++ template 3 | # Prerequisites 4 | *.d 5 | 6 | # Compiled Object files 7 | *.slo 8 | *.lo 9 | *.o 10 | *.obj 11 | 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | 16 | # Compiled Dynamic libraries 17 | *.so 18 | *.dylib 19 | *.dll 20 | 21 | # Fortran module files 22 | *.mod 23 | *.smod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | 36 | # MSVC 37 | .vs 38 | *.sln.DotSettings.user 39 | *.vcxproj.user 40 | /Debug 41 | /Release 42 | /x64/Debug 43 | /x64/Release 44 | /packages 45 | 46 | 47 | nbproject 48 | build 49 | UaParserTest 50 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "uap-core"] 2 | path = uap-core 3 | url = https://github.com/ua-parser/uap-core 4 | branch = master 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | before_install: 3 | - sudo apt-get update -qq 4 | - sudo apt-get install -qq libyaml-cpp-dev cmake libgtest-dev libre2-dev 5 | - cd /usr/src/gtest 6 | - sudo cmake CMakeLists.txt 7 | - sudo make 8 | - sudo cp *.a /usr/lib 9 | - cd - 10 | script: 11 | - make test 12 | notifications: 13 | email: 14 | recipients: 15 | - alex.suhan@gmail.com 16 | on_success: always 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | 3 | project(uap-cpp) 4 | 5 | IF(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 6 | SET(CMAKE_BUILD_TYPE Release) 7 | ENDIF(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 8 | 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall -Werror -fPIC") 10 | 11 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") 12 | 13 | 14 | 15 | set(LIB_SOURCES 16 | UaParser.cpp 17 | internal/AlternativeExpander.cpp 18 | internal/Pattern.cpp 19 | internal/ReplaceTemplate.cpp 20 | internal/SnippetIndex.cpp) 21 | 22 | find_package(yaml-cpp REQUIRED) 23 | include_directories(${YAML_CPP_INCLUDE_DIR}) 24 | 25 | find_package(re2) 26 | 27 | # this is the "object library" target: compiles the sources only once 28 | add_library(objlib OBJECT ${LIB_SOURCES}) 29 | 30 | # shared libraries need PIC 31 | set_property(TARGET objlib PROPERTY POSITION_INDEPENDENT_CODE 1) 32 | 33 | # shared and static libraries built from the same object files 34 | add_library(uap-cpp-static STATIC $) 35 | set_target_properties(uap-cpp-static PROPERTIES OUTPUT_NAME uaparser_cpp) 36 | 37 | add_library(uap-cpp-shared SHARED $) 38 | set_target_properties(uap-cpp-shared PROPERTIES OUTPUT_NAME uaparser_cpp) 39 | 40 | 41 | 42 | set(TEST_SOURCES 43 | UaParserTest.cpp) 44 | 45 | find_package(GTest) 46 | 47 | add_executable(tests ${TEST_SOURCES} $) 48 | set_target_properties(tests PROPERTIES OUTPUT_NAME UaParserTest) 49 | 50 | target_link_libraries(tests re2 yaml-cpp gtest pthread) 51 | 52 | 53 | 54 | set(BENCH_SOURCES 55 | benchmarks/UaParserBench.cpp) 56 | 57 | add_executable(bench ${BENCH_SOURCES} $) 58 | set_target_properties(bench PROPERTIES OUTPUT_NAME UaParserBench) 59 | 60 | target_link_libraries(bench re2 yaml-cpp pthread) 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 Alex Şuhan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef KEEP_ENV_VARS 2 | LDFLAGS += -lre2 -lyaml-cpp 3 | CXXFLAGS += -std=c++14 -Wall -Werror -g -fPIC -O3 4 | endif 5 | 6 | # wildcard object build target 7 | %.o: %.cpp 8 | $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $*.cpp -o $*.o 9 | @$(CXX) -MM $(CPPFLAGS) $(CXXFLAGS) $*.cpp > $*.d 10 | 11 | uaparser_cpp: libuaparser_cpp.a 12 | 13 | OBJECT_FILES = UaParser.o \ 14 | internal/Pattern.o \ 15 | internal/AlternativeExpander.o \ 16 | internal/SnippetIndex.o \ 17 | internal/ReplaceTemplate.o 18 | 19 | libuaparser_cpp.a: $(OBJECT_FILES) 20 | $(AR) rcs $@ $^ 21 | 22 | libuaparser_cpp.so: $(OBJECT_FILES) 23 | $(CXX) $^ -shared $(LDFLAGS) -o $@ 24 | 25 | UaParserTest: libuaparser_cpp.a UaParserTest.o 26 | $(CXX) $^ -o $@ libuaparser_cpp.a $(LDFLAGS) -lgtest -lpthread 27 | 28 | test: UaParserTest libuaparser_cpp.a 29 | ./UaParserTest 30 | 31 | UaParserBench: libuaparser_cpp.a benchmarks/UaParserBench.o 32 | $(CXX) $^ -o $@ libuaparser_cpp.a $(LDFLAGS) -lpthread 33 | 34 | bench: UaParserBench 35 | time ./UaParserBench uap-core/regexes.yaml benchmarks/useragents.txt 1000 36 | 37 | # clean everything generated 38 | clean: 39 | find . -name "*.o" -exec rm -rf {} \; # clean up object files 40 | find . -name "*.d" -exec rm -rf {} \; # clean up dependencies 41 | rm -f UaParserTest UaParserBench *.a *.so 42 | 43 | # automatically include the generated *.d dependency make targets 44 | # that are created from the wildcard %.o build target above 45 | -include $(OBJS:.o=.d) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ua_parser C++ Library 2 | ===================== 3 | 4 | Usage 5 | ----- 6 | 7 | ### Linux 8 | 9 | To build the (static) library: 10 | 11 | make uaparser_cpp 12 | 13 | To build and run the tests: 14 | 15 | make test 16 | 17 | A recent (GCC >= 5.4 or Clang >= 3.9 both work) C++14 compiler is required. 18 | 19 | #### CMake build 20 | 21 | ##### libraries 22 | 23 | cd uap-cpp 24 | mkdir build 25 | cd build 26 | cmake .. 27 | 28 | make uap-cpp-static 29 | 30 | make uap-cpp-shared 31 | 32 | ##### tests 33 | Prepare gtest: 34 | 35 | apt-get install libgtest-dev 36 | apt-get install cmake 37 | cd /usr/src/gtest 38 | cmake CMakeLists.txt 39 | make 40 | cp *.a /usr/lib 41 | 42 | Run from build directory: 43 | 44 | make tests 45 | 46 | Run from uap-cpp directory: 47 | 48 | ./build/UaParserTest 49 | 50 | ##### benchmark 51 | Run from build directory: 52 | 53 | make bench 54 | 55 | Run from uap-cpp directory: 56 | 57 | time ./build/UaParserBench uap-core/regexes.yaml benchmarks/useragents.txt 1000 58 | 59 | ### Windows 60 | 61 | First, open ``uap-cpp.sln`` with MSVC 15 (Visual Studio 2017). 62 | 63 | To build the (static) library: 64 | 65 | build the "UaParser" project 66 | 67 | To build and run the tests: 68 | 69 | build the "UaParserTest" project 70 | 71 | The MSVC projects assume boost to be installed at: ``C:\boost_1_69_0`` and yaml to be installed at: ``C:\yaml-cpp``. Change these paths if needed. ``boost_regex`` needs to be built from source in advance. For yaml-cpp, you can built it from source, or use a prebuilt [here](https://github.com/hsluoyz/yaml-cpp-prebuilt-win32). 72 | 73 | Dependencies 74 | ------------ 75 | 76 | * re2, yaml-cpp (>=0.5) 77 | * gtest (for testing) 78 | * [uap-core](https://github.com/ua-parser/uap-core), same directory level as uap-cpp. You can clone this repo with --recurse-submodules to get it. Alternatively, run `git submodule update --init`. 79 | 80 | Contributing 81 | ------------ 82 | 83 | Pull requests are welcome. Use `clang-format -i *.cpp *.h` to format the sources before sending the patch. 84 | 85 | Credits 86 | ------- 87 | 88 | Inspired from the D implementation by Shripad K and using agent data from BrowserScope. 89 | -------------------------------------------------------------------------------- /UaParser.cpp: -------------------------------------------------------------------------------- 1 | #include "UaParser.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "internal/AlternativeExpander.h" 13 | #include "internal/MakeUnique.h" 14 | #include "internal/Pattern.h" 15 | #include "internal/ReplaceTemplate.h" 16 | #include "internal/SnippetIndex.h" 17 | #include "internal/SnippetMapping.h" 18 | #include "internal/StringUtils.h" 19 | 20 | namespace { 21 | 22 | struct GenericStore { 23 | uap_cpp::ReplaceTemplate replacement; 24 | uap_cpp::Pattern regExpr; 25 | int index{0}; 26 | }; 27 | 28 | struct DeviceStore : GenericStore { 29 | uap_cpp::ReplaceTemplate brandReplacement; 30 | uap_cpp::ReplaceTemplate modelReplacement; 31 | }; 32 | 33 | struct AgentStore : GenericStore { 34 | uap_cpp::ReplaceTemplate majorVersionReplacement; 35 | uap_cpp::ReplaceTemplate minorVersionReplacement; 36 | uap_cpp::ReplaceTemplate patchVersionReplacement; 37 | uap_cpp::ReplaceTemplate patchMinorVersionReplacement; 38 | }; 39 | 40 | struct GenericStoreComparator { 41 | bool operator()(const GenericStore* lhs, const GenericStore* rhs) const { 42 | return lhs->index < rhs->index; 43 | } 44 | }; 45 | 46 | void fill_device_store(const YAML::Node& device_parser, 47 | std::vector>& device_stores, 48 | uap_cpp::SnippetIndex& snippet_index, 49 | uap_cpp::SnippetMapping& mappings) { 50 | device_stores.emplace_back(uap_cpp::make_unique()); 51 | DeviceStore& device = *device_stores.back(); 52 | device.index = device_stores.size(); 53 | 54 | std::string regex; 55 | bool regex_flag = false; 56 | for (auto it = device_parser.begin(); it != device_parser.end(); ++it) { 57 | const auto& key = it->first.as(); 58 | const auto& value = it->second.as(); 59 | if (key == "regex") { 60 | regex = value; 61 | } else if (key == "regex_flag" && value == "i") { 62 | regex_flag = true; 63 | } else if (key == "device_replacement") { 64 | device.replacement = value; 65 | } else if (key == "model_replacement") { 66 | device.modelReplacement = value; 67 | } else if (key == "brand_replacement") { 68 | device.brandReplacement = value; 69 | } else { 70 | assert(false); 71 | } 72 | } 73 | 74 | device.regExpr.assign(regex, !regex_flag); 75 | 76 | for (const auto& e : uap_cpp::AlternativeExpander::expand(regex)) { 77 | auto snippets = snippet_index.registerSnippets(e); 78 | mappings.addMapping(snippets, &device); 79 | } 80 | } 81 | 82 | void fill_agent_store(const YAML::Node& node, 83 | const std::string& repl, 84 | const std::string& major_repl, 85 | const std::string& minor_repl, 86 | const std::string& patch_repl, 87 | std::vector>& agent_stores, 88 | uap_cpp::SnippetIndex& snippet_index, 89 | uap_cpp::SnippetMapping& mapping) { 90 | agent_stores.emplace_back(uap_cpp::make_unique()); 91 | AgentStore& agent_store = *agent_stores.back(); 92 | agent_store.index = agent_stores.size(); 93 | 94 | assert(node.Type() == YAML::NodeType::Map); 95 | for (auto it = node.begin(); it != node.end(); ++it) { 96 | const auto& key = it->first.as(); 97 | const auto& value = it->second.as(); 98 | if (key == "regex") { 99 | agent_store.regExpr.assign(value); 100 | 101 | for (const auto& e : uap_cpp::AlternativeExpander::expand(value)) { 102 | auto snippets = snippet_index.registerSnippets(e); 103 | mapping.addMapping(snippets, &agent_store); 104 | } 105 | } else if (key == repl) { 106 | agent_store.replacement = value; 107 | } else if (key == major_repl && !value.empty()) { 108 | if (value != "$2") { 109 | agent_store.majorVersionReplacement = value; 110 | } 111 | } else if (key == minor_repl && !value.empty()) { 112 | if (value != "$3") { 113 | agent_store.minorVersionReplacement = value; 114 | } 115 | } else if (key == patch_repl && !value.empty()) { 116 | if (value != "$4") { 117 | agent_store.patchVersionReplacement = value; 118 | } 119 | } else { 120 | // Ignore invalid key. 121 | } 122 | } 123 | } 124 | 125 | struct UAStore { 126 | explicit UAStore(const std::string& regexes_file_path) { 127 | auto regexes = YAML::LoadFile(regexes_file_path); 128 | 129 | const auto& user_agent_parsers = regexes["user_agent_parsers"]; 130 | for (const auto& user_agent : user_agent_parsers) { 131 | fill_agent_store(user_agent, 132 | "family_replacement", 133 | "v1_replacement", 134 | "v2_replacement", 135 | "v3_replacement", 136 | browserStore, 137 | browserSnippetIndex, 138 | browserMapping); 139 | } 140 | 141 | const auto& os_parsers = regexes["os_parsers"]; 142 | for (const auto& o : os_parsers) { 143 | fill_agent_store(o, 144 | "os_replacement", 145 | "os_v1_replacement", 146 | "os_v2_replacement", 147 | "os_v3_replacement", 148 | osStore, 149 | osSnippetIndex, 150 | osMapping); 151 | } 152 | 153 | const auto& device_parsers = regexes["device_parsers"]; 154 | for (const auto& device_parser : device_parsers) { 155 | fill_device_store( 156 | device_parser, deviceStore, deviceSnippetIndex, deviceMapping); 157 | } 158 | } 159 | 160 | std::vector> deviceStore; 161 | std::vector> osStore; 162 | std::vector> browserStore; 163 | 164 | uap_cpp::SnippetIndex deviceSnippetIndex; 165 | uap_cpp::SnippetIndex osSnippetIndex; 166 | uap_cpp::SnippetIndex browserSnippetIndex; 167 | 168 | uap_cpp::SnippetMapping deviceMapping; 169 | uap_cpp::SnippetMapping osMapping; 170 | uap_cpp::SnippetMapping browserMapping; 171 | }; 172 | 173 | ///////////// 174 | // HELPERS // 175 | ///////////// 176 | 177 | uap_cpp::Device parse_device_impl(const std::string& ua, 178 | const UAStore* ua_store) { 179 | uap_cpp::Device device; 180 | 181 | auto snippets = ua_store->deviceSnippetIndex.getSnippets(ua); 182 | 183 | std::set regexps; 184 | ua_store->deviceMapping.getExpressions(snippets, regexps); 185 | 186 | for (const auto& entry : regexps) { 187 | const auto& d = *entry; 188 | 189 | thread_local uap_cpp::Match m; 190 | 191 | if (d.regExpr.match(ua, m)) { 192 | if (d.replacement.empty() && m.size() > 1) { 193 | device.family = m.get(1); 194 | } else { 195 | device.family = d.replacement.expand(m); 196 | } 197 | trim(device.family); 198 | 199 | if (!d.brandReplacement.empty()) { 200 | device.brand = d.brandReplacement.expand(m); 201 | trim(device.brand); 202 | } 203 | 204 | if (d.modelReplacement.empty() && m.size() > 1) { 205 | device.model = m.get(1); 206 | } else { 207 | device.model = d.modelReplacement.expand(m); 208 | } 209 | trim(device.model); 210 | 211 | break; 212 | } 213 | } 214 | 215 | return device; 216 | } 217 | 218 | template 219 | void fill_agent(AGENT& agent, 220 | const AGENT_STORE& store, 221 | const uap_cpp::Match& m) { 222 | if (store.replacement.empty() && m.size() > 1) { 223 | agent.family = m.get(1); 224 | } else { 225 | agent.family = store.replacement.expand(m); 226 | } 227 | trim(agent.family); 228 | 229 | if (!store.majorVersionReplacement.empty()) { 230 | agent.major = store.majorVersionReplacement.expand(m); 231 | } else if (m.size() > 2) { 232 | agent.major = m.get(2); 233 | } 234 | if (!store.minorVersionReplacement.empty()) { 235 | agent.minor = store.minorVersionReplacement.expand(m); 236 | } else if (m.size() > 3) { 237 | agent.minor = m.get(3); 238 | } 239 | if (!store.patchVersionReplacement.empty()) { 240 | agent.patch = store.patchVersionReplacement.expand(m); 241 | } else if (m.size() > 4) { 242 | agent.patch = m.get(4); 243 | } 244 | if (m.size() == 6 && (m.get(5).empty() || m.get(5)[0] != '.')) { 245 | agent.patch_minor = m.get(5); 246 | } 247 | } 248 | 249 | uap_cpp::Agent parse_browser_impl(const std::string& ua, 250 | const UAStore* ua_store) { 251 | uap_cpp::Agent browser; 252 | 253 | auto snippets = ua_store->browserSnippetIndex.getSnippets(ua); 254 | 255 | std::set regexps; 256 | ua_store->browserMapping.getExpressions(snippets, regexps); 257 | 258 | for (const auto& entry : regexps) { 259 | const auto& b = *entry; 260 | thread_local uap_cpp::Match m; 261 | if (b.regExpr.match(ua, m)) { 262 | fill_agent(browser, b, m); 263 | break; 264 | } 265 | } 266 | 267 | return browser; 268 | } 269 | 270 | uap_cpp::Agent parse_os_impl(const std::string& ua, const UAStore* ua_store) { 271 | uap_cpp::Agent os; 272 | 273 | auto snippets = ua_store->osSnippetIndex.getSnippets(ua); 274 | 275 | std::set regexps; 276 | ua_store->osMapping.getExpressions(snippets, regexps); 277 | 278 | for (const auto& entry : regexps) { 279 | const auto& o = *entry; 280 | thread_local uap_cpp::Match m; 281 | if (o.regExpr.match(ua, m)) { 282 | fill_agent(os, o, m); 283 | break; 284 | } 285 | } 286 | 287 | return os; 288 | } 289 | 290 | } // namespace 291 | 292 | namespace uap_cpp { 293 | 294 | UserAgentParser::UserAgentParser(const std::string& regexes_file_path) 295 | : regexes_file_path_{regexes_file_path} { 296 | ua_store_ = new UAStore(regexes_file_path); 297 | } 298 | 299 | UserAgentParser::~UserAgentParser() { 300 | delete static_cast(ua_store_); 301 | } 302 | 303 | UserAgent UserAgentParser::parse(const std::string& ua) const noexcept { 304 | const auto ua_store = static_cast(ua_store_); 305 | 306 | try { 307 | const auto device = parse_device_impl(ua, ua_store); 308 | const auto os = parse_os_impl(ua, ua_store); 309 | const auto browser = parse_browser_impl(ua, ua_store); 310 | return {device, os, browser}; 311 | } catch (...) { 312 | return {Device(), Agent(), Agent()}; 313 | } 314 | } 315 | 316 | Device UserAgentParser::parse_device(const std::string& ua) const noexcept { 317 | try { 318 | return parse_device_impl(ua, static_cast(ua_store_)); 319 | } catch (...) { 320 | return Device(); 321 | } 322 | } 323 | 324 | Agent UserAgentParser::parse_os(const std::string& ua) const noexcept { 325 | try { 326 | return parse_os_impl(ua, static_cast(ua_store_)); 327 | } catch (...) { 328 | return Agent(); 329 | } 330 | } 331 | 332 | Agent UserAgentParser::parse_browser(const std::string& ua) const noexcept { 333 | try { 334 | return parse_browser_impl(ua, static_cast(ua_store_)); 335 | } catch (...) { 336 | return Agent(); 337 | } 338 | } 339 | 340 | DeviceType UserAgentParser::device_type(const std::string& ua) noexcept { 341 | // https://gist.github.com/dalethedeveloper/1503252/931cc8b613aaa930ef92a4027916e6687d07feac 342 | static const uap_cpp::Pattern rx_mob( 343 | "Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-" 344 | "Accelerated|(hpw|web)OS|Fennec|Minimo|Opera " 345 | "M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune"); 346 | static const uap_cpp::Pattern rx_tabl( 347 | "(tablet|ipad|playbook|silk)|(android.*)", false); 348 | thread_local uap_cpp::Match m; 349 | try { 350 | if (rx_tabl.match(ua, m) && 351 | m.get(2).find("Mobile") == std::string::npos) { 352 | return DeviceType::kTablet; 353 | } else if (rx_mob.match(ua, m)) { 354 | return DeviceType::kMobile; 355 | } 356 | return DeviceType::kDesktop; 357 | } catch (...) { 358 | return DeviceType::kUnknown; 359 | } 360 | } 361 | 362 | } // namespace uap_cpp 363 | -------------------------------------------------------------------------------- /UaParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace uap_cpp { 6 | 7 | struct Generic { 8 | Generic() : family("Other") {} 9 | std::string family; 10 | }; 11 | 12 | struct Device : Generic { 13 | std::string model; 14 | std::string brand; 15 | }; 16 | 17 | struct Agent : Generic { 18 | std::string major; 19 | std::string minor; 20 | std::string patch; 21 | std::string patch_minor; 22 | 23 | std::string toString() const { return family + " " + toVersionString(); } 24 | 25 | std::string toVersionString() const { 26 | return (major.empty() ? "0" : major) + "." + (minor.empty() ? "0" : minor) + 27 | "." + (patch.empty() ? "0" : patch); 28 | } 29 | }; 30 | 31 | struct UserAgent { 32 | Device device; 33 | 34 | Agent os; 35 | Agent browser; 36 | 37 | std::string toFullString() const { 38 | return browser.toString() + "/" + os.toString(); 39 | } 40 | 41 | bool isSpider() const { return device.family == "Spider"; } 42 | }; 43 | 44 | enum class DeviceType { kUnknown = 0, kDesktop, kMobile, kTablet }; 45 | 46 | class UserAgentParser { 47 | public: 48 | explicit UserAgentParser(const std::string& regexes_file_path); 49 | 50 | UserAgent parse(const std::string&) const noexcept; 51 | 52 | Device parse_device(const std::string&) const noexcept; 53 | Agent parse_os(const std::string&) const noexcept; 54 | Agent parse_browser(const std::string&) const noexcept; 55 | 56 | static DeviceType device_type(const std::string&) noexcept; 57 | 58 | ~UserAgentParser(); 59 | 60 | private: 61 | const std::string regexes_file_path_; 62 | const void* ua_store_; 63 | }; 64 | 65 | } // namespace uap_cpp 66 | -------------------------------------------------------------------------------- /UaParser.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9} 24 | Win32Proj 25 | uapcpp 26 | 10.0.17763.0 27 | UaParser 28 | 29 | 30 | 31 | StaticLibrary 32 | true 33 | v141 34 | Unicode 35 | 36 | 37 | StaticLibrary 38 | false 39 | v141 40 | true 41 | Unicode 42 | 43 | 44 | StaticLibrary 45 | true 46 | v141 47 | Unicode 48 | 49 | 50 | StaticLibrary 51 | false 52 | v141 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | C:\boost_1_69_0;C:\yaml-cpp\include;$(IncludePath) 77 | 78 | 79 | true 80 | C:\boost_1_69_0;C:\yaml-cpp\include;$(IncludePath) 81 | 82 | 83 | false 84 | C:\boost_1_69_0;C:\yaml-cpp\include;$(IncludePath) 85 | 86 | 87 | false 88 | C:\boost_1_69_0;C:\yaml-cpp\include;$(IncludePath) 89 | 90 | 91 | 92 | NotUsing 93 | Level3 94 | Disabled 95 | true 96 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 97 | true 98 | 99 | 100 | Windows 101 | true 102 | 103 | 104 | 105 | 106 | NotUsing 107 | Level3 108 | Disabled 109 | true 110 | _DEBUG;_LIB;%(PreprocessorDefinitions) 111 | true 112 | 113 | 114 | Windows 115 | true 116 | 117 | 118 | 119 | 120 | NotUsing 121 | Level3 122 | MaxSpeed 123 | true 124 | true 125 | true 126 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 127 | true 128 | 129 | 130 | Windows 131 | true 132 | true 133 | true 134 | 135 | 136 | 137 | 138 | NotUsing 139 | Level3 140 | MaxSpeed 141 | true 142 | true 143 | true 144 | NDEBUG;_LIB;%(PreprocessorDefinitions) 145 | true 146 | 147 | 148 | Windows 149 | true 150 | true 151 | true 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /UaParserTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "UaParser.h" 5 | #include "internal/AlternativeExpander.h" 6 | #include "internal/Pattern.h" 7 | #include "internal/ReplaceTemplate.h" 8 | #include "internal/SnippetIndex.h" 9 | #ifdef WITH_MT_TEST 10 | #include 11 | #endif // WITH_MT_TEST 12 | 13 | namespace { 14 | 15 | const std::string UA_CORE_DIR = "./uap-core"; 16 | 17 | const uap_cpp::UserAgentParser g_ua_parser(UA_CORE_DIR + "/regexes.yaml"); 18 | 19 | TEST(UserAgentParser, basic) { 20 | const auto uagent = g_ua_parser.parse( 21 | "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) " 22 | "AppleWebKit/534.46 " 23 | "(KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3"); 24 | ASSERT_EQ("Mobile Safari", uagent.browser.family); 25 | ASSERT_EQ("5", uagent.browser.major); 26 | ASSERT_EQ("1", uagent.browser.minor); 27 | ASSERT_EQ("", uagent.browser.patch); 28 | ASSERT_EQ("Mobile Safari 5.1.0", uagent.browser.toString()); 29 | ASSERT_EQ("5.1.0", uagent.browser.toVersionString()); 30 | 31 | ASSERT_EQ("iOS", uagent.os.family); 32 | ASSERT_EQ("5", uagent.os.major); 33 | ASSERT_EQ("1", uagent.os.minor); 34 | ASSERT_EQ("1", uagent.os.patch); 35 | ASSERT_EQ("iOS 5.1.1", uagent.os.toString()); 36 | ASSERT_EQ("5.1.1", uagent.os.toVersionString()); 37 | 38 | ASSERT_EQ("Mobile Safari 5.1.0/iOS 5.1.1", uagent.toFullString()); 39 | 40 | ASSERT_EQ("iPhone", uagent.device.family); 41 | 42 | ASSERT_FALSE(uagent.isSpider()); 43 | } 44 | 45 | TEST(UserAgentParser, DeviceTypeMobile) { 46 | EXPECT_TRUE(uap_cpp::UserAgentParser::device_type( 47 | "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) " 48 | "AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 " 49 | "Mobile/9B206 Safari/7534.48.3") == 50 | uap_cpp::DeviceType::kMobile); 51 | EXPECT_TRUE( 52 | uap_cpp::UserAgentParser::device_type( 53 | "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-J330FN Build/R16NW) " 54 | "AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 " 55 | "Chrome/11.1.1111.111 Mobile Safari/537.36") == 56 | uap_cpp::DeviceType::kMobile); 57 | } 58 | 59 | TEST(UserAgentParser, DeviceTypeTablet) { 60 | EXPECT_TRUE(uap_cpp::UserAgentParser::device_type( 61 | "Mozilla/5.0 (Linux; U; en-us; KFTT Build/IML74K) " 62 | "AppleWebKit/535.19 (KHTML, like Gecko) Silk/2.0 " 63 | "Safari/535.19 Silk-Accelerated=false") == 64 | uap_cpp::DeviceType::kTablet); 65 | EXPECT_TRUE( 66 | uap_cpp::UserAgentParser::device_type( 67 | "Mozilla/5.0 (Linux; Android 9; SHT-AL09 Build/HUAWEISHT-AL09; wv) " 68 | "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " 69 | "Chrome/79.0.3945.136 Safari/537.36") == 70 | uap_cpp::DeviceType::kTablet); 71 | } 72 | 73 | TEST(UserAgentParser, DeviceTypeDesktop) { 74 | ASSERT_TRUE( 75 | uap_cpp::UserAgentParser::device_type( 76 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.4 (KHTML, like " 77 | "Gecko) Chrome/98 Safari/537.4 (StatusCake)") == 78 | uap_cpp::DeviceType::kDesktop); 79 | } 80 | 81 | TEST(UserAgentParser, DeviceTypeMobileOrTablet) { 82 | const auto test_cases = YAML::LoadFile("./test_device_type_mobile.yaml"); 83 | for (const auto& test : test_cases) { 84 | const auto device_type = 85 | uap_cpp::UserAgentParser::device_type(test.as()); 86 | ASSERT_TRUE(device_type == uap_cpp::DeviceType::kMobile || 87 | device_type == uap_cpp::DeviceType::kTablet); 88 | } 89 | } 90 | 91 | bool has_field(const YAML::Node& root, const std::string& fname) { 92 | const auto& yaml_field = root[fname]; 93 | return yaml_field.IsDefined(); 94 | } 95 | 96 | std::string string_field(const YAML::Node& root, const std::string& fname) { 97 | const auto& yaml_field = root[fname]; 98 | return yaml_field.IsNull() ? "" : yaml_field.as(); 99 | } 100 | 101 | void test_browser_or_os(const std::string file_path, const bool browser) { 102 | auto root = YAML::LoadFile(file_path); 103 | const auto& test_cases = root["test_cases"]; 104 | for (const auto& test : test_cases) { 105 | const auto major = string_field(test, "major"); 106 | const auto minor = string_field(test, "minor"); 107 | const auto patch = string_field(test, "patch"); 108 | const bool has_patch_minor = has_field(test, "patch_minor"); 109 | const auto patch_minor = 110 | has_patch_minor ? string_field(test, "patch_minor") : ""; 111 | const auto family = string_field(test, "family"); 112 | const auto unparsed = string_field(test, "user_agent_string"); 113 | const auto uagent = g_ua_parser.parse(unparsed); 114 | const auto& agent = browser ? uagent.browser : uagent.os; 115 | 116 | EXPECT_EQ(major, agent.major); 117 | EXPECT_EQ(minor, agent.minor); 118 | EXPECT_EQ(patch, agent.patch); 119 | if (has_patch_minor && !browser) { 120 | EXPECT_EQ(patch_minor, agent.patch_minor); 121 | } 122 | EXPECT_EQ(family, agent.family); 123 | } 124 | } 125 | 126 | void test_device(const std::string file_path) { 127 | auto root = YAML::LoadFile(file_path); 128 | const auto& test_cases = root["test_cases"]; 129 | for (const auto& test : test_cases) { 130 | const auto unparsed = string_field(test, "user_agent_string"); 131 | const auto uagent = g_ua_parser.parse(unparsed); 132 | const auto family = string_field(test, "family"); 133 | const auto brand = string_field(test, "brand"); 134 | const auto model = string_field(test, "model"); 135 | 136 | EXPECT_EQ(family, uagent.device.family); 137 | EXPECT_EQ(brand, uagent.device.brand); 138 | EXPECT_EQ(model, uagent.device.model); 139 | } 140 | } 141 | 142 | TEST(OsVersion, test_os) { 143 | test_browser_or_os(UA_CORE_DIR + "/tests/test_os.yaml", false); 144 | } 145 | 146 | TEST(OsVersion, test_ua) { 147 | test_browser_or_os(UA_CORE_DIR + "/tests/test_ua.yaml", true); 148 | } 149 | 150 | TEST(BrowserVersion, firefox_user_agent_strings) { 151 | test_browser_or_os( 152 | UA_CORE_DIR + "/test_resources/firefox_user_agent_strings.yaml", true); 153 | } 154 | 155 | TEST(BrowserVersion, opera_mini_user_agent_strings) { 156 | test_browser_or_os( 157 | UA_CORE_DIR + "/test_resources/opera_mini_user_agent_strings.yaml", true); 158 | } 159 | 160 | TEST(BrowserVersion, pgts_browser_list) { 161 | test_browser_or_os(UA_CORE_DIR + "/test_resources/pgts_browser_list.yaml", 162 | true); 163 | } 164 | 165 | TEST(OsVersion, additional_os_tests) { 166 | test_browser_or_os(UA_CORE_DIR + "/test_resources/additional_os_tests.yaml", 167 | false); 168 | } 169 | 170 | TEST(BrowserVersion, podcasting_user_agent_strings) { 171 | test_browser_or_os( 172 | UA_CORE_DIR + "/test_resources/podcasting_user_agent_strings.yaml", true); 173 | } 174 | 175 | TEST(DeviceFamily, test_device) { 176 | test_device(UA_CORE_DIR + "/tests/test_device.yaml"); 177 | } 178 | 179 | #ifdef WITH_MT_TEST 180 | namespace { 181 | 182 | void do_multithreaded_test(const std::function& work) { 183 | static constexpr int NUM_WORKERS = 4; 184 | 185 | std::vector> workers; 186 | for (int i = 0; i < NUM_WORKERS; ++i) { 187 | workers.push_back(std::async(std::launch::async, [&work]() { work(); })); 188 | } 189 | for (auto& worker : workers) { 190 | worker.wait(); 191 | } 192 | } 193 | 194 | } // namespace 195 | 196 | TEST(OsVersion, test_os_mt) { 197 | do_multithreaded_test( 198 | [] { test_browser_or_os(UA_CORE_DIR + "/tests/test_os.yaml", false); }); 199 | } 200 | 201 | TEST(OsVersion, test_ua_mt) { 202 | do_multithreaded_test( 203 | [] { test_browser_or_os(UA_CORE_DIR + "/tests/test_ua.yaml", true); }); 204 | } 205 | 206 | TEST(BrowserVersion, firefox_user_agent_strings_mt) { 207 | do_multithreaded_test([] { 208 | test_browser_or_os( 209 | UA_CORE_DIR + "/test_resources/firefox_user_agent_strings.yaml", true); 210 | }); 211 | } 212 | 213 | TEST(BrowserVersion, opera_mini_user_agent_strings_mt) { 214 | do_multithreaded_test([] { 215 | test_browser_or_os( 216 | UA_CORE_DIR + "/test_resources/opera_mini_user_agent_strings.yaml", 217 | true); 218 | }); 219 | } 220 | 221 | TEST(BrowserVersion, pgts_browser_list_mt) { 222 | do_multithreaded_test([] { 223 | test_browser_or_os(UA_CORE_DIR + "/test_resources/pgts_browser_list.yaml", 224 | true); 225 | }); 226 | } 227 | 228 | TEST(OsVersion, additional_os_tests_mt) { 229 | do_multithreaded_test([] { 230 | test_browser_or_os(UA_CORE_DIR + "/test_resources/additional_os_tests.yaml", 231 | false); 232 | }); 233 | } 234 | 235 | TEST(BrowserVersion, podcasting_user_agent_strings_mt) { 236 | do_multithreaded_test([] { 237 | test_browser_or_os( 238 | UA_CORE_DIR + "/test_resources/podcasting_user_agent_strings.yaml", 239 | true); 240 | }); 241 | } 242 | 243 | TEST(DeviceFamily, test_device_mt) { 244 | do_multithreaded_test( 245 | [] { test_device(UA_CORE_DIR + "/tests/test_device.yaml"); }); 246 | } 247 | #endif // WITH_MT_TEST 248 | 249 | void test_snippets(const std::string& expression, 250 | std::vector should_match) { 251 | uap_cpp::SnippetIndex index; 252 | auto snippet_set = index.registerSnippets(expression); 253 | auto snippet_string_map = index.getRegisteredSnippets(); 254 | 255 | std::vector snippet_strings; 256 | for (auto snippetId : snippet_set) { 257 | auto it = snippet_string_map.find(snippetId); 258 | ASSERT_NE(it, snippet_string_map.end()); 259 | snippet_strings.emplace_back(it->second); 260 | } 261 | 262 | EXPECT_EQ(snippet_strings, should_match); 263 | } 264 | 265 | TEST(SnippetIndex, snippets) { 266 | test_snippets("foo.bar", {"foo", "bar"}); 267 | test_snippets("foodbar", {"foodbar"}); 268 | test_snippets("food?bar", {"foo", "bar"}); 269 | test_snippets("foo.?bar", {"foo", "bar"}); 270 | test_snippets("foo\\.bar", {"foo", ".bar"}); 271 | test_snippets("(foo)", {"foo"}); 272 | test_snippets("toto(foo|bar)tata", {"toto", "tata"}); 273 | test_snippets("toto(foo)tata", {"toto", "foo", "tata"}); 274 | test_snippets("toto(foo)?tata", {"toto", "tata"}); 275 | test_snippets("toto(foo)*tata", {"toto", "tata"}); 276 | test_snippets("toto(foo){0,10}tata", {"toto", "tata"}); 277 | test_snippets("toto(foo){0,}tata", {"toto", "tata"}); 278 | test_snippets("toto(foo){1,10}tata", {"toto", "foo", "tata"}); 279 | test_snippets("toto(foo){1,}tata", {"toto", "foo", "tata"}); 280 | test_snippets("foo[abc]bar", {"foo", "bar"}); 281 | test_snippets("foo[abc]+bar", {"foo", "bar"}); 282 | test_snippets("foo|bar", {}); 283 | test_snippets("foo[|]bar", {"foo", "bar"}); 284 | test_snippets("[(foo)]bar", {"bar"}); 285 | test_snippets("[]foo]bar", {"bar"}); 286 | test_snippets("(foo[abc)])bar", {"foo", "bar"}); 287 | test_snippets("(foo(abc))bar", {"foo", "abc", "bar"}); 288 | test_snippets("Foo.bAR", {"foo", "bar"}); 289 | test_snippets("/(\\d+)\\.?foo(\\d+)", {"foo"}); 290 | test_snippets("(?:foo|bar);.*(baz)/(\\d+)\\.(\\d+)", {"baz"}); 291 | } 292 | 293 | void test_expand(const std::string& expression, 294 | std::vector should_match) { 295 | EXPECT_EQ(uap_cpp::AlternativeExpander::expand(expression), should_match); 296 | } 297 | 298 | TEST(AlternativeExpander, expansions) { 299 | test_expand("a", {"a"}); 300 | test_expand("(a)", {"(a)"}); 301 | test_expand("(a|b)", {"(a)", "(b)"}); 302 | test_expand("(?:a|b)", {"(?:a)", "(?:b)"}); 303 | test_expand("(a|b)?", {"(a|b)?"}); 304 | test_expand("(a|b)+", {"(a)+", "(b)+"}); 305 | test_expand("(a|b)*", {"(a|b)*"}); 306 | test_expand("(a|b){0,2}", {"(a|b){0,2}"}); 307 | test_expand("a|b", {"a", "b"}); 308 | test_expand("x(a|b)y", {"x(a)y", "x(b)y"}); 309 | test_expand("(a|b)yz", {"(a)yz", "(b)yz"}); 310 | test_expand("xa|by", {"xa", "by"}); 311 | test_expand("(a|b)(c|d)", {"(a)(c)", "(a)(d)", "(b)(c)", "(b)(d)"}); 312 | test_expand("(a(b|c)|d)", {"(a(b))", "(a(c))", "(d)"}); 313 | test_expand("(a", {"(a"}); 314 | test_expand("(a|b", {"(a|b"}); 315 | test_expand("((a", {"((a"}); 316 | test_expand("((a|b", {"((a|b"}); 317 | test_expand("(a((a|b", {"(a((a|b"}); 318 | test_expand("[ab]", {"[ab]"}); 319 | test_expand("[(|)]", {"[(|)]"}); 320 | test_expand("[(a|a)]", {"[(a|a)]"}); 321 | test_expand("a(|)b", {"a()b", "a()b"}); 322 | test_expand("([ab]|[bc])", {"([ab])", "([bc])"}); 323 | test_expand("[[](a|b)", {"[[](a)", "[[](b)"}); 324 | test_expand("[]abc](a|b)", {"[]abc](a)", "[]abc](b)"}); 325 | } 326 | 327 | std::string match_and_expand(const std::string& expression, 328 | const std::string& input_string, 329 | const std::string& replace_template) { 330 | uap_cpp::Pattern p(expression); 331 | uap_cpp::Match m; 332 | if (p.match(input_string, m)) { 333 | return uap_cpp::ReplaceTemplate(replace_template).expand(m); 334 | } 335 | return ""; 336 | } 337 | 338 | TEST(ReplaceTemplate, expansions) { 339 | EXPECT_EQ(match_and_expand("something", "other", "foo"), ""); 340 | EXPECT_EQ(match_and_expand("something", "something", "foo"), "foo"); 341 | EXPECT_EQ(match_and_expand("some(thing)", "something", "no$1"), "nothing"); 342 | EXPECT_EQ(match_and_expand("so([Mm])eth(ing)", "that's something!", "$1$2$3"), 343 | "ming"); 344 | EXPECT_EQ(match_and_expand("([^ ]+) (.+)", "a b", "$2-$1"), "b-a"); 345 | } 346 | } // namespace 347 | 348 | int main(int argc, char** argv) { 349 | testing::InitGoogleTest(&argc, argv); 350 | return RUN_ALL_TESTS(); 351 | } 352 | -------------------------------------------------------------------------------- /UaParserTest.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {0903c4b8-b080-40ef-aab0-c2c662e1c9b4} 23 | Win32Proj 24 | 10.0.17763.0 25 | Application 26 | v141 27 | Unicode 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | C:\yaml-cpp\include;$(IncludePath) 37 | C:\boost_1_69_0\stage\lib;C:\yaml-cpp\lib\x86;$(LibraryPath) 38 | 39 | 40 | C:\yaml-cpp\include;$(IncludePath) 41 | C:\boost_1_69_0\stage\lib;C:\yaml-cpp\lib\x86;$(LibraryPath) 42 | 43 | 44 | C:\yaml-cpp\include;$(IncludePath) 45 | C:\boost_1_69_0\stage\lib;C:\yaml-cpp\lib\x64;$(LibraryPath) 46 | 47 | 48 | C:\yaml-cpp\include;$(IncludePath) 49 | C:\boost_1_69_0\stage\lib;C:\yaml-cpp\lib\x64;$(LibraryPath) 50 | 51 | 52 | 53 | 54 | 55 | 56 | {fe8d697c-f3db-4c20-b978-834fb04ae9f9} 57 | 58 | 59 | 60 | 61 | Designer 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | NotUsing 72 | pch.h 73 | Disabled 74 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 75 | true 76 | EnableFastChecks 77 | MultiThreadedDebugDLL 78 | Level3 79 | 80 | 81 | true 82 | Console 83 | libyaml-cppmdd.lib;%(AdditionalDependencies) 84 | 85 | 86 | 87 | 88 | NotUsing 89 | pch.h 90 | Disabled 91 | X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | EnableFastChecks 94 | MultiThreadedDebugDLL 95 | Level3 96 | 97 | 98 | true 99 | Console 100 | libyaml-cppmdd.lib;%(AdditionalDependencies) 101 | 102 | 103 | 104 | 105 | NotUsing 106 | pch.h 107 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | MultiThreadedDLL 109 | Level3 110 | ProgramDatabase 111 | 112 | 113 | true 114 | Console 115 | true 116 | true 117 | libyaml-cppmd.lib;%(AdditionalDependencies) 118 | 119 | 120 | 121 | 122 | NotUsing 123 | pch.h 124 | X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 125 | MultiThreadedDLL 126 | Level3 127 | ProgramDatabase 128 | 129 | 130 | true 131 | Console 132 | true 133 | true 134 | libyaml-cppmd.lib;%(AdditionalDependencies) 135 | 136 | 137 | 138 | 139 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | Performance benchmarks 2 | ====================== 3 | 4 | Results of `make bench` on two different machines: 5 | 6 | | Processor | Compiler | Real time | User CPU time | System CPU time | 7 | | -------------------- | ------------------- | --------- | ------------- | --------------- | 8 | | Intel Core i7 2.2GHz | AppleClang 10.0.1 | 39.57 | 39.50 | 0.04 | 9 | | Intel N3700 1.6GHz | GCC 8.3 | 98.79 | 98.75 | 0.02 | 10 | 11 | The benchmarks use a realistic set of user agent strings, parsed 1000 times each (to make the numbers more reliable, and to offset the initial setup). 12 | -------------------------------------------------------------------------------- /benchmarks/UaParserBench.cpp: -------------------------------------------------------------------------------- 1 | #include "../UaParser.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char* argv[]) { 8 | if (argc != 4) { 9 | printf("Usage: %s \n", argv[0]); 10 | return -1; 11 | } 12 | 13 | std::vector input; 14 | { 15 | std::ifstream infile(argv[2]); 16 | std::string line; 17 | while (std::getline(infile, line)) { 18 | input.push_back(line); 19 | } 20 | } 21 | 22 | uap_cpp::UserAgentParser p(argv[1]); 23 | 24 | int n = atoi(argv[3]); 25 | for (int i = 0; i < n; i++) { 26 | for (const auto& user_agent_string : input) { 27 | p.parse(user_agent_string); 28 | } 29 | } 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /internal/AlternativeExpander.cpp: -------------------------------------------------------------------------------- 1 | #include "AlternativeExpander.h" 2 | 3 | #include "StringUtils.h" 4 | 5 | namespace uap_cpp { 6 | 7 | namespace { 8 | 9 | bool is_negative_lookahead_operator(const StringView& view) { 10 | const char* s = view.start(); 11 | if (view.isEnd(s) || view.isEnd(s + 1)) { 12 | return false; 13 | } 14 | return s[0] == '?' && s[1] == '!'; 15 | } 16 | 17 | bool is_non_capture_operator(const StringView& view) { 18 | const char* s = view.start(); 19 | if (view.isEnd(s) || view.isEnd(s + 1)) { 20 | return false; 21 | } 22 | return s[0] == '?' && s[1] == ':'; 23 | } 24 | 25 | typedef std::vector Stack; 26 | 27 | void expand(const StringView&, 28 | std::string prefix, 29 | Stack next_stack, 30 | std::vector&); 31 | 32 | bool expand_root_level_alternatives(const StringView& view, 33 | std::string& prefix, 34 | Stack& next, 35 | std::vector& out) { 36 | const char* s = view.start(); 37 | int level = 0; 38 | bool prev_was_backslash = false; 39 | while (!view.isEnd(s)) { 40 | if (!prev_was_backslash) { 41 | if (*s == '(') { 42 | ++level; 43 | } else if (*s == ')') { 44 | if (level > 0) { 45 | --level; 46 | } 47 | } else if (*s == '[') { 48 | const char* closing_parenthesis = get_closing_parenthesis(view.from(s)); 49 | if (closing_parenthesis) { 50 | // Skip character-level alternative block 51 | s = closing_parenthesis; 52 | continue; 53 | } 54 | } 55 | 56 | if (level == 0 && *s == '|') { 57 | // Go through alternative on the left 58 | expand(view.to(s), prefix, next, out); 59 | 60 | // Go through alternative(s) on the right 61 | expand(view.from(s + 1), std::move(prefix), std::move(next), out); 62 | 63 | return true; 64 | } 65 | } 66 | 67 | prev_was_backslash = *s == '\\' && !prev_was_backslash; 68 | ++s; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | bool expand_root_level_parentheses(const StringView& view, 75 | std::string& prefix, 76 | Stack& next, 77 | std::vector& out) { 78 | const char* s = view.start(); 79 | int level = 0; 80 | bool prev_was_backslash = false; 81 | while (!view.isEnd(s)) { 82 | if (!prev_was_backslash) { 83 | if (*s == '(') { 84 | ++level; 85 | if (level == 1) { 86 | const char* closing_parenthesis = 87 | get_closing_parenthesis(view.from(s)); 88 | if (!closing_parenthesis) { 89 | // Bad expression 90 | --level; 91 | ++s; 92 | continue; 93 | } 94 | 95 | if (is_optional_operator(view.from(closing_parenthesis + 1)) || 96 | is_negative_lookahead_operator(view.from(s + 1))) { 97 | // Continue after parentheses 98 | s = closing_parenthesis; 99 | continue; 100 | } 101 | 102 | // Add ( or (?: to prefix 103 | const char* inner_start = s + 1; 104 | if (is_non_capture_operator(view.from(inner_start))) { 105 | inner_start += 2; 106 | } 107 | prefix.append(view.start(), inner_start - view.start()); 108 | 109 | next.emplace_back(view.from(closing_parenthesis)); 110 | 111 | // Recursively go through what is enclosed by parentheses 112 | expand(StringView(inner_start, closing_parenthesis), 113 | std::move(prefix), 114 | std::move(next), 115 | out); 116 | return true; 117 | } 118 | } else if (*s == ')') { 119 | if (level > 0) { 120 | --level; 121 | } 122 | } else if (*s == '[') { 123 | const char* closing_parenthesis = get_closing_parenthesis(view.from(s)); 124 | if (closing_parenthesis) { 125 | // Skip character-level alternative block 126 | s = closing_parenthesis; 127 | continue; 128 | } 129 | } 130 | } 131 | 132 | prev_was_backslash = *s == '\\' && !prev_was_backslash; 133 | ++s; 134 | } 135 | 136 | // No alternatives or parentheses, so add to to prefix 137 | prefix.append(view.start(), s - view.start()); 138 | 139 | return false; 140 | } 141 | 142 | void expand(const StringView& view, 143 | std::string prefix, 144 | Stack next, 145 | std::vector& out) { 146 | if (expand_root_level_alternatives(view, prefix, next, out)) { 147 | // Root-level alternatives were handled recursively 148 | return; 149 | } 150 | 151 | if (expand_root_level_parentheses(view, prefix, next, out)) { 152 | // Root-level parentheses where handled recursively 153 | return; 154 | } 155 | 156 | if (next.empty()) { 157 | // Reached end of string, add what has been collected 158 | out.emplace_back(std::move(prefix)); 159 | } else { 160 | // Pop the stack and continue with rest of string 161 | StringView next_view = next.back(); 162 | next.pop_back(); 163 | 164 | expand(next_view, std::move(prefix), std::move(next), out); 165 | } 166 | } 167 | 168 | } // namespace 169 | 170 | std::vector AlternativeExpander::expand( 171 | const StringView& expression) { 172 | std::vector out; 173 | std::string prefix; 174 | Stack next; 175 | uap_cpp::expand(expression, std::move(prefix), std::move(next), out); 176 | return out; 177 | } 178 | 179 | } // namespace uap_cpp 180 | -------------------------------------------------------------------------------- /internal/AlternativeExpander.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "StringView.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace uap_cpp { 9 | 10 | /** 11 | * Regular expression preprocessor that expands alternatives into their 12 | * possible combinations. 13 | * 14 | * For example, the expression "(Something|Other)/\d+" will be expanded to the 15 | * the expressions "(Something)/\d+" and "(Other)/\d+". 16 | * 17 | * This allows for a better mapping of mandatory snippets without complicating 18 | * the actual indexing implementation. 19 | */ 20 | class AlternativeExpander { 21 | public: 22 | static std::vector expand(const StringView& expression); 23 | }; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /internal/MakeUnique.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace uap_cpp { 6 | 7 | template 8 | std::unique_ptr make_unique(Args&&... args) { 9 | return std::unique_ptr(new T(std::forward(args)...)); 10 | } 11 | 12 | } // namespace uap_cpp 13 | -------------------------------------------------------------------------------- /internal/Pattern.cpp: -------------------------------------------------------------------------------- 1 | #include "Pattern.h" 2 | 3 | #include "MakeUnique.h" 4 | 5 | namespace uap_cpp { 6 | 7 | Pattern::Pattern() : groupCount_(0) {} 8 | 9 | Pattern::Pattern(const std::string& pattern, bool case_sensitive) 10 | : groupCount_(0) { 11 | assign(pattern, case_sensitive); 12 | } 13 | 14 | void Pattern::assign(const std::string& pattern, bool case_sensitive) { 15 | // Add parentheses around expression for capture group 0 16 | std::string pattern_with_zero_group = "(" + pattern + ")"; 17 | 18 | re2::RE2::Options options; 19 | options.set_case_sensitive(case_sensitive); 20 | 21 | regex_ = uap_cpp::make_unique(pattern_with_zero_group, options); 22 | 23 | groupCount_ = regex_->NumberOfCapturingGroups(); 24 | if (groupCount_ > Match::MAX_MATCHES) { 25 | groupCount_ = Match::MAX_MATCHES; 26 | } 27 | } 28 | 29 | bool Pattern::match(const std::string& s, Match& m) const { 30 | if (regex_ && re2::RE2::PartialMatchN(s, *regex_, m.argPtrs_, groupCount_)) { 31 | m.count_ = groupCount_; 32 | return true; 33 | } 34 | m.count_ = 0; 35 | return false; 36 | } 37 | 38 | Match::Match() { 39 | for (size_t i = 0; i < MAX_MATCHES; i++) { 40 | args_[i] = &strings_[i]; 41 | argPtrs_[i] = &args_[i]; 42 | } 43 | } 44 | 45 | size_t Match::size() const { 46 | return count_; 47 | } 48 | 49 | namespace { 50 | std::string empty_string; 51 | } 52 | 53 | const std::string& Match::get(size_t index) const { 54 | if (index > count_) { 55 | return empty_string; 56 | } 57 | return strings_[index]; 58 | } 59 | 60 | } // namespace uap_cpp 61 | -------------------------------------------------------------------------------- /internal/Pattern.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace uap_cpp { 8 | 9 | class Match; 10 | 11 | /** 12 | * Wrapper around a re2 regular expression 13 | */ 14 | class Pattern { 15 | public: 16 | Pattern(); 17 | Pattern(const std::string&, bool case_sensitive = true); 18 | 19 | void assign(const std::string&, bool case_sensitive = true); 20 | 21 | bool match(const std::string&, Match&) const; 22 | 23 | private: 24 | std::unique_ptr regex_; 25 | size_t groupCount_; 26 | }; 27 | 28 | /** 29 | * Intended to be used with thread_local to avoid initialization cost 30 | */ 31 | class Match { 32 | public: 33 | Match(); 34 | 35 | size_t size() const; 36 | const std::string& get(size_t index) const; 37 | 38 | private: 39 | friend class Pattern; 40 | static constexpr size_t MAX_MATCHES = 10; 41 | std::string strings_[MAX_MATCHES]; 42 | re2::RE2::Arg args_[MAX_MATCHES]; 43 | const re2::RE2::Arg* argPtrs_[MAX_MATCHES]; 44 | size_t count_; 45 | }; 46 | 47 | } // namespace uap_cpp 48 | -------------------------------------------------------------------------------- /internal/README.md: -------------------------------------------------------------------------------- 1 | The snippet index adds a preprocessing layer that reduces the number of expressions to loop over, to save processing time. The way expressions are matched does not change, and still uses the `RE2` classes with the same expressions. 2 | 3 | The idea for the preprocessing layer is that the expressions contain mandatory snippets of text that need to be present for the expression to match, and these snippets can be indexed in a tree structure that can be used to get the expressions that can possibly match for the input string in question. 4 | 5 | ```C++ 6 | SnippetIndex index; 7 | SnippetMapping mapping; 8 | 9 | // Register expression 1 (foo.bar) 10 | { 11 | auto snippetIds = index.registerSnippets("foo.bar"); 12 | 13 | std::cout << "Expression 1 (foo.bar) needs" << std::endl; 14 | for (auto snippetId : snippetIds) { 15 | std::cout << " - snippet " << snippetId << std::endl; 16 | } 17 | 18 | mapping.addMapping(snippetIds, 1); 19 | } 20 | 21 | // Register expression 2 (foodbar) 22 | { 23 | auto snippetIds = index.registerSnippets("foodbar"); 24 | 25 | std::cout << "Expression 2 (foodbar) needs" << std::endl; 26 | for (auto snippetId : snippetIds) { 27 | std::cout << " - snippet " << snippetId << std::endl; 28 | } 29 | 30 | mapping.addMapping(snippetIds, 2); 31 | } 32 | 33 | // Match input strings 34 | for (auto& inputString : {"barfood", "foobar", "foodbar", "foo"}) { 35 | std::cout << "Look up " << inputString << std::endl; 36 | 37 | auto snippetIds = index.getSnippets(inputString); 38 | for (auto snippetId : snippetIds) { 39 | std::cout << " - Found snippet " << snippetId << std::endl; 40 | } 41 | 42 | std::unordered_set expressions; 43 | mapping.getExpressions(snippetIds, expressions); 44 | if (expressions.empty()) { 45 | std::cout << " No expression has all the snippet it needs" << std::endl; 46 | } else { 47 | for (int expressionId : expressions) { 48 | std::cout << " => Expression " << expressionId 49 | << " has all the snippets it needs" << std::endl; 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | ``` 56 | Expression 1 (foo.bar) needs 57 | - snippet 1 58 | - snippet 2 59 | 60 | Expression 2 (foodbar) needs 61 | - snippet 3 62 | 63 | Look up barfood 64 | - Found snippet 1 65 | - Found snippet 2 66 | => Expression 1 has all the snippets it needs 67 | 68 | Look up foobar 69 | - Found snippet 1 70 | - Found snippet 2 71 | => Expression 1 has all the snippets it needs 72 | 73 | Look up foodbar 74 | - Found snippet 1 75 | - Found snippet 2 76 | - Found snippet 3 77 | => Expression 2 has all the snippets it needs 78 | => Expression 1 has all the snippets it needs 79 | 80 | Look up foo 81 | - Found snippet 1 82 | No expression has all the snippet it needs 83 | ``` 84 | 85 | Snippet 1 is `foo`, snippet 2 `bar` and snippet 3 `foodbar`. The numbers are assigned in the order the snippets were first found when the expressions were registered. 86 | 87 | For simplicity and lookup performance, the order of the snippets in the input does not matter. Remember that this is not a matter of exact matching, but a filter to avoid running unnecessary regular expression matchings. 88 | -------------------------------------------------------------------------------- /internal/ReplaceTemplate.cpp: -------------------------------------------------------------------------------- 1 | #include "ReplaceTemplate.h" 2 | 3 | #include "Pattern.h" 4 | 5 | namespace uap_cpp { 6 | 7 | ReplaceTemplate::ReplaceTemplate() : approximateSize_(0) {} 8 | 9 | ReplaceTemplate::ReplaceTemplate(const std::string& replace_template) { 10 | const char* start = replace_template.c_str(); 11 | const char* chunk_start = start; 12 | const char* s = start; 13 | while (*s) { 14 | if (s[0] == '$' && '0' <= s[1] && s[1] <= '9') { 15 | chunks_.push_back( 16 | std::string(chunk_start, static_cast(s - chunk_start))); 17 | matchIndices_.push_back(static_cast(s[1] - '0')); 18 | 19 | chunk_start = s + 2; 20 | s = chunk_start; 21 | } else { 22 | ++s; 23 | } 24 | } 25 | chunks_.push_back( 26 | std::string(chunk_start, static_cast(s - chunk_start))); 27 | 28 | approximateSize_ = replace_template.size() + 15 * (chunks_.size() - 1); 29 | } 30 | 31 | bool ReplaceTemplate::empty() const { 32 | return chunks_.empty(); 33 | } 34 | 35 | std::string ReplaceTemplate::expand(const Match& m) const { 36 | if (chunks_.size() == 1) { 37 | return chunks_[0]; 38 | } 39 | 40 | std::string s; 41 | if (approximateSize_ > 0) { 42 | s.reserve(approximateSize_); 43 | } 44 | 45 | size_t index = 0; 46 | for (const auto& chunk : chunks_) { 47 | if (index > 0) { 48 | s += m.get(matchIndices_[index - 1]); 49 | } 50 | if (!chunk.empty()) { 51 | s += chunk; 52 | } 53 | ++index; 54 | } 55 | 56 | return s; 57 | } 58 | 59 | } // namespace uap_cpp 60 | -------------------------------------------------------------------------------- /internal/ReplaceTemplate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace uap_cpp { 7 | 8 | class Match; 9 | 10 | /** 11 | * Takes a string with $1, $2, etc, to be replaced by matched blocks 12 | */ 13 | class ReplaceTemplate { 14 | public: 15 | ReplaceTemplate(); 16 | ReplaceTemplate(const std::string&); 17 | 18 | bool empty() const; 19 | std::string expand(const Match&) const; 20 | 21 | private: 22 | std::vector chunks_; 23 | std::vector matchIndices_; 24 | size_t approximateSize_; 25 | }; 26 | 27 | } // namespace uap_cpp 28 | -------------------------------------------------------------------------------- /internal/SnippetIndex.cpp: -------------------------------------------------------------------------------- 1 | #include "SnippetIndex.h" 2 | 3 | #include "StringUtils.h" 4 | 5 | namespace uap_cpp { 6 | 7 | namespace { 8 | 9 | inline bool is_snippet_char(char c, bool prev_was_backslash) { 10 | if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 11 | ('0' <= c && c <= '9')) { 12 | // Letters and digits are snippet characters, unless preceded by backslash 13 | return !prev_was_backslash; 14 | } 15 | switch (c) { 16 | case ' ': 17 | case '_': 18 | case '-': 19 | case '/': 20 | case ',': 21 | case ';': 22 | case '=': 23 | case '%': 24 | // These are always snippet characters 25 | return true; 26 | default: 27 | // Other characters are snippet characters if preceded by backslash 28 | return prev_was_backslash; 29 | } 30 | } 31 | 32 | inline bool possibly_skip_block(const char*& s, const StringView& view) { 33 | if (*s == '(' || *s == '[' || *s == '{') { 34 | bool had_alternative_operators = false; 35 | const char* closing_parenthesis = 36 | get_closing_parenthesis(view.from(s), &had_alternative_operators); 37 | if (closing_parenthesis) { 38 | if (*s == '{' || had_alternative_operators || 39 | (*s == '(' && 40 | is_optional_operator(view.from(closing_parenthesis + 1)))) { 41 | // Always skip count blocks {1,2} 42 | // Skip block with alteratives (ab|ba) or [ab] 43 | // Skip optional block (a)? (b)* (a){0,} 44 | s = closing_parenthesis; 45 | return true; 46 | } 47 | } 48 | } 49 | return false; 50 | } 51 | 52 | bool has_root_level_alternatives(const StringView& view) { 53 | const char* s = view.start(); 54 | 55 | bool prev_was_backslash = false; 56 | int level = 0; 57 | while (!view.isEnd(s)) { 58 | if (!prev_was_backslash) { 59 | if (*s == '(') { 60 | ++level; 61 | } else if (*s == ')') { 62 | --level; 63 | } else if (*s == '|' && level == 0) { 64 | return true; 65 | } else if (*s == '[') { 66 | // Must ignore character-level alternatives [a(b|c] 67 | const char* closing_parenthesis = get_closing_parenthesis(view.from(s)); 68 | if (closing_parenthesis) { 69 | s = closing_parenthesis; 70 | } 71 | } 72 | } 73 | prev_was_backslash = *s == '\\' && !prev_was_backslash; 74 | ++s; 75 | } 76 | return false; 77 | } 78 | 79 | inline uint8_t to_byte(char c, bool to_lowercase = true) { 80 | uint8_t b = c; 81 | // TODO: Duplicate nodes instead of having case-insensitive expressions? 82 | if (to_lowercase && ('A' <= c && c <= 'Z')) { 83 | b |= 0x20; 84 | } 85 | return b; 86 | } 87 | 88 | } // namespace 89 | 90 | SnippetIndex::SnippetSet SnippetIndex::registerSnippets( 91 | const StringView& expression) { 92 | SnippetSet out; 93 | 94 | if (has_root_level_alternatives(expression)) { 95 | // Skip whole expression if alternatives on root level a|b 96 | return out; 97 | } 98 | 99 | const char* s = expression.start(); 100 | const char* snippet_start = nullptr; 101 | TrieNode* node = nullptr; 102 | 103 | bool prev_was_backslash = false; 104 | while (!expression.isEnd(s)) { 105 | if (is_snippet_char(*s, prev_was_backslash)) { 106 | if (!node) { 107 | snippet_start = s; 108 | node = &trieRootNode_; 109 | } 110 | 111 | TrieNode*& next_node = node->transitions_[to_byte(*s)]; 112 | if (!next_node) { 113 | next_node = new TrieNode; 114 | next_node->parent_ = node; 115 | } 116 | node = next_node; 117 | } else { 118 | if (node) { 119 | const char* snippet_end = s; 120 | if (is_optional_operator(expression.from(snippet_end))) { 121 | // Do not include optional characters a? a* 122 | --snippet_end; 123 | node = node->parent_; 124 | } 125 | 126 | registerSnippet(snippet_start, snippet_end, node, out); 127 | 128 | snippet_start = nullptr; 129 | node = nullptr; 130 | } 131 | } 132 | 133 | if (!prev_was_backslash) { 134 | possibly_skip_block(s, expression); 135 | } 136 | 137 | prev_was_backslash = *s == '\\' && !prev_was_backslash; 138 | ++s; 139 | } 140 | 141 | if (node) { 142 | registerSnippet(snippet_start, s, node, out); 143 | } 144 | 145 | return out; 146 | } 147 | 148 | void SnippetIndex::registerSnippet(const char* start, 149 | const char* end, 150 | TrieNode* node, 151 | SnippetIndex::SnippetSet& out) { 152 | if (node && end - start > 2) { 153 | if (!node->snippetId_) { 154 | node->snippetId_ = ++maxSnippetId_; 155 | } 156 | out.insert(node->snippetId_); 157 | } 158 | } 159 | 160 | SnippetIndex::TrieNode::~TrieNode() { 161 | for (auto* node : transitions_) { 162 | delete node; 163 | } 164 | } 165 | 166 | SnippetIndex::SnippetSet SnippetIndex::getSnippets( 167 | const StringView& text) const { 168 | SnippetSet out; 169 | 170 | const char* snippet_start = text.start(); 171 | while (!text.isEnd(snippet_start)) { 172 | const char* snippet_end = snippet_start; 173 | const TrieNode* node = &trieRootNode_; 174 | while (node && !text.isEnd(snippet_end)) { 175 | // Every character can be the start of a snippet (actually, only snippet 176 | // characters, but unconditionally looking it up in the array is faster) 177 | node = node->transitions_[to_byte(*snippet_end)]; 178 | if (node && node->snippetId_) { 179 | out.insert(node->snippetId_); 180 | } 181 | ++snippet_end; 182 | } 183 | ++snippet_start; 184 | } 185 | 186 | return out; 187 | } 188 | 189 | namespace { 190 | template 191 | void build_map(const TrieNode& node, const std::string& base_string, Map& map) { 192 | if (node.snippetId_) { 193 | map.insert(std::make_pair(node.snippetId_, base_string)); 194 | } 195 | 196 | for (int i = 0; i < 256; i++) { 197 | auto* next_node = node.transitions_[i]; 198 | if (next_node) { 199 | std::string next_string(base_string); 200 | next_string += static_cast(i); 201 | build_map(*next_node, next_string, map); 202 | } 203 | } 204 | } 205 | } // namespace 206 | 207 | std::unordered_map 208 | SnippetIndex::getRegisteredSnippets() const { 209 | std::unordered_map map; 210 | build_map(trieRootNode_, "", map); 211 | return map; 212 | } 213 | 214 | } // namespace uap_cpp 215 | -------------------------------------------------------------------------------- /internal/SnippetIndex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "StringView.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace uap_cpp { 12 | 13 | /** 14 | * Indexes mandatory snippets in regular expressions. 15 | * 16 | * For example, in "(a)?(bc)+.* /", "bc" and " /" need to be present in the 17 | * input string for the expression to match ("a" is optional, however). This 18 | * class handles indexing snippets in expressions, and quickly returning which 19 | * snippets are present in an input string. 20 | */ 21 | class SnippetIndex { 22 | public: 23 | typedef uint32_t SnippetId; 24 | typedef std::set SnippetSet; 25 | 26 | SnippetSet registerSnippets(const StringView& expression); 27 | SnippetSet getSnippets(const StringView& text) const; 28 | 29 | std::unordered_map getRegisteredSnippets() const; 30 | 31 | private: 32 | struct TrieNode { 33 | ~TrieNode(); 34 | TrieNode* transitions_[256]{nullptr}; 35 | TrieNode* parent_{nullptr}; 36 | SnippetId snippetId_{0}; 37 | }; 38 | TrieNode trieRootNode_; 39 | SnippetId maxSnippetId_{0}; 40 | 41 | void registerSnippet(const char* start, 42 | const char* end, 43 | TrieNode*, 44 | SnippetSet&); 45 | }; 46 | 47 | } // namespace uap_cpp 48 | -------------------------------------------------------------------------------- /internal/SnippetMapping.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "MakeUnique.h" 9 | 10 | namespace uap_cpp { 11 | 12 | /** 13 | * Maps a set of snippets to a set of expressions. 14 | */ 15 | template 16 | class SnippetMapping { 17 | public: 18 | typedef uint32_t SnippetId; 19 | 20 | /** 21 | * Add an expression to the mapping. The snippets all need to be present 22 | * for an expression to match. The snippets need to be ordered. 23 | */ 24 | template 25 | void addMapping(const SnippetSet& snippets, const Expression& expression) { 26 | TrieNode* node = &trieRootNode_; 27 | for (SnippetId snippet : snippets) { 28 | auto& slot = node->transitions_[snippet]; 29 | if (!slot) { 30 | slot = uap_cpp::make_unique(); 31 | } 32 | node = slot.get(); 33 | } 34 | node->expressions_.insert(expression); 35 | } 36 | 37 | /** 38 | * Find expressions covered by the found set of snippets. The expressions 39 | * should not require a snippet that is not in the matched set of snippets. 40 | * The snippet set needs to be ordered the same way as when it was added. 41 | */ 42 | template 43 | void getExpressions(const SnippetSet& snippets, Result& expressions) const { 44 | auto end = snippets.end(); 45 | getExpressionsRecursively( 46 | snippets.begin(), end, trieRootNode_, expressions); 47 | } 48 | 49 | private: 50 | struct TrieNode { 51 | std::unordered_map> transitions_; 52 | std::unordered_set expressions_; 53 | }; 54 | TrieNode trieRootNode_; 55 | 56 | template 57 | void getExpressionsRecursively(Iterator it, 58 | const Iterator& end, 59 | const TrieNode& node, 60 | Result& expressions) const { 61 | if (!node.expressions_.empty()) { 62 | expressions.insert(node.expressions_.begin(), node.expressions_.end()); 63 | } 64 | 65 | while (it != end) { 66 | auto findIt = node.transitions_.find(*it); 67 | ++it; 68 | 69 | if (findIt != node.transitions_.end()) { 70 | getExpressionsRecursively(it, end, *findIt->second, expressions); 71 | } 72 | } 73 | } 74 | }; 75 | 76 | } // namespace uap_cpp 77 | -------------------------------------------------------------------------------- /internal/StringUtils.h: -------------------------------------------------------------------------------- 1 | #include "StringView.h" 2 | 3 | namespace { 4 | 5 | inline char get_corresponding_end_char(char start_char) { 6 | switch (start_char) { 7 | case '(': 8 | return ')'; 9 | case '[': 10 | return ']'; 11 | case '{': 12 | return '}'; 13 | default: 14 | return '\0'; 15 | } 16 | } 17 | 18 | inline const char* get_closing_parenthesis( 19 | const uap_cpp::StringView& view, 20 | bool* had_alternative_operators = nullptr) { 21 | if (had_alternative_operators) { 22 | *had_alternative_operators = false; 23 | } 24 | 25 | const char* s = view.start(); 26 | 27 | char start_char = *s; 28 | char end_char = get_corresponding_end_char(start_char); 29 | if (!end_char) { 30 | return nullptr; 31 | } 32 | ++s; 33 | 34 | int level = 0; 35 | bool may_be_nested = start_char == '('; 36 | if (may_be_nested) { 37 | ++level; 38 | } 39 | 40 | while (!view.isEnd(s)) { 41 | if (*s == '\\') { 42 | ++s; 43 | } else if (*s == end_char) { 44 | if (may_be_nested) { 45 | --level; 46 | } 47 | if (level == 0) { 48 | if (start_char == '[' && had_alternative_operators) { 49 | *had_alternative_operators = true; 50 | } 51 | if (!(start_char == '[' && s - view.start() == 1)) { 52 | return s; 53 | } 54 | } 55 | } else if (*s == start_char) { 56 | if (may_be_nested) { 57 | ++level; 58 | } 59 | } else if (start_char == '(' && *s == '|' && level == 1) { 60 | if (had_alternative_operators) { 61 | *had_alternative_operators = true; 62 | } 63 | } 64 | ++s; 65 | } 66 | return nullptr; 67 | } 68 | 69 | inline bool is_optional_operator(const uap_cpp::StringView& view) { 70 | const char* s = view.start(); 71 | if (view.isEnd(s)) { 72 | return false; 73 | } 74 | if (*s == '{') { 75 | return !view.isEnd(s + 1) && (s[1] == '0' || s[1] == ','); 76 | } 77 | return *s == '*' || *s == '?'; 78 | } 79 | 80 | inline bool is_whitespace(char ch) { 81 | return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; 82 | } 83 | 84 | template 85 | void trim(String& s) { 86 | size_t trim_left = 0; 87 | for (auto it = s.begin(); it != s.end(); ++it) { 88 | if (!is_whitespace(*it)) { 89 | break; 90 | } 91 | ++trim_left; 92 | } 93 | 94 | if (trim_left == s.size()) { 95 | s.clear(); 96 | } else { 97 | size_t trim_right = 0; 98 | for (auto it = s.rbegin(); it != s.rend(); ++it) { 99 | if (!is_whitespace(*it)) { 100 | break; 101 | } 102 | ++trim_right; 103 | } 104 | 105 | if (trim_left > 0 || trim_right > 0) { 106 | if (trim_left == 0) { 107 | s.resize(s.size() - trim_right); 108 | } else { 109 | String copy(s.c_str() + trim_left, s.size() - trim_left - trim_right); 110 | s.swap(copy); 111 | } 112 | } 113 | } 114 | } 115 | 116 | } // namespace 117 | -------------------------------------------------------------------------------- /internal/StringView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace uap_cpp { 4 | 5 | class StringView { 6 | public: 7 | template 8 | StringView(const String& s) : start_(s.data()), end_(start_ + s.size()) { 9 | } 10 | 11 | StringView(const char* start) : start_(start), end_(nullptr) { 12 | } 13 | 14 | StringView(const char* start, const char* end) : start_(start), end_(end) { 15 | } 16 | 17 | const char* start() const { 18 | return start_; 19 | } 20 | 21 | bool isEnd(const char* s) const { 22 | return (end_ && s >= end_) || (!end_ && *s == 0); 23 | } 24 | 25 | StringView from(const char* start) const { 26 | return StringView(start, end_); 27 | } 28 | 29 | StringView to(const char* end) const { 30 | return StringView(start_, end); 31 | } 32 | 33 | private: 34 | const char* start_; 35 | const char* end_; 36 | }; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /test_device_type_mobile.yaml: -------------------------------------------------------------------------------- 1 | - Mozilla/5.0 (Android 7.0; Mobile; LG-M150; rv:68.0) Gecko/68.0 Firefox/68.0 2 | - Mozilla/5.0 (Android 8.0.0; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0 3 | - Mozilla/5.0 (Android 9; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0 4 | - Mozilla/5.0 (Linux; Android 10; PH-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 5 | - Mozilla/5.0 (Linux; Android 10; Pixel 2 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 6 | - Mozilla/5.0 (Linux; Android 10; Pixel 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 7 | - Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QP1A.190711.020.C3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 8 | - Mozilla/5.0 (Linux; Android 10; Pixel XL Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 9 | - Mozilla/5.0 (Linux; Android 10; Pixel XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 10 | - Mozilla/5.0 (Linux; Android 4.0.3; HTC Sensation 4G Build/IML74K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.111 Mobile Safari/537.36 11 | - Mozilla/5.0 (Linux; Android 4.2.2; GT-I9152 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.111 Mobile Safari/537.36 12 | - Mozilla/5.0 (Linux; Android 4.4.2; GT-N5110) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 13 | - Mozilla/5.0 (Linux; Android 4.4.2; RCT6773W22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 14 | - Mozilla/5.0 (Linux; Android 4.4.2; SM-T217S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 15 | - Mozilla/5.0 (Linux; Android 4.4.2; SM-T530NU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 16 | - Mozilla/5.0 (Linux; Android 4.4.2; TegraNote-P1640 Build/KOT49H; en-us) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 17 | - Mozilla/5.0 (Linux; Android 5.0.2; SM-A500H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 18 | - Mozilla/5.0 (Linux; Android 5.0.2; SM-T357T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 19 | - Mozilla/5.0 (Linux; Android 5.0.2; SM-T530NU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 20 | - Mozilla/5.0 (Linux; Android 5.0.2; SM-T530NU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 21 | - Mozilla/5.0 (Linux; Android 5.0; RCT6213W87DK) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 YaBrowser/19.4.1.454.01 Safari/537.36 22 | - Mozilla/5.0 (Linux; Android 5.0; SM-N900T Build/LRX21V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/229.0.0.35.117;] 23 | - Mozilla/5.0 (Linux; Android 5.1.1) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/4.4.1 Chrome/70.0.3538.110 Mobile Safari/537.36 24 | - Mozilla/5.0 (Linux; Android 5.1.1; AFTT Build/LVY48F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.26 25 | - Mozilla/5.0 (Linux; Android 5.1.1; LG-AS330) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 26 | - Mozilla/5.0 (Linux; Android 5.1.1; LGL43AL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 27 | - Mozilla/5.0 (Linux; Android 5.1.1; SAMSUNG SM-G530R7 Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.2 Chrome/67.0.3396.87 Mobile Safari/537.36 28 | - Mozilla/5.0 (Linux; Android 5.1.1; SAMSUNG SM-T377P) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Safari/537.36 29 | - Mozilla/5.0 (Linux; Android 5.1.1; SAMSUNG SM-T900) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Safari/537.36 30 | - Mozilla/5.0 (Linux; Android 5.1.1; SAMSUNG-SM-T337A Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 31 | - Mozilla/5.0 (Linux; Android 5.1.1; SM-G360T1 Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.81 Mobile Safari/537.36 32 | - Mozilla/5.0 (Linux; Android 5.1.1; SM-J320FN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 33 | - Mozilla/5.0 (Linux; Android 5.1.1; SM-T280) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36 34 | - Mozilla/5.0 (Linux; Android 5.1.1; SM-T330NU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 35 | - Mozilla/5.0 (Linux; Android 5.1.1; SM-T670) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 36 | - Mozilla/5.0 (Linux; Android 5.1.1; SM-T670) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 37 | - Mozilla/5.0 (Linux; Android 5.1.1; Vodafone Smart ultra 6 Build/LMY47V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 38 | - Mozilla/5.0 (Linux; Android 5.1; BLU Advance 5.0 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36 39 | - Mozilla/5.0 (Linux; Android 5.1; HTC Desire 626s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 40 | - Mozilla/5.0 (Linux; Android 5.1; HUAWEI LUA-L22 Build/HUAWEILUA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.89 Mobile Safari/537.36 41 | - Mozilla/5.0 (Linux; Android 5.1; NX16A11264) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 42 | - Mozilla/5.0 (Linux; Android 5.1; XT1526) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 43 | - Mozilla/5.0 (Linux; Android 6.0.1; CPH1613) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 44 | - Mozilla/5.0 (Linux; Android 6.0.1; LG-M153 Build/MXB48T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36 45 | - Mozilla/5.0 (Linux; Android 6.0.1; LG-M153) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 46 | - Mozilla/5.0 (Linux; Android 6.0.1; LGLS676) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 47 | - Mozilla/5.0 (Linux; Android 6.0.1; N9136) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 48 | - Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Safari/537.36 49 | - Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-G900I) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 50 | - Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-G900P Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 Chrome/59.0.3071.125 Mobile Safari/537.36 51 | - Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-J700M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 52 | - Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-S327VL) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 53 | - Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG-SM-T377A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 54 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36 55 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3770.101 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 56 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 57 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G550T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 58 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G550T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 59 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G550T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 60 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G900V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Mobile Safari/537.36 61 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-G920A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 62 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-J327P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 63 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-N910S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36 64 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-N920V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36 65 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-T350 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Safari/537.36 66 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-T560NU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 67 | - Mozilla/5.0 (Linux; Android 6.0.1; SM-T800) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 68 | - Mozilla/5.0 (Linux; Android 6.0.1; XT1254) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 69 | - Mozilla/5.0 (Linux; Android 6.0.1; Z798BL Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36 70 | - Mozilla/5.0 (Linux; Android 6.0.1; Z799VL Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/45.0.2454.95 Mobile Safari/537.36 71 | - Mozilla/5.0 (Linux; Android 6.0; 5010X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36 72 | - Mozilla/5.0 (Linux; Android 6.0; CAM-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 73 | - Mozilla/5.0 (Linux; Android 6.0; F3313) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 74 | - Mozilla/5.0 (Linux; Android 6.0; RCT6603W47M7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 75 | - Mozilla/5.0 (Linux; Android 7.0; 5049Z Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36 76 | - Mozilla/5.0 (Linux; Android 7.0; ASUS_A002A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 77 | - Mozilla/5.0 (Linux; Android 7.0; Alcatel_5044C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 78 | - Mozilla/5.0 (Linux; Android 7.0; Astra Young Pro Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36 79 | - Mozilla/5.0 (Linux; Android 7.0; Infinix X571) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 80 | - Mozilla/5.0 (Linux; Android 7.0; LG-H872 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.123 Mobile Safari/537.36 81 | - Mozilla/5.0 (Linux; Android 7.0; LG-K425 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36 82 | - Mozilla/5.0 (Linux; Android 7.0; LG-LS777) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 83 | - Mozilla/5.0 (Linux; Android 7.0; LG-M210) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 84 | - Mozilla/5.0 (Linux; Android 7.0; LG-M430) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 85 | - Mozilla/5.0 (Linux; Android 7.0; LG-TP260 Build/NRD90U; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 86 | - Mozilla/5.0 (Linux; Android 7.0; LG-TP260) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 87 | - Mozilla/5.0 (Linux; Android 7.0; LG-TP450 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 88 | - Mozilla/5.0 (Linux; Android 7.0; LG-V521) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Safari/537.36 89 | - Mozilla/5.0 (Linux; Android 7.0; LG-V521) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 90 | - Mozilla/5.0 (Linux; Android 7.0; LGMP260 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36 91 | - Mozilla/5.0 (Linux; Android 7.0; LGMS210 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36 92 | - Mozilla/5.0 (Linux; Android 7.0; LGMS210) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 93 | - Mozilla/5.0 (Linux; Android 7.0; P00I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 94 | - Mozilla/5.0 (Linux; Android 7.0; RS988) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 95 | - Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-J701F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 96 | - Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-J710F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 97 | - Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-N920T Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.2 Chrome/67.0.3396.87 Mobile Safari/537.36 98 | - Mozilla/5.0 (Linux; Android 7.0; SAMSUNG-SM-G920A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 99 | - Mozilla/5.0 (Linux; Android 7.0; SM-G920P Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 Flipboard/4.2.23/4722,4.2.23.4722 100 | - Mozilla/5.0 (Linux; Android 7.0; SM-G920V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 101 | - Mozilla/5.0 (Linux; Android 7.0; SM-G928V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 102 | - Mozilla/5.0 (Linux; Android 7.0; SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 103 | - Mozilla/5.0 (Linux; Android 7.0; SM-G955U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 104 | - Mozilla/5.0 (Linux; Android 7.0; SM-J327T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 105 | - Mozilla/5.0 (Linux; Android 7.0; SM-J327T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 106 | - Mozilla/5.0 (Linux; Android 7.0; SM-J327T1 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 107 | - Mozilla/5.0 (Linux; Android 7.0; SM-J327T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 108 | - Mozilla/5.0 (Linux; Android 7.0; SM-J327T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 109 | - Mozilla/5.0 (Linux; Android 7.0; SM-N9208) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 110 | - Mozilla/5.0 (Linux; Android 7.0; SM-N920P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 111 | - Mozilla/5.0 (Linux; Android 7.0; SM-N920T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 112 | - Mozilla/5.0 (Linux; Android 7.0; SM-T585) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 113 | - Mozilla/5.0 (Linux; Android 7.0; SM-T810) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Safari/537.36 114 | - Mozilla/5.0 (Linux; Android 7.0; SM-T810) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Safari/537.36 115 | - Mozilla/5.0 (Linux; Android 7.0; SM-T810) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 116 | - Mozilla/5.0 (Linux; Android 7.0; SM-T813) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Safari/537.36 117 | - Mozilla/5.0 (Linux; Android 7.0; SM-T813) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 118 | - Mozilla/5.0 (Linux; Android 7.0; ST1009X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Safari/537.36 119 | - Mozilla/5.0 (Linux; Android 7.0; XT1663) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 120 | - Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Mobile Safari/537.36 121 | - Mozilla/5.0 (Linux; Android 7.1.1; A574BL Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 122 | - Mozilla/5.0 (Linux; Android 7.1.1; A574BL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 123 | - Mozilla/5.0 (Linux; Android 7.1.1; CPH1729 Build/N6F26Q; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 124 | - Mozilla/5.0 (Linux; Android 7.1.1; Coolpad 3632A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 125 | - Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4) Plus Build/NCRS26.58-44-20; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.111 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 126 | - Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36 127 | - Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 128 | - Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.73 Mobile Safari/537.36 129 | - Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 130 | - Mozilla/5.0 (Linux; Android 7.1.1; NX591J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 131 | - Mozilla/5.0 (Linux; Android 7.1.1; REVVLPLUS C3701A Build/143.54.190611.3701A-TMO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 132 | - Mozilla/5.0 (Linux; Android 7.1.1; SAMSUNG SM-J320A) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 133 | - Mozilla/5.0 (Linux; Android 7.1.1; SAMSUNG SM-T550) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Safari/537.36 134 | - Mozilla/5.0 (Linux; Android 7.1.1; SAMSUNG-SM-T377A Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Safari/537.36 135 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-J250F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 136 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-J700T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 137 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-T350) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 138 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-T377T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.73 Safari/537.36 139 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-T550 Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 140 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-T550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 141 | - Mozilla/5.0 (Linux; Android 7.1.1; SM-T560NU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 142 | - Mozilla/5.0 (Linux; Android 7.1.1; X20 Build/N6F26Q; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.100 Mobile Safari/537.36 143 | - Mozilla/5.0 (Linux; Android 7.1.1; Z851M Build/NMF26V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36 144 | - Mozilla/5.0 (Linux; Android 7.1.1; Z899VL Build/NMF26V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 145 | - Mozilla/5.0 (Linux; Android 7.1.1; Z982 Build/NMF26V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3770.143 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 146 | - Mozilla/5.0 (Linux; Android 7.1.1; Z982) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 147 | - Mozilla/5.0 (Linux; Android 7.1.2) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/4.4.1 Chrome/70.0.3538.110 Mobile Safari/537.36 148 | - Mozilla/5.0 (Linux; Android 7.1.2; AFTKMST12 Build/NS6265; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.26 149 | - Mozilla/5.0 (Linux; Android 7.1.2; AFTMM Build/NS6265; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.26 150 | - Mozilla/5.0 (Linux; Android 7.1.2; AFTN Build/NS6265; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.26 151 | - Mozilla/5.0 (Linux; Android 7.1.2; KFKAWI Build/NS6301; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Safari/537.36 152 | - Mozilla/5.0 (Linux; Android 7.1.2; LG-SP200) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36 153 | - Mozilla/5.0 (Linux; Android 7.1.2; LG-SP200) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 154 | - Mozilla/5.0 (Linux; Android 7.1.2; LM-X210(G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 155 | - Mozilla/5.0 (Linux; Android 7.1.2; LM-X210) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 156 | - Mozilla/5.0 (Linux; Android 7.1.2; RCT6973W43R) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 157 | - Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 158 | - Mozilla/5.0 (Linux; Android 8.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.132 Mobile Safari/537.36 159 | - Mozilla/5.0 (Linux; Android 8.0.0; ASUS_Z01FD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 160 | - Mozilla/5.0 (Linux; Android 8.0.0; AUM-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 161 | - Mozilla/5.0 (Linux; Android 8.0.0; BRAVIA 4K GB Build/OPR2.170623.027.S25; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 162 | - Mozilla/5.0 (Linux; Android 8.0.0; CMR-W09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 163 | - Mozilla/5.0 (Linux; Android 8.0.0; EVA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.73 Mobile Safari/537.36 164 | - Mozilla/5.0 (Linux; Android 8.0.0; G3223) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 165 | - Mozilla/5.0 (Linux; Android 8.0.0; LG-H910) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 166 | - Mozilla/5.0 (Linux; Android 8.0.0; LG-H931) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 167 | - Mozilla/5.0 (Linux; Android 8.0.0; LG-H932) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 168 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-A520F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 169 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G891A Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36 170 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G935T) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 171 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 172 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-J337T Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.2 Chrome/67.0.3396.87 Mobile Safari/537.36 173 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-J737P) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 174 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-N950F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 175 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG-SM-G891A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Mobile Safari/537.36 176 | - Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG-SM-G935A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 177 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-A720F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 178 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G570F Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3683.90 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/231.0.0.39.113;] 179 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G570Y) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 180 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G930T Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 181 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G930V Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 182 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G930VL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 183 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G935F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36 184 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G935P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 185 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G935T Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 186 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G935T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 187 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 188 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.112 Mobile Safari/537.36 189 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 190 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-J330G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 191 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-J337T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 192 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-J737A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 193 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-J737T1 Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36 194 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-J737T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 195 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-N950F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36 196 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 197 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 198 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-N950U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 199 | - Mozilla/5.0 (Linux; Android 8.0.0; SM-S367VL Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 OPT/1.22.80 200 | - Mozilla/5.0 (Linux; Android 8.0.0; VS995) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 201 | - Mozilla/5.0 (Linux; Android 8.0.0; XT1635-02) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 202 | - Mozilla/5.0 (Linux; Android 8.0.0; moto e5 play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 203 | - Mozilla/5.0 (Linux; Android 8.0.0; moto e5 play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 204 | - Mozilla/5.0 (Linux; Android 8.0.0; moto e5 supra) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 205 | - Mozilla/5.0 (Linux; Android 8.0.0; moto g(6)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 206 | - Mozilla/5.0 (Linux; Android 8.1.0; 5041C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 207 | - Mozilla/5.0 (Linux; Android 8.1.0; 6062W) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 208 | - Mozilla/5.0 (Linux; Android 8.1.0; A502DL Build/OPM1.171019.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36 209 | - Mozilla/5.0 (Linux; Android 8.1.0; A502DL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 210 | - Mozilla/5.0 (Linux; Android 8.1.0; BKK-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 211 | - Mozilla/5.0 (Linux; Android 8.1.0; C4 Build/OPM2.171019.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 212 | - Mozilla/5.0 (Linux; Android 8.1.0; Coolpad 3310A Build/3310A.SPRINT.190213.0S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 213 | - Mozilla/5.0 (Linux; Android 8.1.0; Infinix X604 Build/O11019) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 214 | - Mozilla/5.0 (Linux; Android 8.1.0; Joy 1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 215 | - Mozilla/5.0 (Linux; Android 8.1.0; LAVA LE9820) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 216 | - Mozilla/5.0 (Linux; Android 8.1.0; LG-Q710AL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 217 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-Q610(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 218 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-Q710(FGN) Build/OPM1.171019.019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/235.0.0.38.118;] 219 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-Q710(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 220 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-Q710(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 221 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-Q710(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 222 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-Q710(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 223 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-V405) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 224 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X210(G) Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 agentweb/4.0.2 UCBrowser/11.6.4.950 225 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X210(G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 226 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X210(G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.105 Mobile Safari/537.36 227 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X210(G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 228 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X212(G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 229 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X220) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 230 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X220) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 231 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X220PM Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 232 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X410(FG)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 233 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X410(FG)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 234 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X410(FG)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 235 | - Mozilla/5.0 (Linux; Android 8.1.0; LM-X410.FGN Build/OPM1.171019.019) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36 236 | - Mozilla/5.0 (Linux; Android 8.1.0; LML414DL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 237 | - Mozilla/5.0 (Linux; Android 8.1.0; LML713DL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 238 | - Mozilla/5.0 (Linux; Android 8.1.0; Moto G (5S) Plus) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 239 | - Mozilla/5.0 (Linux; Android 8.1.0; One) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36/TansoDL 240 | - Mozilla/5.0 (Linux; Android 8.1.0; RCT6873W42BMF8KC Build/O11019) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 241 | - Mozilla/5.0 (Linux; Android 8.1.0; REVVL 2 Build/OPM1.171019.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36 242 | - Mozilla/5.0 (Linux; Android 8.1.0; REVVL 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 243 | - Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG SM-J727T) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 244 | - Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG SM-J727T1 Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36 245 | - Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG SM-J727T1) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 246 | - Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG SM-T580 Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Safari/537.36 247 | - Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG-SM-J727A Build/M1AJQ; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 248 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-G610F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 249 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J260T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 250 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J260T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 251 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J260T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 252 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J410F Build/M1AJB) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 253 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727P Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36 254 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727T Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36 255 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 256 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.73 Mobile Safari/537.36 257 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727T1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 258 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 259 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-J727V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 260 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-P580) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 261 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-T380) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Safari/537.36 262 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-T580) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Safari/537.36 EdgA/42.0.2.3928 263 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-T580) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 264 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-T580) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Safari/537.36 265 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-T580) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 266 | - Mozilla/5.0 (Linux; Android 8.1.0; SM-T837T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 267 | - Mozilla/5.0 (Linux; Android 8.1.0; TECNO CF8 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/239.0.0.41.152;] 268 | - Mozilla/5.0 (Linux; Android 8.1.0; V1818CA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 269 | - Mozilla/5.0 (Linux; Android 8.1.0; meizu C9 Build/OPM2.171019.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36 270 | - Mozilla/5.0 (Linux; Android 8.1.0; vivo 1724) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 271 | - Mozilla/5.0 (Linux; Android 8.1.0; vivo 1814) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 272 | - Mozilla/5.0 (Linux; Android 9) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 DuckDuckGo/5 273 | - Mozilla/5.0 (Linux; Android 9; 1825) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36 274 | - Mozilla/5.0 (Linux; Android 9; ANE-LX2 Build/HUAWEIANE-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.132 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/236.0.0.40.117;] 275 | - Mozilla/5.0 (Linux; Android 9; BLA-A09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 276 | - Mozilla/5.0 (Linux; Android 9; CLT-L04) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 277 | - Mozilla/5.0 (Linux; Android 9; CPH1911 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/239.0.0.41.152;] 278 | - Mozilla/5.0 (Linux; Android 9; CPH1923 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.111 Mobile Safari/537.36 279 | - Mozilla/5.0 (Linux; Android 9; ELE-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 280 | - Mozilla/5.0 (Linux; Android 9; G8142) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 281 | - Mozilla/5.0 (Linux; Android 9; GM1911) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 282 | - Mozilla/5.0 (Linux; Android 9; GM1917) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 283 | - Mozilla/5.0 (Linux; Android 9; INE-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 284 | - Mozilla/5.0 (Linux; Android 9; LM-G710 Build/PKQ1.181105.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 285 | - Mozilla/5.0 (Linux; Android 9; LM-Q720) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 286 | - Mozilla/5.0 (Linux; Android 9; LM-V405 Build/PKQ1.190202.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.15 287 | - Mozilla/5.0 (Linux; Android 9; LM-V405) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 288 | - Mozilla/5.0 (Linux; Android 9; LM-V500N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 289 | - Mozilla/5.0 (Linux; Android 9; LM-X420) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Mobile Safari/537.36 290 | - Mozilla/5.0 (Linux; Android 9; LM-X420) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 291 | - Mozilla/5.0 (Linux; Android 9; MAR-LX1A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 292 | - Mozilla/5.0 (Linux; Android 9; MI 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 293 | - Mozilla/5.0 (Linux; Android 9; Mi A2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 294 | - Mozilla/5.0 (Linux; Android 9; Moto Z (2)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 295 | - Mozilla/5.0 (Linux; Android 9; Nokia 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 296 | - Mozilla/5.0 (Linux; Android 9; ONEPLUS A6000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 297 | - Mozilla/5.0 (Linux; Android 9; ONEPLUS A6003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 298 | - Mozilla/5.0 (Linux; Android 9; ONEPLUS A6013 Build/PKQ1.180716.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 299 | - Mozilla/5.0 (Linux; Android 9; ONEPLUS A6013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 300 | - Mozilla/5.0 (Linux; Android 9; ONEPLUS A6013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 301 | - Mozilla/5.0 (Linux; Android 9; PAR-AL00 Build/HUAWEIPAR-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/235.0.0.38.118;] 302 | - Mozilla/5.0 (Linux; Android 9; Pixel 2 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 303 | - Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.190105.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 304 | - Mozilla/5.0 (Linux; Android 9; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 305 | - Mozilla/5.0 (Linux; Android 9; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 306 | - Mozilla/5.0 (Linux; Android 9; Pixel 3a XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 307 | - Mozilla/5.0 (Linux; Android 9; REVVLRY ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 308 | - Mozilla/5.0 (Linux; Android 9; RMX1801) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 309 | - Mozilla/5.0 (Linux; Android 9; Redmi 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 310 | - Mozilla/5.0 (Linux; Android 9; Redmi Note 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36 311 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-A102U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 312 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-A505FN) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 313 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-A505GN) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 314 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G892U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 315 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 316 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G955F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36 317 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G955U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 318 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G9600 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36 319 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 320 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G965U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 321 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G970F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 322 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 323 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G973U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36 324 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 325 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G975U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 326 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-J415F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 327 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-J730F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 328 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-J737P) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 329 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-J737T Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.0 Chrome/67.0.3396.87 Mobile Safari/537.36 330 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 331 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N960F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 332 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N960U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 333 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N960U1 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.2 Chrome/67.0.3396.87 Mobile Safari/537.36 334 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N970U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 335 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N975U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 336 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-N975U1) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Mobile Safari/537.36 337 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-T510) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Safari/537.36 338 | - Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-T720) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Safari/537.36 339 | - Mozilla/5.0 (Linux; Android 9; SM-A102U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Mobile Safari/537.36 340 | - Mozilla/5.0 (Linux; Android 9; SM-A102U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 341 | - Mozilla/5.0 (Linux; Android 9; SM-A105M Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/237.0.0.44.120;] 342 | - Mozilla/5.0 (Linux; Android 9; SM-A205G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 343 | - Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 344 | - Mozilla/5.0 (Linux; Android 9; SM-A505F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 345 | - Mozilla/5.0 (Linux; Android 9; SM-A530F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 346 | - Mozilla/5.0 (Linux; Android 9; SM-A530N Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;KAKAOTALK 1908560 347 | - Mozilla/5.0 (Linux; Android 9; SM-A600T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 348 | - Mozilla/5.0 (Linux; Android 9; SM-A605F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 349 | - Mozilla/5.0 (Linux; Android 9; SM-A920F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 350 | - Mozilla/5.0 (Linux; Android 9; SM-G892A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36 351 | - Mozilla/5.0 (Linux; Android 9; SM-G950F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 352 | - Mozilla/5.0 (Linux; Android 9; SM-G950U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 353 | - Mozilla/5.0 (Linux; Android 9; SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36 354 | - Mozilla/5.0 (Linux; Android 9; SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 355 | - Mozilla/5.0 (Linux; Android 9; SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 356 | - Mozilla/5.0 (Linux; Android 9; SM-G950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 357 | - Mozilla/5.0 (Linux; Android 9; SM-G950U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 358 | - Mozilla/5.0 (Linux; Android 9; SM-G955F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 359 | - Mozilla/5.0 (Linux; Android 9; SM-G955U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.73 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 360 | - Mozilla/5.0 (Linux; Android 9; SM-G955U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 361 | - Mozilla/5.0 (Linux; Android 9; SM-G9600) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 362 | - Mozilla/5.0 (Linux; Android 9; SM-G960U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3683.90 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/233.0.0.36.117;] 363 | - Mozilla/5.0 (Linux; Android 9; SM-G960U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 364 | - Mozilla/5.0 (Linux; Android 9; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36 365 | - Mozilla/5.0 (Linux; Android 9; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36 366 | - Mozilla/5.0 (Linux; Android 9; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 367 | - Mozilla/5.0 (Linux; Android 9; SM-G960U1 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 368 | - Mozilla/5.0 (Linux; Android 9; SM-G960U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 369 | - Mozilla/5.0 (Linux; Android 9; SM-G965F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 370 | - Mozilla/5.0 (Linux; Android 9; SM-G965U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36 371 | - Mozilla/5.0 (Linux; Android 9; SM-G965U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 372 | - Mozilla/5.0 (Linux; Android 9; SM-G965U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3921.2 Mobile Safari/537.36 373 | - Mozilla/5.0 (Linux; Android 9; SM-G965U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 374 | - Mozilla/5.0 (Linux; Android 9; SM-G970U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 375 | - Mozilla/5.0 (Linux; Android 9; SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 376 | - Mozilla/5.0 (Linux; Android 9; SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 377 | - Mozilla/5.0 (Linux; Android 9; SM-G970U1 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 378 | - Mozilla/5.0 (Linux; Android 9; SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36 379 | - Mozilla/5.0 (Linux; Android 9; SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 380 | - Mozilla/5.0 (Linux; Android 9; SM-G973U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 381 | - Mozilla/5.0 (Linux; Android 9; SM-G975U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 382 | - Mozilla/5.0 (Linux; Android 9; SM-G975U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 383 | - Mozilla/5.0 (Linux; Android 9; SM-G975U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 384 | - Mozilla/5.0 (Linux; Android 9; SM-G975U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 385 | - Mozilla/5.0 (Linux; Android 9; SM-J260A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 386 | - Mozilla/5.0 (Linux; Android 9; SM-J337P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 387 | - Mozilla/5.0 (Linux; Android 9; SM-J600FN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.89 Mobile Safari/537.36 388 | - Mozilla/5.0 (Linux; Android 9; SM-J600G Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.73 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/238.0.0.41.116;] 389 | - Mozilla/5.0 (Linux; Android 9; SM-J730F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 390 | - Mozilla/5.0 (Linux; Android 9; SM-J737A Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 391 | - Mozilla/5.0 (Linux; Android 9; SM-J737A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36 392 | - Mozilla/5.0 (Linux; Android 9; SM-J737V Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3770.101 Mobile Safari/537.36 [Pinterest/Android] 393 | - Mozilla/5.0 (Linux; Android 9; SM-J737V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 394 | - Mozilla/5.0 (Linux; Android 9; SM-J810M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 395 | - Mozilla/5.0 (Linux; Android 9; SM-N950U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 396 | - Mozilla/5.0 (Linux; Android 9; SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Mobile Safari/537.36 397 | - Mozilla/5.0 (Linux; Android 9; SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 398 | - Mozilla/5.0 (Linux; Android 9; SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.73 Mobile Safari/537.36 399 | - Mozilla/5.0 (Linux; Android 9; SM-N950U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 400 | - Mozilla/5.0 (Linux; Android 9; SM-N960F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 401 | - Mozilla/5.0 (Linux; Android 9; SM-N960U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 402 | - Mozilla/5.0 (Linux; Android 9; SM-N960U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 403 | - Mozilla/5.0 (Linux; Android 9; SM-N960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 404 | - Mozilla/5.0 (Linux; Android 9; SM-N960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36 405 | - Mozilla/5.0 (Linux; Android 9; SM-N960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 406 | - Mozilla/5.0 (Linux; Android 9; SM-N960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 407 | - Mozilla/5.0 (Linux; Android 9; SM-N960U1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 408 | - Mozilla/5.0 (Linux; Android 9; SM-N975U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.73 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 409 | - Mozilla/5.0 (Linux; Android 9; SM-N975U Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 410 | - Mozilla/5.0 (Linux; Android 9; SM-N975U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 411 | - Mozilla/5.0 (Linux; Android 9; SM-N976V Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/240.0.0.38.121;] 412 | - Mozilla/5.0 (Linux; Android 9; SM-S367VL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 413 | - Mozilla/5.0 (Linux; Android 9; SM-S767VL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36 414 | - Mozilla/5.0 (Linux; Android 9; SM-T597P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 415 | - Mozilla/5.0 (Linux; Android 9; SM-T720) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Safari/537.36 416 | - Mozilla/5.0 (Linux; Android 9; TECNO KC8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 417 | - Mozilla/5.0 (Linux; Android 9; VOG-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 418 | - Mozilla/5.0 (Linux; Android 9; cp3705A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36 419 | - Mozilla/5.0 (Linux; Android 9; moto g(6) Build/PPS29.118-15-11; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.92 Mobile Safari/537.36;dailymotion-player-sdk-android 0.1.31 420 | - Mozilla/5.0 (Linux; Android 9; moto g(6) play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 421 | - Mozilla/5.0 (Linux; Android 9; moto g(7) play Build/PCYS29.105-134-1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.132 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/235.0.0.38.118;] 422 | - Mozilla/5.0 (Linux; Android 9; moto g(7) play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36 423 | - Mozilla/5.0 (Linux; Android 9; moto g(7) power) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36 424 | - Mozilla/5.0 (Linux; Android 9; moto g(7) power) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 425 | - Mozilla/5.0 (Linux; Android 9; moto z4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 426 | - Mozilla/5.0 (Linux; Android 9; moto z4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.73 Mobile Safari/537.36 427 | - Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; GT-P3113 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30 428 | - Mozilla/5.0 (Linux; U; Android 4.1.2; ar-ae; GT-I8160 Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 429 | - Mozilla/5.0 (Linux; U; Android 4.2.2; en-us; Nexus 7 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30; DailymotionEmbedSDK 1.0 430 | - Mozilla/5.0 (Linux; U; Android 4.4; en-us; SM-E500H Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 431 | - Mozilla/5.0 (Linux; U; Android 6.0.1; en-us; LGMS550 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/534.30 432 | - Mozilla/5.0 (Linux; U; Android 6.0.1; en-us; SM-J737T1 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/534.30 433 | - Mozilla/5.0 (Linux; U; Android 7.0; TECNO CA6 Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36 OPR/5.3.2254.135058 434 | - Mozilla/5.0 (Linux; U; Android 7.1.2; id-id; Redmi 5A Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/9.5.6 435 | - Mozilla/5.0 (Linux; U; Android 9; in-id; CPH1911 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 OppoBrowser/25.6.0.0.5beta 436 | - Mozilla/5.0 (Linux; U; Android 9; vivo 1904 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 OPR/44.1.2254.143214 437 | -------------------------------------------------------------------------------- /uap-cpp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.572 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UaParser", "UaParser.vcxproj", "{FE8D697C-F3DB-4C20-B978-834FB04AE9F9}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UaParserTest", "UaParserTest.vcxproj", "{0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Debug|x64.ActiveCfg = Debug|x64 19 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Debug|x64.Build.0 = Debug|x64 20 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Debug|x86.ActiveCfg = Debug|Win32 21 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Debug|x86.Build.0 = Debug|Win32 22 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Release|x64.ActiveCfg = Release|x64 23 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Release|x64.Build.0 = Release|x64 24 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Release|x86.ActiveCfg = Release|Win32 25 | {FE8D697C-F3DB-4C20-B978-834FB04AE9F9}.Release|x86.Build.0 = Release|Win32 26 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Debug|x64.ActiveCfg = Debug|x64 27 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Debug|x64.Build.0 = Debug|x64 28 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Debug|x86.ActiveCfg = Debug|Win32 29 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Debug|x86.Build.0 = Debug|Win32 30 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Release|x64.ActiveCfg = Release|x64 31 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Release|x64.Build.0 = Release|x64 32 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Release|x86.ActiveCfg = Release|Win32 33 | {0903C4B8-B080-40EF-AAB0-C2C662E1C9B4}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {EED0A8B3-F9D8-40BD-9716-92AC4390314F} 40 | EndGlobalSection 41 | EndGlobal 42 | --------------------------------------------------------------------------------