mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-18 06:44:46 -07:00
Find and use app info file
This commit is contained in:
parent
48522c7ddd
commit
2338aeceb1
2 changed files with 187 additions and 30 deletions
|
|
@ -12,6 +12,9 @@
|
||||||
// import EndianReader from 'endian-reader'
|
// import EndianReader from 'endian-reader'
|
||||||
import parseMacho from './macho/index.js'
|
import parseMacho from './macho/index.js'
|
||||||
|
|
||||||
|
|
||||||
|
const plist = require('plist')
|
||||||
|
|
||||||
// console.log('MachOParser', MachOParser)
|
// console.log('MachOParser', MachOParser)
|
||||||
|
|
||||||
const knownArchiveExtensions = new Set([
|
const knownArchiveExtensions = new Set([
|
||||||
|
|
@ -191,11 +194,50 @@ export default class AppFilesScanner {
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
findMachOEntries ( entries ) {
|
matchesMacho ( entry ) {
|
||||||
|
// Skip files that are deeper than 3 folders
|
||||||
|
if ( entry.filename.split('/').length > 4 ) return false
|
||||||
|
|
||||||
// Create a new set to store and search App Names
|
// Skip folders
|
||||||
|
if ( entry.filename.endsWith('/') ) return false
|
||||||
|
|
||||||
|
// `${ appName }.app/Contents/MacOS/${ appName }`
|
||||||
|
// Does this entry path match any of our wanted paths
|
||||||
|
return [
|
||||||
|
// `${ appName }.app/Contents/MacOS/${ appName }`
|
||||||
|
`.app/Contents/MacOS/`
|
||||||
|
].some( pathToMatch => {
|
||||||
|
return entry.filename.includes(pathToMatch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesRootInfo ( entry ) {
|
||||||
|
// Skip files that are deeper than 2 folders
|
||||||
|
if ( entry.filename.split('/').length > 3 ) return false
|
||||||
|
|
||||||
|
// Skip folders
|
||||||
|
if ( entry.filename.endsWith('/') ) return false
|
||||||
|
|
||||||
|
// Does this entry path match any of our wanted paths
|
||||||
|
return [
|
||||||
|
// `zoom.us.app/Contents/Info.plist`
|
||||||
|
`.app/Contents/Info.plist`,
|
||||||
|
`.zip/Contents/Info.plist`
|
||||||
|
].some( pathToMatch => {
|
||||||
|
return entry.filename.endsWith(pathToMatch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
findEntries ( entries, matchersObject ) {
|
||||||
|
|
||||||
|
const matches = {}
|
||||||
|
|
||||||
|
// const matcherKeys = Object.keys( matchers )
|
||||||
|
|
||||||
|
// Create a new set to store found App Names
|
||||||
const appNamesInArchive = new Set()
|
const appNamesInArchive = new Set()
|
||||||
|
|
||||||
|
// Search App Names in entries
|
||||||
entries.forEach( entry => {
|
entries.forEach( entry => {
|
||||||
// Look through filename parts
|
// Look through filename parts
|
||||||
entry.filename.split('/').forEach( filenamePart => {
|
entry.filename.split('/').forEach( filenamePart => {
|
||||||
|
|
@ -205,25 +247,26 @@ export default class AppFilesScanner {
|
||||||
appNamesInArchive.add( appName )
|
appNamesInArchive.add( appName )
|
||||||
}
|
}
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
||||||
|
for ( const key in matchersObject ) {
|
||||||
|
|
||||||
|
// Deos it match the matcher method
|
||||||
|
const entryMatches = matchersObject[key]( entry )
|
||||||
|
|
||||||
|
if ( entryMatches ) {
|
||||||
|
// If we haven't set up an array for this key
|
||||||
|
// then create one
|
||||||
|
if ( !Array.isArray(matches[key]) ) matches[key] = []
|
||||||
|
|
||||||
|
// Push this entry to our matching list
|
||||||
|
matches[key].push( entry )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} )
|
} )
|
||||||
|
|
||||||
// Return any entries that match Mach-o file paths
|
return matches
|
||||||
return entries.filter( entry => {
|
|
||||||
let matchesMachOPath = false
|
|
||||||
|
|
||||||
// Match possible Mach-o names against this entries' filename
|
|
||||||
appNamesInArchive.forEach( appName => {
|
|
||||||
const possibleMachOPath = `${ appName }.app/Contents/MacOS/${ appName }`
|
|
||||||
|
|
||||||
// Check if this possible Mach-o path is contained within this entry's filename
|
|
||||||
if ( entry.filename.includes( possibleMachOPath ) ) {
|
|
||||||
matchesMachOPath = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return matchesMachOPath
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseMachOBlob ( machOBlob, fileName ) {
|
async parseMachOBlob ( machOBlob, fileName ) {
|
||||||
|
|
@ -246,7 +289,11 @@ export default class AppFilesScanner {
|
||||||
Array.from(fileList).forEach( (fileInstance, index) => {
|
Array.from(fileList).forEach( (fileInstance, index) => {
|
||||||
this.files.unshift( {
|
this.files.unshift( {
|
||||||
status: 'loaded',
|
status: 'loaded',
|
||||||
|
displayName: null,
|
||||||
statusMessage: '⏳ File Loaded and Queud',
|
statusMessage: '⏳ File Loaded and Queud',
|
||||||
|
details: [],
|
||||||
|
appVersion: null,
|
||||||
|
|
||||||
name: fileInstance.name,
|
name: fileInstance.name,
|
||||||
size: fileInstance.size,
|
size: fileInstance.size,
|
||||||
type: fileList.item( index ).type,
|
type: fileList.item( index ).type,
|
||||||
|
|
@ -256,7 +303,6 @@ export default class AppFilesScanner {
|
||||||
} )
|
} )
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// Scan for archives
|
// Scan for archives
|
||||||
await Promise.all( this.files.map( async (file, index) => {
|
await Promise.all( this.files.map( async (file, index) => {
|
||||||
|
|
||||||
|
|
@ -269,7 +315,7 @@ export default class AppFilesScanner {
|
||||||
|
|
||||||
// console.log('file', file)
|
// console.log('file', file)
|
||||||
|
|
||||||
await new Promise(r => setTimeout(r, 1000 * index))
|
await new Promise(r => setTimeout(r, 1500 * index))
|
||||||
|
|
||||||
file.statusMessage = '🗃 Decompressing file'
|
file.statusMessage = '🗃 Decompressing file'
|
||||||
|
|
||||||
|
|
@ -289,10 +335,23 @@ export default class AppFilesScanner {
|
||||||
|
|
||||||
file.statusMessage = '👀 Scanning App Files'
|
file.statusMessage = '👀 Scanning App Files'
|
||||||
|
|
||||||
file.machOEntries = this.findMachOEntries( entries )
|
const foundEntries = this.findEntries( entries, {
|
||||||
|
macho: this.matchesMacho,
|
||||||
|
rootInfo: this.matchesRootInfo
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clean out entries now that we're done with them
|
||||||
|
entries = undefined
|
||||||
|
|
||||||
|
// console.log('foundEntries', foundEntries)
|
||||||
|
|
||||||
|
// file.machOEntries = this.findMachOEntries( entries )
|
||||||
|
file.machOEntries = foundEntries.macho
|
||||||
|
|
||||||
|
// If no Macho files were found
|
||||||
|
// then report and stop
|
||||||
if ( file.machOEntries.length === 0 ) {
|
if ( file.machOEntries.length === 0 ) {
|
||||||
console.log('file.machOEntries', file.machOEntries)
|
console.log(`No Macho files found for ${file.name}`, file.machOEntries)
|
||||||
|
|
||||||
file.statusMessage = `❔ Unkown app format`
|
file.statusMessage = `❔ Unkown app format`
|
||||||
file.status = 'finished'
|
file.status = 'finished'
|
||||||
|
|
@ -300,10 +359,93 @@ export default class AppFilesScanner {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// findPlistFile
|
||||||
|
|
||||||
|
// Maybe next time
|
||||||
|
// const matchesInfoPlist = path => path.endsWith(`/Info.plist`)
|
||||||
|
// const matchesRootInfoPlist = entryPath => {
|
||||||
|
|
||||||
|
// // Skip files that are deeper than 2 folders
|
||||||
|
// if ( entryPath.split('/').length > 3 ) return false
|
||||||
|
|
||||||
|
// // Does this entry path match any of our wanted paths
|
||||||
|
// return [
|
||||||
|
// // `zoom.us.app/Contents/Info.plist`
|
||||||
|
// `.app/Contents/Info.plist`,
|
||||||
|
// `.zip/Contents/Info.plist`
|
||||||
|
// ].some( pathToMatch => {
|
||||||
|
// return entryPath.endsWith(pathToMatch)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// const rootInfoFileEntries = entries.filter( entry => {
|
||||||
|
|
||||||
|
// return matchesRootInfoPlist( entry.filename )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Warn if Info.plist doesn't look right
|
||||||
|
if ( foundEntries.rootInfo.length > 1) {
|
||||||
|
console.warn('More than one root Info.plist found', foundEntries.rootInfo)
|
||||||
|
} else if ( foundEntries.rootInfo.length === 0 ) {
|
||||||
|
console.warn('No root Info.plist found', foundEntries.rootInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break out root entry into a variable
|
||||||
|
const [ rootInfoEntry ] = foundEntries.rootInfo
|
||||||
|
|
||||||
|
// Get blob data from zip
|
||||||
|
const infoXml = await rootInfoEntry.getData(
|
||||||
|
// writer
|
||||||
|
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing
|
||||||
|
new zip.TextWriter(),
|
||||||
|
// options
|
||||||
|
{
|
||||||
|
useWebWorkers: true,
|
||||||
|
// onprogress: (index, max) => {
|
||||||
|
|
||||||
|
// const percentageNumber = (index / max * 100)
|
||||||
|
// // onprogress callback
|
||||||
|
// console.log(`Writer progress ${percentageNumber}`)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// console.log('infoXml', infoXml)
|
||||||
|
|
||||||
|
// Parse the Info.plist data
|
||||||
|
const info = plist.parse( infoXml )
|
||||||
|
|
||||||
|
file.appVersion = info.CFBundleShortVersionString
|
||||||
|
file.displayName = info.CFBundleDisplayName
|
||||||
|
|
||||||
|
const detailsData = [
|
||||||
|
[ 'Version', info.CFBundleShortVersionString ],
|
||||||
|
[ 'Bundle Identifier', info.CFBundleIdentifier ],
|
||||||
|
[ 'File Mime Type', file.type ],
|
||||||
|
[ 'Copyright', info.NSHumanReadableCopyright ],
|
||||||
|
// [ 'Version', info.CFBundleShortVersionString ],
|
||||||
|
]
|
||||||
|
|
||||||
|
detailsData.forEach( ([ label, value ]) => {
|
||||||
|
if ( !value || value.length === 0 ) return
|
||||||
|
|
||||||
|
file.details.push({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
} )
|
||||||
|
|
||||||
|
// console.log('infoFiles', file.name, {
|
||||||
|
// path: rootInfoEntry.filename,
|
||||||
|
// info
|
||||||
|
// })
|
||||||
|
|
||||||
// const machOBlob = await file.machOEntries
|
// const machOBlob = await file.machOEntries
|
||||||
|
|
||||||
|
|
||||||
const parsedMachoEntries = await Promise.all( file.machOEntries.map( async machOEntry => {
|
const parsedMachoEntries = await Promise.all( file.machOEntries.map( async machOEntry => {
|
||||||
|
// console.log('Parsing ', machOEntry.filename)
|
||||||
|
|
||||||
// Get blob data from zip
|
// Get blob data from zip
|
||||||
const machOBlob = await machOEntry.getData(
|
const machOBlob = await machOEntry.getData(
|
||||||
// writer
|
// writer
|
||||||
|
|
@ -312,10 +454,12 @@ export default class AppFilesScanner {
|
||||||
new zip.BlobWriter(),
|
new zip.BlobWriter(),
|
||||||
// options
|
// options
|
||||||
{
|
{
|
||||||
onprogress: (index, max) => {
|
useWebWorkers: true,
|
||||||
// onprogress callback
|
// onprogress: (index, max) => {
|
||||||
console.log('Writer progress', index, max)
|
// const percentageNumber = (index / max * 100)
|
||||||
}
|
// // onprogress callback
|
||||||
|
// console.log(`Writer progress ${percentageNumber}`)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
<!-- app.endpoint: {{ app.endpoint }} -->
|
<!-- app.endpoint: {{ app.endpoint }} -->
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
'flex flex-col justify-center inset-x-0 hover:bg-darkest border-2 border-white border-opacity-0 hover:border-opacity-50 focus:outline-none focus:bg-gray-50 duration-300 ease-in-out rounded-lg space-y-2 -mx-5 pl-5 md:pl-20 pr-6 md:pr-64 py-5',
|
'flex flex-col justify-center inset-x-0 hover:bg-darkest border-2 border-white border-opacity-0 hover:border-opacity-50 focus:outline-none focus:bg-gray-50 duration-300 ease-in-out rounded-lg space-y-3 -mx-5 pl-5 md:pl-20 pr-6 md:pr-64 py-5',
|
||||||
(appScan.status !== 'finished') ? 'shimmer' : ''
|
(appScan.status !== 'finished') ? 'shimmer' : ''
|
||||||
]"
|
]"
|
||||||
style="transition-property: border;"
|
style="transition-property: border;"
|
||||||
|
|
@ -101,11 +101,24 @@
|
||||||
<div class="absolute hidden left-0 h-12 w-12 rounded-full md:flex items-center justify-center bg-darker">
|
<div class="absolute hidden left-0 h-12 w-12 rounded-full md:flex items-center justify-center bg-darker">
|
||||||
{{ appScan.name.charAt(0) }}
|
{{ appScan.name.charAt(0) }}
|
||||||
</div>
|
</div>
|
||||||
{{ appScan.name }} <code>{{ appScan.type }}</code>
|
{{ appScan.displayName || appScan.name }} {{ appScan.appVersion ? `- v${appScan.appVersion}` : '' }}
|
||||||
<div class="text-sm leading-5 font-bold">
|
<div class="text-sm leading-5 font-bold">
|
||||||
{{ appScan.statusMessage }}
|
{{ appScan.statusMessage }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<details class="w-full pt-6">
|
||||||
|
<summary class="cursor-pointer mb-3">Details</summary>
|
||||||
|
<div>
|
||||||
|
<div v-if="appScan.details.length === 0">No details available</div>
|
||||||
|
<ul v-else>
|
||||||
|
<li
|
||||||
|
v-for="( detail ) in appScan.details"
|
||||||
|
:key="`${appScan.name}-detail-${detail.label}`"
|
||||||
|
><strong>{{ detail.label }}</strong> <span v-html="detail.value" /></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -124,7 +137,7 @@
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
<pre class="w-full">{{ appsBeingScanned }}</pre>
|
<!-- <pre class="w-full">{{ appsBeingScanned }}</pre> -->
|
||||||
|
|
||||||
<AllUpdatesSubscribe
|
<AllUpdatesSubscribe
|
||||||
:input-class-groups="{
|
:input-class-groups="{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue