mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 03:06:38 +00:00
fix(image-stream): fix Apple disk image detection & reading (#1283)
This fixes two things: The format detection, and a bug in `udif`. First, by categorizing the `.dmg` extension as compressed image, `.isSupportedImage()` would attempt to detect the format after stripping the extension, causing it to be misdetected. Second, `udif`'s ReadStream didn't add the `dataForkOffset` to its position when reading blocks, causing the wrong data to be read for some images, in turn causing zlib to error on invalid headers. Changes: - Classify `.dmg` as `type: 'image'` - Update `udif` to 0.8.0 Change-Type: patch Changelog-Entry: Fix Apple disk image detection & streaming Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
parent
847b41f49a
commit
6c930e2d8d
@ -58,4 +58,19 @@ These are the rules for handling archive images:
|
|||||||
|
|
||||||
The module throws an error if the above rules are not met.
|
The module throws an error if the above rules are not met.
|
||||||
|
|
||||||
|
Supported Formats
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
There are currently three image types in supported formats: `image`, `compressed` and `archive`.
|
||||||
|
|
||||||
|
An extension tagged `image` describes a format which can be directly written to a device by its handler,
|
||||||
|
and an extension tagged `archive` denotes an archive containing an image, and will cause an archive handler
|
||||||
|
to open the archive and search for an image file.
|
||||||
|
|
||||||
|
Note that when marking an extension as `compressed`, the filename will be stripped of that extension,
|
||||||
|
and the leftover extension examined to determine the uncompressed image format (i.e. `.img.gz -> .img`).
|
||||||
|
|
||||||
|
As an archive (such as `.tar`) might be additionally compressed, this will allow for constructs such as
|
||||||
|
`.tar.gz` (a compressed archive, containing a file with an extension tagged as `image`) to be handled correctly.
|
||||||
|
|
||||||
[etcher-image-write]: https://github.com/resin-io-modules/etcher-image-write
|
[etcher-image-write]: https://github.com/resin-io-modules/etcher-image-write
|
||||||
|
@ -16,6 +16,14 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Supported filename extensions
|
||||||
|
* @description
|
||||||
|
* NOTE: Extensions with type: 'compressed' will be stripped
|
||||||
|
* from filenames to determine the format of the uncompressed image.
|
||||||
|
* For details, see lib/image-stream/README.md
|
||||||
|
* @const {Array}
|
||||||
|
*/
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
extension: 'zip',
|
extension: 'zip',
|
||||||
@ -37,10 +45,6 @@ module.exports = [
|
|||||||
extension: 'xz',
|
extension: 'xz',
|
||||||
type: 'compressed'
|
type: 'compressed'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
extension: 'dmg',
|
|
||||||
type: 'compressed'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
extension: 'img',
|
extension: 'img',
|
||||||
type: 'image'
|
type: 'image'
|
||||||
@ -60,5 +64,9 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
extension: 'raw',
|
extension: 'raw',
|
||||||
type: 'image'
|
type: 'image'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extension: 'dmg',
|
||||||
|
type: 'image'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
4
npm-shrinkwrap.json
generated
4
npm-shrinkwrap.json
generated
@ -6772,9 +6772,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"udif": {
|
"udif": {
|
||||||
"version": "0.7.0",
|
"version": "0.8.0",
|
||||||
"from": "udif@latest",
|
"from": "udif@latest",
|
||||||
"resolved": "https://registry.npmjs.org/udif/-/udif-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/udif/-/udif-0.8.0.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-js": {
|
"base64-js": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
|
@ -26,7 +26,7 @@ describe('Shared: SupportedFormats', function() {
|
|||||||
|
|
||||||
it('should return the supported compressed extensions', function() {
|
it('should return the supported compressed extensions', function() {
|
||||||
const extensions = supportedFormats.getCompressedExtensions();
|
const extensions = supportedFormats.getCompressedExtensions();
|
||||||
m.chai.expect(extensions).to.deep.equal([ 'gz', 'bz2', 'xz', 'dmg' ]);
|
m.chai.expect(extensions).to.deep.equal([ 'gz', 'bz2', 'xz' ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -35,7 +35,7 @@ describe('Shared: SupportedFormats', function() {
|
|||||||
|
|
||||||
it('should return the supported non compressed extensions', function() {
|
it('should return the supported non compressed extensions', function() {
|
||||||
const extensions = supportedFormats.getNonCompressedExtensions();
|
const extensions = supportedFormats.getNonCompressedExtensions();
|
||||||
m.chai.expect(extensions).to.deep.equal([ 'img', 'iso', 'dsk', 'hddimg', 'raw' ]);
|
m.chai.expect(extensions).to.deep.equal([ 'img', 'iso', 'dsk', 'hddimg', 'raw', 'dmg' ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -64,6 +64,92 @@ describe('Shared: SupportedFormats', function() {
|
|||||||
|
|
||||||
describe('.isSupportedImage()', function() {
|
describe('.isSupportedImage()', function() {
|
||||||
|
|
||||||
|
_.forEach([
|
||||||
|
|
||||||
|
// Type: 'archive'
|
||||||
|
'path/to/filename.zip',
|
||||||
|
'path/to/filename.etch',
|
||||||
|
|
||||||
|
// Type: 'compressed'
|
||||||
|
'path/to/filename.img.gz',
|
||||||
|
'path/to/filename.img.bz2',
|
||||||
|
'path/to/filename.img.xz',
|
||||||
|
|
||||||
|
// Type: 'image'
|
||||||
|
'path/to/filename.img',
|
||||||
|
'path/to/filename.iso',
|
||||||
|
'path/to/filename.dsk',
|
||||||
|
'path/to/filename.hddimg',
|
||||||
|
'path/to/filename.raw',
|
||||||
|
'path/to/filename.dmg'
|
||||||
|
|
||||||
|
], (filename) => {
|
||||||
|
it(`should return true for ${filename}`, function() {
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(filename);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the file has no extension', function() {
|
||||||
|
const isSupported = supportedFormats.isSupportedImage('/path/to/foo');
|
||||||
|
m.chai.expect(isSupported).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the extension is not included in .getAllExtensions()', function() {
|
||||||
|
const isSupported = supportedFormats.isSupportedImage('/path/to/foo.jpg');
|
||||||
|
m.chai.expect(isSupported).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the extension is included in .getAllExtensions()', function() {
|
||||||
|
const nonCompressedExtension = _.first(supportedFormats.getNonCompressedExtensions());
|
||||||
|
const imagePath = `/path/to/foo.${nonCompressedExtension}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore casing when determining extension validity', function() {
|
||||||
|
const nonCompressedExtension = _.first(supportedFormats.getNonCompressedExtensions());
|
||||||
|
const imagePath = `/path/to/foo.${_.toUpper(nonCompressedExtension)}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not consider an extension before a non compressed extension', function() {
|
||||||
|
const nonCompressedExtension = _.first(supportedFormats.getNonCompressedExtensions());
|
||||||
|
const imagePath = `/path/to/foo.1234.${nonCompressedExtension}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the extension is supported and the file name includes dots', function() {
|
||||||
|
const nonCompressedExtension = _.first(supportedFormats.getNonCompressedExtensions());
|
||||||
|
const imagePath = `/path/to/foo.1.2.3-bar.${nonCompressedExtension}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the extension is only a supported archive extension', function() {
|
||||||
|
const archiveExtension = _.first(supportedFormats.getArchiveExtensions());
|
||||||
|
const imagePath = `/path/to/foo.${archiveExtension}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the extension is a supported one plus a supported compressed extensions', function() {
|
||||||
|
const nonCompressedExtension = _.first(supportedFormats.getNonCompressedExtensions());
|
||||||
|
const compressedExtension = _.first(supportedFormats.getCompressedExtensions());
|
||||||
|
const imagePath = `/path/to/foo.${nonCompressedExtension}.${compressedExtension}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the extension is an unsupported one plus a supported compressed extensions', function() {
|
||||||
|
const compressedExtension = _.first(supportedFormats.getCompressedExtensions());
|
||||||
|
const imagePath = `/path/to/foo.jpg.${compressedExtension}`;
|
||||||
|
const isSupported = supportedFormats.isSupportedImage(imagePath);
|
||||||
|
m.chai.expect(isSupported).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('should return false if the file has no extension', function() {
|
it('should return false if the file has no extension', function() {
|
||||||
const isSupported = supportedFormats.isSupportedImage('/path/to/foo');
|
const isSupported = supportedFormats.isSupportedImage('/path/to/foo');
|
||||||
m.chai.expect(isSupported).to.be.false;
|
m.chai.expect(isSupported).to.be.false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user