package org.fdroid.fdroid.compat;

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) {
        symlinkOs(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 {

        void symlink(SanitizedFile source, SanitizedFile dest) {
            try {
                android.system.Os.symlink(source.getAbsolutePath(), dest.getAbsolutePath());
            } catch (ErrnoException e) {
                // Do nothing...
            }
        }
    }

    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);
        }
    }
}
