diff --git a/wp-includes/functions.php b/wp-includes/functions.php index 6bcc79061..6fb59a478 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -624,6 +624,80 @@ function delete_option( $name ) { return true; } +/** + * Delete a transient + * + * @since 2.8.0 + * @package WordPress + * @subpackage Transient + * + * @param string $transient Transient name. Expected to not be SQL-escaped + * @return bool true if successful, false otherwise + */ +function delete_transient($transient) { + global $_wp_using_ext_object_cache, $wpdb; + + if ( $_wp_using_ext_object_cache ) { + return wp_cache_delete($transient, 'transient'); + } else { + $transient = '_transient_' . $wpdb->escape($transient); + return delete_option($transient); + } +} + +/** + * Get the value of a transient + * + * If the transient does not exist or does not have a value, then the return value + * will be false. + * + * @since 2.8.0 + * @package WordPress + * @subpackage Transient + * + * @param string $transient Transient name. Expected to not be SQL-escaped + * @return mixed Value of transient + */ +function get_transient($transient) { + global $_wp_using_ext_object_cache, $wpdb; + + if ( $_wp_using_ext_object_cache ) { + return wp_cache_get($transient, 'transient'); + } else { + $transient = '_transient_' . $wpdb->escape($transient); + return get_option($transient); + } +} + +/** + * Set/update the value of a transient + * + * You do not need to serialize values, if the value needs to be serialize, then + * it will be serialized before it is set. + * + * @since 2.8.0 + * @package WordPress + * @subpackage Transient + * + * @param string $transient Transient name. Expected to not be SQL-escaped + * @param mixed $value Transient value. + * @return bool False if value was not set and true if value was set. + */ +function set_transient($transient, $value) { + global $_wp_using_ext_object_cache, $wpdb; + + if ( $_wp_using_ext_object_cache ) { + return wp_cache_set($transient, $value, 'transient'); + } else { + $transient = '_transient_' . $transient; + $safe_transient = $wpdb->escape($transient); + if ( false === get_option( $safe_transient ) ) + return add_option($transient, $value, '', 'no'); + else + return update_option($transient, $value); + } +} + /** * Saves and restores user interface settings stored in a cookie. * diff --git a/wp-includes/http.php b/wp-includes/http.php index a832e1748..1c06bf611 100644 --- a/wp-includes/http.php +++ b/wp-includes/http.php @@ -351,7 +351,7 @@ class WP_Http { * * @param string $url URI resource. * @param str|array $args Optional. Override the defaults. - * @return boolean + * @return array containing 'headers', 'body', 'response', 'cookies' */ function request( $url, $args = array() ) { global $wp_version; @@ -399,6 +399,9 @@ class WP_Http { $r['user-agent'] = $r['headers']['user-agent']; unset($r['headers']['user-agent']); } + + // Construct Cookie: header if any cookies are set + WP_Http::buildCookieHeader( $r ); if( WP_Http_Encoding::is_available() ) $r['headers']['Accept-Encoding'] = WP_Http_Encoding::accept_encoding(); @@ -424,10 +427,10 @@ class WP_Http { if( has_action('http_api_debug') ) do_action('http_api_debug', $transports, 'transports_list'); - $response = array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); + $response = array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); foreach( (array) $transports as $transport ) { $response = $transport->request($url, $r); - + if( has_action('http_api_debug') ) do_action( 'http_api_debug', $response, 'response', get_class($transport) ); @@ -519,7 +522,8 @@ class WP_Http { * @since 2.7.0 * * @param string|array $headers - * @return array Processed string headers + * @return array Processed string headers. If duplicate headers are encountered, + * Then a numbered array is returned as the value of that header-key. */ function processHeaders($headers) { if ( is_string($headers) ) @@ -527,6 +531,7 @@ class WP_Http { $response = array('code' => 0, 'message' => ''); + $cookies = array(); $newheaders = array(); foreach ( $headers as $tempheader ) { if ( empty($tempheader) ) @@ -541,13 +546,43 @@ class WP_Http { list($key, $value) = explode(':', $tempheader, 2); - if ( ! empty($value) ) - $newheaders[strtolower($key)] = trim($value); + if ( !empty( $value ) ) { + $key = strtolower( $key ); + if ( isset( $newheaders[$key] ) ) { + $newheaders[$key] = array( $newheaders[$key], trim( $value ) ); + } else { + $newheaders[$key] = trim( $value ); + } + if ( 'set-cookie' == strtolower( $key ) ) + $cookies[] = new WP_Http_Cookie( $value ); + } } - return array('response' => $response, 'headers' => $newheaders); + return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies); } - + + /** + * Takes the arguments for a ::request() and checks for the cookie array. + * If it's found, then it's assumed to contain WP_Http_Cookie objects, which + * are each parsed into strings and added to the Cookie: header (within the + * arguments array). Edits the array by reference. + * + * @access public + * @static + * + * @param array $r Full array of args passed into ::request() + */ + function buildCookieHeader( &$r ) { + if ( count( $r['cookies'] ) ) { + $cookies_header = ''; + foreach ( $r['cookies'] as $cookie ) { + $cookies_header .= $cookie->getHeaderValue() . '; '; + } + $cookies_header = substr( $cookies_header, 0, -2 ); + $r['headers']['cookie'] = $cookies_header; + } + } + /** * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification. * @@ -618,7 +653,7 @@ class WP_Http_Fsockopen { * @access public * @param string $url URI resource. * @param str|array $args Optional. Override the defaults. - * @return array 'headers', 'body', and 'response' keys. + * @return array 'headers', 'body', 'cookies' and 'response' keys. */ function request($url, $args = array()) { $defaults = array( @@ -638,6 +673,9 @@ class WP_Http_Fsockopen { unset($r['headers']['user-agent']); } + // Construct Cookie: header if any cookies are set + WP_Http::buildCookieHeader( $r ); + $iError = null; // Store error number $strError = null; // Store error string @@ -710,7 +748,7 @@ class WP_Http_Fsockopen { if ( ! $r['blocking'] ) { fclose($handle); - return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); } $strResponse = ''; @@ -745,7 +783,7 @@ class WP_Http_Fsockopen { if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders) ) $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); - return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response']); + return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies']); } /** @@ -793,7 +831,7 @@ class WP_Http_Fopen { * * @param string $url URI resource. * @param str|array $args Optional. Override the defaults. - * @return array 'headers', 'body', and 'response' keys. + * @return array 'headers', 'body', 'cookies' and 'response' keys. */ function request($url, $args = array()) { global $http_response_header; @@ -802,7 +840,7 @@ class WP_Http_Fopen { 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, - 'headers' => array(), 'body' => null + 'headers' => array(), 'body' => null, 'cookies' => array() ); $r = wp_parse_args( $args, $defaults ); @@ -829,7 +867,7 @@ class WP_Http_Fopen { if ( ! $r['blocking'] ) { fclose($handle); - return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); } $strResponse = ''; @@ -858,7 +896,7 @@ class WP_Http_Fopen { if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders) ) $strResponse = WP_Http_Encoding::decompress( $strResponse ); - return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']); + return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']); } /** @@ -897,14 +935,14 @@ class WP_Http_Streams { * * @param string $url * @param str|array $args Optional. Override the defaults. - * @return array 'headers', 'body', and 'response' keys. + * @return array 'headers', 'body', 'cookies' and 'response' keys. */ function request($url, $args = array()) { $defaults = array( 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, - 'headers' => array(), 'body' => null + 'headers' => array(), 'body' => null, 'cookies' => array() ); $r = wp_parse_args( $args, $defaults ); @@ -916,6 +954,9 @@ class WP_Http_Streams { $r['user-agent'] = $r['headers']['user-agent']; unset($r['headers']['user-agent']); } + + // Construct Cookie: header if any cookies are set + WP_Http::buildCookieHeader( $r ); $arrURL = parse_url($url); @@ -968,7 +1009,7 @@ class WP_Http_Streams { if ( ! $r['blocking'] ) { stream_set_blocking($handle, 0); fclose($handle); - return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); } $strResponse = stream_get_contents($handle); @@ -988,7 +1029,7 @@ class WP_Http_Streams { if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders) ) $strResponse = WP_Http_Encoding::decompress( $strResponse ); - return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']); + return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']); } /** @@ -1034,14 +1075,14 @@ class WP_Http_ExtHTTP { * * @param string $url * @param str|array $args Optional. Override the defaults. - * @return array 'headers', 'body', and 'response' keys. + * @return array 'headers', 'body', 'cookies' and 'response' keys. */ function request($url, $args = array()) { $defaults = array( 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, - 'headers' => array(), 'body' => null + 'headers' => array(), 'body' => null, 'cookies' => array() ); $r = wp_parse_args( $args, $defaults ); @@ -1053,6 +1094,9 @@ class WP_Http_ExtHTTP { $r['user-agent'] = $r['headers']['user-agent']; unset($r['headers']['user-agent']); } + + // Construct Cookie: header if any cookies are set + WP_Http::buildCookieHeader( $r ); switch ( $r['method'] ) { case 'POST': @@ -1092,7 +1136,7 @@ class WP_Http_ExtHTTP { return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']); if ( ! $r['blocking'] ) - return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2); $theHeaders = WP_Http::processHeaders($theHeaders); @@ -1111,7 +1155,7 @@ class WP_Http_ExtHTTP { $theResponse['code'] = $info['response_code']; $theResponse['message'] = get_status_header_desc($info['response_code']); - return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse); + return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse, 'cookies' => $theHeaders['cookies']); } /** @@ -1148,14 +1192,14 @@ class WP_Http_Curl { * * @param string $url * @param str|array $args Optional. Override the defaults. - * @return array 'headers', 'body', and 'response' keys. + * @return array 'headers', 'body', 'cookies' and 'response' keys. */ function request($url, $args = array()) { $defaults = array( 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, - 'headers' => array(), 'body' => null + 'headers' => array(), 'body' => null, 'cookies' => array() ); $r = wp_parse_args( $args, $defaults ); @@ -1167,6 +1211,9 @@ class WP_Http_Curl { $r['user-agent'] = $r['headers']['user-agent']; unset($r['headers']['user-agent']); } + + // Construct Cookie: header if any cookies are set + WP_Http::buildCookieHeader( $r ); // cURL extension will sometimes fail when the timeout is less than 1 as // it may round down to 0, which gives it unlimited timeout. @@ -1203,8 +1250,14 @@ class WP_Http_Curl { if ( !ini_get('safe_mode') && !ini_get('open_basedir') ) curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true ); - if( ! is_null($r['headers']) ) - curl_setopt( $handle, CURLOPT_HTTPHEADER, $r['headers'] ); + if ( !empty( $r['headers'] ) ) { + // cURL expects full header strings in each element + $headers = array(); + foreach ( $r['headers'] as $name => $value ) { + $headers[] = "{$name}: $value"; + } + curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); + } if ( $r['httpversion'] == '1.0' ) curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); @@ -1216,12 +1269,12 @@ class WP_Http_Curl { // without some reference. do_action_ref_array( 'http_api_curl', array(&$handle) ); - // We don't need to return the body, so don't. Just execution request + // We don't need to return the body, so don't. Just execute request // and return. if ( ! $r['blocking'] ) { curl_exec( $handle ); curl_close( $handle ); - return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); } $theResponse = curl_exec( $handle ); @@ -1241,7 +1294,7 @@ class WP_Http_Curl { if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) ) return new WP_Error('http_request_failed', __('Too many redirects.')); - $theHeaders = array( 'headers' => array() ); + $theHeaders = array( 'headers' => array(), 'cookies' => array() ); $theBody = ''; } @@ -1254,7 +1307,7 @@ class WP_Http_Curl { if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders) ) $theBody = WP_Http_Encoding::decompress( $theBody ); - return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response); + return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies']); } /** @@ -1273,6 +1326,129 @@ class WP_Http_Curl { } } + +/** + * Internal representation of a cookie. + * + * Returned cookies are represented using this class, and when cookies are + * set, if they are not already a WP_Http_Cookie() object, then they are turned + * into one. + * + * @package WordPress + * @subpackage HTTP + */ +class WP_Http_Cookie { + var $name, + $value, + $expires, + $path, + $domain; + + /** + * PHP4 style Constructor - Calls PHP5 Style Constructor + */ + function WP_Http_Cookie( $data ) { + return $this->__construct( $data ); + } + + /** + * Sets up this cookie object. + * + * @access public + * + * @param mixed $data Either an associative array describing the cookie, or a header-string detailing it. + * If it's an array, it should include the following elements: + * - name + * - value [should NOT be urlencoded already] + * - expires (optional) String or int (UNIX timestamp) + * - path (optional) + * - domain (optional) + */ + function __construct( $data ) { + if ( is_string( $data ) ) { + // Assume it's a header string direct from a previous request + $pairs = explode( ';', $data ); + + // Special handling for first pair; name=value. Also be careful of "=" in value + $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) ); + $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 ); + $this->name = $name; + $this->value = urldecode( $value ); + array_shift( $pairs ); + + // Set everything else as a property + foreach ( $pairs as $pair ) { + list( $key, $val ) = explode( '=', $pair ); + $key = strtolower( trim( $key ) ); + if ( 'expires' == $key ) + $val = strtotime( $val ); + $this->$key = $val; + } + } else { + // Set properties based directly on parameters + $this->name = $data['name']; + $this->value = $data['value']; + $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] ); + $this->path = $data['path']; + $this->domain = $data['domain']; + } + } + + /** + * Confirms that it's OK to send this cookie to the URL checked against. + * + * Decision is based on RFC 2109/2965, so look there for details on validity. + * + * @access public + * + * @param string $url URL you intend to send this cookie to + * @return boolean TRUE if allowed, FALSE otherwise. + */ + function test( $url ) { + // Expires - if expired then nothing else matters + if ( time() > $this->expires ) + return false; + + // Get details on the URL we're thinking about sending to + $url = parse_url( $url ); + $url['port'] = isset( $url['port'] ) ? $url['port'] : 80; + $url['path'] = isset( $url['path'] ) ? $url['path'] : '/'; + + // Values to use for comparison against the URL + $path = isset( $this->path ) ? $this->path : '/'; + $port = isset( $this->port ) ? $this->port : 80; + $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] ); + if ( false === stripos( $domain, '.' ) ) + $domain .= '.local'; + + // Host - very basic check that the request URL ends with the domain restriction (minus leading dot) + $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain; + if ( substr( $url['host'], -strlen( $domain ) ) != $domain ) + return false; + + // Port - supports "port-lists" in the format: "80,8000,8080" + if ( !in_array( $url['port'], explode( ',', $port) ) ) + return false; + + // Path - request path must start with path restriction + if ( substr( $url['path'], 0, strlen( $path ) ) != $path ) + return false; + + return true; + } + + function getHeaderValue() { + if ( empty( $this->name ) || empty( $this->value ) ) + return ''; + + return $this->name . '=' . urlencode( $this->value ); + } + + function getFullHeader() { + return 'Cookie: ' . $this->getHeaderValue(); + } +} + /** * Returns the initialized WP_Http Object * @@ -1296,7 +1472,7 @@ function &_wp_http_get_object() { * The array structure is a little complex. * * - * $res = array( 'headers' => array(), 'response' => array('code', 'message') ); + * $res = array( 'headers' => array(), 'response' => array('code' => int, 'message' => string) ); * * * All of the headers in $res['headers'] are with the name as the key and the diff --git a/wp-includes/rewrite.php b/wp-includes/rewrite.php index 04e94971e..7efb96ef8 100644 --- a/wp-includes/rewrite.php +++ b/wp-includes/rewrite.php @@ -1596,11 +1596,11 @@ class WP_Rewrite { * @return array Rewrite rules. */ function wp_rewrite_rules() { - $this->rules = get_option('rewrite_rules'); + $this->rules = get_transient('rewrite_rules'); if ( empty($this->rules) ) { $this->matches = 'matches'; $this->rewrite_rules(); - update_option('rewrite_rules', $this->rules); + set_transient('rewrite_rules', $this->rules); } return $this->rules; @@ -1783,7 +1783,7 @@ class WP_Rewrite { * @access public */ function flush_rules() { - delete_option('rewrite_rules'); + delete_transient('rewrite_rules'); $this->wp_rewrite_rules(); if ( function_exists('save_mod_rewrite_rules') ) save_mod_rewrite_rules(); diff --git a/wp-includes/rss.php b/wp-includes/rss.php index 996277348..a33c3a7dc 100644 --- a/wp-includes/rss.php +++ b/wp-includes/rss.php @@ -714,14 +714,8 @@ class RSSCache { $cache_option = 'rss_' . $this->file_name( $url ); $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts'; - // shouldn't these be using get_option() ? - if ( !$wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name = %s", $cache_option ) ) ) - add_option($cache_option, '', '', 'no'); - if ( !$wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name = %s", $cache_timestamp ) ) ) - add_option($cache_timestamp, '', '', 'no'); - - update_option($cache_option, $rss); - update_option($cache_timestamp, time() ); + set_transient($cache_option, $rss); + set_transient($cache_timestamp, time() ); return $cache_option; } @@ -736,15 +730,13 @@ class RSSCache { $this->ERROR = ""; $cache_option = 'rss_' . $this->file_name( $url ); - if ( ! get_option( $cache_option ) ) { + if ( ! $rss = get_transient( $cache_option ) ) { $this->debug( "Cache doesn't contain: $url (cache option: $cache_option)" ); return 0; } - $rss = get_option( $cache_option ); - return $rss; } @@ -760,7 +752,7 @@ class RSSCache { $cache_option = $this->file_name( $url ); $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts'; - if ( $mtime = get_option($cache_timestamp) ) { + if ( $mtime = get_transient($cache_timestamp) ) { // find how long ago the file was added to the cache // and whether that is longer then MAX_AGE $age = time() - $mtime;