Skip to content

Make TextBundleWrapper init nullable and fail on non-UTF-8 text - #25968

Open
jkmassel wants to merge 6 commits into
trunkfrom
jkmassel/textbundle-nullable-init
Open

Make TextBundleWrapper init nullable and fail on non-UTF-8 text#25968
jkmassel wants to merge 6 commits into
trunkfrom
jkmassel/textbundle-nullable-init

Conversation

@jkmassel

@jkmassel jkmassel commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Follows the pattern from #25964. Highest real-world risk in the audit: reachable via a user-supplied file through the Share Extension, with a guaranteed crash. It grew from that TextBundleWrapper crash fix into surfacing any share-extraction failure to the user, instead of swallowing it.

Summary

  • TextBundleWrapper's initWithContentsOfURL:options:error: (and initWithFileWrapper:error:) are declared _Nonnull under NS_ASSUME_NONNULL_BEGIN, but return nil on any read failure — an unreadable URL, a missing info.json, or a missing text file.
  • The text property is declared nonnull but is assigned from [[NSString alloc] initWithData:… encoding:NSUTF8StringEncoding], which is nil on non-UTF-8 bytes — yet readFromFilewrapper: still returned YES, so a structurally valid bundle could initialize with text == nil.
  • ShareExtractor imported the initializer as non-optional and non-throwing, then dereferenced bundleWrapper and bundleWrapper.text. A nil NSString bridged to non-optional String traps.
  • Separately, extraction failures were swallowed: ShareExtractor collected whatever loaded and silently dropped the rest, so a failed share opened a blank editor with no signal.

Fix — TextBundleWrapper

  • Mark both initializers nullable. An error:-parameter initializer with a nullable return bridges to a Swift throwing initializer.
  • After decoding text, fail the read (return NO with TextBundleErrorInvalidFormat) when the result is nil, so text stays honestly nonnull.
  • Annotate fileWrapperForAssetFilename: nullable to match its own doc comment.

Fix — surface extraction failures

  • TypeBasedExtensionContentExtractor.convert(payload:) is now throws; the error from the throwing TextBundleWrapper init propagates up handleTextBundlehandleTextPackprocessLocalFileURLExtractor.convert to the NSItemProvider.loadItem boundary, where it's logged.
  • extract(...) returns (items, failures) per provider — accumulated under an NSLock, since loadItem calls back concurrently — and loadShare returns a ShareLoadOutcome: the assembled share, the skipped attachments, and whether anything usable was extracted.
  • The share and draft extensions act on the outcome (shared helpers on ShareExtensionAbstractViewController):
    • Nothing extracted and something erroredNSExtensionContext.cancelRequest(withError:), via the existing dismissal path, behind an alert.
    • Some attachments failed, others loaded → a non-blocking notice; keep editing.
    • Merely empty (e.g. an empty text selection — no content, no error) → open the editor as before. Empty is not a failure.

Test plan

  • WordPress app and its embedded Share Extension build (generic iOS Simulator).
  • Sharing a valid .textbundle/.textpack imports as before.
  • Sharing a malformed bundle (missing text file / non-UTF-8 text) no longer crashes — the extension cancels with an alert.
  • Sharing good content alongside a malformed attachment loads the good content and shows the skipped notice.
  • Sharing an empty selection opens the editor with no error alert.

Adds Share Extension strings under shareExtension.contentError.* and shareExtension.partialError.*.

Part of the Objective-C non-null nullability audit; see #25964.

@jkmassel jkmassel added this to the 27.3 milestone Sep 1, 2026
@jkmassel jkmassel self-assigned this Sep 1, 2026
@wpmobilebot

wpmobilebot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number34143
VersionPR #25968
Bundle IDorg.wordpress.alpha
Commit19e22d7
Installation URL5v1r4en5vjg10
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number34143
VersionPR #25968
Bundle IDcom.jetpack.alpha
Commit19e22d7
Installation URL7hrd37n50c240
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

@dangermattic

Copy link
Copy Markdown
Collaborator
2 Warnings
⚠️ Modules/Package.swift was changed without updating its corresponding Package.resolved.

If the change includes adding, removing, or editing a dependency please resolve the Swift packages as appropriate to your project setup (e.g. in Xcode or by running swift package resolve).

If the change to the Package.swift did not modify dependencies, ignoring this warning should be safe, but we recommend double checking and running the package resolution just in case.
.

⚠️ Package.swift was changed without updating its corresponding Package.resolved.

If the change includes adding, removing, or editing a dependency please resolve the Swift packages as appropriate to your project setup (e.g. in Xcode or by running swift package resolve).

If the change to the Package.swift did not modify dependencies, ignoring this warning should be safe, but we recommend double checking and running the package resolution just in case.
.

Generated by 🚫 Danger

Now that the TextBundleWrapper initializer bridges to a throwing Swift
initializer, surface its read failures instead of swallowing them with
try?. Make TypeBasedExtensionContentExtractor.convert(payload:) throwing
and thread the error up through handleTextBundle, handleTextPack,
processLocalFile, and URLExtractor.convert. The loadItem completion — which
already receives an Error — logs both load and conversion failures and
balances the dispatch group via defer.
loadShare now returns a ShareLoadOutcome — the assembled share, the
attachments that were skipped, and whether anything usable was extracted.
extract() yields (items, failures) per provider, accumulated under a lock
since loadItem calls back concurrently.

The share and draft extensions act on it: cancel the request when nothing
extracted and something errored, show a non-blocking notice on a partial
failure, and open the editor as before when the share was merely empty.
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.

3 participants