From 1ab13782594141c8b15f1ef12e3cdf7a35c4f7ff Mon Sep 17 00:00:00 2001 From: Gert Date: Mon, 13 Apr 2015 13:10:56 +0200 Subject: [PATCH 01/21] fix flaws in authentication --- .../src/Grav/Common/User/Authentication.php | 22 ++++++++++++------- system/src/Grav/Common/User/User.php | 17 ++++++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/system/src/Grav/Common/User/Authentication.php b/system/src/Grav/Common/User/Authentication.php index 41b23122b..fc26bf8d7 100644 --- a/system/src/Grav/Common/User/Authentication.php +++ b/system/src/Grav/Common/User/Authentication.php @@ -13,11 +13,22 @@ abstract class Authentication * Create password hash from plaintext password. * * @param string $password Plaintext password. + * @throws \RuntimeException * @return string|bool */ public static function create($password) { - return password_hash($password, PASSWORD_DEFAULT); + if (!$password) { + throw new \RuntimeException('Password hashing failed: no password provided.'); + } + + $hash = password_hash($password, PASSWORD_DEFAULT); + + if (!$hash) { + throw new \RuntimeException('Password hashing failed: internal error.'); + } + + return $hash; } /** @@ -29,13 +40,8 @@ abstract class Authentication */ public static function verify($password, $hash) { - // Always accept plaintext passwords (needs an update). - if ($password && $password == $hash) { - return 2; - } - - // Fail if hash doesn't match. - if (!$password || !password_verify($password, $hash)) { + // Fail if hash doesn't match + if (!$password || !$hash || !password_verify($password, $hash)) { return 0; } diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index b3382b13d..fb9fd21f8 100644 --- a/system/src/Grav/Common/User/User.php +++ b/system/src/Grav/Common/User/User.php @@ -53,11 +53,24 @@ class User extends Data */ public function authenticate($password) { - $result = Authentication::verify($password, $this->password); + $save = false; + + if ($this->password) { + $save = true; + + $this->hashed_password = Authentication::create($this->password); + unset($this->password); + } + + $result = Authentication::verify($password, $this->hashed_password); // Password needs to be updated, save the file. if ($result == 2) { - $this->password = Authentication::create($password); + $save = true; + $this->hashed_password = Authentication::create($password); + } + + if ($save) { $this->save(); } From cd3fd5a7b7ffaa7bee6cd09b32f900f644b41649 Mon Sep 17 00:00:00 2001 From: Gert Date: Mon, 13 Apr 2015 14:44:56 +0200 Subject: [PATCH 02/21] protect against timing attacks --- system/config/system.yaml | 3 +++ system/src/Grav/Common/User/User.php | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/system/config/system.yaml b/system/config/system.yaml index 59f4b36a9..0288d962c 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -71,3 +71,6 @@ debugger: images: default_image_quality: 85 # Default image quality to use when resampling images (85%) debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example + +security: + default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index fb9fd21f8..13dc5d8e8 100644 --- a/system/src/Grav/Common/User/User.php +++ b/system/src/Grav/Common/User/User.php @@ -55,11 +55,23 @@ class User extends Data { $save = false; + // Plain-text is still stored if ($this->password) { - $save = true; - $this->hashed_password = Authentication::create($this->password); - unset($this->password); + if ($password !== $this->password) { + // Plain-text passwords do not match, we know we should fail but execute + // verify to protect us from timing attacks and return false regardless of + // the result + Authentication::verify($password, self::getGrav()['config']->get('system.security.default_hash')); + return false; + } else { + // Plain-text does match, we can update the hash and proceed + $save = true; + + $this->hashed_password = Authentication::create($this->password); + unset($this->password); + } + } $result = Authentication::verify($password, $this->hashed_password); From 29ae5b7aaec8e53d1b22a1ab103f6d8d0865380d Mon Sep 17 00:00:00 2001 From: Gert Date: Thu, 16 Apr 2015 12:48:37 +0200 Subject: [PATCH 03/21] Merge branch develop into feature/password_improvement --- CHANGELOG.md | 15 ++ composer.json | 2 +- system/config/system.yaml | 7 + system/defines.php | 2 +- system/src/Grav/Common/Assets.php | 16 +- system/src/Grav/Common/Grav.php | 34 ++-- .../Grav/Common/Page/Medium/ImageMedium.php | 6 +- system/src/Grav/Common/Page/Medium/Medium.php | 43 ++++- .../Grav/Common/Page/Medium/MediumFactory.php | 3 +- system/src/Grav/Common/Uri.php | 19 +- system/src/Grav/Common/Utils.php | 165 ++++++++++++++++++ 11 files changed, 283 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49789ba71..54d02ea79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# v0.9.24 +## 04/15/2015 + +1. [](#new) + * Added support for chunked downloads of Assets + * Added new `onBeforeDownload()` event + * Added new `download()` and `getMimeType()` methods to Utils class + * Added configuration option for supported page types + * Added assets and media timestamp options (off by default) + * Added page expires configuration option +2. [](#bugfix) + * Fixed issue with Nginx/Gzip and `ob_flush()` throwing error + * Fixed assets actions on 'direct media' URLs + * Fix for 'direct assets` with any parameters + # v0.9.23 ## 04/09/2015 diff --git a/composer.json b/composer.json index 53bcf0113..6d4f3362b 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "maximebf/debugbar": "dev-master", "filp/whoops": "1.2.*@dev", "monolog/monolog": "~1.0", - "gregwar/image": "~2.0", + "gregwar/image": "~2.0", "ircmaxell/password-compat": "1.0.*", "mrclay/minify": "dev-master", "donatj/phpuseragentparser": "dev-master", diff --git a/system/config/system.yaml b/system/config/system.yaml index 0288d962c..b3d5d3464 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -30,6 +30,8 @@ pages: special_chars: # List of special characters to automatically convert to entities '>': 'gt' '<': 'lt' + types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types + expires: 604800 # Page expires time in seconds (default 7 days) cache: enabled: true # Set to true to enable caching @@ -40,6 +42,7 @@ cache: lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite) gzip: false # GZip compress the page output + twig: cache: true # Set to true to enable twig caching debug: false # Enable Twig debug @@ -55,6 +58,7 @@ assets: # Configuration for Assets Manager (JS, C css_rewrite: true # Rewrite any CSS relative URLs during pipelining js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file js_minify: true # Minify the JS during pipelining + enable_asset_timestamp: false # Enable asset timetsamps collections: jquery: system://assets/jquery/jquery-2.1.3.min.js @@ -72,5 +76,8 @@ images: default_image_quality: 85 # Default image quality to use when resampling images (85%) debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example +media: + enable_media_timestamp: false # Enable media timetsamps + security: default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK diff --git a/system/defines.php b/system/defines.php index a9f6bc5e4..04f8ac6df 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '0.9.23'); +define('GRAV_VERSION', '0.9.24'); define('DS', '/'); // Directories and Paths diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index a63a08d3d..3b232f457 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -71,6 +71,7 @@ class Assets // Some configuration variables protected $config; protected $base_url; + protected $timestamp = ''; // Default values for pipeline settings protected $css_minify = true; @@ -82,7 +83,6 @@ class Assets protected $css_no_pipeline = array(); protected $js_no_pipeline = array(); - public function __construct(array $options = array()) { // Forward config options @@ -154,6 +154,12 @@ class Assets } } + // Set timestamp + if (isset($config['enable_asset_timestamp']) && $config['enable_asset_timestamp'] === true) { + $this->timestamp = '?' . self::getGrav()['cache']->getKey(); + } + + return $this; } @@ -422,11 +428,11 @@ class Assets $output .= '' . "\n"; foreach ($this->css_no_pipeline as $file) { - $output .= '' . "\n"; + $output .= '' . "\n"; } } else { foreach ($this->css as $file) { - $output .= '' . "\n"; + $output .= '' . "\n"; } } @@ -480,11 +486,11 @@ class Assets if ($this->js_pipeline) { $output .= '' . "\n"; foreach ($this->js_no_pipeline as $file) { - $output .= '' . "\n"; + $output .= '' . "\n"; } } else { foreach ($this->js as $file) { - $output .= '' . "\n"; + $output .= '' . "\n"; } } diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index e0a91a06a..df584fe4b 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -2,6 +2,7 @@ namespace Grav\Common; use Grav\Common\Filesystem\Folder; +use Grav\Common\Page\Medium\ImageMedium; use Grav\Common\Page\Pages; use Grav\Common\Service\ConfigServiceProvider; use Grav\Common\Service\ErrorServiceProvider; @@ -10,7 +11,6 @@ use Grav\Common\Service\StreamsServiceProvider; use RocketTheme\Toolbox\DI\Container; use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\Event\EventDispatcher; -use Grav\Common\Page\Medium\Medium; /** * Grav @@ -99,32 +99,34 @@ class Grav extends Container /** @var Pages $pages */ $pages = $c['pages']; - // If base URI is set, we want to remove it from the URL. - $path = '/' . ltrim(Folder::getRelativePath($c['uri']->route(), $pages->base()), '/'); + /** @var Uri $uri */ + $uri = $c['uri']; + + $path = $uri->path(); $page = $pages->dispatch($path); if (!$page || !$page->routable()) { - - // special case where a media file is requested $path_parts = pathinfo($path); - $page = $c['pages']->dispatch($path_parts['dirname'], true); if ($page) { $media = $page->media()->all(); - $media_file = urldecode($path_parts['basename']); + + $parsed_url = parse_url(urldecode($uri->basename())); + + $media_file = $parsed_url['path']; + + // if this is a media object, try actions first if (isset($media[$media_file])) { $medium = $media[$media_file]; - - // loop through actions for the image and call them - foreach ($c['uri']->query(null, true) as $action => $params) { - if (in_array($action, Medium::$valid_actions)) { + foreach ($uri->query(null, true) as $action => $params) { + if (in_array($action, ImageMedium::$magic_actions)) { call_user_func_array(array(&$medium, $action), explode(',', $params)); } } - header('Content-type: '. $medium->get('mime')); - echo file_get_contents($medium->path()); - die; + Utils::download($medium->path(), false); + } else { + Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), true); } } @@ -296,6 +298,8 @@ class Grav extends Container $extension = $this['uri']->extension(); header('Content-type: ' . $this->mime($extension)); + header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $this['config']->get('system.pages.expires'))); + // Set debugger data in headers if (!($extension === null || $extension == 'html')) { $this['debugger']->enabled(false); @@ -345,7 +349,7 @@ class Grav extends Container header("Connection: close\r\n"); ob_end_flush(); // regular buffer - ob_flush(); + @ob_flush(); flush(); if (function_exists('fastcgi_finish_request')) { diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index ede065f09..17a9663de 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -42,7 +42,7 @@ class ImageMedium extends Medium public static $magic_actions = [ 'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop', 'negate', 'brightness', 'contrast', 'grayscale', 'emboss', - 'smooth', 'sharp', 'edge', 'colorize', 'sepia' + 'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive' ]; /** @@ -127,7 +127,7 @@ class ImageMedium extends Medium $this->reset(); } - return self::$grav['base_url'] . $output . $this->urlHash(); + return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash(); } @@ -299,7 +299,7 @@ class ImageMedium extends Medium } if (!in_array($method, self::$magic_actions)) { - return $this; + return parent::__call($method, $args); } // Always initialize image. diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php index e001ec3c6..5c210e563 100644 --- a/system/src/Grav/Common/Page/Medium/Medium.php +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -60,6 +60,10 @@ class Medium extends Data implements RenderableInterface { parent::__construct($items, $blueprint); + if (self::getGrav()['config']->get('media.enable_media_timestamp', true)) { + $this->querystring('&' . self::getGrav()['cache']->getKey()); + } + $this->def('mime', 'application/octet-stream'); $this->reset(); } @@ -129,7 +133,33 @@ class Medium extends Data implements RenderableInterface $this->reset(); } - return self::$grav['base_url'] . $output . $this->urlHash(); + return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash(); + } + + /** + * Get/set querystring for the file's url + * + * @param string $hash + * @param boolean $withHash + * @return string + */ + public function querystring($querystring = null, $withQuestionmark = true) + { + if ($querystring) { + $this->set('querystring', ltrim($querystring, '?&')); + + foreach ($this->alternatives as $alt) { + $alt->querystring($querystring, $withQuestionmark); + } + } + + $querystring = $this->get('querystring', ''); + + if ($withQuestionmark && !empty($querystring)) { + return '?' . $querystring; + } else { + return $querystring; + } } /** @@ -337,6 +367,17 @@ class Medium extends Data implements RenderableInterface */ public function __call($method, $args) { + $qs = $method; + if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) { + $qs .= '=' . implode(',', array_map(function ($a) { return urlencode($a); }, $args)); + } + + if (!empty($qs)) { + $this->querystring($this->querystring(null, false) . '&' . $qs); + } + + self::$grav['debugger']->addMessage($this->querystring()); + return $this; } diff --git a/system/src/Grav/Common/Page/Medium/MediumFactory.php b/system/src/Grav/Common/Page/Medium/MediumFactory.php index 9ff05d4b9..ffa6b8f50 100644 --- a/system/src/Grav/Common/Page/Medium/MediumFactory.php +++ b/system/src/Grav/Common/Page/Medium/MediumFactory.php @@ -131,8 +131,7 @@ class MediumFactory $debug = $medium->get('debug'); $medium->set('debug', false); - $file = $medium->resize($width, $height)->setPrettyName($basename)->url(); - $file = preg_replace('|'. preg_quote(self::getGrav()['base_url_relative']) .'$|', '', GRAV_ROOT) . $file; + $file = $medium->resize($width, $height)->path(); $medium->set('debug', $debug); diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index d1dad9fda..e2b7b6670 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -11,6 +11,7 @@ class Uri { public $url; + protected $basename; protected $base; protected $root; protected $bits; @@ -64,6 +65,7 @@ class Uri $this->base = $base; $this->root = $base . $root_path; $this->url = $base . $uri; + } /** @@ -84,7 +86,11 @@ class Uri // remove the extension if there is one set $parts = pathinfo($uri); - if (preg_match("/\.(txt|xml|html|json|rss|atom)$/", $parts['basename'])) { + + // set the original basename + $this->basename = $parts['basename']; + + if (preg_match("/\.(".$config->get('system.pages.types').")$/", $parts['basename'])) { $uri = rtrim($parts['dirname'], '/').'/'.$parts['filename']; $this->extension = $parts['extension']; } @@ -282,6 +288,17 @@ class Uri return $this->host(); } + + /** + * Return the basename of the URI + * + * @return String The basename of the URI + */ + public function basename() + { + return $this->basename; + } + /** * Return the base of the URI * diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index c82aabf33..4d7163e35 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -1,6 +1,8 @@ fireEvent('onBeforeDownload', new Event(['file' => $file])); + + $file_parts = pathinfo($file); + $filesize = filesize($file); + $range = false; + + set_time_limit(0); + ignore_user_abort(false); + ini_set('output_buffering', 0); + ini_set('zlib.output_compression', 0); + + if ($force_download) { + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename='.$file_parts['basename']); + header('Content-Transfer-Encoding: binary'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + } else { + header("Content-Type: " . Utils::getMimeType($file_parts['extension'])); + } + header('Content-Length: ' . $filesize); + + // 8kb chunks for now + $chunk = 8 * 1024; + + $fh = fopen($file, "rb"); + + if ($fh === false) { + return; + } + + // Repeat reading until EOF + while (!feof($fh)) { + echo fread($fh, $chunk); + + ob_flush(); // flush output + flush(); + } + + exit; + } + } + + /** + * Return the mimetype based on filename + * + * @param $extension Extension of file (eg .txt) + * + * @return string + */ + public static function getMimeType($extension) + { + $extension = strtolower($extension); + + switch($extension) + { + case "js": + return "application/x-javascript"; + + case "json": + return "application/json"; + + case "jpg": + case "jpeg": + case "jpe": + return "image/jpg"; + + case "png": + case "gif": + case "bmp": + case "tiff": + return "image/" . $extension; + + case "css": + return "text/css"; + + case "xml": + return "application/xml"; + + case "doc": + case "docx": + return "application/msword"; + + case "xls": + case "xlt": + case "xlm": + case "xld": + case "xla": + case "xlc": + case "xlw": + case "xll": + return "application/vnd.ms-excel"; + + case "ppt": + case "pps": + return "application/vnd.ms-powerpoint"; + + case "rtf": + return "application/rtf"; + + case "pdf": + return "application/pdf"; + + case "html": + case "htm": + case "php": + return "text/html"; + + case "txt": + return "text/plain"; + + case "mpeg": + case "mpg": + case "mpe": + return "video/mpeg"; + + case "mp3": + return "audio/mpeg3"; + + case "wav": + return "audio/wav"; + + case "aiff": + case "aif": + return "audio/aiff"; + + case "avi": + return "video/msvideo"; + + case "wmv": + return "video/x-ms-wmv"; + + case "mov": + return "video/quicktime"; + + case "zip": + return "application/zip"; + + case "tar": + return "application/x-tar"; + + case "swf": + return "application/x-shockwave-flash"; + + default: + return "application/octet-stream"; + } + } } From 20b2856dee58a96634bf571f8e2557cd417ecbec Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Fri, 17 Apr 2015 10:59:03 -0700 Subject: [PATCH 04/21] Update README.md grammar changes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 965c57fdd..fcbe06d1f 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principals to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself. +Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself. -The underlying architecture of Grav has been designed to use well-established and _best-in-class_ technologies, where applicable, to ensure that Grav is simple to use and easy to extend. Some of these key technologies include: +The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies, to ensure that Grav is simple to use and easy to extend. Some of these key technologies include: * [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface * [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation From 548081471c0ab21f234928ca8a0c5295f45b5a2f Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 20 Apr 2015 12:41:26 -0600 Subject: [PATCH 05/21] Should fix: spaces in webroot for `bin/grav install` #164 --- system/src/Grav/Console/Cli/InstallCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/src/Grav/Console/Cli/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php index 56963c240..f0510a51f 100644 --- a/system/src/Grav/Console/Cli/InstallCommand.php +++ b/system/src/Grav/Console/Cli/InstallCommand.php @@ -125,7 +125,7 @@ class InstallCommand extends Command foreach ($this->config['git'] as $repo => $data) { $path = $this->destination . DS . $data['path']; if (!file_exists($path)) { - exec('cd ' . $this->destination . ' && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']); + exec('cd "' . $this->destination . '" && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']); $output->writeln('SUCCESS cloned ' . $data['url'] . ' -> ' . $path . ''); $output->writeln(''); } else { From cff4e225e650213d11244f5176934e2da2cce043 Mon Sep 17 00:00:00 2001 From: Gert Date: Mon, 20 Apr 2015 20:53:26 +0200 Subject: [PATCH 06/21] fix bug in collection filtering --- system/src/Grav/Common/Page/Collection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php index 59302b781..9cd237c04 100644 --- a/system/src/Grav/Common/Page/Collection.php +++ b/system/src/Grav/Common/Page/Collection.php @@ -361,12 +361,13 @@ class Collection extends Iterator { $routable = []; - foreach (array_keys($this->items) as $path => $slug) { + foreach ($this->items as $path => $slug) { $page = $this->pages->get($path); if ($page->routable()) { $routable[$path] = $slug; } } + $this->items = $routable; return $this; } From 20e771f1213b188d87e5fa74e4b3af87debcf59f Mon Sep 17 00:00:00 2001 From: Gert Date: Mon, 20 Apr 2015 20:53:39 +0200 Subject: [PATCH 07/21] add metod to get all pages --- system/src/Grav/Common/Page/Pages.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index fd70981d6..4f52f8e6c 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -331,6 +331,26 @@ class Pages return $blueprint; } + /** + * Get all pages + * @return Collection + */ + public function all(Page $current = null) + { + $all = new Collection(); + $current = $current ?: $this->root(); + + if ($current->routable()) { + $all[$current->path()] = [ 'slug' => $current->slug() ]; + } + + foreach ($current->children() as $next) { + $all->append($this->all($next)); + } + + return $all; + } + /** * Get list of route/title of all pages. * From 1a238ea1b1063e5e79dbfd649c068801a58b260a Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 20 Apr 2015 13:33:54 -0600 Subject: [PATCH 08/21] fix for spaces in relative dir --- system/src/Grav/Common/Uri.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index e2b7b6670..60546dc39 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -34,8 +34,7 @@ class Uri $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80; $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - $root_path = rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/'); - + $root_path = str_replace(' ', '%20', rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/')); if (isset($_SERVER['HTTPS'])) { $base = (@$_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'; From 974f9d52a40464f5b6533833a32bf68c73a652da Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 20 Apr 2015 19:05:53 -0600 Subject: [PATCH 09/21] Refactored link handling to better absolute handles pages and url elements. Fixes issue #173 --- .../Common/Markdown/ParsedownGravTrait.php | 58 ++++++++++--------- system/src/Grav/Common/Utils.php | 26 ++++++++- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index dd4380b5c..3c478b103 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -6,6 +6,7 @@ use Grav\Common\Debugger; use Grav\Common\GravTrait; use Grav\Common\Page\Medium\Medium; use Grav\Common\Uri; +use Grav\Common\Utils; /** * A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra @@ -116,7 +117,6 @@ trait ParsedownGravTrait // if this is an image if (isset($excerpt['element']['attributes']['src'])) { - $alt = $excerpt['element']['attributes']['alt'] ?: ''; $title = $excerpt['element']['attributes']['title'] ?: ''; $class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : ''; @@ -128,7 +128,6 @@ trait ParsedownGravTrait // if there is no host set but there is a path, the file is local if (!isset($url['host']) && isset($url['path'])) { - // get the local path to page media if possible if ($path_parts['dirname'] == $this->page->url()) { $url['path'] = ltrim(str_replace($this->page->url(), '', $url['path']), '/'); @@ -136,7 +135,6 @@ trait ParsedownGravTrait $media = $this->page->media(); } else { - // see if this is an external page to this one $page_route = str_replace($this->base_url, '', $path_parts['dirname']); @@ -219,37 +217,43 @@ trait ParsedownGravTrait protected function convertUrl($markdown_url) { // if absolute and starts with a base_url move on - if ($this->base_url != '' && strpos($markdown_url, $this->base_url) === 0) { + if (Utils::startsWith($markdown_url, $this->base_url)) { + return $markdown_url; + // if contains only a fragment + } elseif (Utils::startsWith($markdown_url, '#')) { return $markdown_url; - // if its absolute and starts with / - } elseif (strpos($markdown_url, '/') === 0) { - return $this->base_url . $markdown_url; } else { - $relative_path = $this->base_url . $this->page->route(); - $real_path = $this->page->path() . '/' . parse_url($markdown_url, PHP_URL_PATH); - - // strip numeric order from markdown path - if (($real_path)) { - $markdown_url = preg_replace('/^([\d]+\.)/', '', preg_replace('/\/([\d]+\.)/', '/', trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/'))); + $target = null; + // see if page is relative to this or absolute + if (Utils::startsWith($markdown_url, '/')) { + $normalized_path = Utils::normalizePath($this->pages_dir . $markdown_url); + $normalized_url = Utils::normalizePath($this->base_url . $markdown_url); + } else { + $normalized_path = Utils::normalizePath($this->page->path() . '/' . $markdown_url); + $normalized_url = $this->base_url . Utils::normalizePath($this->page->route() . '/' . $markdown_url); } - // else its a relative path already - $newpath = array(); - $paths = explode('/', $markdown_url); + $url_bits = parse_url($normalized_path); + $full_path = $url_bits['path']; - // remove the updirectory references (..) - foreach ($paths as $path) { - if ($path == '..') { - $relative_path = dirname($relative_path); - } else { - $newpath[] = $path; + if (file_exists($full_path)) { + $page_path = pathinfo($full_path, PATHINFO_DIRNAME); + + // get page instances and try to find one that fits + $instances = $this->pages->instances(); + if (isset($instances[$full_path])) { + $target = $instances[$full_path]; + } elseif (isset($instances[$page_path])) { + $target = $instances[$page_path]; + } + + // if a page target is found... + if ($target) { + $url_bits['path'] = $this->base_url . $target->route(); + return Uri::buildUrl($url_bits); } } - - // build the new url - $new_url = rtrim($relative_path, '/') . '/' . implode('/', $newpath); + return $normalized_url; } - - return $new_url; } } diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 4d7163e35..eee0ee4f9 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -295,7 +295,6 @@ abstract class Utils * Return the mimetype based on filename * * @param $extension Extension of file (eg .txt) - * * @return string */ public static function getMimeType($extension) @@ -396,4 +395,29 @@ abstract class Utils return "application/octet-stream"; } } + + /** + * Normalize path by processing relative `.` and `..` syntax and merging path + * + * @param $path + * @return string + */ + public static function normalizePath($path) + { + $root = ($path[0] === '/') ? '/' : ''; + + $segments = explode('/', trim($path, '/')); + $ret = array(); + foreach ($segments as $segment) { + if (($segment == '.') || empty($segment)) { + continue; + } + if ($segment == '..') { + array_pop($ret); + } else { + array_push($ret, $segment); + } + } + return $root . implode('/', $ret); + } } From 030d23031265ef338b55d245af4a886f8ad6fd58 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 20 Apr 2015 21:38:26 -0600 Subject: [PATCH 10/21] Support page defaults merged with system config - #174 --- system/src/Grav/Common/Markdown/Parsedown.php | 4 ++-- system/src/Grav/Common/Markdown/ParsedownExtra.php | 4 ++-- system/src/Grav/Common/Markdown/ParsedownGravTrait.php | 7 +++++-- system/src/Grav/Common/Page/Page.php | 4 ++-- system/src/Grav/Common/TwigExtension.php | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/system/src/Grav/Common/Markdown/Parsedown.php b/system/src/Grav/Common/Markdown/Parsedown.php index 1db2d1707..f453a544a 100644 --- a/system/src/Grav/Common/Markdown/Parsedown.php +++ b/system/src/Grav/Common/Markdown/Parsedown.php @@ -5,9 +5,9 @@ class Parsedown extends \Parsedown { use ParsedownGravTrait; - public function __construct($page) + public function __construct($page, $defaults) { - $this->init($page); + $this->init($page, $defaults); } } diff --git a/system/src/Grav/Common/Markdown/ParsedownExtra.php b/system/src/Grav/Common/Markdown/ParsedownExtra.php index da20ca1e0..526e5f905 100644 --- a/system/src/Grav/Common/Markdown/ParsedownExtra.php +++ b/system/src/Grav/Common/Markdown/ParsedownExtra.php @@ -5,9 +5,9 @@ class ParsedownExtra extends \ParsedownExtra { use ParsedownGravTrait; - public function __construct($page) + public function __construct($page, $defaults) { parent::__construct(); - $this->init($page); + $this->init($page, $defaults); } } diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 3c478b103..927cf8a9d 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -26,8 +26,9 @@ trait ParsedownGravTrait * Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait * * @param $page + * @param $defaults */ - protected function init($page) + protected function init($page, $defaults) { $this->page = $page; $this->pages = self::getGrav()['pages']; @@ -36,7 +37,9 @@ trait ParsedownGravTrait $this->pages_dir = self::getGrav()['locator']->findResource('page://'); $this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); - $defaults = self::getGrav()['config']->get('system.pages.markdown'); + if ($defaults == null) { + $defaults = self::getGrav()['config']->get('system.pages.markdown'); + } $this->setBreaksEnabled($defaults['auto_line_breaks']); $this->setUrlsLinked($defaults['auto_url_links']); diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 95b558f18..799ce6f83 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -442,9 +442,9 @@ class Page // Initialize the preferred variant of Parsedown if ($defaults['extra']) { - $parsedown = new ParsedownExtra($this); + $parsedown = new ParsedownExtra($this, $defaults); } else { - $parsedown = new Parsedown($this); + $parsedown = new Parsedown($this, $defaults); } $this->content = $parsedown->text($this->content); diff --git a/system/src/Grav/Common/TwigExtension.php b/system/src/Grav/Common/TwigExtension.php index 64ab73f66..cfec33ba7 100644 --- a/system/src/Grav/Common/TwigExtension.php +++ b/system/src/Grav/Common/TwigExtension.php @@ -337,9 +337,9 @@ class TwigExtension extends \Twig_Extension // Initialize the preferred variant of Parsedown if ($defaults['extra']) { - $parsedown = new ParsedownExtra($page); + $parsedown = new ParsedownExtra($page, $defaults); } else { - $parsedown = new Parsedown($page); + $parsedown = new Parsedown($page, $defaults); } $string = $parsedown->text($string); From 1ffd1cb6e7e908fda82092754aef171b7543178a Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 20 Apr 2015 21:40:06 -0600 Subject: [PATCH 11/21] Fixes for absolute images --- .../Common/Markdown/ParsedownGravTrait.php | 25 ++++++++++++------- .../Common/Page/Medium/ParsedownHtmlTrait.php | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 927cf8a9d..2b17974df 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -239,20 +239,27 @@ trait ParsedownGravTrait $url_bits = parse_url($normalized_path); $full_path = $url_bits['path']; + // if this file exits, get the page and work with that if (file_exists($full_path)) { - $page_path = pathinfo($full_path, PATHINFO_DIRNAME); + $path_info = pathinfo($full_path); + $page_path = $path_info['dirname']; + $filename = ''; + + // save the filename if a file is part of the path + $filename_regex = "/([\w\d-_]+\.([a-zA-Z]{2,4}))$/"; + if (preg_match($filename_regex, $full_path, $matches)) { + if ($matches[2] != 'md') { + $filename = '/' . $matches[1]; + } + } else { + $page_path = $full_path; + } // get page instances and try to find one that fits $instances = $this->pages->instances(); - if (isset($instances[$full_path])) { - $target = $instances[$full_path]; - } elseif (isset($instances[$page_path])) { + if (isset($instances[$page_path])) { $target = $instances[$page_path]; - } - - // if a page target is found... - if ($target) { - $url_bits['path'] = $this->base_url . $target->route(); + $url_bits['path'] = $this->base_url . $target->route() . $filename; return Uri::buildUrl($url_bits); } } diff --git a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php index 2567705d7..7052dea82 100644 --- a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php +++ b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php @@ -23,7 +23,7 @@ trait ParsedownHtmlTrait $element = $this->parsedownElement($title, $alt, $class, $reset); if (!$this->parsedown) { - $this->parsedown = new Parsedown(null); + $this->parsedown = new Parsedown(null, null); } return $this->parsedown->elementToHtml($element); From 0860f53d68c78fd42cd14a36c0a2558b1c2c7485 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 12:29:45 -0600 Subject: [PATCH 12/21] doc tag fix --- system/src/Grav/Common/Page/Pages.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index 4f52f8e6c..ac0e9e70f 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -333,7 +333,9 @@ class Pages /** * Get all pages - * @return Collection + * + * @param \Grav\Common\Page\Page $current + * @return \Grav\Common\Page\Collection */ public function all(Page $current = null) { From 5f1b190ba9e2d3781c203c13740b361abe38ffcd Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 13:25:42 -0600 Subject: [PATCH 13/21] rework to only check .md files --- system/src/Grav/Common/Filesystem/Folder.php | 9 +++------ .../Filesystem/RecursiveFileFilterIterator.php | 13 ++++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index 3d30d12eb..c839d02aa 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -44,15 +44,12 @@ abstract class Folder { $last_modified = 0; - $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); - $filterItr = new RecursiveFileFilterIterator($dirItr); - $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST); + $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + $itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); + $itr = new RecursiveFileFilterIterator($itrItr); /** @var \RecursiveDirectoryIterator $file */ foreach ($itr as $file) { - if ($file->isDir()) { - continue; - } $file_modified = $file->getMTime(); if ($file_modified > $last_modified) { $last_modified = $file_modified; diff --git a/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php index be9667c3b..dcf1c640d 100644 --- a/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php +++ b/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php @@ -1,13 +1,16 @@ current()->getFilename(), self::$FILTERS, true); + // Ensure only valid file names are skipped + $current = $this->current()->getFilename(); + $accept = Utils::endsWith($current, '.md'); + + return $accept; } } From 1c1bf86e9a093530332f7133c0816727ccd4a4f4 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 13:55:53 -0600 Subject: [PATCH 14/21] more optimizations.. using regexiterator now --- system/src/Grav/Common/Filesystem/Folder.php | 8 +++++--- .../Filesystem/RecursiveFileFilterIterator.php | 16 ---------------- 2 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index c839d02aa..69725018b 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -42,14 +42,16 @@ abstract class Folder */ public static function lastModifiedFile($path) { + // pipe separated list of extensions to search for + $extensions = 'md|yaml'; $last_modified = 0; $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); - $itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); - $itr = new RecursiveFileFilterIterator($itrItr); + $itrDir = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); + $itr = new \RegexIterator($itrDir, '/^.+\.'.$extensions.'$/i'); /** @var \RecursiveDirectoryIterator $file */ - foreach ($itr as $file) { + foreach ($itr as $filepath => $file) { $file_modified = $file->getMTime(); if ($file_modified > $last_modified) { $last_modified = $file_modified; diff --git a/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php deleted file mode 100644 index dcf1c640d..000000000 --- a/system/src/Grav/Common/Filesystem/RecursiveFileFilterIterator.php +++ /dev/null @@ -1,16 +0,0 @@ -current()->getFilename(); - $accept = Utils::endsWith($current, '.md'); - - return $accept; - } -} From c368fbcda9dd8f79224df6810afe2b0d09b7df9c Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 14:08:00 -0600 Subject: [PATCH 15/21] tweaks --- system/src/Grav/Common/Filesystem/Folder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index 69725018b..9355038b8 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -42,13 +42,13 @@ abstract class Folder */ public static function lastModifiedFile($path) { - // pipe separated list of extensions to search for + // pipe separated list of extensions to search for changes with $extensions = 'md|yaml'; $last_modified = 0; $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); - $itrDir = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); - $itr = new \RegexIterator($itrDir, '/^.+\.'.$extensions.'$/i'); + $itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); + $itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i'); /** @var \RecursiveDirectoryIterator $file */ foreach ($itr as $filepath => $file) { From b8023b244420480d776bdd82d7f36434d634429a Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 15:39:55 -0600 Subject: [PATCH 16/21] Reset default expires time to 0 seconds --- system/config/system.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/config/system.yaml b/system/config/system.yaml index b3d5d3464..38550d690 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -31,7 +31,7 @@ pages: '>': 'gt' '<': 'lt' types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types - expires: 604800 # Page expires time in seconds (default 7 days) + expires: 0 # Page expires time in seconds (604800 seconds = 7 days) cache: enabled: true # Set to true to enable caching From f13593aac7e2b2d107b44117a22371b2918948f4 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 15:57:17 -0600 Subject: [PATCH 17/21] Added E-Tag, Last-Modified, Cache-Control and Page-based Expires --- system/src/Grav/Common/Grav.php | 20 +++++++++++++++++++- system/src/Grav/Common/Page/Page.php | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index df584fe4b..42085cd29 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -296,9 +296,27 @@ class Grav extends Container public function header() { $extension = $this['uri']->extension(); + + /** @var Page $page */ + $page = $this['page']; + header('Content-type: ' . $this->mime($extension)); - header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $this['config']->get('system.pages.expires'))); + // Calculate Expires Headers if set to > 0 + $expires = $page->expires(); + + if ($expires > 0) { + $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'; + header('Cache-Control: max-age=' . $expires_date); + header('Expires: '. $expires_date); + } + + // Set the last modified time + $last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT'; + header('Last-Modified: ' . $last_modified_date); + + // Calculate a Hash based on the raw file + header('ETag: ' . md5($page->raw().$page->modified())); // Set debugger data in headers if (!($extension === null || $extension == 'html')) { diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 799ce6f83..e0c7ca963 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -47,6 +47,7 @@ class Page protected $parent; protected $template; + protected $expires; protected $visible; protected $published; protected $publish_date; @@ -273,6 +274,10 @@ class Page if (isset($this->header->unpublish_date)) { $this->unpublish_date = strtotime($this->header->unpublish_date); } + if (isset($this->header->expires)) { + $this->expires = intval($this->header->expires); + } + } return $this->header; @@ -781,6 +786,20 @@ class Page return $this->template; } + /** + * Gets and sets the expires field. If not set will return the default + * + * @param string $var The name of this page. + * @return string The name of this page. + */ + public function expires($var = null) + { + if ($var !== null) { + $this->expires = $var; + } + return empty($this->expires) ? self::getGrav()['config']->get('system.pages.expires') : $this->expires; + } + /** * Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name * From 1517d0e40b8e5b1b5fb41db515284f0fddb1e2fa Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 21 Apr 2015 19:21:49 -0600 Subject: [PATCH 18/21] fix for relative images at root --- system/src/Grav/Common/Markdown/ParsedownGravTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 2b17974df..1ea8e78ec 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -220,7 +220,7 @@ trait ParsedownGravTrait protected function convertUrl($markdown_url) { // if absolute and starts with a base_url move on - if (Utils::startsWith($markdown_url, $this->base_url)) { + if ($this->base_url != '' && Utils::startsWith($markdown_url, $this->base_url)) { return $markdown_url; // if contains only a fragment } elseif (Utils::startsWith($markdown_url, '#')) { From 9549e5aa4cbb50d5c444d494d3dad421d00518f6 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Wed, 22 Apr 2015 12:31:16 -0600 Subject: [PATCH 19/21] moved to stable version of toolbox (now in packagist) --- composer.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 6d4f3362b..b4422074b 100644 --- a/composer.json +++ b/composer.json @@ -21,15 +21,8 @@ "mrclay/minify": "dev-master", "donatj/phpuseragentparser": "dev-master", "pimple/pimple": "~3.0", - "rockettheme/toolbox": "dev-develop" + "rockettheme/toolbox": "1.0.*" }, - "repositories": [ - { - "type": "vcs", - "no-api": true, - "url": "https://github.com/rockettheme/toolbox" - } - ], "autoload": { "psr-4": { "Grav\\": "system/src/Grav" From 89b9b4e9b708314b274ede20e8da9190944ddd62 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Wed, 22 Apr 2015 14:12:45 -0600 Subject: [PATCH 20/21] Some optimizations and fixes --- .../Common/Markdown/ParsedownGravTrait.php | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 1ea8e78ec..b3d204232 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -127,10 +127,10 @@ trait ParsedownGravTrait //get the url and parse it $url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src'])); - $path_parts = pathinfo($url['path']); - // if there is no host set but there is a path, the file is local if (!isset($url['host']) && isset($url['path'])) { + $path_parts = pathinfo($url['path']); + // get the local path to page media if possible if ($path_parts['dirname'] == $this->page->url()) { $url['path'] = ltrim(str_replace($this->page->url(), '', $url['path']), '/'); @@ -232,37 +232,45 @@ trait ParsedownGravTrait $normalized_path = Utils::normalizePath($this->pages_dir . $markdown_url); $normalized_url = Utils::normalizePath($this->base_url . $markdown_url); } else { - $normalized_path = Utils::normalizePath($this->page->path() . '/' . $markdown_url); + // contains path, so need to normalize it + if (Utils::contains($markdown_url, '/')) { + $normalized_path = Utils::normalizePath($this->page->path() . '/' . $markdown_url); + } else { + $normalized_path = false; + } $normalized_url = $this->base_url . Utils::normalizePath($this->page->route() . '/' . $markdown_url); } - $url_bits = parse_url($normalized_path); - $full_path = $url_bits['path']; - // if this file exits, get the page and work with that - if (file_exists($full_path)) { - $path_info = pathinfo($full_path); - $page_path = $path_info['dirname']; - $filename = ''; + if ($normalized_path) { + $url_bits = parse_url($normalized_path); + $full_path = $url_bits['path']; - // save the filename if a file is part of the path - $filename_regex = "/([\w\d-_]+\.([a-zA-Z]{2,4}))$/"; - if (preg_match($filename_regex, $full_path, $matches)) { - if ($matches[2] != 'md') { - $filename = '/' . $matches[1]; + if ($full_path && file_exists($full_path)) { + $path_info = pathinfo($full_path); + $page_path = $path_info['dirname']; + $filename = ''; + + // save the filename if a file is part of the path + $filename_regex = "/([\w\d-_]+\.([a-zA-Z]{2,4}))$/"; + if (preg_match($filename_regex, $full_path, $matches)) { + if ($matches[2] != 'md') { + $filename = '/' . $matches[1]; + } + } else { + $page_path = $full_path; } - } else { - $page_path = $full_path; - } - // get page instances and try to find one that fits - $instances = $this->pages->instances(); - if (isset($instances[$page_path])) { - $target = $instances[$page_path]; - $url_bits['path'] = $this->base_url . $target->route() . $filename; - return Uri::buildUrl($url_bits); + // get page instances and try to find one that fits + $instances = $this->pages->instances(); + if (isset($instances[$page_path])) { + $target = $instances[$page_path]; + $url_bits['path'] = $this->base_url . $target->route() . $filename; + return Uri::buildUrl($url_bits); + } } } + return $normalized_url; } } From d350bd31cb4708e13b671b8ea5b35b60f70d6651 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Fri, 24 Apr 2015 14:06:44 -0600 Subject: [PATCH 21/21] version update --- CHANGELOG.md | 16 ++++++++++++++++ system/defines.php | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54d02ea79..584027e5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# v0.9.25 +## 04/24/2015 + +1. [](#new) + * Added support for E-Tag, Last-Modified, Cache-Control and Page-based expires headers +2. [](#improved) + * Refactored media image handling to make it more flexible and support absolute paths + * Refactored page modification check process to make it faster + * User account improvements in preparation for Admin plugin + * Protect against timing attacks + * Reset default system expires time to 0 seconds (can override if you need to) +3. [](#bugfix) + * Fix issues with spaces in webroot when using `bin/grav install` + * Fix for spaces in relative directory + * Bug fix in collection filtering + # v0.9.24 ## 04/15/2015 diff --git a/system/defines.php b/system/defines.php index 04f8ac6df..da3f94687 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '0.9.24'); +define('GRAV_VERSION', '0.9.25'); define('DS', '/'); // Directories and Paths