New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

node-apk-parser

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-apk-parser - npm Package Compare versions

Comparing version
0.1.8
to
0.2.0
+1
-13
index.js
(function() {
var Path;
Path = require('path');
module.exports = (function() {
switch (Path.extname(__filename)) {
case '.coffee':
return require('./src/apkreader');
default:
return require('./lib/apkreader');
}
})();
module.exports = return require('./lib/apkreader');
}).call(this);
+23
-17
{
"name": "node-apk-parser",
"version": "0.1.8",
"version": "0.2.0",
"description": "Extracts information from APK files.",

@@ -21,3 +21,3 @@ "keywords": [

},
"main": "./index",
"main": "./index.js",
"repository": {

@@ -28,5 +28,2 @@ "type": "git",

"scripts": {
"postpublish": "gulp postpublish --require coffee-script/register",
"prepublish": "gulp prepublish --require coffee-script/register",
"test": "gulp test --require coffee-script/register"
},

@@ -40,14 +37,23 @@ "dependencies": {

},
"devDependencies": {
"coffee-script": "~1.7.1",
"sinon-chai": "~2.5.0",
"chai": "~1.9.1",
"sinon": "~1.9.0",
"mocha": "~1.18.2",
"gulp-mocha": "~0.4.1",
"gulp-clean": "~0.2.4",
"gulp-coffee": "~1.4.1",
"gulp": "~3.5.6",
"gulp-jsonlint": "0.0.3"
}
"homepage": "https://github.com/arthur-zhang/node-apk-parser",
"_id": "node-apk-parser@0.1.8",
"_shasum": "31a5c125df731cbde1c0d7d812857751516cefb2",
"_from": "node-apk-parser@0.1.8",
"_npmVersion": "1.4.28",
"_npmUser": {
"name": "zhangya_no1",
"email": "zhangya_no1@qq.com"
},
"maintainers": [
{
"name": "zhangya_no1",
"email": "zhangya_no1@qq.com"
}
],
"dist": {
"shasum": "31a5c125df731cbde1c0d7d812857751516cefb2",
"tarball": "http://registry.npmjs.org/node-apk-parser/-/node-apk-parser-0.1.8.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/node-apk-parser/-/node-apk-parser-0.1.8.tgz"
}
gulp = require 'gulp'
clean = require 'gulp-clean'
coffee = require 'gulp-coffee'
mocha = require 'gulp-mocha'
jsonlint = require 'gulp-jsonlint'
gulp.task 'clean', ->
gulp.src ['lib', 'index.js'], read: false
.pipe clean()
gulp.task 'jsonlint', ->
gulp.src '*.json'
.pipe jsonlint()
.pipe jsonlint.reporter()
gulp.task 'lint', ['jsonlint']
gulp.task 'compile-src', ->
gulp.src 'src/**/*.coffee'
.pipe coffee()
.pipe gulp.dest 'lib'
gulp.task 'compile-main', ->
gulp.src 'index.coffee'
.pipe coffee()
.pipe gulp.dest '.'
gulp.task 'compile', ['compile-src', 'compile-main']
gulp.task 'mocha', ->
gulp.src 'test/**/*.coffee', read: false
.pipe mocha
reporter: 'spec'
gulp.task 'test', ['lint', 'mocha']
gulp.task 'prepublish', ['test', 'compile']
gulp.task 'postpublish', ['clean']
gulp.task 'default', ['test']
Path = require 'path'
module.exports = switch Path.extname __filename
when '.coffee' then require './src/apkreader'
else require './lib/apkreader'
Zip = require 'adm-zip'
ManifestParser = require './apkreader/parser/manifest'
BinaryXmlParser = require './apkreader/parser/binaryxml'
class ApkReader
MANIFEST = 'AndroidManifest.xml'
@readFile: (apk) ->
new ApkReader apk
constructor: (@apk) ->
try
@zip = new Zip @apk
catch err
if typeof err is 'string'
throw new Error err
else
throw err
readManifestSync: ->
if manifest = @zip.getEntry MANIFEST
new ManifestParser(manifest.getData()).parse()
else
throw new Error "APK does not contain '#{MANIFEST}'"
readXmlSync: (path) ->
if file = @zip.getEntry path
new BinaryXmlParser(file.getData()).parse()
else
throw new Error "APK does not contain '#{path}'"
module.exports = ApkReader
debug = require('debug')('adb:apkreader:parser:binaryxml')
# Heavily inspired by https://github.com/xiaxiaocao/apk-parser
class BinaryXmlParser
NodeType =
ELEMENT_NODE: 1
ATTRIBUTE_NODE: 2
CDATA_SECTION_NODE: 4
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
StringFlags =
SORTED: 1 << 0
UTF8: 1 << 8
# Taken from android.util.TypedValue
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
constructor: (@buffer) ->
@cursor = 0
@strings = []
@resources = []
@document = null
@parent = null
@stack = []
readU8: ->
val = @buffer[@cursor]
@cursor += 1
return val
readU16: ->
val = @buffer.readUInt16LE @cursor
@cursor += 2
return val
readS32: ->
val = @buffer.readInt32LE @cursor
@cursor += 4
return val
readU32: ->
val = @buffer.readUInt32LE @cursor
@cursor += 4
return val
readLength8: ->
len = this.readU8()
if len & 0x80
len = (len & 0x7f) << 7
len += this.readU8()
return len
readLength16: ->
len = this.readU16()
if len & 0x8000
len = (len & 0x7fff) << 15
len += this.readU16()
return len
readDimension: ->
dimension =
value: null
unit: null
rawUnit: null
value = this.readU32()
unit = dimension.value & 0xff
dimension.value = value >> 8
dimension.rawUnit = unit
switch unit
when TypedValue.COMPLEX_UNIT_MM
dimension.unit = 'mm'
when TypedValue.COMPLEX_UNIT_PX
dimension.unit = 'px'
when TypedValue.COMPLEX_UNIT_DIP
dimension.unit = 'dp'
when TypedValue.COMPLEX_UNIT_SP
dimension.unit = 'sp'
when TypedValue.COMPLEX_UNIT_PT
dimension.unit = 'pt'
when TypedValue.COMPLEX_UNIT_IN
dimension.unit = 'in'
return dimension
readFraction: ->
fraction =
value: null
type: null
rawType: null
value = this.readU32()
type = value & 0xf
fraction.value = this.convertIntToFloat value >> 4
fraction.rawType = type
switch type
when TypedValue.COMPLEX_UNIT_FRACTION
fraction.type = '%'
when TypedValue.COMPLEX_UNIT_FRACTION_PARENT
fraction.type = '%p'
return fraction
readHex24: ->
(this.readU32() & 0xffffff).toString 16
readHex32: ->
this.readU32().toString 16
readTypedValue: ->
typedValue =
value: null
type: null
rawType: null
start = @cursor
size = this.readU16()
zero = this.readU8()
dataType = this.readU8()
typedValue.rawType = dataType
switch dataType
when TypedValue.TYPE_INT_DEC
typedValue.value = this.readS32()
typedValue.type = 'int_dec'
when TypedValue.TYPE_INT_HEX
typedValue.value = this.readS32()
typedValue.type = 'int_hex'
when TypedValue.TYPE_STRING
ref = this.readS32()
typedValue.value = if ref > 0 then @strings[ref] else ''
typedValue.type = 'string'
when TypedValue.TYPE_REFERENCE
id = this.readU32()
typedValue.value = "resourceId:0x#{id.toString 16}"
typedValue.type = 'reference'
when TypedValue.TYPE_INT_BOOLEAN
typedValue.value = this.readS32() isnt 0
typedValue.type = 'boolean'
when TypedValue.TYPE_NULL
this.readU32()
typedValue.value = null
typedValue.type = 'null'
when TypedValue.TYPE_INT_COLOR_RGB8
typedValue.value = this.readHex24()
typedValue.type = 'rgb8'
when TypedValue.TYPE_INT_COLOR_RGB4
typedValue.value = this.readHex24()
typedValue.type = 'rgb4'
when TypedValue.TYPE_INT_COLOR_ARGB8
typedValue.value = this.readHex32()
typedValue.type = 'argb8'
when TypedValue.TYPE_INT_COLOR_ARGB4
typedValue.value = this.readHex32()
typedValue.type = 'argb4'
when TypedValue.TYPE_DIMENSION
typedValue.value = this.readDimension()
typedValue.type = 'dimension'
when TypedValue.TYPE_FRACTION
typedValue.value = this.readFraction()
typedValue.type = 'fraction'
else
type = dataType.toString 16
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
end = start + size
if @cursor isnt end
type = dataType.toString 16
diff = end - @cursor
debug "Cursor is off by #{diff} bytes at #{@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."
@cursor = end
return typedValue
convertIntToFloat: (int) ->
buf = new ArrayBuffer 4
new Int32Array(buf)[0] = buf
return new Float32Array(buf)[0]
readString: (encoding) ->
switch encoding
when 'utf-8'
stringLength = this.readLength8 encoding
byteLength = this.readLength8 encoding
value = @buffer.toString encoding, @cursor, @cursor += byteLength
this.readU16() # Trailing zero
return value
when 'ucs2'
stringLength = this.readLength16 encoding
byteLength = stringLength * 2
value = @buffer.toString encoding, @cursor, @cursor += byteLength
this.readU16() # Trailing zero
return value
else
throw new Error "Unsupported encoding '#{encoding}'"
readChunkHeader: ->
chunkType: this.readU16()
headerSize: this.readU16()
chunkSize: this.readU32()
readStringPool: (header) ->
header.stringCount = this.readU32()
header.styleCount = this.readU32()
header.flags = this.readU32()
header.stringsStart = this.readU32()
header.stylesStart = this.readU32()
if header.chunkType isnt ChunkType.STRING_POOL
throw new Error 'Invalid string pool header'
anchor = @cursor
offsets = []
offsets.push this.readU32() for [0...header.stringCount]
#sorted = stringPoolHeader.flags & StringFlags.SORTED
encoding =
if header.flags & StringFlags.UTF8
'utf-8'
else
'ucs2'
@cursor = anchor + header.stringsStart - header.headerSize
@strings.push this.readString encoding for [0...header.stringCount]
# Skip styles
@cursor = anchor + header.chunkSize - header.headerSize
return null
readResourceMap: (header) ->
count = Math.floor (header.chunkSize - header.headerSize) / 4
@resources.push this.readU32() for [0...count]
return null
readXmlNamespaceStart: (header) ->
line = this.readU32()
commentRef = this.readU32()
prefixRef = this.readS32()
uriRef = this.readS32()
# We don't currently care about the values, but they could
# be accessed like so:
#
# @namespaceURI.prefix = @strings[prefixRef] if prefixRef > 0
# @namespaceURI.uri = @strings[uriRef] if uriRef > 0
return null
readXmlNamespaceEnd: (header) ->
line = this.readU32()
commentRef = this.readU32()
prefixRef = this.readS32()
uriRef = this.readS32()
# We don't currently care about the values, but they could
# be accessed like so:
#
# @namespaceURI.prefix = @strings[prefixRef] if prefixRef > 0
# @namespaceURI.uri = @strings[uriRef] if uriRef > 0
return null
readXmlElementStart: (header) ->
node =
namespaceURI: null
nodeType: NodeType.ELEMENT_NODE
nodeName: null
attributes: []
childNodes: []
line = this.readU32()
commentRef = this.readU32()
nsRef = this.readS32()
nameRef = this.readS32()
node.namespaceURI = @strings[nsRef] if nsRef > 0
node.nodeName = @strings[nameRef]
attrStart = this.readU16()
attrSize = this.readU16()
attrCount = this.readU16()
idIndex = this.readU16()
classIndex = this.readU16()
styleIndex = this.readU16()
node.attributes.push this.readXmlAttribute() for [0...attrCount]
if @document
@parent.childNodes.push node
@parent = node
else
@document = @parent = node
@stack.push node
return node
readXmlAttribute: ->
attr =
namespaceURI: null
nodeType: NodeType.ATTRIBUTE_NODE
nodeName: null
name: null
value: null
typedValue: null
nsRef = this.readS32()
nameRef = this.readS32()
valueRef = this.readS32()
attr.namespaceURI = @strings[nsRef] if nsRef > 0
attr.nodeName = attr.name = @strings[nameRef]
attr.value = @strings[valueRef] if valueRef > 0
attr.typedValue = this.readTypedValue()
return attr
readXmlElementEnd: (header) ->
line = this.readU32()
commentRef = this.readU32()
nsRef = this.readS32()
nameRef = this.readS32()
@stack.pop()
@parent = @stack[@stack.length - 1]
return null
readXmlCData: (header) ->
cdata =
namespaceURI: null
nodeType: NodeType.CDATA_SECTION_NODE
nodeName: '#cdata'
data: null
typedValue: null
line = this.readU32()
commentRef = this.readU32()
dataRef = this.readS32()
cdata.data = @strings[dataRef] if dataRef > 0
cdata.typedValue = this.readTypedValue()
@parent.childNodes.push cdata
return cdata
readNull: (header) ->
@cursor += header.chunkSize - header.headerSize
return null
parse: ->
xmlHeader = this.readChunkHeader()
if xmlHeader.chunkType isnt ChunkType.XML
throw new Error 'Invalid XML header'
this.readStringPool this.readChunkHeader()
resMapHeader = this.readChunkHeader()
if resMapHeader.chunkType is ChunkType.XML_RESOURCE_MAP
this.readResourceMap resMapHeader
this.readXmlNamespaceStart this.readChunkHeader()
else
this.readXmlNamespaceStart resMapHeader
while @cursor < @buffer.length
start = @cursor
header = this.readChunkHeader()
switch header.chunkType
when ChunkType.XML_START_NAMESPACE
this.readXmlNamespaceStart header
when ChunkType.XML_END_NAMESPACE
this.readXmlNamespaceEnd header
when ChunkType.XML_START_ELEMENT
this.readXmlElementStart header
when ChunkType.XML_END_ELEMENT
this.readXmlElementEnd header
when ChunkType.XML_CDATA
this.readXmlCData header
when ChunkType.NULL
this.readNull header
else
throw new Error "Unsupported chunk type '#{header.chunkType}'"
# Ensure we consume the whole chunk
end = start + header.chunkSize
if @cursor isnt end
diff = end - @cursor
type = header.chunkType.toString 16
debug "Cursor is off by #{diff} bytes at #{@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."
@cursor = end
return @document
module.exports = BinaryXmlParser
BinaryXmlParser = require './binaryxml'
class ManifestParser
NS_ANDROID = 'http://schemas.android.com/apk/res/android'
INTENT_MAIN = 'android.intent.action.MAIN'
CATEGORY_LAUNCHER = 'android.intent.category.LAUNCHER'
constructor: (@buffer) ->
@xmlParser = new BinaryXmlParser @buffer
collapseAttributes: (element) ->
collapsed = Object.create null
for attr in element.attributes
collapsed[attr.name] = attr.typedValue.value
return collapsed
parseIntents: (element, target) ->
target.intentFilters = []
target.metaData = []
element.childNodes.forEach (element) =>
switch element.nodeName
when 'intent-filter'
intentFilter = this.collapseAttributes element
intentFilter.actions = []
intentFilter.categories = []
intentFilter.data = []
element.childNodes.forEach (element) =>
switch element.nodeName
when 'action'
intentFilter.actions.push this.collapseAttributes element
when 'category'
intentFilter.categories.push this.collapseAttributes element
when 'data'
intentFilter.data.push this.collapseAttributes element
target.intentFilters.push intentFilter
when 'meta-data'
target.metaData.push this.collapseAttributes element
parseApplication: (element) ->
app = this.collapseAttributes element
app.activities = []
app.activityAliases = []
app.launcherActivities = []
app.services = []
app.receivers = []
app.providers = []
app.usesLibraries = []
app.metaDatas = []
element.childNodes.forEach (element) =>
switch element.nodeName
when 'activity'
activity = this.collapseAttributes element
this.parseIntents element, activity
app.activities.push activity
if this.isLauncherActivity activity
app.launcherActivities.push activity
when 'activity-alias'
activityAlias = this.collapseAttributes element
this.parseIntents element, activityAlias
app.activityAliases.push activityAlias
if this.isLauncherActivity activityAlias
app.launcherActivities.push activityAlias
when 'service'
service = this.collapseAttributes element
this.parseIntents element, service
app.services.push service
when 'receiver'
receiver = this.collapseAttributes element
this.parseIntents element, receiver
app.receivers.push receiver
when 'provider'
provider = this.collapseAttributes element
provider.grantUriPermissions = []
provider.metaData = []
provider.pathPermissions = []
element.childNodes.forEach (element) =>
switch element.nodeName
when 'grant-uri-permission'
provider.grantUriPermissions.push \
this.collapseAttributes element
when 'meta-data'
provider.metaData.push this.collapseAttributes element
when 'path-permission'
provider.pathPermissions.push \
this.collapseAttributes element
app.providers.push provider
when 'uses-library'
app.usesLibraries.push this.collapseAttributes element
when 'meta-data'
app.metaDatas.push this.collapseAttributes element
return app
isLauncherActivity: (activity) ->
activity.intentFilters.some (filter) ->
hasMain = filter.actions.some (action) ->
action.name is INTENT_MAIN
return false unless hasMain
filter.categories.some (category) ->
category.name is CATEGORY_LAUNCHER
parse: ->
document = @xmlParser.parse()
manifest = this.collapseAttributes document
manifest.usesPermissions = []
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
when 'uses-permission'
manifest.usesPermissions.push this.collapseAttributes element
when 'permission'
manifest.permissions.push this.collapseAttributes element
when 'permission-tree'
manifest.permissionTrees.push this.collapseAttributes element
when 'permission-group'
manifest.permissionGroups.push this.collapseAttributes element
when 'instrumentation'
manifest.instrumentation = this.collapseAttributes element
when 'uses-sdk'
manifest.usesSdk = this.collapseAttributes element
when 'uses-configuration'
manifest.usesConfiguration = this.collapseAttributes element
when 'uses-feature'
manifest.usesFeatures.push this.collapseAttributes element
when 'supports-screens'
manifest.supportsScreens = this.collapseAttributes element
when 'compatible-screens'
element.childNodes.forEach (screen) =>
manifest.compatibleScreens.push this.collapseAttributes screen
when 'supports-gl-texture'
manifest.supportsGlTextures.push this.collapseAttributes element
when 'application'
manifest.application = this.parseApplication element
return manifest
module.exports = ManifestParser

Sorry, the diff of this file is not supported yet