Path: blob/trunk/third_party/closure/goog/crypt/blobhasher.js
2868 views
// Copyright 2011 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Asynchronous hash computer for the Blob interface.16*17* The Blob interface, part of the HTML5 File API, is supported on Chrome 7+,18* Firefox 4.0 and Opera 11. No Blob interface implementation is expected on19* Internet Explorer 10. Chrome 11, Firefox 5.0 and the subsequent release of20* Opera are supposed to use vendor prefixes due to evolving API, see21* http://dev.w3.org/2006/webapi/FileAPI/ for details.22*23* This implementation currently uses upcoming Chrome and Firefox prefixes,24* plus the original Blob.slice specification, as implemented on Chrome 1025* and Firefox 4.0.26*27*/2829goog.provide('goog.crypt.BlobHasher');30goog.provide('goog.crypt.BlobHasher.EventType');3132goog.require('goog.asserts');33goog.require('goog.events.EventTarget');34goog.require('goog.fs');35goog.require('goog.log');36373839/**40* Construct the hash computer.41*42* @param {!goog.crypt.Hash} hashFn The hash function to use.43* @param {number=} opt_blockSize Processing block size.44* @constructor45* @struct46* @extends {goog.events.EventTarget}47* @final48*/49goog.crypt.BlobHasher = function(hashFn, opt_blockSize) {50goog.crypt.BlobHasher.base(this, 'constructor');5152/**53* The actual hash function.54* @type {!goog.crypt.Hash}55* @private56*/57this.hashFn_ = hashFn;5859/**60* The blob being processed or null if no blob is being processed.61* @type {Blob}62* @private63*/64this.blob_ = null;6566/**67* Computed hash value.68* @type {Array<number>}69* @private70*/71this.hashVal_ = null;7273/**74* Number of bytes already processed.75* @type {number}76* @private77*/78this.bytesProcessed_ = 0;7980/**81* The number of bytes to hash or Infinity for no limit.82* @type {number}83* @private84*/85this.hashingLimit_ = Infinity;8687/**88* Processing block size.89* @type {number}90* @private91*/92this.blockSize_ = opt_blockSize || 5000000;9394/**95* File reader object. Will be null if no chunk is currently being read.96* @type {FileReader}97* @private98*/99this.fileReader_ = null;100101/**102* The logger used by this object.103* @type {goog.log.Logger}104* @private105*/106this.logger_ = goog.log.getLogger('goog.crypt.BlobHasher');107};108goog.inherits(goog.crypt.BlobHasher, goog.events.EventTarget);109110111/**112* Event names for hash computation events113* @enum {string}114*/115goog.crypt.BlobHasher.EventType = {116STARTED: 'started',117PROGRESS: 'progress',118THROTTLED: 'throttled',119COMPLETE: 'complete',120ABORT: 'abort',121ERROR: 'error'122};123124125/**126* Start the hash computation.127* @param {!Blob} blob The blob of data to compute the hash for.128*/129goog.crypt.BlobHasher.prototype.hash = function(blob) {130this.abort();131this.hashFn_.reset();132this.blob_ = blob;133this.hashVal_ = null;134this.bytesProcessed_ = 0;135this.dispatchEvent(goog.crypt.BlobHasher.EventType.STARTED);136137this.processNextBlock_();138};139140141/**142* Sets the maximum number of bytes to hash or Infinity for no limit. Can be143* called before hash() to throttle the hash computation. The hash computation144* can then be continued by repeatedly calling setHashingLimit() with greater145* byte offsets. This is useful if you don't need the hash until some time in146* the future, for example when uploading a file and you don't need the hash147* until the transfer is complete.148* @param {number} byteOffset The byte offset to compute the hash up to.149* Should be a non-negative integer or Infinity for no limit. Negative150* values are not allowed.151*/152goog.crypt.BlobHasher.prototype.setHashingLimit = function(byteOffset) {153goog.asserts.assert(byteOffset >= 0, 'Hashing limit must be non-negative.');154this.hashingLimit_ = byteOffset;155156// Resume processing if a blob is currently being hashed, but no block read157// is currently in progress.158if (this.blob_ && !this.fileReader_) {159this.processNextBlock_();160}161};162163164/**165* Abort hash computation.166*/167goog.crypt.BlobHasher.prototype.abort = function() {168if (this.fileReader_) {169this.fileReader_.abort();170this.fileReader_ = null;171}172173if (this.blob_) {174this.blob_ = null;175this.dispatchEvent(goog.crypt.BlobHasher.EventType.ABORT);176}177};178179180/**181* @return {number} Number of bytes processed so far.182*/183goog.crypt.BlobHasher.prototype.getBytesProcessed = function() {184return this.bytesProcessed_;185};186187188/**189* @return {Array<number>} The computed hash value or null if not ready.190*/191goog.crypt.BlobHasher.prototype.getHash = function() {192return this.hashVal_;193};194195196/**197* Helper function setting up the processing for the next block, or finalizing198* the computation if all blocks were processed.199* @private200*/201goog.crypt.BlobHasher.prototype.processNextBlock_ = function() {202goog.asserts.assert(this.blob_, 'A hash computation must be in progress.');203204if (this.bytesProcessed_ < this.blob_.size) {205if (this.hashingLimit_ <= this.bytesProcessed_) {206// Throttle limit reached. Wait until we are allowed to hash more bytes.207this.dispatchEvent(goog.crypt.BlobHasher.EventType.THROTTLED);208return;209}210211// We have to reset the FileReader every time, otherwise it fails on212// Chrome, including the latest Chrome 12 beta.213// http://code.google.com/p/chromium/issues/detail?id=82346214this.fileReader_ = new FileReader();215this.fileReader_.onload = goog.bind(this.onLoad_, this);216this.fileReader_.onerror = goog.bind(this.onError_, this);217218var endOffset = Math.min(this.hashingLimit_, this.blob_.size);219var size = Math.min(endOffset - this.bytesProcessed_, this.blockSize_);220var chunk = goog.fs.sliceBlob(221this.blob_, this.bytesProcessed_, this.bytesProcessed_ + size);222if (!chunk || chunk.size != size) {223goog.log.error(this.logger_, 'Failed slicing the blob');224this.onError_();225return;226}227228if (this.fileReader_.readAsArrayBuffer) {229this.fileReader_.readAsArrayBuffer(chunk);230} else if (this.fileReader_.readAsBinaryString) {231this.fileReader_.readAsBinaryString(chunk);232} else {233goog.log.error(this.logger_, 'Failed calling the chunk reader');234this.onError_();235}236} else {237this.hashVal_ = this.hashFn_.digest();238this.blob_ = null;239this.dispatchEvent(goog.crypt.BlobHasher.EventType.COMPLETE);240}241};242243244/**245* Handle processing block loaded.246* @private247*/248goog.crypt.BlobHasher.prototype.onLoad_ = function() {249goog.log.info(this.logger_, 'Successfully loaded a chunk');250251var array = null;252if (this.fileReader_.result instanceof Array ||253goog.isString(this.fileReader_.result)) {254array = this.fileReader_.result;255} else if (256goog.global['ArrayBuffer'] && goog.global['Uint8Array'] &&257this.fileReader_.result instanceof ArrayBuffer) {258array = new Uint8Array(this.fileReader_.result);259}260if (!array) {261goog.log.error(this.logger_, 'Failed reading the chunk');262this.onError_();263return;264}265266this.hashFn_.update(array);267this.bytesProcessed_ += array.length;268this.fileReader_ = null;269this.dispatchEvent(goog.crypt.BlobHasher.EventType.PROGRESS);270271this.processNextBlock_();272};273274275/**276* Handles error.277* @private278*/279goog.crypt.BlobHasher.prototype.onError_ = function() {280this.fileReader_ = null;281this.blob_ = null;282this.dispatchEvent(goog.crypt.BlobHasher.EventType.ERROR);283};284285286