class-wp-http-cookie.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. /**
  3. * HTTP API: WP_Http_Cookie class
  4. *
  5. * @package WordPress
  6. * @subpackage HTTP
  7. * @since 4.4.0
  8. */
  9. /**
  10. * Core class used to encapsulate a single cookie object for internal use.
  11. *
  12. * Returned cookies are represented using this class, and when cookies are set, if they are not
  13. * already a WP_Http_Cookie() object, then they are turned into one.
  14. *
  15. * @todo The WordPress convention is to use underscores instead of camelCase for function and method
  16. * names. Need to switch to use underscores instead for the methods.
  17. *
  18. * @since 2.8.0
  19. */
  20. #[AllowDynamicProperties]
  21. class WP_Http_Cookie {
  22. /**
  23. * Cookie name.
  24. *
  25. * @since 2.8.0
  26. *
  27. * @var string
  28. */
  29. public $name;
  30. /**
  31. * Cookie value.
  32. *
  33. * @since 2.8.0
  34. *
  35. * @var string
  36. */
  37. public $value;
  38. /**
  39. * When the cookie expires. Unix timestamp or formatted date.
  40. *
  41. * @since 2.8.0
  42. *
  43. * @var string|int|null
  44. */
  45. public $expires;
  46. /**
  47. * Cookie URL path.
  48. *
  49. * @since 2.8.0
  50. *
  51. * @var string
  52. */
  53. public $path;
  54. /**
  55. * Cookie Domain.
  56. *
  57. * @since 2.8.0
  58. *
  59. * @var string
  60. */
  61. public $domain;
  62. /**
  63. * Cookie port or comma-separated list of ports.
  64. *
  65. * @since 2.8.0
  66. *
  67. * @var int|string
  68. */
  69. public $port;
  70. /**
  71. * host-only flag.
  72. *
  73. * @since 5.2.0
  74. *
  75. * @var bool
  76. */
  77. public $host_only;
  78. /**
  79. * Sets up this cookie object.
  80. *
  81. * The parameter $data should be either an associative array containing the indices names below
  82. * or a header string detailing it.
  83. *
  84. * @since 2.8.0
  85. * @since 5.2.0 Added `host_only` to the `$data` parameter.
  86. *
  87. * @param string|array $data {
  88. * Raw cookie data as header string or data array.
  89. *
  90. * @type string $name Cookie name.
  91. * @type mixed $value Value. Should NOT already be urlencoded.
  92. * @type string|int|null $expires Optional. Unix timestamp or formatted date. Default null.
  93. * @type string $path Optional. Path. Default '/'.
  94. * @type string $domain Optional. Domain. Default host of parsed $requested_url.
  95. * @type int|string $port Optional. Port or comma-separated list of ports. Default null.
  96. * @type bool $host_only Optional. host-only storage flag. Default true.
  97. * }
  98. * @param string $requested_url The URL which the cookie was set on, used for default $domain
  99. * and $port values.
  100. */
  101. public function __construct( $data, $requested_url = '' ) {
  102. if ( $requested_url ) {
  103. $parsed_url = parse_url( $requested_url );
  104. }
  105. if ( isset( $parsed_url['host'] ) ) {
  106. $this->domain = $parsed_url['host'];
  107. }
  108. $this->path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/';
  109. if ( '/' !== substr( $this->path, -1 ) ) {
  110. $this->path = dirname( $this->path ) . '/';
  111. }
  112. if ( is_string( $data ) ) {
  113. // Assume it's a header string direct from a previous request.
  114. $pairs = explode( ';', $data );
  115. // Special handling for first pair; name=value. Also be careful of "=" in value.
  116. $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) );
  117. $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 );
  118. $this->name = $name;
  119. $this->value = urldecode( $value );
  120. // Removes name=value from items.
  121. array_shift( $pairs );
  122. // Set everything else as a property.
  123. foreach ( $pairs as $pair ) {
  124. $pair = rtrim( $pair );
  125. // Handle the cookie ending in ; which results in a empty final pair.
  126. if ( empty( $pair ) ) {
  127. continue;
  128. }
  129. list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' );
  130. $key = strtolower( trim( $key ) );
  131. if ( 'expires' === $key ) {
  132. $val = strtotime( $val );
  133. }
  134. $this->$key = $val;
  135. }
  136. } else {
  137. if ( ! isset( $data['name'] ) ) {
  138. return;
  139. }
  140. // Set properties based directly on parameters.
  141. foreach ( array( 'name', 'value', 'path', 'domain', 'port', 'host_only' ) as $field ) {
  142. if ( isset( $data[ $field ] ) ) {
  143. $this->$field = $data[ $field ];
  144. }
  145. }
  146. if ( isset( $data['expires'] ) ) {
  147. $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] );
  148. } else {
  149. $this->expires = null;
  150. }
  151. }
  152. }
  153. /**
  154. * Confirms that it's OK to send this cookie to the URL checked against.
  155. *
  156. * Decision is based on RFC 2109/2965, so look there for details on validity.
  157. *
  158. * @since 2.8.0
  159. *
  160. * @param string $url URL you intend to send this cookie to
  161. * @return bool true if allowed, false otherwise.
  162. */
  163. public function test( $url ) {
  164. if ( is_null( $this->name ) ) {
  165. return false;
  166. }
  167. // Expires - if expired then nothing else matters.
  168. if ( isset( $this->expires ) && time() > $this->expires ) {
  169. return false;
  170. }
  171. // Get details on the URL we're thinking about sending to.
  172. $url = parse_url( $url );
  173. $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' === $url['scheme'] ? 443 : 80 );
  174. $url['path'] = isset( $url['path'] ) ? $url['path'] : '/';
  175. // Values to use for comparison against the URL.
  176. $path = isset( $this->path ) ? $this->path : '/';
  177. $port = isset( $this->port ) ? $this->port : null;
  178. $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] );
  179. if ( false === stripos( $domain, '.' ) ) {
  180. $domain .= '.local';
  181. }
  182. // Host - very basic check that the request URL ends with the domain restriction (minus leading dot).
  183. $domain = ( '.' === substr( $domain, 0, 1 ) ) ? substr( $domain, 1 ) : $domain;
  184. if ( substr( $url['host'], -strlen( $domain ) ) !== $domain ) {
  185. return false;
  186. }
  187. // Port - supports "port-lists" in the format: "80,8000,8080".
  188. if ( ! empty( $port ) && ! in_array( $url['port'], array_map( 'intval', explode( ',', $port ) ), true ) ) {
  189. return false;
  190. }
  191. // Path - request path must start with path restriction.
  192. if ( substr( $url['path'], 0, strlen( $path ) ) !== $path ) {
  193. return false;
  194. }
  195. return true;
  196. }
  197. /**
  198. * Convert cookie name and value back to header string.
  199. *
  200. * @since 2.8.0
  201. *
  202. * @return string Header encoded cookie name and value.
  203. */
  204. public function getHeaderValue() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
  205. if ( ! isset( $this->name ) || ! isset( $this->value ) ) {
  206. return '';
  207. }
  208. /**
  209. * Filters the header-encoded cookie value.
  210. *
  211. * @since 3.4.0
  212. *
  213. * @param string $value The cookie value.
  214. * @param string $name The cookie name.
  215. */
  216. return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name );
  217. }
  218. /**
  219. * Retrieve cookie header for usage in the rest of the WordPress HTTP API.
  220. *
  221. * @since 2.8.0
  222. *
  223. * @return string
  224. */
  225. public function getFullHeader() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
  226. return 'Cookie: ' . $this->getHeaderValue();
  227. }
  228. /**
  229. * Retrieves cookie attributes.
  230. *
  231. * @since 4.6.0
  232. *
  233. * @return array {
  234. * List of attributes.
  235. *
  236. * @type string|int|null $expires When the cookie expires. Unix timestamp or formatted date.
  237. * @type string $path Cookie URL path.
  238. * @type string $domain Cookie domain.
  239. * }
  240. */
  241. public function get_attributes() {
  242. return array(
  243. 'expires' => $this->expires,
  244. 'path' => $this->path,
  245. 'domain' => $this->domain,
  246. );
  247. }
  248. }