Skip to content

Commit cb16ab4

Browse files
committed
Use Commons Compress instead of libfragmentzip for BuildManifest.plist
1 parent 7f91ab0 commit cb16ab4

File tree

8 files changed

+120
-98
lines changed

8 files changed

+120
-98
lines changed

.github/workflows/main.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ jobs:
1616
uses: actions/setup-java@v1
1717
with:
1818
java-version: '15'
19-
- name: Install Linux dependencies
20-
run: sudo apt install libcurl4-openssl-dev libzip-dev && git clone --depth=1 https://github.com/tihmstar/libgeneral.git && cd libgeneral && ./autogen.sh && make && sudo make install && git clone --depth=1 https://github.com/tihmstar/libfragmentzip.git && cd libfragmentzip && sed -i '/LIBGENERAL_REQUIRES_STR/d' ./configure.ac && ./autogen.sh && make && sudo make install && sudo ln -s /usr/local/lib/libfragmentzip.so /usr/lib/
21-
if: runner.os == 'Linux'
2219
- name: gradle build
2320
uses: eskatos/gradle-command-action@v1
2421
with:

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ dependencies {
5959
implementation group: 'org.json', name: 'json', version: '20210307'
6060
implementation 'de.jangassen:nsmenufx:3.1.0'
6161
// implementation 'net.java.dev.jna:jna:jpms:5.7.0'
62+
implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20'
63+
6264

6365
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
6466
}

dist/linux/README-linux.txt

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
blobsaver has one required dependency and two optional.
2-
Make sure to install these on your system before continuing.
1+
blobsaver has two optional dependencies for reading information from connected devices:
32

4-
Required:
5-
- libfragmentzip (https://github.com/tihmstar/libfragmentzip)
6-
7-
Optional (for reading information from connected devices):
8-
- libimobiledevice (https://github.com/libimobiledevice/libimobiledevice)
9-
- libirecovery (https://github.com/libimobiledevice/libirecovery)
10-
11-
If you do not install these optional dependencies, blobsaver's
12-
"Read from device" feature will not work.
3+
- libimobiledevice: https://github.com/libimobiledevice/libimobiledevice (for ECID and device information)
4+
- libirecovery: https://github.com/libimobiledevice/libirecovery (for reading apnonce)

src/main/java/airsquared/blobsaver/app/TSS.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ protected String call() throws TSSException {
8888
// can't use forEach() because exception won't be caught
8989
for (Utils.IOSVersion iosVersion : iosVersions) {
9090
try {
91-
args.set(args.size() - 1, extractBuildManifest(iosVersion.ipswURL).getAbsolutePath());
91+
args.set(args.size() - 1, extractBuildManifest(iosVersion.ipswURL).toString());
9292
} catch (IOException e) {
9393
throw new TSSException("Unable to extract BuildManifest.", true, e);
9494
}

src/main/java/airsquared/blobsaver/app/Utils.java

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
package airsquared.blobsaver.app;
2020

21-
import airsquared.blobsaver.app.natives.Libfragmentzip;
2221
import com.sun.jna.Platform;
2322
import javafx.scene.Node;
2423
import javafx.scene.control.Alert;
@@ -35,19 +34,21 @@
3534
import javafx.scene.paint.Color;
3635
import javafx.stage.DirectoryChooser;
3736
import javafx.stage.Window;
37+
import org.apache.commons.compress.archivers.zip.ZipFile;
3838
import org.json.JSONArray;
3939
import org.json.JSONException;
4040
import org.json.JSONObject;
4141

42-
import java.io.BufferedReader;
43-
import java.io.File;
44-
import java.io.IOException;
45-
import java.io.InputStreamReader;
46-
import java.io.PrintWriter;
47-
import java.io.StringWriter;
48-
import java.io.UncheckedIOException;
42+
import java.io.*;
4943
import java.net.URL;
5044
import java.net.URLConnection;
45+
import java.nio.ByteBuffer;
46+
import java.nio.channels.Channels;
47+
import java.nio.channels.ReadableByteChannel;
48+
import java.nio.channels.SeekableByteChannel;
49+
import java.nio.file.Files;
50+
import java.nio.file.Path;
51+
import java.nio.file.StandardCopyOption;
5152
import java.util.ArrayList;
5253
import java.util.List;
5354
import java.util.Objects;
@@ -358,15 +359,100 @@ public String toString() {
358359
}
359360
}
360361

361-
static File extractBuildManifest(String url) throws IOException {
362-
File buildManifest = File.createTempFile("BuildManifest", ".plist");
363-
System.out.println("Extracting build manifest from " + url);
364-
int err = Libfragmentzip.downloadFile(url, "BuildManifest.plist", buildManifest.getAbsolutePath());
365-
if (err != 0) {
366-
throw new IOException("problem with libfragmentzip download: code=" + err);
362+
static Path extractBuildManifest(String ipswUrl) throws IOException {
363+
Path buildManifest = Files.createTempFile("BuildManifest", ".plist");
364+
try (ZipFile ipsw = new ZipFile(new HttpChannel(new URL(ipswUrl)), "ipsw", "UTF8", true, true);
365+
InputStream is = ipsw.getInputStream(ipsw.getEntry("BuildManifest.plist"))) {
366+
Files.copy(is, buildManifest, StandardCopyOption.REPLACE_EXISTING);
367367
}
368368
System.out.println("Extracted to " + buildManifest);
369-
return buildManifest;
369+
return buildManifest.toRealPath();
370+
}
371+
372+
/**
373+
* Source: https://github.com/jcodec/jcodec/blob/6e1ec651eca92d21b41f9790143a0e6e4d26811e/android/src/main/org/jcodec/common/io/HttpChannel.java
374+
*
375+
* @author The JCodec project
376+
*/
377+
private static final class HttpChannel implements SeekableByteChannel {
378+
379+
private final URL url;
380+
private ReadableByteChannel ch;
381+
private long pos;
382+
private long length;
383+
384+
public HttpChannel(URL url) {
385+
this.url = url;
386+
}
387+
388+
@Override
389+
public long position() {
390+
return pos;
391+
}
392+
393+
@Override
394+
public SeekableByteChannel position(long newPosition) throws IOException {
395+
if (newPosition == pos) {
396+
return this;
397+
} else if (ch != null) {
398+
ch.close();
399+
ch = null;
400+
}
401+
pos = newPosition;
402+
return this;
403+
}
404+
405+
@Override
406+
public long size() throws IOException {
407+
ensureOpen();
408+
return length;
409+
}
410+
411+
@Override
412+
public SeekableByteChannel truncate(long size) {
413+
throw new UnsupportedOperationException("Truncate on HTTP is not supported.");
414+
}
415+
416+
@Override
417+
public int read(ByteBuffer buffer) throws IOException {
418+
ensureOpen();
419+
int read = ch.read(buffer);
420+
if (read != -1)
421+
pos += read;
422+
return read;
423+
}
424+
425+
@Override
426+
public int write(ByteBuffer buffer) {
427+
throw new UnsupportedOperationException("Write to HTTP is not supported.");
428+
}
429+
430+
@Override
431+
public boolean isOpen() {
432+
return ch != null && ch.isOpen();
433+
}
434+
435+
@Override
436+
public void close() throws IOException {
437+
ch.close();
438+
}
439+
440+
private void ensureOpen() throws IOException {
441+
if (ch == null) {
442+
URLConnection connection = url.openConnection();
443+
if (pos > 0)
444+
connection.addRequestProperty("Range", "bytes=" + pos + "-");
445+
ch = Channels.newChannel(connection.getInputStream());
446+
String resp = connection.getHeaderField("Content-Range");
447+
if (resp != null) {
448+
length = Long.parseLong(resp.split("/")[1]);
449+
} else {
450+
resp = connection.getHeaderField("Content-Length");
451+
length = Long.parseLong(resp);
452+
}
453+
}
454+
}
455+
370456
}
371457

372458
static String exceptionToString(Throwable t) {

src/main/java/airsquared/blobsaver/app/natives/Libfragmentzip.java

Lines changed: 0 additions & 63 deletions
This file was deleted.

src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
requires com.sun.jna;
88
requires nsmenufx;
99
requires org.json;
10+
requires org.apache.commons.compress;
1011
}

src/test/java/airsquared/blobsaver/app/UtilsTest.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,26 @@
2020

2121
import org.junit.jupiter.api.Test;
2222

23-
import java.io.File;
2423
import java.io.IOException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
2526

26-
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
2728

2829
public class UtilsTest extends BlobsaverTest {
2930

3031
private static final String ipswUrl = "http://updates-http.cdn-apple.com/2020WinterFCS/fullrestores/061-20302/454ACB32-D3F6-4984-92CB-27C8FA368165/iPhone11,8,iPhone12,1_13.4_17E255_Restore.ipsw";
3132

3233
@Test
3334
public void extractBuildManifest() throws IOException {
34-
File buildManifest = Utils.extractBuildManifest(ipswUrl);
35-
assertTrue(buildManifest.exists());
36-
assertTrue(buildManifest.isFile());
35+
Path buildManifest = Utils.extractBuildManifest(ipswUrl);
36+
37+
try (var reader = Files.newBufferedReader(buildManifest)) {
38+
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", reader.readLine());
39+
assertEquals("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">", reader.readLine());
40+
assertEquals("<plist version=\"1.0\">", reader.readLine());
41+
assertEquals("<dict>", reader.readLine());
42+
}
3743
}
44+
3845
}

0 commit comments

Comments
 (0)