diff options
author | mattab <matthieu.aubry@gmail.com> | 2013-09-14 04:14:23 +0400 |
---|---|---|
committer | mattab <matthieu.aubry@gmail.com> | 2013-09-14 04:14:23 +0400 |
commit | cf279bc8aa095c849932182bd3eb1c60ed9f67a3 (patch) | |
tree | ab8230b7e4e7b46065c079d3bfdaf3d28c443978 /core | |
parent | df470ed7170e4b323b4623fd55453abc2d7810ad (diff) |
ProxyHttp class until I find a better name
Diffstat (limited to 'core')
-rw-r--r-- | core/AssetManager.php | 2 | ||||
-rw-r--r-- | core/DataTable/Renderer/Csv.php | 12 | ||||
-rw-r--r-- | core/DataTable/Renderer/Json.php | 7 | ||||
-rw-r--r-- | core/FrontController.php | 3 | ||||
-rw-r--r-- | core/Piwik.php | 36 | ||||
-rw-r--r-- | core/ProxyHttp.php | 233 | ||||
-rw-r--r-- | core/ReportRenderer.php | 8 | ||||
-rw-r--r-- | core/Session.php | 5 | ||||
-rw-r--r-- | core/View.php | 2 |
9 files changed, 249 insertions, 59 deletions
diff --git a/core/AssetManager.php b/core/AssetManager.php index e65639b12f..af8e8a4bd4 100644 --- a/core/AssetManager.php +++ b/core/AssetManager.php @@ -500,7 +500,7 @@ class AssetManager } // Tries to remove compressed version of the merged file. - // See Piwik::serveFile() for more info on static file compression + // See Piwik::serverStaticFile() for more info on static file compression $compressedFileLocation = PIWIK_USER_PATH . Piwik::COMPRESSED_FILE_LOCATION . $filename; @unlink($compressedFileLocation . ".deflate"); diff --git a/core/DataTable/Renderer/Csv.php b/core/DataTable/Renderer/Csv.php index 64a62e3c2a..e1f4556025 100644 --- a/core/DataTable/Renderer/Csv.php +++ b/core/DataTable/Renderer/Csv.php @@ -10,14 +10,14 @@ */ namespace Piwik\DataTable\Renderer; -use Piwik\DataTable\Simple; +use Piwik\Common; use Piwik\DataTable\Renderer; +use Piwik\DataTable\Simple; +use Piwik\DataTable; +use Piwik\Date; use Piwik\Period; use Piwik\Period\Range; -use Piwik\Piwik; -use Piwik\Common; -use Piwik\Date; -use Piwik\DataTable; +use Piwik\ProxyHttp; /** * CSV export @@ -353,7 +353,7 @@ class Csv extends Renderer // silent fail otherwise unit tests fail @header('Content-Type: application/vnd.ms-excel'); @header('Content-Disposition: attachment; filename="' . $fileName . '"'); - Piwik::overrideCacheControlHeaders(); + ProxyHttp::overrideCacheControlHeaders(); } /** diff --git a/core/DataTable/Renderer/Json.php b/core/DataTable/Renderer/Json.php index 89dd73ab37..ad22cab3ad 100644 --- a/core/DataTable/Renderer/Json.php +++ b/core/DataTable/Renderer/Json.php @@ -10,11 +10,10 @@ */ namespace Piwik\DataTable\Renderer; -use Piwik\DataTable\Renderer; -use Piwik\Piwik; use Piwik\Common; +use Piwik\DataTable\Renderer; use Piwik\DataTable; -use Piwik\DataTable\Renderer\Php; +use Piwik\ProxyHttp; /** * JSON export. @@ -114,7 +113,7 @@ class Json extends Renderer protected function renderHeader() { self::sendHeaderJSON(); - Piwik::overrideCacheControlHeaders(); + ProxyHttp::overrideCacheControlHeaders(); } public static function sendHeaderJSON() diff --git a/core/FrontController.php b/core/FrontController.php index 79a57b5e93..b26a754808 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -15,7 +15,6 @@ use Piwik\API\Request; use Piwik\API\ResponseBuilder; use Piwik\Log; use Piwik\Session; -use Piwik\Profiler; use Zend_Registry; /** @@ -344,7 +343,7 @@ class FrontController { if (!Common::isPhpCliMode() && Config::getInstance()->General['force_ssl'] == 1 - && !Piwik::isHttps() + && !ProxyHttp::isHttps() // Specifically disable for the opt out iframe && !(Common::getRequestVar('module', '') == 'CoreAdminHome' && Common::getRequestVar('action', '') == 'optOut') diff --git a/core/Piwik.php b/core/Piwik.php index 6d178f45eb..469b1f80e6 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -178,42 +178,6 @@ class Piwik return $url; } - /** - * Returns true if this appears to be a secure HTTPS connection - * - * @return bool - */ - static public function isHttps() - { - return Url::getCurrentScheme() === 'https'; - } - - /** - * Workaround IE bug when downloading certain document types over SSL and - * cache control headers are present, e.g., - * - * Cache-Control: no-cache - * Cache-Control: no-store,max-age=0,must-revalidate - * Pragma: no-cache - * - * @see http://support.microsoft.com/kb/316431/ - * @see RFC2616 - * - * @param string $override One of "public", "private", "no-cache", or "no-store". (optional) - */ - static public function overrideCacheControlHeaders($override = null) - { - if ($override || self::isHttps()) { - @header('Pragma: '); - @header('Expires: '); - if (in_array($override, array('public', 'private', 'no-cache', 'no-store'))) { - @header("Cache-Control: $override, must-revalidate"); - } else { - @header('Cache-Control: must-revalidate'); - } - } - } - /* * File and directory operations */ diff --git a/core/ProxyHttp.php b/core/ProxyHttp.php new file mode 100644 index 0000000000..5bcde43de1 --- /dev/null +++ b/core/ProxyHttp.php @@ -0,0 +1,233 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ +namespace Piwik; + +/** + * Http helper: static file server proxy, with compression, caching, isHttps() helper... + * + * Used to server piwik.js and the merged+minified CSS and JS files + * + * @package Piwik + */ +class ProxyHttp +{ + /** + * Returns true if the current request appears to be a secure HTTPS connection + * + * @return bool + */ + public static function isHttps() + { + return Url::getCurrentScheme() === 'https'; + } + + /** + * Serve static files through php proxy. + * + * It performs the following actions: + * - Checks the file is readable or returns "HTTP/1.0 404 Not Found" + * - Returns "HTTP/1.1 304 Not Modified" after comparing the HTTP_IF_MODIFIED_SINCE + * with the modification date of the static file + * - Will try to compress the static file according to HTTP_ACCEPT_ENCODING. Compressed files are store in + * the /tmp directory. If compressing extensions are not available, a manually gzip compressed file + * can be provided in the /tmp directory. It has to bear the same name with an added .gz extension. + * Using manually compressed static files requires you to manually update the compressed file when + * the static file is updated. + * - Overrides server cache control config to allow caching + * - Sends Very Accept-Encoding to tell proxies to store different version of the static file according + * to users encoding capacities. + * + * Warning: + * Compressed filed are stored in the /tmp directory. + * If this method is used with two files bearing the same name but located in different locations, + * there is a risk of conflict. One file could be served with the content of the other. + * A future upgrade of this method would be to recreate the directory structure of the static file + * within a /tmp/compressed-static-files directory. + * + * @param string $file The location of the static file to serve + * @param string $contentType The content type of the static file. + * @param bool $expireFarFuture If set to true, will set Expires: header in far future. + * Should be set to false for files that don't have a cache buster (eg. piwik.js) + */ + public static function serverStaticFile($file, $contentType, $expireFarFuture = true) + { + if (file_exists($file)) { + // conditional GET + $modifiedSince = ''; + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; + + // strip any trailing data appended to header + if (false !== ($semicolon = strpos($modifiedSince, ';'))) { + $modifiedSince = substr($modifiedSince, 0, $semicolon); + } + } + + $fileModifiedTime = @filemtime($file); + $lastModified = gmdate('D, d M Y H:i:s', $fileModifiedTime) . ' GMT'; + + // set HTTP response headers + self::overrideCacheControlHeaders('public'); + @header('Vary: Accept-Encoding'); + @header('Content-Disposition: inline; filename=' . basename($file)); + + if ($expireFarFuture) { + // Required by proxy caches potentially in between the browser and server to cache the request indeed + @header("Expires: " . gmdate('D, d M Y H:i:s', time() + 86400 * 100) . ' GMT'); + } + + // Returns 304 if not modified since + if ($modifiedSince === $lastModified) { + self::setHttpStatus('304 Not Modified'); + } else { + // optional compression + $compressed = false; + $encoding = ''; + $compressedFileLocation = PIWIK_USER_PATH . Piwik::COMPRESSED_FILE_LOCATION . basename($file); + + $phpOutputCompressionEnabled = ProxyHttp::isPhpOutputCompressed(); + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && !$phpOutputCompressionEnabled) { + $acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING']; + + if (extension_loaded('zlib') && function_exists('file_get_contents') && function_exists('file_put_contents')) { + if (preg_match('/(?:^|, ?)(deflate)(?:,|$)/', $acceptEncoding, $matches)) { + $encoding = 'deflate'; + $filegz = $compressedFileLocation . '.deflate'; + } else if (preg_match('/(?:^|, ?)((x-)?gzip)(?:,|$)/', $acceptEncoding, $matches)) { + $encoding = $matches[1]; + $filegz = $compressedFileLocation . '.gz'; + } + + if (!empty($encoding)) { + // compress-on-demand and use cache + if (!file_exists($filegz) || ($fileModifiedTime > @filemtime($filegz))) { + $data = file_get_contents($file); + + if ($encoding == 'deflate') { + $data = gzdeflate($data, 9); + } else if ($encoding == 'gzip' || $encoding == 'x-gzip') { + $data = gzencode($data, 9); + } + + file_put_contents($filegz, $data); + } + + $compressed = true; + $file = $filegz; + } + } else { + // manually compressed + $filegz = $compressedFileLocation . '.gz'; + if (preg_match('/(?:^|, ?)((x-)?gzip)(?:,|$)/', $acceptEncoding, $matches) && file_exists($filegz) && ($fileModifiedTime < @filemtime($filegz))) { + $encoding = $matches[1]; + $compressed = true; + $file = $filegz; + } + } + } + + @header('Last-Modified: ' . $lastModified); + + if (!$phpOutputCompressionEnabled) { + @header('Content-Length: ' . filesize($file)); + } + + if (!empty($contentType)) { + @header('Content-Type: ' . $contentType); + } + + if ($compressed) { + @header('Content-Encoding: ' . $encoding); + } + + if (!_readfile($file)) { + self::setHttpStatus('505 Internal server error'); + } + } + } else { + self::setHttpStatus('404 Not Found'); + } + } + + /** + * Test if php output is compressed + * + * @return bool True if php output is (or suspected/likely) to be compressed + */ + public static function isPhpOutputCompressed() + { + // Off = ''; On = '1'; otherwise, it's a buffer size + $zlibOutputCompression = ini_get('zlib.output_compression'); + + // could be ob_gzhandler, ob_deflatehandler, etc + $outputHandler = ini_get('output_handler'); + + // output handlers can be stacked + $obHandlers = array_filter(ob_list_handlers(), function ($var) { + return $var !== "default output handler"; + }); + + // user defined handler via wrapper + $autoPrependFile = ini_get('auto_prepend_file'); + $autoAppendFile = ini_get('auto_append_file'); + + return !empty($zlibOutputCompression) || + !empty($outputHandler) || + !empty($obHandlers) || + !empty($autoPrependFile) || + !empty($autoAppendFile); + } + + + /** + * Workaround IE bug when downloading certain document types over SSL and + * cache control headers are present, e.g., + * + * Cache-Control: no-cache + * Cache-Control: no-store,max-age=0,must-revalidate + * Pragma: no-cache + * + * @see http://support.microsoft.com/kb/316431/ + * @see RFC2616 + * + * @param string $override One of "public", "private", "no-cache", or "no-store". (optional) + */ + public static function overrideCacheControlHeaders($override = null) + { + if ($override || self::isHttps()) { + @header('Pragma: '); + @header('Expires: '); + if (in_array($override, array('public', 'private', 'no-cache', 'no-store'))) { + @header("Cache-Control: $override, must-revalidate"); + } else { + @header('Cache-Control: must-revalidate'); + } + } + } + + + /** + * Set response header, e.g., HTTP/1.0 200 Ok + * + * @param string $status Status + * @return bool + */ + protected static function setHttpStatus($status) + { + if (substr_compare(PHP_SAPI, '-fcgi', -5)) { + @header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status); + } else { + // FastCGI + @header('Status: ' . $status); + } + } + +}
\ No newline at end of file diff --git a/core/ReportRenderer.php b/core/ReportRenderer.php index f724935209..3287a6e97e 100644 --- a/core/ReportRenderer.php +++ b/core/ReportRenderer.php @@ -11,12 +11,10 @@ namespace Piwik; use Exception; -use Piwik\DataTable\Simple; +use Piwik\API\Request; use Piwik\DataTable\Row; -use Piwik\Piwik; +use Piwik\DataTable\Simple; use Piwik\DataTable; -use Piwik\Loader; -use Piwik\API\Request; use Piwik\Plugins\ImageGraph\API; /** @@ -175,7 +173,7 @@ abstract class ReportRenderer { $filename = ReportRenderer::appendExtension($filename, $extension); - Piwik::overrideCacheControlHeaders(); + ProxyHttp::overrideCacheControlHeaders(); header('Content-Description: File Transfer'); header('Content-Type: ' . $contentType); header('Content-Disposition: attachment; filename="' . str_replace('"', '\'', basename($filename)) . '";'); diff --git a/core/Session.php b/core/Session.php index 16378607ed..59e02a89a2 100644 --- a/core/Session.php +++ b/core/Session.php @@ -11,9 +11,6 @@ namespace Piwik; use Exception; -use Piwik\Config; -use Piwik\Piwik; -use Piwik\Common; use Piwik\Session\SaveHandler\DbTable; use Zend_Registry; use Zend_Session; @@ -63,7 +60,7 @@ class Session extends Zend_Session @ini_set('session.use_only_cookies', '1'); // advise browser that session cookie should only be sent over secure connection - if (Piwik::isHttps()) { + if (ProxyHttp::isHttps()) { @ini_set('session.cookie_secure', '1'); } diff --git a/core/View.php b/core/View.php index fea23de6ac..910e5b1397 100644 --- a/core/View.php +++ b/core/View.php @@ -134,7 +134,7 @@ class View implements ViewInterface $this->totalNumberOfQueries = 0; } - Piwik::overrideCacheControlHeaders('no-store'); + ProxyHttp::overrideCacheControlHeaders('no-store'); @header('Content-Type: ' . $this->contentType); // always sending this header, sometimes empty, to ensure that Dashboard embed loads (which could call this header() multiple times, the last one will prevail) |