mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-04-02 01:52:10 +09:00
Merge #1639 파일 다운로드 개선 by kijin
* pr/1639: Create and verify file download keys without adding too much data to the session Improve filename encoding for modern browsers that support RFC 5987 Implement 304 Not Modified and 206 Partial Content for file downloads Thoroughly comment existing behavior
This commit is contained in:
commit
5e6878d0c7
1 changed files with 100 additions and 36 deletions
|
|
@ -263,87 +263,151 @@ class fileController extends file
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call a trigger (before)
|
||||
$output = ModuleHandler::triggerCall('file.downloadFile', 'before', $file_obj);
|
||||
if(!$output->toBool()) return $this->stop(($output->message)?$output->message:'msg_not_permitted_download');
|
||||
|
||||
|
||||
// 다운로드 후 (가상)
|
||||
// Increase download_count
|
||||
$args = new stdClass();
|
||||
$args->file_srl = $file_srl;
|
||||
executeQuery('file.updateFileDownloadCount', $args);
|
||||
|
||||
// Call a trigger (after)
|
||||
$output = ModuleHandler::triggerCall('file.downloadFile', 'after', $file_obj);
|
||||
|
||||
$random = new Password();
|
||||
$file_key = $_SESSION['__XE_FILE_KEY__'][$file_srl] = $random->createSecureSalt(32, 'hex');
|
||||
// Redirect to procFileOutput using file key
|
||||
if(!isset($_SESSION['__XE_FILE_KEY__']) || !is_string($_SESSION['__XE_FILE_KEY__']) || strlen($_SESSION['__XE_FILE_KEY__']) != 32)
|
||||
{
|
||||
$random = new Password();
|
||||
$_SESSION['__XE_FILE_KEY__'] = $random->createSecureSalt(32, 'hex');
|
||||
}
|
||||
$file_key_data = $file_obj->file_srl . $file_obj->file_size . $file_obj->uploaded_filename . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'];
|
||||
$file_key = substr(hash_hmac('sha256', $file_key_data, $_SESSION['__XE_FILE_KEY__']), 0, 32);
|
||||
header('Location: '.getNotEncodedUrl('', 'act', 'procFileOutput','file_srl',$file_srl,'file_key',$file_key));
|
||||
Context::close();
|
||||
exit();
|
||||
|
||||
}
|
||||
|
||||
public function procFileOutput()
|
||||
{
|
||||
// Get requsted file info
|
||||
$oFileModel = getModel('file');
|
||||
$file_srl = Context::get('file_srl');
|
||||
$file_key = Context::get('file_key');
|
||||
if(strstr($_SERVER['HTTP_USER_AGENT'], "Android")) $is_android = true;
|
||||
|
||||
if($is_android && $_SESSION['__XE_FILE_KEY_AND__'][$file_srl]) $session_key = '__XE_FILE_KEY_AND__';
|
||||
else $session_key = '__XE_FILE_KEY__';
|
||||
$columnList = array('source_filename', 'uploaded_filename', 'file_size');
|
||||
$file_obj = $oFileModel->getFile($file_srl, $columnList);
|
||||
$filesize = $file_obj->file_size;
|
||||
$filename = $file_obj->source_filename;
|
||||
$etag = md5($file_srl . $file_key . $_SERVER['HTTP_USER_AGENT']);
|
||||
|
||||
$uploaded_filename = $file_obj->uploaded_filename;
|
||||
|
||||
if(!file_exists($uploaded_filename)) return $this->stop('msg_file_not_found');
|
||||
|
||||
if(!$file_key || $_SESSION[$session_key][$file_srl] != $file_key)
|
||||
// Check file key
|
||||
if(strlen($file_key) != 32 || !isset($_SESSION['__XE_FILE_KEY__']) || !is_string($_SESSION['__XE_FILE_KEY__']))
|
||||
{
|
||||
unset($_SESSION[$session_key][$file_srl]);
|
||||
return $this->stop('msg_invalid_request');
|
||||
}
|
||||
$file_key_data = $file_srl . $file_obj->file_size . $file_obj->uploaded_filename . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'];
|
||||
$file_key_compare = substr(hash_hmac('sha256', $file_key_data, $_SESSION['__XE_FILE_KEY__']), 0, 32);
|
||||
if($file_key !== $file_key_compare)
|
||||
{
|
||||
return $this->stop('msg_invalid_request');
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
$uploaded_filename = $file_obj->uploaded_filename;
|
||||
if(!file_exists($uploaded_filename))
|
||||
{
|
||||
return $this->stop('msg_file_not_found');
|
||||
}
|
||||
|
||||
$file_size = $file_obj->file_size;
|
||||
$filename = $file_obj->source_filename;
|
||||
if(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE || (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== FALSE && strpos($_SERVER['HTTP_USER_AGENT'], 'Trident') !== FALSE && strpos($_SERVER['HTTP_USER_AGENT'], 'rv:') !== FALSE))
|
||||
// If client sent an If-None-Match header with the correct ETag, do not download again
|
||||
if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim(trim($_SERVER['HTTP_IF_NONE_MATCH']), '\'"') === $etag)
|
||||
{
|
||||
header('HTTP/1.1 304 Not Modified');
|
||||
exit();
|
||||
}
|
||||
|
||||
// If client sent an If-Modified-Since header with a recent modification date, do not download again
|
||||
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) > filemtime($uploaded_filename))
|
||||
{
|
||||
header('HTTP/1.1 304 Not Modified');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Filename encoding for browsers that support RFC 5987
|
||||
if(preg_match('#(?:Chrome|Edge)/(\d+)\.#', $_SERVER['HTTP_USER_AGENT'], $matches) && $matches[1] >= 11)
|
||||
{
|
||||
$filename_param = "filename*=UTF-8''" . rawurlencode($filename) . '; filename="' . rawurlencode($filename) . '"';
|
||||
}
|
||||
elseif(preg_match('#(?:Firefox|Safari|Trident)/(\d+)\.#', $_SERVER['HTTP_USER_AGENT'], $matches) && $matches[1] >= 6)
|
||||
{
|
||||
$filename_param = "filename*=UTF-8''" . rawurlencode($filename) . '; filename="' . rawurlencode($filename) . '"';
|
||||
}
|
||||
// Filename encoding for browsers that do not support RFC 5987
|
||||
elseif(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE)
|
||||
{
|
||||
$filename = rawurlencode($filename);
|
||||
$filename = preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1);
|
||||
$filename_param = 'filename="' . preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1) . '"';
|
||||
}
|
||||
|
||||
if($is_android)
|
||||
else
|
||||
{
|
||||
if($_SESSION['__XE_FILE_KEY__'][$file_srl]) $_SESSION['__XE_FILE_KEY_AND__'][$file_srl] = $file_key;
|
||||
$filename_param = 'filename="' . $filename . '"';
|
||||
}
|
||||
|
||||
unset($_SESSION[$session_key][$file_srl]);
|
||||
|
||||
// Close context to prevent blocking the session
|
||||
Context::close();
|
||||
|
||||
// Open file
|
||||
$fp = fopen($uploaded_filename, 'rb');
|
||||
if(!$fp) return $this->stop('msg_file_not_found');
|
||||
if(!$fp)
|
||||
{
|
||||
return $this->stop('msg_file_not_found');
|
||||
}
|
||||
|
||||
header("Cache-Control: ");
|
||||
// Take care of pause and resume
|
||||
if(isset($_SERVER['HTTP_RANGE']) && preg_match('/^bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches))
|
||||
{
|
||||
$range_start = $matches[1];
|
||||
$range_end = $matches[2] ? $matches[2] : ($filesize - 1);
|
||||
$range_length = $range_end - $range_start + 1;
|
||||
if($range_length < 1 || $range_start < 0 || $range_start >= $filesize || $range_end >= $filesize)
|
||||
{
|
||||
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||
fclose($fp);
|
||||
exit();
|
||||
}
|
||||
fseek($fp, $range_start);
|
||||
header('HTTP/1.1 206 Partial Content');
|
||||
header('Content-Range: bytes ' . $range_start . '-' . $range_end . '/' . $filesize);
|
||||
}
|
||||
else
|
||||
{
|
||||
$range_start = 0;
|
||||
$range_length = $filesize - $range_start;
|
||||
}
|
||||
|
||||
// Clear buffer
|
||||
while(ob_get_level()) ob_end_clean();
|
||||
|
||||
// Set headers
|
||||
header("Cache-Control: private; max-age=3600");
|
||||
header("Pragma: ");
|
||||
header("Content-Type: application/octet-stream");
|
||||
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
|
||||
|
||||
header("Content-Length: " .(string)($file_size));
|
||||
header('Content-Disposition: attachment; filename="'.$filename.'"');
|
||||
header("Content-Transfer-Encoding: binary\n");
|
||||
header('Content-Disposition: attachment; ' . $filename_param);
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Content-Length: ' . $range_length);
|
||||
header('Accept-Ranges: bytes');
|
||||
header('Etag: "' . $etag . '"');
|
||||
|
||||
// if file size is lager than 10MB, use fread function (#18675748)
|
||||
if(filesize($uploaded_filename) > 1024 * 1024)
|
||||
// Print the file contents
|
||||
for($offset = 0; $offset < $range_length; $offset += 4096)
|
||||
{
|
||||
while(!feof($fp)) echo fread($fp, 1024);
|
||||
fclose($fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
fpassthru($fp);
|
||||
$buffer_size = min(4096, $range_length - $offset);
|
||||
echo fread($fp, $buffer_size);
|
||||
flush();
|
||||
}
|
||||
|
||||
exit();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue