Camouflage.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <?php
  2. namespace WhichBrowser\Analyser;
  3. use WhichBrowser\Constants;
  4. use WhichBrowser\Data;
  5. use WhichBrowser\Model\Version;
  6. trait Camouflage
  7. {
  8. private function &detectCamouflage()
  9. {
  10. if ($ua = $this->getHeader('User-Agent')) {
  11. $this
  12. ->detectCamouflagedAndroidBrowser($ua)
  13. ->detectCamouflagedAndroidAsusBrowser($ua)
  14. ->detectCamouflagedAsSafari($ua)
  15. ->detectCamouflagedAsChrome($ua);
  16. }
  17. if (!empty($this->options->useragent)) {
  18. $this->detectCamouflagedUCBrowser($this->options->useragent);
  19. }
  20. if (isset($this->options->engine)) {
  21. $this->detectCamouflagedBasedOnEngines();
  22. }
  23. if (isset($this->options->features)) {
  24. $this->detectCamouflagedBasedOnFeatures();
  25. }
  26. return $this;
  27. }
  28. private function &detectCamouflagedAndroidBrowser($ua)
  29. {
  30. if (preg_match('/Mac OS X 10_6_3; ([^;]+); [a-z]{2}(?:-[a-z]{2})?\)/u', $ua, $match)) {
  31. $this->data->browser->name = 'Android Browser';
  32. $this->data->browser->version = null;
  33. $this->data->browser->mode = 'desktop';
  34. $this->data->os->name = 'Android';
  35. $this->data->os->alias = null;
  36. $this->data->os->version = null;
  37. $this->data->engine->name = 'Webkit';
  38. $this->data->engine->version = null;
  39. $this->data->device->type = 'mobile';
  40. $device = Data\DeviceModels::identify('android', $match[1]);
  41. if ($device->identified) {
  42. $device->identified |= $this->data->device->identified;
  43. $this->data->device = $device;
  44. }
  45. $this->data->features[] = 'foundDevice';
  46. }
  47. if (preg_match('/Mac OS X 10_5_7; [^\/\);]+\/([^\/\);]+)\//u', $ua, $match)) {
  48. $this->data->browser->name = 'Android Browser';
  49. $this->data->browser->version = null;
  50. $this->data->browser->mode = 'desktop';
  51. $this->data->os->name = 'Android';
  52. $this->data->os->alias = null;
  53. $this->data->os->version = null;
  54. $this->data->engine->name = 'Webkit';
  55. $this->data->engine->version = null;
  56. $this->data->device->type = 'mobile';
  57. $device = Data\DeviceModels::identify('android', $match[1]);
  58. if ($device->identified) {
  59. $device->identified |= $this->data->device->identified;
  60. $this->data->device = $device;
  61. }
  62. $this->data->features[] = 'foundDevice';
  63. }
  64. return $this;
  65. }
  66. private function &detectCamouflagedAndroidAsusBrowser($ua)
  67. {
  68. if (preg_match('/Linux Ventana; [a-z]{2}(?:-[a-z]{2})?; (.+) Build/u', $ua, $match)) {
  69. $this->data->browser->name = 'Android Browser';
  70. $this->data->browser->version = null;
  71. $this->data->browser->channel = null;
  72. $this->data->browser->mode = 'desktop';
  73. $this->data->engine->name = 'Webkit';
  74. $this->data->engine->version = null;
  75. $this->data->features[] = 'foundDevice';
  76. }
  77. return $this;
  78. }
  79. private function &detectCamouflagedAsSafari($ua)
  80. {
  81. if ($this->data->isBrowser('Safari') && !preg_match('/Darwin/u', $ua)) {
  82. if ($this->data->isOs('iOS') && !preg_match('/^Mozilla/u', $ua)) {
  83. $this->data->features[] = 'noMozillaPrefix';
  84. $this->data->camouflage = true;
  85. }
  86. if (!preg_match('/Version\/[0-9\.]+/u', $ua)) {
  87. $this->data->features[] = 'noVersion';
  88. $this->data->camouflage = true;
  89. }
  90. }
  91. return $this;
  92. }
  93. private function &detectCamouflagedAsChrome($ua)
  94. {
  95. if ($this->data->isBrowser('Chrome')) {
  96. if (preg_match('/(?:Chrome|CrMo|CriOS)\//u', $ua)
  97. && !preg_match('/(?:Chrome|CrMo|CriOS)\/([0-9]{1,2}\.[0-9]\.[0-9]{3,4}\.[0-9]+)/u', $ua)
  98. ) {
  99. $this->data->features[] = 'wrongVersion';
  100. $this->data->camouflage = true;
  101. }
  102. }
  103. return $this;
  104. }
  105. private function &detectCamouflagedUCBrowser($ua)
  106. {
  107. if ($ua == 'Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.2.3.4) Gecko/') {
  108. if (!$this->data->isBrowser('UC Browser')) {
  109. $this->data->browser->name = 'UC Browser';
  110. $this->data->browser->version = null;
  111. $this->data->browser->stock = false;
  112. }
  113. if ($this->data->isOs('Windows')) {
  114. $this->data->os->reset();
  115. }
  116. $this->data->engine->reset([ 'name' => 'Gecko' ]);
  117. $this->data->device->type = 'mobile';
  118. }
  119. if ($this->data->isBrowser('Chrome')) {
  120. if (preg_match('/UBrowser\/?([0-9.]*)/u', $ua, $match)) {
  121. $this->data->browser->stock = false;
  122. $this->data->browser->name = 'UC Browser';
  123. $this->data->browser->version = new Version([ 'value' => $match[1], 'details' => 2 ]);
  124. $this->data->browser->type = Constants\BrowserType::BROWSER;
  125. unset($this->data->browser->channel);
  126. }
  127. }
  128. return $this;
  129. }
  130. private function &detectCamouflagedBasedOnEngines()
  131. {
  132. if (isset($this->data->engine->name) && $this->data->browser->mode != 'proxy') {
  133. /* If it claims not to be Trident, but it is probably Trident running camouflage mode */
  134. if ($this->options->engine & Constants\EngineType::TRIDENT) {
  135. $this->data->features[] = 'trident';
  136. if ($this->data->engine->name && $this->data->engine->name != 'Trident') {
  137. $this->data->camouflage = !isset($this->data->browser->name) || ($this->data->browser->name != 'Maxthon' && $this->data->browser->name != 'Motorola WebKit');
  138. }
  139. }
  140. /* If it claims not to be Opera, but it is probably Opera running camouflage mode */
  141. if ($this->options->engine & Constants\EngineType::PRESTO) {
  142. $this->data->features[] = 'presto';
  143. if ($this->data->engine->name && $this->data->engine->name != 'Presto') {
  144. $this->data->camouflage = true;
  145. }
  146. if (isset($this->data->browser->name) && $this->data->browser->name == 'Internet Explorer') {
  147. $this->data->camouflage = true;
  148. }
  149. }
  150. /* If it claims not to be Gecko, but it is probably Gecko running camouflage mode */
  151. if ($this->options->engine & Constants\EngineType::GECKO) {
  152. $this->data->features[] = 'gecko';
  153. if ($this->data->engine->name && $this->data->engine->name != 'Gecko') {
  154. $this->data->camouflage = true;
  155. }
  156. if (isset($this->data->browser->name) && $this->data->browser->name == 'Internet Explorer') {
  157. $this->data->camouflage = true;
  158. }
  159. }
  160. /* If it claims not to be Webkit, but it is probably Webkit running camouflage mode */
  161. if ($this->options->engine & Constants\EngineType::WEBKIT) {
  162. $this->data->features[] = 'webkit';
  163. if ($this->data->engine->name && ($this->data->engine->name != 'Blink' && $this->data->engine->name != 'Webkit')) {
  164. $this->data->camouflage = true;
  165. }
  166. if (isset($this->data->browser->name) && $this->data->browser->name == 'Internet Explorer') {
  167. $this->data->camouflage = true;
  168. }
  169. /* IE 11 on mobile now supports Webkit APIs */
  170. if (isset($this->data->browser->name) && $this->data->browser->name == 'Mobile Internet Explorer'
  171. && isset($this->data->browser->version) && $this->data->browser->version->toFloat() >= 11
  172. && isset($this->data->os->name) && $this->data->os->name == 'Windows Phone'
  173. ) {
  174. $this->data->camouflage = false;
  175. }
  176. /* IE 11 Developer Preview now supports Webkit APIs */
  177. if (isset($this->data->browser->name) && $this->data->browser->name == 'Internet Explorer'
  178. && isset($this->data->browser->version) && $this->data->browser->version->toFloat() >= 11
  179. && isset($this->data->os->name) && $this->data->os->name == 'Windows'
  180. ) {
  181. $this->data->camouflage = false;
  182. }
  183. /* EdgeHTML rendering engine also appears to be WebKit */
  184. if (isset($this->data->engine->name) && $this->data->engine->name == 'EdgeHTML') {
  185. $this->data->camouflage = false;
  186. }
  187. /* Firefox 48+ support certain Webkit features */
  188. if ($this->options->engine & Constants\EngineType::GECKO) {
  189. $this->data->camouflage = false;
  190. }
  191. }
  192. if ($this->options->engine & Constants\EngineType::CHROMIUM) {
  193. $this->data->features[] = 'chrome';
  194. if ($this->data->engine->name && ($this->data->engine->name != 'EdgeHTML' && $this->data->engine->name != 'Blink' && $this->data->engine->name != 'Webkit')) {
  195. $this->data->camouflage = true;
  196. }
  197. }
  198. /* If it claims to be Safari and uses V8, it is probably an Android device running camouflage mode */
  199. if ($this->data->engine->name == 'Webkit' && $this->options->engine & Constants\EngineType::V8) {
  200. $this->data->features[] = 'v8';
  201. if (isset($this->data->browser->name) && $this->data->browser->name == 'Safari') {
  202. $this->data->camouflage = true;
  203. }
  204. }
  205. }
  206. return $this;
  207. }
  208. private function &detectCamouflagedBasedOnFeatures()
  209. {
  210. if (isset($this->data->browser->name) && isset($this->data->os->name)) {
  211. if ($this->data->os->name == 'iOS' && $this->data->browser->name != 'Opera Mini' && $this->data->browser->name != 'UC Browser' && isset($this->data->os->version)) {
  212. if ($this->data->os->version->toFloat() < 4.0 && $this->options->features & Constants\Feature::SANDBOX) {
  213. $this->data->features[] = 'foundSandbox';
  214. $this->data->camouflage = true;
  215. }
  216. if ($this->data->os->version->toFloat() < 4.2 && $this->options->features & Constants\Feature::WEBSOCKET) {
  217. $this->data->features[] = 'foundSockets';
  218. $this->data->camouflage = true;
  219. }
  220. if ($this->data->os->version->toFloat() < 5.0 && $this->options->features & Constants\Feature::WORKER) {
  221. $this->data->features[] = 'foundWorker';
  222. $this->data->camouflage = true;
  223. }
  224. }
  225. if ($this->data->os->name != 'iOS' && $this->data->browser->name == 'Safari' && isset($this->data->browser->version)) {
  226. if ($this->data->browser->version->toFloat() < 4.0 && $this->options->features & Constants\Feature::APPCACHE) {
  227. $this->data->features[] = 'foundAppCache';
  228. $this->data->camouflage = true;
  229. }
  230. if ($this->data->browser->version->toFloat() < 4.1 && $this->options->features & Constants\Feature::HISTORY) {
  231. $this->data->features[] = 'foundHistory';
  232. $this->data->camouflage = true;
  233. }
  234. if ($this->data->browser->version->toFloat() < 5.1 && $this->options->features & Constants\Feature::FULLSCREEN) {
  235. $this->data->features[] = 'foundFullscreen';
  236. $this->data->camouflage = true;
  237. }
  238. if ($this->data->browser->version->toFloat() < 5.2 && $this->options->features & Constants\Feature::FILEREADER) {
  239. $this->data->features[] = 'foundFileReader';
  240. $this->data->camouflage = true;
  241. }
  242. }
  243. }
  244. return $this;
  245. }
  246. }