blobhasher.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  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 on
  19. * Internet Explorer 10. Chrome 11, Firefox 5.0 and the subsequent release of
  20. * Opera are supposed to use vendor prefixes due to evolving API, see
  21. * 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 10
  25. * and Firefox 4.0.
  26. *
  27. */
  28. goog.provide('goog.crypt.BlobHasher');
  29. goog.provide('goog.crypt.BlobHasher.EventType');
  30. goog.require('goog.asserts');
  31. goog.require('goog.events.EventTarget');
  32. goog.require('goog.fs');
  33. goog.require('goog.log');
  34. /**
  35. * Construct the hash computer.
  36. *
  37. * @param {!goog.crypt.Hash} hashFn The hash function to use.
  38. * @param {number=} opt_blockSize Processing block size.
  39. * @constructor
  40. * @struct
  41. * @extends {goog.events.EventTarget}
  42. * @final
  43. */
  44. goog.crypt.BlobHasher = function(hashFn, opt_blockSize) {
  45. goog.crypt.BlobHasher.base(this, 'constructor');
  46. /**
  47. * The actual hash function.
  48. * @type {!goog.crypt.Hash}
  49. * @private
  50. */
  51. this.hashFn_ = hashFn;
  52. /**
  53. * The blob being processed or null if no blob is being processed.
  54. * @type {Blob}
  55. * @private
  56. */
  57. this.blob_ = null;
  58. /**
  59. * Computed hash value.
  60. * @type {Array<number>}
  61. * @private
  62. */
  63. this.hashVal_ = null;
  64. /**
  65. * Number of bytes already processed.
  66. * @type {number}
  67. * @private
  68. */
  69. this.bytesProcessed_ = 0;
  70. /**
  71. * The number of bytes to hash or Infinity for no limit.
  72. * @type {number}
  73. * @private
  74. */
  75. this.hashingLimit_ = Infinity;
  76. /**
  77. * Processing block size.
  78. * @type {number}
  79. * @private
  80. */
  81. this.blockSize_ = opt_blockSize || 5000000;
  82. /**
  83. * File reader object. Will be null if no chunk is currently being read.
  84. * @type {FileReader}
  85. * @private
  86. */
  87. this.fileReader_ = null;
  88. /**
  89. * The logger used by this object.
  90. * @type {goog.log.Logger}
  91. * @private
  92. */
  93. this.logger_ = goog.log.getLogger('goog.crypt.BlobHasher');
  94. };
  95. goog.inherits(goog.crypt.BlobHasher, goog.events.EventTarget);
  96. /**
  97. * Event names for hash computation events
  98. * @enum {string}
  99. */
  100. goog.crypt.BlobHasher.EventType = {
  101. STARTED: 'started',
  102. PROGRESS: 'progress',
  103. THROTTLED: 'throttled',
  104. COMPLETE: 'complete',
  105. ABORT: 'abort',
  106. ERROR: 'error'
  107. };
  108. /**
  109. * Start the hash computation.
  110. * @param {!Blob} blob The blob of data to compute the hash for.
  111. */
  112. goog.crypt.BlobHasher.prototype.hash = function(blob) {
  113. this.abort();
  114. this.hashFn_.reset();
  115. this.blob_ = blob;
  116. this.hashVal_ = null;
  117. this.bytesProcessed_ = 0;
  118. this.dispatchEvent(goog.crypt.BlobHasher.EventType.STARTED);
  119. this.processNextBlock_();
  120. };
  121. /**
  122. * Sets the maximum number of bytes to hash or Infinity for no limit. Can be
  123. * called before hash() to throttle the hash computation. The hash computation
  124. * can then be continued by repeatedly calling setHashingLimit() with greater
  125. * byte offsets. This is useful if you don't need the hash until some time in
  126. * the future, for example when uploading a file and you don't need the hash
  127. * until the transfer is complete.
  128. * @param {number} byteOffset The byte offset to compute the hash up to.
  129. * Should be a non-negative integer or Infinity for no limit. Negative
  130. * values are not allowed.
  131. */
  132. goog.crypt.BlobHasher.prototype.setHashingLimit = function(byteOffset) {
  133. goog.asserts.assert(byteOffset >= 0, 'Hashing limit must be non-negative.');
  134. this.hashingLimit_ = byteOffset;
  135. // Resume processing if a blob is currently being hashed, but no block read
  136. // is currently in progress.
  137. if (this.blob_ && !this.fileReader_) {
  138. this.processNextBlock_();
  139. }
  140. };
  141. /**
  142. * Abort hash computation.
  143. */
  144. goog.crypt.BlobHasher.prototype.abort = function() {
  145. if (this.fileReader_) {
  146. this.fileReader_.abort();
  147. this.fileReader_ = null;
  148. }
  149. if (this.blob_) {
  150. this.blob_ = null;
  151. this.dispatchEvent(goog.crypt.BlobHasher.EventType.ABORT);
  152. }
  153. };
  154. /**
  155. * @return {number} Number of bytes processed so far.
  156. */
  157. goog.crypt.BlobHasher.prototype.getBytesProcessed = function() {
  158. return this.bytesProcessed_;
  159. };
  160. /**
  161. * @return {Array<number>} The computed hash value or null if not ready.
  162. */
  163. goog.crypt.BlobHasher.prototype.getHash = function() {
  164. return this.hashVal_;
  165. };
  166. /**
  167. * Helper function setting up the processing for the next block, or finalizing
  168. * the computation if all blocks were processed.
  169. * @private
  170. */
  171. goog.crypt.BlobHasher.prototype.processNextBlock_ = function() {
  172. goog.asserts.assert(this.blob_, 'A hash computation must be in progress.');
  173. if (this.bytesProcessed_ < this.blob_.size) {
  174. if (this.hashingLimit_ <= this.bytesProcessed_) {
  175. // Throttle limit reached. Wait until we are allowed to hash more bytes.
  176. this.dispatchEvent(goog.crypt.BlobHasher.EventType.THROTTLED);
  177. return;
  178. }
  179. // We have to reset the FileReader every time, otherwise it fails on
  180. // Chrome, including the latest Chrome 12 beta.
  181. // http://code.google.com/p/chromium/issues/detail?id=82346
  182. this.fileReader_ = new FileReader();
  183. this.fileReader_.onload = goog.bind(this.onLoad_, this);
  184. this.fileReader_.onerror = goog.bind(this.onError_, this);
  185. var endOffset = Math.min(this.hashingLimit_, this.blob_.size);
  186. var size = Math.min(endOffset - this.bytesProcessed_, this.blockSize_);
  187. var chunk = goog.fs.sliceBlob(
  188. this.blob_, this.bytesProcessed_, this.bytesProcessed_ + size);
  189. if (!chunk || chunk.size != size) {
  190. goog.log.error(this.logger_, 'Failed slicing the blob');
  191. this.onError_();
  192. return;
  193. }
  194. if (this.fileReader_.readAsArrayBuffer) {
  195. this.fileReader_.readAsArrayBuffer(chunk);
  196. } else if (this.fileReader_.readAsBinaryString) {
  197. this.fileReader_.readAsBinaryString(chunk);
  198. } else {
  199. goog.log.error(this.logger_, 'Failed calling the chunk reader');
  200. this.onError_();
  201. }
  202. } else {
  203. this.hashVal_ = this.hashFn_.digest();
  204. this.blob_ = null;
  205. this.dispatchEvent(goog.crypt.BlobHasher.EventType.COMPLETE);
  206. }
  207. };
  208. /**
  209. * Handle processing block loaded.
  210. * @private
  211. */
  212. goog.crypt.BlobHasher.prototype.onLoad_ = function() {
  213. goog.log.info(this.logger_, 'Successfully loaded a chunk');
  214. var array = null;
  215. if (this.fileReader_.result instanceof Array ||
  216. goog.isString(this.fileReader_.result)) {
  217. array = this.fileReader_.result;
  218. } else if (
  219. goog.global['ArrayBuffer'] && goog.global['Uint8Array'] &&
  220. this.fileReader_.result instanceof ArrayBuffer) {
  221. array = new Uint8Array(this.fileReader_.result);
  222. }
  223. if (!array) {
  224. goog.log.error(this.logger_, 'Failed reading the chunk');
  225. this.onError_();
  226. return;
  227. }
  228. this.hashFn_.update(array);
  229. this.bytesProcessed_ += array.length;
  230. this.fileReader_ = null;
  231. this.dispatchEvent(goog.crypt.BlobHasher.EventType.PROGRESS);
  232. this.processNextBlock_();
  233. };
  234. /**
  235. * Handles error.
  236. * @private
  237. */
  238. goog.crypt.BlobHasher.prototype.onError_ = function() {
  239. this.fileReader_ = null;
  240. this.blob_ = null;
  241. this.dispatchEvent(goog.crypt.BlobHasher.EventType.ERROR);
  242. };