package functions import ( "encoding/base64" "fmt" "io" "os" "path/filepath" "strings" "github.com/dop251/goja" "github.com/j3ssie/osmedeus/v5/internal/config" "github.com/j3ssie/osmedeus/v5/internal/installer" "github.com/j3ssie/osmedeus/v5/internal/logger" "github.com/j3ssie/osmedeus/v5/internal/terminal" "go.uber.org/zap" ) // goGetter downloads files/repos using go-getter // Usage: go_getter(url, dest) -> bool // url: source URL (supports git repos, archives, files) // dest: destination path func (vf *vmFunc) goGetter(call goja.FunctionCall) goja.Value { logger.Get().Debug("Calling " + terminal.HiGreen("go_getter")) if len(call.Arguments) < 2 { logger.Get().Warn("go_getter: requires 2 arguments") return vf.vm.ToValue(false) } url := call.Argument(0).String() dest := call.Argument(1).String() if url == "" || url == "undefined" { logger.Get().Warn("go_getter: url cannot be empty") return vf.vm.ToValue(false) } if dest == "" || dest == "undefined" { logger.Get().Warn("go_getter: dest cannot be empty") return vf.vm.ToValue(false) } logger.Get().Debug(terminal.HiGreen("go_getter")+" params", zap.String("url", url), zap.String("dest", dest)) err := installer.GetViaGoGetter(url, dest) if err != nil { logger.Get().Warn("go_getter: download failed", zap.Error(err)) return vf.vm.ToValue(false) } return vf.vm.ToValue(true) } // goGetterWithSSHKey downloads git repos using go-getter with SSH key authentication // Usage: go_getter_with_sshkey(ssh_key_path, git_url, dest) -> bool // ssh_key_path: path to SSH private key file // git_url: git repository URL (will be prefixed with git:: if needed) // dest: destination path // The SSH key is base64 encoded and appended as ?sshkey= query parameter func (vf *vmFunc) goGetterWithSSHKey(call goja.FunctionCall) goja.Value { logger.Get().Debug("Calling " + terminal.HiGreen("go_getter_with_sshkey")) if len(call.Arguments) < 3 { logger.Get().Warn("go_getter_with_sshkey: requires 3 arguments (ssh_key_path, git_url, dest)") return vf.vm.ToValue(false) } sshKeyPath := call.Argument(0).String() gitURL := call.Argument(1).String() dest := call.Argument(2).String() // Validate arguments if sshKeyPath == "" || sshKeyPath == "undefined" { logger.Get().Warn("go_getter_with_sshkey: ssh_key_path cannot be empty") return vf.vm.ToValue(false) } if gitURL == "" || gitURL == "undefined" { logger.Get().Warn("go_getter_with_sshkey: git_url cannot be empty") return vf.vm.ToValue(false) } if dest == "" || dest == "undefined" { logger.Get().Warn("go_getter_with_sshkey: dest cannot be empty") return vf.vm.ToValue(false) } // Expand path (handle ~ and env vars) sshKeyPath = installer.ExpandPath(sshKeyPath) // Read SSH key file keyContent, err := os.ReadFile(sshKeyPath) if err != nil { logger.Get().Warn("go_getter_with_sshkey: failed to read SSH key file", zap.String("path", sshKeyPath), zap.Error(err)) return vf.vm.ToValue(false) } // Base64 encode the key encodedKey := base64.StdEncoding.EncodeToString(keyContent) // Build the URL with sshkey parameter // go-getter format: git::git@github.com:user/repo.git?sshkey= separator := "?" if strings.Contains(gitURL, "?") { separator = "&" } // Add git:: prefix if not present and URL looks like SSH if strings.HasPrefix(gitURL, "git@") && !strings.HasPrefix(gitURL, "git::") { gitURL = "git::" + gitURL } fullURL := gitURL + separator + "sshkey=" + encodedKey logger.Get().Debug(terminal.HiGreen("go_getter_with_sshkey")+" params", zap.String("ssh_key_path", sshKeyPath), zap.String("git_url", gitURL), zap.String("dest", dest), zap.Int("key_length", len(keyContent))) // Call go-getter with the modified URL err = installer.GetViaGoGetter(fullURL, dest) if err != nil { logger.Get().Warn("go_getter_with_sshkey: download failed", zap.Error(err)) return vf.vm.ToValue(false) } return vf.vm.ToValue(true) } // nixInstall installs a package using Nix // Usage: nix_install(package, dest?) -> bool // package: Nix package name (e.g., "nuclei", "subfinder") // dest: optional destination folder for binary copy (default: no copy) func (vf *vmFunc) nixInstall(call goja.FunctionCall) goja.Value { logger.Get().Debug("Calling " + terminal.HiGreen("nix_install")) if len(call.Arguments) < 1 { logger.Get().Warn("nix_install: requires at least 1 argument") return vf.vm.ToValue(false) } pkg := call.Argument(0).String() if pkg == "" || pkg == "undefined" { logger.Get().Warn("nix_install: package cannot be empty") return vf.vm.ToValue(false) } // Optional destination folder dest := "" if len(call.Arguments) >= 2 { destArg := call.Argument(1).String() if destArg != "" && destArg != "undefined" { dest = destArg } } logger.Get().Debug(terminal.HiGreen("nix_install")+" params", zap.String("package", pkg), zap.String("dest", dest)) // Check if Nix is installed if !installer.IsNixInstalled() { logger.Get().Warn("nix_install: Nix is not installed") return vf.vm.ToValue(false) } // Install the package via Nix err := installer.InstallBinaryViaNix(pkg, pkg, dest) if err != nil { logger.Get().Warn("nix_install: installation failed", zap.Error(err)) return vf.vm.ToValue(false) } return vf.vm.ToValue(true) } // filepathInstaller installs a local file (archive or binary) to the binaries folder // Usage: filepath_installer(local_path, tool_name, dest?) -> bool // local_path: path to a local .tar.gz, .zip, .gz, or executable binary file // tool_name: name for the installed binary (used when archive contains files with different names) // dest: optional destination directory // // Defaults to external_binaries_path ({{base_folder}}/external-binaries) from config func (vf *vmFunc) filepathInstaller(call goja.FunctionCall) goja.Value { logger.Get().Debug("Calling " + terminal.HiGreen(FnFilepathInstaller)) if len(call.Arguments) < 2 { logger.Get().Warn("filepath_installer: requires at least 2 arguments: local_path, tool_name") return vf.vm.ToValue(false) } localPath := call.Argument(0).String() toolName := call.Argument(1).String() // Validate required args if localPath == "" || localPath == "undefined" { logger.Get().Warn("filepath_installer: local_path cannot be empty") return vf.vm.ToValue(false) } if toolName == "" || toolName == "undefined" { logger.Get().Warn("filepath_installer: tool_name cannot be empty") return vf.vm.ToValue(false) } // Optional destination directory dest := "" if len(call.Arguments) >= 3 { dest = call.Argument(2).String() if dest == "undefined" { dest = "" } } // Default destination: external_binaries_path ({{base_folder}}/external-binaries) // Priority: 1) explicit dest param, 2) Binaries from VM context, 3) config.BinariesPath if dest == "" { if val := vf.vm.Get("Binaries"); val != nil && val != goja.Undefined() { dest = val.String() } } // Fallback to global config's external_binaries_path if dest == "" { cfg := config.Get() if cfg != nil && cfg.BinariesPath != "" { dest = cfg.BinariesPath } } if dest == "" { logger.Get().Warn("filepath_installer: destination directory not specified and Binaries path not available (ensure config is loaded)") return vf.vm.ToValue(false) } // Check if source file exists if _, err := os.Stat(localPath); os.IsNotExist(err) { logger.Get().Warn("filepath_installer: source file not found", zap.String("path", localPath)) return vf.vm.ToValue(false) } // Ensure destination directory exists if err := os.MkdirAll(dest, 0755); err != nil { logger.Get().Warn("filepath_installer: failed to create destination directory", zap.String("dest", dest), zap.Error(err)) return vf.vm.ToValue(false) } destPath := filepath.Join(dest, toolName) // Detect archive type archiveType := installer.DetectArchiveType(localPath) logger.Get().Debug(terminal.HiGreen(FnFilepathInstaller)+" params", zap.String("local_path", localPath), zap.String("tool_name", toolName), zap.String("dest", dest), zap.String("archive_type", archiveType)) switch archiveType { case "tar.gz": // Extract to temp dir, find binary, copy to dest if err := extractAndInstallBinary(localPath, toolName, destPath, installer.ExtractTarGz); err != nil { logger.Get().Warn("filepath_installer: failed to extract tar.gz", zap.String("path", localPath), zap.Error(err)) return vf.vm.ToValue(false) } case "zip": // Extract to temp dir, find binary, copy to dest if err := extractAndInstallBinary(localPath, toolName, destPath, installer.ExtractZip); err != nil { logger.Get().Warn("filepath_installer: failed to extract zip", zap.String("path", localPath), zap.Error(err)) return vf.vm.ToValue(false) } case "gz": // Single gzip file - extract directly to destination if err := installer.ExtractGz(localPath, destPath); err != nil { logger.Get().Warn("filepath_installer: failed to extract gz", zap.String("path", localPath), zap.Error(err)) return vf.vm.ToValue(false) } // ExtractGz already sets 0755 permissions default: // Assume it's a binary - copy directly if err := copyBinaryFile(localPath, destPath); err != nil { logger.Get().Warn("filepath_installer: failed to copy binary", zap.String("src", localPath), zap.String("dest", destPath), zap.Error(err)) return vf.vm.ToValue(false) } // Set executable permissions if err := os.Chmod(destPath, 0755); err != nil { logger.Get().Warn("filepath_installer: failed to set executable permissions", zap.String("path", destPath), zap.Error(err)) return vf.vm.ToValue(false) } } logger.Get().Info("filepath_installer: successfully installed", zap.String("tool", toolName), zap.String("dest", destPath)) return vf.vm.ToValue(true) } // extractAndInstallBinary extracts an archive to a temp dir, finds the binary, and copies it to destPath func extractAndInstallBinary(archivePath, toolName, destPath string, extractFn func(string, string) error) error { // Create temp directory for extraction tmpDir, err := os.MkdirTemp("", "filepath_installer_*") if err != nil { return fmt.Errorf("failed to create temp directory: %w", err) } defer func() { _ = os.RemoveAll(tmpDir) }() // Extract archive if err := extractFn(archivePath, tmpDir); err != nil { return fmt.Errorf("extraction failed: %w", err) } // Find the binary - try exact name first, then search binaryPath := findBinaryInDir(tmpDir, toolName) if binaryPath == "" { return fmt.Errorf("could not find binary '%s' in extracted archive", toolName) } // Copy binary to destination if err := copyBinaryFile(binaryPath, destPath); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } // Set executable permissions if err := os.Chmod(destPath, 0755); err != nil { return fmt.Errorf("failed to set executable permissions: %w", err) } return nil } // findBinaryInDir searches for a binary in the extracted directory func findBinaryInDir(dir, toolName string) string { // First, try exact match at root level exactPath := filepath.Join(dir, toolName) if info, err := os.Stat(exactPath); err == nil && !info.IsDir() { return exactPath } // Walk directory to find the binary var found string _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } // Match by name if info.Name() == toolName { found = path return filepath.SkipAll } return nil }) return found } // copyBinaryFile copies a file from src to dst func copyBinaryFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer func() { _ = sourceFile.Close() }() destFile, err := os.Create(dst) if err != nil { return err } defer func() { _ = destFile.Close() }() _, err = io.Copy(destFile, sourceFile) return err }