mirror of
https://github.com/coreybutler/nvm-windows.git
synced 2026-01-15 07:04:31 +08:00
348 lines
7.8 KiB
Go
348 lines
7.8 KiB
Go
package web
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"nvm/arch"
|
|
"nvm/file"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"archive/zip"
|
|
|
|
"github.com/blang/semver"
|
|
fs "github.com/coreybutler/go-fsutil"
|
|
)
|
|
|
|
var client = &http.Client{}
|
|
var nodeBaseAddress = "https://nodejs.org/dist/"
|
|
var npmBaseAddress = "https://github.com/npm/cli/archive/"
|
|
|
|
// var oldNpmBaseAddress = "https://github.com/npm/npm/archive/"
|
|
|
|
func SetProxy(p string, verifyssl bool) {
|
|
if p != "" && p != "none" {
|
|
proxyUrl, _ := url.Parse(p)
|
|
client = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl), TLSClientConfig: &tls.Config{InsecureSkipVerify: verifyssl}}}
|
|
} else {
|
|
client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: verifyssl}}}
|
|
}
|
|
}
|
|
|
|
func SetMirrors(node_mirror string, npm_mirror string) {
|
|
if node_mirror != "" && node_mirror != "none" {
|
|
nodeBaseAddress = node_mirror
|
|
if strings.ToLower(nodeBaseAddress[0:4]) != "http" {
|
|
nodeBaseAddress = "http://" + nodeBaseAddress
|
|
}
|
|
if !strings.HasSuffix(nodeBaseAddress, "/") {
|
|
nodeBaseAddress = nodeBaseAddress + "/"
|
|
}
|
|
}
|
|
if npm_mirror != "" && npm_mirror != "none" {
|
|
npmBaseAddress = npm_mirror
|
|
if strings.ToLower(npmBaseAddress[0:4]) != "http" {
|
|
npmBaseAddress = "http://" + npmBaseAddress
|
|
}
|
|
if !strings.HasSuffix(npmBaseAddress, "/") {
|
|
npmBaseAddress = npmBaseAddress + "/"
|
|
}
|
|
}
|
|
}
|
|
|
|
func GetFullNodeUrl(path string) string {
|
|
return nodeBaseAddress + path
|
|
}
|
|
|
|
func GetFullNpmUrl(path string) string {
|
|
return npmBaseAddress + path
|
|
}
|
|
|
|
func Download(url string, target string, version string) bool {
|
|
output, err := os.Create(target)
|
|
if err != nil {
|
|
fmt.Println("Error while creating", target, "-", err)
|
|
return false
|
|
}
|
|
defer output.Close()
|
|
|
|
response, err := client.Get(url)
|
|
if err != nil {
|
|
fmt.Println("Error while downloading", url, "-", err)
|
|
}
|
|
defer response.Body.Close()
|
|
c := make(chan os.Signal, 2)
|
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
go func() {
|
|
<-c
|
|
fmt.Println("Download interrupted.Rolling back...")
|
|
output.Close()
|
|
response.Body.Close()
|
|
var err error
|
|
if strings.Contains(target, "node") {
|
|
err = os.RemoveAll(os.Getenv("NVM_HOME") + "\\v" + version)
|
|
} else {
|
|
err = os.Remove(target)
|
|
}
|
|
if err != nil {
|
|
fmt.Println("Error while rolling back", err)
|
|
}
|
|
os.Exit(1)
|
|
}()
|
|
_, err = io.Copy(output, response.Body)
|
|
if err != nil {
|
|
fmt.Println("Error while downloading", url, "-", err)
|
|
}
|
|
if response.Status[0:3] != "200" {
|
|
fmt.Println("Download failed. Rolling Back.")
|
|
err := os.Remove(target)
|
|
if err != nil {
|
|
fmt.Println("Rollback failed.", err)
|
|
}
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func GetNodeJS(root string, v string, a string, append bool) bool {
|
|
a = arch.Validate(a)
|
|
|
|
vpre := ""
|
|
vers := strings.Fields(strings.Replace(v, ".", " ", -1))
|
|
main, _ := strconv.ParseInt(vers[0], 0, 0)
|
|
|
|
if a == "32" {
|
|
if main > 0 {
|
|
vpre = "win-x86/"
|
|
} else {
|
|
vpre = ""
|
|
}
|
|
} else if a == "64" {
|
|
if main > 0 {
|
|
vpre = "win-x64/"
|
|
} else {
|
|
vpre = "x64/"
|
|
}
|
|
}
|
|
|
|
url := getNodeUrl(v, vpre, a, append)
|
|
|
|
if url == "" {
|
|
//No url should mean this version/arch isn't available
|
|
fmt.Println("Node.js v" + v + " " + a + "bit isn't available right now.")
|
|
} else {
|
|
fileName := root + "\\v" + v + "\\node" + a + ".exe"
|
|
if strings.HasSuffix(url, ".zip") {
|
|
fileName = root + "\\v" + v + "\\node.zip"
|
|
}
|
|
|
|
fmt.Println("Downloading node.js version " + v + " (" + a + "-bit)... ")
|
|
|
|
if Download(url, fileName, v) {
|
|
// Extract the zip file
|
|
if strings.HasSuffix(url, ".zip") {
|
|
fmt.Println("Extracting...")
|
|
err := unzip(fileName, root+"\\v"+v)
|
|
if err != nil {
|
|
fmt.Println("Error extracting from Node archive: " + err.Error())
|
|
|
|
err = os.Remove(fileName)
|
|
if err != nil {
|
|
fmt.Printf("Failed to remove %v after failed extraction. Please remove manually.", fileName)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
err = os.Remove(fileName)
|
|
if err != nil {
|
|
fmt.Printf("Failed to remove %v after successful extraction. Please remove manually.", fileName)
|
|
}
|
|
|
|
zip := root + "\\v" + v + "\\" + strings.Replace(filepath.Base(url), ".zip", "", 1)
|
|
err = fs.Move(zip, root+"\\v"+v, true)
|
|
if err != nil {
|
|
fmt.Println("ERROR moving file: " + err.Error())
|
|
}
|
|
|
|
err = os.RemoveAll(zip)
|
|
if err != nil {
|
|
fmt.Printf("Failed to remove %v after successful extraction. Please remove manually.", zip)
|
|
}
|
|
}
|
|
fmt.Printf("Complete\n")
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
|
|
}
|
|
|
|
func GetNpm(root string, v string) bool {
|
|
url := GetFullNpmUrl("v" + v + ".zip")
|
|
// temp directory to download the .zip file
|
|
tempDir := root + "\\temp"
|
|
|
|
// if the temp directory doesn't exist, create it
|
|
if !file.Exists(tempDir) {
|
|
fmt.Println("Creating " + tempDir + "\n")
|
|
err := os.Mkdir(tempDir, os.ModePerm)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
fileName := tempDir + "\\" + "npm-v" + v + ".zip"
|
|
|
|
fmt.Printf("Downloading npm version " + v + "... ")
|
|
if Download(url, fileName, v) {
|
|
fmt.Printf("Complete\n")
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func GetRemoteTextFile(url string) string {
|
|
response, httperr := client.Get(url)
|
|
if httperr != nil {
|
|
fmt.Println("\nCould not retrieve " + url + ".\n\n")
|
|
fmt.Printf("%s", httperr)
|
|
os.Exit(1)
|
|
} else {
|
|
defer response.Body.Close()
|
|
contents, readerr := ioutil.ReadAll(response.Body)
|
|
if readerr != nil {
|
|
fmt.Printf("%s", readerr)
|
|
os.Exit(1)
|
|
}
|
|
return string(contents)
|
|
}
|
|
os.Exit(1)
|
|
return ""
|
|
}
|
|
|
|
func IsNode64bitAvailable(v string) bool {
|
|
if v == "latest" {
|
|
return true
|
|
}
|
|
|
|
// Anything below version 8 doesn't have a 64 bit version
|
|
vers := strings.Fields(strings.Replace(v, ".", " ", -1))
|
|
main, _ := strconv.ParseInt(vers[0], 0, 0)
|
|
minor, _ := strconv.ParseInt(vers[1], 0, 0)
|
|
if main == 0 && minor < 8 {
|
|
return false
|
|
}
|
|
|
|
// TODO: fixme. Assume a 64 bit version exists
|
|
return true
|
|
}
|
|
|
|
func getNodeUrl(v string, vpre string, arch string, append bool) string {
|
|
a := "x86"
|
|
if arch == "64" {
|
|
a = "x64"
|
|
}
|
|
|
|
//url := "http://nodejs.org/dist/v"+v+"/" + vpre + "/node.exe"
|
|
url := GetFullNodeUrl("v" + v + "/" + vpre + "/node.exe")
|
|
|
|
if !append {
|
|
version, err := semver.Make(v)
|
|
if err != nil {
|
|
fmt.Println("Node.js v" + v + " " + a + "bit isn't available right now.")
|
|
fmt.Println(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
corepack, _ := semver.Make("16.9.0")
|
|
|
|
if version.GTE(corepack) {
|
|
url = GetFullNodeUrl("v" + v + "/node-v" + v + "-win-" + a + ".zip")
|
|
}
|
|
}
|
|
|
|
// Check online to see if a 64 bit version exists
|
|
_, err := client.Head(url)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return url
|
|
}
|
|
|
|
func unzip(src, dest string) error {
|
|
r, err := zip.OpenReader(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := r.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
os.MkdirAll(dest, 0755)
|
|
|
|
// Closure to address file descriptors issue with all the deferred .Close() methods
|
|
extractAndWriteFile := func(f *zip.File) error {
|
|
rc, err := f.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := rc.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
path := filepath.Join(dest, f.Name)
|
|
|
|
// Check for ZipSlip (Directory traversal)
|
|
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
|
return fmt.Errorf("illegal file path: %s", path)
|
|
}
|
|
|
|
if f.FileInfo().IsDir() {
|
|
os.MkdirAll(path, f.Mode())
|
|
} else {
|
|
os.MkdirAll(filepath.Dir(path), f.Mode())
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := f.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
_, err = io.Copy(f, rc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
for _, f := range r.File {
|
|
err := extractAndWriteFile(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|