summaryrefslogtreecommitdiff
path: root/framework/Web/UI/TPageStatePersister.php
blob: d312ed8452e8b5c91a1739e18e1c367ba67718f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<?php
/**
 * TPageStatePersister class file
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Revision: $  $Date: $
 * @package System.Web.UI
 */

/**
 * TPageStatePersister class
 *
 * TPageStatePersister implements a page state persistent method based on
 * form hidden fields. It is the default way of storing page state.
 * Should you need to access this module, you may get it via
 * {@link TPageService::getPageStatePersister}.
 *
 * TPageStatePersister uses a private key to generate a private unique hash
 * code to prevent the page state from being tampered.
 * By default, the private key is a randomly generated string.
 * You may specify it explicitly by setting the {@link setPrivateKey PrivateKey} property.
 * This may be useful if your application is running on a server farm.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @version $Revision: $  $Date: $
 * @package System.Web.UI
 * @since 3.0
 */
class TPageStatePersister extends TModule implements IStatePersister
{
	/**
	 * @var string private key
	 */
	private $_privateKey=null;

	/**
	 * Registers the module with the page service.
	 * This method is required by IModule interface and is invoked when the module is initialized.
	 * @param TXmlElement module configuration
	 */
	public function init($config)
	{
		$this->getService()->setPageStatePersister($this);
	}

	/**
	 * Saves state in hidden fields.
	 * @param mixed state to be stored
	 */
	public function save($state)
	{
		$data=Prado::serialize($state);
		$hmac=$this->computeHMAC($data,$this->getPrivateKey());
		if(extension_loaded('zlib'))
			$data=gzcompress($hmac.$data);
		else
			$data=$hmac.$data;
		$this->getService()->getRequestedPage()->getClientScript()->registerHiddenField(TPage::FIELD_PAGESTATE,base64_encode($data));
	}

	/**
	 * Loads page state from hidden fields.
	 * @return mixed the restored state
	 * @throws THttpException if page state is corrupted
	 */
	public function load()
	{
		$str=base64_decode($this->getApplication()->getRequest()->getItems()->itemAt(TPage::FIELD_PAGESTATE));
		if($str==='')
			return null;
		if(extension_loaded('zlib'))
			$data=gzuncompress($str);
		else
			$data=$str;
		if($data!==false && strlen($data)>32)
		{
			$hmac=substr($data,0,32);
			$state=substr($data,32);
			if($hmac===$this->computeHMAC($state,$this->getPrivateKey()))
				return Prado::unserialize($state);
		}
		throw new THttpException(400,'pagestatepersister_pagestate_corrupted');
	}

	/**
	 * Generates a random private key used for hashing the state.
	 * You may override this method to provide your own way of private key generation.
	 * @return string  the rondomly generated private key
	 */
	protected function generatePrivateKey()
	{
		$v1=rand();
		$v2=rand();
		$v3=rand();
		return md5("$v1$v2$v3");
	}

	/**
	 * @return string private key used for hashing the state.
	 */
	public function getPrivateKey()
	{
		if(empty($this->_privateKey))
		{
			if(($this->_privateKey=$this->getApplication()->getGlobalState('prado:pagestatepersister:privatekey'))===null)
			{
				$this->_privateKey=$this->generatePrivateKey();
				$this->getApplication()->setGlobalState('prado:pagestatepersister:privatekey',$this->_privateKey,null);
			}
		}
		return $this->_privateKey;
	}

	/**
	 * @param string private key used for hashing the state.
	 * @throws TInvalidDataValueException if the length of the private key is shorter than 8.
	 */
	public function setPrivateKey($value)
	{
		if(strlen($value)<8)
			throw new TInvalidDataValueException('pagestatepersister_privatekey_invalid');
		$this->_privateKey=$value;
	}

	/**
	 * Computes a hashing code based on the input data and the private key.
	 * @param string input data
	 * @param string the private key
	 * @return string the hashing code
	 */
	private function computeHMAC($data,$key)
	{
		if (strlen($key) > 64)
			$key = pack('H32', md5($key));
		else if (strlen($key) < 64)
			$key = str_pad($key, 64, "\0");
		return md5((str_repeat("\x5c", 64) ^ substr($key, 0, 64)) . pack('H32', md5((str_repeat("\x36", 64) ^ substr($key, 0, 64)) . $data)));
	}
}

?>