月度归档:2013年01月

Zen-cart中SESSION的配置与封装

关于SESSION的配置,对应后台Configuration -> Sessions:
Zen-cart会话配置

相关配置对应的常量定义如下:
Zen-cart会话配置对应的常量定义

除了Cookie Domain对应SESSION_USE_FQDN外,其它都是见名知意的。如下:

SESSION_WRITE_DIRECTORY          当使用文件来存储会话内容时,它用来控制存储的目录,不过再150以后的版本只能使用数据库来存储了,这个参数无用了
SESSION_USE_FQDN                 这个是设置会话cookie的域名,它可以控制添加的cookie是否是FQDN,其实如果域名是访问网址是www.ifeeline.com,这个变量设置为Ture时就是www.ifeeline.com,是False是就是ifeeline.com
SESSION_FORCE_COOKIE_USE         是否强制使用cookie来传递会话ID
SESSION_CHECK_SSL_SESSION_ID     是否检查SSL会话ID
SESSION_CHECK_USER_AGENT         是否检查用户浏览器前后是否一致
SESSION_CHECK_IP_ADDRESS         是否检查用户IP前后是否一致
SESSION_BLOCK_SPIDERS            是否阻止机器人会话(如果强制使用cookie,则此设置没有使用)
SESSION_RECREATE                 是否更换会话ID(比如登录后更换ID)
SESSION_IP_TO_HOST_ADDRESS       是否把客户端的IP转换成名字(将发起DNS查询,建议不要开启)
SESSION_USE_ROOT_COOKIE_PATH     是否使用跟作为会话cookie的路径参数(默认为False,将根据程序实际情况自己决定)
SESSION_ADD_PERIOD_PREFIX        是否添加域名前缀(点号)到会话cookie的域名设置参数

关于SESSION_USE_FQDN设置,主要初始化文件在includes/init_includes/init_tlds.php中:

// 主要代码,zen_get_top_level_domain()函数受到SESSION_USE_FQDN设置影响
$http_domain = zen_get_top_level_domain(HTTP_SERVER);
$https_domain = zen_get_top_level_domain(HTTPS_SERVER);
$cookieDomain = $current_domain = (($request_type == 'NONSSL') ? $http_domain : $https_domain);

测试zen_get_top_level_domain()函数,假如当前网址是www.ifeeline.com,当SESSION_USE_FQDN是True时,输出是www.ifeeline.com,当SESSION_USE_FQDN是False时,输入ifeeline.com。这个设置主要影响到会话cookie的域设置,如果SESSION_USE_FQDN为Flase时,带www的网址www将被去掉:
会话域设置

关于SESSION_RECREATE设置,主要在登录和退出登录时用到。

其它的设置主要在includes/init_includes/init_sessions.php中,以下为代码逻辑(删除了部分代码,添加了详细注释)

require(DIR_WS_FUNCTIONS . 'sessions.php');
// 设置会话名称
zen_session_name('zenid');
// 对应后台Session Directory 设置保存路径,不过只是使用文件保存会话内容时 不过1.5.0以后只采用数据库保存
zen_session_save_path(SESSION_WRITE_DIRECTORY);

// 准备设置会话cookie的参数 前台所有页面都是通过访问index.php展示的,所有$_SERVER['SCRIPT_NAME']永远都是/index.php, 如果网站是http://sample.com/zcc,那么就是/zcc/index.php
$path = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
// 对应后台的 Use root path for cookie path,默认是False的,比如设置了访问网站是http://sample.com/zcc,那么将使用/zcc作为会话cookie的路径
if (defined('SESSION_USE_ROOT_COOKIE_PATH') && SESSION_USE_ROOT_COOKIE_PATH  == 'True') $path = '/';
$path = (defined('CUSTOM_COOKIE_PATH')) ? CUSTOM_COOKIE_PATH : $path;
// 对应后台的 Add period prefix to cookie domain
$domainPrefix = (!defined('SESSION_ADD_PERIOD_PREFIX') || SESSION_ADD_PERIOD_PREFIX == 'True') ? '.' : '';
// 这句代码是150版本以后新加 目的是当网站全站使用SSL时,设置cookie也只使用SSL来发送
$secureFlag = ((ENABLE_SSL == 'true' && substr(HTTP_SERVER, 0, 6) == 'https:' && substr(HTTPS_SERVER, 0, 6) == 'https:') || (ENABLE_SSL == 'false' && substr(HTTP_SERVER, 0, 6) == 'https:')) ? TRUE : FALSE;
// $cookieDomain在includes/init_includes/init_tlds.php中
if (PHP_VERSION >= '5.2.0') {
  	session_set_cookie_params(0, $path, (zen_not_null($cookieDomain) ? $domainPrefix . $cookieDomain : ''), $secureFlag, TRUE);
} else {
  	session_set_cookie_params(0, $path, (zen_not_null($cookieDomain) ? $domainPrefix . $cookieDomain : ''), $secureFlag);
}

if (isset($_POST[zen_session_name()])) {
  	zen_session_id($_POST[zen_session_name()]);
} elseif ( ($request_type == 'SSL') && isset($_GET[zen_session_name()]) ) {
  	zen_session_id($_GET[zen_session_name()]);
}

$ipAddressArray = explode(',', $_SERVER['REMOTE_ADDR']);
$ipAddress = (sizeof($ipAddressArray) > 0) ? $ipAddressArray[0] : '';
$_SERVER['REMOTE_ADDR'] = $ipAddress;

// 这里是启用会话的逻辑 
$session_started = false;
// 首先这里对应后台 Force Cookie use, 如果为True,那么先设置一个cookie,如果检测到有内容回发说明支持cookie。首先,第一次访问不会启用会话,第二,所有不支持cookie的都不启用会话,那么如果是爬行蜘蛛,则不会发送cookie,说明也起到了阻止机器人SESSION
if (SESSION_FORCE_COOKIE_USE == 'True') {
  	zen_setcookie('cookie_test', 'please_accept_for_session', time()+60*60*24*30, '/', (zen_not_null($current_domain) ? $current_domain : ''));

  	if (isset($_COOKIE['cookie_test'])) {
    	zen_session_start();
    	$session_started = true;
  	}
// 这里对应后台的 Prevent Spider Sessions,如果强制使用cookie,这里就没有意义(因为如果强制使用cookie,那么也就意味着阻止机器人会话了),当没有强制使用cookie来开启会话时,特别阻止机器人SESSION是有意义的。
} elseif (SESSION_BLOCK_SPIDERS == 'True') {
} else {
  	zen_session_start();
  	$session_started = true;
}
unset($spiders);
// 这个对应后台的 IP to Host Conversion Status  这个是根据IP反查主机地址 一般没有必要启用
if (!isset($_SESSION['customers_host_address'])) {
  if (SESSION_IP_TO_HOST_ADDRESS == 'true') {
    $_SESSION['customers_host_address']= @gethostbyaddr($_SERVER['REMOTE_ADDR']);
  } else {
    $_SESSION['customers_host_address'] = OFFICE_IP_TO_HOST_ADDRESS;
  }
}
// 这个对应后台的 Check SSL Session ID  如果请求类型是SSL,设置是否效验SSL_SESSION_ID,这个对应防止SSL挟持有一定作用
if ( ($request_type == 'SSL') && (SESSION_CHECK_SSL_SESSION_ID == 'True') && (ENABLE_SSL == 'true') && ($session_started == true) ) {
  	$ssl_session_id = $_SERVER['SSL_SESSION_ID'];
  	if (!$_SESSION['SSL_SESSION_ID']) {
    	$_SESSION['SSL_SESSION_ID'] = $ssl_session_id;
  	}
  	if ($_SESSION['SSL_SESSION_ID'] != $ssl_session_id) {
    	zen_session_destroy();
    	zen_redirect(zen_href_link(FILENAME_SSL_CHECK));
  	}
}
// 这个对应后台的 Check User Agent 效验用户代理是否一致,防止会话挟持,一般应该开启
if (SESSION_CHECK_USER_AGENT == 'True') {
}
// 这个对应后台的 Check IP Address 效验用户的访问IP是否以一致,一般不应该开启,现在的很多访问是同一个会话,但是来源IP可能不一样,因为用户可能从一个代理进来
if (SESSION_CHECK_IP_ADDRESS == 'True') {
}

另外一个对SESSION的封装主要在includes/functions/sessions.php文件:

  // 针对前后台设置会话有效时间,这个参数主要影用来设置保存在数据库中的会话过期时间
  if (IS_ADMIN_FLAG === true) {
    if (!$SESS_LIFE = (SESSION_TIMEOUT_ADMIN > 900 ? 900 : SESSION_TIMEOUT_ADMIN)) {
      $SESS_LIFE = (SESSION_TIMEOUT_ADMIN > 900 ? 900 : SESSION_TIMEOUT_ADMIN);
    }
  } else {
    if (!$SESS_LIFE = get_cfg_var('session.gc_maxlifetime')) {
      $SESS_LIFE = 1440;
    }
  }
  
  // 使用session_set_save_handler()函数修改会话内容保存的介质,zen-cart150以后,用户不能选择保存到文件,只能是保存到数据库
  session_set_save_handler('_sess_open', '_sess_close', '_sess_read', '_sess_write', '_sess_destroy', '_sess_gc');
  
  // 会话开始的封装,这里检查了会话ID是否是合法值,伪造的可能非法,这个检查非常有必要,另外设置一个会话安全码给securityToken,这样表单中提交的数据如果没有这个值,或者值对应不上,请求就会被终止,这个设置有效拒绝了来自第三方提交的表单数据。
  function zen_session_start() {
    @ini_set('session.gc_probability', 1);
    @ini_set('session.gc_divisor', 2);
    if (IS_ADMIN_FLAG === true) {
      @ini_set('session.gc_maxlifetime', (SESSION_TIMEOUT_ADMIN > 900 ? 900 : SESSION_TIMEOUT_ADMIN));
    }
    if (preg_replace('/[a-zA-Z0-9]/', '', session_id()) != '')
    {
      zen_session_id(md5(uniqid(rand(), true)));
    }
    $temp = session_start();
    if (!isset($_SESSION['securityToken'])) {
      $_SESSION['securityToken'] = md5(uniqid(rand(), true));
    }
    return $temp;
  }

  // 以下是对session_id()函数的封装,如果提过了SESSION ID(比如zen_session_start()函数会用到此情况),那么就要检查这个值是合法的
  function zen_session_id($sessid = '') {
    if (!empty($sessid)) {
      $tempSessid = $sessid;
  	  if (preg_replace('/[a-zA-Z0-9]/', '', $tempSessid) != '')
  	  {
  	    $sessid = md5(uniqid(rand(), true));
  	  }
      return session_id($sessid);
    } else {
      return session_id();
    }
  }

原创文章,转载务必保留出处。
http://blog.ifeeline.com/310.html

PHP 会话处理

会话处理过程
启用会话的页面执行的第一项任务是确定是否存在有效的会话,或者是否要开始新的会话。如果不存在有效的会话,就生成一个会话并与此用户关联(SID的设置方法可以来自URL或cookie)。

问题:会话的ID是保存在cookie中,在这个会话有效期内,cookie来回传递确保了服务端能为当前用户的会话取得数据,比如用户登录了,如果这个cookie中存储的ID被别人知道了,并伪造了一个cookie,然后开始访问,那么用户信息将泄漏?

配置指令
1 管理会话存储介质
session.save_handler = files|mm|sqlite|user
数据可以通过4种方法存储:

平面文件(files)		默认
共享内存(mm)		需要从www.ossp.org/pkg/mm下载并安装mm库
sqlite			利用SQLite
user			用户自定义函数,配置复杂,但最灵活,比如要存在到mysql中,就需要使用自定义函数。

2 设置会话文件路径
如果session.save_hanlder设置为files存储选项,则session.save_path指令必须指向存储目录(实际上如果忽略,则通向系统临时目录):
session.save_path = string
session.save_path默认设置为未启用,如果session.save_hanlder设置为files就需要指定一个适当的存储目录。出于效率的原因,可以使用语法N:/path来定义session.save_path,其中N是一个整数,表示可以存储会话数据的N层深度子目录的数量。如果使用这个特性,要注意PHP不会自动创建这些目录。Linux下可以通过执行mod-files.sh文件(在ext/session目录)来自动化这过程。

3 自动启用会话
网页默认只能通过调用session_satart()来启动会话。不过如果要在网站一直使用会话,可以将session.auto_start指令设置为1。
session.auto_start = 0|1

4 设置会话名称
PHP默认使用PHPSESSID会话名。但可以使用命令session.name把默认值改为任何的名字。

5 选择cookie或URL重写
session.use_cookies = 0 | 1 默认为1,表示使用cookie,这时不要再显式调用cookie设置函数(PHP会话函数自动完成),另外还要考虑其它指令。

6 如果session.use_cookies被禁用,用户唯一的SID必须要附加在URL后面,以确保SID的传播。要实现这个目的,可以启用指令session.use_trans_sid来完成。

7 设置会话cookie的生存期
session.cookie_lifetime = integer
设置会话cookie的有效期,以秒为单位,如果设置为0(默认),则cookie将一直存活直到浏览器重启。

8 设置会话cookie的有效URL路径
session.cookie_path = string
确定cookie在哪个路径中是有效的。如果该指令设置为/(默认),则cookie对整个网站都有效,如果设置为/books,则只有http://xxx/books/路径中调用才有效。

9 设置会话cookie的有效域
session.cookie_domain = string
确定cookie在哪个域中有效。如果忽略此指令设置,则cookie的域被设为生成它的服务器的主机名(正确应该是被处理的请求URL的域)。
如果要在网站的子域中使用会话,可以设置:session.cookie_domain = .example.com

10 使用referer来验证会话
session.referer_check = string

11 为启用会话的页面设置缓存方向
如果客户端经过代理,那么session.cache_limiter指令可以设置启用会话的页面在代理中缓存的方式。
对应有session.cache_expire。默认session.cache_limiter没有启用。

12 设置会话生存期
session.gc_maxlifetime = integer
以秒为单位,默认为1440,一旦达到这个限制,会话信息将被销毁。有关调整会话垃圾回收特性的信息,查看session.gc_divisor和session.gc_probability指令。

配置文件中的参考

[Session]
; Handler used to store/retrieve data.
session.save_handler = files

; Argument passed to save_handler.  In the case of files, this is the path
; where data files are stored. Note: Windows users have to change this
; variable in order to use PHP's session functions.
;
; As of PHP 4.0.1, you can define the path as:
;
;     session.save_path = "N;/path"
;
; where N is an integer.  Instead of storing all the session files in
; /path, what this will do is use subdirectories N-levels deep, and
; store the session data in those directories.  This is useful if you
; or your OS have problems with lots of files in one directory, and is
; a more efficient layout for servers that handle lots of sessions.
;
; NOTE 1: PHP will not create this directory structure automatically.
;         You can use the script in the ext/session dir for that purpose.
; NOTE 2: See the section on garbage collection below if you choose to
;         use subdirectories for session storage
;
; The file storage module creates files using mode 600 by default.
; You can change that by using
;
;     session.save_path = "N;MODE;/path"
;
; where MODE is the octal representation of the mode. Note that this
; does not overwrite the process's umask.
;session.save_path = "/tmp"

; Whether to use cookies.
session.use_cookies = 1

; This option enables administrators to make their users invulnerable to
; attacks which involve passing session ids in URLs; defaults to 0.
; session.use_only_cookies = 1

; Name of the session (used as cookie name).
session.name = PHPSESSID

; Initialize session on request startup.
session.auto_start = 0

; Lifetime in seconds of cookie or, if 0, until browser is restarted.
session.cookie_lifetime = 0

; The path for which the cookie is valid.
session.cookie_path = /

; The domain for which the cookie is valid.
session.cookie_domain =

; Handler used to serialize data.  php is the standard serializer of PHP.
session.serialize_handler = php

; Define the probability that the 'garbage collection' process is started
; on every session initialization.
; The probability is calculated by using gc_probability/gc_divisor,
; e.g. 1/100 means there is a 1% chance that the GC process starts
; on each request.

session.gc_probability = 1
session.gc_divisor     = 1000

; After this number of seconds, stored data will be seen as 'garbage' and
; cleaned up by the garbage collection process.
session.gc_maxlifetime = 1440

; NOTE: If you are using the subdirectory option for storing session files
;       (see session.save_path above), then garbage collection does *not*
;       happen automatically.  You will need to do your own garbage
;       collection through a shell script, cron entry, or some other method.
;       For example, the following script would is the equivalent of
;       setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes):
;          cd /path/to/sessions; find -cmin +24 | xargs rm

; PHP 4.2 and less have an undocumented feature/bug that allows you to
; to initialize a session variable in the global scope, albeit register_globals
; is disabled.  PHP 4.3 and later will warn you, if this feature is used.
; You can disable the feature and the warning seperately. At this time,
; the warning is only displayed, if bug_compat_42 is enabled.

session.bug_compat_42 = 0
session.bug_compat_warn = 1

; Check HTTP Referer to invalidate externally stored URLs containing ids.
; HTTP_REFERER has to contain this substring for the session to be
; considered as valid.
session.referer_check =

; How many bytes to read from the file.
session.entropy_length = 0

; Specified here to create the session id.
session.entropy_file =

;session.entropy_length = 16

;session.entropy_file = /dev/urandom

; Set to {nocache,private,public,} to determine HTTP caching aspects
; or leave this empty to avoid sending anti-caching headers.
session.cache_limiter = nocache

; Document expires after n minutes.
session.cache_expire = 180

; trans sid support is disabled by default.
; Use of trans sid may risk your users security.
; Use this option with caution.
; - User may send URL contains active session ID
;   to other person via. email/irc/etc.
; - URL that contains active session ID may be stored
;   in publically accessible computer.
; - User may access your site with the same session ID
;   always using URL stored in browser's history or bookmarks.
session.use_trans_sid = 0

; Select a hash function
; 0: MD5   (128 bits)
; 1: SHA-1 (160 bits)
session.hash_function = 0

; Define how many bits are stored in each character when converting
; the binary hash data to something readable.
;
; 4 bits: 0-9, a-f
; 5 bits: 0-9, a-v
; 6 bits: 0-9, a-z, A-Z, "-", ","
session.hash_bits_per_character = 5

; The URL rewriter will look for URLs in a defined set of HTML tags.
; form/fieldset are special; if you include them here, the rewriter will
; add a hidden <input> field with the info which is otherwise appended
; to URLs.  If you want XHTML conformity, remove the form entry.
; Note that all valid entries require a "=", even if no value follows.
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"

处理会话
1 开始会话
sessiont_start()
每次请求都要显式地启动和恢复会话。都是使用这个函数。函数session_start()会创建一个新会话(如果SID未找到)或继续当前会话(如果存在一个SID)。

补充说明:会话ID使用cookie来存储时,第一次访问时响应会设置一个存储了SID的cookie给客户端(自动完成),以后这个cookie就会伴随每次请求发送到服务器,但这个保存会话ID的这个cookie是可以设置有效时间的,指令session.cookie_lifetime默认为0,表示浏览器退出,这个cookie被删除,如果设置为比较短的时间,比如60秒,那么是否意味着这个会话只有60秒操作时间呢?

在firefox下测试的确如此,60秒后,保存会话ID的cookie被浏览器删除,原来会话失效。不过PHP提供了session_set_cookie_params()函数,它可以修改php.ini配置中的针对保存会话的这个cookie的参数(非常有用)

把session.cookie_lifetime改为60:
会话coolike生存期

把session.cookie_lifetime改为0:
会话cookie生存期
可以启用配置指令session.auto_start,从而不必执行这个函数。默认是没有启用的,一般是在需要的时候使用session_start()函数按需启用。

2 销毁会话
可以配置PHP的会话处理指令,根据过期时间或垃圾回收机制自动销毁会话,但有时需要手工处理。通过session_unset()和session_destroy()函数完成。

session_unset()函数清除存储在当前会话中的所有会话变量,它能有效地将会话重置为创建时的状态。

执行session_unset()确实会删除存储在当前会话中的所有会话变量,但并不会从存储机制中完成删除会话。如果要完全销毁会话,需要使用session_destroy()。该函数从存储机制中完全删除会话,使当前会话失效。但他不会销毁用户浏览器中的任何cookie。

补充:
session中的内容实际是存入文件 或 数据库数据表中的,当使用session_unset()函数时,实际是清空对应的存储文件或数据表中的记录而已,对于当前的$_SESSION数组也被清空,session_destroy()也一样,只是它不是清空,而是删除对应的存储文件或数据表记录,但是对应的$_SESSION数组并未做任何修改(它会在下一次请求中遇到session_start()函数时被修改或重新生成),当前这个$_SESSION数组也能操作,但是它的值并没有被保存(没有写入到文件或数据库表中)。这样看起来要干净销毁当前会话,应该是sesssion_unset()之后再调用session_destroy()。

// ==  代码1 测试session_destry()函数
session_start();
echo "SID is: ".session_id()."<br />";
$_SESSION['bsession'] = "Befor session_destroy().<br />";

print_r($_SESSION);

session_destroy();

echo "SID is: ".session_id()."<br />";
$_SESSION['asession'] = 'After session_ destroy ().<br />';
print_r($_SESSION);

//输出
SID is: 903v1qbhvq54gr0cu24o3n5kq5
Array ( 
        [bsession] => Befor session_ destroy ()
)
SID is: 						//会话ID没有了
Array (
 	[bsession] => Befor session_ destroy ().	//之前写入的变量还存储,说明$_SESSION没有被重置
        [asession] => After session_ destroy ().	//写入了新内容
)
// 以上代码刷新后显示一样,说明最后写入的新内容被没有被保存,另外,会话ID还是一样,说明session_destroy()没有删除客户端的cookie,会话ID被重用。

// ==  代码2 测试session_unset()函数
session_start();
echo "SID is: ".session_id()."<br />";
$_SESSION['bsession'] = "Befor session_unset().<br />";

print_r($_SESSION);

session_unset();

echo "SID is: ".session_id()."<br />";
print_r($_SESSION);

//输出
SID is: 903v1qbhvq54gr0cu24o3n5kq5
Array (
[bsession] => Befor session_unset()
)
SID is: 903v1qbhvq54gr0cu24o3n5kq5		//SID继续输出,说明对应会话还存在
Array ( )					//说明$_SESSION被清空了

3 设置和获取会话ID
string session_id([string sid])
函数session_id()可以设置和获得SID。如果没有参数,则函数session_id()返回当前SID。如果包括了可选参数sid,当前SID将被该值替换。

补充:
关于会话的ID,如果客户端有传递会话ID的cookie过来,那么就使用这个ID作为当前会话的ID,然而这个ID对应的会话文件可能已经过期了(原来内容或文件已经被清除),如果过期了,虽然是重用ID值,但是它却是一个新的会话。

会话是有会话生存周期的,它是如何控制这个时间的呢?现在知道每次使用session_start()函数时,都会从文件或数据库中读取内容过来,不过如果存储到文件中,当往session存储了内容后,接下来只调用session_start(),发现对应的文件修改时间也修改了,这充分说明,调用session_start()时,是先读出来,再把内容写回去,这样就可以通过文件的修改时间控制会话的有效期。
会话文件
如果是存储在数据库中,那么必须提供一个控制有效期的字段。

4 创建和删除会话变量
直接使用unset()函数即可。

5 编码和解编码会话数据
每个会话变量由3部分主材:名、长度和值。语法格式:
name|s:length:”value”;
PHP可以自动处理会话数据的编码。不过也提供了session_encode()和session_decode()函数。

考虑一种情况,比如在数据库中保存了一个会话(这个会话设置了一个比较长的有效期),现在要还原它,那么从数据库中读取出来,然后用session_decode()处理,那么当前的$_SESSION数组就是从数据库中读来的内容。只是保存在数据库中的数据库可能不是文本,而是经过编码处理的字节流,比如使用base64进行编码(常用)

6 更新生成会话ID
通常在会话中保存会话数据不变,通过对请求重新生成会话ID,可以有效防止会话挟持。不过通用做法不是每次请求都生成新的ID,这样做开销比较大,一般是在关键操作之后(比如登录后)才更换会话ID。
session_regenerate_id()函数可以完成更换ID的操作。

//以下观察更换ID是如何实现的
session_start();
$_SESSION[‘some_text’] =“some test text”;

echo “SID is”.session_id().”
”; session_regenerate_id(); echo “Now SID is ”.session_id().”
”; print_r($_SESSION); //输出 SID is 2ifi7nld2feief44qnf0skmql7 Now SID is 1rkkv0f9l75vm3rd5fb4qf2r30 Array ( [some_text] => some test text )

看下图:
会话ID重新生成实验
可以发现,它首先对旧的SESSION文件进行了清空(内部应该是调用session_unset()函数),然后生成了新的SESSION文件,并把之前的内容写了回去。这样旧的SESSION文件其实就是个垃圾文件了,它会被PHP本身的会话垃圾回收机制自动清理,不过也可以在调用session_regenerate_id()时传递一个true给它让其主动删除。

接下来看看cookie的传递:
会话ID重新生成cookie的修改

第一访问,由于session_start()的调用,设置了一个cookie回来,它的ID是n6hghpj8dg5ba7s5evvs2s4406,接着调用session_regenerate_id(),所有又设置了一个cookie,这个cookie的名称(即会话名称)是相同的,会话ID是0cf8frd4efonm8bh8hg2pcboc6,那么这个新的cookie将会覆盖旧的cookie设置。

这样,工作过程就非常清楚了。

7 设置会话cookie的相关参数
会话的cookie是自动设置的,不过cookie本身可以设置很多参数,比如生存期,绑定域名,绑定到根还是子目录。
void session_set_cookie_params ( int $lifetime [, string $path [, string $domain [, bool $secure = false [, bool $httponly = false ]]]] )

服务器上可能运行多个域,如果不指定cookie的域,实际上总是采用请求是的域作为cookie的域的,这样就可以避免冲突了。比如一个叫nsid.php的页面,可以通过localhost/nsid.php和127.0.0.1/nsid.php访问,那么分别产生名为PHPSESSID的两个会话,发送给客户端的都是叫PHPSESSID的cookie,但是保存的内容不一样,最重要默认绑定的域不一样,这样访问localhost那个连接时,发送回来的是名叫PHPSESSID绑定了localhost的域的cookie,访问127.0.0.1那个连接时,发送回来的是名叫PHPSESSID绑定了127.0.0.1的域的cookie。这样cookie的发送就有条不紊了。

这里有一个要注意的地方,如果要使得所有子域也能获取对应的会话cookie,那么绑定域时只要在前面加一点,比如.domain.com,这样以后访问doc.domain.com时,相关的cookie也会发送过来。

创建定制会话处理程序
在4种存储方法中,用户定义的会话处理程序提供了最大程度的灵活性。使用session_set_save_handler()函数可以把会话处理指定为自定义的函数,一共需要提供6个函数,原型如下:

bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc )

open(string $savePath, string $sessionName) 
The open callback works like a constructor in classes and is executed when the session is being opened. It is the first callback function executed when the session is started automatically or manually with session_start(). Return value is TRUE for success, FALSE for failure. 
close() 
The close callback works like a destructor in classes and is executed after the session write callback has been called. It is also invoked when session_write_close() is called. Return value should be TRUE for success, FALSE for failure. 
read(string $sessionId) 
The read callback must always return a session encoded (serialized) string, or an empty string if there is no data to read. 
This callback is called internally by PHP when the session starts or when session_start() is called. Before this callback is invoked PHP will invoke the open callback. 
The value this callback returns must be in exactly the same serialized format that was originally passed for storage to the write callback. The value returned will be unserialized automatically by PHP and used to populate the $_SESSION superglobal. While the data looks similar to serialize() please note it is a different format which is speficied in the session.serialize_handler ini setting. 
write(string $sessionId, string $data) 
The write callback is called when the session needs to be saved and closed. This callback receives the current session ID a serialized version the $_SESSION superglobal. The serialization method used internally by PHP is specified in the session.serialize_handler ini setting. 
The serialized session data passed to this callback should be stored against the passed session ID. When retrieving this data, the read callback must return the exact value that was originally passed to the write callback. 
This callback is invoked when PHP shuts down or explicitly when session_write_close() is called. Note that after executing this function PHP will internally execute the close callback. 
Note: 
The "write" handler is not executed until after the output stream is closed. Thus, output from debugging statements in the "write" handler will never be seen in the browser. If debugging output is necessary, it is suggested that the debug output be written to a file instead. 
destroy($sessionId) 
This callback is executed when a session is destroyed with session_destroy() or with session_regenerate_id() with the destroy parameter set to TRUE. Return value should be TRUE for success, FALSE for failure. 
gc($lifetime) 
The garbage collector callback is invoked internally by PHP periodically in order to purge old session data. The frequency is controlled by session.gc_probability and session.gc_divisor. The value of lifetime which is passed to this callback can be set in session.gc_maxlifetime. Return value should be TRUE for success, FALSE for failure. 

以下提供一个实践参考:

  if (IS_ADMIN_FLAG === true) {
    if (!$SESS_LIFE = (SESSION_TIMEOUT_ADMIN > 900 ? 900 : SESSION_TIMEOUT_ADMIN)) {
      $SESS_LIFE = (SESSION_TIMEOUT_ADMIN > 900 ? 900 : SESSION_TIMEOUT_ADMIN);
    }
  } else {
    if (!$SESS_LIFE = get_cfg_var('session.gc_maxlifetime')) {
      $SESS_LIFE = 1440;
    }
  }
  function _sess_open($save_path, $session_name) {
    return true;
  }

  function _sess_close() {
    return true;
  }

  function _sess_read($key) {
    global $db;
    $qid = "select value
            from " . TABLE_SESSIONS . "
            where sesskey = '" . zen_db_input($key) . "'
            and expiry > '" . time() . "'";

    $value = $db->Execute($qid);

    if (isset($value->fields['value']) && $value->fields['value']) {
      $value->fields['value'] = base64_decode($value->fields['value']);
      return $value->fields['value'];
    }

    return ("");
  }

  function _sess_write($key, $val) {
    global $db;
    if (!is_object($db)) {
      //PHP 5.2.0 bug workaround ...
      if (!class_exists('queryFactory')) require('includes/classes/db/' .DB_TYPE . '/query_factory.php');
      $db = new queryFactory();
      $db->connect(DB_SERVER, DB_SERVER_USERNAME, DB_SERVER_PASSWORD, DB_DATABASE, USE_PCONNECT, false);
    }
    $val = base64_encode($val);

    global $SESS_LIFE;

    $expiry = time() + $SESS_LIFE;

    $qid = "select count(*) as total
            from " . TABLE_SESSIONS . "
            where sesskey = '" . zen_db_input($key) . "'";

    $total = $db->Execute($qid);

    if ($total->fields['total'] > 0) {
      $sql = "update " . TABLE_SESSIONS . "
              set expiry = '" . zen_db_input($expiry) . "', value = '" . zen_db_input($val) . "'
              where sesskey = '" . zen_db_input($key) . "'";

      $result = $db->Execute($sql);

    } else {
      $sql = "insert into " . TABLE_SESSIONS . "
              values ('" . zen_db_input($key) . "', '" . zen_db_input($expiry) . "', '" .
                       zen_db_input($val) . "')";

      $result = $db->Execute($sql);

    }
  return (!empty($result) && !empty($result->resource));
  }

  function _sess_destroy($key) {
    global $db;
    $sql = "delete from " . TABLE_SESSIONS . " where sesskey = '" . zen_db_input($key) . "'";
    return $db->Execute($sql);
  }

  function _sess_gc($maxlifetime) {
    global $db;
    $sql = "delete from " . TABLE_SESSIONS . " where expiry < " . time();
    $db->Execute($sql);
    return true;
  }

  session_set_save_handler('_sess_open', '_sess_close', '_sess_read', '_sess_write', '_sess_destroy', '_sess_gc');

原创文章,转载务必保留出处。
永久连接:http://blog.ifeeline.com/300.html

PHP 函数参考->与变量和类型有关的扩展->Filter – Data Filtering

数据的过滤和清理是常用操作,PHP中抽闲了这类操作,大部分可能的操作都已经有提供。

主要有两种过滤器: validation 和 sanitization,有很多Flags可以让validation 和 sanitization过滤器使用。
参考:http://php.net/manual/zh/filter.configuration.php

所有的过滤器 参考:http://php.net/manual/zh/filter.filters.php

Filter函数

filter_has_var — Checks if variable of specified type exists 判断指定变量是否存在
filter_id — Returns the filter ID belonging to a named filter
filter_input_array — Gets external variables and optionally filters them
filter_input — Gets a specific external variable by name and optionally filters it
filter_list — Returns a list of all supported filters 返回过滤器列表
filter_var_array — Gets multiple variables and optionally filters them 当多次过滤多个数据时
filter_var — Filters a variable with a specified filter 过滤值

可能最常用的是filter_var()函数:

mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
第一参数指定要过滤的数据,第二参数指定使用哪个过滤器,第三个是选项,可以认为是配置项,可以传递一个数组。返回值可能是过滤后的值 或FALSE,比如验证邮件地址时,如果是合法邮件地址返回这个串,否则返回FALSE。

//以下是PHP手册中的例子
$options = array(
    'options' => array(
        'default' => 3, // 设置过滤失败是返回值,默认是布尔值,这里修改它
        // 其它参数
        'min_range' => 0
    ),
    'flags' => FILTER_FLAG_ALLOW_OCTAL, //这个flag表明允许是八进制数,默认是十进制数
);
$var1 = filter_var('0755', FILTER_VALIDATE_INT, $options); //返回493,就是0755八进制转换成了十进制

// FILTER_NULL_ON_FAILURE选项表示过滤失败时返回NULL
$var2 = filter_var('oops', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

// 另一种使用方法
$var3 = filter_var('oops', FILTER_VALIDATE_BOOLEAN,
                  array('flags' => FILTER_NULL_ON_FAILURE));

// 以下是一个特殊用法, 它使用FILTER_CALLBACK说明使用一个回调函数来过滤数据
Function foo($value){}
$var = filter_var('Doe, Jane Sue', FILTER_CALLBACK, array('options' => 'foo'));

过滤器 列表

验证器
FILTER_VALIDATE_BOOLEAN		是否是布尔值。Returns TRUE for "1", "true", "on" and "yes".
FILTER_VALIDATE_EMAIL		是否是电子邮件
FILTER_VALIDATE_FLOAT		是否是浮点数
FILTER_VALIDATE_INT		是否是整型数
FILTER_VALIDATE_IP		是否是IP
FILTER_VALIDATE_REGEXP		是否是正则表达式
FILTER_VALIDATE_URL		是否是URL

清理器
FILTER_SANITIZE_EMAIL		清理电子邮件地址非法字符
FILTER_SANITIZE_ENCODED
FILTER_SANITIZE_MAGIC_QUOTES
FILTER_SANITIZE_NUMBER_FLOAT
FILTER_SANITIZE_NUMBER_INT
FILTER_SANITIZE_SPECIAL_CHARS
FILTER_SANITIZE_FULL_SPECIAL_CHARS
FILTER_SANITIZE_STRING		清理标签
FILTER_SANITIZE_STRIPPED	FILTER_SANITIZE_STRING的别名
FILTER_SANITIZE_URL		清除URL中非法字符
FILTER_UNSAFE_RAW

永久连接: http://blog.ifeeline.com/297.html

Zen-cart插件IP Deny 实用改进

Zen-cart插件IP Deny原版只适用与1.3.8a,后来有朋友修改了下,加入了阻止浏览器语言,不过阻止IP无法根据段来批量写入,感觉上非常不实用。另外还要手动拷贝代码并且不适用于1.5.0版本。这里就是要改进这些个问题。

首先,修改了IP段判断问题,使用了http://blog.ifeeline.com/285.html介绍的方法:
批量IP段
这些IP段在以前版本表示的就不是这个意思(让人匪夷所思)。

另外,添加了自动加载文件:

//添加文件 includes/auto_loaders/config.ip_blocker.php
if (!defined('IS_ADMIN_FLAG')) {
 	die('Illegal Access');
}

$autoLoadConfig[200][] = array('autoType'=>'init_script',
                                 'loadFile'=> 'init_ip_blocker.php');

//添加文件 includes/init_includes/init_ip_blocker.php
if (!defined('IS_ADMIN_FLAG')) {
  	die('Illegal Access');
}

ip_blocker();

后台文件针对1.5.0以后添加了如下文件:

// admin/includes/auto_loaders/config.ip_blocker.php
if (!defined('IS_ADMIN_FLAG')) {
  die('Illegal Access');
} 

$autoLoadConfig[999][] = array(
  'autoType' => 'init_script',
  'loadFile' => 'init_ip_blocker.php'
);

// admin/includes/init_includes/init_ip_blocker.php
if (!defined('IS_ADMIN_FLAG')) {
	die('Illegal Access');
}
$zc150 = (PROJECT_VERSION_MAJOR > 1 || (PROJECT_VERSION_MAJOR == 1 && substr(PROJECT_VERSION_MINOR, 0, 3) >= 5));

////////////////////
if ($zc150) { // continue Zen Cart 1.5.0
    // add configuration menu
	
	
    if (!zen_page_key_exists('ip_blocker')) {
		$tools_sorder = (int)$db->Execute("select sort_order from ".TABLE_ADMIN_PAGES." where menu_key = 'tools' order by sort_order DESC")->fields['sort_order']+1;
        zen_register_admin_page('ip_blocker',
                                'BOX_TOOLS_IP_BLOCKER', 
                                'FILENAME_IP_BLOCKER',
                                '', 
                                'tools', 
                                'Y',
                                $tools_sorder);
          
        $messageStack->add('Enabled IP BLOCKER Configuration menu.', 'success');
    }
}
//////////////////

这样就对1.5.0以后的版本适用了。

后台功能上,除了修改IP段设置外,还添加了阻止机器人设置和阻止浏览器语言和阻止浏览器的语言国家功能,另外界面也美化了下。
IP Deny插件预览

阻止某些spider浏览:
阻止某些spider访问

阻止浏览器某些语言访问:
阻止某些语言访问

如果符合设置的策略,需要输入密码才能继续访问(也可以设置跳转页面,不一定是要求登录)
输入密码才能继续访问

等等。

原创文章,转载务必保留出处。
永久连接: http://blog.ifeeline.com/287.html

PHP中判断IP是否在某个给定的IP段中

在Linux中,可以往iptables防火墙中添加规则,如-A INPUT -s 180.76.5.0/24 -j DROP,这样客户端的IP如果落在180.76.5.0/24这个段中,那么这个IP过来的数据包将被丢弃,这里iptables帮我们判断了IP是否落在某给定的IP段中,但是在PHP中如何实现这个判断呢?

解决方案,可以从IP段中读出段的网络地址和广播地址,然后再把它们转换成数字,判断访问者IP的数字值是否落在其中,如果在,则说明在这个段中,否则就不在这个段中。

思路看起来很简单,不过实现过程就要非常小心。

//把客户端访问IP转换成数字,使用ip2long()函数
$ip = "202.105.77.179";
//这个输出在32位系统下是负值,原因是ip2long()回返回PHP的整型数,而PHP的整型是带符号的,数得简单点就是整型用32位,但是第32位是符号位,0表示正,1表示负,这样正负最大能到达的是2^31。而这里的IP转换成整型数时,它超过了PHP整型数能表示的范围,叫做溢出。
$ip_long = ip2long($ip);
//虽然转换成整数时产生了溢出,但是并不影响转换成二进制的正确性,返回一个二进制串,注意这个函数并没有假定它一定返回二进制串的长度
$ip_bin = decbin($ip_long);
//这个时候再从二进制转换成十进制,使用bindec()函数,这个函数返回的数字串是不受符号位影响的,就是通过这样的转换获得了正确的数字串
$ip_dec = bindec($ip_bin);

接下来需要一些函数:

//判断是否是合法IP
function isIp($str) {
	$ip = explode(".", $str);
	if (count($ip) < 4 || count($ip) > 4) return FALSE;
	foreach($ip as $ip_addr) {
		if ( !is_numeric($ip_addr) ) return FALSE;
		if ( $ip_addr < 0 || $ip_addr > 255 ) return FALSE;
	}
    return (preg_match("/^([0-9]{1,3}\.){3}[0-9]{1,3}$/is", $str));
}

//根据给出的数字生成二进制掩码串
function mask2bin($n){ 
	$n = intval($n); 
	if($n < 0 || $n > 32) return FALSE; 
	return str_repeat( "1",$n).str_repeat( "0",32-$n); 
}
//反转掩码串
function revBin($s)   { 
	$p = array('0','1','2'); 
	$r = array('2','0','1'); 
	return  str_replace($p,$r,$s); 
} 

//根据IP和掩码得到网络地址 十进制
function getSubnet($ip, $mask){
        //这里的按为运算务必要保证长度一致,都是32位
	$bin_ip = str_pad(decbin(ip2long($ip)),32,'0',STR_PAD_LEFT);
	$msk = mask2bin($mask);
	
	return bindec($bin_ip & $msk);
}
//根据IP和掩码得到广播地址 十进制
function getBroadcast($ip, $mask){
	$bin_ip = str_pad(decbin(ip2long($ip)),32,'0',STR_PAD_LEFT);
	$msk = mask2bin($mask);
	
	return bindec($bin_ip | revBin($msk));
}

以上的函数getSubnet() 和 getBroadcast()可以获得网络地址和广播地址,返回的是十进制的数字串(不是整型数),这个数字串可能已经超过了PHP整型能表示的范围,所以,如果要判断$ip_dec是否落在它们之间,绝对不能转换成整型然后比较。必须得使用bccomp()函数,这个函数前两个是操作数,第三参数表示精度,如果第一操作数大于第二操作数,返回整型数1,否则返回-1,如果相等返回0。

以下为判断$ip_dec是否落在给定段的代码:

$passlist[] = "192.168.0.0/16";
$passlist[]= "10.0.0.1/8";
$passlist[]= "8.8.8.8";

$ip = $ip_dec;

function inTheList($passlist,$ip){
	foreach ($passlist as $pass){
		//$dec_ip = bindec(decbin(ip2long($ip)));
		$dec_ip = $ip;
		$tm = explode("/",$pass);
		if(count($tm) > 1){
			// 合法IP 和 掩码数
			if(isIp($tm[0]) === FALSE){ continue; }
			if( ((int)$tm[1] < 1) || ((int)$tm[1] > 32)){ continue; }
			
			$dec_from = getSubnet($tm[0],(int)$tm[1]);
			$dec_end = getBroadcast($tm[0],(int)$tm[1]);
			// 在段中
			if( (bccomp($dec_ip,$dec_from) == 1) && (bccomp($dec_ip,$dec_end) == -1) ){
				return TRUE;
				break;
			}
		}else if(trim($ip) == bindec(decbin(ip2long(trim($pass))))){
			return TRUE;
			break;
		}
	}
        return FALSE;
}

if(inTheList($passlist,$ip)){
        echo "The ip -> $ip is in the list.";
}else{
        echo "The ip -> $ip is not in the list.";
}

原创文章,转载务必保留出处。
永久连接: http://blog.ifeeline.com/285.html

读“数学之美”片段摘录

随着文明的进步,信息量的增加,埃及的象形文字数量便不再随着文明的发展而增加了,因为没有人能够学会和记住这么多的文字。于是,概念的第一次概况和归类就开始了。

在古埃及的象形文字中,读音相同的词可能用同一个符号记录。

中国古代学者对儒家经典的主释和正义,其实都是在按照自己的理解做消除歧义性的工作。

翻译这件事之所以能达成,仅仅是因为不同的文字系统在记录信息上的能力是等价的。

早期数字并没有书写的形式,而是掰指头,这就是为什么我们今天使用十进制的原因。

数字和其他文字一样,在早期只是承载信息的工具,并不具有任何抽象的含义。

几乎所有的文明都采用了十进制,那么是否有文明采用二十进制呢,也就是说他们数完全部的手指和脚趾才开始进位呢?答案是肯定的,这就睡玛雅文明。

描述数字最有效的是古印度人,他们发明了包括0在内的10个阿拉伯数字,就是今天全世界通用的数字。这种表示方法比中国和罗马的都抽象,但是使用方便。因此,它们有阿拉伯人传入欧洲后,马上得到普及。只是欧洲人并不知道这些数字的真正发明者是印度人,而把功劳给了“二道贩子”阿拉伯人。阿拉伯数字或者说印度数字的革命性不仅在于它的简洁有效,而且标志着数字和文字的分离。

从象形文件到拼音文字是一个飞跃,因为人类在描述物体的方式上,从物体的外表到抽象的概念,同时不自觉地采用了对信息的编码。

中国古代语言学的研究主要集中在语义而非语法上。

即使学了10年的英语语法,也不能涵盖全部的英语。

自然语言在演变过程中,产生了语义上下文相关的特性。

最好的一种分词方法应该保证分完词后这个句子出现的概率最大。

少年时的教育,几个观点:
首先,小学生和中学生其实没有必要花那么多时间读书,而他们的社会经验、生活能力以及在那时树立起的志向将帮助他们的一生。第二,中学阶段花很多时间比同伴多读的课程,在大学以后用非常短的时间就可以读完,因为在大学阶段,人的理解力要强得多。举个例子,在中学需要花500小时才能学会的内容,在大学可能花100小时就够了。因此,一个学生在中小学阶段建立的那一点点优势在大学很快就会丧失殆尽。第三,学习(和教育)是一个人一辈子的过程,很多中学成绩好的亚裔学生进入名校后表现明显不如那些因为兴趣而读书的美国同伴,因为前者不断读书的动力不足。第四,书本的内容可以早学,也可以晚学,但是错过了成长阶段却是无法补回来的。

为了保证对任何搜索都能提供相关的网页,主要的搜索引擎都是对所有的词进行索引。

根据需要网页的重要性、质量和访问的频率建立常用和非常用等不同级别的索引。常用的索引需要访问速度快,附加的信息多,更新也要快;而非常用的要求就低多了。

离散数学是当代数学的一个重要分支,也是计算机科学的数学基础。它包括数理逻辑、集合论、图论和近世代数四个分支。

任何商业的搜索引擎,十条结果都有七八条是相关的了,这时一个新的搜索引擎在技术上投入再大,可提升的空间却非常有限,用户很难感觉到差别。这也是后来微软很难再搜索上有所作为的原因。

抓作弊需要一定的时间,以前只是还没有检测到这些作弊的网站而已。

永久链接: http://blog.ifeeline.com/283.html

Zen-cart 插件 Limint Access 根据国家和语言限制用户访问

根据国家和语言限制用户访问,本人编写这个插件就依据这个需求而写。你经常可能遇到这样的情况,去访问某个网址时,得到的结果是页面不存在,或定位到一个登录页面,输入密码后才能访问:
重定位到登录页
或者:
重定位到错误页面

要实现这个限制,需要依靠$_SERVER[‘HTTP_USER_AGENT’]变量的支持,这个变量获取浏览器支持的语言,一般按照如下格式传递:

zh-cn,en-us;q=0.7,en;q=0.3

它使用逗号分隔,得到的段中再以冒号分隔,这样一般就会得到两段,第一段是语言和国家代码,第二段是权值。先说第一段,它用横杠分出语言代码 和 国家代码,比如en-us表示en语言,就是英文,然后是国家代码us,就是美国,这个就代表了美国英语。第二段是一个权值,值越大表明优先值越大,如果没有指定值,那么它默认就是1,一般表示它首先接受的语言或国家代码,这里说的是一般,是因为有些浏览器并不按照这个规则操作,比如Chrome。

从这个分析来看,如果要限制中文,只要这个串含有zh,就可以认为它支持中文,那么它就是限制的对象,如果不想限制中文访问,但是要限制中国大陆访问,那么这个串中含有cn就是限制对象,这样香港中文和台湾中文就都可以访问了。

另外,对于某些爬虫是可以直接禁止的,比如LWP::Simple,BBBike,wget,scrapbot,这其中有的是知了名的采集程序,这个依赖$_SERVER[‘HTTP_USER_AGENT’]。

附上一段关键代码:

if(defined('LIMIT_ACCESS_ENABLE') && (LIMIT_ACCESS_ENABLE == 'true')){
			
	$spiders = array();
	if(defined('LIMIT_ACCESS_SPIDERS') && (LIMIT_ACCESS_SPIDERS != '')){
			$spiders = explode(',',LIMIT_ACCESS_SPIDERS);
	}
	if(count($spiders) <= 0){
		$spiders = array("LWP::Simple","BBBike","wget","scrapbot");
	}
		
	if(trim($_SERVER['HTTP_USER_AGENT']) == ""){
		header("Status: 404 Not Found");
		exit();
	}else{
		foreach($spiders as $ua){
			if( strpos(strtolower(trim($_SERVER['HTTP_USER_AGENT'])),strtolower(trim($ua))) !== false ){
				header("Status: 404 Not Found");
				break;
				exit();
			}
		}
	}

	$pass = true;
	if(defined('LIMIT_ACCESS_PASSWORD') && (LIMIT_ACCESS_PASSWORD != '')){
		if(isset($_SESSION['force_login']) && trim($_SESSION['force_login']) != ''){
			$pass = false;
		}
	}

	if($pass){
		$languages = array(); //array("zh");
		$contries = array();  //array("cn","jp","ko");
				
		if(defined('LIMIT_ACCESS_LANGUAGES') && (LIMIT_ACCESS_LANGUAGES != '')){
			$languages = explode(',',LIMIT_ACCESS_LANGUAGES);
		}
				
		if(defined('LIMIT_ACCESS_CONTRIES') && (LIMIT_ACCESS_CONTRIES != '')){
			$contries = explode(',',LIMIT_ACCESS_CONTRIES);
		}
				
		$break = false;
		foreach($languages as $lan){
			if( strpos(strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])),strtolower(trim($lan))) !== false ){
				$break = true;
				break;
			}
		}
				
		foreach($contries as $cou){
			if( strpos(strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])),strtolower(trim($cou))) !== false ){
				$break = true;
				break;
			}
		}
					
		if($break){
			if(defined('LIMIT_ACCESS_PASSWORD') && (LIMIT_ACCESS_PASSWORD != '')){
				if(strpos(strtolower($_SERVER['REQUEST_URI']),"login.php") === false){
					zen_redirect("/login.php");
					exit();
				}
			}else{
				header("Status: 404 Not Found");
				exit;
			}
		}
	}
		
}

另外,有些朋友希望根据浏览器所在的操作系统的语言来限制访问,比如如果操作系统语言是中文,则限制访问,但是在我测试的浏览器中,只有IE内核的浏览器可以获得操作系统语言。换句话说就是,如果你用IE来访问,而且用的是中文操作系统,而我现在要限制中文访问,那么你的IE就无法访问我的站点了,或者输入密码后才能进入。

原创文章,转载务必保留本文链接。
永久链接:http://blog.ifeeline.com/276.html