我正在为我的 Messenger 进行文件加密,但在加密完成后我正在努力上传文件。
加密在性能方面似乎不错,但是当我尝试上传时,浏览器完全挂起。 Profiler无限写入“小GC”事件,每10秒出现关于挂起脚本的黄色条。
我已经尝试过的:
用FileReader读取文件到ArrayBuffer,然后把它变成一个基本的Array,加密它,然后创建一个FormData对象,从数据中创建一个File,将其附加到FormData并发送。当我没有进行加密时,它可以快速处理大约 1.3 Mb 大小的原始、未触及的文件,但是在上传后加密的“假”文件对象上我得到了 4.7 Mb 的文件并且它不可用。
作为普通的 POST 字段发送(多部分表单数据编码)。用这种方式保存在PHP上后数据损坏了。
作为 Base64 编码的 POST 字段发送。最后,在我找到了一个从二进制数组到 Base64 字符串的快速转换函数后,它开始以这种方式工作。 btoa() 在编码/解码后给出了错误的结果。但是在我尝试了一个 8.5 Mb 大小的文件后,它又挂了。
我尝试将额外的数据移动到 URL 字符串,并按照 here 所述将文件作为 Blob 发送。没有成功,浏览器仍然挂起。
我尝试向 Blob 构造函数传递一个基本数组,一个由它组成的 Uint8Array,最后我尝试按照文档中的建议发送 File,但结果仍然相同,即使文件很小。
代码有什么问题?发生此挂起时,HDD 负载为 0%。而且有问题的文件真的很小
当我按下按钮紧急终止 JS 脚本时,这是我从服务器脚本中得到的输出:
Warning: Unknown: POST Content-Length of 22146226 bytes exceeds the limit of 8388608 bytes in Unknown on line 0
Warning: Cannot modify header information - headers already sent in Unknown on line 0
Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent in D:\xmessenger\upload.php on line 2
Array ( )
这是我的 JavaScript:
function uploadEncryptedFile(nonce) {
if (typeof window['FormData'] !== 'function' || typeof window['File'] !== 'function') return
var file_input = document.getElementById('attachment')
if (!file_input.files.length) return
var file = file_input.files[0]
var reader = new FileReader();
reader.addEventListener('load', function() {
var data = Array.from(new Uint8Array(reader.result))
var encrypted = encryptFile(data, nonce)
//return //Here it never hangs
var form_data = new FormData()
form_data.append('name', file.name)
form_data.append('type', file.type)
form_data.append('attachment', arrayBufferToBase64(encrypted))
/* form_data.append('attachment', btoa(encrypted)) // Does not help */
form_data.append('nonce', nonce)
var req = getXmlHttp()
req.open('POST', 'upload.php?attachencryptedfile', true)
req.onload = function() {
var data = req.responseText.split(':')
document.getElementById('filelist').lastChild.realName = data[2]
document.getElementById('progress2').style.display = 'none'
document.getElementById('attachment').onclick = null
encryptFilename(data[0], data[1], data[2])
}
req.send(form_data)
/* These lines also fail when the file is larger */
/* req.send(new Blob(encrypted)) */
/* req.send(new Blob(new Uint8Array(encrypted))) */
})
reader.readAsArrayBuffer(file)
}
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
这是我的 PHP 处理程序代码:
if (isset($_GET['attachencryptedfile'])) {
$entityBody = file_get_contents('php://input');
if ($entityBody == '') exit(print_r($_POST, true));
else exit($entityBody);
if (!isset($_POST["name"])) exit("Error");
$name = @preg_replace("/[^0-9A-Za-z._-]/", "", $_POST["name"]);
$nonce = @preg_replace("/[^0-9A-Za-z+\\/]/", "", $_POST["nonce"]);
if ($name == ".htaccess") exit();
$data = base64_decode($_POST["attachment"]);
//print_r($_POST);
//exit();
if (strlen($data)>1024*15*1024) exit('<script type="text/javascript">parent.showInfo("Файл слишком большой"); parent.document.getElementById(\'filelist\').removeChild(parent.document.getElementById(\'filelist\').lastChild); parent.document.getElementById(\'progress2\').style.display = \'none\'; parent.document.getElementById(\'attachment\').onclick = null</script>');
$uname = uniqid()."_".str_pad($_SESSION['xm_user_id'], 6, "0", STR_PAD_LEFT).substr($name, strrpos($name, "."));
file_put_contents("upload/".$uname, $data);
mysql_query("ALTER TABLE `attachments` AUTO_INCREMENT=0");
mysql_query("INSERT INTO `attachments` VALUES('0', '".$uname."', '".$name."', '0', '".$nonce."')");
exit(mysql_insert_id().":".$uname.":".$name);
}
HTML 表单:
<form name="fileForm" id="fileForm" method="post" enctype="multipart/form-data" action="upload.php?attachfile" target="ifr">
<div id="fileButton" title="Прикрепить файл" onclick="document.getElementById('attachment').click()"></div>
<input type="file" name="attachment" id="attachment" title="Прикрепить файл" onchange="addFile()" />
</form>
答案 0 :(得分:1)
UPD:不幸的是,问题没有解决。我的回答只是部分正确。现在我在代码中犯了一个愚蠢的错误(忘记更新服务器端),我发现了另一个可能挂起的原因。如果我提交一个基本的 POST 表单 (x-www-urlencoded) 并且 PHP 脚本中的代码尝试执行这一行($uname 已定义,$_FILES 是一个空数组)
if (!copy($_FILES['attachment']['tmp_name'], "upload/".$uname)) exit("Error");
然后整个事情又挂了。如果我终止脚本,服务器响应是代码 200,并且正文内容很好(我的开发机器上有错误输出)。我知道这是一件坏事 - 用第一个完全未定义的参数调用 copy
,但即使服务器错误 500 也不能以这种方式挂起浏览器(顺便说一句,新的最新版本的 Firefox 也受到影响) .
我在 Windows 7 x64 和 PHP 5.3 上安装了 Apache 2.4。有人可以验证这件事吗?也许应该向 Apache/Firefox 团队提交错误?
天啊。这种可怕的行为是由 php.ini 中设置的... post_max_size = 8M
引起的。我发现小于 8 Mb 的文件实际上并没有挂起浏览器。
最后一个问题是 - 为什么?为什么 Apache/PHP(顺便说一句,我有 Apache 2.4,它不旧)以某种方式优雅地中止连接,告诉浏览器超出了限制?或者它可能是 XHR 实现中的一个错误,不适用于基本表单提交。无论如何,对偶然发现它的人很有用。
顺便说一下,我在 Chrome 中尝试了相同的 POST 大小限制,但它并没有像在 Firefox 中那样完全挂在那里(请求仍然处于某种挂起状态,“无响应”,但 JS 引擎并且 UI 没有被阻止)。