package org.fdroid.fdroid.compat;
import android.annotation.TargetApi;
import android.os.Build;
import android.system.ErrnoException;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.SanitizedFile;
import java.io.IOException;
import java.lang.reflect.Method;
/**
* This class works only with {@link SanitizedFile} instances to enforce
* filtering of the file names from files downloaded from the internet.
* This helps prevent things like SQL injection, shell command injection
* and other attacks based on putting various characters into filenames.
*/
public class FileCompat {
private static final String TAG = "FileCompat";
public static boolean symlink(SanitizedFile source, SanitizedFile dest) {
if (Build.VERSION.SDK_INT >= 21) {
symlinkOs(source, dest);
} else if (Build.VERSION.SDK_INT >= 15) {
symlinkLibcore(source, dest);
} else {
symlinkRuntime(source, dest);
}
return dest.exists();
}
/**
* Moved into a separate class rather than just a method, so that phones without API 21 will
* not attempt to load this class at runtime. Otherwise, using the Os.symlink method will cause
* a VerifyError to be thrown at runtime when the FileCompat class is first used.
*/
private static class Symlink21 {
@TargetApi(21)
void symlink(SanitizedFile source, SanitizedFile dest) {
try {
android.system.Os.symlink(source.getAbsolutePath(), dest.getAbsolutePath());
} catch (ErrnoException e) {
// Do nothing...
}
}
}
@TargetApi(21)
static void symlinkOs(SanitizedFile source, SanitizedFile dest) {
new Symlink21().symlink(source, dest);
}
static void symlinkRuntime(SanitizedFile source, SanitizedFile dest) {
String[] commands = {
FDroidApp.SYSTEM_DIR_NAME + "/bin/ln",
"-s",
source.getAbsolutePath(),
dest.getAbsolutePath(),
};
try {
Utils.debugLog(TAG, "Executing command: " + commands[0] + " " + commands[1]
+ " " + commands[2] + " " + commands[3]);
Process proc = Runtime.getRuntime().exec(commands);
Utils.consumeStream(proc.getInputStream());
Utils.consumeStream(proc.getErrorStream());
} catch (IOException e) {
// Do nothing
}
}
static void symlinkLibcore(SanitizedFile source, SanitizedFile dest) {
try {
Object os = Class.forName("libcore.io.Libcore").getField("os").get(null);
Method symlink = os.getClass().getMethod("symlink", String.class, String.class);
symlink.invoke(os, source.getAbsolutePath(), dest.getAbsolutePath());
} catch (Exception e) {
// Should catch more specific exceptions than just "Exception" here, but there are
// some which come from libcore.io.Libcore, which we don't have access to at compile time.
Log.e(TAG, "Could not symlink " + source.getAbsolutePath() + " to " + dest.getAbsolutePath(), e);
}
}
}