import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class SimpanUnggahan {

    static final long BATAS_BYTE = 1_048_576L; // 1 MiB
    // List (bukan Set) agar urutan cetak deterministik.
    static final List<String> EKSTENSI_DIIZINKAN = List.of(".pdf", ".png", ".jpg");
    static final Path BASIS = Path.of("unggahan");

    record Unggahan(String namaAsli, String tipeMime, long ukuran) { }

    // ==== Versi RENTAN: nama asli dipakai langsung sebagai path tujuan ====
    static String targetRentan(Unggahan u) {
        // Tanpa normalize, tanpa pemeriksaan batas direktori.
        return BASIS.resolve(u.namaAsli()).toString();
    }

    static String statusRentan(Unggahan u) {
        Path target = BASIS.resolve(u.namaAsli()).normalize();
        boolean diDalam = target.startsWith(BASIS);
        return diDalam ? "di dalam basis (kebetulan tidak bahaya)"
                : "KELUAR DARI BASIS (penulisan lolos ke luar unggahan/)";
    }

    // ==== Versi AMAN: periksa ukuran, ekstensi, dan batas direktori ====
    static class PenyimpanAman {

        private final Map<String, Long> tersimpan = new LinkedHashMap<>();
        private int urut = 0;

        String simpan(Unggahan u) {
            // Normalisasi menuntaskan semua "." dan ".." sebelum diuji batas.
            Path target = BASIS.resolve(u.namaAsli()).normalize();
            if (!target.startsWith(BASIS)) {
                return "DITOLAK: path keluar dari basis penyimpanan";
            }
            if (u.ukuran() > BATAS_BYTE) {
                return "DITOLAK: ukuran " + ukuranTeks(u.ukuran())
                        + " melebihi batas " + ukuranTeks(BATAS_BYTE);
            }
            String ekstensi = ekstensiDari(target.getFileName().toString());
            if (!EKSTENSI_DIIZINKAN.contains(ekstensi)) {
                return "DITOLAK: ekstensi " + ekstensi + " tidak ada di daftar izin";
            }
            // Nama baru dari server: penyerang tidak memilih nama akhir di disk.
            String namaBaru = String.format("berkas-%03d%s", ++urut, ekstensi);
            tersimpan.put(namaBaru, u.ukuran());
            return "DITERIMA sebagai " + namaBaru + " (" + ukuranTeks(u.ukuran()) + ")";
        }

        Map<String, Long> daftar() {
            return tersimpan;
        }
    }

    public static void main(String[] args) {
        PenyimpanAman penyimpan = new PenyimpanAman();
        Unggahan[] uji = {
                new Unggahan("laporan-q1.pdf", "application/pdf", 122_880L),
                new Unggahan("../../etc/konfig-penting", "text/plain", 200L),
                new Unggahan("/etc/penting", "text/plain", 200L),
                new Unggahan("skrip.bat", "application/octet-stream", 500L),
                new Unggahan("foto-liburan.jpg", "image/jpeg", 2_097_152L)
        };

        System.out.println("Basis penyimpanan: " + BASIS + "/  batas=" + ukuranTeks(BATAS_BYTE)
                + "  ekstensi=" + EKSTENSI_DIIZINKAN);
        System.out.println();

        for (int i = 0; i < uji.length; i++) {
            Unggahan u = uji[i];
            System.out.printf("Unggahan %d: namaAsli=\"%s\" tipe=%s ukuran=%s%n",
                    i + 1, u.namaAsli(), u.tipeMime(), ukuranTeks(u.ukuran()));
            System.out.println("  [rentan] target=" + targetRentan(u)
                    + " -> " + statusRentan(u));
            System.out.println("  [aman]   " + penyimpan.simpan(u));
            System.out.println();
        }

        System.out.println("Isi penyimpanan aman di akhir sesi:");
        for (Map.Entry<String, Long> e : penyimpan.daftar().entrySet()) {
            System.out.println("  " + e.getKey() + " (" + ukuranTeks(e.getValue()) + ")");
        }
    }

    // Ambil ekstensi kecil (lowercase) dari segmen nama terakhir.
    static String ekstensiDari(String nama) {
        int titik = nama.lastIndexOf('.');
        if (titik < 0) {
            return "";
        }
        return nama.substring(titik).toLowerCase(Locale.ROOT);
    }

    static String ukuranTeks(long bytes) {
        return bytes < 1024 ? bytes + " B" : (bytes / 1024) + " KB";
    }
}
