Skip to content

[Bug] xlsx write with password() leaves an unencrypted workbook when encryption fails #1135

Description

@nkuprins

Search before asking

  • I searched in the issues and found nothing similar.

Fesod version

main

JDK version

any

Operating system

Linux

Steps To Reproduce

When an xlsx is written with .password(...) and the encryption step fails, the caller gets an exception, but the workbook has already been written unencrypted. This happens both when writing to an OutputStream and when writing to a File.

OutputStream: make encryption fail by pointing the temp directory at a path that can't be created:

File blocker = File.createTempFile("blocker", ".tmp");
String original = FileUtils.getTempFilePrefix();
FileUtils.setTempFilePrefix(blocker.getAbsolutePath() + File.separator + "sub" + File.separator);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    FesodSheet.write(out, DemoData.class)
            .excelType(ExcelTypeEnum.XLSX)
            .password("secret")
            .sheet("s")
            .doWrite(data);                  // throws
} finally {
    FileUtils.setTempFilePrefix(original);
}
// out holds data readable with no password

File: make the target read-only once its output stream is open, so the encrypt-in-place step can't reopen it (run as a non-root user):

File target = new File("secret.xlsx");
FesodSheet.write(target, DemoData.class)
        .excelType(ExcelTypeEnum.XLSX)
        .password("secret")
        .registerWriteHandler(new WorkbookWriteHandler() {
            @Override
            public void afterWorkbookDispose(WorkbookWriteHandlerContext context) {
                target.setReadOnly();
            }
        })
        .sheet("s")
        .doWrite(data);                      // throws
// secret.xlsx is a plain xlsx, readable with no password

These setups only force the failure. For a File, any error in the encrypt-in-place step triggers it (the target can't be reopened, e.g. locked by another process; or encryption runs out of memory on a large workbook). For an OutputStream, it needs the encryption temp file to fail to open while the rest of the write succeeded, e.g. under file-descriptor exhaustion. Rare either way, but when it happens, the caller gets unprotected data after asking for a password.

Current Behavior

The exception is thrown, but a complete, unencrypted xlsx is in the stream or on disk, and it reads fine without a password. If the caller has already sent the response or keeps the file, the data goes out without protection.

Expected Behavior

The write fails and leaves no unencrypted output: nothing is written to the stream, and no file is left on disk. The caller asked for password protection, so an exception with no output seems like the only safe result.

Anything else?

Cause: WriteContextImpl#finish(boolean) saves each step's exception and still runs the remaining steps:

  • When doOutputStreamEncrypt07() throws, isOutputStreamEncrypt stays false, so the next block writes the plain workbook to the stream, because writeExcel is still true.
  • When doFileEncrypt07() throws, nothing removes the file that was already written unencrypted.

This doesn't look intended. When EasyExcel added encryption (alibaba/easyexcel@44068c94), that catch threw straight away, so nothing was written to the stream. alibaba/easyexcel@2c8918be, an unrelated fix, changed every catch in finish() to save the exception and carry on, so the later close steps would still run. From then on, an encryption failure falls through to the unencrypted write. The code came into Fesod unchanged.

Proposed fix:

  • OutputStream: when doOutputStreamEncrypt07() fails, set writeExcel = false, so the unencrypted workbook is not written. The workbook and streams are still closed.
  • File: when doFileEncrypt07() fails, delete the target file. Nothing is lost, because the builder already truncated the file when it opened it.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions