mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-15 06:35:20 -07:00
Enable parsing macho file
This commit is contained in:
parent
e852d275ef
commit
70e72a86e4
2 changed files with 115 additions and 45 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
import { Blob } from 'buffer'
|
import { Blob } from 'buffer'
|
||||||
import plist from 'plist'
|
import plist from 'plist'
|
||||||
|
import prettyBytes from 'pretty-bytes'
|
||||||
// import zip from '@zip.js/zip.js'
|
// import zip from '@zip.js/zip.js'
|
||||||
|
import FileApi, { File } from 'file-api'
|
||||||
|
|
||||||
import {
|
import parseMacho from '~/helpers/macho/index.js'
|
||||||
isValidHttpUrl
|
|
||||||
} from '~/helpers/check-types.js'
|
|
||||||
|
|
||||||
|
|
||||||
// For some reason inline 'import()' works better than 'import from'
|
// For some reason inline 'import()' works better than 'import from'
|
||||||
|
|
@ -32,8 +32,12 @@ export class AppScan {
|
||||||
this.file = null
|
this.file = null
|
||||||
this.bundleFileEntries = []
|
this.bundleFileEntries = []
|
||||||
this.infoPlist = {}
|
this.infoPlist = {}
|
||||||
|
this.machoExcutables = []
|
||||||
this.machoMeta = {}
|
this.machoMeta = {}
|
||||||
|
|
||||||
|
this.bundleExecutable = null
|
||||||
|
this.displayBinarySize = ''
|
||||||
|
this.binarySize = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage ( details ) {
|
sendMessage ( details ) {
|
||||||
|
|
@ -50,31 +54,35 @@ export class AppScan {
|
||||||
return Object.keys( this.infoPlist ).length > 0
|
return Object.keys( this.infoPlist ).length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// get bundleExecutablePath () {
|
get hasMachoMeta () {
|
||||||
// if ( !this.hasInfoPlist ) return ''
|
return Object.keys( this.machoMeta ).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
// // There our CFBundleExecutable is a path to the executable
|
get bundleExecutablePath () {
|
||||||
// // then use it
|
if ( !this.hasInfoPlist ) return ''
|
||||||
// if ( this.infoPlist.CFBundleExecutable.includes('/') ) return `/Contents/${ this.infoPlist.CFBundleExecutable }`
|
|
||||||
|
|
||||||
// // Use default executable path
|
// There our CFBundleExecutable is a path to the executable
|
||||||
// return `/Contents/MacOS/${ this.infoPlist.CFBundleExecutable }`
|
// then use it
|
||||||
// }
|
if ( this.infoPlist.CFBundleExecutable.includes('/') ) return `/Contents/${ this.infoPlist.CFBundleExecutable }`
|
||||||
|
|
||||||
async readFileEntryData ( fileEntry ) {
|
// Use default executable path
|
||||||
|
return `/Contents/MacOS/${ this.infoPlist.CFBundleExecutable }`
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFileEntryData ( fileEntry, Writer = zip.TextWriter ) {
|
||||||
// Get blob data from zip
|
// Get blob data from zip
|
||||||
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry
|
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry
|
||||||
return await fileEntry.getData(
|
return await fileEntry.getData(
|
||||||
// writer
|
// writer
|
||||||
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing
|
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing
|
||||||
new zip.TextWriter(),
|
new Writer()//zip.TextWriter(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async readFileBlob ( File ) {
|
async readFileBlob ( FileInstance ) {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
const fileReader = new zip.BlobReader( new Blob( File.arrayBuffer ) )
|
const fileReader = new zip.BlobReader( new Blob( FileInstance.arrayBuffer ) )
|
||||||
|
|
||||||
// this.sendMessage({
|
// this.sendMessage({
|
||||||
// message: '📖 Setting up BlobReader',
|
// message: '📖 Setting up BlobReader',
|
||||||
|
|
@ -135,18 +143,19 @@ export class AppScan {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
matchesMacho ( entry ) {
|
matchesMachoExecutable ( entry ) {
|
||||||
// Skip files that are deeper than 3 folders
|
// Skip files that are deeper than 3 folders
|
||||||
if ( entry.filename.split('/').length > 4 ) return false
|
if ( entry.filename.split('/').length > 4 ) return false
|
||||||
|
|
||||||
// Skip folders
|
// Skip folders
|
||||||
if ( entry.filename.endsWith('/') ) return false
|
// if ( !!entry.directory ) return false
|
||||||
|
|
||||||
// `${ appName }.app/Contents/MacOS/${ appName }`
|
// `${ appName }.app/Contents/MacOS/${ appName }`
|
||||||
// Does this entry path match any of our wanted paths
|
// Does this entry path match any of our wanted paths
|
||||||
return [
|
return [
|
||||||
// `${ appName }.app/Contents/MacOS/${ appName }`
|
// `${ appName }.app/Contents/MacOS/${ appName }`
|
||||||
`.app/Contents/MacOS/`
|
// `.app/Contents/MacOS/`,
|
||||||
|
`Contents/MacOS/`
|
||||||
].some( pathToMatch => {
|
].some( pathToMatch => {
|
||||||
return entry.filename.includes( pathToMatch )
|
return entry.filename.includes( pathToMatch )
|
||||||
})
|
})
|
||||||
|
|
@ -176,7 +185,7 @@ export class AppScan {
|
||||||
fileEntryType ( fileEntry ) {
|
fileEntryType ( fileEntry ) {
|
||||||
if ( !!fileEntry.directory ) return 'directory'
|
if ( !!fileEntry.directory ) return 'directory'
|
||||||
|
|
||||||
if ( this.matchesMacho( fileEntry ) ) return 'macho'
|
if ( this.matchesMachoExecutable( fileEntry ) ) return 'machoExecutable'
|
||||||
|
|
||||||
if ( this.matchesRootInfoPlist( fileEntry ) ) return 'rootInfoPlist'
|
if ( this.matchesRootInfoPlist( fileEntry ) ) return 'rootInfoPlist'
|
||||||
|
|
||||||
|
|
@ -202,18 +211,64 @@ export class AppScan {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// async storeMachoMeta ( fileEntry ) {
|
storeMachoExecutable = ( fileEntry ) => {
|
||||||
// const machoData = await this.readFileEntryData( fileEntry )
|
this.machoExcutables.push( fileEntry )
|
||||||
// }
|
|
||||||
|
this.sendMessage({
|
||||||
|
message: '🥊 Found a Macho executable',
|
||||||
|
// data: machoExecutable,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
storeMachoMeta = async ( fileEntry ) => {
|
||||||
|
// Throw if we have more than one target file
|
||||||
|
if ( this.hasMachoMeta ) {
|
||||||
|
throw new Error( 'More than one primary Macho executable found' )
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get blob data from zip
|
||||||
|
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry
|
||||||
|
const bundleExecutableBlob = await this.readFileEntryData( fileEntry, zip.Uint8ArrayWriter )
|
||||||
|
|
||||||
|
// console.log( 'bundleExecutableBlob', bundleExecutableBlob.buffer )
|
||||||
|
|
||||||
|
const machoFileInstance = new File({
|
||||||
|
name: this.bundleExecutable.filename,
|
||||||
|
type: 'application/x-mach-binary',
|
||||||
|
buffer: bundleExecutableBlob,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.machoMeta = await parseMacho( machoFileInstance, FileApi ) //await this.parseMachOBlob( bundleExecutableBlob, file.name )
|
||||||
|
// console.log( 'this.machoMeta', this.machoMeta )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
targetFiles = {
|
targetFiles = {
|
||||||
rootInfoPlist: {
|
rootInfoPlist: {
|
||||||
method: this.storeInfoPlist
|
method: this.storeInfoPlist
|
||||||
},
|
},
|
||||||
// macho: {
|
machoExecutable: {
|
||||||
// method: this.storeMacho,
|
method: this.storeMachoExecutable,
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findMainExecutable () {
|
||||||
|
// Now that we have the info.plist Determine our entry Macho Executable from the list of Macho Executables
|
||||||
|
const bundleExecutables = this.machoExcutables.filter( machoEntry => {
|
||||||
|
return this.bundleExecutablePath.includes( machoEntry.filename )
|
||||||
|
})
|
||||||
|
|
||||||
|
// Warn if Bundle Executable doesn't look right
|
||||||
|
if ( bundleExecutables.length > 1) {
|
||||||
|
throw new Error('More than one root bundleExecutable found', bundleExecutables)
|
||||||
|
} else if ( bundleExecutables.length === 0 ) {
|
||||||
|
throw new Error('No root bundleExecutable found', bundleExecutables)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundleExecutables[ 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
async findTargetFiles () {
|
async findTargetFiles () {
|
||||||
|
|
@ -225,6 +280,7 @@ export class AppScan {
|
||||||
|
|
||||||
// Check if we have a target file
|
// Check if we have a target file
|
||||||
if ( this.targetFiles[ type ] ) {
|
if ( this.targetFiles[ type ] ) {
|
||||||
|
// console.log( 'fileEntry', type, fileEntry.filename )
|
||||||
|
|
||||||
// Call the target file method
|
// Call the target file method
|
||||||
await this.targetFiles[ type ].method( fileEntry )
|
await this.targetFiles[ type ].method( fileEntry )
|
||||||
|
|
@ -233,6 +289,18 @@ export class AppScan {
|
||||||
// console.log( 'File Entry Type:', type )
|
// console.log( 'File Entry Type:', type )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that we have the info.plist Determine our entry Macho Executable from the list of Macho Executables
|
||||||
|
|
||||||
|
// Set the bundleExecutable
|
||||||
|
this.bundleExecutable = this.findMainExecutable()
|
||||||
|
|
||||||
|
console.log('Parsing ', this.bundleExecutable.filename, this.bundleExecutable.uncompressedSize / 1000 )
|
||||||
|
|
||||||
|
this.displayBinarySize = prettyBytes( this.bundleExecutable.uncompressedSize )
|
||||||
|
this.binarySize = this.bundleExecutable.uncompressedSize
|
||||||
|
|
||||||
|
|
||||||
|
await this.storeMachoMeta( this.bundleExecutable )
|
||||||
}
|
}
|
||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
|
|
@ -255,7 +323,6 @@ export class AppScan {
|
||||||
|
|
||||||
await this.findTargetFiles()
|
await this.findTargetFiles()
|
||||||
|
|
||||||
|
|
||||||
this.sendMessage({
|
this.sendMessage({
|
||||||
message: '🏁 Scan complete',
|
message: '🏁 Scan complete',
|
||||||
status: 'complete'
|
status: 'complete'
|
||||||
|
|
|
||||||
|
|
@ -49,32 +49,35 @@ async function makeZipFromBundlePath ( bundlePath ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
describe.concurrent('Apps', () => {
|
describe.concurrent('Apps', async () => {
|
||||||
|
|
||||||
// Compress plain app bundles to zipped File Objects
|
// Compress plain app bundles to zipped File Objects
|
||||||
for ( const bundlePath of plainAppBundles ) {
|
for ( const bundlePath of plainAppBundles ) {
|
||||||
|
|
||||||
it( `Can read info.plist for ${ path.basename( bundlePath ) } bundle` , async () => {
|
const appName = path.basename( bundlePath )
|
||||||
|
|
||||||
// Compress plain app bundles to zipped File Objects
|
// Create a new AppScan instance
|
||||||
for ( const bundlePath of plainAppBundles ) {
|
const scan = new AppScan({
|
||||||
|
fileLoader: () => makeZipFromBundlePath( bundlePath ),
|
||||||
// Create a new AppScan instance
|
messageReceiver: ( details ) => {
|
||||||
const scan = new AppScan({
|
console.log( 'Scan message:', details )
|
||||||
fileLoader: () => makeZipFromBundlePath( bundlePath ),
|
|
||||||
messageReceiver: ( details ) => {
|
|
||||||
console.log( 'Scan message:', details )
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Scan the archive
|
|
||||||
await scan.start()
|
|
||||||
|
|
||||||
// console.log( 'infoPlist', scan.infoPlist )
|
|
||||||
|
|
||||||
expect( scan.hasInfoPlist ).toBe( true )
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Scan the archive
|
||||||
|
await scan.start()
|
||||||
|
|
||||||
|
|
||||||
|
it( `Can read info.plist for ${ appName } bundle` , () => {
|
||||||
|
// console.log( 'infoPlist', scan.infoPlist )
|
||||||
|
|
||||||
|
expect( scan.hasInfoPlist ).toBe( true )
|
||||||
|
})
|
||||||
|
|
||||||
|
it( `Can read macho meta for entry ${ appName } bundle`, () => {
|
||||||
|
// console.log( 'machoMeta', scan.machoMeta )
|
||||||
|
|
||||||
|
expect( scan.hasMachoMeta ).toBe( true )
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue