Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / metadata / byte_reader.js
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8  * @constructor
9  * @param {ArrayBuffer} arrayBuffer  // TODO(JSDOC).
10  * @param {number=} opt_offset  // TODO(JSDOC).
11  * @param {number=} opt_length  // TODO(JSDOC).
12  */
13 function ByteReader(arrayBuffer, opt_offset, opt_length) {
14   opt_offset = opt_offset || 0;
15   opt_length = opt_length || (arrayBuffer.byteLength - opt_offset);
16   this.view_ = new DataView(arrayBuffer, opt_offset, opt_length);
17   this.pos_ = 0;
18   this.seekStack_ = [];
19   this.setByteOrder(ByteReader.BIG_ENDIAN);
20 }
21
22 // Static constants and methods.
23
24 /**
25  * Intel, 0x1234 is [0x34, 0x12]
26  * @const
27  * @type {number}
28  */
29 ByteReader.LITTLE_ENDIAN = 0;
30 /**
31  * Motorola, 0x1234 is [0x12, 0x34]
32  * @const
33  * @type {number}
34  */
35 ByteReader.BIG_ENDIAN = 1;
36
37 /**
38  * Seek relative to the beginning of the buffer.
39  * @const
40  * @type {number}
41  */
42 ByteReader.SEEK_BEG = 0;
43 /**
44  * Seek relative to the current position.
45  * @const
46  * @type {number}
47  */
48 ByteReader.SEEK_CUR = 1;
49 /**
50  * Seek relative to the end of the buffer.
51  * @const
52  * @type {number}
53  */
54 ByteReader.SEEK_END = 2;
55
56 /**
57  * Throw an error if (0 > pos >= end) or if (pos + size > end).
58  *
59  * Static utility function.
60  *
61  * @param {number} pos  // TODO(JSDOC).
62  * @param {number} size  // TODO(JSDOC).
63  * @param {number} end  // TODO(JSDOC).
64  */
65 ByteReader.validateRead = function(pos, size, end) {
66   if (pos < 0 || pos >= end)
67     throw new Error('Invalid read position');
68
69   if (pos + size > end)
70     throw new Error('Read past end of buffer');
71 };
72
73 /**
74  * Read as a sequence of characters, returning them as a single string.
75  *
76  * This is a static utility function.  There is a member function with the
77  * same name which side-effects the current read position.
78  *
79  * @param {DataView} dataView  // TODO(JSDOC).
80  * @param {number} pos  // TODO(JSDOC).
81  * @param {number} size  // TODO(JSDOC).
82  * @param {number=} opt_end  // TODO(JSDOC).
83  * @return {string}  // TODO(JSDOC).
84  */
85 ByteReader.readString = function(dataView, pos, size, opt_end) {
86   ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
87
88   var codes = [];
89
90   for (var i = 0; i < size; ++i)
91     codes.push(dataView.getUint8(pos + i));
92
93   return String.fromCharCode.apply(null, codes);
94 };
95
96 /**
97  * Read as a sequence of characters, returning them as a single string.
98  *
99  * This is a static utility function.  There is a member function with the
100  * same name which side-effects the current read position.
101  *
102  * @param {DataView} dataView  // TODO(JSDOC).
103  * @param {number} pos  // TODO(JSDOC).
104  * @param {number} size  // TODO(JSDOC).
105  * @param {number=} opt_end  // TODO(JSDOC).
106  * @return {string}  // TODO(JSDOC).
107  */
108 ByteReader.readNullTerminatedString = function(dataView, pos, size, opt_end) {
109   ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
110
111   var codes = [];
112
113   for (var i = 0; i < size; ++i) {
114     var code = dataView.getUint8(pos + i);
115     if (code == 0) break;
116     codes.push(code);
117   }
118
119   return String.fromCharCode.apply(null, codes);
120 };
121
122 /**
123  * Read as a sequence of UTF16 characters, returning them as a single string.
124  *
125  * This is a static utility function.  There is a member function with the
126  * same name which side-effects the current read position.
127  *
128  * @param {DataView} dataView  // TODO(JSDOC).
129  * @param {number} pos  // TODO(JSDOC).
130  * @param {boolean} bom  // TODO(JSDOC).
131  * @param {number} size  // TODO(JSDOC).
132  * @param {number=} opt_end  // TODO(JSDOC).
133  * @return {string}  // TODO(JSDOC).
134  */
135 ByteReader.readNullTerminatedStringUTF16 = function(
136     dataView, pos, bom, size, opt_end) {
137   ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
138
139   var littleEndian = false;
140   var start = 0;
141
142   if (bom) {
143     littleEndian = (dataView.getUint8(pos) == 0xFF);
144     start = 2;
145   }
146
147   var codes = [];
148
149   for (var i = start; i < size; i += 2) {
150     var code = dataView.getUint16(pos + i, littleEndian);
151     if (code == 0) break;
152     codes.push(code);
153   }
154
155   return String.fromCharCode.apply(null, codes);
156 };
157
158 /**
159  * @const
160  * @type {Array.<string>}
161  * @private
162  */
163 ByteReader.base64Alphabet_ =
164     ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/').
165     split('');
166
167 /**
168  * Read as a sequence of bytes, returning them as a single base64 encoded
169  * string.
170  *
171  * This is a static utility function.  There is a member function with the
172  * same name which side-effects the current read position.
173  *
174  * @param {DataView} dataView  // TODO(JSDOC).
175  * @param {number} pos  // TODO(JSDOC).
176  * @param {number} size  // TODO(JSDOC).
177  * @param {number=} opt_end  // TODO(JSDOC).
178  * @return {string}  // TODO(JSDOC).
179  */
180 ByteReader.readBase64 = function(dataView, pos, size, opt_end) {
181   ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
182
183   var rv = [];
184   var chars = [];
185   var padding = 0;
186
187   for (var i = 0; i < size; /* incremented inside */) {
188     var bits = dataView.getUint8(pos + (i++)) << 16;
189
190     if (i < size) {
191       bits |= dataView.getUint8(pos + (i++)) << 8;
192
193       if (i < size) {
194         bits |= dataView.getUint8(pos + (i++));
195       } else {
196         padding = 1;
197       }
198     } else {
199       padding = 2;
200     }
201
202     chars[3] = ByteReader.base64Alphabet_[bits & 63];
203     chars[2] = ByteReader.base64Alphabet_[(bits >> 6) & 63];
204     chars[1] = ByteReader.base64Alphabet_[(bits >> 12) & 63];
205     chars[0] = ByteReader.base64Alphabet_[(bits >> 18) & 63];
206
207     rv.push.apply(rv, chars);
208   }
209
210   if (padding > 0)
211     rv[rv.length - 1] = '=';
212   if (padding > 1)
213     rv[rv.length - 2] = '=';
214
215   return rv.join('');
216 };
217
218 /**
219  * Read as an image encoded in a data url.
220  *
221  * This is a static utility function.  There is a member function with the
222  * same name which side-effects the current read position.
223  *
224  * @param {DataView} dataView  // TODO(JSDOC).
225  * @param {number} pos  // TODO(JSDOC).
226  * @param {number} size  // TODO(JSDOC).
227  * @param {number=} opt_end  // TODO(JSDOC).
228  * @return {string}  // TODO(JSDOC).
229  */
230 ByteReader.readImage = function(dataView, pos, size, opt_end) {
231   opt_end = opt_end || dataView.byteLength;
232   ByteReader.validateRead(pos, size, opt_end);
233
234   // Two bytes is enough to identify the mime type.
235   var prefixToMime = {
236      '\x89P' : 'png',
237      '\xFF\xD8' : 'jpeg',
238      'BM' : 'bmp',
239      'GI' : 'gif'
240   };
241
242   var prefix = ByteReader.readString(dataView, pos, 2, opt_end);
243   var mime = prefixToMime[prefix] ||
244       dataView.getUint16(pos, false).toString(16);  // For debugging.
245
246   var b64 = ByteReader.readBase64(dataView, pos, size, opt_end);
247   return 'data:image/' + mime + ';base64,' + b64;
248 };
249
250 // Instance methods.
251
252 /**
253  * Return true if the requested number of bytes can be read from the buffer.
254  *
255  * @param {number} size  // TODO(JSDOC).
256  * @return {boolean}  // TODO(JSDOC).
257  */
258 ByteReader.prototype.canRead = function(size) {
259    return this.pos_ + size <= this.view_.byteLength;
260 };
261
262 /**
263  * Return true if the current position is past the end of the buffer.
264  * @return {boolean}  // TODO(JSDOC).
265  */
266 ByteReader.prototype.eof = function() {
267   return this.pos_ >= this.view_.byteLength;
268 };
269
270 /**
271  * Return true if the current position is before the beginning of the buffer.
272  * @return {boolean}  // TODO(JSDOC).
273  */
274 ByteReader.prototype.bof = function() {
275   return this.pos_ < 0;
276 };
277
278 /**
279  * Return true if the current position is outside the buffer.
280  * @return {boolean}  // TODO(JSDOC).
281  */
282 ByteReader.prototype.beof = function() {
283   return this.pos_ >= this.view_.byteLength || this.pos_ < 0;
284 };
285
286 /**
287  * Set the expected byte ordering for future reads.
288  * @param {number} order  // TODO(JSDOC).
289  */
290 ByteReader.prototype.setByteOrder = function(order) {
291   this.littleEndian_ = order == ByteReader.LITTLE_ENDIAN;
292 };
293
294 /**
295  * Throw an error if the reader is at an invalid position, or if a read a read
296  * of |size| would put it in one.
297  *
298  * You may optionally pass opt_end to override what is considered to be the
299  * end of the buffer.
300  *
301  * @param {number} size  // TODO(JSDOC).
302  * @param {number=} opt_end  // TODO(JSDOC).
303  */
304 ByteReader.prototype.validateRead = function(size, opt_end) {
305   if (typeof opt_end == 'undefined')
306     opt_end = this.view_.byteLength;
307
308   ByteReader.validateRead(this.view_, this.pos_, size, opt_end);
309 };
310
311 /**
312  * @param {number} width  // TODO(JSDOC).
313  * @param {boolean=} opt_signed  // TODO(JSDOC).
314  * @param {number=} opt_end  // TODO(JSDOC).
315  * @return {string}  // TODO(JSDOC).
316  */
317 ByteReader.prototype.readScalar = function(width, opt_signed, opt_end) {
318   var method = opt_signed ? 'getInt' : 'getUint';
319
320   switch (width) {
321     case 1:
322       method += '8';
323       break;
324
325     case 2:
326       method += '16';
327       break;
328
329     case 4:
330       method += '32';
331       break;
332
333     case 8:
334       method += '64';
335       break;
336
337     default:
338       throw new Error('Invalid width: ' + width);
339       break;
340   }
341
342   this.validateRead(width, opt_end);
343   var rv = this.view_[method](this.pos_, this.littleEndian_);
344   this.pos_ += width;
345   return rv;
346 };
347
348 /**
349  * Read as a sequence of characters, returning them as a single string.
350  *
351  * Adjusts the current position on success.  Throws an exception if the
352  * read would go past the end of the buffer.
353  *
354  * @param {number} size  // TODO(JSDOC).
355  * @param {number=} opt_end  // TODO(JSDOC).
356  * @return {string}  // TODO(JSDOC).
357  */
358 ByteReader.prototype.readString = function(size, opt_end) {
359   var rv = ByteReader.readString(this.view_, this.pos_, size, opt_end);
360   this.pos_ += size;
361   return rv;
362 };
363
364
365 /**
366  * Read as a sequence of characters, returning them as a single string.
367  *
368  * Adjusts the current position on success.  Throws an exception if the
369  * read would go past the end of the buffer.
370  *
371  * @param {number} size  // TODO(JSDOC).
372  * @param {number=} opt_end  // TODO(JSDOC).
373  * @return {string}  // TODO(JSDOC).
374  */
375 ByteReader.prototype.readNullTerminatedString = function(size, opt_end) {
376   var rv = ByteReader.readNullTerminatedString(this.view_,
377                                                this.pos_,
378                                                size,
379                                                opt_end);
380   this.pos_ += rv.length;
381
382   if (rv.length < size) {
383     // If we've stopped reading because we found '0' but didn't hit size limit
384     // then we should skip additional '0' character
385     this.pos_++;
386   }
387
388   return rv;
389 };
390
391
392 /**
393  * Read as a sequence of UTF16 characters, returning them as a single string.
394  *
395  * Adjusts the current position on success.  Throws an exception if the
396  * read would go past the end of the buffer.
397  *
398  * @param {boolean} bom  // TODO(JSDOC).
399  * @param {number} size  // TODO(JSDOC).
400  * @param {number=} opt_end  // TODO(JSDOC).
401  * @return {string}  // TODO(JSDOC).
402  */
403 ByteReader.prototype.readNullTerminatedStringUTF16 =
404     function(bom, size, opt_end) {
405   var rv = ByteReader.readNullTerminatedStringUTF16(
406       this.view_, this.pos_, bom, size, opt_end);
407
408   if (bom) {
409     // If the BOM word was present advance the position.
410     this.pos_ += 2;
411   }
412
413   this.pos_ += rv.length;
414
415   if (rv.length < size) {
416     // If we've stopped reading because we found '0' but didn't hit size limit
417     // then we should skip additional '0' character
418     this.pos_ += 2;
419   }
420
421   return rv;
422 };
423
424
425 /**
426  * Read as an array of numbers.
427  *
428  * Adjusts the current position on success.  Throws an exception if the
429  * read would go past the end of the buffer.
430  *
431  * @param {number} size  // TODO(JSDOC).
432  * @param {number=} opt_end  // TODO(JSDOC).
433  * @param {function(new:Array.<*>)=} opt_arrayConstructor  // TODO(JSDOC).
434  * @return {Array.<*>}  // TODO(JSDOC).
435  */
436 ByteReader.prototype.readSlice = function(size, opt_end,
437                                           opt_arrayConstructor) {
438   this.validateRead(size, opt_end);
439
440   var arrayConstructor = opt_arrayConstructor || Uint8Array;
441   var slice = new arrayConstructor(
442       this.view_.buffer, this.view_.byteOffset + this.pos, size);
443   this.pos_ += size;
444
445   return slice;
446 };
447
448 /**
449  * Read as a sequence of bytes, returning them as a single base64 encoded
450  * string.
451  *
452  * Adjusts the current position on success.  Throws an exception if the
453  * read would go past the end of the buffer.
454  *
455  * @param {number} size  // TODO(JSDOC).
456  * @param {number=} opt_end  // TODO(JSDOC).
457  * @return {string}  // TODO(JSDOC).
458  */
459 ByteReader.prototype.readBase64 = function(size, opt_end) {
460   var rv = ByteReader.readBase64(this.view_, this.pos_, size, opt_end);
461   this.pos_ += size;
462   return rv;
463 };
464
465 /**
466  * Read an image returning it as a data url.
467  *
468  * Adjusts the current position on success.  Throws an exception if the
469  * read would go past the end of the buffer.
470  *
471  * @param {number} size  // TODO(JSDOC).
472  * @param {number=} opt_end  // TODO(JSDOC).
473  * @return {string}  // TODO(JSDOC).
474  */
475 ByteReader.prototype.readImage = function(size, opt_end) {
476   var rv = ByteReader.readImage(this.view_, this.pos_, size, opt_end);
477   this.pos_ += size;
478   return rv;
479 };
480
481 /**
482  * Seek to a give position relative to opt_seekStart.
483  *
484  * @param {number} pos  // TODO(JSDOC).
485  * @param {number=} opt_seekStart  // TODO(JSDOC).
486  * @param {number=} opt_end  // TODO(JSDOC).
487  */
488 ByteReader.prototype.seek = function(pos, opt_seekStart, opt_end) {
489   opt_end = opt_end || this.view_.byteLength;
490
491   var newPos;
492   if (opt_seekStart == ByteReader.SEEK_CUR) {
493     newPos = this.pos_ + pos;
494   } else if (opt_seekStart == ByteReader.SEEK_END) {
495     newPos = opt_end + pos;
496   } else {
497     newPos = pos;
498   }
499
500   if (newPos < 0 || newPos > this.view_.byteLength)
501     throw new Error('Seek outside of buffer: ' + (newPos - opt_end));
502
503   this.pos_ = newPos;
504 };
505
506 /**
507  * Seek to a given position relative to opt_seekStart, saving the current
508  * position.
509  *
510  * Recover the current position with a call to seekPop.
511  *
512  * @param {number} pos  // TODO(JSDOC).
513  * @param {number=} opt_seekStart  // TODO(JSDOC).
514  */
515 ByteReader.prototype.pushSeek = function(pos, opt_seekStart) {
516   var oldPos = this.pos_;
517   this.seek(pos, opt_seekStart);
518   // Alter the seekStack_ after the call to seek(), in case it throws.
519   this.seekStack_.push(oldPos);
520 };
521
522 /**
523  * Undo a previous seekPush.
524  */
525 ByteReader.prototype.popSeek = function() {
526   this.seek(this.seekStack_.pop());
527 };
528
529 /**
530  * Return the current read position.
531  * @return {number}  // TODO(JSDOC).
532  */
533 ByteReader.prototype.tell = function() {
534   return this.pos_;
535 };