Files
ipsw/pkg/plist/app.go

120 lines
6.1 KiB
Go

package plist
import (
"bytes"
"fmt"
"os"
"path/filepath"
"github.com/blacktop/go-plist"
)
// AppInfo is the Info.plist object found in .app files
// https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009248-SW1
type AppInfo struct {
BuildMachineOSBuild string `plist:"BuildMachineOSBuild,omitempty"`
CFBundleDevelopmentRegion string `plist:"CFBundleDevelopmentRegion,omitempty"`
CFBundleDocumentTypes []any `plist:"CFBundleDocumentTypes,omitempty"`
CFBundleExecutable string `plist:"CFBundleExecutable,omitempty"`
CFBundleIconFile string `plist:"CFBundleIconFile,omitempty"`
CFBundleIconName string `plist:"CFBundleIconName,omitempty"`
CFBundleIdentifier string `plist:"CFBundleIdentifier,omitempty"`
CFBundleInfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion,omitempty"`
CFBundleName string `plist:"CFBundleName,omitempty"`
CFBundlePackageType string `plist:"CFBundlePackageType,omitempty"`
CFBundleShortVersionString string `plist:"CFBundleShortVersionString,omitempty"`
CFBundleSignature string `plist:"CFBundleSignature,omitempty"`
CFBundleSupportedPlatforms []string `plist:"CFBundleSupportedPlatforms,omitempty"`
CFBundleURLTypes []struct {
CFBundleTypeRole string `plist:"CFBundleTypeRole,omitempty"`
CFBundleURLIconFile string `plist:"CFBundleURLIconFile,omitempty"`
CFBundleURLName string `plist:"CFBundleURLName,omitempty"`
CFBundleURLSchemes []string `plist:"CFBundleURLSchemes,omitempty"`
} `plist:"CFBundleURLTypes,omitempty"`
CFBundleVersion string `plist:"CFBundleVersion,omitempty"`
DTCompiler string `plist:"DTCompiler,omitempty"`
DTPlatformBuild string `plist:"DTPlatformBuild,omitempty"`
DTPlatformName string `plist:"DTPlatformName,omitempty"`
DTPlatformVersion string `plist:"DTPlatformVersion,omitempty"`
DTSDKBuild string `plist:"DTSDKBuild,omitempty"`
DTSDKName string `plist:"DTSDKName,omitempty"`
DTXcode string `plist:"DTXcode,omitempty"`
DTXcodeBuild string `plist:"DTXcodeBuild,omitempty"`
LSApplicationCategoryType string `plist:"LSApplicationCategoryType,omitempty"`
LSMinimumSystemVersion string `plist:"LSMinimumSystemVersion,omitempty"`
LSUIElement bool `plist:"LSUIElement,omitempty"`
MinimumOSVersion string `plist:"MinimumOSVersion,omitempty"`
NSHumanReadableCopyright string `plist:"NSHumanReadableCopyright,omitempty"`
NSMainNibFile string `plist:"NSMainNibFile,omitempty"`
NSPrincipalClass string `plist:"NSPrincipalClass,omitempty"`
NSSupportsAutomaticGraphicsSwitching bool `plist:"NSSupportsAutomaticGraphicsSwitching,omitempty"`
NSUbiquitousDisplaySet string `plist:"NSUbiquitousDisplaySet,omitempty"`
NSUserActivityTypes []string `plist:"NSUserActivityTypes,omitempty"`
NSUserNotificationAlertStyle string `plist:"NSUserNotificationAlertStyle,omitempty"`
NSUserNotificationAlertBody string `plist:"NSUserNotificationAlertBody,omitempty"`
UTExportedTypeDeclarations []any `plist:"UTExportedTypeDeclarations,omitempty"`
}
func (r *AppInfo) String() string {
var out string
out += "[Info]\n"
out += "======\n"
out += fmt.Sprintf("CFBundleDevelopmentRegion: %s\n", r.CFBundleDevelopmentRegion)
out += fmt.Sprintf("CFBundleExecutable: %s\n", r.CFBundleExecutable)
out += fmt.Sprintf("CFBundleIdentifier: %s\n", r.CFBundleIdentifier)
out += fmt.Sprintf("CFBundleInfoDictionaryVersion: %s\n", r.CFBundleInfoDictionaryVersion)
out += fmt.Sprintf("CFBundleName: %s\n", r.CFBundleName)
out += fmt.Sprintf("CFBundlePackageType: %s\n", r.CFBundlePackageType)
out += fmt.Sprintf("CFBundleShortVersionString: %s\n", r.CFBundleShortVersionString)
out += fmt.Sprintf("CFBundleSignature: %s\n", r.CFBundleSignature)
out += fmt.Sprintf("CFBundleSupportedPlatforms: %s\n", r.CFBundleSupportedPlatforms)
out += fmt.Sprintf("CFBundleURLTypes: %s\n", r.CFBundleURLTypes)
out += fmt.Sprintf("CFBundleVersion: %s\n", r.CFBundleVersion)
return out
}
// ParseAppInfo parses the .app/Info.plist
func ParseAppInfo(data []byte) (*AppInfo, error) {
i := &AppInfo{}
if err := plist.NewDecoder(bytes.NewReader(data)).Decode(i); err != nil {
return nil, fmt.Errorf("failed to parse Info.plist: %w", err)
}
return i, nil
}
// GetBinaryInApp returns the binary path of the given .app
func GetBinaryInApp(path string) (string, error) {
if filepath.Ext(path) == ".app" {
infoPath := filepath.Join(path, "Info.plist")
if _, err := os.Stat(filepath.Join(path, "Info.plist")); err == nil { // iOS
dat, err := os.ReadFile(infoPath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %w", infoPath, err)
}
ainfo, err := ParseAppInfo(dat)
if err != nil {
return "", fmt.Errorf("failed to parse %s: %w", infoPath, err)
}
if ainfo.CFBundleExecutable != "" {
return filepath.Join(path, ainfo.CFBundleExecutable), nil
}
return "", fmt.Errorf("failed to find CFBundleExecutable in %s", infoPath)
} else if _, err := os.Stat(filepath.Join(path, "Contents", "Info.plist")); err == nil { // MacOS
infoPath = filepath.Join(path, "Contents", "Info.plist")
dat, err := os.ReadFile(infoPath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %w", infoPath, err)
}
ainfo, err := ParseAppInfo(dat)
if err != nil {
return "", fmt.Errorf("failed to parse %s: %w", infoPath, err)
}
if ainfo.CFBundleExecutable != "" {
return filepath.Join(path, "Contents", "MacOS", ainfo.CFBundleExecutable), nil
}
return "", fmt.Errorf("failed to find CFBundleExecutable in %s", infoPath)
}
}
return path, fmt.Errorf("%s is not a <binary>, .app/<binary> or a .app/Contents/MacOS/<binary>", path)
}