mirror of
				https://gitcode.com/github-mirrors/react-native-update-cli.git
				synced 2025-10-31 23:03:11 +08:00 
			
		
		
		
	v1.20.6
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "react-native-update-cli", |   "name": "react-native-update-cli", | ||||||
|   "version": "1.20.5", |   "version": "1.20.6", | ||||||
|   "description": "Command tools for javaScript updater with `pushy` service for react native apps.", |   "description": "Command tools for javaScript updater with `pushy` service for react native apps.", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @@ -31,7 +31,6 @@ | |||||||
|   }, |   }, | ||||||
|   "homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli", |   "homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "app-info-parser": "github:sunnylqm/app-info-parser#fix/support-ascii-versionname", |  | ||||||
|     "cli-arguments": "^0.2.1", |     "cli-arguments": "^0.2.1", | ||||||
|     "filesize-parser": "^1.5.0", |     "filesize-parser": "^1.5.0", | ||||||
|     "fs-extra": "8", |     "fs-extra": "8", | ||||||
|   | |||||||
							
								
								
									
										117
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										117
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -5,9 +5,6 @@ settings: | |||||||
|   excludeLinksFromLockfile: false |   excludeLinksFromLockfile: false | ||||||
|  |  | ||||||
| dependencies: | dependencies: | ||||||
|   app-info-parser: |  | ||||||
|     specifier: github:sunnylqm/app-info-parser#fix/support-ascii-versionname |  | ||||||
|     version: github.com/sunnylqm/app-info-parser/f7748ce60278fb16f6f280d908d35025a241eb57 |  | ||||||
|   cli-arguments: |   cli-arguments: | ||||||
|     specifier: ^0.2.1 |     specifier: ^0.2.1 | ||||||
|     version: 0.2.1 |     version: 0.2.1 | ||||||
| @@ -437,10 +434,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} |     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} | ||||||
|     dev: true |     dev: true | ||||||
|  |  | ||||||
|   /base64-js@1.5.1: |  | ||||||
|     resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /base@0.11.2: |   /base@0.11.2: | ||||||
|     resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} |     resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
| @@ -462,11 +455,6 @@ packages: | |||||||
|       tweetnacl: 0.14.5 |       tweetnacl: 0.14.5 | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /big-integer@1.6.50: |  | ||||||
|     resolution: {integrity: sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ==} |  | ||||||
|     engines: {node: '>=0.6'} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /binary-extensions@1.13.1: |   /binary-extensions@1.13.1: | ||||||
|     resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} |     resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
| @@ -496,13 +484,6 @@ packages: | |||||||
|       wrap-ansi: 7.0.0 |       wrap-ansi: 7.0.0 | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /bplist-parser@0.2.0: |  | ||||||
|     resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} |  | ||||||
|     engines: {node: '>= 5.10.0'} |  | ||||||
|     dependencies: |  | ||||||
|       big-integer: 1.6.50 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /brace-expansion@1.1.11: |   /brace-expansion@1.1.11: | ||||||
|     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} |     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} | ||||||
|     dependencies: |     dependencies: | ||||||
| @@ -551,24 +532,6 @@ packages: | |||||||
|     resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=} |     resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=} | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /buffer@5.7.1: |  | ||||||
|     resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} |  | ||||||
|     dependencies: |  | ||||||
|       base64-js: 1.5.1 |  | ||||||
|       ieee754: 1.2.1 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /bufferpack@0.0.6: |  | ||||||
|     resolution: {integrity: sha512-MTWvLHElqczrIVhge9qHtqgNigJFyh0+tCDId5yCbFAfuekHWIG+uAgvoHVflwrDPuY/e47JE1ki5qcM7w4uLg==} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /bytebuffer@5.0.1: |  | ||||||
|     resolution: {integrity: sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ==} |  | ||||||
|     engines: {node: '>=0.8'} |  | ||||||
|     dependencies: |  | ||||||
|       long: 3.2.0 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /cache-base@1.0.1: |   /cache-base@1.0.1: | ||||||
|     resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} |     resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
| @@ -620,15 +583,6 @@ packages: | |||||||
|     resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} |     resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /cgbi-to-png@1.0.7: |  | ||||||
|     resolution: {integrity: sha512-YR80kxTPuq9oRpZUdQmNEQWrmTKLINk1cfLVfyrV7Rfr9KLtLJdcockPKbreIr4JYAq+DhHBR7w+WA/tF5VDaQ==} |  | ||||||
|     dependencies: |  | ||||||
|       bufferpack: 0.0.6 |  | ||||||
|       crc: 3.8.0 |  | ||||||
|       stream-to-buffer: 0.1.0 |  | ||||||
|       streamifier: 0.1.1 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /chalk@1.1.3: |   /chalk@1.1.3: | ||||||
|     resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=} |     resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
| @@ -753,11 +707,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} |     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} | ||||||
|     dev: true |     dev: true | ||||||
|  |  | ||||||
|   /commander@7.2.0: |  | ||||||
|     resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} |  | ||||||
|     engines: {node: '>= 10'} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /component-emitter@1.3.0: |   /component-emitter@1.3.0: | ||||||
|     resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} |     resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} | ||||||
|     requiresBuild: true |     requiresBuild: true | ||||||
| @@ -809,12 +758,6 @@ packages: | |||||||
|     dev: true |     dev: true | ||||||
|     optional: true |     optional: true | ||||||
|  |  | ||||||
|   /crc@3.8.0: |  | ||||||
|     resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} |  | ||||||
|     dependencies: |  | ||||||
|       buffer: 5.7.1 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /crypto-random-string@2.0.0: |   /crypto-random-string@2.0.0: | ||||||
|     resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} |     resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} | ||||||
|     engines: {node: '>=8'} |     engines: {node: '>=8'} | ||||||
| @@ -1485,10 +1428,6 @@ packages: | |||||||
|       sshpk: 1.16.1 |       sshpk: 1.16.1 | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /ieee754@1.2.1: |  | ||||||
|     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /import-lazy@2.1.0: |   /import-lazy@2.1.0: | ||||||
|     resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} |     resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} | ||||||
|     engines: {node: '>=4'} |     engines: {node: '>=4'} | ||||||
| @@ -1850,13 +1789,6 @@ packages: | |||||||
|     dev: true |     dev: true | ||||||
|     optional: true |     optional: true | ||||||
|  |  | ||||||
|   /isomorphic-unzip@1.1.5: |  | ||||||
|     resolution: {integrity: sha512-2McA51lWhmO3Kk438jxVcYeh6L+AOqVnl9XdX1yI7GlLA9RwEyTBgGem1rNuRIU2abAmOiv+IagThdUxASY4IA==} |  | ||||||
|     dependencies: |  | ||||||
|       buffer: 5.7.1 |  | ||||||
|       yauzl: 2.10.0 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /isstream@0.1.2: |   /isstream@0.1.2: | ||||||
|     resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} |     resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} | ||||||
|     dev: false |     dev: false | ||||||
| @@ -1980,11 +1912,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} |     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} | ||||||
|     dev: true |     dev: true | ||||||
|  |  | ||||||
|   /long@3.2.0: |  | ||||||
|     resolution: {integrity: sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==} |  | ||||||
|     engines: {node: '>=0.6'} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /loose-envify@1.4.0: |   /loose-envify@1.4.0: | ||||||
|     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} |     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} | ||||||
|     hasBin: true |     hasBin: true | ||||||
| @@ -2347,14 +2274,6 @@ packages: | |||||||
|     resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} |     resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /plist@3.0.4: |  | ||||||
|     resolution: {integrity: sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==} |  | ||||||
|     engines: {node: '>=6'} |  | ||||||
|     dependencies: |  | ||||||
|       base64-js: 1.5.1 |  | ||||||
|       xmlbuilder: 9.0.7 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /posix-character-classes@0.1.1: |   /posix-character-classes@0.1.1: | ||||||
|     resolution: {integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=} |     resolution: {integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
| @@ -2785,29 +2704,12 @@ packages: | |||||||
|     dev: true |     dev: true | ||||||
|     optional: true |     optional: true | ||||||
|  |  | ||||||
|   /stream-to-buffer@0.1.0: |  | ||||||
|     resolution: {integrity: sha512-Da4WoKaZyu3nf+bIdIifh7IPkFjARBnBK+pYqn0EUJqksjV9afojjaCCHUemH30Jmu7T2qcKvlZm2ykN38uzaw==} |  | ||||||
|     engines: {node: '>= 0.8'} |  | ||||||
|     dependencies: |  | ||||||
|       stream-to: 0.2.2 |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /stream-to@0.2.2: |  | ||||||
|     resolution: {integrity: sha512-Kg1BSDTwgGiVMtTCJNlo7kk/xzL33ZuZveEBRt6rXw+f1WLK/8kmz2NVCT/Qnv0JkV85JOHcLhD82mnXsR3kPw==} |  | ||||||
|     engines: {node: '>= 0.10.0'} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /stream-transform@2.1.3: |   /stream-transform@2.1.3: | ||||||
|     resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} |     resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} | ||||||
|     dependencies: |     dependencies: | ||||||
|       mixme: 0.5.4 |       mixme: 0.5.4 | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /streamifier@0.1.1: |  | ||||||
|     resolution: {integrity: sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==} |  | ||||||
|     engines: {node: '>=0.10'} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /string-width@4.2.3: |   /string-width@4.2.3: | ||||||
|     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} |     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} | ||||||
|     engines: {node: '>=8'} |     engines: {node: '>=8'} | ||||||
| @@ -3164,11 +3066,6 @@ packages: | |||||||
|     engines: {node: '>=8'} |     engines: {node: '>=8'} | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   /xmlbuilder@9.0.7: |  | ||||||
|     resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==} |  | ||||||
|     engines: {node: '>=4.0'} |  | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /y18n@4.0.3: |   /y18n@4.0.3: | ||||||
|     resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} |     resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} | ||||||
|     dev: false |     dev: false | ||||||
| @@ -3237,17 +3134,3 @@ packages: | |||||||
|     dependencies: |     dependencies: | ||||||
|       buffer-crc32: 0.2.13 |       buffer-crc32: 0.2.13 | ||||||
|     dev: false |     dev: false | ||||||
|  |  | ||||||
|   github.com/sunnylqm/app-info-parser/f7748ce60278fb16f6f280d908d35025a241eb57: |  | ||||||
|     resolution: {tarball: https://codeload.github.com/sunnylqm/app-info-parser/tar.gz/f7748ce60278fb16f6f280d908d35025a241eb57} |  | ||||||
|     name: app-info-parser |  | ||||||
|     version: 1.1.6 |  | ||||||
|     hasBin: true |  | ||||||
|     dependencies: |  | ||||||
|       bplist-parser: 0.2.0 |  | ||||||
|       bytebuffer: 5.0.1 |  | ||||||
|       cgbi-to-png: 1.0.7 |  | ||||||
|       commander: 7.2.0 |  | ||||||
|       isomorphic-unzip: 1.1.5 |  | ||||||
|       plist: 3.0.4 |  | ||||||
|     dev: false |  | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								src/utils/app-info-parser/apk.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/utils/app-info-parser/apk.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | const Zip = require('./zip') | ||||||
|  | const { mapInfoResource, findApkIconPath, getBase64FromBuffer } = require('./utils') | ||||||
|  | const ManifestName = /^androidmanifest\.xml$/ | ||||||
|  | const ResourceName = /^resources\.arsc$/ | ||||||
|  |  | ||||||
|  | const ManifestXmlParser = require('./xml-parser/manifest') | ||||||
|  | const ResourceFinder = require('./resource-finder') | ||||||
|  |  | ||||||
|  | class ApkParser extends Zip { | ||||||
|  |   /** | ||||||
|  |    * parser for parsing .apk file | ||||||
|  |    * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser | ||||||
|  |    */ | ||||||
|  |   constructor (file) { | ||||||
|  |     super(file) | ||||||
|  |     if (!(this instanceof ApkParser)) { | ||||||
|  |       return new ApkParser(file) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   parse () { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       this.getEntries([ManifestName, ResourceName]).then(buffers => { | ||||||
|  |         if (!buffers[ManifestName]) { | ||||||
|  |           throw new Error('AndroidManifest.xml can\'t be found.') | ||||||
|  |         } | ||||||
|  |         let apkInfo = this._parseManifest(buffers[ManifestName]) | ||||||
|  |         let resourceMap | ||||||
|  |         if (!buffers[ResourceName]) { | ||||||
|  |           resolve(apkInfo) | ||||||
|  |         } else { | ||||||
|  |           // parse resourceMap | ||||||
|  |           resourceMap = this._parseResourceMap(buffers[ResourceName]) | ||||||
|  |           // update apkInfo with resourceMap | ||||||
|  |           apkInfo = mapInfoResource(apkInfo, resourceMap) | ||||||
|  |  | ||||||
|  |           // find icon path and parse icon | ||||||
|  |           const iconPath = findApkIconPath(apkInfo) | ||||||
|  |           if (iconPath) { | ||||||
|  |             this.getEntry(iconPath).then(iconBuffer => { | ||||||
|  |               apkInfo.icon = iconBuffer ? getBase64FromBuffer(iconBuffer) : null | ||||||
|  |               resolve(apkInfo) | ||||||
|  |             }).catch(e => { | ||||||
|  |               apkInfo.icon = null | ||||||
|  |               resolve(apkInfo) | ||||||
|  |               console.warn('[Warning] failed to parse icon: ', e) | ||||||
|  |             }) | ||||||
|  |           } else { | ||||||
|  |             apkInfo.icon = null | ||||||
|  |             resolve(apkInfo) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }).catch(e => { | ||||||
|  |         reject(e) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * Parse manifest | ||||||
|  |    * @param {Buffer} buffer // manifest file's buffer | ||||||
|  |    */ | ||||||
|  |   _parseManifest (buffer) { | ||||||
|  |     try { | ||||||
|  |       const parser = new ManifestXmlParser(buffer, { | ||||||
|  |         ignore: [ | ||||||
|  |           'application.activity', | ||||||
|  |           'application.service', | ||||||
|  |           'application.receiver', | ||||||
|  |           'application.provider', | ||||||
|  |           'permission-group' | ||||||
|  |         ] | ||||||
|  |       }) | ||||||
|  |       return parser.parse() | ||||||
|  |     } catch (e) { | ||||||
|  |       throw new Error('Parse AndroidManifest.xml error: ', e) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * Parse resourceMap | ||||||
|  |    * @param {Buffer} buffer // resourceMap file's buffer | ||||||
|  |    */ | ||||||
|  |   _parseResourceMap (buffer) { | ||||||
|  |     try { | ||||||
|  |       return new ResourceFinder().processResourceTable(buffer) | ||||||
|  |     } catch (e) { | ||||||
|  |       throw new Error('Parser resources.arsc error: ' + e) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = ApkParser | ||||||
							
								
								
									
										35
									
								
								src/utils/app-info-parser/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/utils/app-info-parser/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | const ApkParser = require('./apk') | ||||||
|  | const IpaParser = require('./ipa') | ||||||
|  | const supportFileTypes = ['ipa', 'apk'] | ||||||
|  |  | ||||||
|  | class AppInfoParser { | ||||||
|  |   /** | ||||||
|  |    * parser for parsing .ipa or .apk file | ||||||
|  |    * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser | ||||||
|  |    */ | ||||||
|  |   constructor (file) { | ||||||
|  |     if (!file) { | ||||||
|  |       throw new Error('Param miss: file(file\'s path in Node, instance of File or Blob in browser).') | ||||||
|  |     } | ||||||
|  |     const splits = (file.name || file).split('.') | ||||||
|  |     const fileType = splits[splits.length - 1].toLowerCase() | ||||||
|  |     if (!supportFileTypes.includes(fileType)) { | ||||||
|  |       throw new Error('Unsupported file type, only support .ipa or .apk file.') | ||||||
|  |     } | ||||||
|  |     this.file = file | ||||||
|  |  | ||||||
|  |     switch (fileType) { | ||||||
|  |       case 'ipa': | ||||||
|  |         this.parser = new IpaParser(this.file) | ||||||
|  |         break | ||||||
|  |       case 'apk': | ||||||
|  |         this.parser = new ApkParser(this.file) | ||||||
|  |         break | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   parse () { | ||||||
|  |     return this.parser.parse() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = AppInfoParser | ||||||
							
								
								
									
										92
									
								
								src/utils/app-info-parser/ipa.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/utils/app-info-parser/ipa.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | const parsePlist = require('plist').parse | ||||||
|  | const parseBplist = require('bplist-parser').parseBuffer | ||||||
|  | const cgbiToPng = require('cgbi-to-png') | ||||||
|  |  | ||||||
|  | const Zip = require('./zip') | ||||||
|  | const { findIpaIconPath, getBase64FromBuffer, isBrowser } = require('./utils') | ||||||
|  |  | ||||||
|  | const PlistName = new RegExp('payload/[^/]+?.app/info.plist$', 'i') | ||||||
|  | const ProvisionName = /payload\/.+?\.app\/embedded.mobileprovision/ | ||||||
|  |  | ||||||
|  | class IpaParser extends Zip { | ||||||
|  |   /** | ||||||
|  |    * parser for parsing .ipa file | ||||||
|  |    * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser | ||||||
|  |    */ | ||||||
|  |   constructor (file) { | ||||||
|  |     super(file) | ||||||
|  |     if (!(this instanceof IpaParser)) { | ||||||
|  |       return new IpaParser(file) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   parse () { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       this.getEntries([PlistName, ProvisionName]).then(buffers => { | ||||||
|  |         if (!buffers[PlistName]) { | ||||||
|  |           throw new Error('Info.plist can\'t be found.') | ||||||
|  |         } | ||||||
|  |         const plistInfo = this._parsePlist(buffers[PlistName]) | ||||||
|  |         // parse mobile provision | ||||||
|  |         const provisionInfo = this._parseProvision(buffers[ProvisionName]) | ||||||
|  |         plistInfo.mobileProvision = provisionInfo | ||||||
|  |  | ||||||
|  |         // find icon path and parse icon | ||||||
|  |         const iconRegex = new RegExp(findIpaIconPath(plistInfo).toLowerCase()) | ||||||
|  |         this.getEntry(iconRegex).then(iconBuffer => { | ||||||
|  |           try { | ||||||
|  |             // In general, the ipa file's icon has been specially processed, should be converted | ||||||
|  |             plistInfo.icon = iconBuffer ? getBase64FromBuffer(cgbiToPng.revert(iconBuffer)) : null | ||||||
|  |           } catch (err) { | ||||||
|  |             if (isBrowser()) { | ||||||
|  |               // Normal conversion in other cases | ||||||
|  |               plistInfo.icon = iconBuffer ? getBase64FromBuffer(window.btoa(String.fromCharCode(...iconBuffer))) : null | ||||||
|  |             } else { | ||||||
|  |               plistInfo.icon = null | ||||||
|  |               console.warn('[Warning] failed to parse icon: ', err) | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           resolve(plistInfo) | ||||||
|  |         }).catch(e => { | ||||||
|  |           reject(e) | ||||||
|  |         }) | ||||||
|  |       }).catch(e => { | ||||||
|  |         reject(e) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * Parse plist | ||||||
|  |    * @param {Buffer} buffer // plist file's buffer | ||||||
|  |    */ | ||||||
|  |   _parsePlist (buffer) { | ||||||
|  |     let result | ||||||
|  |     const bufferType = buffer[0] | ||||||
|  |     if (bufferType === 60 || bufferType === '<' || bufferType === 239) { | ||||||
|  |       result = parsePlist(buffer.toString()) | ||||||
|  |     } else if (bufferType === 98) { | ||||||
|  |       result = parseBplist(buffer)[0] | ||||||
|  |     } else { | ||||||
|  |       throw new Error('Unknown plist buffer type.') | ||||||
|  |     } | ||||||
|  |     return result | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * parse provision | ||||||
|  |    * @param {Buffer} buffer // provision file's buffer | ||||||
|  |    */ | ||||||
|  |   _parseProvision (buffer) { | ||||||
|  |     let info = {} | ||||||
|  |     if (buffer) { | ||||||
|  |       let content = buffer.toString('utf-8') | ||||||
|  |       const firstIndex = content.indexOf('<?xml') | ||||||
|  |       const endIndex = content.indexOf('</plist>') | ||||||
|  |       content = content.slice(firstIndex, endIndex + 8) | ||||||
|  |       if (content) { | ||||||
|  |         info = parsePlist(content) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return info | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = IpaParser | ||||||
							
								
								
									
										499
									
								
								src/utils/app-info-parser/resource-finder.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										499
									
								
								src/utils/app-info-parser/resource-finder.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,499 @@ | |||||||
|  | /** | ||||||
|  |  * Code translated from a C# project https://github.com/hylander0/Iteedee.ApkReader/blob/master/Iteedee.ApkReader/ApkResourceFinder.cs | ||||||
|  |  * | ||||||
|  |  * Decode binary file `resources.arsc` from a .apk file to a JavaScript Object. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | var ByteBuffer = require("bytebuffer"); | ||||||
|  |  | ||||||
|  | var DEBUG = false; | ||||||
|  |  | ||||||
|  | var RES_STRING_POOL_TYPE = 0x0001; | ||||||
|  | var RES_TABLE_TYPE = 0x0002; | ||||||
|  | var RES_TABLE_PACKAGE_TYPE = 0x0200; | ||||||
|  | var RES_TABLE_TYPE_TYPE = 0x0201; | ||||||
|  | var RES_TABLE_TYPE_SPEC_TYPE = 0x0202; | ||||||
|  |  | ||||||
|  | // The 'data' holds a ResTable_ref, a reference to another resource | ||||||
|  | // table entry. | ||||||
|  | var TYPE_REFERENCE = 0x01; | ||||||
|  | // The 'data' holds an index into the containing resource table's | ||||||
|  | // global value string pool. | ||||||
|  | var TYPE_STRING = 0x03; | ||||||
|  |  | ||||||
|  | function ResourceFinder() { | ||||||
|  |   this.valueStringPool = null; | ||||||
|  |   this.typeStringPool = null; | ||||||
|  |   this.keyStringPool = null; | ||||||
|  |  | ||||||
|  |   this.package_id = 0; | ||||||
|  |  | ||||||
|  |   this.responseMap = {}; | ||||||
|  |   this.entryMap = {}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Same to C# BinaryReader.readBytes | ||||||
|  |  * | ||||||
|  |  * @param bb ByteBuffer | ||||||
|  |  * @param len length | ||||||
|  |  * @returns {Buffer} | ||||||
|  |  */ | ||||||
|  | ResourceFinder.readBytes = function(bb, len) { | ||||||
|  |   var uint8Array = new Uint8Array(len); | ||||||
|  |   for (var i = 0; i < len; i++) { | ||||||
|  |     uint8Array[i] = bb.readUint8(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ByteBuffer.wrap(uint8Array, "binary", true); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // | ||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * @param {ByteBuffer} bb | ||||||
|  |  * @return {Map<String, Set<String>>} | ||||||
|  |  */ | ||||||
|  | ResourceFinder.prototype.processResourceTable = function(resourceBuffer) { | ||||||
|  |   const bb = ByteBuffer.wrap(resourceBuffer, "binary", true); | ||||||
|  |  | ||||||
|  |   // Resource table structure | ||||||
|  |   var type = bb.readShort(), | ||||||
|  |     headerSize = bb.readShort(), | ||||||
|  |     size = bb.readInt(), | ||||||
|  |     packageCount = bb.readInt(), | ||||||
|  |     buffer, | ||||||
|  |     bb2; | ||||||
|  |   if (type != RES_TABLE_TYPE) { | ||||||
|  |     throw new Error("No RES_TABLE_TYPE found!"); | ||||||
|  |   } | ||||||
|  |   if (size != bb.limit) { | ||||||
|  |     throw new Error("The buffer size not matches to the resource table size."); | ||||||
|  |   } | ||||||
|  |   bb.offset = headerSize; | ||||||
|  |  | ||||||
|  |   var realStringPoolCount = 0, | ||||||
|  |     realPackageCount = 0; | ||||||
|  |  | ||||||
|  |   while (true) { | ||||||
|  |     var pos, t, hs, s; | ||||||
|  |     try { | ||||||
|  |       pos = bb.offset; | ||||||
|  |       t = bb.readShort(); | ||||||
|  |       hs = bb.readShort(); | ||||||
|  |       s = bb.readInt(); | ||||||
|  |     } catch (e) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     if (t == RES_STRING_POOL_TYPE) { | ||||||
|  |       // Process the string pool | ||||||
|  |       if (realStringPoolCount == 0) { | ||||||
|  |         // Only the first string pool is processed. | ||||||
|  |         if (DEBUG) { | ||||||
|  |           console.log("Processing the string pool ..."); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         buffer = new ByteBuffer(s); | ||||||
|  |         bb.offset = pos; | ||||||
|  |         bb.prependTo(buffer); | ||||||
|  |  | ||||||
|  |         bb2 = ByteBuffer.wrap(buffer, "binary", true); | ||||||
|  |  | ||||||
|  |         bb2.LE(); | ||||||
|  |         this.valueStringPool = this.processStringPool(bb2); | ||||||
|  |       } | ||||||
|  |       realStringPoolCount++; | ||||||
|  |     } else if (t == RES_TABLE_PACKAGE_TYPE) { | ||||||
|  |       // Process the package | ||||||
|  |       if (DEBUG) { | ||||||
|  |         console.log("Processing the package " + realPackageCount + " ..."); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       buffer = new ByteBuffer(s); | ||||||
|  |       bb.offset = pos; | ||||||
|  |       bb.prependTo(buffer); | ||||||
|  |  | ||||||
|  |       bb2 = ByteBuffer.wrap(buffer, "binary", true); | ||||||
|  |       bb2.LE(); | ||||||
|  |       this.processPackage(bb2); | ||||||
|  |  | ||||||
|  |       realPackageCount++; | ||||||
|  |     } else { | ||||||
|  |       throw new Error("Unsupported type"); | ||||||
|  |     } | ||||||
|  |     bb.offset = pos + s; | ||||||
|  |     if (!bb.remaining()) break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (realStringPoolCount != 1) { | ||||||
|  |     throw new Error("More than 1 string pool found!"); | ||||||
|  |   } | ||||||
|  |   if (realPackageCount != packageCount) { | ||||||
|  |     throw new Error("Real package count not equals the declared count."); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return this.responseMap; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * @param {ByteBuffer} bb | ||||||
|  |  */ | ||||||
|  | ResourceFinder.prototype.processPackage = function(bb) { | ||||||
|  |   // Package structure | ||||||
|  |   var type = bb.readShort(), | ||||||
|  |     headerSize = bb.readShort(), | ||||||
|  |     size = bb.readInt(), | ||||||
|  |     id = bb.readInt(); | ||||||
|  |  | ||||||
|  |   this.package_id = id; | ||||||
|  |  | ||||||
|  |   for (var i = 0; i < 256; ++i) { | ||||||
|  |     bb.readUint8(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   var typeStrings = bb.readInt(), | ||||||
|  |     lastPublicType = bb.readInt(), | ||||||
|  |     keyStrings = bb.readInt(), | ||||||
|  |     lastPublicKey = bb.readInt(); | ||||||
|  |  | ||||||
|  |   if (typeStrings != headerSize) { | ||||||
|  |     throw new Error( | ||||||
|  |       "TypeStrings must immediately following the package structure header." | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (DEBUG) { | ||||||
|  |     console.log("Type strings:"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   var lastPosition = bb.offset; | ||||||
|  |   bb.offset = typeStrings; | ||||||
|  |   var bbTypeStrings = ResourceFinder.readBytes(bb, bb.limit - bb.offset); | ||||||
|  |   bb.offset = lastPosition; | ||||||
|  |   this.typeStringPool = this.processStringPool(bbTypeStrings); | ||||||
|  |  | ||||||
|  |   // Key strings | ||||||
|  |   if (DEBUG) { | ||||||
|  |     console.log("Key strings:"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bb.offset = keyStrings; | ||||||
|  |   var key_type = bb.readShort(), | ||||||
|  |     key_headerSize = bb.readShort(), | ||||||
|  |     key_size = bb.readInt(); | ||||||
|  |  | ||||||
|  |   lastPosition = bb.offset; | ||||||
|  |   bb.offset = keyStrings; | ||||||
|  |   var bbKeyStrings = ResourceFinder.readBytes(bb, bb.limit - bb.offset); | ||||||
|  |   bb.offset = lastPosition; | ||||||
|  |   this.keyStringPool = this.processStringPool(bbKeyStrings); | ||||||
|  |  | ||||||
|  |   // Iterate through all chunks | ||||||
|  |   var typeSpecCount = 0; | ||||||
|  |   var typeCount = 0; | ||||||
|  |  | ||||||
|  |   bb.offset = keyStrings + key_size; | ||||||
|  |  | ||||||
|  |   var bb2; | ||||||
|  |  | ||||||
|  |   while (true) { | ||||||
|  |     var pos = bb.offset; | ||||||
|  |     try { | ||||||
|  |       var t = bb.readShort(); | ||||||
|  |       var hs = bb.readShort(); | ||||||
|  |       var s = bb.readInt(); | ||||||
|  |     } catch (e) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (t == RES_TABLE_TYPE_SPEC_TYPE) { | ||||||
|  |       bb.offset = pos; | ||||||
|  |       bb2 = ResourceFinder.readBytes(bb, s); | ||||||
|  |       this.processTypeSpec(bb2); | ||||||
|  |  | ||||||
|  |       typeSpecCount++; | ||||||
|  |     } else if (t == RES_TABLE_TYPE_TYPE) { | ||||||
|  |       bb.offset = pos; | ||||||
|  |       bb2 = ResourceFinder.readBytes(bb, s); | ||||||
|  |       this.processType(bb2); | ||||||
|  |  | ||||||
|  |       typeCount++; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (s == 0) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bb.offset = pos + s; | ||||||
|  |  | ||||||
|  |     if (!bb.remaining()) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * @param {ByteBuffer} bb | ||||||
|  |  */ | ||||||
|  | ResourceFinder.prototype.processType = function(bb) { | ||||||
|  |   var type = bb.readShort(), | ||||||
|  |     headerSize = bb.readShort(), | ||||||
|  |     size = bb.readInt(), | ||||||
|  |     id = bb.readByte(), | ||||||
|  |     res0 = bb.readByte(), | ||||||
|  |     res1 = bb.readShort(), | ||||||
|  |     entryCount = bb.readInt(), | ||||||
|  |     entriesStart = bb.readInt(); | ||||||
|  |  | ||||||
|  |   var refKeys = {}; | ||||||
|  |  | ||||||
|  |   var config_size = bb.readInt(); | ||||||
|  |  | ||||||
|  |   // Skip the config data | ||||||
|  |   bb.offset = headerSize; | ||||||
|  |  | ||||||
|  |   if (headerSize + entryCount * 4 != entriesStart) { | ||||||
|  |     throw new Error("HeaderSize, entryCount and entriesStart are not valid."); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Start to get entry indices | ||||||
|  |   var entryIndices = new Array(entryCount); | ||||||
|  |   for (var i = 0; i < entryCount; ++i) { | ||||||
|  |     entryIndices[i] = bb.readInt(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Get entries | ||||||
|  |   for (var i = 0; i < entryCount; ++i) { | ||||||
|  |     if (entryIndices[i] == -1) continue; | ||||||
|  |  | ||||||
|  |     var resource_id = (this.package_id << 24) | (id << 16) | i; | ||||||
|  |  | ||||||
|  |     var pos = bb.offset, | ||||||
|  |       entry_size, | ||||||
|  |       entry_flag, | ||||||
|  |       entry_key, | ||||||
|  |       value_size, | ||||||
|  |       value_res0, | ||||||
|  |       value_dataType, | ||||||
|  |       value_data; | ||||||
|  |     try { | ||||||
|  |       entry_size = bb.readShort() | ||||||
|  |       entry_flag = bb.readShort() | ||||||
|  |       entry_key = bb.readInt() | ||||||
|  |     } catch (e) { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Get the value (simple) or map (complex) | ||||||
|  |  | ||||||
|  |     var FLAG_COMPLEX = 0x0001; | ||||||
|  |     if ((entry_flag & FLAG_COMPLEX) == 0) { | ||||||
|  |       // Simple case | ||||||
|  |       value_size = bb.readShort(); | ||||||
|  |       value_res0 = bb.readByte(); | ||||||
|  |       value_dataType = bb.readByte(); | ||||||
|  |       value_data = bb.readInt(); | ||||||
|  |  | ||||||
|  |       var idStr = Number(resource_id).toString(16); | ||||||
|  |       var keyStr = this.keyStringPool[entry_key]; | ||||||
|  |  | ||||||
|  |       var data = null; | ||||||
|  |  | ||||||
|  |       if (DEBUG) { | ||||||
|  |         console.log( | ||||||
|  |           "Entry 0x" + idStr + ", key: " + keyStr + ", simple value type: " | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       var key = parseInt(idStr, 16); | ||||||
|  |  | ||||||
|  |       var entryArr = this.entryMap[key]; | ||||||
|  |       if (entryArr == null) { | ||||||
|  |         entryArr = []; | ||||||
|  |       } | ||||||
|  |       entryArr.push(keyStr); | ||||||
|  |  | ||||||
|  |       this.entryMap[key] = entryArr; | ||||||
|  |  | ||||||
|  |       if (value_dataType == TYPE_STRING) { | ||||||
|  |         data = this.valueStringPool[value_data]; | ||||||
|  |  | ||||||
|  |         if (DEBUG) { | ||||||
|  |           console.log(", data: " + this.valueStringPool[value_data] + ""); | ||||||
|  |         } | ||||||
|  |       } else if (value_dataType == TYPE_REFERENCE) { | ||||||
|  |         var hexIndex = Number(value_data).toString(16); | ||||||
|  |  | ||||||
|  |         refKeys[idStr] = value_data; | ||||||
|  |       } else { | ||||||
|  |         data = "" + value_data; | ||||||
|  |         if (DEBUG) { | ||||||
|  |           console.log(", data: " + value_data + ""); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       this.putIntoMap("@" + idStr, data); | ||||||
|  |     } else { | ||||||
|  |       // Complex case | ||||||
|  |       var entry_parent = bb.readInt(); | ||||||
|  |       var entry_count = bb.readInt(); | ||||||
|  |  | ||||||
|  |       for (var j = 0; j < entry_count; ++j) { | ||||||
|  |         var ref_name = bb.readInt(); | ||||||
|  |         value_size = bb.readShort(); | ||||||
|  |         value_res0 = bb.readByte(); | ||||||
|  |         value_dataType = bb.readByte(); | ||||||
|  |         value_data = bb.readInt(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (DEBUG) { | ||||||
|  |         console.log( | ||||||
|  |           "Entry 0x" + | ||||||
|  |             Number(resource_id).toString(16) + | ||||||
|  |             ", key: " + | ||||||
|  |             this.keyStringPool[entry_key] + | ||||||
|  |             ", complex value, not printed." | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (var refK in refKeys) { | ||||||
|  |     var values = this.responseMap[ | ||||||
|  |       "@" + | ||||||
|  |         Number(refKeys[refK]) | ||||||
|  |           .toString(16) | ||||||
|  |           .toUpperCase() | ||||||
|  |     ]; | ||||||
|  |     if (values != null && Object.keys(values).length < 1000) { | ||||||
|  |       for (var value in values) { | ||||||
|  |         this.putIntoMap("@" + refK, values[value]); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * @param {ByteBuffer} bb | ||||||
|  |  * @return {Array} | ||||||
|  |  */ | ||||||
|  | ResourceFinder.prototype.processStringPool = function(bb) { | ||||||
|  |   // String pool structure | ||||||
|  |   // | ||||||
|  |   var type = bb.readShort(), | ||||||
|  |     headerSize = bb.readShort(), | ||||||
|  |     size = bb.readInt(), | ||||||
|  |     stringCount = bb.readInt(), | ||||||
|  |     styleCount = bb.readInt(), | ||||||
|  |     flags = bb.readInt(), | ||||||
|  |     stringsStart = bb.readInt(), | ||||||
|  |     stylesStart = bb.readInt(), | ||||||
|  |     u16len, | ||||||
|  |     buffer; | ||||||
|  |  | ||||||
|  |   var isUTF_8 = (flags & 256) != 0; | ||||||
|  |  | ||||||
|  |   var offsets = new Array(stringCount); | ||||||
|  |   for (var i = 0; i < stringCount; ++i) { | ||||||
|  |     offsets[i] = bb.readInt(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   var strings = new Array(stringCount); | ||||||
|  |  | ||||||
|  |   for (var i = 0; i < stringCount; ++i) { | ||||||
|  |     var pos = stringsStart + offsets[i]; | ||||||
|  |     bb.offset = pos; | ||||||
|  |  | ||||||
|  |     strings[i] = ""; | ||||||
|  |  | ||||||
|  |     if (isUTF_8) { | ||||||
|  |       u16len = bb.readUint8(); | ||||||
|  |  | ||||||
|  |       if ((u16len & 0x80) != 0) { | ||||||
|  |         u16len = ((u16len & 0x7f) << 8) + bb.readUint8(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       var u8len = bb.readUint8(); | ||||||
|  |       if ((u8len & 0x80) != 0) { | ||||||
|  |         u8len = ((u8len & 0x7f) << 8) + bb.readUint8(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (u8len > 0) { | ||||||
|  |         buffer = ResourceFinder.readBytes(bb, u8len); | ||||||
|  |         try { | ||||||
|  |           strings[i] = ByteBuffer.wrap(buffer, "utf8", true).toString("utf8"); | ||||||
|  |         } catch (e) { | ||||||
|  |           if (DEBUG) { | ||||||
|  |             console.error(e); | ||||||
|  |             console.log("Error when turning buffer to utf-8 string."); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         strings[i] = ""; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       u16len = bb.readUint16(); | ||||||
|  |       if ((u16len & 0x8000) != 0) { | ||||||
|  |         // larger than 32768 | ||||||
|  |         u16len = ((u16len & 0x7fff) << 16) + bb.readUint16(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (u16len > 0) { | ||||||
|  |         var len = u16len * 2; | ||||||
|  |         buffer = ResourceFinder.readBytes(bb, len); | ||||||
|  |         try { | ||||||
|  |           strings[i] = ByteBuffer.wrap(buffer, "utf8", true).toString("utf8"); | ||||||
|  |         } catch (e) { | ||||||
|  |           if (DEBUG) { | ||||||
|  |             console.error(e); | ||||||
|  |             console.log("Error when turning buffer to utf-8 string."); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (DEBUG) { | ||||||
|  |       console.log("Parsed value: {0}", strings[i]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return strings; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * @param {ByteBuffer} bb | ||||||
|  |  */ | ||||||
|  | ResourceFinder.prototype.processTypeSpec = function(bb) { | ||||||
|  |   var type = bb.readShort(), | ||||||
|  |     headerSize = bb.readShort(), | ||||||
|  |     size = bb.readInt(), | ||||||
|  |     id = bb.readByte(), | ||||||
|  |     res0 = bb.readByte(), | ||||||
|  |     res1 = bb.readShort(), | ||||||
|  |     entryCount = bb.readInt(); | ||||||
|  |  | ||||||
|  |   if (DEBUG) { | ||||||
|  |     console.log("Processing type spec " + this.typeStringPool[id - 1] + "..."); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   var flags = new Array(entryCount); | ||||||
|  |  | ||||||
|  |   for (var i = 0; i < entryCount; ++i) { | ||||||
|  |     flags[i] = bb.readInt(); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ResourceFinder.prototype.putIntoMap = function(resId, value) { | ||||||
|  |   if (this.responseMap[resId.toUpperCase()] == null) { | ||||||
|  |     this.responseMap[resId.toUpperCase()] = [] | ||||||
|  |   } | ||||||
|  |   if(value){ | ||||||
|  |     this.responseMap[resId.toUpperCase()].push(value) | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = ResourceFinder; | ||||||
							
								
								
									
										167
									
								
								src/utils/app-info-parser/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/utils/app-info-parser/utils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | function objectType (o) { | ||||||
|  |   return Object.prototype.toString.call(o).slice(8, -1).toLowerCase() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function isArray (o) { | ||||||
|  |   return objectType(o) === 'array' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function isObject (o) { | ||||||
|  |   return objectType(o) === 'object' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function isPrimitive (o) { | ||||||
|  |   return o === null || ['boolean', 'number', 'string', 'undefined'].includes(objectType(o)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function isBrowser () { | ||||||
|  |   return ( | ||||||
|  |     typeof process === 'undefined' || | ||||||
|  |     Object.prototype.toString.call(process) !== '[object process]' | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * map file place with resourceMap | ||||||
|  |  * @param {Object} apkInfo // json info parsed from .apk file | ||||||
|  |  * @param {Object} resourceMap // resourceMap | ||||||
|  |  */ | ||||||
|  | function mapInfoResource (apkInfo, resourceMap) { | ||||||
|  |   iteratorObj(apkInfo) | ||||||
|  |   return apkInfo | ||||||
|  |   function iteratorObj (obj) { | ||||||
|  |     for (var i in obj) { | ||||||
|  |       if (isArray(obj[i])) { | ||||||
|  |         iteratorArray(obj[i]) | ||||||
|  |       } else if (isObject(obj[i])) { | ||||||
|  |         iteratorObj(obj[i]) | ||||||
|  |       } else if (isPrimitive(obj[i])) { | ||||||
|  |         if (isResources(obj[i])) { | ||||||
|  |           obj[i] = resourceMap[transKeyToMatchResourceMap(obj[i])] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   function iteratorArray (array) { | ||||||
|  |     const l = array.length | ||||||
|  |     for (let i = 0; i < l; i++) { | ||||||
|  |       if (isArray(array[i])) { | ||||||
|  |         iteratorArray(array[i]) | ||||||
|  |       } else if (isObject(array[i])) { | ||||||
|  |         iteratorObj(array[i]) | ||||||
|  |       } else if (isPrimitive(array[i])) { | ||||||
|  |         if (isResources(array[i])) { | ||||||
|  |           array[i] = resourceMap[transKeyToMatchResourceMap(array[i])] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   function isResources (attrValue) { | ||||||
|  |     if (!attrValue) return false | ||||||
|  |     if (typeof attrValue !== 'string') { | ||||||
|  |       attrValue = attrValue.toString() | ||||||
|  |     } | ||||||
|  |     return attrValue.indexOf('resourceId:') === 0 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   function transKeyToMatchResourceMap (resourceId) { | ||||||
|  |     return '@' + resourceId.replace('resourceId:0x', '').toUpperCase() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * find .apk file's icon path from json info | ||||||
|  |  * @param info // json info parsed from .apk file | ||||||
|  |  */ | ||||||
|  | function findApkIconPath (info) { | ||||||
|  |   if (!info.application.icon || !info.application.icon.splice) { | ||||||
|  |     return '' | ||||||
|  |   } | ||||||
|  |   const rulesMap = { | ||||||
|  |     mdpi: 48, | ||||||
|  |     hdpi: 72, | ||||||
|  |     xhdpi: 96, | ||||||
|  |     xxdpi: 144, | ||||||
|  |     xxxhdpi: 192 | ||||||
|  |   } | ||||||
|  |   const resultMap = {} | ||||||
|  |   const maxDpiIcon = { dpi: 120, icon: '' } | ||||||
|  |  | ||||||
|  |   for (const i in rulesMap) { | ||||||
|  |     info.application.icon.some((icon) => { | ||||||
|  |       if (icon && icon.indexOf(i) !== -1) { | ||||||
|  |         resultMap['application-icon-' + rulesMap[i]] = icon | ||||||
|  |         return true | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     // get the maximal size icon | ||||||
|  |     if ( | ||||||
|  |       resultMap['application-icon-' + rulesMap[i]] && | ||||||
|  |       rulesMap[i] >= maxDpiIcon.dpi | ||||||
|  |     ) { | ||||||
|  |       maxDpiIcon.dpi = rulesMap[i] | ||||||
|  |       maxDpiIcon.icon = resultMap['application-icon-' + rulesMap[i]] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (Object.keys(resultMap).length === 0 || !maxDpiIcon.icon) { | ||||||
|  |     maxDpiIcon.dpi = 120 | ||||||
|  |     maxDpiIcon.icon = info.application.icon[0] || '' | ||||||
|  |     resultMap['applicataion-icon-120'] = maxDpiIcon.icon | ||||||
|  |   } | ||||||
|  |   return maxDpiIcon.icon | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * find .ipa file's icon path from json info | ||||||
|  |  * @param info // json info parsed from .ipa file | ||||||
|  |  */ | ||||||
|  | function findIpaIconPath (info) { | ||||||
|  |   if ( | ||||||
|  |     info.CFBundleIcons && | ||||||
|  |     info.CFBundleIcons.CFBundlePrimaryIcon && | ||||||
|  |     info.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles && | ||||||
|  |     info.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles.length | ||||||
|  |   ) { | ||||||
|  |     return info.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles[info.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles.length - 1] | ||||||
|  |   } else if (info.CFBundleIconFiles && info.CFBundleIconFiles.length) { | ||||||
|  |     return info.CFBundleIconFiles[info.CFBundleIconFiles.length - 1] | ||||||
|  |   } else { | ||||||
|  |     return '.app/Icon.png' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * transform buffer to base64 | ||||||
|  |  * @param {Buffer} buffer | ||||||
|  |  */ | ||||||
|  | function getBase64FromBuffer (buffer) { | ||||||
|  |   return 'data:image/png;base64,' + buffer.toString('base64') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 去除unicode空字符 | ||||||
|  |  * @param {String} str | ||||||
|  |  */ | ||||||
|  | function decodeNullUnicode (str) { | ||||||
|  |   if (typeof str === 'string') { | ||||||
|  |     // eslint-disable-next-line | ||||||
|  |     str = str.replace(/\u0000/g, '') | ||||||
|  |   } | ||||||
|  |   return str | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   isArray, | ||||||
|  |   isObject, | ||||||
|  |   isPrimitive, | ||||||
|  |   isBrowser, | ||||||
|  |   mapInfoResource, | ||||||
|  |   findApkIconPath, | ||||||
|  |   findIpaIconPath, | ||||||
|  |   getBase64FromBuffer, | ||||||
|  |   decodeNullUnicode | ||||||
|  | } | ||||||
							
								
								
									
										674
									
								
								src/utils/app-info-parser/xml-parser/binary.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										674
									
								
								src/utils/app-info-parser/xml-parser/binary.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,674 @@ | |||||||
|  | // From https://github.com/openstf/adbkit-apkreader | ||||||
|  | const NodeType = { | ||||||
|  |   ELEMENT_NODE: 1, | ||||||
|  |   ATTRIBUTE_NODE: 2, | ||||||
|  |   CDATA_SECTION_NODE: 4 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const ChunkType = { | ||||||
|  |   NULL: 0x0000, | ||||||
|  |   STRING_POOL: 0x0001, | ||||||
|  |   TABLE: 0x0002, | ||||||
|  |   XML: 0x0003, | ||||||
|  |   XML_FIRST_CHUNK: 0x0100, | ||||||
|  |   XML_START_NAMESPACE: 0x0100, | ||||||
|  |   XML_END_NAMESPACE: 0x0101, | ||||||
|  |   XML_START_ELEMENT: 0x0102, | ||||||
|  |   XML_END_ELEMENT: 0x0103, | ||||||
|  |   XML_CDATA: 0x0104, | ||||||
|  |   XML_LAST_CHUNK: 0x017f, | ||||||
|  |   XML_RESOURCE_MAP: 0x0180, | ||||||
|  |   TABLE_PACKAGE: 0x0200, | ||||||
|  |   TABLE_TYPE: 0x0201, | ||||||
|  |   TABLE_TYPE_SPEC: 0x0202 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const StringFlags = { | ||||||
|  |   SORTED: 1 << 0, | ||||||
|  |   UTF8: 1 << 8 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Taken from android.util.TypedValue | ||||||
|  | const TypedValue = { | ||||||
|  |   COMPLEX_MANTISSA_MASK: 0x00ffffff, | ||||||
|  |   COMPLEX_MANTISSA_SHIFT: 0x00000008, | ||||||
|  |   COMPLEX_RADIX_0p23: 0x00000003, | ||||||
|  |   COMPLEX_RADIX_16p7: 0x00000001, | ||||||
|  |   COMPLEX_RADIX_23p0: 0x00000000, | ||||||
|  |   COMPLEX_RADIX_8p15: 0x00000002, | ||||||
|  |   COMPLEX_RADIX_MASK: 0x00000003, | ||||||
|  |   COMPLEX_RADIX_SHIFT: 0x00000004, | ||||||
|  |   COMPLEX_UNIT_DIP: 0x00000001, | ||||||
|  |   COMPLEX_UNIT_FRACTION: 0x00000000, | ||||||
|  |   COMPLEX_UNIT_FRACTION_PARENT: 0x00000001, | ||||||
|  |   COMPLEX_UNIT_IN: 0x00000004, | ||||||
|  |   COMPLEX_UNIT_MASK: 0x0000000f, | ||||||
|  |   COMPLEX_UNIT_MM: 0x00000005, | ||||||
|  |   COMPLEX_UNIT_PT: 0x00000003, | ||||||
|  |   COMPLEX_UNIT_PX: 0x00000000, | ||||||
|  |   COMPLEX_UNIT_SHIFT: 0x00000000, | ||||||
|  |   COMPLEX_UNIT_SP: 0x00000002, | ||||||
|  |   DENSITY_DEFAULT: 0x00000000, | ||||||
|  |   DENSITY_NONE: 0x0000ffff, | ||||||
|  |   TYPE_ATTRIBUTE: 0x00000002, | ||||||
|  |   TYPE_DIMENSION: 0x00000005, | ||||||
|  |   TYPE_FIRST_COLOR_INT: 0x0000001c, | ||||||
|  |   TYPE_FIRST_INT: 0x00000010, | ||||||
|  |   TYPE_FLOAT: 0x00000004, | ||||||
|  |   TYPE_FRACTION: 0x00000006, | ||||||
|  |   TYPE_INT_BOOLEAN: 0x00000012, | ||||||
|  |   TYPE_INT_COLOR_ARGB4: 0x0000001e, | ||||||
|  |   TYPE_INT_COLOR_ARGB8: 0x0000001c, | ||||||
|  |   TYPE_INT_COLOR_RGB4: 0x0000001f, | ||||||
|  |   TYPE_INT_COLOR_RGB8: 0x0000001d, | ||||||
|  |   TYPE_INT_DEC: 0x00000010, | ||||||
|  |   TYPE_INT_HEX: 0x00000011, | ||||||
|  |   TYPE_LAST_COLOR_INT: 0x0000001f, | ||||||
|  |   TYPE_LAST_INT: 0x0000001f, | ||||||
|  |   TYPE_NULL: 0x00000000, | ||||||
|  |   TYPE_REFERENCE: 0x00000001, | ||||||
|  |   TYPE_STRING: 0x00000003 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class BinaryXmlParser { | ||||||
|  |   constructor (buffer, options = {}) { | ||||||
|  |     this.buffer = buffer | ||||||
|  |     this.cursor = 0 | ||||||
|  |     this.strings = [] | ||||||
|  |     this.resources = [] | ||||||
|  |     this.document = null | ||||||
|  |     this.parent = null | ||||||
|  |     this.stack = [] | ||||||
|  |     this.debug = options.debug || false | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readU8 () { | ||||||
|  |     this.debug && console.group('readU8') | ||||||
|  |     this.debug && console.debug('cursor:', this.cursor) | ||||||
|  |     const val = this.buffer[this.cursor] | ||||||
|  |     this.debug && console.debug('value:', val) | ||||||
|  |     this.cursor += 1 | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return val | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readU16 () { | ||||||
|  |     this.debug && console.group('readU16') | ||||||
|  |     this.debug && console.debug('cursor:', this.cursor) | ||||||
|  |     const val = this.buffer.readUInt16LE(this.cursor) | ||||||
|  |     this.debug && console.debug('value:', val) | ||||||
|  |     this.cursor += 2 | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return val | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readS32 () { | ||||||
|  |     this.debug && console.group('readS32') | ||||||
|  |     this.debug && console.debug('cursor:', this.cursor) | ||||||
|  |     const val = this.buffer.readInt32LE(this.cursor) | ||||||
|  |     this.debug && console.debug('value:', val) | ||||||
|  |     this.cursor += 4 | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return val | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readU32 () { | ||||||
|  |     this.debug && console.group('readU32') | ||||||
|  |     this.debug && console.debug('cursor:', this.cursor) | ||||||
|  |     const val = this.buffer.readUInt32LE(this.cursor) | ||||||
|  |     this.debug && console.debug('value:', val) | ||||||
|  |     this.cursor += 4 | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return val | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readLength8 () { | ||||||
|  |     this.debug && console.group('readLength8') | ||||||
|  |     let len = this.readU8() | ||||||
|  |     if (len & 0x80) { | ||||||
|  |       len = (len & 0x7f) << 8 | ||||||
|  |       len += this.readU8() | ||||||
|  |     } | ||||||
|  |     this.debug && console.debug('length:', len) | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return len | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readLength16 () { | ||||||
|  |     this.debug && console.group('readLength16') | ||||||
|  |     let len = this.readU16() | ||||||
|  |     if (len & 0x8000) { | ||||||
|  |       len = (len & 0x7fff) << 16 | ||||||
|  |       len += this.readU16() | ||||||
|  |     } | ||||||
|  |     this.debug && console.debug('length:', len) | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return len | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readDimension () { | ||||||
|  |     this.debug && console.group('readDimension') | ||||||
|  |  | ||||||
|  |     const dimension = { | ||||||
|  |       value: null, | ||||||
|  |       unit: null, | ||||||
|  |       rawUnit: null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const value = this.readU32() | ||||||
|  |     const unit = dimension.value & 0xff | ||||||
|  |  | ||||||
|  |     dimension.value = value >> 8 | ||||||
|  |     dimension.rawUnit = unit | ||||||
|  |  | ||||||
|  |     switch (unit) { | ||||||
|  |       case TypedValue.COMPLEX_UNIT_MM: | ||||||
|  |         dimension.unit = 'mm' | ||||||
|  |         break | ||||||
|  |       case TypedValue.COMPLEX_UNIT_PX: | ||||||
|  |         dimension.unit = 'px' | ||||||
|  |         break | ||||||
|  |       case TypedValue.COMPLEX_UNIT_DIP: | ||||||
|  |         dimension.unit = 'dp' | ||||||
|  |         break | ||||||
|  |       case TypedValue.COMPLEX_UNIT_SP: | ||||||
|  |         dimension.unit = 'sp' | ||||||
|  |         break | ||||||
|  |       case TypedValue.COMPLEX_UNIT_PT: | ||||||
|  |         dimension.unit = 'pt' | ||||||
|  |         break | ||||||
|  |       case TypedValue.COMPLEX_UNIT_IN: | ||||||
|  |         dimension.unit = 'in' | ||||||
|  |         break | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return dimension | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readFraction () { | ||||||
|  |     this.debug && console.group('readFraction') | ||||||
|  |  | ||||||
|  |     const fraction = { | ||||||
|  |       value: null, | ||||||
|  |       type: null, | ||||||
|  |       rawType: null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const value = this.readU32() | ||||||
|  |     const type = value & 0xf | ||||||
|  |  | ||||||
|  |     fraction.value = this.convertIntToFloat(value >> 4) | ||||||
|  |     fraction.rawType = type | ||||||
|  |  | ||||||
|  |     switch (type) { | ||||||
|  |       case TypedValue.COMPLEX_UNIT_FRACTION: | ||||||
|  |         fraction.type = '%' | ||||||
|  |         break | ||||||
|  |       case TypedValue.COMPLEX_UNIT_FRACTION_PARENT: | ||||||
|  |         fraction.type = '%p' | ||||||
|  |         break | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return fraction | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readHex24 () { | ||||||
|  |     this.debug && console.group('readHex24') | ||||||
|  |     var val = (this.readU32() & 0xffffff).toString(16) | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return val | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readHex32 () { | ||||||
|  |     this.debug && console.group('readHex32') | ||||||
|  |     var val = this.readU32().toString(16) | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return val | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readTypedValue () { | ||||||
|  |     this.debug && console.group('readTypedValue') | ||||||
|  |  | ||||||
|  |     const typedValue = { | ||||||
|  |       value: null, | ||||||
|  |       type: null, | ||||||
|  |       rawType: null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const start = this.cursor | ||||||
|  |  | ||||||
|  |     let size = this.readU16() | ||||||
|  |     /* const zero = */ this.readU8() | ||||||
|  |     const dataType = this.readU8() | ||||||
|  |  | ||||||
|  |     // Yes, there has been a real world APK where the size is malformed. | ||||||
|  |     if (size === 0) { | ||||||
|  |       size = 8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     typedValue.rawType = dataType | ||||||
|  |  | ||||||
|  |     switch (dataType) { | ||||||
|  |       case TypedValue.TYPE_INT_DEC: | ||||||
|  |         typedValue.value = this.readS32() | ||||||
|  |         typedValue.type = 'int_dec' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_INT_HEX: | ||||||
|  |         typedValue.value = this.readS32() | ||||||
|  |         typedValue.type = 'int_hex' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_STRING: | ||||||
|  |         var ref = this.readS32() | ||||||
|  |         typedValue.value = ref > 0 ? this.strings[ref] : '' | ||||||
|  |         typedValue.type = 'string' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_REFERENCE: | ||||||
|  |         var id = this.readU32() | ||||||
|  |         typedValue.value = `resourceId:0x${id.toString(16)}` | ||||||
|  |         typedValue.type = 'reference' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_INT_BOOLEAN: | ||||||
|  |         typedValue.value = this.readS32() !== 0 | ||||||
|  |         typedValue.type = 'boolean' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_NULL: | ||||||
|  |         this.readU32() | ||||||
|  |         typedValue.value = null | ||||||
|  |         typedValue.type = 'null' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_INT_COLOR_RGB8: | ||||||
|  |         typedValue.value = this.readHex24() | ||||||
|  |         typedValue.type = 'rgb8' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_INT_COLOR_RGB4: | ||||||
|  |         typedValue.value = this.readHex24() | ||||||
|  |         typedValue.type = 'rgb4' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_INT_COLOR_ARGB8: | ||||||
|  |         typedValue.value = this.readHex32() | ||||||
|  |         typedValue.type = 'argb8' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_INT_COLOR_ARGB4: | ||||||
|  |         typedValue.value = this.readHex32() | ||||||
|  |         typedValue.type = 'argb4' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_DIMENSION: | ||||||
|  |         typedValue.value = this.readDimension() | ||||||
|  |         typedValue.type = 'dimension' | ||||||
|  |         break | ||||||
|  |       case TypedValue.TYPE_FRACTION: | ||||||
|  |         typedValue.value = this.readFraction() | ||||||
|  |         typedValue.type = 'fraction' | ||||||
|  |         break | ||||||
|  |       default: { | ||||||
|  |         const type = dataType.toString(16) | ||||||
|  |         console.debug(`Not sure what to do with typed value of type 0x${type}, falling back to reading an uint32.`) | ||||||
|  |         typedValue.value = this.readU32() | ||||||
|  |         typedValue.type = 'unknown' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Ensure we consume the whole value | ||||||
|  |     const end = start + size | ||||||
|  |     if (this.cursor !== end) { | ||||||
|  |       const type = dataType.toString(16) | ||||||
|  |       const diff = end - this.cursor | ||||||
|  |       console.debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed end \ | ||||||
|  | of typed value of type 0x${type}. The typed value started at offset ${start} \ | ||||||
|  | and is supposed to end at offset ${end}. Ignoring the rest of the value.`) | ||||||
|  |       this.cursor = end | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return typedValue | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // https://twitter.com/kawasima/status/427730289201139712 | ||||||
|  |   convertIntToFloat (int) { | ||||||
|  |     const buf = new ArrayBuffer(4) | ||||||
|  |     ;(new Int32Array(buf))[0] = int | ||||||
|  |     return (new Float32Array(buf))[0] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readString (encoding) { | ||||||
|  |     this.debug && console.group('readString', encoding) | ||||||
|  |     switch (encoding) { | ||||||
|  |       case 'utf-8': | ||||||
|  |         var stringLength = this.readLength8(encoding) | ||||||
|  |         this.debug && console.debug('stringLength:', stringLength) | ||||||
|  |         var byteLength = this.readLength8(encoding) | ||||||
|  |         this.debug && console.debug('byteLength:', byteLength) | ||||||
|  |         var value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength)) | ||||||
|  |         this.debug && console.debug('value:', value) | ||||||
|  |         this.debug && console.groupEnd() | ||||||
|  |         return value | ||||||
|  |       case 'ucs2': | ||||||
|  |         stringLength = this.readLength16(encoding) | ||||||
|  |         this.debug && console.debug('stringLength:', stringLength) | ||||||
|  |         byteLength = stringLength * 2 | ||||||
|  |         this.debug && console.debug('byteLength:', byteLength) | ||||||
|  |         value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength)) | ||||||
|  |         this.debug && console.debug('value:', value) | ||||||
|  |         this.debug && console.groupEnd() | ||||||
|  |         return value | ||||||
|  |       default: | ||||||
|  |         throw new Error(`Unsupported encoding '${encoding}'`) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readChunkHeader () { | ||||||
|  |     this.debug && console.group('readChunkHeader') | ||||||
|  |     var header = { | ||||||
|  |       startOffset: this.cursor, | ||||||
|  |       chunkType: this.readU16(), | ||||||
|  |       headerSize: this.readU16(), | ||||||
|  |       chunkSize: this.readU32() | ||||||
|  |     } | ||||||
|  |     this.debug && console.debug('startOffset:', header.startOffset) | ||||||
|  |     this.debug && console.debug('chunkType:', header.chunkType) | ||||||
|  |     this.debug && console.debug('headerSize:', header.headerSize) | ||||||
|  |     this.debug && console.debug('chunkSize:', header.chunkSize) | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return header | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readStringPool (header) { | ||||||
|  |     this.debug && console.group('readStringPool') | ||||||
|  |  | ||||||
|  |     header.stringCount = this.readU32() | ||||||
|  |     this.debug && console.debug('stringCount:', header.stringCount) | ||||||
|  |     header.styleCount = this.readU32() | ||||||
|  |     this.debug && console.debug('styleCount:', header.styleCount) | ||||||
|  |     header.flags = this.readU32() | ||||||
|  |     this.debug && console.debug('flags:', header.flags) | ||||||
|  |     header.stringsStart = this.readU32() | ||||||
|  |     this.debug && console.debug('stringsStart:', header.stringsStart) | ||||||
|  |     header.stylesStart = this.readU32() | ||||||
|  |     this.debug && console.debug('stylesStart:', header.stylesStart) | ||||||
|  |  | ||||||
|  |     if (header.chunkType !== ChunkType.STRING_POOL) { | ||||||
|  |       throw new Error('Invalid string pool header') | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const offsets = [] | ||||||
|  |     for (let i = 0, l = header.stringCount; i < l; ++i) { | ||||||
|  |       this.debug && console.debug('offset:', i) | ||||||
|  |       offsets.push(this.readU32()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const sorted = (header.flags & StringFlags.SORTED) === StringFlags.SORTED | ||||||
|  |     this.debug && console.debug('sorted:', sorted) | ||||||
|  |     const encoding = (header.flags & StringFlags.UTF8) === StringFlags.UTF8 | ||||||
|  |       ? 'utf-8' | ||||||
|  |       : 'ucs2' | ||||||
|  |     this.debug && console.debug('encoding:', encoding) | ||||||
|  |  | ||||||
|  |     const stringsStart = header.startOffset + header.stringsStart | ||||||
|  |     this.cursor = stringsStart | ||||||
|  |     for (let i = 0, l = header.stringCount; i < l; ++i) { | ||||||
|  |       this.debug && console.debug('string:', i) | ||||||
|  |       this.debug && console.debug('offset:', offsets[i]) | ||||||
|  |       this.cursor = stringsStart + offsets[i] | ||||||
|  |       this.strings.push(this.readString(encoding)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Skip styles | ||||||
|  |     this.cursor = header.startOffset + header.chunkSize | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readResourceMap (header) { | ||||||
|  |     this.debug && console.group('readResourceMap') | ||||||
|  |     const count = Math.floor((header.chunkSize - header.headerSize) / 4) | ||||||
|  |     for (let i = 0; i < count; ++i) { | ||||||
|  |       this.resources.push(this.readU32()) | ||||||
|  |     } | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readXmlNamespaceStart (/* header */) { | ||||||
|  |     this.debug && console.group('readXmlNamespaceStart') | ||||||
|  |  | ||||||
|  |     /* const line = */ this.readU32() | ||||||
|  |     /* const commentRef = */ this.readU32() | ||||||
|  |     /* const prefixRef = */ this.readS32() | ||||||
|  |     /* const uriRef = */ this.readS32() | ||||||
|  |  | ||||||
|  |     // We don't currently care about the values, but they could | ||||||
|  |     // be accessed like so: | ||||||
|  |     // | ||||||
|  |     // namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0 | ||||||
|  |     // namespaceURI.uri = this.strings[uriRef] // if uriRef > 0 | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readXmlNamespaceEnd (/* header */) { | ||||||
|  |     this.debug && console.group('readXmlNamespaceEnd') | ||||||
|  |  | ||||||
|  |     /* const line = */ this.readU32() | ||||||
|  |     /* const commentRef = */ this.readU32() | ||||||
|  |     /* const prefixRef = */ this.readS32() | ||||||
|  |     /* const uriRef = */ this.readS32() | ||||||
|  |  | ||||||
|  |     // We don't currently care about the values, but they could | ||||||
|  |     // be accessed like so: | ||||||
|  |     // | ||||||
|  |     // namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0 | ||||||
|  |     // namespaceURI.uri = this.strings[uriRef] // if uriRef > 0 | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readXmlElementStart (/* header */) { | ||||||
|  |     this.debug && console.group('readXmlElementStart') | ||||||
|  |  | ||||||
|  |     const node = { | ||||||
|  |       namespaceURI: null, | ||||||
|  |       nodeType: NodeType.ELEMENT_NODE, | ||||||
|  |       nodeName: null, | ||||||
|  |       attributes: [], | ||||||
|  |       childNodes: [] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* const line = */ this.readU32() | ||||||
|  |     /* const commentRef = */ this.readU32() | ||||||
|  |     const nsRef = this.readS32() | ||||||
|  |     const nameRef = this.readS32() | ||||||
|  |  | ||||||
|  |     if (nsRef > 0) { | ||||||
|  |       node.namespaceURI = this.strings[nsRef] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     node.nodeName = this.strings[nameRef] | ||||||
|  |  | ||||||
|  |     /* const attrStart = */ this.readU16() | ||||||
|  |     /* const attrSize = */ this.readU16() | ||||||
|  |     const attrCount = this.readU16() | ||||||
|  |     /* const idIndex = */ this.readU16() | ||||||
|  |     /* const classIndex = */ this.readU16() | ||||||
|  |     /* const styleIndex = */ this.readU16() | ||||||
|  |  | ||||||
|  |     for (let i = 0; i < attrCount; ++i) { | ||||||
|  |       node.attributes.push(this.readXmlAttribute()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.document) { | ||||||
|  |       this.parent.childNodes.push(node) | ||||||
|  |       this.parent = node | ||||||
|  |     } else { | ||||||
|  |       this.document = (this.parent = node) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.stack.push(node) | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return node | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readXmlAttribute () { | ||||||
|  |     this.debug && console.group('readXmlAttribute') | ||||||
|  |  | ||||||
|  |     const attr = { | ||||||
|  |       namespaceURI: null, | ||||||
|  |       nodeType: NodeType.ATTRIBUTE_NODE, | ||||||
|  |       nodeName: null, | ||||||
|  |       name: null, | ||||||
|  |       value: null, | ||||||
|  |       typedValue: null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const nsRef = this.readS32() | ||||||
|  |     const nameRef = this.readS32() | ||||||
|  |     const valueRef = this.readS32() | ||||||
|  |  | ||||||
|  |     if (nsRef > 0) { | ||||||
|  |       attr.namespaceURI = this.strings[nsRef] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     attr.nodeName = attr.name = this.strings[nameRef] | ||||||
|  |  | ||||||
|  |     if (valueRef > 0) { | ||||||
|  |       // some apk have versionName with special characters | ||||||
|  |       if (attr.name === 'versionName') { | ||||||
|  |         // only keep printable characters | ||||||
|  |         // https://www.ascii-code.com/characters/printable-characters | ||||||
|  |         this.strings[valueRef] = this.strings[valueRef].replace(/[^\x21-\x7E]/g, '') | ||||||
|  |       } | ||||||
|  |       attr.value = this.strings[valueRef] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     attr.typedValue = this.readTypedValue() | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return attr | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readXmlElementEnd (/* header */) { | ||||||
|  |     this.debug && console.group('readXmlCData') | ||||||
|  |  | ||||||
|  |     /* const line = */ this.readU32() | ||||||
|  |     /* const commentRef = */ this.readU32() | ||||||
|  |     /* const nsRef = */ this.readS32() | ||||||
|  |     /* const nameRef = */ this.readS32() | ||||||
|  |  | ||||||
|  |     this.stack.pop() | ||||||
|  |     this.parent = this.stack[this.stack.length - 1] | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readXmlCData (/* header */) { | ||||||
|  |     this.debug && console.group('readXmlCData') | ||||||
|  |  | ||||||
|  |     const cdata = { | ||||||
|  |       namespaceURI: null, | ||||||
|  |       nodeType: NodeType.CDATA_SECTION_NODE, | ||||||
|  |       nodeName: '#cdata', | ||||||
|  |       data: null, | ||||||
|  |       typedValue: null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* const line = */ this.readU32() | ||||||
|  |     /* const commentRef = */ this.readU32() | ||||||
|  |     const dataRef = this.readS32() | ||||||
|  |  | ||||||
|  |     if (dataRef > 0) { | ||||||
|  |       cdata.data = this.strings[dataRef] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     cdata.typedValue = this.readTypedValue() | ||||||
|  |  | ||||||
|  |     this.parent.childNodes.push(cdata) | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return cdata | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   readNull (header) { | ||||||
|  |     this.debug && console.group('readNull') | ||||||
|  |     this.cursor += header.chunkSize - header.headerSize | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   parse () { | ||||||
|  |     this.debug && console.group('BinaryXmlParser.parse') | ||||||
|  |  | ||||||
|  |     const xmlHeader = this.readChunkHeader() | ||||||
|  |     if (xmlHeader.chunkType !== ChunkType.XML) { | ||||||
|  |       throw new Error('Invalid XML header') | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     while (this.cursor < this.buffer.length) { | ||||||
|  |       this.debug && console.group('chunk') | ||||||
|  |       const start = this.cursor | ||||||
|  |       const header = this.readChunkHeader() | ||||||
|  |       switch (header.chunkType) { | ||||||
|  |         case ChunkType.STRING_POOL: | ||||||
|  |           this.readStringPool(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.XML_RESOURCE_MAP: | ||||||
|  |           this.readResourceMap(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.XML_START_NAMESPACE: | ||||||
|  |           this.readXmlNamespaceStart(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.XML_END_NAMESPACE: | ||||||
|  |           this.readXmlNamespaceEnd(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.XML_START_ELEMENT: | ||||||
|  |           this.readXmlElementStart(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.XML_END_ELEMENT: | ||||||
|  |           this.readXmlElementEnd(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.XML_CDATA: | ||||||
|  |           this.readXmlCData(header) | ||||||
|  |           break | ||||||
|  |         case ChunkType.NULL: | ||||||
|  |           this.readNull(header) | ||||||
|  |           break | ||||||
|  |         default: | ||||||
|  |           throw new Error(`Unsupported chunk type '${header.chunkType}'`) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Ensure we consume the whole chunk | ||||||
|  |       const end = start + header.chunkSize | ||||||
|  |       if (this.cursor !== end) { | ||||||
|  |         const diff = end - this.cursor | ||||||
|  |         const type = header.chunkType.toString(16) | ||||||
|  |         console.debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed \ | ||||||
|  | end of chunk of type 0x${type}. The chunk started at offset ${start} and is \ | ||||||
|  | supposed to end at offset ${end}. Ignoring the rest of the chunk.`) | ||||||
|  |         this.cursor = end | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       this.debug && console.groupEnd() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.debug && console.groupEnd() | ||||||
|  |  | ||||||
|  |     return this.document | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = BinaryXmlParser | ||||||
							
								
								
									
										216
									
								
								src/utils/app-info-parser/xml-parser/manifest.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/utils/app-info-parser/xml-parser/manifest.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | |||||||
|  | // From https://github.com/openstf/adbkit-apkreader | ||||||
|  | const BinaryXmlParser = require('./binary') | ||||||
|  |  | ||||||
|  | const INTENT_MAIN = 'android.intent.action.MAIN' | ||||||
|  | const CATEGORY_LAUNCHER = 'android.intent.category.LAUNCHER' | ||||||
|  |  | ||||||
|  | class ManifestParser { | ||||||
|  |   constructor (buffer, options = {}) { | ||||||
|  |     this.buffer = buffer | ||||||
|  |     this.xmlParser = new BinaryXmlParser(this.buffer, options) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   collapseAttributes (element) { | ||||||
|  |     const collapsed = Object.create(null) | ||||||
|  |     for (let attr of Array.from(element.attributes)) { | ||||||
|  |       collapsed[attr.name] = attr.typedValue.value | ||||||
|  |     } | ||||||
|  |     return collapsed | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   parseIntents (element, target) { | ||||||
|  |     target.intentFilters = [] | ||||||
|  |     target.metaData = [] | ||||||
|  |  | ||||||
|  |     return element.childNodes.forEach(element => { | ||||||
|  |       switch (element.nodeName) { | ||||||
|  |         case 'intent-filter': { | ||||||
|  |           const intentFilter = this.collapseAttributes(element) | ||||||
|  |  | ||||||
|  |           intentFilter.actions = [] | ||||||
|  |           intentFilter.categories = [] | ||||||
|  |           intentFilter.data = [] | ||||||
|  |  | ||||||
|  |           element.childNodes.forEach(element => { | ||||||
|  |             switch (element.nodeName) { | ||||||
|  |               case 'action': | ||||||
|  |                 intentFilter.actions.push(this.collapseAttributes(element)) | ||||||
|  |                 break | ||||||
|  |               case 'category': | ||||||
|  |                 intentFilter.categories.push(this.collapseAttributes(element)) | ||||||
|  |                 break | ||||||
|  |               case 'data': | ||||||
|  |                 intentFilter.data.push(this.collapseAttributes(element)) | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |  | ||||||
|  |           target.intentFilters.push(intentFilter) | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |         case 'meta-data': | ||||||
|  |           target.metaData.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   parseApplication (element) { | ||||||
|  |     const app = this.collapseAttributes(element) | ||||||
|  |  | ||||||
|  |     app.activities = [] | ||||||
|  |     app.activityAliases = [] | ||||||
|  |     app.launcherActivities = [] | ||||||
|  |     app.services = [] | ||||||
|  |     app.receivers = [] | ||||||
|  |     app.providers = [] | ||||||
|  |     app.usesLibraries = [] | ||||||
|  |     app.metaData = [] | ||||||
|  |  | ||||||
|  |     element.childNodes.forEach(element => { | ||||||
|  |       switch (element.nodeName) { | ||||||
|  |         case 'activity': { | ||||||
|  |           const activity = this.collapseAttributes(element) | ||||||
|  |           this.parseIntents(element, activity) | ||||||
|  |           app.activities.push(activity) | ||||||
|  |           if (this.isLauncherActivity(activity)) { | ||||||
|  |             app.launcherActivities.push(activity) | ||||||
|  |           } | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |         case 'activity-alias': { | ||||||
|  |           const activityAlias = this.collapseAttributes(element) | ||||||
|  |           this.parseIntents(element, activityAlias) | ||||||
|  |           app.activityAliases.push(activityAlias) | ||||||
|  |           if (this.isLauncherActivity(activityAlias)) { | ||||||
|  |             app.launcherActivities.push(activityAlias) | ||||||
|  |           } | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |         case 'service': { | ||||||
|  |           const service = this.collapseAttributes(element) | ||||||
|  |           this.parseIntents(element, service) | ||||||
|  |           app.services.push(service) | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |         case 'receiver': { | ||||||
|  |           const receiver = this.collapseAttributes(element) | ||||||
|  |           this.parseIntents(element, receiver) | ||||||
|  |           app.receivers.push(receiver) | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |         case 'provider': { | ||||||
|  |           const provider = this.collapseAttributes(element) | ||||||
|  |  | ||||||
|  |           provider.grantUriPermissions = [] | ||||||
|  |           provider.metaData = [] | ||||||
|  |           provider.pathPermissions = [] | ||||||
|  |  | ||||||
|  |           element.childNodes.forEach(element => { | ||||||
|  |             switch (element.nodeName) { | ||||||
|  |               case 'grant-uri-permission': | ||||||
|  |                 provider.grantUriPermissions.push(this.collapseAttributes(element)) | ||||||
|  |                 break | ||||||
|  |               case 'meta-data': | ||||||
|  |                 provider.metaData.push(this.collapseAttributes(element)) | ||||||
|  |                 break | ||||||
|  |               case 'path-permission': | ||||||
|  |                 provider.pathPermissions.push(this.collapseAttributes(element)) | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |  | ||||||
|  |           app.providers.push(provider) | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |         case 'uses-library': | ||||||
|  |           app.usesLibraries.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'meta-data': | ||||||
|  |           app.metaData.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     return app | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   isLauncherActivity (activity) { | ||||||
|  |     return activity.intentFilters.some(function (filter) { | ||||||
|  |       const hasMain = filter.actions.some(action => action.name === INTENT_MAIN) | ||||||
|  |       if (!hasMain) { | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|  |       return filter.categories.some(category => category.name === CATEGORY_LAUNCHER) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   parse () { | ||||||
|  |     const document = this.xmlParser.parse() | ||||||
|  |     const manifest = this.collapseAttributes(document) | ||||||
|  |  | ||||||
|  |     manifest.usesPermissions = [] | ||||||
|  |     manifest.usesPermissionsSDK23 = [] | ||||||
|  |     manifest.permissions = [] | ||||||
|  |     manifest.permissionTrees = [] | ||||||
|  |     manifest.permissionGroups = [] | ||||||
|  |     manifest.instrumentation = null | ||||||
|  |     manifest.usesSdk = null | ||||||
|  |     manifest.usesConfiguration = null | ||||||
|  |     manifest.usesFeatures = [] | ||||||
|  |     manifest.supportsScreens = null | ||||||
|  |     manifest.compatibleScreens = [] | ||||||
|  |     manifest.supportsGlTextures = [] | ||||||
|  |     manifest.application = Object.create(null) | ||||||
|  |  | ||||||
|  |     document.childNodes.forEach(element => { | ||||||
|  |       switch (element.nodeName) { | ||||||
|  |         case 'uses-permission': | ||||||
|  |           manifest.usesPermissions.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'uses-permission-sdk-23': | ||||||
|  |           manifest.usesPermissionsSDK23.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'permission': | ||||||
|  |           manifest.permissions.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'permission-tree': | ||||||
|  |           manifest.permissionTrees.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'permission-group': | ||||||
|  |           manifest.permissionGroups.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'instrumentation': | ||||||
|  |           manifest.instrumentation = this.collapseAttributes(element) | ||||||
|  |           break | ||||||
|  |         case 'uses-sdk': | ||||||
|  |           manifest.usesSdk = this.collapseAttributes(element) | ||||||
|  |           break | ||||||
|  |         case 'uses-configuration': | ||||||
|  |           manifest.usesConfiguration = this.collapseAttributes(element) | ||||||
|  |           break | ||||||
|  |         case 'uses-feature': | ||||||
|  |           manifest.usesFeatures.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'supports-screens': | ||||||
|  |           manifest.supportsScreens = this.collapseAttributes(element) | ||||||
|  |           break | ||||||
|  |         case 'compatible-screens': | ||||||
|  |           element.childNodes.forEach(screen => { | ||||||
|  |             return manifest.compatibleScreens.push(this.collapseAttributes(screen)) | ||||||
|  |           }) | ||||||
|  |           break | ||||||
|  |         case 'supports-gl-texture': | ||||||
|  |           manifest.supportsGlTextures.push(this.collapseAttributes(element)) | ||||||
|  |           break | ||||||
|  |         case 'application': | ||||||
|  |           manifest.application = this.parseApplication(element) | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     return manifest | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = ManifestParser | ||||||
							
								
								
									
										48
									
								
								src/utils/app-info-parser/zip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/utils/app-info-parser/zip.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | const Unzip = require('isomorphic-unzip') | ||||||
|  | const { isBrowser, decodeNullUnicode } = require('./utils') | ||||||
|  |  | ||||||
|  | class Zip { | ||||||
|  |   constructor (file) { | ||||||
|  |     if (isBrowser()) { | ||||||
|  |       if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) { | ||||||
|  |         throw new Error('Param error: [file] must be an instance of Blob or File in browser.') | ||||||
|  |       } | ||||||
|  |       this.file = file | ||||||
|  |     } else { | ||||||
|  |       if (typeof file !== 'string') { | ||||||
|  |         throw new Error('Param error: [file] must be file path in Node.') | ||||||
|  |       } | ||||||
|  |       this.file = require('path').resolve(file) | ||||||
|  |     } | ||||||
|  |     this.unzip = new Unzip(this.file) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * get entries by regexps, the return format is: { <filename>: <Buffer|Blob> } | ||||||
|  |    * @param {Array} regexps // regexps for matching files | ||||||
|  |    * @param {String} type // return type, can be buffer or blob, default buffer | ||||||
|  |    */ | ||||||
|  |   getEntries (regexps, type = 'buffer') { | ||||||
|  |     regexps = regexps.map(regex => decodeNullUnicode(regex)) | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       this.unzip.getBuffer(regexps, { type }, (err, buffers) => { | ||||||
|  |         err ? reject(err) : resolve(buffers) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * get entry by regex, return an instance of Buffer or Blob | ||||||
|  |    * @param {Regex} regex // regex for matching file | ||||||
|  |    * @param {String} type // return type, can be buffer or blob, default buffer | ||||||
|  |    */ | ||||||
|  |   getEntry (regex, type = 'buffer') { | ||||||
|  |     regex = decodeNullUnicode(regex) | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       this.unzip.getBuffer([regex], { type }, (err, buffers) => { | ||||||
|  |         err ? reject(err) : resolve(buffers[regex]) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Zip | ||||||
| @@ -2,7 +2,7 @@ import fs from 'fs-extra'; | |||||||
| import os from 'os'; | import os from 'os'; | ||||||
| import path from 'path'; | import path from 'path'; | ||||||
| import pkg from '../../package.json'; | import pkg from '../../package.json'; | ||||||
| import AppInfoParser from 'app-info-parser'; | import AppInfoParser from './app-info-parser'; | ||||||
|  |  | ||||||
| import read from 'read'; | import read from 'read'; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 sunnylqm
					sunnylqm