Skip to content

Ignore errors about invalid headers when extra data are found at EOF - #11285

Merged
saimn merged 6 commits into
astropy:masterfrom
cmarmo:bad-padding-eof
Feb 20, 2021
Merged

saimn merged 6 commits into
astropy:masterfrom
cmarmo:bad-padding-eof

Conversation

@cmarmo

@cmarmo cmarmo commented Jan 29, 2021 •

Copy link
Copy Markdown
Member

Description

This pull request checks if extra data at the end of file contain the SIMPLE keyword, following @embray suggestion in the corresponding issue.
If extra data are not an header a warning is thrown

WARNING: Unexpected extra padding at the end of the file.  This padding may not be preserved when saving changes. [astropy.io.fits.header]

Fixes #4351

Additional information

I have added a test but:

  • producing a small file with the same issue is quite tricky (edited: I thought it was! :)): I have used the file in the issue (https://dataverse.harvard.edu/api/access/datafile/2439198) and marked the test as remote_data
  • I can't make the test work: the Warning is not thrown. Yet, running locally
>>> from astropy.io import fits
>>> fits.open('https://dataverse.harvard.edu/api/access/datafile/2439198', cache=False)
Downloading https://dataverse.harvard.edu/api/access/datafile/2439198
|==========================================| 511M/511M (100.00%)        14s
WARNING: Unexpected extra padding at the end of the file.  This padding may not be preserved when saving changes. [astropy.io.fits.header]
[<astropy.io.fits.hdu.image.PrimaryHDU object at 0x7f9db80c0220>]

gives the expected result. I'm clearly missing something about the way test are written or run...

@embray

embray commented Jan 30, 2021

Copy link
Copy Markdown
Member

Hello, thank you for the PR! I'm taking a look at it.

@embray

embray commented Jan 30, 2021

Copy link
Copy Markdown
Member

Here's a way you can write a test for this that doesn't rely on remote_data or downloading such a large file. Upon examining the original problem file, it has just a seemingly arbitrary number of zeros at the end. So you can reproduce this in your test like:

# write some arbitrary data to a FITS file
fits.writeto(self.temp('temp.fits'), np.arange(100))
# append some arbitrary number of zeros to the end
with open(self.temp('temp.fits'), 'ab') as fobj:
    fobj.write(b'\x00' * 1234)
# try to read the file
fits.info(self.temp('temp.fits'))

This will reproduce the problem--please try rewriting the test something like that.

embray
embray previously requested changes Jan 30, 2021
Comment thread astropy/io/fits/tests/test_hdulist.py Outdated
fits.open(
'https://dataverse.harvard.edu/api/access/datafile/2439198',
cache=False
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason this test works for you interactively but not in the test suite, as that fits.open() doesn't actually immediately try to read the whole file (it is lazy).

When you do this at the interactive prompt it tries to represent the resulting HDUList object; i.e. it is the calling the equivalent of

_ = fits.open('...')
repr(_)

whereas here the return value of fits.open is simply ignored and not used. Try instead calling something that will force it to read the full file, like fits.info(). See also my earlier comment on how you can rewrite this test to reproduce the problem with an arbitrary file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: It turns out, ironically, calling fits.info() when the file contains trailing bytes does not reproduce the error, and instead results in the warning we want. I have to find out what is happening differently when calling fits.info().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out fits.info() passes ignore_missing_end=True by default, so it doesn't reproduce the error. Instead, try something like len(fits.open(self.temp('temp.fits'))). That does the trick.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for all your explanations. I have modified the test accordingly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have modified a bit your suggestion because in my previous version (without the with block), a RessourceWarning was thrown (the file wasn't closed).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, of course.

Comment thread astropy/io/fits/header.py Outdated
block = encode_ascii(block)

if not end_found and is_eof and endcard:
if not end_found and is_eof and endcard and is_header:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a perfect interpretation of what I wrote in #4351, though I think what I wrote there is not quite correct. Normally SIMPLE = is only in the first header of a FITS file. So if we were dealing with something like an accidentally truncated FITS file, the more likely thing you would see at the beginning of another header would be XTENSION =.

But I think I found a simpler solution to this problem. Instead of what you did, try moving the lines a bit below this:

        header_str = ''.join(read_blocks)
        _check_padding(header_str, actual_block_size, is_eof,
                       check_block_size=padding)

to just before this line. As far as I can tell that also solves the problem, and doesn't break any existing tests. Please try out that solution and double-check me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally SIMPLE = is only in the first header of a FITS file. So if we were dealing with something like an accidentally truncated FITS file, the more likely thing you would see at the beginning of another header would be XTENSION =.

You are right. I should have thought about that! Anyway, moving the check before the if statement works like a charm.

@pllim pllim added the Bug label Feb 1, 2021
@pllim pllim added this to the v4.0.5 milestone Feb 1, 2021
@embray

embray commented Feb 7, 2021

Copy link
Copy Markdown
Member

@cmarmo Thanks, looks good! Just needs a changelog entry, but if you'd like I can add that.

@cmarmo

cmarmo commented Feb 8, 2021

Copy link
Copy Markdown
Member Author

@cmarmo Thanks, looks good! Just needs a changelog entry, but if you'd like I can add that.

Thanks @embray, it's done.

@saimn

saimn commented Feb 10, 2021

Copy link
Copy Markdown
Contributor

@cmarmo - Could you please rebase on master ? This should fix the CI jobs.

@saimn saimn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @cmarmo ! The remaining failures are unrelated. Great to see the solution was quite simple. I will it leave open for now if @embray wants to have a last look.

@cmarmo

cmarmo commented Feb 19, 2021

Copy link
Copy Markdown
Member Author

I see that #11319 has been merged. Should I rebase to make the "32-bit and parallel" check pass? Thanks!

Comment thread CHANGES.rst Resolved
@saimn

saimn commented Feb 20, 2021

Copy link
Copy Markdown
Contributor

@cmarmo - The failing test has been xfailed so I don't think we need to run again the CI. Looks good to me, so let's merge. Thanks for the useful contribution!

@saimn
saimn dismissed embray’s stale review February 20, 2021 16:42

Requested changes have been done.

@saimn
saimn merged commit 5a3abf9 into astropy:master Feb 20, 2021
astrofrog pushed a commit that referenced this pull request Mar 16, 2021
Ignore errors about invalid headers when extra data are found at EOF
eteq pushed a commit that referenced this pull request Mar 25, 2021
Ignore errors about invalid headers when extra data are found at EOF
@pllim

pllim commented Mar 26, 2021

Copy link
Copy Markdown
Member

Update: The backport of this cause CI failure in v4.2.x but surprisingly not v4.0.x. @saimn is investigating.

saimn added a commit to saimn/astropy that referenced this pull request Mar 26, 2021
With the backport of astropy#11285 on v4.2, a new warning is rasied and turned
into an error. This doesn't happen on v4.0 because the way to catch
warning changed (using pytest) and does not happen on master/4.3 because
some new code was added to detect if the file is a valid FITS file.
saimn added a commit that referenced this pull request Mar 27, 2021
@cmarmo
cmarmo deleted the bad-padding-eof branch April 11, 2021 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue with error about missing END card

4 participants