public function test_no_nocache_headers_on_unauthenticated_requests() { $editor = self::factory()->user->create(array('role' => 'editor')); $request = new WP_REST_Request('GET', '/', array()); $result = $this->server->serve_request('/'); $headers = $this->server->sent_headers; foreach (wp_get_nocache_headers() as $header => $value) { $this->assertFalse(isset($headers[$header]) && $headers[$header] === $value, sprintf('Header %s is set to nocache.', $header)); } }
/** * Send additional HTTP headers for caching, content type, etc. * * Sets the X-Pingback header, 404 status (if 404), Content-type. If showing * a feed, it will also send last-modified, etag, and 304 status if needed. * * @since 2.0.0 */ function send_headers() { $headers = array('X-Pingback' => get_bloginfo('pingback_url')); $status = null; $exit_required = false; if (is_user_logged_in()) { $headers = array_merge($headers, wp_get_nocache_headers()); } if (!empty($this->query_vars['error'])) { $status = (int) $this->query_vars['error']; if (404 === $status) { if (!is_user_logged_in()) { $headers = array_merge($headers, wp_get_nocache_headers()); } $headers['Content-Type'] = get_option('html_type') . '; charset=' . get_option('blog_charset'); } elseif (in_array($status, array(403, 500, 502, 503))) { $exit_required = true; } } else { if (empty($this->query_vars['feed'])) { $headers['Content-Type'] = get_option('html_type') . '; charset=' . get_option('blog_charset'); } else { // We're showing a feed, so WP is indeed the only thing that last changed if (!empty($this->query_vars['withcomments']) || false !== strpos($this->query_vars['feed'], 'comments-') || empty($this->query_vars['withoutcomments']) && (!empty($this->query_vars['p']) || !empty($this->query_vars['name']) || !empty($this->query_vars['page_id']) || !empty($this->query_vars['pagename']) || !empty($this->query_vars['attachment']) || !empty($this->query_vars['attachment_id']))) { $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastcommentmodified('GMT'), 0) . ' GMT'; } else { $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0) . ' GMT'; } $wp_etag = '"' . md5($wp_last_modified) . '"'; $headers['Last-Modified'] = $wp_last_modified; $headers['ETag'] = $wp_etag; // Support for Conditional GET if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $client_etag = wp_unslash($_SERVER['HTTP_IF_NONE_MATCH']); } else { $client_etag = false; } $client_last_modified = empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? '' : trim($_SERVER['HTTP_IF_MODIFIED_SINCE']); // If string is empty, return 0. If not, attempt to parse into a timestamp $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0; // Make a timestamp for our most recent modification... $wp_modified_timestamp = strtotime($wp_last_modified); if ($client_last_modified && $client_etag ? $client_modified_timestamp >= $wp_modified_timestamp && $client_etag == $wp_etag : $client_modified_timestamp >= $wp_modified_timestamp || $client_etag == $wp_etag) { $status = 304; $exit_required = true; } } } /** * Filter the HTTP headers before they're sent to the browser. * * @since 2.8.0 * * @param array $headers The list of headers to be sent. * @param WP $this Current WordPress environment instance. */ $headers = apply_filters('wp_headers', $headers, $this); if (!empty($status)) { status_header($status); } // If Last-Modified is set to false, it should not be sent (no-cache situation). if (isset($headers['Last-Modified']) && false === $headers['Last-Modified']) { unset($headers['Last-Modified']); // In PHP 5.3+, make sure we are not sending a Last-Modified header. if (function_exists('header_remove')) { @header_remove('Last-Modified'); } else { // In PHP 5.2, send an empty Last-Modified header, but only as a // last resort to override a header already sent. #WP23021 foreach (headers_list() as $header) { if (0 === stripos($header, 'Last-Modified')) { $headers['Last-Modified'] = ''; break; } } } } foreach ((array) $headers as $name => $field_value) { @header("{$name}: {$field_value}"); } if ($exit_required) { exit; } /** * Fires once the requested HTTP headers for caching, content type, etc. have been sent. * * @since 2.1.0 * * @param WP &$this Current WordPress environment instance (passed by reference). */ do_action_ref_array('send_headers', array(&$this)); }
/** * Set the headers to prevent caching for the different browsers. * * Different browsers support different nocache headers, so several * headers must be sent so that all of them get the point that no * caching should occur. * * @since 2.0.0 * * @see wp_get_nocache_headers() */ function nocache_headers() { $headers = wp_get_nocache_headers(); unset($headers['Last-Modified']); // In PHP 5.3+, make sure we are not sending a Last-Modified header. if (function_exists('header_remove')) { @header_remove('Last-Modified'); } else { // In PHP 5.2, send an empty Last-Modified header, but only as a // last resort to override a header already sent. #WP23021 foreach (headers_list() as $header) { if (0 === stripos($header, 'Last-Modified')) { $headers['Last-Modified'] = ''; break; } } } foreach ($headers as $name => $field_value) { @header("{$name}: {$field_value}"); } }
/** * Send additional HTTP headers for caching, content type, etc. * * Sets the X-Pingback header, 404 status (if 404), Content-type. If showing * a feed, it will also send last-modified, etag, and 304 status if needed. * * @since 2.0.0 */ function send_headers() { $headers = array('X-Pingback' => get_bloginfo('pingback_url')); $status = null; $exit_required = false; if (is_user_logged_in()) { $headers = array_merge($headers, wp_get_nocache_headers()); } if (!empty($this->query_vars['error'])) { $status = (int) $this->query_vars['error']; if (404 === $status) { if (!is_user_logged_in()) { $headers = array_merge($headers, wp_get_nocache_headers()); } $headers['Content-Type'] = get_option('html_type') . '; charset=' . get_option('blog_charset'); } elseif (in_array($status, array(403, 500, 502, 503))) { $exit_required = true; } } else { if (empty($this->query_vars['feed'])) { $headers['Content-Type'] = get_option('html_type') . '; charset=' . get_option('blog_charset'); } else { // We're showing a feed, so WP is indeed the only thing that last changed if (!empty($this->query_vars['withcomments']) || empty($this->query_vars['withoutcomments']) && (!empty($this->query_vars['p']) || !empty($this->query_vars['name']) || !empty($this->query_vars['page_id']) || !empty($this->query_vars['pagename']) || !empty($this->query_vars['attachment']) || !empty($this->query_vars['attachment_id']))) { $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastcommentmodified('GMT'), 0) . ' GMT'; } else { $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0) . ' GMT'; } $wp_etag = '"' . md5($wp_last_modified) . '"'; $headers['Last-Modified'] = $wp_last_modified; $headers['ETag'] = $wp_etag; // Support for Conditional GET if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $client_etag = stripslashes(stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])); } else { $client_etag = false; } $client_last_modified = empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? '' : trim($_SERVER['HTTP_IF_MODIFIED_SINCE']); // If string is empty, return 0. If not, attempt to parse into a timestamp $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0; // Make a timestamp for our most recent modification... $wp_modified_timestamp = strtotime($wp_last_modified); if ($client_last_modified && $client_etag ? $client_modified_timestamp >= $wp_modified_timestamp && $client_etag == $wp_etag : $client_modified_timestamp >= $wp_modified_timestamp || $client_etag == $wp_etag) { $status = 304; $exit_required = true; } } } $headers = apply_filters('wp_headers', $headers, $this); if (!empty($status)) { status_header($status); } foreach ((array) $headers as $name => $field_value) { @header("{$name}: {$field_value}"); } if ($exit_required) { exit; } do_action_ref_array('send_headers', array(&$this)); }
/** * Handles serving an API request. * * Matches the current server URI to a route and runs the first matching * callback then outputs a JSON representation of the returned value. * * @since 4.4.0 * @access public * * @see WP_REST_Server::dispatch() * * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used. * Default null. * @return false|null Null if not served and a HEAD request, false otherwise. */ public function serve_request($path = null) { $content_type = isset($_GET['_jsonp']) ? 'application/javascript' : 'application/json'; $this->send_header('Content-Type', $content_type . '; charset=' . get_option('blog_charset')); $this->send_header('X-Robots-Tag', 'noindex'); $api_root = get_rest_url(); if (!empty($api_root)) { $this->send_header('Link', '<' . esc_url_raw($api_root) . '>; rel="https://api.w.org/"'); } /* * Mitigate possible JSONP Flash attacks. * * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ */ $this->send_header('X-Content-Type-Options', 'nosniff'); $this->send_header('Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages'); $this->send_header('Access-Control-Allow-Headers', 'Authorization'); /** * Send nocache headers on authenticated requests. * * @since 4.4.0 * * @param bool $rest_send_nocache_headers Whether to send no-cache headers. */ $send_no_cache_headers = apply_filters('rest_send_nocache_headers', is_user_logged_in()); if ($send_no_cache_headers) { foreach (wp_get_nocache_headers() as $header => $header_value) { $this->send_header($header, $header_value); } } /** * Filters whether the REST API is enabled. * * @since 4.4.0 * * @param bool $rest_enabled Whether the REST API is enabled. Default true. */ $enabled = apply_filters('rest_enabled', true); /** * Filters whether jsonp is enabled. * * @since 4.4.0 * * @param bool $jsonp_enabled Whether jsonp is enabled. Default true. */ $jsonp_enabled = apply_filters('rest_jsonp_enabled', true); $jsonp_callback = null; if (!$enabled) { echo $this->json_error('rest_disabled', __('The REST API is disabled on this site.'), 404); return false; } if (isset($_GET['_jsonp'])) { if (!$jsonp_enabled) { echo $this->json_error('rest_callback_disabled', __('JSONP support is disabled on this site.'), 400); return false; } $jsonp_callback = $_GET['_jsonp']; if (!wp_check_jsonp_callback($jsonp_callback)) { echo $this->json_error('rest_callback_invalid', __('The JSONP callback function is invalid.'), 400); return false; } } if (empty($path)) { if (isset($_SERVER['PATH_INFO'])) { $path = $_SERVER['PATH_INFO']; } else { $path = '/'; } } $request = new WP_REST_Request($_SERVER['REQUEST_METHOD'], $path); $request->set_query_params(wp_unslash($_GET)); $request->set_body_params(wp_unslash($_POST)); $request->set_file_params($_FILES); $request->set_headers($this->get_headers(wp_unslash($_SERVER))); $request->set_body($this->get_raw_data()); /* * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE * header. */ if (isset($_GET['_method'])) { $request->set_method($_GET['_method']); } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $request->set_method($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); } $result = $this->check_authentication(); if (!is_wp_error($result)) { $result = $this->dispatch($request); } // Normalize to either WP_Error or WP_REST_Response... $result = rest_ensure_response($result); // ...then convert WP_Error across. if (is_wp_error($result)) { $result = $this->error_to_response($result); } /** * Filters the API response. * * Allows modification of the response before returning. * * @since 4.4.0 * @since 4.5.0 Applied to embedded responses. * * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. * @param WP_REST_Server $this Server instance. * @param WP_REST_Request $request Request used to generate the response. */ $result = apply_filters('rest_post_dispatch', rest_ensure_response($result), $this, $request); // Wrap the response in an envelope if asked for. if (isset($_GET['_envelope'])) { $result = $this->envelope_response($result, isset($_GET['_embed'])); } // Send extra data from response objects. $headers = $result->get_headers(); $this->send_headers($headers); $code = $result->get_status(); $this->set_status($code); /** * Filters whether the request has already been served. * * Allow sending the request manually - by returning true, the API result * will not be sent to the client. * * @since 4.4.0 * * @param bool $served Whether the request has already been served. * Default false. * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. * @param WP_REST_Request $request Request used to generate the response. * @param WP_REST_Server $this Server instance. */ $served = apply_filters('rest_pre_serve_request', false, $result, $request, $this); if (!$served) { if ('HEAD' === $request->get_method()) { return null; } // Embed links inside the request. $result = $this->response_to_data($result, isset($_GET['_embed'])); $result = wp_json_encode($result); $json_error_message = $this->get_json_last_error(); if ($json_error_message) { $json_error_obj = new WP_Error('rest_encode_error', $json_error_message, array('status' => 500)); $result = $this->error_to_response($json_error_obj); $result = wp_json_encode($result->data[0]); } if ($jsonp_callback) { // Prepend '/**/' to mitigate possible JSONP Flash attacks // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ echo '/**/' . $jsonp_callback . '(' . $result . ')'; } else { echo $result; } } return null; }
/** * Adds nocache headers to WP page headers * * @author Jonathan Davis * @since 1.1 * * @param array $headers The current WP HTTP headers * @return array Modified headers **/ public function nocache(array $headers) { $headers = array_merge($headers, wp_get_nocache_headers()); return $headers; }
/** * Sets the headers to prevent caching for the different browsers. * * Different browsers support different nocache headers, so several headers must * be sent so that all of them get the point that no caching should occur. * * @since 2.0.0 * @uses wp_get_nocache_headers() */ function nocache_headers() { $headers = wp_get_nocache_headers(); foreach ($headers as $name => $field_value) { @header("{$name}: {$field_value}"); } }
function fix_bbp_404() { global $wp_query; if (class_exists('bbPress')) { if (bbp_is_single_user() || bbp_is_single_user_edit()) { if ($wp_query->bbp_is_single_user || $wp_query->bbp_is_single_user_edit || $wp_query->bbp_is_view) { $wp_query->is_404 = false; // Unset nocache_headers foreach (wp_get_nocache_headers() as $name => $field_value) { @header("{$name}: "); } // Set status 200 status_header(200); } } } }
/** * Sets the headers to prevent caching for the different browsers. * * Different browsers support different nocache headers, so several headers must * be sent so that all of them get the point that no caching should occur. * * @since 2.0.0 * @uses wp_get_nocache_headers() */ function nocache_headers() { $headers = wp_get_nocache_headers(); foreach ($headers as $name => $field_value) { @header("{$name}: {$field_value}"); } if (empty($headers['Last-Modified']) && function_exists('header_remove')) { @header_remove('Last-Modified'); } }
/** * Generate download headers * * @since 1.4 * @param string $filename Full path to file * @return array Headers in key=>value format */ public static function generate_headers($filename) { // Disable compression if (function_exists('apache_setenv')) { @apache_setenv('no-gzip', 1); } @ini_set('zlib.output_compression', 'Off'); // @todo Make this more configurable $headers = wp_get_nocache_headers(); // Content-Disposition $filename_parts = pathinfo($filename); $headers['Content-Disposition'] = 'attachment; filename="' . $filename_parts['basename'] . '"'; // Content-Type $filetype = wp_check_filetype($filename); $headers['Content-Type'] = $filetype['type']; // Content-Length $filesize = filesize($filename); $headers['Content-Length'] = $filesize; return $headers; }
/** * Set headers that prevent caching for search. * * @since 1.2 * @access public * * @param array $headers The list of headers to be sent. * @param WP $wp Current WordPress environment instance. * @return array $headers Modifies list of headers to be sent. */ public function nocache_search($headers, $wp) { if (isset($wp->query_vars['s']) && $wp->query_vars['s']) { $headers = array_merge($headers, wp_get_nocache_headers()); } return $headers; }
/** * Interrupt WP execution and serve response if called for. * * @see HTTPS_Resource_Proxy::send_proxy_response() * * @action template_redirect */ function handle_proxy_request() { $is_request = get_query_var(self::NONCE_QUERY_VAR) && get_query_var(self::HOST_QUERY_VAR) && get_query_var(self::PATH_QUERY_VAR); if (!$is_request) { return; } $params = array('nonce' => get_query_var(self::NONCE_QUERY_VAR), 'host' => get_query_var(self::HOST_QUERY_VAR), 'path' => get_query_var(self::PATH_QUERY_VAR), 'query' => null, 'if_none_match' => null, 'if_modified_since' => null); if (!empty($_SERVER['QUERY_STRING'])) { // input var okay $params['query'] = wp_unslash($_SERVER['QUERY_STRING']); // input var okay; sanitization okay } if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { // input var okay $params['if_modified_since'] = wp_unslash($_SERVER['HTTP_IF_MODIFIED_SINCE']); // input var okay; sanitization okay } if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { // input var okay $params['if_none_match'] = wp_unslash($_SERVER['HTTP_IF_NONE_MATCH']); // input var okay; sanitization okay } try { $r = $this->send_proxy_response($params); $code = wp_remote_retrieve_response_code($r); $message = wp_remote_retrieve_response_message($r); if (empty($message)) { $message = get_status_header_desc($code); } $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : null; // input var okay; sanitization okay if ('HTTP/1.1' !== $protocol && 'HTTP/1.0' !== $protocol) { $protocol = 'HTTP/1.0'; } $status_header = "{$protocol} {$code} {$message}"; header($status_header, true, $code); // Remove headers added by nocache_headers() foreach (array_keys(wp_get_nocache_headers()) as $name) { header_remove($name); } foreach ($r['headers'] as $name => $value) { header("{$name}: {$value}"); } if (304 !== $code) { echo wp_remote_retrieve_body($r); // xss ok (we're passing things through on purpose) } die; } catch (Exception $e) { $code = $e->getCode(); if ($code < 400 || $code >= 600) { $code = $e->getCode(); } status_header($code); die(esc_html($e->getMessage())); } }
/** * Output file * * @param string $path * @param string $mime_type * @param string $file_name */ protected function print_file($path, $mime_type, $file_name = '') { set_time_limit(0); if (!$file_name) { $file_name = basename($path); } foreach (array_merge(wp_get_nocache_headers(), ['Content-Type' => $mime_type, 'Content-Disposition' => sprintf('attachment; filename="%s"', $file_name), 'Content-Length' => filesize($path)]) as $header => $value) { header("{$header}: {$value}"); } readfile($path); exit; }
protected function sendInitialHeaders(array $globals = null) { $globals || ($globals =& $GLOBALS); $content_type = isset($globals['_GET'][self::JSONP_PARAM]) ? 'application/javascript' : 'application/json'; $this->send_header('Content-Type', $content_type . '; charset=' . get_option('blog_charset')); // Mitigate possible JSONP Flash attacks. // http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ $this->send_header('X-Content-Type-Options', 'nosniff'); $this->send_header('Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages'); $this->send_header('Access-Control-Allow-Headers', 'Authorization'); /** * Send nocache headers on authenticated requests. * * @since 4.4.0 * * @param bool $rest_send_nocache_headers Whether to send no-cache headers. */ $no_cache = apply_filters('rest_send_nocache_headers', is_user_logged_in()); if ($no_cache) { foreach (wp_get_nocache_headers() as $header => $header_value) { $this->send_header($header, $header_value); } } }