diff --git a/public_html/dispatch.php b/public_html/dispatch.php index 4971299d..ee6e2045 100644 --- a/public_html/dispatch.php +++ b/public_html/dispatch.php @@ -89,6 +89,8 @@ $postValidation = \Chibi\Router::register(['PostController', 'upvotedView'], 'GET', '/upvoted/{page}', $postValidation); \Chibi\Router::register(['PostController', 'genericView'], 'GET', '/post/{id}', $postValidation); +\Chibi\Router::register(['PostController', 'fileView'], 'GET', '/post/{name}/retrieve', $postValidation); +\Chibi\Router::register(['PostController', 'thumbView'], 'GET', '/post/{name}/thumb', $postValidation); \Chibi\Router::register(['PostController', 'toggleTagAction'], 'POST', '/post/{id}/toggle-tag/{tag}/{enable}', $postValidation); \Chibi\Router::register(['PostController', 'flagAction'], 'POST', '/post/{id}/flag', $postValidation); @@ -114,9 +116,6 @@ $commentValidation = foreach (['GET', 'POST'] as $method) { - \Chibi\Router::register(['PostController', 'retrieveAction'], $method, '/post/{name}/retrieve', $postValidation); - \Chibi\Router::register(['PostController', 'thumbAction'], $method, '/post/{name}/thumb', $postValidation); - $tagValidation = [ 'page' => '\d*', diff --git a/src/Api.php b/src/Api.php index caa1baf2..26555d8a 100644 --- a/src/Api.php +++ b/src/Api.php @@ -40,12 +40,4 @@ class Api }); return $statuses; } - - public static function serializeFile($filePath, $fileName) - { - $x = new StdClass; - $x->filePath = $filePath; - $x->fileName = $fileName; - return $x; - } } diff --git a/src/ApiFileInput.php b/src/ApiFileInput.php new file mode 100644 index 00000000..e2fe7fd0 --- /dev/null +++ b/src/ApiFileInput.php @@ -0,0 +1,17 @@ +filePath = $filePath; + $this->fileName = $fileName; + } +} diff --git a/src/ApiFileOutput.php b/src/ApiFileOutput.php new file mode 100644 index 00000000..9b2547db --- /dev/null +++ b/src/ApiFileOutput.php @@ -0,0 +1,17 @@ +fileContent = file_get_contents($filePath); + $this->fileName = $fileName; + $this->lastModified = filemtime($filePath); + $this->mimeType = mime_content_type($filePath); + } +} diff --git a/src/Controllers/PostController.php b/src/Controllers/PostController.php index ef4631bd..cdea2a36 100644 --- a/src/Controllers/PostController.php +++ b/src/Controllers/PostController.php @@ -107,7 +107,7 @@ class PostController $file = $_FILES['file']; TransferHelper::handleUploadErrors($file); - $jobArgs[EditPostContentJob::POST_CONTENT] = Api::serializeFile( + $jobArgs[EditPostContentJob::POST_CONTENT] = new ApiFileInput( $file['tmp_name'], $file['name']); } @@ -147,7 +147,7 @@ class PostController $file = $_FILES['file']; TransferHelper::handleUploadErrors($file); - $jobArgs[EditPostContentJob::POST_CONTENT] = Api::serializeFile( + $jobArgs[EditPostContentJob::POST_CONTENT] = new ApiFileInput( $file['tmp_name'], $file['name']); } @@ -157,7 +157,7 @@ class PostController $file = $_FILES['thumb']; TransferHelper::handleUploadErrors($file); - $jobArgs[EditPostThumbJob::THUMB_CONTENT] = Api::serializeFile( + $jobArgs[EditPostThumbJob::THUMB_CONTENT] = new ApiFileInput( $file['tmp_name'], $file['name']); } @@ -256,67 +256,34 @@ class PostController $context->transport->nextPostId = $nextPostId ? $nextPostId : null; } - public function thumbAction($name, $width = null, $height = null) + public function fileView($name) { + $ret = Api::run(new GetPostContentJob(), [GetPostContentJob::POST_NAME => $name]); + $context = getContext(); - $path = PostModel::getThumbCustomPath($name, $width, $height); - if (!file_exists($path)) - { - $path = PostModel::getThumbDefaultPath($name, $width, $height); - if (!file_exists($path)) - { - $post = PostModel::findByIdOrName($name); - Access::assert(Privilege::ListPosts); - Access::assert(Privilege::ListPosts, PostSafety::toString($post->safety)); - $post->generateThumb($width, $height); - if (!file_exists($path)) - { - $path = getConfig()->main->mediaPath . DS . 'img' . DS . 'thumb.jpg'; - $path = TextHelper::absolutePath($path); - } - } - } - - if (!is_readable($path)) - throw new SimpleException('Thumbnail file is not readable'); - + $context->transport->cacheDaysToLive = 14; + $context->transport->customFileName = $ret->fileName; + $context->transport->mimeType = $ret->mimeType; + $context->transport->fileHash = 'post' . md5(substr($ret->fileContent, 0, 4096)); + $context->transport->fileContent = $ret->fileContent; + $context->transport->lastModified = $ret->lastModified; $context->layoutName = 'layout-file'; - $context->transport->cacheDaysToLive = 365; - $context->transport->mimeType = 'image/jpeg'; - $context->transport->fileHash = 'thumb' . md5($name . filemtime($path)); - $context->transport->filePath = $path; } - public function retrieveAction($name) + public function thumbView($name, $width = null, $height = null) { - $post = PostModel::findByName($name, true); - $config = getConfig(); + $ret = Api::run(new GetPostThumbJob(), [ + GetPostThumbJob::POST_NAME => $name, + GetPostThumbJob::WIDTH => $width, + GetPostThumbJob::HEIGHT => $height]); + $context = getContext(); - - Access::assert(Privilege::RetrievePost); - Access::assert(Privilege::RetrievePost, PostSafety::toString($post->safety)); - - $path = $config->main->filesPath . DS . $post->name; - $path = TextHelper::absolutePath($path); - if (!file_exists($path)) - throw new SimpleNotFoundException('Post file does not exist'); - if (!is_readable($path)) - throw new SimpleException('Post file is not readable'); - - $fn = sprintf('%s_%s_%s.%s', - $config->main->title, - $post->id, - join(',', array_map(function($tag) { return $tag->name; }, $post->getTags())), - TextHelper::resolveMimeType($post->mimeType) ?: 'dat'); - $fn = preg_replace('/[[:^print:]]/', '', $fn); - - $ttl = 60 * 60 * 24 * 14; - + $context->transport->cacheDaysToLive = 365; + $context->transport->customFileName = $ret->fileName; + $context->transport->mimeType = 'image/jpeg'; + $context->transport->fileHash = 'thumb' . md5(substr($ret->fileContent, 0, 4096)); + $context->transport->fileContent = $ret->fileContent; + $context->transport->lastModified = $ret->lastModified; $context->layoutName = 'layout-file'; - $context->transport->cacheDaysToLive = 14; - $context->transport->customFileName = $fn; - $context->transport->mimeType = $post->mimeType; - $context->transport->fileHash = 'post' . $post->fileHash; - $context->transport->filePath = $path; } } diff --git a/src/Jobs/Abstraction/AbstractJob.php b/src/Jobs/Abstraction/AbstractJob.php index a17a7768..32139658 100644 --- a/src/Jobs/Abstraction/AbstractJob.php +++ b/src/Jobs/Abstraction/AbstractJob.php @@ -3,6 +3,7 @@ abstract class AbstractJob { const COMMENT_ID = 'comment-id'; const POST_ID = 'post-id'; + const POST_NAME = 'post-name'; const TAG_NAME = 'tag-name'; const TAG_NAMES = 'tags'; const TEXT = 'text'; diff --git a/src/Jobs/GetPostContentJob.php b/src/Jobs/GetPostContentJob.php new file mode 100644 index 00000000..2c556da7 --- /dev/null +++ b/src/Jobs/GetPostContentJob.php @@ -0,0 +1,48 @@ +getArgument(self::POST_NAME)); + + //todo: refactor this so that requiresPrivilege can accept multiple privileges + if ($post->hidden) + Access::assert(Privilege::RetrievePost, 'hidden'); + Access::assert(Privilege::RetrievePost); + Access::assert(Privilege::RetrievePost, PostSafety::toString($post->safety)); + + $config = getConfig(); + + $path = $config->main->filesPath . DS . $post->name; + $path = TextHelper::absolutePath($path); + if (!file_exists($path)) + throw new SimpleNotFoundException('Post file does not exist'); + if (!is_readable($path)) + throw new SimpleException('Post file is not readable'); + + $fileName = sprintf('%s_%s_%s.%s', + $config->main->title, + $post->id, + join(',', array_map(function($tag) { return $tag->name; }, $post->getTags())), + TextHelper::resolveMimeType($post->mimeType) ?: 'dat'); + $fileName = preg_replace('/[[:^print:]]/', '', $fileName); + + return new ApiFileOutput($path, $fileName); + } + + public function requiresPrivilege() + { + //temporarily enforced in execute + return false; + } + + public function requiresAuthentication() + { + return false; + } + + public function requiresConfirmedEmail() + { + return false; + } +} diff --git a/src/Jobs/GetPostJob.php b/src/Jobs/GetPostJob.php index 410f2088..cd0c945e 100644 --- a/src/Jobs/GetPostJob.php +++ b/src/Jobs/GetPostJob.php @@ -6,10 +6,10 @@ class GetPostJob extends AbstractPostEditJob $post = $this->post; //todo: refactor this so that requiresPrivilege can accept multiple privileges - if ($this->post->hidden) + if ($post->hidden) Access::assert(Privilege::ViewPost, 'hidden'); Access::assert(Privilege::ViewPost); - Access::assert(Privilege::ViewPost, PostSafety::toString($this->post->safety)); + Access::assert(Privilege::ViewPost, PostSafety::toString($post->safety)); CommentModel::preloadCommenters($post->getComments()); diff --git a/src/Jobs/GetPostThumbJob.php b/src/Jobs/GetPostThumbJob.php new file mode 100644 index 00000000..e44810d9 --- /dev/null +++ b/src/Jobs/GetPostThumbJob.php @@ -0,0 +1,57 @@ +getArgument(self::POST_NAME); + $width = $this->hasArgument(self::WIDTH) ? $this->getArgument(self::WIDTH) : null; + $height = $this->hasArgument(self::HEIGHT) ? $this->getArgument(self::HEIGHT) : null; + + $path = PostModel::getThumbCustomPath($name, $width, $height); + if (!file_exists($path)) + { + $path = PostModel::getThumbDefaultPath($name, $width, $height); + if (!file_exists($path)) + { + $post = PostModel::findByIdOrName($name); + + if ($post->hidden) + Access::assert(Privilege::ListPosts, 'hidden'); + Access::assert(Privilege::ListPosts); + Access::assert(Privilege::ListPosts, PostSafety::toString($post->safety)); + + $post->generateThumb($width, $height); + + if (!file_exists($path)) + { + $path = getConfig()->main->mediaPath . DS . 'img' . DS . 'thumb.jpg'; + $path = TextHelper::absolutePath($path); + } + } + } + + if (!is_readable($path)) + throw new SimpleException('Thumbnail file is not readable'); + + return new ApiFileOutput($path, 'thumbnail.jpg'); + } + + public function requiresPrivilege() + { + //manually enforced in execute when post is retrieved + return false; + } + + public function requiresAuthentication() + { + return false; + } + + public function requiresConfirmedEmail() + { + return false; + } +} diff --git a/src/Views/layout-file.phtml b/src/Views/layout-file.phtml index 05363f3c..70d8400f 100644 --- a/src/Views/layout-file.phtml +++ b/src/Views/layout-file.phtml @@ -1,47 +1,38 @@ context->transport->errorMessage)) +$lastModified = $this->context->transport->lastModified; +$eTag = $this->context->transport->fileHash; +$ttl = $this->context->transport->cacheDaysToLive * 24 * 3600; + +$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) + ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] + : false; + +$eTagHeader = isset($_SERVER['HTTP_IF_NONE_MATCH']) + ? trim(trim($_SERVER['HTTP_IF_NONE_MATCH']), '"') + : false; + +\Chibi\Util\Headers::set('ETag', '"' . $eTag . '"'); +\Chibi\Util\Headers::set('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $lastModified)); +\Chibi\Util\Headers::set('Pragma', 'public'); +\Chibi\Util\Headers::set('Cache-Control', 'public, max-age=' . $ttl); +\Chibi\Util\Headers::set('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + $ttl)); + +if (isset($this->context->transport->customFileName)) { - \Chibi\Util\Headers::set('Content-Type', 'text/plain; charset=utf-8'); - echo $this->context->transport->errorMessage; -} -else -{ - $lastModified = filemtime($this->context->transport->filePath); - $eTag = $this->context->transport->fileHash; - $ttl = $this->context->transport->cacheDaysToLive * 24 * 3600; - - $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) - ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] - : false; - - $eTagHeader = isset($_SERVER['HTTP_IF_NONE_MATCH']) - ? trim(trim($_SERVER['HTTP_IF_NONE_MATCH']), '"') - : false; - - \Chibi\Util\Headers::set('ETag', '"' . $eTag . '"'); - \Chibi\Util\Headers::set('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $lastModified)); - \Chibi\Util\Headers::set('Pragma', 'public'); - \Chibi\Util\Headers::set('Cache-Control', 'public, max-age=' . $ttl); - \Chibi\Util\Headers::set('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + $ttl)); - - if (isset($this->context->transport->customFileName)) - { - \Chibi\Util\Headers::set( - 'Content-Disposition', - 'inline; filename="' . $this->context->transport->customFileName . '"'); - } - \Chibi\Util\Headers::set( - 'Content-Type', - $this->context->transport->mimeType); - - - if (strtotime($ifModifiedSince) == $lastModified or $eTagHeader == $eTag) - { - header('HTTP/1.1 304 Not Modified'); - exit; - } - - readfile($this->context->transport->filePath); - flush(); + 'Content-Disposition', + 'inline; filename="' . $this->context->transport->customFileName . '"'); } + +\Chibi\Util\Headers::set( + 'Content-Type', + $this->context->transport->mimeType); + +if (strtotime($ifModifiedSince) == $lastModified or $eTagHeader == $eTag) +{ + \Chibi\Util\Headers::setCode('304'); + exit; +} + +echo $this->context->transport->fileContent; +flush(); diff --git a/src/Views/post-file-render.phtml b/src/Views/post-file-render.phtml index 4a743f68..17aa4a3c 100644 --- a/src/Views/post-file-render.phtml +++ b/src/Views/post-file-render.phtml @@ -1,6 +1,6 @@ $this->context->transport->post->name])); $post = $this->context->transport->post; ?> @@ -13,7 +13,7 @@ $post = $this->context->transport->post; <?= $post->name ?> context->imageLink)): ?> @@ -27,12 +27,12 @@ $post = $this->context->transport->post; width="imageWidth ?>" height="imageHeight ?>" data=" $post->name]) ?>"> @@ -52,7 +52,7 @@ $post = $this->context->transport->post; Your browser doesn't support HTML5 <video> tag. diff --git a/src/Views/post-small.phtml b/src/Views/post-small.phtml index 5ac83ce2..013915cd 100644 --- a/src/Views/post-small.phtml +++ b/src/Views/post-small.phtml @@ -48,7 +48,7 @@ if ($masstag) @<?= $this->context->post->id ?> 0; context->transport->post->type != PostType::Youtube): ?>