Skip to content

fix(make-mime): preserve non-ASCII attachment filenames - #7071

Merged
blackboxsw merged 1 commit into
canonical:mainfrom
Benziza:fix/make-mime-unicode-filenames
Sep 21, 2026
Merged

blackboxsw merged 1 commit into
canonical:mainfrom
Benziza:fix/make-mime-unicode-filenames

Conversation

@Benziza

@Benziza Benziza commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Proposed Commit Message

fix(make-mime): preserve non-ASCII attachment filenames

Additional Context

make-mime loses filenames such as café.yaml and إعداد.yaml when its output is parsed: get_filename() returns None, and UserDataProcessor substitutes part-001. The attachment content is preserved.

Pass filename separately to add_header so Python applies RFC 2231 encoding, matching the existing pattern in cloudinit/user_data.py. No CLI options change.

Test Steps

tox -e py3 -- tests/unittests/cmd/devel/test_make_mime.py --no-cov
tox -e py3
tox -e check_format
  • Regression tests: 4 failures before the fix; all 12 cases pass afterward on Python 3.12 and 3.14, covering ASCII and Unicode filenames, content type, and payload.
  • Full unit suite (Python 3.14): 5759 passed, 6 skipped, 13 xfailed.
  • Formatting and checks on the changed files pass. Full check_format remains unsuccessful locally: Python 3.12 reports Pylint E1120 in cloudinit/util.py:2142 (also reproduced on unmodified main), while Python 3.14 reports missing crypt imports in unchanged files.
CLI reproduction

Run from the checkout after creating the tox test environment. No VM is required.

.tox/py3/bin/python - <<'PY'
import email
import subprocess
import sys
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "café.yaml"
    path.write_text("#cloud-config\nhostname: example\n", encoding="utf-8")
    result = subprocess.run(
        [sys.executable, "-m", "cloudinit.cmd.devel.make_mime",
         "-a", str(path) + ":cloud-config"],
        capture_output=True, text=True, check=True,
    )
    part = email.message_from_string(result.stdout).get_payload()[0]
    print("Parsed filename:", part.get_filename())
    assert part.get_filename() == str(path)
    assert part.get_payload(decode=True) == path.read_bytes()
PY

Before the fix, the filename assertion fails because the parsed filename is None. Afterward, both assertions pass. Temporary files are cleaned up automatically.

Merge type

  • Squash merge using "Proposed Commit Message"
  • Rebase and merge unique commits. Requires commit messages per-commit each referencing the pull request number (#<PR_NUM>)

@github-actions

Copy link
Copy Markdown

Hello! Thank you for this proposed change to cloud-init. This pull request is now marked as stale as it has not seen any activity in 14 days. If no activity occurs within the next 7 days, this pull request will automatically close.

If you are waiting for code review and you are seeing this message, apologies! Please reply, tagging blackboxsw, and he will ensure that someone takes a look soon.

(If the pull request is closed and you would like to continue working on it, please do tag blackboxsw to reopen it.)

@github-actions github-actions Bot added the stale-pr Pull request is stale; will be auto-closed soon label Sep 19, 2026
@Benziza

Benziza commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Hi @blackboxsw, this PR is ready for review. Could you please take a look when you have a chance? Thank you!

@github-actions github-actions Bot removed the stale-pr Pull request is stale; will be auto-closed soon label Sep 20, 2026

@nryanl nryanl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey there, thanks for your contribution!

I'm happy with the fix here. For your test: I wonder if we should add coverage between encode and decode as well. If not only just to test the intermediate representation, but it would also catch future changes to the default header encoding elsewhere. What do you think?

Something like this:

[attachment] = parsed.get_payload()
assert attachment.get_filename() == filename

disposition = attachment["Content-Disposition"]
if filename.isascii():
    assert 'filename="' in disposition
    assert "filename*=" not in disposition
else:
    assert "filename*=utf-8''" in disposition

Thanks again!

@Benziza
Benziza force-pushed the fix/make-mime-unicode-filenames branch from 789496b to 8019abf Compare September 21, 2026 21:06
@Benziza

Benziza commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Good point, thanks! I added the Content-Disposition assertions, rebased onto the latest main, and pushed the update. All tests pass locally. Could you take another look?

@blackboxsw blackboxsw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for the proposal @Benziza and the good review @nryanl. This changeset is self-contained, correct and well tested. Let's ship it.

@blackboxsw
blackboxsw merged commit e4dae84 into canonical:main Sep 21, 2026
18 checks passed
nryanl pushed a commit to nryanl/cloud-init that referenced this pull request Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants