Summary
PR #506 (v2.5.0) switched replicateZip to OpenRaw/CreateRaw. Apps installed
over the air (itms-services / MDM / OTA) then fail to install — a regression of
#469, which fixed exactly this for #433.
Symptom
The produced .ipa installs fine over USB (ideviceinstaller) but an OTA
install reaches ~100% and then iOS demotes the app to a placeholder ("cloud")
icon. Device syslog:
SZExtractorErrorDomain Code=3 "Finishing an incomplete stream! This stream will not be resumable"
-[StreamingUnzipState finishStream]
USB works because ideviceinstaller unpacks only after the whole file has
landed; itms-services streams and unzips as it downloads.
What differs (measured)
Running the same App Store base through both replicateZip implementations and
parsing the local file headers:
| local file header |
Apple original |
CreateRaw (#506) |
CreateHeader (#469) |
| general-purpose bit 3 |
clear |
clear |
set |
| CRC / sizes inline |
yes |
yes |
zero (deferred) |
| trailing data descriptor |
none |
none |
present |
CreateRaw output is byte-identical to Apple's original (a verbatim copy).
CreateHeader recompresses each entry and re-frames it with a trailing
data-descriptor record (PK\x07\x08, general-purpose bit 3 set).
Observed behaviour
Only the CreateHeader (recompressed / data-descriptor) form installs over the
air; the verbatim CreateRaw form fails with the syslog above. App Store IPAs are
never OTA-installed as-is — the tool injects the sinf and repackages first, and
that repackage historically used CreateHeader, so the inline form first reached
the OTA path with #506, which is when OTA installs broke.
Most likely cause
iOS's OTA streaming installer (SZExtractor / StreamingUnzip) reads
front-to-back without the central directory and appears to finalize each entry on
the trailing data descriptor that only the recompress path emits; the
descriptor-less verbatim form is left unterminated (Finishing an incomplete stream). I can't fully isolate this from the recompression itself —
CreateHeader changes both the framing and the compressed bytes — so this is the
observed-effect explanation, not a proven mechanism.
Fix
Restore the Open/CreateHeader path (as #469). The cost is CPU to recompress;
if that regression matters, the raw fast path could be kept behind an opt-in flag
with CreateHeader as the OTA-safe default.
A branch with the fix + a unit test (asserts a Deflate entry is re-framed with a
data descriptor and round-trips; go test ./... passes) is here, since PRs are
limited to collaborators:
https://github.com/iosconstantine/ipatool/tree/fix/itms-ota-raw-zip
Refs: #433, #469, #506.
Summary
PR #506 (v2.5.0) switched
replicateZiptoOpenRaw/CreateRaw. Apps installedover the air (itms-services / MDM / OTA) then fail to install — a regression of
#469, which fixed exactly this for #433.
Symptom
The produced
.ipainstalls fine over USB (ideviceinstaller) but an OTAinstall reaches ~100% and then iOS demotes the app to a placeholder ("cloud")
icon. Device syslog:
USB works because
ideviceinstallerunpacks only after the whole file haslanded; itms-services streams and unzips as it downloads.
What differs (measured)
Running the same App Store base through both
replicateZipimplementations andparsing the local file headers:
CreateRawoutput is byte-identical to Apple's original (a verbatim copy).CreateHeaderrecompresses each entry and re-frames it with a trailingdata-descriptor record (
PK\x07\x08, general-purpose bit 3 set).Observed behaviour
Only the
CreateHeader(recompressed / data-descriptor) form installs over theair; the verbatim
CreateRawform fails with the syslog above. App Store IPAs arenever OTA-installed as-is — the tool injects the
sinfand repackages first, andthat repackage historically used
CreateHeader, so the inline form first reachedthe OTA path with #506, which is when OTA installs broke.
Most likely cause
iOS's OTA streaming installer (
SZExtractor/StreamingUnzip) readsfront-to-back without the central directory and appears to finalize each entry on
the trailing data descriptor that only the recompress path emits; the
descriptor-less verbatim form is left unterminated (
Finishing an incomplete stream). I can't fully isolate this from the recompression itself —CreateHeaderchanges both the framing and the compressed bytes — so this is theobserved-effect explanation, not a proven mechanism.
Fix
Restore the
Open/CreateHeaderpath (as #469). The cost is CPU to recompress;if that regression matters, the raw fast path could be kept behind an opt-in flag
with
CreateHeaderas the OTA-safe default.A branch with the fix + a unit test (asserts a Deflate entry is re-framed with a
data descriptor and round-trips;
go test ./...passes) is here, since PRs arelimited to collaborators:
https://github.com/iosconstantine/ipatool/tree/fix/itms-ota-raw-zip
Refs: #433, #469, #506.