-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix reading pascal string #42
Conversation
Thanks! We'll need a test case that has a minimal PSD file where
The test can live somewhere in here https://github.com/chinedufn/psd/tree/master/tests After that we can get this merged. |
I've added a 1x1 PSD that
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for adding a tiny test PSD file. Looks great.
The String::from_utf8_lossy
changes look good. (thank you!)
My only concern is that some of the parsing changes aren't tested.
I left questions on the changes that don't appear to be tested.
Thanks for working to put this together!
if cursor.position() + bytes_to_read as u64 > len { | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are passing without this change.
Is this check solving for a real PSD that you came across?
If so can we add a test fixture?
If not, what led you to want to check here to make sure that we aren't reading passed the length? Can we remove this check, or do we need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is a real PSD file (and its owner has explicitly stated that it is confidential). It can be opened in ImageMagick and Krita, but FFmpeg also complains the file is malformed.
From its XMP metadata I can tell it had been edited by
Photoshop CC 2017 and then Photoshop 23.0 on Windows.
When the code hits this IF branch, cursor.position()
and len
are both 5964830, and bytes_to_read
is 1. The 0 is at the end of the blue channel. I really have no idea why there is an extra byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks for explaining.
I think we can unit test this then.
Can add something like this to the bottom of this file:
#[cfg(test)]
mod tests {
use crate::PsdLayer;
use super::*;
/// Verify that when when inserting an RLE channel's bytes into an RGBA byte vec we do not
/// attempt to read beyond the channel's length.
#[test]
fn does_not_read_beyond_rle_channels_bytes() {
let layer = PsdLayer {
channels: todo!("Set up some failing data"),
layer_properties: todo!("Set up some failing data"),
};
let rgba = todo!("");
let channel_bytes = todo!("");
layer.insert_channel_bytes(&mut rgba, PsdChannelKind::Red, &channel_bytes);
assert_eq!(rgba, vec![...]);
}
}
Then just craft a PsdLayer
and rgba
and channel_bytes
that would fail without your fix.
Can make PsdLayer.channels
pub(crate)
so that it is visible from here.
Can also add a second test for the other fix below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both tests have been added, please take another look.
if cursor.position() + 1 > len { | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are passing without this change.
Is this check solving for a real PSD that you came across?
If so can we add a test fixture?
If not, what led you to want to check here to make sure that we aren't reading passed the length? Can we remove this check, or do we need it?
let result = String::from_utf8(data.to_vec()).unwrap(); | ||
let result = String::from_utf8_lossy(data).into_owned(); | ||
|
||
if len % 2 == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests still pass without this len % 2 == 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I forgot the problem actually occurred in another image resource block, and the original data contains a string of odd length (13) so the size field following it was incorrectly decoded (0x0005AC00 instead of 0x000005AC):
38 42 49 4D 0B B8 0D 4F 72 69 67 69 6E 44 61 74 8BIM.¸.OriginDat
61 49 52 42 00 00 05 AC 00 00 00 10 00 00 00 01 aIRB...¬........
...
How about removing one byte of space (0x20) from the non-UTF-8 string in the test fixture?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Want to add a second fixture that is dedicated to this odd length pascal string case?
This way we don't stuff too many different cases into a single fixture.
If you can easily do that then that would be preferred.
If not, we can add a test to the bottom of this file that handles this case (feel free to do both if you want).
Thanks!
cursor.read_4(); | ||
let len = cursor.read_u32(); | ||
|
||
if len == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only change where tests start failing if we comment it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for explaining the changes.
Everything that you explained makes sense. I left an idea for a way that we can get this tested.
Once we have some coverage we can merge this and I can deploy a new version.
if cursor.position() + bytes_to_read as u64 > len { | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks for explaining.
I think we can unit test this then.
Can add something like this to the bottom of this file:
#[cfg(test)]
mod tests {
use crate::PsdLayer;
use super::*;
/// Verify that when when inserting an RLE channel's bytes into an RGBA byte vec we do not
/// attempt to read beyond the channel's length.
#[test]
fn does_not_read_beyond_rle_channels_bytes() {
let layer = PsdLayer {
channels: todo!("Set up some failing data"),
layer_properties: todo!("Set up some failing data"),
};
let rgba = todo!("");
let channel_bytes = todo!("");
layer.insert_channel_bytes(&mut rgba, PsdChannelKind::Red, &channel_bytes);
assert_eq!(rgba, vec![...]);
}
}
Then just craft a PsdLayer
and rgba
and channel_bytes
that would fail without your fix.
Can make PsdLayer.channels
pub(crate)
so that it is visible from here.
Can also add a second test for the other fix below.
let result = String::from_utf8(data.to_vec()).unwrap(); | ||
let result = String::from_utf8_lossy(data).into_owned(); | ||
|
||
if len % 2 == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Want to add a second fixture that is dedicated to this odd length pascal string case?
This way we don't stuff too many different cases into a single fixture.
If you can easily do that then that would be preferred.
If not, we can add a test to the bottom of this file that handles this case (feel free to do both if you want).
Thanks!
Thanks for getting this all tested! |
I encountered a PSD file that caused
psd
crate to panic. The problems are:read_pascal_string
seems to have a bug. Pascal string is padded to make the size even, so the extra byte only exists when data length + 1 (header byte) is odd. Related to DescriptorStructure can occur inside an ImageResourcesBlock #40. Ref implementation: https://github.com/ImageMagick/ImageMagick/blob/7.1.1-3/coders/psd.c#L798-L799This PR attempts to resolve above issues.
Update: Also fixes panicking when the Layer and mask information section is empty (Krita can produce such PSDs).