diff --git a/core/page.php b/core/page.php
index 12189999..e31ed7d3 100644
--- a/core/page.php
+++ b/core/page.php
@@ -31,6 +31,7 @@ abstract class PageMode
const REDIRECT = 'redirect';
const DATA = 'data';
const PAGE = 'page';
+ const FILE = 'file';
}
/**
@@ -75,9 +76,14 @@ class Page
/** @var string; public only for unit test */
public $data = "";
+ /** @var string; */
+ public $file = null;
+
/** @var string; public only for unit test */
public $filename = null;
+ private $disposition = null;
+
/**
* Set the raw data to be sent.
*/
@@ -86,12 +92,18 @@ class Page
$this->data = $data;
}
+ public function set_file(string $file): void
+ {
+ $this->file = $file;
+ }
+
/**
* Set the recommended download filename.
*/
- public function set_filename(string $filename): void
+ public function set_filename(string $filename, string $disposition = "attachment"): void
{
$this->filename = $filename;
+ $this->disposition = $disposition;
}
@@ -171,7 +183,7 @@ class Page
/**
* Add a line to the HTML head section.
*/
- public function add_html_header(string $line, int $position=50): void
+ public function add_html_header(string $line, int $position = 50): void
{
while (isset($this->html_headers[$position])) {
$position++;
@@ -182,7 +194,7 @@ class Page
/**
* Add a http header to be sent to the client.
*/
- public function add_http_header(string $line, int $position=50): void
+ public function add_http_header(string $line, int $position = 50): void
{
while (isset($this->http_headers[$position])) {
$position++;
@@ -197,13 +209,13 @@ class Page
*/
public function add_cookie(string $name, string $value, int $time, string $path): void
{
- $full_name = COOKIE_PREFIX."_".$name;
+ $full_name = COOKIE_PREFIX . "_" . $name;
$this->cookies[] = [$full_name, $value, $time, $path];
}
public function get_cookie(string $name): ?string
{
- $full_name = COOKIE_PREFIX."_".$name;
+ $full_name = COOKIE_PREFIX . "_" . $name;
if (isset($_COOKIE[$full_name])) {
return $_COOKIE[$full_name];
} else {
@@ -252,8 +264,8 @@ class Page
global $page, $user;
header("HTTP/1.0 {$this->code} Shimmie");
- header("Content-type: ".$this->type);
- header("X-Powered-By: SCore-".SCORE_VERSION);
+ header("Content-type: " . $this->type);
+ header("X-Powered-By: SCore-" . SCORE_VERSION);
if (!headers_sent()) {
foreach ($this->http_headers as $head) {
@@ -292,15 +304,84 @@ class Page
$layout->display_page($page);
break;
case PageMode::DATA:
- header("Content-Length: ".strlen($this->data));
+ header("Content-Length: " . strlen($this->data));
if (!is_null($this->filename)) {
- header('Content-Disposition: attachment; filename='.$this->filename);
+ header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
}
print $this->data;
break;
+ case PageMode::FILE:
+ if (!is_null($this->filename)) {
+ header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
+ }
+
+ //https://gist.github.com/codler/3906826
+
+ $size = filesize($this->file); // File size
+ $length = $size; // Content length
+ $start = 0; // Start byte
+ $end = $size - 1; // End byte
+
+ header("Content-Length: " . strlen($size));
+ header('Accept-Ranges: bytes');
+
+ if (isset($_SERVER['HTTP_RANGE'])) {
+
+ $c_start = $start;
+ $c_end = $end;
+ list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+ if (strpos($range, ',') !== false) {
+ header('HTTP/1.1 416 Requested Range Not Satisfiable');
+ header("Content-Range: bytes $start-$end/$size");
+ break;
+ }
+ if ($range == '-') {
+ $c_start = $size - substr($range, 1);
+ } else {
+ $range = explode('-', $range);
+ $c_start = $range[0];
+ $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
+ }
+ $c_end = ($c_end > $end) ? $end : $c_end;
+ if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
+ header('HTTP/1.1 416 Requested Range Not Satisfiable');
+ header("Content-Range: bytes $start-$end/$size");
+ break;
+ }
+ $start = $c_start;
+ $end = $c_end;
+ $length = $end - $start + 1;
+ header('HTTP/1.1 206 Partial Content');
+ }
+ header("Content-Range: bytes $start-$end/$size");
+ header("Content-Length: " . $length);
+
+
+ $fp = fopen($this->file, 'r');
+ try {
+ fseek($fp, $start);
+ $buffer = 1024 * 64;
+ while (!feof($fp) && ($p = ftell($fp)) <= $end) {
+ if ($p + $buffer > $end) {
+ $buffer = $end - $p + 1;
+ }
+ set_time_limit(0);
+ echo fread($fp, $buffer);
+ flush();
+
+ // After flush, we can tell if the client browser has disconnected.
+ // This means we can start sending a large file, and if we detect they disappeared
+ // then we can just stop and not waste any more resources or bandwidth.
+ if (connection_status() != 0)
+ break;
+ }
+ } finally {
+ fclose($fp);
+ }
+ break;
case PageMode::REDIRECT:
- header('Location: '.$this->redirect);
- print 'You should be redirected to '.$this->redirect.'';
+ header('Location: ' . $this->redirect);
+ print 'You should be redirected to ' . $this->redirect . '';
break;
default:
print "Invalid page mode";
@@ -341,7 +422,7 @@ class Page
/*** Generate CSS cache files ***/
$css_latest = $config_latest;
$css_files = array_merge(
- zglob("ext/{".ENABLED_EXTS."}/style.css"),
+ zglob("ext/{" . ENABLED_EXTS . "}/style.css"),
zglob("themes/$theme_name/style.css")
);
foreach ($css_files as $css) {
@@ -354,7 +435,7 @@ class Page
foreach ($css_files as $file) {
$file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
- $replace = 'url("../../../'.dirname($file).'/$1")';
+ $replace = 'url("../../../' . dirname($file) . '/$1")';
$file_data = preg_replace($pattern, $replace, $file_data);
$css_data .= $file_data . "\n";
}
@@ -372,7 +453,7 @@ class Page
"vendor/bower-asset/js-cookie/src/js.cookie.js",
"ext/handle_static/modernizr-3.3.1.custom.js",
],
- zglob("ext/{".ENABLED_EXTS."}/script.js"),
+ zglob("ext/{" . ENABLED_EXTS . "}/script.js"),
zglob("themes/$theme_name/script.js")
);
foreach ($js_files as $js) {
diff --git a/ext/image/main.php b/ext/image/main.php
index 3fc63325..c8d51126 100644
--- a/ext/image/main.php
+++ b/ext/image/main.php
@@ -255,7 +255,6 @@ class ImageIO extends Extension
global $page;
if (!is_null($image)) {
- $page->set_mode(PageMode::DATA);
if ($type == "thumb") {
$ext = $config->get_string("thumb_type");
if (array_key_exists($ext, MIME_TYPE_MAP)) {
@@ -263,7 +262,7 @@ class ImageIO extends Extension
} else {
$page->set_type("image/jpeg");
}
-
+
$file = $image->get_thumb_filename();
} else {
$page->set_type($image->get_mime_type());
@@ -278,26 +277,29 @@ class ImageIO extends Extension
$gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
if ($if_modified_since == $gmdate_mod) {
+ $page->set_mode(PageMode::DATA);
$page->set_code(304);
$page->set_data("");
} else {
+ $page->set_mode(PageMode::FILE);
$page->add_http_header("Last-Modified: $gmdate_mod");
if ($type != "thumb") {
- $page->add_http_header("Content-Disposition: inline; filename=".$image->get_nice_image_name());
+ $page->set_filename($image->get_nice_image_name(), 'inline');
}
- $page->set_data(file_get_contents($file));
+
+ $page->set_file($file);
if ($config->get_int("image_expires")) {
$expires = date(DATE_RFC1123, time() + $config->get_int("image_expires"));
} else {
$expires = 'Fri, 2 Sep 2101 12:42:42 GMT'; // War was beginning
}
- $page->add_http_header('Expires: '.$expires);
+ $page->add_http_header('Expires: ' . $expires);
}
} else {
$page->set_title("Not Found");
$page->set_heading("Not Found");
- $page->add_block(new Block("Navigation", "Index", "left", 0));
+ $page->add_block(new Block("Navigation", "Index", "left", 0));
$page->add_block(new Block(
"Image not in database",
"The requested image was not found in the database"