PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/phase3/includes/Cookie.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 248 lines | 141 code | 37 blank | 70 comment | 64 complexity | 1af61eec04d358e81327a2074d0b6050 MD5 | raw file
  1. <?php
  2. /**
  3. * @defgroup HTTP HTTP
  4. */
  5. class Cookie {
  6. protected $name;
  7. protected $value;
  8. protected $expires;
  9. protected $path;
  10. protected $domain;
  11. protected $isSessionKey = true;
  12. // TO IMPLEMENT protected $secure
  13. // TO IMPLEMENT? protected $maxAge (add onto expires)
  14. // TO IMPLEMENT? protected $version
  15. // TO IMPLEMENT? protected $comment
  16. function __construct( $name, $value, $attr ) {
  17. $this->name = $name;
  18. $this->set( $value, $attr );
  19. }
  20. /**
  21. * Sets a cookie. Used before a request to set up any individual
  22. * cookies. Used internally after a request to parse the
  23. * Set-Cookie headers.
  24. *
  25. * @param $value String: the value of the cookie
  26. * @param $attr Array: possible key/values:
  27. * expires A date string
  28. * path The path this cookie is used on
  29. * domain Domain this cookie is used on
  30. */
  31. public function set( $value, $attr ) {
  32. $this->value = $value;
  33. if ( isset( $attr['expires'] ) ) {
  34. $this->isSessionKey = false;
  35. $this->expires = strtotime( $attr['expires'] );
  36. }
  37. if ( isset( $attr['path'] ) ) {
  38. $this->path = $attr['path'];
  39. } else {
  40. $this->path = '/';
  41. }
  42. if ( isset( $attr['domain'] ) ) {
  43. if ( self::validateCookieDomain( $attr['domain'] ) ) {
  44. $this->domain = $attr['domain'];
  45. }
  46. } else {
  47. throw new MWException( 'You must specify a domain.' );
  48. }
  49. }
  50. /**
  51. * Return the true if the cookie is valid is valid. Otherwise,
  52. * false. The uses a method similar to IE cookie security
  53. * described here:
  54. * http://kuza55.blogspot.com/2008/02/understanding-cookie-security.html
  55. * A better method might be to use a blacklist like
  56. * http://publicsuffix.org/
  57. *
  58. * @fixme fails to detect 3-letter top-level domains
  59. * @fixme fails to detect 2-letter top-level domains for single-domain use (probably not a big problem in practice, but there are test cases)
  60. *
  61. * @param $domain String: the domain to validate
  62. * @param $originDomain String: (optional) the domain the cookie originates from
  63. * @return Boolean
  64. */
  65. public static function validateCookieDomain( $domain, $originDomain = null ) {
  66. // Don't allow a trailing dot
  67. if ( substr( $domain, -1 ) == '.' ) {
  68. return false;
  69. }
  70. $dc = explode( ".", $domain );
  71. // Only allow full, valid IP addresses
  72. if ( preg_match( '/^[0-9.]+$/', $domain ) ) {
  73. if ( count( $dc ) != 4 ) {
  74. return false;
  75. }
  76. if ( ip2long( $domain ) === false ) {
  77. return false;
  78. }
  79. if ( $originDomain == null || $originDomain == $domain ) {
  80. return true;
  81. }
  82. }
  83. // Don't allow cookies for "co.uk" or "gov.uk", etc, but allow "supermarket.uk"
  84. if ( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) {
  85. if ( ( count( $dc ) == 2 && strlen( $dc[0] ) <= 2 )
  86. || ( count( $dc ) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) {
  87. return false;
  88. }
  89. if ( ( count( $dc ) == 2 || ( count( $dc ) == 3 && $dc[0] == '' ) )
  90. && preg_match( '/(com|net|org|gov|edu)\...$/', $domain ) ) {
  91. return false;
  92. }
  93. }
  94. if ( $originDomain != null ) {
  95. if ( substr( $domain, 0, 1 ) != '.' && $domain != $originDomain ) {
  96. return false;
  97. }
  98. if ( substr( $domain, 0, 1 ) == '.'
  99. && substr_compare( $originDomain, $domain, -strlen( $domain ),
  100. strlen( $domain ), true ) != 0 ) {
  101. return false;
  102. }
  103. }
  104. return true;
  105. }
  106. /**
  107. * Serialize the cookie jar into a format useful for HTTP Request headers.
  108. *
  109. * @param $path String: the path that will be used. Required.
  110. * @param $domain String: the domain that will be used. Required.
  111. * @return String
  112. */
  113. public function serializeToHttpRequest( $path, $domain ) {
  114. $ret = '';
  115. if ( $this->canServeDomain( $domain )
  116. && $this->canServePath( $path )
  117. && $this->isUnExpired() ) {
  118. $ret = $this->name . '=' . $this->value;
  119. }
  120. return $ret;
  121. }
  122. /**
  123. * @param $domain
  124. * @return bool
  125. */
  126. protected function canServeDomain( $domain ) {
  127. if ( $domain == $this->domain
  128. || ( strlen( $domain ) > strlen( $this->domain )
  129. && substr( $this->domain, 0, 1 ) == '.'
  130. && substr_compare( $domain, $this->domain, -strlen( $this->domain ),
  131. strlen( $this->domain ), true ) == 0 ) ) {
  132. return true;
  133. }
  134. return false;
  135. }
  136. /**
  137. * @param $path
  138. * @return bool
  139. */
  140. protected function canServePath( $path ) {
  141. return ( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 );
  142. }
  143. /**
  144. * @return bool
  145. */
  146. protected function isUnExpired() {
  147. return $this->isSessionKey || $this->expires > time();
  148. }
  149. }
  150. class CookieJar {
  151. private $cookie = array();
  152. /**
  153. * Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
  154. * @see Cookie::set()
  155. */
  156. public function setCookie ( $name, $value, $attr ) {
  157. /* cookies: case insensitive, so this should work.
  158. * We'll still send the cookies back in the same case we got them, though.
  159. */
  160. $index = strtoupper( $name );
  161. if ( isset( $this->cookie[$index] ) ) {
  162. $this->cookie[$index]->set( $value, $attr );
  163. } else {
  164. $this->cookie[$index] = new Cookie( $name, $value, $attr );
  165. }
  166. }
  167. /**
  168. * @see Cookie::serializeToHttpRequest
  169. */
  170. public function serializeToHttpRequest( $path, $domain ) {
  171. $cookies = array();
  172. foreach ( $this->cookie as $c ) {
  173. $serialized = $c->serializeToHttpRequest( $path, $domain );
  174. if ( $serialized ) {
  175. $cookies[] = $serialized;
  176. }
  177. }
  178. return implode( '; ', $cookies );
  179. }
  180. /**
  181. * Parse the content of an Set-Cookie HTTP Response header.
  182. *
  183. * @param $cookie String
  184. * @param $domain String: cookie's domain
  185. */
  186. public function parseCookieResponseHeader ( $cookie, $domain ) {
  187. $len = strlen( 'Set-Cookie:' );
  188. if ( substr_compare( 'Set-Cookie:', $cookie, 0, $len, true ) === 0 ) {
  189. $cookie = substr( $cookie, $len );
  190. }
  191. $bit = array_map( 'trim', explode( ';', $cookie ) );
  192. if ( count( $bit ) >= 1 ) {
  193. list( $name, $value ) = explode( '=', array_shift( $bit ), 2 );
  194. $attr = array();
  195. foreach ( $bit as $piece ) {
  196. $parts = explode( '=', $piece );
  197. if ( count( $parts ) > 1 ) {
  198. $attr[strtolower( $parts[0] )] = $parts[1];
  199. } else {
  200. $attr[strtolower( $parts[0] )] = true;
  201. }
  202. }
  203. if ( !isset( $attr['domain'] ) ) {
  204. $attr['domain'] = $domain;
  205. } elseif ( !Cookie::validateCookieDomain( $attr['domain'], $domain ) ) {
  206. return null;
  207. }
  208. $this->setCookie( $name, $value, $attr );
  209. }
  210. }
  211. }