是否可以使用FileReader API和onprogress事件访问HTML5中的数据?
如果是这样,是否有“在线”版本的MD5或其他快速哈希算法,以便我可以在文件完全读取之前开始计算哈希值?
我想在客户端计算哈希值,然后在发送整个文件之前将哈希值发送到服务器,以便在启动文件上载之前检查重复项。
此时我并不关心对旧浏览器的支持。
编辑:我认识到哈希冲突并不能保证重复文件,唯一可靠的方法是逐字节检查,这意味着无论如何我都必须上传文件。概率足够低,我愿意冒这个风险;最坏的情况我提示用户说“这个文件已经出现在服务器上了;你确定要上传吗?”
答案 0 :(得分:2)
是否存在MD5或其他快速哈希算法的“在线”版本,以便我可以在文件完全读取之前开始计算哈希值?
是的,如果您想使用SHA,可以使用sjcl。 sjcl没有对MD5的本机支持,所以你必须自己编写它(虽然我确定其他人已经完成了它)。 CryptoJS具有本机MD5支持但速度明显较慢。
我认识到哈希冲突不能保证重复文件[...]概率足够低,我愿意冒这个风险;
概率足够低,以至于流星撞击地球并终止人类生命的机会(因此完全不需要散列)比自然发生的碰撞更有可能。当然,除非用户故意制造碰撞,否则MD5的碰撞阻力会被打破。
以下是我认为您要完成的实时demo ,减去“访问数据来源”部分。我不确定这是否可能。我很久以前写过这篇文章它使用的是CryptoJS,因此性能不是那么好但是它完成了工作。重要的块是:
function handleFileSelect(evt)
{
evt.stopPropagation();
evt.preventDefault();
var files = evt.target.files || evt.dataTransfer.files; // FileList object.
for (var i=0, file; file = files[i]; ++i)
{
// this creates the FileReader and reads stuff as text
var fr = new FileReader();
fr.onload = (function(theFile) {
return function (e) {
var hashes = parsePseudoBuffer(e.target.result);
document.getElementById('output').innerHTML += '<br />' + theFile.name + '<br />'
+ 'MD5: ' + hashes.md5 + '<br />' + 'SHA1: ' + hashes.sha1 + '<br />' ;
};
}) (file);
fr.readAsArrayBuffer(file); // ArrayBuffer
}
}
function parsePseudoBuffer(result)
{
var buffs = new Uint8Array(result); // buffer thingie
var md5 = CryptoJS.algo.MD5.create();
var sha1 = CryptoJS.algo.SHA1.create();
var bufsize = 8 * 1024; // 8K buffer
for (var bstart=0, bend=bufsize; bstart < buffs.length; bstart+=bufsize, bend+= bufsize)
{
var data = CryptoJS.lib.WordArray.create(buffs.subarray(bstart, bend));
md5.update(data);
sha1.update(data);
}
md5 = md5.finalize();
sha1 = sha1.finalize();
return {'md5': md5, 'sha1': sha1} ;
}
答案 1 :(得分:2)
我做了一些实验。看起来我们可以通过利用reader对象上不完整的onprogress
来获取result
事件中的最后一个块读取。如果我们使用reader.readAsArrayBuffer
(仅限Chrome?)或reader.readAsBinaryString
,它似乎只能访问。字符串的问题在于,如果你想要占用它的一大块,你必须对其进行切片以制作副本(非常慢)。
ArrayBuffers有一个.subarray
方法,可以在缓冲区中创建view,而无需复制任何数据。这正是我们想要的。但是,它似乎在基类上不可用;并且从文档中不清楚当我们使用这个缓冲区构造派生类(例如Uint8Array
)时会发生什么,但考虑到原始缓冲区可以通过readonly属性访问,我假设它不是复制。
sjcl和CryptoJS都方便地使用.update
方法,这些方法将接收此ArrayBufferView,以便您可以动态更新哈希。因此,我提出了以下解决方案(使用jQuery,下划线和sjcl):
$(document).on('drop', function(dropEvent) {
dropEvent.preventDefault();
_.each(dropEvent.originalEvent.dataTransfer.files, function(file) {
var reader = new FileReader();
var pos = 0;
var hash = new sjcl.hash.sha256();
reader.onprogress = function(progress) {
var chunk = new Uint8Array(reader.result, pos, progress.loaded - pos);
pos = progress.loaded;
hash.update(chunk);
if(progress.lengthComputable) {
console.log((progress.loaded/progress.total*100).toFixed(1)+'%');
}
};
reader.onload = function() {
var chunk = new Uint8Array(reader.result, pos);
if(chunk.length > 0) hash.update(chunk);
console.log(sjcl.codec.hex.fromBits(hash.finalize()));
};
reader.readAsArrayBuffer(file);
});
});
请注意,此解决方案目前仅适用于Chrome,速度相当慢。我认为sjcl不只是对文件进行哈希处理,而且它是关键加强它,这实际上不是我想要的。稍后会调查。