Loading go_port/main.go +99 −35 Original line number Diff line number Diff line Loading @@ -1158,15 +1158,30 @@ func isInteractiveTTY(f *os.File) bool { } func ensureJava() (string, error) { if javaBin, err := exec.LookPath("java"); err == nil { return javaBin, nil } curSys := currentSystem() suitableJDK := findJDK(curSys) if suitableJDK != "" { if suitableJDK == "" { return "", errors.New(T("err.java_required")) } destinationFolder := filepath.Join(userMaxDir, "jdk") if !dirExists(destinationFolder) { javaBin := bundledJavaPath(destinationFolder, curSys) if javaBin == "" { return "", errors.New(T("err.java_required")) } if fileExists(javaBin) { if err := ensureBundledJavaRuntimeExecutables(javaBin); err != nil { return "", err } return javaBin, nil } _ = os.RemoveAll(destinationFolder) if err := os.MkdirAll(destinationFolder, 0o755); err != nil { return "", err } destinationArchive := filepath.Join(userMaxDir, filepath.Base(suitableJDK)) if err := cachedDownload(suitableJDK, destinationArchive); err != nil { return "", err Loading @@ -1174,25 +1189,27 @@ func ensureJava() (string, error) { if err := extractArchive(destinationArchive, destinationFolder); err != nil { return "", err } } var javaBin string switch curSys { case "linux/x86_64", "linux/amd64": javaBin = filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java") case "windows/x86_64", "windows/amd64": javaBin = filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java.exe") case "darwin/arm64": javaBin = filepath.Join(destinationFolder, "jdk-22.0.2.jdk", "Contents", "Home", "bin", "java") if !fileExists(javaBin) { return "", errors.New(T("err.java_required")) } if fileExists(javaBin) { if err := ensureExecutable(javaBin); err != nil { if err := ensureBundledJavaRuntimeExecutables(javaBin); err != nil { return "", err } return javaBin, nil } func bundledJavaPath(destinationFolder, curSys string) string { switch curSys { case "linux/x86_64", "linux/amd64", "linux/arm64": return filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java") case "windows/x86_64", "windows/amd64": return filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java.exe") case "darwin/arm64": return filepath.Join(destinationFolder, "jdk-22.0.2.jdk", "Contents", "Home", "bin", "java") default: return "" } return "", errors.New(T("err.java_required")) } func ensureExecutable(filePath string) error { Loading @@ -1211,6 +1228,23 @@ func ensureExecutable(filePath string) error { return os.Chmod(filePath, mode|0o111) } func ensureBundledJavaRuntimeExecutables(javaBin string) error { if err := ensureExecutable(javaBin); err != nil { return err } if currentOS() == "windows" { return nil } javaHome := filepath.Dir(filepath.Dir(javaBin)) jspawnhelper := filepath.Join(javaHome, "lib", "jspawnhelper") if fileExists(jspawnhelper) { if err := ensureExecutable(jspawnhelper); err != nil { return err } } return nil } func syncBundles(rootDirectory string) error { ignoreFile := ".ignore" installedFile := ".installed" Loading Loading @@ -2768,7 +2802,7 @@ func extractZip(src, destination string) error { return fmt.Errorf(T("err.invalid_zip_entry"), f.Name) } if f.FileInfo().IsDir() { if err := os.MkdirAll(cleanTarget, 0o755); err != nil { if err := os.MkdirAll(cleanTarget, effectivePerm(f.Mode(), 0o755)); err != nil { return err } continue Loading @@ -2780,7 +2814,7 @@ func extractZip(src, destination string) error { if err != nil { return err } out, err := os.Create(cleanTarget) out, err := os.OpenFile(cleanTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, effectivePerm(f.Mode(), 0o644)) if err != nil { in.Close() return err Loading Loading @@ -2827,14 +2861,14 @@ func extractTarGz(src, destination string) error { } switch hdr.Typeflag { case tar.TypeDir: if err := os.MkdirAll(cleanTarget, 0o755); err != nil { if err := os.MkdirAll(cleanTarget, effectivePerm(hdr.FileInfo().Mode(), 0o755)); err != nil { return err } case tar.TypeReg: case tar.TypeReg, tar.TypeRegA: if err := os.MkdirAll(filepath.Dir(cleanTarget), 0o755); err != nil { return err } out, err := os.Create(cleanTarget) out, err := os.OpenFile(cleanTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, effectivePerm(hdr.FileInfo().Mode(), 0o644)) if err != nil { return err } Loading @@ -2843,7 +2877,37 @@ func extractTarGz(src, destination string) error { return err } out.Close() case tar.TypeSymlink: if err := os.MkdirAll(filepath.Dir(cleanTarget), 0o755); err != nil { return err } _ = os.Remove(cleanTarget) if err := os.Symlink(hdr.Linkname, cleanTarget); err != nil { return err } case tar.TypeLink: linkTarget := filepath.Join(destination, hdr.Linkname) cleanLinkTarget := filepath.Clean(linkTarget) cleanDest := filepath.Clean(destination) if !strings.HasPrefix(cleanLinkTarget, cleanDest+string(os.PathSeparator)) && cleanLinkTarget != cleanDest { return fmt.Errorf(T("err.invalid_tar_entry"), hdr.Linkname) } if err := os.MkdirAll(filepath.Dir(cleanTarget), 0o755); err != nil { return err } _ = os.Remove(cleanTarget) if err := os.Link(cleanLinkTarget, cleanTarget); err != nil { return err } } } return nil } func effectivePerm(mode os.FileMode, fallback os.FileMode) os.FileMode { perm := mode.Perm() if perm == 0 { return fallback } return perm } go_port/main_test.go +56 −0 Original line number Diff line number Diff line package main import ( "archive/tar" "compress/gzip" "net/url" "os" "path/filepath" "runtime" "strings" "testing" ) Loading Loading @@ -102,3 +105,56 @@ func TestProjectsJSONStorageLifecycle(t *testing.T) { t.Fatalf("expected 0 project rows after deletion, got %d", len(rows)) } } func TestExtractTarGzPreservesExecutableBit(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("executable permission bits are not relevant on windows") } archivePath := filepath.Join(t.TempDir(), "jdk.tar.gz") archiveFile, err := os.Create(archivePath) if err != nil { t.Fatal(err) } gz := gzip.NewWriter(archiveFile) tw := tar.NewWriter(gz) content := []byte("#!/bin/sh\necho ok\n") hdr := &tar.Header{ Name: "jdk-22.0.2/bin/java", Mode: 0o755, Size: int64(len(content)), Typeflag: tar.TypeReg, } if err := tw.WriteHeader(hdr); err != nil { t.Fatal(err) } if _, err := tw.Write(content); err != nil { t.Fatal(err) } if err := tw.Close(); err != nil { t.Fatal(err) } if err := gz.Close(); err != nil { t.Fatal(err) } if err := archiveFile.Close(); err != nil { t.Fatal(err) } dest := t.TempDir() if err := extractTarGz(archivePath, dest); err != nil { t.Fatal(err) } extracted := filepath.Join(dest, "jdk-22.0.2", "bin", "java") info, err := os.Stat(extracted) if err != nil { t.Fatal(err) } if info.Mode()&0o111 == 0 { t.Fatalf("expected executable mode on %s, got %v", extracted, info.Mode()) } } Loading
go_port/main.go +99 −35 Original line number Diff line number Diff line Loading @@ -1158,15 +1158,30 @@ func isInteractiveTTY(f *os.File) bool { } func ensureJava() (string, error) { if javaBin, err := exec.LookPath("java"); err == nil { return javaBin, nil } curSys := currentSystem() suitableJDK := findJDK(curSys) if suitableJDK != "" { if suitableJDK == "" { return "", errors.New(T("err.java_required")) } destinationFolder := filepath.Join(userMaxDir, "jdk") if !dirExists(destinationFolder) { javaBin := bundledJavaPath(destinationFolder, curSys) if javaBin == "" { return "", errors.New(T("err.java_required")) } if fileExists(javaBin) { if err := ensureBundledJavaRuntimeExecutables(javaBin); err != nil { return "", err } return javaBin, nil } _ = os.RemoveAll(destinationFolder) if err := os.MkdirAll(destinationFolder, 0o755); err != nil { return "", err } destinationArchive := filepath.Join(userMaxDir, filepath.Base(suitableJDK)) if err := cachedDownload(suitableJDK, destinationArchive); err != nil { return "", err Loading @@ -1174,25 +1189,27 @@ func ensureJava() (string, error) { if err := extractArchive(destinationArchive, destinationFolder); err != nil { return "", err } } var javaBin string switch curSys { case "linux/x86_64", "linux/amd64": javaBin = filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java") case "windows/x86_64", "windows/amd64": javaBin = filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java.exe") case "darwin/arm64": javaBin = filepath.Join(destinationFolder, "jdk-22.0.2.jdk", "Contents", "Home", "bin", "java") if !fileExists(javaBin) { return "", errors.New(T("err.java_required")) } if fileExists(javaBin) { if err := ensureExecutable(javaBin); err != nil { if err := ensureBundledJavaRuntimeExecutables(javaBin); err != nil { return "", err } return javaBin, nil } func bundledJavaPath(destinationFolder, curSys string) string { switch curSys { case "linux/x86_64", "linux/amd64", "linux/arm64": return filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java") case "windows/x86_64", "windows/amd64": return filepath.Join(destinationFolder, "jdk-22.0.2", "bin", "java.exe") case "darwin/arm64": return filepath.Join(destinationFolder, "jdk-22.0.2.jdk", "Contents", "Home", "bin", "java") default: return "" } return "", errors.New(T("err.java_required")) } func ensureExecutable(filePath string) error { Loading @@ -1211,6 +1228,23 @@ func ensureExecutable(filePath string) error { return os.Chmod(filePath, mode|0o111) } func ensureBundledJavaRuntimeExecutables(javaBin string) error { if err := ensureExecutable(javaBin); err != nil { return err } if currentOS() == "windows" { return nil } javaHome := filepath.Dir(filepath.Dir(javaBin)) jspawnhelper := filepath.Join(javaHome, "lib", "jspawnhelper") if fileExists(jspawnhelper) { if err := ensureExecutable(jspawnhelper); err != nil { return err } } return nil } func syncBundles(rootDirectory string) error { ignoreFile := ".ignore" installedFile := ".installed" Loading Loading @@ -2768,7 +2802,7 @@ func extractZip(src, destination string) error { return fmt.Errorf(T("err.invalid_zip_entry"), f.Name) } if f.FileInfo().IsDir() { if err := os.MkdirAll(cleanTarget, 0o755); err != nil { if err := os.MkdirAll(cleanTarget, effectivePerm(f.Mode(), 0o755)); err != nil { return err } continue Loading @@ -2780,7 +2814,7 @@ func extractZip(src, destination string) error { if err != nil { return err } out, err := os.Create(cleanTarget) out, err := os.OpenFile(cleanTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, effectivePerm(f.Mode(), 0o644)) if err != nil { in.Close() return err Loading Loading @@ -2827,14 +2861,14 @@ func extractTarGz(src, destination string) error { } switch hdr.Typeflag { case tar.TypeDir: if err := os.MkdirAll(cleanTarget, 0o755); err != nil { if err := os.MkdirAll(cleanTarget, effectivePerm(hdr.FileInfo().Mode(), 0o755)); err != nil { return err } case tar.TypeReg: case tar.TypeReg, tar.TypeRegA: if err := os.MkdirAll(filepath.Dir(cleanTarget), 0o755); err != nil { return err } out, err := os.Create(cleanTarget) out, err := os.OpenFile(cleanTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, effectivePerm(hdr.FileInfo().Mode(), 0o644)) if err != nil { return err } Loading @@ -2843,7 +2877,37 @@ func extractTarGz(src, destination string) error { return err } out.Close() case tar.TypeSymlink: if err := os.MkdirAll(filepath.Dir(cleanTarget), 0o755); err != nil { return err } _ = os.Remove(cleanTarget) if err := os.Symlink(hdr.Linkname, cleanTarget); err != nil { return err } case tar.TypeLink: linkTarget := filepath.Join(destination, hdr.Linkname) cleanLinkTarget := filepath.Clean(linkTarget) cleanDest := filepath.Clean(destination) if !strings.HasPrefix(cleanLinkTarget, cleanDest+string(os.PathSeparator)) && cleanLinkTarget != cleanDest { return fmt.Errorf(T("err.invalid_tar_entry"), hdr.Linkname) } if err := os.MkdirAll(filepath.Dir(cleanTarget), 0o755); err != nil { return err } _ = os.Remove(cleanTarget) if err := os.Link(cleanLinkTarget, cleanTarget); err != nil { return err } } } return nil } func effectivePerm(mode os.FileMode, fallback os.FileMode) os.FileMode { perm := mode.Perm() if perm == 0 { return fallback } return perm }
go_port/main_test.go +56 −0 Original line number Diff line number Diff line package main import ( "archive/tar" "compress/gzip" "net/url" "os" "path/filepath" "runtime" "strings" "testing" ) Loading Loading @@ -102,3 +105,56 @@ func TestProjectsJSONStorageLifecycle(t *testing.T) { t.Fatalf("expected 0 project rows after deletion, got %d", len(rows)) } } func TestExtractTarGzPreservesExecutableBit(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("executable permission bits are not relevant on windows") } archivePath := filepath.Join(t.TempDir(), "jdk.tar.gz") archiveFile, err := os.Create(archivePath) if err != nil { t.Fatal(err) } gz := gzip.NewWriter(archiveFile) tw := tar.NewWriter(gz) content := []byte("#!/bin/sh\necho ok\n") hdr := &tar.Header{ Name: "jdk-22.0.2/bin/java", Mode: 0o755, Size: int64(len(content)), Typeflag: tar.TypeReg, } if err := tw.WriteHeader(hdr); err != nil { t.Fatal(err) } if _, err := tw.Write(content); err != nil { t.Fatal(err) } if err := tw.Close(); err != nil { t.Fatal(err) } if err := gz.Close(); err != nil { t.Fatal(err) } if err := archiveFile.Close(); err != nil { t.Fatal(err) } dest := t.TempDir() if err := extractTarGz(archivePath, dest); err != nil { t.Fatal(err) } extracted := filepath.Join(dest, "jdk-22.0.2", "bin", "java") info, err := os.Stat(extracted) if err != nil { t.Fatal(err) } if info.Mode()&0o111 == 0 { t.Fatalf("expected executable mode on %s, got %v", extracted, info.Mode()) } }