进行了一些优化
This commit is contained in:
+82
-56
@@ -329,18 +329,21 @@ class Download {
|
||||
$idx = (int) $row['source_index'];
|
||||
if (empty($meta['downloads'][$idx]['url'])) $this->fail_410();
|
||||
|
||||
// HEAD probes validate the link but must never consume it.
|
||||
$is_head = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'HEAD';
|
||||
// Probes (HEAD requests and Speculative-Loading prefetches) validate
|
||||
// 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']);
|
||||
|
||||
// Local attachment / uploads file: stream without artificial throttling.
|
||||
$local_path = $this->resolve_local_download_path($target_url);
|
||||
if ($local_path !== '' && is_readable($local_path)) {
|
||||
$delivered = $this->stream_local_file($local_path);
|
||||
// Count only fully delivered bodies; aborted transfers stay reusable.
|
||||
if ($delivered && !$is_head) {
|
||||
$this->consume_token($row, $token);
|
||||
$this->send_download_headers($local_path);
|
||||
if (!$is_probe) {
|
||||
// Count only fully delivered bodies; aborted transfers stay reusable.
|
||||
if ($this->stream_body($local_path)) {
|
||||
$this->consume_token($row, $token);
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
@@ -348,13 +351,84 @@ class Download {
|
||||
nocache_headers();
|
||||
wp_redirect($target_url, 302);
|
||||
// Remote hosts give no completion signal, so the redirect hand-off
|
||||
// counts as delivery; HEAD probes still never consume.
|
||||
if (!$is_head) {
|
||||
// counts as delivery; probes still never consume.
|
||||
if (!$is_probe) {
|
||||
$this->consume_token($row, $token);
|
||||
}
|
||||
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).
|
||||
*/
|
||||
@@ -388,54 +462,6 @@ class Download {
|
||||
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
|
||||
* max_uses instead of overshooting the counter.
|
||||
|
||||
Reference in New Issue
Block a user