进行了一些优化
This commit is contained in:
1 parent
50c5769d01
commit
f9e411dda8
1 file changed
+82
-56
+82
-56
@@ -329,18 +329,21 @@ class Download {
|
|||||||
$idx = (int) $row['source_index'];
|
$idx = (int) $row['source_index'];
|
||||||
if (empty($meta['downloads'][$idx]['url'])) $this->fail_410();
|
if (empty($meta['downloads'][$idx]['url'])) $this->fail_410();
|
||||||
|
|
||||||
// HEAD probes validate the link but must never consume it.
|
// Probes (HEAD requests and Speculative-Loading prefetches) validate
|
||||||
$is_head = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'HEAD';
|
// availability but must never consume a use nor waste bandwidth.
|
||||||
|
$is_probe = $this->is_probe_request();
|
||||||
|
|
||||||
$target_url = esc_url_raw((string) $meta['downloads'][$idx]['url']);
|
$target_url = esc_url_raw((string) $meta['downloads'][$idx]['url']);
|
||||||
|
|
||||||
// Local attachment / uploads file: stream without artificial throttling.
|
// Local attachment / uploads file: stream without artificial throttling.
|
||||||
$local_path = $this->resolve_local_download_path($target_url);
|
$local_path = $this->resolve_local_download_path($target_url);
|
||||||
if ($local_path !== '' && is_readable($local_path)) {
|
if ($local_path !== '' && is_readable($local_path)) {
|
||||||
$delivered = $this->stream_local_file($local_path);
|
$this->send_download_headers($local_path);
|
||||||
// Count only fully delivered bodies; aborted transfers stay reusable.
|
if (!$is_probe) {
|
||||||
if ($delivered && !$is_head) {
|
// Count only fully delivered bodies; aborted transfers stay reusable.
|
||||||
$this->consume_token($row, $token);
|
if ($this->stream_body($local_path)) {
|
||||||
|
$this->consume_token($row, $token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -348,13 +351,84 @@ class Download {
|
|||||||
nocache_headers();
|
nocache_headers();
|
||||||
wp_redirect($target_url, 302);
|
wp_redirect($target_url, 302);
|
||||||
// Remote hosts give no completion signal, so the redirect hand-off
|
// Remote hosts give no completion signal, so the redirect hand-off
|
||||||
// counts as delivery; HEAD probes still never consume.
|
// counts as delivery; probes still never consume.
|
||||||
if (!$is_head) {
|
if (!$is_probe) {
|
||||||
$this->consume_token($row, $token);
|
$this->consume_token($row, $token);
|
||||||
}
|
}
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that inspect the link instead of taking the download: plain
|
||||||
|
* HEAD plus browser speculative prefetches (Sec-Purpose: prefetch).
|
||||||
|
*/
|
||||||
|
private function is_probe_request(): bool {
|
||||||
|
if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'HEAD') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$purpose = strtolower((string) ($_SERVER['HTTP_SEC_PURPOSE'] ?? ($_SERVER['HTTP_PURPOSE'] ?? '')));
|
||||||
|
return $purpose !== '' && str_contains($purpose, 'prefetch');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the attachment headers for a local download.
|
||||||
|
*/
|
||||||
|
private function send_download_headers(string $path): void {
|
||||||
|
$filename = basename($path);
|
||||||
|
$size = filesize($path);
|
||||||
|
if ($size === false) {
|
||||||
|
$this->fail_410();
|
||||||
|
}
|
||||||
|
|
||||||
|
nocache_headers();
|
||||||
|
header('Content-Description: File Transfer');
|
||||||
|
header('Content-Type: application/octet-stream');
|
||||||
|
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
|
||||||
|
header('Content-Transfer-Encoding: binary');
|
||||||
|
header('Content-Length: ' . (string) $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream the local file body after its headers. Returns true only when
|
||||||
|
* every byte was flushed to the client.
|
||||||
|
*/
|
||||||
|
private function stream_body(string $path): bool {
|
||||||
|
$size = filesize($path);
|
||||||
|
if ($size === false) {
|
||||||
|
$this->fail_410();
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunk = 1024 * 64; // 64KB
|
||||||
|
|
||||||
|
$fh = fopen($path, 'rb');
|
||||||
|
if (!$fh) {
|
||||||
|
$this->fail_410();
|
||||||
|
}
|
||||||
|
// Disable output compression so Content-Length remains accurate.
|
||||||
|
if (function_exists('apache_setenv')) {
|
||||||
|
@apache_setenv('no-gzip', '1');
|
||||||
|
}
|
||||||
|
@ini_set('zlib.output_compression', 'Off');
|
||||||
|
while (!feof($fh)) {
|
||||||
|
if (connection_aborted()) {
|
||||||
|
fclose($fh);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$buf = fread($fh, $chunk);
|
||||||
|
if ($buf === false) {
|
||||||
|
fclose($fh);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
echo $buf;
|
||||||
|
if (function_exists('flush')) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fh);
|
||||||
|
|
||||||
|
return !connection_aborted();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map a site-local URL to a filesystem path under uploads (or ABSPATH).
|
* Map a site-local URL to a filesystem path under uploads (or ABSPATH).
|
||||||
*/
|
*/
|
||||||
@@ -388,54 +462,6 @@ class Download {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream a local file. Returns true only when the body was sent in full.
|
|
||||||
*/
|
|
||||||
private function stream_local_file(string $path): bool {
|
|
||||||
$filename = basename($path);
|
|
||||||
$size = filesize($path);
|
|
||||||
if ($size === false) {
|
|
||||||
$this->fail_410();
|
|
||||||
}
|
|
||||||
|
|
||||||
nocache_headers();
|
|
||||||
header('Content-Description: File Transfer');
|
|
||||||
header('Content-Type: application/octet-stream');
|
|
||||||
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
|
|
||||||
header('Content-Transfer-Encoding: binary');
|
|
||||||
header('Content-Length: ' . (string) $size);
|
|
||||||
|
|
||||||
$chunk = 1024 * 64; // 64KB
|
|
||||||
|
|
||||||
$fh = fopen($path, 'rb');
|
|
||||||
if (!$fh) {
|
|
||||||
$this->fail_410();
|
|
||||||
}
|
|
||||||
// Disable output compression so Content-Length remains accurate.
|
|
||||||
if (function_exists('apache_setenv')) {
|
|
||||||
@apache_setenv('no-gzip', '1');
|
|
||||||
}
|
|
||||||
@ini_set('zlib.output_compression', 'Off');
|
|
||||||
while (!feof($fh)) {
|
|
||||||
if (connection_aborted()) {
|
|
||||||
fclose($fh);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$buf = fread($fh, $chunk);
|
|
||||||
if ($buf === false) {
|
|
||||||
fclose($fh);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
echo $buf;
|
|
||||||
if (function_exists('flush')) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose($fh);
|
|
||||||
|
|
||||||
return !connection_aborted();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count a delivered download. The guard caps concurrent finishers at
|
* Count a delivered download. The guard caps concurrent finishers at
|
||||||
* max_uses instead of overshooting the counter.
|
* max_uses instead of overshooting the counter.
|
||||||
|
|||||||
Reference in new issue
Block a user