Allow importing from zip file
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
217ab5e17f
commit
f4e462fabd
@ -39,11 +39,16 @@ import com.wireguard.android.util.AsyncWorker;
|
|||||||
import com.wireguard.android.util.ExceptionLoggers;
|
import com.wireguard.android.util.ExceptionLoggers;
|
||||||
import com.wireguard.config.Config;
|
import com.wireguard.config.Config;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import java9.util.concurrent.CompletableFuture;
|
import java9.util.concurrent.CompletableFuture;
|
||||||
import java9.util.concurrent.CompletionStage;
|
|
||||||
import java9.util.function.Function;
|
|
||||||
import java9.util.stream.Collectors;
|
import java9.util.stream.Collectors;
|
||||||
import java9.util.stream.IntStream;
|
import java9.util.stream.IntStream;
|
||||||
import java9.util.stream.StreamSupport;
|
import java9.util.stream.StreamSupport;
|
||||||
@ -68,7 +73,10 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
if (activity == null)
|
if (activity == null)
|
||||||
return;
|
return;
|
||||||
final ContentResolver contentResolver = activity.getContentResolver();
|
final ContentResolver contentResolver = activity.getContentResolver();
|
||||||
final CompletionStage<String> nameStage = asyncWorker.supplyAsync(() -> {
|
|
||||||
|
final List<CompletableFuture<Tunnel>> futureTunnels = new ArrayList<>();
|
||||||
|
final List<Throwable> throwables = new ArrayList<>();
|
||||||
|
asyncWorker.supplyAsync(() -> {
|
||||||
final String[] columns = {OpenableColumns.DISPLAY_NAME};
|
final String[] columns = {OpenableColumns.DISPLAY_NAME};
|
||||||
String name = null;
|
String name = null;
|
||||||
try (Cursor cursor = contentResolver.query(uri, columns, null, null, null)) {
|
try (Cursor cursor = contentResolver.query(uri, columns, null, null, null)) {
|
||||||
@ -77,17 +85,71 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
if (name == null)
|
if (name == null)
|
||||||
name = Uri.decode(uri.getLastPathSegment());
|
name = Uri.decode(uri.getLastPathSegment());
|
||||||
if (name.indexOf('/') >= 0)
|
int idx = name.lastIndexOf('/');
|
||||||
name = name.substring(name.lastIndexOf('/') + 1);
|
if (idx >= 0) {
|
||||||
if (name.endsWith(".conf"))
|
if (idx >= name.length() - 1)
|
||||||
|
throw new IllegalArgumentException("Illegal file name: " + name);
|
||||||
|
name = name.substring(idx + 1);
|
||||||
|
}
|
||||||
|
boolean isZip = name.toLowerCase().endsWith(".zip");
|
||||||
|
if (name.toLowerCase().endsWith(".conf"))
|
||||||
name = name.substring(0, name.length() - ".conf".length());
|
name = name.substring(0, name.length() - ".conf".length());
|
||||||
Log.d(TAG, "Import mapped URI " + uri + " to tunnel name " + name);
|
|
||||||
return name;
|
if (isZip) {
|
||||||
|
ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri));
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(zip, StandardCharsets.UTF_8));
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zip.getNextEntry()) != null) {
|
||||||
|
if (entry.isDirectory())
|
||||||
|
continue;
|
||||||
|
name = entry.getName();
|
||||||
|
idx = name.lastIndexOf('/');
|
||||||
|
if (idx >= 0) {
|
||||||
|
if (idx >= name.length() - 1)
|
||||||
|
continue;
|
||||||
|
name = name.substring(name.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
if (name.toLowerCase().endsWith(".conf"))
|
||||||
|
name = name.substring(0, name.length() - ".conf".length());
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
Config config = null;
|
||||||
|
try {
|
||||||
|
config = Config.from(reader);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throwables.add(e);
|
||||||
|
}
|
||||||
|
if (config != null)
|
||||||
|
futureTunnels.add(tunnelManager.create(name, config).toCompletableFuture());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
futureTunnels.add(tunnelManager.create(name, Config.from(contentResolver.openInputStream(uri))).toCompletableFuture());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (futureTunnels.isEmpty() && throwables.size() == 1)
|
||||||
|
throw throwables.get(0);
|
||||||
|
|
||||||
|
return CompletableFuture.allOf(futureTunnels.toArray(new CompletableFuture[futureTunnels.size()]));
|
||||||
|
}).whenComplete((future, exception) -> {
|
||||||
|
if (exception != null) {
|
||||||
|
this.onTunnelImportFinished(Arrays.asList(), Arrays.asList(exception));
|
||||||
|
} else {
|
||||||
|
future.whenComplete((ignored1, ignored2) -> {
|
||||||
|
ArrayList<Tunnel> tunnels = new ArrayList<>(futureTunnels.size());
|
||||||
|
for (CompletableFuture<Tunnel> futureTunnel : futureTunnels) {
|
||||||
|
Tunnel tunnel = null;
|
||||||
|
try {
|
||||||
|
tunnel = futureTunnel.getNow(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throwables.add(e);
|
||||||
|
}
|
||||||
|
if (tunnel != null)
|
||||||
|
tunnels.add(tunnel);
|
||||||
|
}
|
||||||
|
onTunnelImportFinished(tunnels, throwables);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
asyncWorker.supplyAsync(() -> Config.from(contentResolver.openInputStream(uri)))
|
|
||||||
.thenCombine(nameStage, (config, name) -> tunnelManager.create(name, config))
|
|
||||||
.thenCompose(Function.identity())
|
|
||||||
.whenComplete(this::onTunnelImportFinished);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -164,16 +226,25 @@ public class TunnelListFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTunnelImportFinished(final Tunnel tunnel, final Throwable throwable) {
|
private void onTunnelImportFinished(final List<Tunnel> tunnels, final List<Throwable> throwables) {
|
||||||
final String message;
|
String message = null;
|
||||||
if (throwable == null) {
|
|
||||||
message = getString(R.string.import_success, tunnel.getName());
|
for (final Throwable throwable : throwables) {
|
||||||
} else {
|
|
||||||
final String error = ExceptionLoggers.unwrap(throwable).getMessage();
|
final String error = ExceptionLoggers.unwrap(throwable).getMessage();
|
||||||
message = getString(R.string.import_error, error);
|
message = getString(R.string.import_error, error);
|
||||||
Log.e(TAG, message, throwable);
|
Log.e(TAG, message, throwable);
|
||||||
}
|
}
|
||||||
if (binding != null) {
|
|
||||||
|
if (tunnels.size() == 1 && throwables.isEmpty())
|
||||||
|
message = getString(R.string.import_success, tunnels.get(0).getName());
|
||||||
|
else if (tunnels.isEmpty() && throwables.size() == 1)
|
||||||
|
/* Use the exception message from above. */;
|
||||||
|
else if (throwables.isEmpty())
|
||||||
|
message = getString(R.string.import_total_success, tunnels.size());
|
||||||
|
else if (!throwables.isEmpty())
|
||||||
|
message = getString(R.string.import_partial_success, tunnels.size(), tunnels.size() + throwables.size());
|
||||||
|
|
||||||
|
if (binding != null && message != null) {
|
||||||
final CoordinatorLayout container = binding.mainContainer;
|
final CoordinatorLayout container = binding.mainContainer;
|
||||||
Snackbar.make(container, message, Snackbar.LENGTH_LONG).show();
|
Snackbar.make(container, message, Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
@ -97,11 +97,12 @@ public class Config implements Parcelable {
|
|||||||
in.readTypedList(peers, Peer.CREATOR);
|
in.readTypedList(peers, Peer.CREATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Config from(final InputStream stream)
|
public static Config from(final InputStream stream) throws IOException {
|
||||||
throws IOException {
|
return from(new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Config from(final BufferedReader reader) throws IOException {
|
||||||
final Config config = new Config();
|
final Config config = new Config();
|
||||||
try (BufferedReader reader = new BufferedReader(
|
|
||||||
new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
|
||||||
Peer currentPeer = null;
|
Peer currentPeer = null;
|
||||||
String line;
|
String line;
|
||||||
boolean inInterfaceSection = false;
|
boolean inInterfaceSection = false;
|
||||||
@ -126,7 +127,6 @@ public class Config implements Parcelable {
|
|||||||
if (!inInterfaceSection && currentPeer == null) {
|
if (!inInterfaceSection && currentPeer == null) {
|
||||||
throw new IllegalArgumentException("Could not find any config information");
|
throw new IllegalArgumentException("Could not find any config information");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,9 @@
|
|||||||
<string name="hint_optional">(optional)</string>
|
<string name="hint_optional">(optional)</string>
|
||||||
<string name="hint_random">(random)</string>
|
<string name="hint_random">(random)</string>
|
||||||
<string name="import_error">Unable to import tunnel: %s</string>
|
<string name="import_error">Unable to import tunnel: %s</string>
|
||||||
<string name="import_success">Successfully imported “%s”</string>
|
<string name="import_success">Imported “%s”</string>
|
||||||
|
<string name="import_total_success">Imported %d tunnels</string>
|
||||||
|
<string name="import_partial_success">Imported %d of %d tunnels</string>
|
||||||
<string name="interface_title">Interface</string>
|
<string name="interface_title">Interface</string>
|
||||||
<string name="listen_port">Listen port</string>
|
<string name="listen_port">Listen port</string>
|
||||||
<string name="mtu">MTU</string>
|
<string name="mtu">MTU</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user