-
-
Notifications
You must be signed in to change notification settings - Fork 30
/
main.go
157 lines (137 loc) · 6.76 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package installer
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path"
"strings"
"github.com/vedantmgoyal2009/vedantmgoyal2009/winget-pkgs-automation/internal/msi"
)
var cache_installerInfo = map[string]map[string]string{}
func DownloadAndGetInfo(installerUrl string) map[string]string {
if installerInfo, ok := cache_installerInfo[installerUrl]; ok {
return installerInfo
}
installerUrl, err := url.QueryUnescape(installerUrl)
if err != nil {
panic(fmt.Errorf("error while unescaping installerUrl: %v", err))
}
resp, err := http.Get(installerUrl)
if err != nil {
panic(fmt.Errorf("error while downloading installer: %v", err))
}
// a non-2xx response doesn't cause an error, so we need to check it manually
if resp.StatusCode >= 400 && resp.StatusCode <= 599 { // 400-499: client error; 500-599: server error;
panic(fmt.Errorf("installer url is invalid. status code: %d", resp.StatusCode))
}
defer resp.Body.Close()
tempDir, err := os.MkdirTemp("", "winget-pkgs-automation-*")
if err != nil {
panic(fmt.Errorf("error while creating temp dir for downloading installer: %v", err))
}
defer os.RemoveAll(tempDir)
installerPath := path.Join(tempDir, path.Base(installerUrl))
out, err := os.Create(installerPath)
if err != nil {
panic(fmt.Errorf("error while creating installer file: %v", err))
}
// using `defer` keyword executes the function when this function has reached its end, but
// in this case, we need to close the file just after writing response body to it, so that
// msi.dll (syscall) can access the file and open msi database for reading values from it
// defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
panic(fmt.Errorf("error while writing installer to file: %v", err))
}
out.Close() // closing the file here
installerInfo := make(map[string]string)
installerInfo["InstallerUrl"] = installerUrl
installerInfo["InstallerSha256"] = getFileSha256Hash(installerPath)
installerInfo["InstallerType"] = getInstallerType(installerPath)
// get scope from url, (warning) may result in false positives
if strings.Contains(strings.ToLower(installerUrl), "user") {
installerInfo["Scope"] = "user"
} else if strings.Contains(strings.ToLower(installerUrl), "machine") {
installerInfo["Scope"] = "machine"
}
// get architecture from url, if not found, get it from pe header
installerInfo["Architecture"] = getArchitectureFromUrl(installerUrl)
if installerInfo["Architecture"] == "" {
installerInfo["Architecture"] = getArchitectureFromPeHeader(installerPath)
}
switch installerInfo["InstallerType"] {
case "msi", "wix":
installerInfo["Publisher"], _ = msi.GetProperty("Manufacturer", installerPath)
installerInfo["PackageVersion"], _ = msi.GetProperty("ProductVersion", installerPath)
installerInfo["ProductCode"], _ = msi.GetProperty("ProductCode", installerPath)
installerInfo["UpgradeCode"], _ = msi.GetProperty("UpgradeCode", installerPath)
installerInfo["InstallerLocale"], _ = msi.GetLocale(installerPath)
installerInfo["Scope"], _ = msi.GetScope(installerPath)
case "msix", "appx":
installerInfo["PackageFamilyName"] = getMsixPackageFamilyName(installerPath, tempDir)
installerInfo["SignatureSha256"] = getFileSha256Hash(extractFileFromZip(installerPath, "AppxSignature.p7x", tempDir))
default:
// TODO: Get package version from exe (fix this...)
var cmdOutput bytes.Buffer
command := exec.Command("pwsh", "-Command", fmt.Sprintf("(Get-Item '%s').VersionInfo.ProductVersion.ToString()", installerPath))
command.Stdout = &cmdOutput
command.Run()
installerInfo["PackageVersion"] = strings.TrimSpace(cmdOutput.String())
}
for key, value := range installerInfo {
if value == "" { // delete empty values
delete(installerInfo, key)
}
}
cache_installerInfo[installerUrl] = installerInfo
return installerInfo
}
func getInstallerType(installerPath string) string {
switch strings.ToLower(path.Ext(installerPath)) {
case ".msix", ".msixbundle":
return "msix"
case ".appx", ".appxbundle":
return "appx"
case ".zip":
return "zip"
case ".msi":
if _, err := msi.GetProperty("WixUI_Mode", installerPath); err == nil {
return "wix"
}
return "msi"
case ".exe":
file, err := os.Open(installerPath)
if err != nil {
panic(fmt.Errorf("[installer-type] error while opening exe: %v", err))
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
panic(fmt.Errorf("[installer-type] error while getting exe file info: %v", err))
}
magicBytes := make([]byte, fileInfo.Size())
if _, err = file.Read(magicBytes); err != nil {
panic(fmt.Errorf("[installer-type] error while reading exe file: %v", err))
}
if bytes.HasPrefix(magicBytes, nullsoftBytes) {
return "nullsoft"
} else if bytes.HasPrefix(magicBytes, innoBytes) {
return "inno"
} else if bytes.Contains(magicBytes, burnBytes) {
return "burn"
} else {
return "exe"
}
default:
panic(fmt.Errorf("[installer-type] unknown installer type"))
}
}
var (
burnBytes = []byte{0x2E, 0x77, 0x69, 0x78, 0x62, 0x75, 0x72, 0x6E}
innoBytes = []byte{77, 90, 80, 0, 2, 0, 0, 0, 4, 0, 15, 0, 255, 255, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 64, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 186, 16, 0, 14, 31, 180, 9, 205, 33, 184, 1, 76, 205, 33, 144, 144, 84, 104, 105, 115, 32, 112, 114, 111, 103, 114, 97, 109, 32, 109, 117, 115, 116, 32, 98, 101, 32, 114, 117, 110, 32, 117, 110, 100, 101, 114, 32, 87, 105, 110, 51, 50, 13, 10, 36, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 69, 0, 0, 76, 1, 10, 0}
nullsoftBytes = []byte{77, 90, 144, 0, 3, 0, 0, 0, 4, 0, 0, 0, 255, 255, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 14, 31, 186, 14, 0, 180, 9, 205, 33, 184, 1, 76, 205, 33, 84, 104, 105, 115, 32, 112, 114, 111, 103, 114, 97, 109, 32, 99, 97, 110, 110, 111, 116, 32, 98, 101, 32, 114, 117, 110, 32, 105, 110, 32, 68, 79, 83, 32, 109, 111, 100, 101, 46, 13, 13, 10, 36, 0, 0, 0, 0, 0, 0, 0, 173, 49, 8, 129, 233, 80, 102, 210, 233, 80, 102, 210, 233, 80, 102, 210, 42, 95, 57, 210, 235, 80, 102, 210, 233, 80, 103, 210, 76, 80, 102, 210, 42, 95, 59, 210, 230, 80, 102, 210, 189, 115, 86, 210, 227, 80, 102, 210, 46, 86, 96, 210, 232, 80, 102, 210, 82, 105, 99, 104, 233, 80, 102, 210, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 69, 0, 0, 76, 1, 5, 0}
)