Cookie.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <?php
  2. /**
  3. * Cookie for HTTP requests.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup HTTP
  22. */
  23. class Cookie {
  24. protected $name;
  25. protected $value;
  26. protected $expires;
  27. protected $path;
  28. protected $domain;
  29. protected $isSessionKey = true;
  30. // TO IMPLEMENT protected $secure
  31. // TO IMPLEMENT? protected $maxAge (add onto expires)
  32. // TO IMPLEMENT? protected $version
  33. // TO IMPLEMENT? protected $comment
  34. function __construct( $name, $value, $attr ) {
  35. $this->name = $name;
  36. $this->set( $value, $attr );
  37. }
  38. /**
  39. * Sets a cookie. Used before a request to set up any individual
  40. * cookies. Used internally after a request to parse the
  41. * Set-Cookie headers.
  42. *
  43. * @param string $value The value of the cookie
  44. * @param array $attr Possible key/values:
  45. * expires A date string
  46. * path The path this cookie is used on
  47. * domain Domain this cookie is used on
  48. * @throws InvalidArgumentException
  49. */
  50. public function set( $value, $attr ) {
  51. $this->value = $value;
  52. if ( isset( $attr['expires'] ) ) {
  53. $this->isSessionKey = false;
  54. $this->expires = strtotime( $attr['expires'] );
  55. }
  56. $this->path = $attr['path'] ?? '/';
  57. if ( isset( $attr['domain'] ) ) {
  58. if ( self::validateCookieDomain( $attr['domain'] ) ) {
  59. $this->domain = $attr['domain'];
  60. }
  61. } else {
  62. throw new InvalidArgumentException( '$attr must contain a domain' );
  63. }
  64. }
  65. /**
  66. * Return the true if the cookie is valid is valid. Otherwise,
  67. * false. The uses a method similar to IE cookie security
  68. * described here:
  69. * http://kuza55.blogspot.com/2008/02/understanding-cookie-security.html
  70. * A better method might be to use a blacklist like
  71. * http://publicsuffix.org/
  72. *
  73. * @todo fixme fails to detect 3-letter top-level domains
  74. * @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably
  75. * not a big problem in practice, but there are test cases)
  76. *
  77. * @param string $domain The domain to validate
  78. * @param string|null $originDomain (optional) the domain the cookie originates from
  79. * @return bool
  80. */
  81. public static function validateCookieDomain( $domain, $originDomain = null ) {
  82. $dc = explode( ".", $domain );
  83. // Don't allow a trailing dot or addresses without a or just a leading dot
  84. if ( substr( $domain, -1 ) == '.' ||
  85. count( $dc ) <= 1 ||
  86. count( $dc ) == 2 && $dc[0] === ''
  87. ) {
  88. return false;
  89. }
  90. // Only allow full, valid IP addresses
  91. if ( preg_match( '/^[0-9.]+$/', $domain ) ) {
  92. if ( count( $dc ) != 4 ) {
  93. return false;
  94. }
  95. if ( ip2long( $domain ) === false ) {
  96. return false;
  97. }
  98. if ( $originDomain == null || $originDomain == $domain ) {
  99. return true;
  100. }
  101. }
  102. // Don't allow cookies for "co.uk" or "gov.uk", etc, but allow "supermarket.uk"
  103. if ( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) {
  104. if ( ( count( $dc ) == 2 && strlen( $dc[0] ) <= 2 )
  105. || ( count( $dc ) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) {
  106. return false;
  107. }
  108. if ( ( count( $dc ) == 2 || ( count( $dc ) == 3 && $dc[0] == '' ) )
  109. && preg_match( '/(com|net|org|gov|edu)\...$/', $domain ) ) {
  110. return false;
  111. }
  112. }
  113. if ( $originDomain != null ) {
  114. if ( substr( $domain, 0, 1 ) != '.' && $domain != $originDomain ) {
  115. return false;
  116. }
  117. if ( substr( $domain, 0, 1 ) == '.'
  118. && substr_compare(
  119. $originDomain,
  120. $domain,
  121. -strlen( $domain ),
  122. strlen( $domain ),
  123. true
  124. ) != 0
  125. ) {
  126. return false;
  127. }
  128. }
  129. return true;
  130. }
  131. /**
  132. * Serialize the cookie jar into a format useful for HTTP Request headers.
  133. *
  134. * @param string $path The path that will be used. Required.
  135. * @param string $domain The domain that will be used. Required.
  136. * @return string
  137. */
  138. public function serializeToHttpRequest( $path, $domain ) {
  139. $ret = '';
  140. if ( $this->canServeDomain( $domain )
  141. && $this->canServePath( $path )
  142. && $this->isUnExpired() ) {
  143. $ret = $this->name . '=' . $this->value;
  144. }
  145. return $ret;
  146. }
  147. /**
  148. * @param string $domain
  149. * @return bool
  150. */
  151. protected function canServeDomain( $domain ) {
  152. if ( $domain == $this->domain
  153. || ( strlen( $domain ) > strlen( $this->domain )
  154. && substr( $this->domain, 0, 1 ) == '.'
  155. && substr_compare(
  156. $domain,
  157. $this->domain,
  158. -strlen( $this->domain ),
  159. strlen( $this->domain ),
  160. true
  161. ) == 0
  162. )
  163. ) {
  164. return true;
  165. }
  166. return false;
  167. }
  168. /**
  169. * @param string $path
  170. * @return bool
  171. */
  172. protected function canServePath( $path ) {
  173. return ( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 );
  174. }
  175. /**
  176. * @return bool
  177. */
  178. protected function isUnExpired() {
  179. return $this->isSessionKey || $this->expires > time();
  180. }
  181. }