diff --git a/changes/24873-pkg-name b/changes/24873-pkg-name
new file mode 100644
index 000000000000..a15f6b616f39
--- /dev/null
+++ b/changes/24873-pkg-name
@@ -0,0 +1 @@
+- Added a fallback for extracting app name from .pkg installers that have default or incorrect title attributes in their distribution file.
\ No newline at end of file
diff --git a/pkg/file/testdata/distribution/distribution-cold-turkey.xml b/pkg/file/testdata/distribution/distribution-cold-turkey.xml
new file mode 100644
index 000000000000..a174d3585da6
--- /dev/null
+++ b/pkg/file/testdata/distribution/distribution-cold-turkey.xml
@@ -0,0 +1,228 @@
+
+
+
+
+
+ DISTRIBUTION_TITLE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #NMHFirefox.pkg
+ #NMHEdge.pkg
+ #NMHChrome.pkg
+ #Cold_Turkey_Blocker.pkg
+
+
+
\ No newline at end of file
diff --git a/pkg/file/testdata/distribution/distribution-sentinelone.xml b/pkg/file/testdata/distribution/distribution-sentinelone.xml
new file mode 100644
index 000000000000..f1cca6211e7e
--- /dev/null
+++ b/pkg/file/testdata/distribution/distribution-sentinelone.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+ DISTRIBUTION_TITLE
+
+
+
+
+
+
+
+
+
+
+ #SentinelOne.pkg
+
+
+
+
\ No newline at end of file
diff --git a/pkg/file/xar.go b/pkg/file/xar.go
index 31e3773b2bc3..c2e5cc49bfe2 100644
--- a/pkg/file/xar.go
+++ b/pkg/file/xar.go
@@ -107,9 +107,11 @@ type xmlFile struct {
// distributionXML represents the structure of the distributionXML.xml
type distributionXML struct {
- Title string `xml:"title"`
- Product distributionProduct `xml:"product"`
- PkgRefs []distributionPkgRef `xml:"pkg-ref"`
+ Title string `xml:"title"`
+ Product distributionProduct `xml:"product"`
+ PkgRefs []distributionPkgRef `xml:"pkg-ref"`
+ Choices []distributionChoice `xml:"choice"`
+ ChoicesOutline distributionChoicesOutline `xml:"choices-outline"`
}
type packageInfoXML struct {
@@ -135,6 +137,20 @@ type distributionPkgRef struct {
InstallKBytes string `xml:"installKBytes,attr"`
}
+type distributionChoice struct {
+ PkgRef distributionPkgRef `xml:"pkg-ref"`
+ Title string `xml:"title,attr"`
+ ID string `xml:"id,attr"`
+}
+
+type distributionChoicesOutline struct {
+ Lines []distributionLine `xml:"line"`
+}
+
+type distributionLine struct {
+ Choice string `xml:"choice,attr"`
+}
+
// distributionBundleVersion represents the bundle-version element
type distributionBundleVersion struct {
Bundles []distributionBundle `xml:"bundle"`
@@ -270,7 +286,14 @@ func parseDistributionFile(rawXML []byte) (*InstallerMetadata, error) {
BundleIdentifier: identifier,
PackageIDs: packageIDs,
}, nil
+}
+// Set of package names we know are incorrect. If we see these in the Distribution file we should
+// try to get the name some other way.
+var knownBadNames = map[string]struct{}{
+ "DISTRIBUTION_TITLE": {},
+ "MacFULL": {},
+ "SU_TITLE": {},
}
// getDistributionInfo gets the name, bundle identifier and version of a PKG distribution file
@@ -278,7 +301,7 @@ func getDistributionInfo(d *distributionXML) (name string, identifier string, ve
var appVersion string
// find the package ids that have an installation size
- var packageIDSet = make(map[string]struct{}, 1)
+ packageIDSet := make(map[string]struct{}, 1)
for _, pkg := range d.PkgRefs {
if pkg.InstallKBytes != "" && pkg.InstallKBytes != "0" {
var id string
@@ -339,6 +362,36 @@ out:
}
}
+ // Try to get the identifier based on the choices list, if we have one. Some .pkgs have multiple
+ // sub-pkgs inside, so the choices list helps us be a bit smarter.
+ if identifier == "" && len(d.ChoicesOutline.Lines) > 0 {
+ choicesByID := make(map[string]distributionChoice, len(d.Choices))
+ for _, c := range d.Choices {
+ choicesByID[c.ID] = c
+ }
+
+ for _, l := range d.ChoicesOutline.Lines {
+ c := choicesByID[l.Choice]
+ // Note: we can't create a map of pkg-refs by ID like we do for the choices above
+ // because different pkg-refs can have the same ID attribute. See distribution-go.xml
+ // for an example of this (this case is covered in tests).
+ for _, p := range d.PkgRefs {
+ if p.ID == c.PkgRef.ID {
+ identifier = p.PackageIdentifier
+ if identifier == "" {
+ identifier = p.ID
+ }
+ break
+ }
+ }
+
+ if identifier != "" {
+ // we found it, so we can quit looping
+ break
+ }
+ }
+ }
+
if identifier == "" {
for _, pkg := range d.PkgRefs {
if pkg.PackageIdentifier != "" {
@@ -368,8 +421,17 @@ out:
if name == "" && d.Title != "" {
name = d.Title
}
- if name == "" {
+
+ if _, ok := knownBadNames[name]; name == "" || ok {
name = identifier
+
+ // Try to find a tag that matches the bundle ID for this app. It might have the app
+ // name, so if we find it we can use that.
+ for _, c := range d.Choices {
+ if c.PkgRef.ID == identifier && c.Title != "" {
+ name = c.Title
+ }
+ }
}
// for the version, try to use the top-level product version, if not,
@@ -405,12 +467,11 @@ func parsePackageInfoFile(rawXML []byte) (*InstallerMetadata, error) {
BundleIdentifier: identifier,
PackageIDs: packageIDs,
}, nil
-
}
// getPackageInfo gets the name, bundle identifier and version of a PKG top level PackageInfo file
func getPackageInfo(p *packageInfoXML) (name string, identifier string, version string, packageIDs []string) {
- var packageIDSet = make(map[string]struct{}, 1)
+ packageIDSet := make(map[string]struct{}, 1)
for _, bundle := range p.Bundles {
installPath := bundle.Path
if p.InstallLocation != "" {
diff --git a/pkg/file/xar_test.go b/pkg/file/xar_test.go
index 15f76dff25b7..bd31b0eb6352 100644
--- a/pkg/file/xar_test.go
+++ b/pkg/file/xar_test.go
@@ -108,8 +108,10 @@ func TestParseRealDistributionFiles(t *testing.T) {
expectedName: "Microsoft Teams.app",
expectedVersion: "24124.1412.2911.3341",
expectedBundleID: "com.microsoft.teams2",
- expectedPackageIDs: []string{"com.microsoft.teams2", "com.microsoft.package.Microsoft_AutoUpdate.app",
- "com.microsoft.MSTeamsAudioDevice"},
+ expectedPackageIDs: []string{
+ "com.microsoft.teams2", "com.microsoft.package.Microsoft_AutoUpdate.app",
+ "com.microsoft.MSTeamsAudioDevice",
+ },
},
{
file: "distribution-zoom.xml",
@@ -123,8 +125,10 @@ func TestParseRealDistributionFiles(t *testing.T) {
expectedName: "Adobe Acrobat Reader.app",
expectedVersion: "24.002.20857",
expectedBundleID: "com.adobe.Reader",
- expectedPackageIDs: []string{"com.adobe.acrobat.DC.reader.app.pkg.MUI", "com.adobe.acrobat.DC.reader.appsupport.pkg.MUI",
- "com.adobe.acrobat.reader.DC.reader.app.pkg.MUI", "com.adobe.armdc.app.pkg"},
+ expectedPackageIDs: []string{
+ "com.adobe.acrobat.DC.reader.app.pkg.MUI", "com.adobe.acrobat.DC.reader.appsupport.pkg.MUI",
+ "com.adobe.acrobat.reader.DC.reader.app.pkg.MUI", "com.adobe.armdc.app.pkg",
+ },
},
{
file: "distribution-airtame.xml",
@@ -138,8 +142,10 @@ func TestParseRealDistributionFiles(t *testing.T) {
expectedName: "Box.app",
expectedVersion: "2.38.173",
expectedBundleID: "com.box.desktop",
- expectedPackageIDs: []string{"com.box.desktop.installer.desktop", "com.box.desktop.installer.local.appsupport",
- "com.box.desktop.installer.autoupdater", "com.box.desktop.installer.osxfuse"},
+ expectedPackageIDs: []string{
+ "com.box.desktop.installer.desktop", "com.box.desktop.installer.local.appsupport",
+ "com.box.desktop.installer.autoupdater", "com.box.desktop.installer.osxfuse",
+ },
},
{
file: "distribution-iriunwebcam.xml",
@@ -155,24 +161,30 @@ func TestParseRealDistributionFiles(t *testing.T) {
expectedName: "Microsoft Excel.app",
expectedVersion: "16.86",
expectedBundleID: "com.microsoft.Excel",
- expectedPackageIDs: []string{"com.microsoft.package.Microsoft_Excel.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
- "com.microsoft.pkg.licensing"},
+ expectedPackageIDs: []string{
+ "com.microsoft.package.Microsoft_Excel.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
+ "com.microsoft.pkg.licensing",
+ },
},
{
file: "distribution-microsoftword.xml",
expectedName: "Microsoft Word.app",
expectedVersion: "16.86",
expectedBundleID: "com.microsoft.Word",
- expectedPackageIDs: []string{"com.microsoft.package.Microsoft_Word.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
- "com.microsoft.pkg.licensing"},
+ expectedPackageIDs: []string{
+ "com.microsoft.package.Microsoft_Word.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
+ "com.microsoft.pkg.licensing",
+ },
},
{
file: "distribution-miscrosoftpowerpoint.xml",
expectedName: "Microsoft PowerPoint.app",
expectedVersion: "16.86",
expectedBundleID: "com.microsoft.Powerpoint",
- expectedPackageIDs: []string{"com.microsoft.package.Microsoft_PowerPoint.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
- "com.microsoft.pkg.licensing"},
+ expectedPackageIDs: []string{
+ "com.microsoft.package.Microsoft_PowerPoint.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
+ "com.microsoft.pkg.licensing",
+ },
},
{
file: "distribution-ringcentral.xml",
@@ -195,6 +207,20 @@ func TestParseRealDistributionFiles(t *testing.T) {
expectedBundleID: "com.bozo.zeroinstallsize",
expectedPackageIDs: []string{"com.bozo.zeroinstallsize.app"},
},
+ {
+ file: "distribution-sentinelone.xml",
+ expectedName: "SentinelOne",
+ expectedVersion: "24.3.2.7753",
+ expectedBundleID: "com.sentinelone.pkg.sentinel-agent",
+ expectedPackageIDs: []string{"com.sentinelone.pkg.sentinel-agent"},
+ },
+ {
+ file: "distribution-cold-turkey.xml",
+ expectedName: "Cold Turkey Blocker",
+ expectedVersion: "4.5",
+ expectedBundleID: "com.getcoldturkey.coldturkeyblocker",
+ expectedPackageIDs: []string{"com.getcoldturkey.coldturkeyblocker", "com.getcoldturkey.blocker-firefox-ext", "com.getcoldturkey.blocker-edge-ext", "com.getcoldturkey.blocker-chrome-ext"},
+ },
}
for _, tt := range tests {
@@ -232,8 +258,10 @@ func TestParsePackageInfoFiles(t *testing.T) {
expectedName: "IriunWebcam.app",
expectedVersion: "2.8.10",
expectedBundleID: "com.iriun.macwebcam",
- expectedPackageIDs: []string{"com.iriun.macwebcam", "com.iriun.macwebcam.extension4", "com.iriun.macwebcam.extension",
- "com.iriun.mic"},
+ expectedPackageIDs: []string{
+ "com.iriun.macwebcam", "com.iriun.macwebcam.extension4", "com.iriun.macwebcam.extension",
+ "com.iriun.mic",
+ },
},
{
file: "packageInfo-scriptOnly.xml",