diff --git a/system/config/system.yaml b/system/config/system.yaml index 59f4b36a9..a5eec78fe 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -30,6 +30,7 @@ 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 cache: enabled: true # Set to true to enable caching diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index e0a91a06a..832c00e89 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); } } 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"; + } + } }