Index: chrome/browser/resources/file_manager/js/exif_parser.js |
diff --git a/chrome/browser/resources/file_manager/js/exif_parser.js b/chrome/browser/resources/file_manager/js/exif_parser.js |
index bc1e69e96e9ec49c2d2486126d3ff0705873ff9b..d1018be01bf0c88d4f2c17dcf98424853b1cbd3b 100644 |
--- a/chrome/browser/resources/file_manager/js/exif_parser.js |
+++ b/chrome/browser/resources/file_manager/js/exif_parser.js |
@@ -29,170 +29,177 @@ function ExifParser(parent) { |
ExifParser.prototype = {__proto__: ImageParser.prototype}; |
ExifParser.prototype.parse = function(file, callback, errorCallback) { |
+ var metadata = this.createDefaultMetadata(); |
+ this.requestSlice(file, callback, errorCallback, metadata, 0); |
+}; |
+ |
+ExifParser.prototype.requestSlice = function ( |
+ file, callback, errorCallback, metadata, filePos, opt_length) { |
+ // Read at least 1Kb so that we do not issue too many read requests. |
+ opt_length = Math.max(1024, opt_length || 0); |
+ |
var self = this; |
- var currentStep = -1; |
+ var reader = new FileReader(); |
+ reader.onerror = errorCallback; |
+ reader.onload = function() { self.parseSlice( |
+ file, callback, errorCallback, metadata, filePos, reader.result); |
+ }; |
+ reader.readAsArrayBuffer(file.webkitSlice(filePos, filePos + opt_length)); |
+}; |
- function nextStep(var_args) { |
- self.vlog('exif nextStep: ' + steps[currentStep + 1].name); |
- try { |
- steps[++currentStep].apply(null, arguments); |
- } catch(e) { |
- onError(e.stack || e.toString()); |
+ExifParser.prototype.parseSlice = function( |
+ file, callback, errorCallback, metadata, filePos, buf) { |
+ try { |
+ var br = new ByteReader(buf); |
+ |
+ if (!br.canRead(4)) { |
+ // We never ask for less than 4 bytes. This can only mean we reached EOF. |
+ throw new Error('Unexpected EOF @' + (filePos + buf.byteLength)); |
} |
- } |
- function onError(err) { |
- errorCallback(err, steps[currentStep].name); |
- } |
+ if (filePos == 0) { |
+ // First slice, check for the SOI mark. |
+ var firstMark = this.readMark(br); |
+ if (firstMark != EXIF_MARK_SOI) |
+ throw new Error('Invalid file header: ' + firstMark.toString(16)); |
+ } |
- var steps = |
- [ // Step one, read the file header into a byte array. |
- function readHeader(file) { |
- var reader = new FileReader(); |
- reader.onerror = onError; |
- reader.onload = function(event) { nextStep(file, reader.result) }; |
- reader.readAsArrayBuffer(file.webkitSlice(0, 1024)); |
- }, |
- |
- // Step two, find the exif marker and read all exif data. |
- function findExif(file, buf) { |
- var br = new ByteReader(buf); |
- var mark = self.readMark(br); |
- if (mark != EXIF_MARK_SOI) |
- return onError('Invalid file header: ' + mark.toString(16)); |
- |
- while (true) { |
- if (mark == EXIF_MARK_SOS || br.eof()) { |
- return onError('Unable to find EXIF or SOF marker'); |
- } |
- |
- mark = self.readMark(br); |
- if (mark == EXIF_MARK_SOF) { |
- // If we reached this section first then there is no EXIF data. |
- // Extract image dimensions and return. |
- |
- // TODO(kaznacheev) Here we are assuming that SOF section lies within |
- // first 1024 bytes. This must be true for any normal JPEG file |
- // with no EXIF data. Still might want to handle this more carefully. |
- if (br.tell() + 7 < buf.byteLength) { |
- br.seek(3, ByteReader.SEEK_CUR); |
- var metadata = self.createDefaultMetadata(); |
- metadata.width = br.readScalar(2); |
- metadata.height = br.readScalar(2); |
- callback(metadata); |
- return; |
- } |
- } |
- if (mark == EXIF_MARK_EXIF) { |
- var length = self.readMarkLength(br); |
- |
- // Offsets inside the EXIF block are based after this bit of |
- // magic, so we verify and discard it here, before exif parsing, |
- // to make offset calculations simpler. |
- var magic = br.readString(6); |
- if (magic != 'Exif\0\0') |
- return onError('Invalid EXIF magic: ' + magic.toString(16)); |
- |
- var pos = br.tell(); |
- var reader = new FileReader(); |
- reader.onerror = onError; |
- reader.onload = function(event) { nextStep(file, reader.result) }; |
- reader.readAsArrayBuffer(file.webkitSlice(pos, pos + length - 6)); |
- return; |
- } |
- |
- self.skipMarkData(br); |
- } |
- }, |
- |
- // Step three, parse the exif data. |
- function readDirectories(file, buf) { |
- var br = new ByteReader(buf); |
- var order = br.readScalar(2); |
- if (order == EXIF_ALIGN_LITTLE) { |
- br.setByteOrder(ByteReader.LITTLE_ENDIAN); |
- } else if (order != EXIF_ALIGN_BIG) { |
- return onError('Invalid alignment value: ' + order.toString(16)); |
- } |
+ var self = this; |
+ function reread(opt_offset, opt_bytes) { |
+ self.requestSlice(file, callback, errorCallback, metadata, |
+ filePos + br.tell() + (opt_offset || 0), opt_bytes); |
+ } |
- var tag = br.readScalar(2); |
- if (tag != EXIF_TAG_TIFF) |
- return onError('Invalid TIFF tag: ' + tag.toString(16)); |
- |
- var metadata = self.createDefaultMetadata(); |
- metadata.littleEndian = (order == EXIF_ALIGN_LITTLE); |
- metadata.ifd = { |
- image: {}, |
- thumbnail: {} |
- }; |
- var directoryOffset = br.readScalar(4); |
- |
- // Image directory. |
- self.vlog('Read image directory.'); |
- br.seek(directoryOffset); |
- directoryOffset = self.readDirectory(br, metadata.ifd.image); |
- metadata.imageTransform = self.parseOrientation(metadata.ifd.image); |
- |
- // Thumbnail Directory chained from the end of the image directory. |
- if (directoryOffset) { |
- self.vlog('Read thumbnail directory.'); |
- br.seek(directoryOffset); |
- self.readDirectory(br, metadata.ifd.thumbnail); |
- // If no thumbnail orientation is encoded, assume same orientation as |
- // the primary image. |
- metadata.thumbnailTransform = |
- self.parseOrientation(metadata.ifd.thumbnail) || |
- metadata.imageTransform; |
+ while (true) { |
+ if (!br.canRead(4)) { |
+ // Cannot read the mark and the length, request a minimum-size slice. |
+ reread(); |
+ return; |
} |
- // EXIF Directory may be specified as a tag in the image directory. |
- if (EXIF_TAG_EXIFDATA in metadata.ifd.image) { |
- self.vlog('Read EXIF directory.'); |
- directoryOffset = metadata.ifd.image[EXIF_TAG_EXIFDATA].value; |
- br.seek(directoryOffset); |
- metadata.ifd.exif = {}; |
- self.readDirectory(br, metadata.ifd.exif); |
- |
- if (EXIF_TAG_X_DIMENSION in metadata.ifd.exif && |
- EXIF_TAG_Y_DIMENSION in metadata.ifd.exif) { |
- if (metadata.imageTransform && metadata.imageTransform.rotate90) { |
- metadata.width = metadata.ifd.exif[EXIF_TAG_Y_DIMENSION].value; |
- metadata.height = metadata.ifd.exif[EXIF_TAG_X_DIMENSION].value; |
- } else { |
- metadata.width = metadata.ifd.exif[EXIF_TAG_X_DIMENSION].value; |
- metadata.height = metadata.ifd.exif[EXIF_TAG_Y_DIMENSION].value; |
- } |
- } |
- } |
+ var mark = this.readMark(br); |
+ if (mark == EXIF_MARK_SOS) |
+ throw new Error('SOS marker found before SOF'); |
- // GPS Directory may also be linked from the image directory. |
- if (EXIF_TAG_GPSDATA in metadata.ifd.image) { |
- self.vlog('Read GPS directory.'); |
- directoryOffset = metadata.ifd.image[EXIF_TAG_GPSDATA].value; |
- br.seek(directoryOffset); |
- metadata.ifd.gps = {}; |
- self.readDirectory(br, metadata.ifd.gps); |
+ var markLength = this.readMarkLength(br); |
+ |
+ var nextSectionStart = br.tell() + markLength; |
+ if (!br.canRead(markLength)) { |
+ // Get the entire section. |
+ reread(-4, markLength + 4); |
+ return; |
} |
- // Thumbnail may be linked from the image directory. |
- if (EXIF_TAG_JPG_THUMB_OFFSET in metadata.ifd.thumbnail && |
- EXIF_TAG_JPG_THUMB_LENGTH in metadata.ifd.thumbnail) { |
- self.vlog('Read thumbnail image.'); |
- br.seek(metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_OFFSET].value); |
- metadata.thumbnailURL = br.readImage( |
- metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_LENGTH].value); |
- } else { |
- self.vlog('Image has EXIF data, but no JPG thumbnail.'); |
+ if (mark == EXIF_MARK_EXIF) { |
+ this.parseExifSection(metadata, buf, br); |
+ } else if (mark == EXIF_MARK_SOF) { |
+ // The most reliable size information is encoded in the SOF section. |
+ br.seek(1, ByteReader.SEEK_CUR); // Skip the precision byte. |
+ var height = br.readScalar(2); |
+ var width = br.readScalar(2); |
+ ExifParser.setImageSize(metadata, width, height); |
+ callback(metadata); // We are done! |
+ return; |
} |
- nextStep(metadata); |
- }, |
+ br.seek(nextSectionStart, ByteReader.SEEK_BEG); |
+ } |
+ } catch (e) { |
+ errorCallback(e.toString()); |
+ } |
+}; |
- // Step four, we're done. |
- callback |
- ]; |
+ExifParser.prototype.parseExifSection = function(metadata, buf, br) { |
+ var magic = br.readString(6); |
+ if (magic != 'Exif\0\0') { |
+ // Some JPEG files may have sections marked with EXIF_MARK_EXIF |
+ // but containing something else (e.g. XML text). Ignore such sections. |
+ this.vlog('Invalid EXIF magic: ' + magic + br.readString(100)); |
+ return; |
+ } |
- nextStep(file); |
+ // Offsets inside the EXIF block are based after the magic string. |
+ // Create a new ByteReader based on the current position to make offset |
+ // calculations simpler. |
+ br = new ByteReader(buf, br.tell()); |
+ |
+ var order = br.readScalar(2); |
+ if (order == EXIF_ALIGN_LITTLE) { |
+ br.setByteOrder(ByteReader.LITTLE_ENDIAN); |
+ } else if (order != EXIF_ALIGN_BIG) { |
+ this.log('Invalid alignment value: ' + order.toString(16)); |
+ return; |
+ } |
+ |
+ var tag = br.readScalar(2); |
+ if (tag != EXIF_TAG_TIFF) { |
+ this.log('Invalid TIFF tag: ' + tag.toString(16)); |
+ return; |
+ } |
+ |
+ metadata.littleEndian = (order == EXIF_ALIGN_LITTLE); |
+ metadata.ifd = { |
+ image: {}, |
+ thumbnail: {} |
+ }; |
+ var directoryOffset = br.readScalar(4); |
+ |
+ // Image directory. |
+ this.vlog('Read image directory.'); |
+ br.seek(directoryOffset); |
+ directoryOffset = this.readDirectory(br, metadata.ifd.image); |
+ metadata.imageTransform = this.parseOrientation(metadata.ifd.image); |
+ |
+ // Thumbnail Directory chained from the end of the image directory. |
+ if (directoryOffset) { |
+ this.vlog('Read thumbnail directory.'); |
+ br.seek(directoryOffset); |
+ this.readDirectory(br, metadata.ifd.thumbnail); |
+ // If no thumbnail orientation is encoded, assume same orientation as |
+ // the primary image. |
+ metadata.thumbnailTransform = |
+ this.parseOrientation(metadata.ifd.thumbnail) || |
+ metadata.imageTransform; |
+ } |
+ |
+ // EXIF Directory may be specified as a tag in the image directory. |
+ if (EXIF_TAG_EXIFDATA in metadata.ifd.image) { |
+ this.vlog('Read EXIF directory.'); |
+ directoryOffset = metadata.ifd.image[EXIF_TAG_EXIFDATA].value; |
+ br.seek(directoryOffset); |
+ metadata.ifd.exif = {}; |
+ this.readDirectory(br, metadata.ifd.exif); |
+ } |
+ |
+ // GPS Directory may also be linked from the image directory. |
+ if (EXIF_TAG_GPSDATA in metadata.ifd.image) { |
+ this.vlog('Read GPS directory.'); |
+ directoryOffset = metadata.ifd.image[EXIF_TAG_GPSDATA].value; |
+ br.seek(directoryOffset); |
+ metadata.ifd.gps = {}; |
+ this.readDirectory(br, metadata.ifd.gps); |
+ } |
+ |
+ // Thumbnail may be linked from the image directory. |
+ if (EXIF_TAG_JPG_THUMB_OFFSET in metadata.ifd.thumbnail && |
+ EXIF_TAG_JPG_THUMB_LENGTH in metadata.ifd.thumbnail) { |
+ this.vlog('Read thumbnail image.'); |
+ br.seek(metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_OFFSET].value); |
+ metadata.thumbnailURL = br.readImage( |
+ metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_LENGTH].value); |
+ } else { |
+ this.vlog('Image has EXIF data, but no JPG thumbnail.'); |
+ } |
+}; |
+ |
+ExifParser.setImageSize = function(metadata, width, height) { |
+ if (metadata.imageTransform && metadata.imageTransform.rotate90) { |
+ metadata.width = height; |
+ metadata.height = width; |
+ } else { |
+ metadata.width = width; |
+ metadata.height = height; |
+ } |
}; |
ExifParser.prototype.readMark = function(br) { |
@@ -204,15 +211,6 @@ ExifParser.prototype.readMarkLength = function(br) { |
return br.readScalar(2) - 2; |
}; |
-ExifParser.prototype.readMarkData = function(br) { |
- var length = this.readMarkLength(br); |
- return br.readSlice(length); |
-}; |
- |
-ExifParser.prototype.skipMarkData = function(br) { |
- br.seek(this.readMarkLength(br), ByteReader.SEEK_CUR); |
-}; |
- |
ExifParser.prototype.readDirectory = function(br, tags) { |
var entryCount = br.readScalar(2); |
for (var i = 0; i < entryCount; i++) { |