LibScout-2.3.2/ 0000775 0000000 0000000 00000000000 13424313624 0013244 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/.gitignore 0000664 0000000 0000000 00000000013 13424313624 0015226 0 ustar 00root root 0000000 0000000 bin
build
LibScout-2.3.2/LICENSE 0000664 0000000 0000000 00000026136 13424313624 0014261 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
LibScout-2.3.2/README.md 0000664 0000000 0000000 00000027603 13424313624 0014533 0 ustar 00root root 0000000 0000000 # LibScout
LibScout is a light-weight and effective static analysis tool to detect third-party libraries in Android/Java apps. The detection is resilient against common bytecode obfuscation techniques such as identifier renaming or code-based obfuscations such as reflection-based API hiding or control-flow randomization. Further, LibScout is capable of pinpointing exact library versions including versions that contain severe bugs or security issues.
LibScout requires the original library SDKs (compiled .jar/.aar files) to extract library profiles that can be used for detection on Android apps. Pre-generated library profiles are hosted at the repository [LibScout-Profiles](https://github.com/reddr/LibScout-Profiles).
Unique detection features:
* Library detection resilient against many kinds of bytecode obfuscation (e.g. obfuscations by ProGuard)
* Capability of pinpointing the exact library version (in some cases to a set of 2-3 candidate versions)
* Capability of handling dead-code elimination, by computing a similarity score against baseline SDKs
* Library API usage analysis upon detection
Over time LibScout has been extended to perform additional analyses both on library SDKs and detected libraries in apps:
* API compatibility analysis across library versions and lib developers adherence to semantic versioning.
* Library updatability analysis to infer if and to which extent detected libraries in apps can be updated without code changes based on their API usage.
In addition, there is an Android Studio extension [up2dep](https://github.com/ngcuongst/up2dep) that integrates the API compatibility information into the IDE to help developers keeping their dependencies up-to-date (and more).
## Library History Scraper (./scripts)
The scripts directory contains a [library-scraper](scripts/library-scraper.py) python script to automatically download original library SDKs including complete version histories from *Maven Central*, *JCenter* and *custom mvn repositories*. The original library SDKs can be used to generate profiles and to conduct library API compatibility analyses (see modules below). Use the [library-profile-generator](scripts/library-profile-generator.sh) script to conveniently generate profiles at scale.
The scrapers need to be configured with a json config that includes metadata of the libraries to be fetched (name, repo, groupid, artefactid). The *scripts/library-specs* directory contains config files to retrieve over 100 libraries from maven central and a config to download Amazon libraries and Android libraries from Google's maven repository (350 libraries, including support, gms, ktx, jetpack, ..).
## Detecting (vulnerable) library versions
Ready-to-use library profiles and library meta-data can be found in the repository [LibScout-Profiles](https://github.com/reddr/LibScout-Profiles).
LibScout has builtin functionality to report library versions with the following security vulnerabilities.
The pre-generated profiles for vulnerable versions are tagged with [SECURITY], patches with [SECURITY-FIX].
This information is encoded in the library.xml files that have been used to generate the profiles.
We try to update the list/profiles whenever we encounter new security issues. If you can share information, please let us know.
| Library | Version(s) | Fix Version | Vulnerability | Link |
| ---------- | ---------------:|--------------:|--------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Airpush | < 8.1 | > 8.1 | Unsanitized default WebView settings | [Link](https://support.google.com/faqs/answer/6376737) |
| Apache CC | 3.2.1 / 4.0 | 3.2.2 / 4.1 | Deserialization vulnerability | [Link](http://www.kb.cert.org/vuls/id/576313) |
| Dropbox | 1.5.4 - 1.6.1 | 1.6.2 | DroppedIn vulnerability | [Link](https://blogs.dropbox.com/developers/2015/03/security-bug-resolved-in-the-dropbox-sdks-for-android) |
| Facebook | 3.15 | 3.16 | Account hijacking vulnerability | [Link](http://thehackernews.com/2014/07/facebook-sdk-vulnerability-puts.html) |
| MoPub | < 4.4.0 | 4.4.0 | Unsanitized default WebView settings | [Link](https://support.google.com/faqs/answer/6345928) |
| OkHttp | 2.1 - 2.7.4 3.0.0- 3.1.2 | 2.7.5 3.2.0 | Certificate pinning bypass | [Link](https://medium.com/square-corner-blog/vulnerability-in-okhttps-certificate-pinner-2a7326ad073b) |
| Plexus Archiver | < 3.6.0 | 3.6.0 | Zip Slip vulnerability | [Link](https://github.com/snyk/zip-slip-vulnerability)
| SuperSonic | < 6.3.5 | 6.3.5 | Unsafe functionality exposure via JS | [Link](https://support.google.com/faqs/answer/7126517) |
| Vungle | < 3.3.0 | 3.3.0 | MitM attack vulnerability | [Link](https://support.google.com/faqs/answer/6313713) |
| ZeroTurnaround | < 1.13 | 1.13 | Zip Slip vulnerability | [Link](https://github.com/snyk/zip-slip-vulnerability)
### Identified Issues
On our last scan of free apps on Google Play (05/25/2017), LibScout detected >20k apps still containing one of these vulnerable lib versions. The findings have been reported to Google's [ASI program](https://developer.android.com/google/play/asi.html). Unfortunately, the report seemed to be ignored.
In consequence, we manually notified many app developers.
Among others, McAfee published a [Security Advisory](http://service.mcafee.com/FAQDocument.aspx?&id=TS102785) for one of their apps.
## LibScout 101
* LibScout requires Java 1.8 or higher and can be build with Gradle.
* Generate a runnable jar with the gradle wrapper gradlew (Linux/MacOS) or gradlew.bat (Windows), by invoking it with the build task, e.g. ./gradlew build
The LibScout.jar is output to the build/libs directory.
* Some less frequently changing options can be configured via LibScout's config file [LibScout.toml](config/LibScout.toml).
* Most LibScout modules require an Android SDK (jar) to distinguish app code from framework code (via the -a switch).
Refer to https://developer.android.com/studio/ for download instructions.
* By default, LibScout logs to stdout. Use the -d switch to redirect output to files. The -m switch disables any text output. Depending on the operation mode (see below), LibScout's results can be written to disk in JSON format or JAVA serialization.
* LibScout repo structure in a nutshell:
|_ gradlew / gradlew.bat (gradle wrappers to generate runnable LibScout.jar)
|_ assets
| |_ library.xml (Library meta-data template)
|_ config
| |_ LibScout.toml (LibScout's config file)
| |_ logback.xml (log4j configuration file)
|_ data
| |_ app-version-codes.csv (Google Play app packages with valid version codes)
|_ lib
| Android axml
|_ scripts
| |_ library-specs (pre-defined library specs)
| |_ library-scraper.py (scraper for mvn-central, jcenter, custom mvn)
| |_ library-profile-generator.sh (convenience profile generator)
|_ src
source directory of LibScout (de/infsec/tpl). Includes some open-source,
third-party code to parse AXML resources / app manifests etc.
* LibScout supports different use cases implemented as modules (modes of operation). Below a detailed description for each module.
### Library Profiling (-o profile)
This module generates unique library fingerprints from original lib SDKs (.jar and .aar files supported). These profiles can subsequently be used for testing whether the respective library
versions are included in apps. Each library file additionally requires a library.xml that contains meta data (e.g. name, version,..). A template can be found in the assets directory.
For your convenience, you can use the library scraper (./scripts) to download full library histories from Maven repositories.
By default, LibScout generates hashtree-based profiles with Package and Class information (omitting methods).
### Library Detection (-o match)
Detects libraries in apps using pre-generated profiles. Optionally, LibScout also conducts an API usage analysis for detected libraries, i.e. which library APIs are used by the app or by other libraries (-u switch).
Analysis results can be written in different formats.
the JSON format (-j switch), creates subfolders in the specified directory following the app package, i.e. *com.foo* will create *com/foo* subfolders.
This is useful when coping with a large number of apps. For detailed information about the information stored, please refer to the JSON output specification.
the serialization option (-s switch) writes stat files per app to disk (deprecated)
### Library API compatibility analysis (-o lib_api_analysis)
Analyzes changes in the documented (public) API sets of library versions.
The analysis results currently include the following information:
Compliance to Semantic Versioning (SemVer), i.e. whether the change in the version string between consecutive versions (expected SemVer) matches
the changes in the respective public API sets (actual SemVer). Results further include statistics about changes in API sets (additions/removals/modifcations). For removed APIs,
LibScout additionally tries to infer alternative APIs (based on different features).
For the analysis, you have to provide a path to the original library SDKs. LibScout recursively searches for library jars|aars (leaf directories are expected to have at most one jar|aar file and one library.xml file).
For your convenience use the library scraper. Analysis results are written to disk in JSON format (-j switch).
### Library Updatability analysis (-o updatability)
This mode is an extension to the match mode. It first detects library versions in the provided apps and conducts a library usage analysis (-u is implied). In addition, it requires library API compat data (via the -l switch) as generated in the lib_api_analysis mode . Based on the lib API usage in the app and the compat info, LibScout determines the highest version that is still compatible to the set of used lib APIs. Note: The new implementation still lacks some features, e.g. the results are currently logged but not yet written to json. See the code comments for more information.
## Scientific Publications
For technical details and large-scale evaluation results, please refer to our publications:
> Reliable Third-Party Library Detection in Android and its Security Applications
> https://www.infsec.cs.uni-saarland.de/~derr/publications/pdfs/derr_ccs16.pdf
>
> Keep me Updated: An Empirical Study of Third-Party Library Updatability on Android
> https://www.infsec.cs.uni-saarland.de/~derr/publications/pdfs/derr_ccs17.pdf
If you use LibScout in a scientific publication, we would appreciate citations using these Bibtex entries: [[bib-ccs16]](https://www.infsec.cs.uni-saarland.de/~derr/publications/bib/derr_ccs16.bib)
[[bib-ccs17]](https://www.infsec.cs.uni-saarland.de/~derr/publications/bib/derr_ccs17.bib)
LibScout-2.3.2/assets/ 0000775 0000000 0000000 00000000000 13424313624 0014546 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/assets/library.xml 0000664 0000000 0000000 00000001144 13424313624 0016734 0 ustar 00root root 0000000 0000000
LibScout-2.3.2/build.gradle 0000664 0000000 0000000 00000002631 13424313624 0015525 0 ustar 00root root 0000000 0000000 apply plugin: 'java'
repositories {
mavenCentral()
}
configurations {
provided
// Make implementation extend from our provided configuration so that classes from dependencies can be bundled
implementation.extendsFrom(provided)
}
sourceSets {
main {
java {
srcDirs = ['src']
}
}
}
dependencies {
provided 'commons-io:commons-io:2.6'
provided 'commons-cli:commons-cli:1.2'
provided 'com.google.code.gson:gson:2.8.0'
provided 'com.github.zafarkhaja:java-semver:0.9.0'
provided 'ch.qos.logback:logback-classic:1.1.2'
provided 'ch.qos.logback:logback-core:1.1.2'
provided 'org.slf4j:slf4j-api:1.7.7'
provided 'net.consensys.cava:cava-toml:0.5.0'
provided 'com.google.guava:guava:27.0.1-jre'
// WALA and dexlib
provided 'com.ibm.wala:com.ibm.wala.core:1.5.1'
provided 'com.ibm.wala:com.ibm.wala.dalvik:1.5.1'
provided 'com.ibm.wala:com.ibm.wala.shrike:1.5.1'
provided 'com.ibm.wala:com.ibm.wala.util:1.5.1'
provided 'org.smali:dexlib2:2.2.6'
provided files('lib/android-xml.jar') // axml relevant classes from Android SDK
}
jar {
manifest {
attributes 'Main-Class': 'de.infsec.tpl.TplCLI',
'Tool': 'LibScout',
'Version': '2.3.2'
}
// build fat jar
from {
configurations.provided.collect { it.isDirectory() ? it : zipTree(it) }
}
}
LibScout-2.3.2/config/ 0000775 0000000 0000000 00000000000 13424313624 0014511 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/config/LibScout.toml 0000664 0000000 0000000 00000001206 13424313624 0017131 0 ustar 00root root 0000000 0000000 #
# This is the LibScout configuration file in TOML format
#
[ reporting ]
# if true, shows comment from library.xml in logs/json
# upon lib detection
show_comments = false
[ sdk ]
# path to Android SDK jar file
# (recommended to use an absolute path)
## android_sdk_jar = "/path/to/sdk/android.jar"
[ logging ]
# path to log4j config file
# (recommended to use an absolute path)
log4j_config_file = "./config/logback.xml"
[ packageTree ]
# if set to true, the packageTree rendering uses ASCII characters
# instead of box-drawing unicode characters (e.g. if the console
# cannot correctly render unicode chars)
ascii_rendering = false
LibScout-2.3.2/config/logback.xml 0000664 0000000 0000000 00000003303 13424313624 0016634 0 ustar 00root root 0000000 0000000 %d{HH:mm:ss} %-5level %-25logger{0} : %msg%nappPath./defaultApp${appPath}.logfalse%d{HH:mm:ss} %-5level %-25logger{0} : %msg%n
LibScout-2.3.2/data/ 0000775 0000000 0000000 00000000000 13424313624 0014155 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/gradle/ 0000775 0000000 0000000 00000000000 13424313624 0014502 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/gradle/wrapper/ 0000775 0000000 0000000 00000000000 13424313624 0016162 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/gradle/wrapper/gradle-wrapper.properties 0000664 0000000 0000000 00000000310 13424313624 0023206 0 ustar 00root root 0000000 0000000 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
LibScout-2.3.2/gradlew 0000775 0000000 0000000 00000012260 13424313624 0014620 0 ustar 00root root 0000000 0000000 #!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
LibScout-2.3.2/gradlew.bat 0000664 0000000 0000000 00000004324 13424313624 0015364 0 ustar 00root root 0000000 0000000 @if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
LibScout-2.3.2/lib/ 0000775 0000000 0000000 00000000000 13424313624 0014012 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/scripts/ 0000775 0000000 0000000 00000000000 13424313624 0014733 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/scripts/library-profile-generator.sh 0000775 0000000 0000000 00000005642 13424313624 0022367 0 ustar 00root root 0000000 0000000 #!/bin/bash
#
# Convenience script to generate library profiles with LibScout
# Usage: $0
#
# In each subdirectory of there must only be one library .jar|.aar file
# with its library.xml description. The mvn scraper scripts automatically create such a file structure, e.g.
#
# |_OkHttp
# |_3.0.0
# | |_library.xml
# | |_okhttp-3.0.0.jar
# |_3.0.1
# |_library.xml
# |_okhttp-3.0.1.jar
#
# ATTENTION:
# Before being able to use this script, you first have to replace the placeholder
# values for the LibScout root directory and the path to the Android SDK.
#
# The LibScout.jar is automatically being built, if not existing.
# Change $JOBS to run multiple LibScout instances in parallel.
# The profiles are emitted to $LIBSCOUT_ROOT/profiles
#
# @author Erik Derr [derr@cs.uni-saarland.de]
#
# LibScout dir and arguments
LIBSCOUT_ROOT="" # path to the LibScout root directory
LIBSCOUT="$LIBSCOUT_ROOT/build/libs/LibScout.jar"
ANDROID_SDK="" # argument: path to Android SDK
LOG_DIR="" # optional argument: enable logging via "-d "
JOBS=2 # Number of parallel instances
GRADLE_BUILD="$LIBSCOUT_ROOT/gradlew build"
LIBXML="library.xml"
function usage() {
echo "Usage $0 "
exit 0
}
function seconds2Time() {
H=$(($1/60/60%24))
M=$(($1/60%60))
S=$(($1%60))
if [ ${H} != 0 ]; then
echo ${H} h ${M} min ${S} sec
elif [ ${M} != 0 ]; then
echo ${M} min ${S} sec
else
echo ${S} sec
fi
}
## 1. check for variables
if [ $LIBSCOUT_ROOT = "" ]; then
echo "Please set the path to LibScout.jar via the \"LIBSCOUT_ROOT\" variable and retry."
exit 1
fi
if [ $ANDROID_SDK = "" ]; then
echo "Please set the path to the Android SDK via the \"ANDROID_SDK\" variable and retry."
exit 1
fi
## 2. process command line args
if [ $# -gt 1 -o $# -eq 0 ]; then
usage
elif [ $# -eq 1 ]; then
LIBDIR=$1
if [ ! -d $LIBDIR ]; then
echo "[error] $LIBDIR is not a directory"!
usage
fi
fi
# change to libscout root
CUR_DIR=`pwd`
cd $LIBSCOUT_ROOT
## 3. generate LibScout.jar if not existing
if [ ! -e $LIBSCOUT ]; then
echo -n "[info] $LIBSCOUT does not exist, generating jar file now..."
$GRADLE_BUILD > /dev/null
if [ $? != 0 ]; then
echo "[failed]"
exit $rc;
fi
echo "[done]"
fi
## 4. generate library profiles
echo "= Generating library profiles ="
STARTTIME=$(date +%s)
# run $JOBS instances in parallel
echo "# `find $LIBDIR -type f -name $LIBXML| wc -l` library.xml files found in $LIBDIR"
find $LIBDIR -type f -name $LIBXML | parallel --no-notice --jobs $JOBS "echo \" - gen profile: {//}\" ; java -jar $LIBSCOUT -o profile -m -a $ANDROID_SDK $LOG_DIR -x {} {//}"
ENDTIME=$(date +%s)
echo
echo "# processing done in `seconds2Time $[ $ENDTIME - $STARTTIME ]`"
cd $CUR_DIR # restore old dir
LibScout-2.3.2/scripts/library-scraper.py 0000775 0000000 0000000 00000023037 13424313624 0020416 0 ustar 00root root 0000000 0000000 #!/usr/bin/python
#
# Scraper for libraries hosted at jcenter / mvn central / custom maven repos
# Retrieves jar|aar files along with some meta data
#
# @author erik derr [derr@cs.uni-saarland.de]
#
import sys
import json
from urllib.request import urlopen
from urllib.error import URLError
from urllib.error import HTTPError
import datetime
import os
import errno
import zipfile
import traceback
import xml.etree.ElementTree as ElementTree
from retrying import retry
## functions ##
def unix2Date(unixTime):
unixTime = int(str(unixTime)[:-3])
return datetime.datetime.fromtimestamp(unixTime).strftime('%d.%m.%Y')
def make_sure_path_exists(path):
try:
os.makedirs(path)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
def write_library_description(fileName, libName, category, version, date, comment):
make_sure_path_exists(os.path.dirname(fileName))
# write lib description in xml format
with open(fileName, "w") as desc:
desc.write("\n")
desc.write("\n")
desc.write(" \n")
desc.write(" {}\n".format(libName))
desc.write("\n")
desc.write(" \n")
desc.write(" {}\n".format(category))
desc.write("\n")
desc.write(" \n")
desc.write(" {}\n".format(version))
desc.write("\n")
desc.write(" \n")
desc.write(" {}\n".format(date))
desc.write("\n")
desc.write(" \n")
desc.write(" {}\n".format(comment))
desc.write("\n")
@retry(URLError, tries=3, delay=3, backoff=1)
def urlopen_with_retry(URL):
return urlopen(URL)
def downloadLibFile(targetDir, repo, groupid, artefactid, version, filetype):
make_sure_path_exists(os.path.dirname(targetDir + "/"))
# assemble download URL
artefactid_r = artefactid.replace(".","/")
groupid_r = groupid.replace(".","/")
if repo == MVN_CENTRAL:
repoURL = "http://search.maven.org/remotecontent?filepath="
fileName = artefactid_r + "-" + version + "." + filetype
else:
repoURL = repo
fileName = artefactid + "-" + version + "." + filetype
# retrieve and save file
URL = repoURL + groupid_r + "/" + artefactid_r + "/" + version + "/" + fileName
targetFile = targetDir + "/" + fileName
try:
libFile = urlopen(URL)
with open(targetFile,'wb') as output:
output.write(libFile.read())
return 0
except HTTPError as e:
if filetype != 'aar':
print(' !! HTTP Error while retrieving ' + filetype + ' file: ' + str(e.code))
return 1
except URLError as e:
print(' !! URL Error while retrieving ' + filetype + ' file: ' + str(e.reason))
return 1
except Exception as excp:
print(' !! Download failed: ' + str(excp))
return 1
## library updating routine for mvn central
def updateLibraryMvnCentral(libName, category, comment, groupId, artefactId):
# replace all blanks with dash
libName = libName.replace(" ", "-")
print(" # check library " + libName + " [" + category + "] (g:\"" + groupId + "\" AND a:\"" + artefactId + "\")")
baseDirName = localRepoDir + category + "/" + libName + "/"
dir = os.path.dirname(baseDirName)
make_sure_path_exists(dir);
# Assemble mvn central search URL and retrieve meta data
try:
mvnSearchURL = "http://search.maven.org/solrsearch/select?q=g:%22" + groupId + "%22+AND+a:%22" + artefactId + "%22&rows=100&core=gav"
response = urlopen(mvnSearchURL)
data = json.loads(response.read())
except URLError as e:
print('URLError = ' + str(e.reason))
return
except Exception as excp:
print('Could not retrieve meta data for ' + libName + ' [SKIP] (' + str(excp) + ')')
return
# DEBUG: pretty print json
#print(json.dumps(data, indent=4, sort_keys=True))
#print()
numberOfVersions = data["response"]["numFound"]
print(" - retrieved meta data for " + str(numberOfVersions) + " versions:")
numberOfUpdates = 0
if numberOfVersions > 0:
for version in data["response"]["docs"]:
if skipAlphaBeta and any(x in version["v"].lower() for x in SKIP_KEYWORDS):
continue;
# skip lib version if already existing
if not os.path.isfile(baseDirName + "/" + version["v"] + "/" + LIB_DESCRIPTOR_FILE_NAME):
numberOfUpdates += 1
date = unix2Date(version["timestamp"])
targetDir = baseDirName + version["v"]
print(" - update version: {} type: {} date: {} target-dir: {}".format(version["v"], version["p"], date, targetDir))
result = downloadLibFile(targetDir, MVN_CENTRAL, groupId, artefactId, version["v"], "aar")
if result == 1:
result = downloadLibFile(targetDir, MVN_CENTRAL, groupId, artefactId, version["v"], "jar")
if result == 0:
# write lib description
fileName = targetDir + "/" + LIB_DESCRIPTOR_FILE_NAME
write_library_description(fileName, libName, category, version["v"], date, comment)
if numberOfUpdates == 0:
print(" -> all versions up-to-date")
## library updating routine for jcenter + custom mvn repos
def updateLibrary(libName, category, comment, repoURL, groupId, artefactId):
# replace all blanks with dash
libName = libName.replace(" ", "-")
print(" # check library " + libName + " [" + category + "] (g:\"" + groupId + "\" AND a:\"" + artefactId + "\")")
baseDirName = localRepoDir + category + "/" + libName + "/"
dir = os.path.dirname(baseDirName)
make_sure_path_exists(dir);
# Assemble base URL and retrieve meta data
try:
if repoURL == "jcenter":
repoURL = JCENTER_URL
if not repoURL.endswith("/"):
repoURL = repoURL + "/"
metaURL = repoURL + groupId.replace(".","/") + "/" + artefactId.replace(".","/") + "/maven-metadata.xml"
response = urlopen(metaURL)
data = response.read()
response.close()
except URLError as e:
print('URLError = ' + str(e.reason))
return
except Exception as excp:
print('Could not retrieve meta data for ' + libName + ' [SKIP] (' + str(excp) + ')')
return
# retrieve available versions
versions = []
root = ElementTree.fromstring(data)
for vg in root.find('versioning'):
for v in vg.iter('version'):
if not skipAlphaBeta or (skipAlphaBeta and not any(x in v.text.lower() for x in SKIP_KEYWORDS)):
versions.append(v.text)
numberOfVersions = len(versions)
print(" - retrieved meta data for " + str(numberOfVersions) + " versions:")
numberOfUpdates = 0
if numberOfVersions > 0:
for version in versions:
# skip lib version if already existing
if not os.path.isfile(baseDirName + "/" + version + "/" + LIB_DESCRIPTOR_FILE_NAME):
numberOfUpdates += 1
targetDir = baseDirName + version
fileType = "aar"
result = downloadLibFile(targetDir, repoURL, groupId, artefactId, version, fileType)
if result == 1:
fileType = "jar"
result = downloadLibFile(targetDir, repoURL, groupId, artefactId, version, fileType)
if result == 0:
print(" - update version: {} type: {} date: {} target-dir: {}".format(version, fileType, "n/a", targetDir))
fileName = targetDir + "/" + LIB_DESCRIPTOR_FILE_NAME
write_library_description(fileName, libName, category, version, "", comment)
if numberOfUpdates == 0:
print(" -> all versions up-to-date")
## Main functionality ##
LIB_DESCRIPTOR_FILE_NAME = "library.xml"
JCENTER = "jcenter"
JCENTER_URL = "http://jcenter.bintray.com"
MVN_CENTRAL = "mvn-central"
SKIP_KEYWORDS = ['-alpha', '-prealpha', '-beta', '-rc', '-dev', '-snapshot']
skipAlphaBeta = True # skip alpha and beta versions
localRepoDir = "my-lib-repo/" # the directory to store libraries on disk with trailing path separator, e.g. "/"
print("== maven/jcenter scraper ==")
# Requires one argument (path to json file with library descriptions)
args = len(sys.argv)
if args != 2:
print("Usage: " + sys.argv[0] + " ")
sys.exit(1)
else:
inputFile = sys.argv[1]
print("- Load library info from " + sys.argv[1])
print("- Store libs to " + localRepoDir)
# load library definitions
with open(inputFile) as ifile:
data = json.load(ifile)
# update each lib
print("- Update libraries" + (" (skip alpha/beta versions)" if skipAlphaBeta else "") + ":")
for lib in data["libraries"]:
if 'repo' not in lib:
print("[WARN] Skip library: " + lib["name"] + " (No repo defined!)")
continue
elif lib['repo'] == MVN_CENTRAL:
updateLibraryMvnCentral(lib["name"], lib["category"], lib["comment"], lib["groupid"], lib["artefactid"])
else: # jcenter or custom mvn repo URL
updateLibrary(lib["name"], lib["category"], lib["comment"], lib['repo'], lib["groupid"], lib["artefactid"])
LibScout-2.3.2/scripts/library-specs/ 0000775 0000000 0000000 00000000000 13424313624 0017512 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/scripts/library-specs/amazon-libraries.json 0000664 0000000 0000000 00000020446 13424313624 0023652 0 ustar 00root root 0000000 0000000 {
"libraries": [
{
"name": "Amazon AWS Mobile Analytics",
"category": "Analytics",
"comment": "With Amazon Mobile Analytics, you can measure app usage and app revenue.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-mobileanalytics",
"repo": "mvn-central"
},{
"name": "Amazon AWS Mobile SDK core",
"category": "Cloud",
"comment": "",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-core",
"repo": "mvn-central"
},{
"name": "Amazon API Gateway",
"category": "Cloud",
"comment": "API Gateway is an AWS service that enables developers to create, publish, maintain, monitor, and secure APIs at any scale.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-apigateway-core",
"repo": "mvn-central"
},{
"name": "Amazon AWS IoT",
"category": "Cloud",
"comment": "AWS IoT is a managed cloud platform that lets connected devices easily and securely interact with cloud applications and other devices",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-iot",
"repo": "mvn-central"
},{
"name": "Amazon AWS Elastic Beanstalk",
"category": "Cloud",
"comment": "With Elastic Beanstalk, you can quickly deploy and manage applications in the AWS Cloud without worrying about the infrastructure that runs those applications.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-elb",
"repo": "mvn-central"
},{
"name": "Amazon SimpleDB",
"category": "Cloud",
"comment": "Amazon SimpleDB is a highly available NoSQL data store that offloads the work of database administration.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-sdb",
"repo": "mvn-central"
},{
"name": "Amazon Rekognition",
"category": "Cloud",
"comment": "Amazon Rekognition is a service that enables you to add image analysis to your applications.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-rekognition",
"repo": "mvn-central"
},{
"name": "Amazon Cloud Watch",
"category": "Cloud",
"comment": "Amazon CloudWatch is a monitoring service for AWS cloud resources and the applications you run on AWS.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-cloudwatch",
"repo": "mvn-central"
},{
"name": "Amazon Cognito Identity Provider",
"category": "Cloud",
"comment": "",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-cognitoidentityprovider",
"repo": "mvn-central"
},{
"name": "Amazon Cognito Auth",
"category": "Cloud",
"comment": "",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-cognitoauth",
"repo": "mvn-central"
},{
"name": "Amazon Cognito Sync",
"category": "Cloud",
"comment": "Using Amazon Cognito Identity, you can create unique identities for your users and authenticate them for secure access to your AWS resources",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-cognito",
"repo": "mvn-central"
},{
"name": "Amazon DynamoDB",
"category": "Cloud",
"comment": "DynamoDB is a fast, highly scalable, highly available, cost-effective, nonrelational database service.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-ddb",
"repo": "mvn-central"
},{
"name": "Amazon DynamoDB Document",
"category": "Cloud",
"comment": "",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-ddb-document",
"repo": "mvn-central"
},{
"name": "Amazon DynamoDB Object Mapper",
"category": "Cloud",
"comment": "DynamoDB Object Mapper lets you map client-side classes to DynamoDB tables; perform various create, read, update, and delete (CRUD) operations; and execute queries.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-ddb-mapper",
"repo": "mvn-central"
},{
"name": "Amazon Elastic Compute Cloud (EC2)",
"category": "Cloud",
"comment": "Amazon Elastic Compute Cloud (Amazon EC2) is a web service that provides secure, resizable compute capacity in the cloud.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-ec2",
"repo": "mvn-central"
},{
"name": "Amazon Kinesis Streams",
"category": "Cloud",
"comment": "Amazon Kinesis is a fully managed service for real-time processing of streaming data at massive scale.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-kinesis",
"repo": "mvn-central"
},{
"name": "Amazon Machine Learning",
"category": "Cloud",
"comment": "Amazon Machine Learning is a service that makes it easy for developers of all skill levels to use machine learning technology.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-machinelearning",
"repo": "mvn-central"
},{
"name": "Amazon Polly",
"category": "Cloud",
"comment": "Add Text to Speech Capability to your Android App with Amazon Polly",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-polly",
"repo": "mvn-central"
},{
"name": "Amazon Pinpoint",
"category": "Cloud",
"comment": "Create Push Notification Campaigns with Amazon Pinpoint",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-pinpoint",
"repo": "mvn-central"
},{
"name": "Amazon Simple Storage Service (S3)",
"category": "Cloud",
"comment": "Amazon S3 provides secure, durable, highly-scalable object storage in the cloud.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-s3",
"repo": "mvn-central"
},{
"name": "Amazon Simple Email Service (SES)",
"category": "Cloud",
"comment": "SES eliminates the complexity and expense of building an in-house email solution.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-ses",
"repo": "mvn-central"
},{
"name": "Amazon Simple Notification Service (SNS)",
"category": "Cloud",
"comment": "SNS is a flexible, fully managed pub/sub messaging and mobile notifications service for coordinating the delivery of messages to subscribing endpoints and clients.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-sns",
"repo": "mvn-central"
},{
"name": "Amazon Simple Queue Service (SQS)",
"category": "Cloud",
"comment": "SQS is a fully managed message queuing service that makes it easy to decouple and scale microservices, distributed systems, and serverless applications.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-sqs",
"repo": "mvn-central"
},{
"name": "Amazon AWS Auto Scaling",
"category": "Cloud",
"comment": "Auto Scaling helps you maintain application availability and allows you to dynamically scale your Amazon EC2 capacity up or down automatically according to conditions you define.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-autoscaling",
"repo": "mvn-central"
},{
"name": "Amazon AWS Key Management Service (KMS)",
"category": "Cloud",
"comment": "KMS is a managed service that makes it easy for you to create and control the encryption keys used to encrypt your data, and uses Hardware Security Modules (HSMs) to protect the security of your keys.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-kms",
"repo": "mvn-central"
},{
"name": "Amazon AWS Lambda",
"category": "Cloud",
"comment": "Execute Code On Demand with Amazon Lambda",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-lambda",
"repo": "mvn-central"
},{
"name": "Amazon Lex",
"category": "Cloud",
"comment": "Amazon Lex is an AWS service for building conversational interfaces for any applications using voice and text.",
"groupid": "com.amazonaws",
"artefactid": "aws-android-sdk-lex",
"repo": "mvn-central"
}
]
}
LibScout-2.3.2/scripts/library-specs/google-libraries.json 0000664 0000000 0000000 00000262476 13424313624 0023654 0 ustar 00root root 0000000 0000000 {
"libraries": [
{
"artefactid": "constraint-layout-solver",
"category": "Android",
"comment": "",
"groupid": "com.android.support.constraint",
"name": "com.android.support.constraint::constraint-layout-solver",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "constraint-layout",
"category": "Android",
"comment": "",
"groupid": "com.android.support.constraint",
"name": "com.android.support.constraint::constraint-layout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "library",
"category": "Android",
"comment": "",
"groupid": "com.android.databinding",
"name": "com.android.databinding::library",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "adapters",
"category": "Android",
"comment": "",
"groupid": "com.android.databinding",
"name": "com.android.databinding::adapters",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "compiler",
"category": "Android",
"comment": "",
"groupid": "com.android.databinding",
"name": "com.android.databinding::compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "compilerCommon",
"category": "Android",
"comment": "",
"groupid": "com.android.databinding",
"name": "com.android.databinding::compilerCommon",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "baseLibrary",
"category": "Android",
"comment": "",
"groupid": "com.android.databinding",
"name": "com.android.databinding::baseLibrary",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-compat",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-compat",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "leanback-v17",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::leanback-v17",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "recommendation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::recommendation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-tv-provider",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-tv-provider",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-vector-drawable",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-vector-drawable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "recyclerview-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::recyclerview-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "preference-leanback-v17",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::preference-leanback-v17",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "preference-v14",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::preference-v14",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "percent",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::percent",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-media-compat",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-media-compat",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "cardview-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::cardview-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "wearable",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::wearable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "exifinterface",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::exifinterface",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-annotations",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-annotations",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "appcompat-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::appcompat-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "palette-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::palette-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "multidex-instrumentation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::multidex-instrumentation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "multidex",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::multidex",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "mediarouter-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::mediarouter-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "preference-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::preference-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-dynamic-animation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-dynamic-animation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-fragment",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-fragment",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "transition",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::transition",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "customtabs",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::customtabs",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-core-ui",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-core-ui",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "gridlayout-v7",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::gridlayout-v7",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "animated-vector-drawable",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::animated-vector-drawable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-core-utils",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-core-utils",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-v13",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-v13",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "instantvideo",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::instantvideo",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-v4",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-v4",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-emoji",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-emoji",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "wear",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::wear",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-emoji-appcompat",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-emoji-appcompat",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-emoji-bundled",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-emoji-bundled",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-content",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::support-content",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-bottomnavigation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-bottomnavigation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-button",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-button",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-circularreveal-cardview",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-circularreveal-cardview",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-bottomappbar",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-bottomappbar",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-card",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-card",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-shape",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-shape",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-drawable",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-drawable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-bottomsheet",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-bottomsheet",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-floatingactionbutton",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-floatingactionbutton",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-circularreveal-coordinatorlayout",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-circularreveal-coordinatorlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-textfield",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-textfield",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-stateful",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-stateful",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-circularreveal",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-circularreveal",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-expandable",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-expandable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-navigation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-navigation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-dialog",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-dialog",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-canvas",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-canvas",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-tabs",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-tabs",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-chip",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-chip",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-snackbar",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-snackbar",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-theme",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-theme",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-math",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-math",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-transformation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-transformation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-widget",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-widget",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-animation",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-animation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-typography",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-typography",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-color",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-color",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-internal",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-internal",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-resources",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-resources",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "design-ripple",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::design-ripple",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "coordinatorlayout",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::coordinatorlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "collections",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::collections",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slidingpanelayout",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::slidingpanelayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "asynclayoutinflater",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::asynclayoutinflater",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slices-view",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::slices-view",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "recyclerview-selection",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::recyclerview-selection",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "viewpager",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::viewpager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "cursoradapter",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::cursoradapter",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "localbroadcastmanager",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::localbroadcastmanager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "heifwriter",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::heifwriter",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "customview",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::customview",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "print",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::print",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slices-builders",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::slices-builders",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "interpolator",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::interpolator",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slices-core",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::slices-core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "loader",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::loader",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "swiperefreshlayout",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::swiperefreshlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "drawerlayout",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::drawerlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "documentfile",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::documentfile",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "webkit",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::webkit",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "car",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::car",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "versionedparcelable",
"category": "Android",
"comment": "",
"groupid": "com.android.support",
"name": "com.android.support::versionedparcelable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "compiler",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-db-impl",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::support-db-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "runtime",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "support-db",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::support-db",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "migration",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::migration",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "rxjava2",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::rxjava2",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "common",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "db",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::db",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "db-impl",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::db-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "guava",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence.room",
"name": "android.arch.persistence.room::guava",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "compiler",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "runtime",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "extensions",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::extensions",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "reactivestreams",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::reactivestreams",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "common",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "common-java8",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::common-java8",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "viewmodel",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::viewmodel",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "livedata-core",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::livedata-core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "livedata",
"category": "Android",
"comment": "",
"groupid": "android.arch.lifecycle",
"name": "android.arch.lifecycle::livedata",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "core",
"category": "Android",
"comment": "",
"groupid": "android.arch.core",
"name": "android.arch.core::core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "runtime",
"category": "Android",
"comment": "",
"groupid": "android.arch.core",
"name": "android.arch.core::runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "common",
"category": "Android",
"comment": "",
"groupid": "android.arch.core",
"name": "android.arch.core::common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "instantapps",
"category": "Android",
"comment": "",
"groupid": "com.google.android.instantapps",
"name": "com.google.android.instantapps::instantapps",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "volleycompat",
"category": "Android",
"comment": "",
"groupid": "com.google.android.instantapps.thirdpartycompat",
"name": "com.google.android.instantapps.thirdpartycompat::volleycompat",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "dvlib",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::dvlib",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "sdklib",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::sdklib",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "repository",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::repository",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "annotations",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::annotations",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "devicelib",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::devicelib",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "sdk-common",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::sdk-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "common",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "r8",
"category": "Android",
"comment": "",
"groupid": "com.android.tools",
"name": "com.android.tools::r8",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-dynamic-links",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-dynamic-links",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-crash",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-crash",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-ads",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-ads",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-analytics",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-analytics",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-common",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-auth",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-auth",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-appindexing",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-appindexing",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-auth-common",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-auth-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-invites",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-invites",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-config",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-config",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-analytics-impl",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-analytics-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-storage",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-storage",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-messaging",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-messaging",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-auth-module",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-auth-module",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-auth-impl",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-auth-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-database-connection",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-database-connection",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-storage-common",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-storage-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-core",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-database",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-database",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-perf",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-perf",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-iid",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-iid",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-firestore",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-firestore",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-functions",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-functions",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-ads-lite",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-ads-lite",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-database-collection",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-database-collection",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-abt",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-abt",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-iid-interop",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-iid-interop",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "protolite-well-known-types",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::protolite-well-known-types",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-measurement-connector",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-measurement-connector",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-auth-interop",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-auth-interop",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-measurement-connector-impl",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-measurement-connector-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-ml-common",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-ml-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-ml-vision-image-label-model",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-ml-vision-image-label-model",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-ml-vision",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-ml-vision",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "firebase-ml-model-interpreter",
"category": "Android",
"comment": "",
"groupid": "com.google.firebase",
"name": "com.google.firebase::firebase-ml-model-interpreter",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-vision",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-vision",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-tagmanager",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-tagmanager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-vision-common",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-vision-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-all-wear",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-all-wear",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-instantapps",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-instantapps",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-tasks",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-tasks",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-ads-lite",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-ads-lite",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-analytics-impl",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-analytics-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-maps",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-maps",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-appindexing",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-appindexing",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-wearable",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-wearable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-awareness",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-awareness",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-nearby",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-nearby",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-basement",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-basement",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-tagmanager-api",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-tagmanager-api",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-tagmanager-v4-impl",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-tagmanager-v4-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-safetynet",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-safetynet",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-plus",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-plus",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-base",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-base",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-iid",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-iid",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-panorama",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-panorama",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-contextmanager",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-contextmanager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-games",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-games",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-cast",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-cast",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-location",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-location",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-places",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-places",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-wallet",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-wallet",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-identity",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-identity",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-analytics",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-analytics",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-appinvite",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-appinvite",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-auth-base",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-auth-base",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-auth-api-phone",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-auth-api-phone",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-gass",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-gass",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-appstate",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-appstate",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-fitness",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-fitness",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-drive",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-drive",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-measurement",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-measurement",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-ads",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-ads",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-clearcut",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-clearcut",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-gcm",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-gcm",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-auth",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-auth",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-cast-framework",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-cast-framework",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-fido",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-fido",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "auth-api-impl",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::auth-api-impl",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-places-placereport",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-places-placereport",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-measurement-base",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-measurement-base",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-audience",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-audience",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-ads-identifier",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-ads-identifier",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-flags",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-flags",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-phenotype",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-phenotype",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-ads-base",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-ads-base",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-stats",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-stats",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "play-services-vision-image-label",
"category": "Android",
"comment": "",
"groupid": "com.google.android.gms",
"name": "com.google.android.gms::play-services-vision-image-label",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "runtime",
"category": "Android",
"comment": "",
"groupid": "android.arch.paging",
"name": "android.arch.paging::runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "common",
"category": "Android",
"comment": "",
"groupid": "android.arch.paging",
"name": "android.arch.paging::common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "rxjava2",
"category": "Android",
"comment": "",
"groupid": "android.arch.paging",
"name": "android.arch.paging::rxjava2",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "answers",
"category": "Android",
"comment": "",
"groupid": "com.crashlytics.sdk.android",
"name": "com.crashlytics.sdk.android::answers",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "beta",
"category": "Android",
"comment": "",
"groupid": "com.crashlytics.sdk.android",
"name": "com.crashlytics.sdk.android::beta",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "crashlytics-core",
"category": "Android",
"comment": "",
"groupid": "com.crashlytics.sdk.android",
"name": "com.crashlytics.sdk.android::crashlytics-core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "crashlytics",
"category": "Android",
"comment": "",
"groupid": "com.crashlytics.sdk.android",
"name": "com.crashlytics.sdk.android::crashlytics",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "crashlytics-ndk",
"category": "Android",
"comment": "",
"groupid": "com.crashlytics.sdk.android",
"name": "com.crashlytics.sdk.android::crashlytics-ndk",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "fabric",
"category": "Android",
"comment": "",
"groupid": "io.fabric.sdk.android",
"name": "io.fabric.sdk.android::fabric",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "db-framework",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence",
"name": "android.arch.persistence::db-framework",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "db",
"category": "Android",
"comment": "",
"groupid": "android.arch.persistence",
"name": "android.arch.persistence::db",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "wearable",
"category": "Android",
"comment": "",
"groupid": "com.google.android.wearable",
"name": "com.google.android.wearable::wearable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "wearable",
"category": "Android",
"comment": "",
"groupid": "com.google.android.support",
"name": "com.google.android.support::wearable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "installreferrer",
"category": "Android",
"comment": "",
"groupid": "com.android.installreferrer",
"name": "com.android.installreferrer::installreferrer",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "core-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.core",
"name": "androidx.core::core-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "core",
"category": "Android",
"comment": "",
"groupid": "androidx.core",
"name": "androidx.core::core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "androidthings",
"category": "Android",
"comment": "",
"groupid": "com.google.android.things",
"name": "com.google.android.things::androidthings",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "baseLibrary",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::baseLibrary",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "compiler",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "compilerCommon",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::compilerCommon",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "adapters",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::adapters",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "library",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::library",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "databinding-runtime",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::databinding-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "databinding-adapters",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::databinding-adapters",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "databinding-compiler-common",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::databinding-compiler-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "databinding-compiler",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::databinding-compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "databinding-common",
"category": "Android",
"comment": "",
"groupid": "androidx.databinding",
"name": "androidx.databinding::databinding-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "constraintlayout-solver",
"category": "Android",
"comment": "",
"groupid": "androidx.constraintlayout",
"name": "androidx.constraintlayout::constraintlayout-solver",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "constraintlayout",
"category": "Android",
"comment": "",
"groupid": "androidx.constraintlayout",
"name": "androidx.constraintlayout::constraintlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "core",
"category": "Android",
"comment": "",
"groupid": "com.google.android.play",
"name": "com.google.android.play::core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "multidex",
"category": "Android",
"comment": "",
"groupid": "androidx.multidex",
"name": "androidx.multidex::multidex",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "multidex-instrumentation",
"category": "Android",
"comment": "",
"groupid": "androidx.multidex",
"name": "androidx.multidex::multidex-instrumentation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "material",
"category": "Android",
"comment": "",
"groupid": "com.google.android.material",
"name": "com.google.android.material::material",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "room-rxjava2",
"category": "Android",
"comment": "",
"groupid": "androidx.room",
"name": "androidx.room::room-rxjava2",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "room-common",
"category": "Android",
"comment": "",
"groupid": "androidx.room",
"name": "androidx.room::room-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "room-compiler",
"category": "Android",
"comment": "",
"groupid": "androidx.room",
"name": "androidx.room::room-compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "room-guava",
"category": "Android",
"comment": "",
"groupid": "androidx.room",
"name": "androidx.room::room-guava",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "room-migration",
"category": "Android",
"comment": "",
"groupid": "androidx.room",
"name": "androidx.room::room-migration",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "room-runtime",
"category": "Android",
"comment": "",
"groupid": "androidx.room",
"name": "androidx.room::room-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "paging-common",
"category": "Android",
"comment": "",
"groupid": "androidx.paging",
"name": "androidx.paging::paging-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "paging-runtime",
"category": "Android",
"comment": "",
"groupid": "androidx.paging",
"name": "androidx.paging::paging-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "paging-rxjava2",
"category": "Android",
"comment": "",
"groupid": "androidx.paging",
"name": "androidx.paging::paging-rxjava2",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-reactivestreams-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-reactivestreams-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-viewmodel",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-viewmodel",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-process",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-process",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-viewmodel-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-viewmodel-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-reactivestreams",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-reactivestreams",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-common-java8",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-common-java8",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-extensions",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-extensions",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-livedata-core",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-livedata-core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-runtime",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-compiler",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-compiler",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-livedata",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-livedata",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-common",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "lifecycle-service",
"category": "Android",
"comment": "",
"groupid": "androidx.lifecycle",
"name": "androidx.lifecycle::lifecycle-service",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "sqlite-framework",
"category": "Android",
"comment": "",
"groupid": "androidx.sqlite",
"name": "androidx.sqlite::sqlite-framework",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "sqlite",
"category": "Android",
"comment": "",
"groupid": "androidx.sqlite",
"name": "androidx.sqlite::sqlite",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "sqlite-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.sqlite",
"name": "androidx.sqlite::sqlite-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "core-common",
"category": "Android",
"comment": "",
"groupid": "androidx.arch.core",
"name": "androidx.arch.core::core-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "core-runtime",
"category": "Android",
"comment": "",
"groupid": "androidx.arch.core",
"name": "androidx.arch.core::core-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "work-runtime-ktx",
"category": "Android",
"comment": "",
"groupid": "android.arch.work",
"name": "android.arch.work::work-runtime-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "work-firebase",
"category": "Android",
"comment": "",
"groupid": "android.arch.work",
"name": "android.arch.work::work-firebase",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "work-runtime",
"category": "Android",
"comment": "",
"groupid": "android.arch.work",
"name": "android.arch.work::work-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-common",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-common",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-runtime",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-runtime",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-common-ktx",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-common-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-ui",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-ui",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-fragment-ktx",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-fragment-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-ui-ktx",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-ui-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-safe-args-generator",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-safe-args-generator",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-runtime-ktx",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-runtime-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "navigation-fragment",
"category": "Android",
"comment": "",
"groupid": "android.arch.navigation",
"name": "android.arch.navigation::navigation-fragment",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "mediarouter",
"category": "Android",
"comment": "",
"groupid": "androidx.mediarouter",
"name": "androidx.mediarouter::mediarouter",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "percentlayout",
"category": "Android",
"comment": "",
"groupid": "androidx.percentlayout",
"name": "androidx.percentlayout::percentlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "emoji",
"category": "Android",
"comment": "",
"groupid": "androidx.emoji",
"name": "androidx.emoji::emoji",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "emoji-appcompat",
"category": "Android",
"comment": "",
"groupid": "androidx.emoji",
"name": "androidx.emoji::emoji-appcompat",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "emoji-bundled",
"category": "Android",
"comment": "",
"groupid": "androidx.emoji",
"name": "androidx.emoji::emoji-bundled",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "cardview",
"category": "Android",
"comment": "",
"groupid": "androidx.cardview",
"name": "androidx.cardview::cardview",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "preference",
"category": "Android",
"comment": "",
"groupid": "androidx.preference",
"name": "androidx.preference::preference",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "preference-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.preference",
"name": "androidx.preference::preference-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "wear",
"category": "Android",
"comment": "",
"groupid": "androidx.wear",
"name": "androidx.wear::wear",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "legacy-support-v13",
"category": "Android",
"comment": "",
"groupid": "androidx.legacy",
"name": "androidx.legacy::legacy-support-v13",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "legacy-preference-v14",
"category": "Android",
"comment": "",
"groupid": "androidx.legacy",
"name": "androidx.legacy::legacy-preference-v14",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "legacy-support-v4",
"category": "Android",
"comment": "",
"groupid": "androidx.legacy",
"name": "androidx.legacy::legacy-support-v4",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "legacy-support-core-ui",
"category": "Android",
"comment": "",
"groupid": "androidx.legacy",
"name": "androidx.legacy::legacy-support-core-ui",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "legacy-support-core-utils",
"category": "Android",
"comment": "",
"groupid": "androidx.legacy",
"name": "androidx.legacy::legacy-support-core-utils",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "documentfile",
"category": "Android",
"comment": "",
"groupid": "androidx.documentfile",
"name": "androidx.documentfile::documentfile",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "car",
"category": "Android",
"comment": "",
"groupid": "androidx.car",
"name": "androidx.car::car",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "swiperefreshlayout",
"category": "Android",
"comment": "",
"groupid": "androidx.swiperefreshlayout",
"name": "androidx.swiperefreshlayout::swiperefreshlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "leanback",
"category": "Android",
"comment": "",
"groupid": "androidx.leanback",
"name": "androidx.leanback::leanback",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "leanback-preference",
"category": "Android",
"comment": "",
"groupid": "androidx.leanback",
"name": "androidx.leanback::leanback-preference",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "appcompat",
"category": "Android",
"comment": "",
"groupid": "androidx.appcompat",
"name": "androidx.appcompat::appcompat",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "customview",
"category": "Android",
"comment": "",
"groupid": "androidx.customview",
"name": "androidx.customview::customview",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "gridlayout",
"category": "Android",
"comment": "",
"groupid": "androidx.gridlayout",
"name": "androidx.gridlayout::gridlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "vectordrawable-animated",
"category": "Android",
"comment": "",
"groupid": "androidx.vectordrawable",
"name": "androidx.vectordrawable::vectordrawable-animated",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "vectordrawable",
"category": "Android",
"comment": "",
"groupid": "androidx.vectordrawable",
"name": "androidx.vectordrawable::vectordrawable",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "heifwriter",
"category": "Android",
"comment": "",
"groupid": "androidx.heifwriter",
"name": "androidx.heifwriter::heifwriter",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "transition",
"category": "Android",
"comment": "",
"groupid": "androidx.transition",
"name": "androidx.transition::transition",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "print",
"category": "Android",
"comment": "",
"groupid": "androidx.print",
"name": "androidx.print::print",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "viewpager",
"category": "Android",
"comment": "",
"groupid": "androidx.viewpager",
"name": "androidx.viewpager::viewpager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "annotation",
"category": "Android",
"comment": "",
"groupid": "androidx.annotation",
"name": "androidx.annotation::annotation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "exifinterface",
"category": "Android",
"comment": "",
"groupid": "androidx.exifinterface",
"name": "androidx.exifinterface::exifinterface",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "dynamicanimation",
"category": "Android",
"comment": "",
"groupid": "androidx.dynamicanimation",
"name": "androidx.dynamicanimation::dynamicanimation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "browser",
"category": "Android",
"comment": "",
"groupid": "androidx.browser",
"name": "androidx.browser::browser",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "localbroadcastmanager",
"category": "Android",
"comment": "",
"groupid": "androidx.localbroadcastmanager",
"name": "androidx.localbroadcastmanager::localbroadcastmanager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "asynclayoutinflater",
"category": "Android",
"comment": "",
"groupid": "androidx.asynclayoutinflater",
"name": "androidx.asynclayoutinflater::asynclayoutinflater",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "contentpager",
"category": "Android",
"comment": "",
"groupid": "androidx.contentpager",
"name": "androidx.contentpager::contentpager",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slidingpanelayout",
"category": "Android",
"comment": "",
"groupid": "androidx.slidingpanelayout",
"name": "androidx.slidingpanelayout::slidingpanelayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "cursoradapter",
"category": "Android",
"comment": "",
"groupid": "androidx.cursoradapter",
"name": "androidx.cursoradapter::cursoradapter",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "media-widget",
"category": "Android",
"comment": "",
"groupid": "androidx.media",
"name": "androidx.media::media-widget",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "media",
"category": "Android",
"comment": "",
"groupid": "androidx.media",
"name": "androidx.media::media",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "loader",
"category": "Android",
"comment": "",
"groupid": "androidx.loader",
"name": "androidx.loader::loader",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "interpolator",
"category": "Android",
"comment": "",
"groupid": "androidx.interpolator",
"name": "androidx.interpolator::interpolator",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "coordinatorlayout",
"category": "Android",
"comment": "",
"groupid": "androidx.coordinatorlayout",
"name": "androidx.coordinatorlayout::coordinatorlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "fragment-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.fragment",
"name": "androidx.fragment::fragment-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "fragment",
"category": "Android",
"comment": "",
"groupid": "androidx.fragment",
"name": "androidx.fragment::fragment",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "tvprovider",
"category": "Android",
"comment": "",
"groupid": "androidx.tvprovider",
"name": "androidx.tvprovider::tvprovider",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slice-core",
"category": "Android",
"comment": "",
"groupid": "androidx.slice",
"name": "androidx.slice::slice-core",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slice-builders",
"category": "Android",
"comment": "",
"groupid": "androidx.slice",
"name": "androidx.slice::slice-builders",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slice-view",
"category": "Android",
"comment": "",
"groupid": "androidx.slice",
"name": "androidx.slice::slice-view",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "slice-builders-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.slice",
"name": "androidx.slice::slice-builders-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "collection-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.collection",
"name": "androidx.collection::collection-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "collection",
"category": "Android",
"comment": "",
"groupid": "androidx.collection",
"name": "androidx.collection::collection",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "recommendation",
"category": "Android",
"comment": "",
"groupid": "androidx.recommendation",
"name": "androidx.recommendation::recommendation",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "drawerlayout",
"category": "Android",
"comment": "",
"groupid": "androidx.drawerlayout",
"name": "androidx.drawerlayout::drawerlayout",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "recyclerview-selection",
"category": "Android",
"comment": "",
"groupid": "androidx.recyclerview",
"name": "androidx.recyclerview::recyclerview-selection",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "recyclerview",
"category": "Android",
"comment": "",
"groupid": "androidx.recyclerview",
"name": "androidx.recyclerview::recyclerview",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "webkit",
"category": "Android",
"comment": "",
"groupid": "androidx.webkit",
"name": "androidx.webkit::webkit",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "palette-ktx",
"category": "Android",
"comment": "",
"groupid": "androidx.palette",
"name": "androidx.palette::palette-ktx",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "palette",
"category": "Android",
"comment": "",
"groupid": "androidx.palette",
"name": "androidx.palette::palette",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "consent-library",
"category": "Android",
"comment": "",
"groupid": "com.google.android.ads.consent",
"name": "com.google.android.ads.consent::consent-library",
"repo": "https://dl.google.com/dl/android/maven2"
},{
"artefactid": "versionedparcelable",
"category": "Android",
"comment": "",
"groupid": "androidx.versionedparcelable",
"name": "androidx.versionedparcelable::versionedparcelable",
"repo": "https://dl.google.com/dl/android/maven2"
}
]
}
LibScout-2.3.2/scripts/library-specs/mvn-central-libraries.json 0000664 0000000 0000000 00000054170 13424313624 0024614 0 ustar 00root root 0000000 0000000 {
"libraries": [
{
"name": "Facebook Audience",
"category": "Advertising",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "audience-network-sdk",
"repo": "mvn-central"
},
{
"name": "Picasso",
"category": "Android",
"comment": "A powerful image downloading and caching library for Android",
"groupid": "com.squareup.picasso",
"artefactid": "picasso",
"repo": "mvn-central"
},{
"name": "Fresco",
"category": "Android",
"comment": "An Android library for managing images and the memory they use. (http://frescolib.org)",
"groupid": "com.facebook.fresco",
"artefactid": "fresco",
"repo": "mvn-central"
},{
"name": "RxAndroid",
"category": "Android",
"comment": "Android specific bindings for RxJava",
"groupid": "io.reactivex",
"artefactid": "rxandroid",
"repo": "mvn-central"
},{
"name": "glide",
"category": "Android",
"comment": "An image loading and caching library for Android focused on smooth scrolling",
"groupid": "com.github.bumptech.glide",
"artefactid": "glide",
"repo": "mvn-central"
},{
"name": "New Relic",
"category": "Android",
"comment": "Mobile app performance monitoring",
"groupid": "com.newrelic.agent.android",
"artefactid": "android-agent",
"repo": "mvn-central"
},{
"name": "ACRA",
"category": "Android",
"comment": "Application Crash Reports for Android",
"groupid": "ch.acra",
"artefactid": "acra",
"repo": "mvn-central"
},{
"name": "Dexter",
"category": "Android",
"comment": "Dexter is an Android library that simplifies the process of requesting permissions at runtime",
"groupid": "com.karumi",
"artefactid": "dexter",
"repo": "mvn-central"
},{
"name": "universal image loader",
"category": "Android",
"comment": "Powerful and flexible library for loading, caching and displaying images on Android.",
"groupid": "com.nostra13.universalimageloader",
"artefactid": "universal-image-loader",
"repo": "mvn-central"
},{
"name": "CleverTap",
"category": "Android",
"comment": "App Personalization and Engagement",
"groupid": "com.clevertap.android",
"artefactid": "clevertap-android-sdk",
"repo": "mvn-central"
},{
"name": "Ticker",
"category": "Android",
"comment": "Android text view with scrolling text change animation",
"groupid": "com.robinhood.ticker",
"artefactid": "ticker",
"repo": "mvn-central"
},{
"name": "MaterialEditText",
"category": "Android",
"comment": "EditText in Material Design",
"groupid": "com.rengwuxian.materialedittext",
"artefactid": "library",
"repo": "mvn-central"
},{
"name": "Calligraphy",
"category": "Android",
"comment": "Custom fonts in Android the easy way",
"groupid": "uk.co.chrisjenx",
"artefactid": "calligraphy",
"repo": "mvn-central"
},{
"name": "Ion",
"category": "Android",
"comment": "Android Asynchronous Networking and Image Loading",
"groupid": "com.koushikdutta.ion",
"artefactid": "ion",
"repo": "mvn-central"
},{
"name": "AndroidAsync",
"category": "Android",
"comment": "low level network protocol library",
"groupid": "com.koushikdutta.async",
"artefactid": "androidasync",
"repo": "mvn-central"
},{
"name": "uCrop",
"category": "Android",
"comment": "Image Cropping Library for Android",
"groupid": "com.yalantis",
"artefactid": "ucrop",
"repo": "mvn-central"
},{
"name": "android-gif-drawable",
"category": "Android",
"comment": "Views and Drawable for displaying animated GIFs on Android",
"groupid": "pl.droidsonroids.gif",
"artefactid": "android-gif-drawable",
"repo": "mvn-central"
},{
"name": "AirMapView",
"category": "Android",
"comment": "A view abstraction to provide a map user interface with various underlying map providers",
"groupid": "com.airbnb.android",
"artefactid": "airmapview",
"repo": "mvn-central"
},{
"name": "Logger",
"category": "Android",
"comment": "Simple, pretty and powerful logger for Android",
"groupid": "com.orhanobut",
"artefactid": "logger",
"repo": "mvn-central"
},{
"name": "RxAndroidBle",
"category": "Android",
"comment": "RxAndroidBle is a powerful painkiller for Android's Bluetooth Low Energy headaches.",
"groupid": "com.polidea.rxandroidble",
"artefactid": "rxandroidble",
"repo": "mvn-central"
},{
"name": "Sensey",
"category": "Android",
"comment": "Detecting gestures in a snap",
"groupid": "com.github.nisrulz",
"artefactid": "sensey",
"repo": "mvn-central"
},{
"name": "Material Calendar View",
"category": "Android",
"comment": "A Material design back port of Android's CalendarView",
"groupid": "com.prolificinteractive",
"artefactid": "material-calendarview",
"repo": "mvn-central"
},{
"name": "TimesSquare",
"category": "Android",
"comment": "Standalone Android widget for picking a single date from a calendar view.",
"groupid": "com.squareup",
"artefactid": "android-times-square",
"repo": "mvn-central"
},{
"name": "Stetho",
"category": "Android",
"comment": "A debug bridge for Android applications",
"groupid": "com.facebook.stetho",
"artefactid": "stetho",
"repo": "mvn-central"
},{
"name": "Android Beacon Library",
"category": "Android",
"comment": "An Android library providing APIs to interact with BLE beacons",
"groupid": "org.altbeacon",
"artefactid": "android-beacon-library",
"repo": "mvn-central"
},{
"name": "EventBus",
"category": "Android",
"comment": "EventBus is a publish/subscribe event bus optimized for Android",
"groupid": "org.greenrobot",
"artefactid": "eventbus",
"repo": "mvn-central"
},{
"name": "Butter Knife",
"category": "Android",
"comment": "Bind Android views and callbacks to fields and methods",
"groupid": "com.jakewharton",
"artefactid": "butterknife",
"repo": "mvn-central"
},
{
"name": "Crittercism",
"category": "Analytics",
"comment": "",
"groupid": "com.crittercism",
"artefactid": "crittercism-android-agent",
"repo": "mvn-central"
},{
"name": "HockeyApp",
"category": "Analytics",
"comment": "Microsoft HockeyApp: http://hockeyapp.net/features/",
"groupid": "net.hockeyapp.android",
"artefactid": "HockeySDK",
"repo": "mvn-central"
},{
"name": "Segment",
"category": "Analytics",
"comment": "",
"groupid": "com.segment.analytics.android",
"artefactid": "analytics-core",
"repo": "mvn-central"
},{
"name": "Segment",
"category": "Analytics",
"comment": "",
"groupid": "com.segment.analytics.android",
"artefactid": "analytics",
"repo": "mvn-central"
},{
"name": "Urban Airship",
"category": "Analytics",
"comment": "",
"groupid": "com.urbanairship.android",
"artefactid": "urbanairship-sdk",
"repo": "mvn-central"
},{
"name": "AppsFlyer",
"category": "Analytics",
"comment": "Mobile Attribution and Marketing Analytics",
"groupid": "com.appsflyer",
"artefactid": "af-android-sdk",
"repo": "mvn-central"
},
{
"name": "Parse",
"category": "Cloud",
"comment": "",
"groupid": "com.parse",
"artefactid": "parse-android",
"repo": "mvn-central"
},{
"name": "OneDrive",
"category": "Cloud",
"comment": "Microsoft OneDrive",
"groupid": "com.onedrive.sdk",
"artefactid": "onedrive-sdk-android",
"repo": "mvn-central"
},{
"name": "Dropbox",
"category": "Cloud",
"comment": "A Java library to access Dropbox's HTTP-based Core API v2",
"groupid": "com.dropbox.core",
"artefactid": "dropbox-core-sdk",
"repo": "mvn-central"
},{
"name": "Twilio",
"category": "Cloud",
"comment": "High-quality VoIP calling for web and mobile apps",
"groupid": "com.twilio.sdk",
"artefactid": "twilio-java-sdk",
"repo": "mvn-central"
},
{
"name": "Facebook",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-android-sdk",
"repo": "mvn-central"
},{
"name": "Facebook Core",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-core",
"repo": "mvn-central"
},{
"name": "Facebook Login",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-login",
"repo": "mvn-central"
},{
"name": "Facebook Sharing",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-share",
"repo": "mvn-central"
},{
"name": "Facebook Places",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-places",
"repo": "mvn-central"
},{
"name": "Facebook Messenger",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-messenger",
"repo": "mvn-central"
},{
"name": "Facebook App Links",
"category": "SocialMedia",
"comment": "",
"groupid": "com.facebook.android",
"artefactid": "facebook-applinks",
"repo": "mvn-central"
},{
"name": "vkontakte",
"category": "SocialMedia",
"comment": "",
"groupid": "com.vk",
"artefactid": "androidsdk",
"repo": "mvn-central"
},{
"name": "flickrj",
"category": "SocialMedia",
"comment": "A Java Flickr API library built for POJO, Android and Google App Engine",
"groupid": "com.googlecode.flickrj-android",
"artefactid": "flickrj-android",
"repo": "mvn-central"
},{
"name": "twitter4j",
"category": "SocialMedia",
"comment": "Twitter4J is an unofficial Java library for the Twitter API",
"groupid": "org.twitter4j",
"artefactid": "twitter4j-core",
"repo": "mvn-central"
},
{
"name": "apache httpclient android",
"category": "Utilities",
"comment": "Apache HttpClient for Android",
"groupid": "org.apache.httpcomponents",
"artefactid": "httpclient-android",
"repo": "mvn-central"
},{
"name": "log4j",
"category": "Utilities",
"comment": "Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor",
"groupid": "org.apache.logging.log4j",
"artefactid": "log4j-core",
"repo": "mvn-central"
},{
"name": "MessagePack",
"category": "Utilities",
"comment": "MessagePack is an efficient binary serialization format",
"groupid": "org.msgpack",
"artefactid": "msgpack",
"repo": "mvn-central"
},{
"name": "roboguice",
"category": "Utilities",
"comment": "Google Guice on Android",
"groupid": "org.roboguice",
"artefactid": "roboguice",
"repo": "mvn-central"
},{
"name": "ScribeJava Core",
"category": "Utilities",
"comment": "Simple OAuth library for Java",
"groupid": "com.github.scribejava",
"artefactid": "scribejava-core",
"repo": "mvn-central"
},{
"name": "scribe",
"category": "Utilities",
"comment": "",
"groupid": "org.scribe",
"artefactid": "scribe",
"repo": "mvn-central"
},{
"name": "android-oauth-client",
"category": "Utilities",
"comment": "Android OAuth Client Library",
"groupid": "com.wu-man",
"artefactid": "android-oauth-client",
"repo": "mvn-central"
},{
"name": "MapBox",
"category": "Utilities",
"comment": "",
"groupid": "com.mapbox.mapboxsdk",
"artefactid": "mapbox-android-sdk",
"repo": "mvn-central"
},{
"name": "Paypal",
"category": "Utilities",
"comment": "",
"groupid": "com.paypal.sdk",
"artefactid": "paypal-android-sdk",
"repo": "mvn-central"
},{
"name": "braintree payments",
"category": "Utilities",
"comment": "",
"groupid": "com.braintreepayments.api",
"artefactid": "braintree",
"repo": "mvn-central"
},{
"name": "Guice",
"category": "Utilities",
"comment": "Guice is a lightweight dependency injection framework for Java 6+",
"groupid": "com.google.inject",
"artefactid": "guice",
"repo": "mvn-central"
},{
"name": "Guava",
"category": "Utilities",
"comment": "Google Core Libraries for Java 6+",
"groupid": "com.google.guava",
"artefactid": "guava",
"repo": "mvn-central"
},{
"name": "Gson",
"category": "Utilities",
"comment": "A Java serialization/deserialization library that can convert Java Objects into JSON and back.",
"groupid": "com.google.code.gson",
"artefactid": "gson",
"repo": "mvn-central"
},{
"name": "okio",
"category": "Utilities",
"comment": "fast I/O and resizable buffers",
"groupid": "com.squareup.okio",
"artefactid": "okio",
"repo": "mvn-central"
},{
"name": "leakcanary",
"category": "Utilities",
"comment": "A memory leak detection library for Android and Java",
"groupid": "com.squareup.leakcanary",
"artefactid": "leakcanary-android",
"repo": "mvn-central"
},{
"name": "Dagger",
"category": "Utilities",
"comment": "A fast dependency injector for Android and Java.",
"groupid": "com.squareup.dagger",
"artefactid": "dagger",
"repo": "mvn-central"
},{
"name": "Dagger2",
"category": "Utilities",
"comment": "A fast dependency injector for Android and Java.",
"groupid": "com.google.dagger",
"artefactid": "dagger",
"repo": "mvn-central"
},{
"name": "OkHttp",
"category": "Utilities",
"comment": "An HTTP and HTTP/2 client for Android and Java applications",
"groupid": "com.squareup.okhttp",
"artefactid": "okhttp",
"repo": "mvn-central"
},{
"name": "OkHttp",
"category": "Utilities",
"comment": "An HTTP and HTTP/2 client for Android and Java applications",
"groupid": "com.squareup.okhttp3",
"artefactid": "okhttp",
"repo": "mvn-central"
},{
"name": "Tape",
"category": "Utilities",
"comment": "A lightning fast, transactional, file-based FIFO for Android and Java.",
"groupid": "com.squareup",
"artefactid": "tape",
"repo": "mvn-central"
},{
"name": "Bolts",
"category": "Utilities",
"comment": "Bolts is a collection of low-level libraries designed to make developing mobile apps easier.",
"groupid": "com.parse.bolts",
"artefactid": "bolts-android",
"repo": "mvn-central"
},{
"name": "jsoup",
"category": "Utilities",
"comment": "Java HTML Parser",
"groupid": "org.jsoup",
"artefactid": "jsoup",
"repo": "mvn-central"
},{
"name": "Joda Time",
"category": "Utilities",
"comment": "Joda-Time is the widely used replacement for the Java date and time classes.",
"groupid": "joda-time",
"artefactid": "joda-time",
"repo": "mvn-central"
},{
"name": "FasterXML Jackson-Core",
"category": "Utilities",
"comment": "Core part of Jackson that defines Streaming API as well as basic shared abstractions",
"groupid": "com.fasterxml.jackson.core",
"artefactid": "jackson-core",
"repo": "mvn-central"
},{
"name": "OrmLite",
"category": "Utilities",
"comment": "Lightweight Object Relational Mapping (ORM)",
"groupid": "com.j256.ormlite",
"artefactid": "ormlite-android",
"repo": "mvn-central"
},{
"name": "mapsforge",
"category": "Utilities",
"comment": "Maps rendering based on OpenStreetMap data",
"groupid": "org.mapsforge",
"artefactid": "mapsforge-core",
"repo": "mvn-central"
},{
"name": "GeoJSON",
"category": "Utilities",
"comment": "A complete GeoJSON implementation for Android.",
"groupid": "com.cocoahero.android",
"artefactid": "geojson",
"repo": "mvn-central"
},{
"name": "Retrofit",
"category": "Utilities",
"comment": "Type-safe HTTP client for Android and Java",
"groupid": "com.squareup.retrofit",
"artefactid": "retrofit",
"repo": "mvn-central"
},{
"name": "Retrofit",
"category": "Utilities",
"comment": "Type-safe HTTP client for Android and Java",
"groupid": "com.squareup.retrofit2",
"artefactid": "retrofit",
"repo": "mvn-central"
},{
"name": "PrettyTime",
"category": "Utilities",
"comment": "PrettyTime is an OpenSource time formatting library",
"groupid": "org.ocpsoft.prettytime",
"artefactid": "prettytime",
"repo": "mvn-central"
},{
"name": "SQLCipher",
"category": "Utilities",
"comment": "Android SQLite API based on SQLCipher",
"groupid": "net.zetetic",
"artefactid": "android-database-sqlcipher",
"repo": "mvn-central"
},{
"name": "PubNub",
"category": "Utilities",
"comment": "Publish/Subscribe model for real-time data streaming and device signaling",
"groupid": "com.pubnub",
"artefactid": "pubnub",
"repo": "mvn-central"
},{
"name": "SQLBrite",
"category": "Utilities",
"comment": "A lightweight wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations.",
"groupid": "com.squareup.sqlbrite",
"artefactid": "sqlbrite",
"repo": "mvn-central"
},{
"name": "Retrolambda",
"category": "Utilities",
"comment": "Backport of Java 8's lambda expressions to Java 7, 6 and 5",
"groupid": "net.orfjackal.retrolambda",
"artefactid": "retrolambda",
"repo": "mvn-central"
},{
"name": "JSch",
"category": "Utilities",
"comment": "Java Secure Channel (JSch) is a pure Java implementation of SSH2.",
"groupid": "com.jcraft",
"artefactid": "jsch",
"repo": "mvn-central"
},{
"name": "RxJava",
"category": "Utilities",
"comment": "Reactive Extensions for the JVM",
"groupid": "io.reactivex.rxjava2",
"artefactid": "rxjava",
"repo": "mvn-central"
}
]
}
LibScout-2.3.2/src/ 0000775 0000000 0000000 00000000000 13424313624 0014033 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/android/ 0000775 0000000 0000000 00000000000 13424313624 0015453 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/android/content/ 0000775 0000000 0000000 00000000000 13424313624 0017125 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/android/content/res/ 0000775 0000000 0000000 00000000000 13424313624 0017716 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/android/content/res/AXmlResourceParser.java 0000664 0000000 0000000 00000057751 13424313624 0024326 0 ustar 00root root 0000000 0000000 /*
* Copyright 2008 Android4ME
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.res;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import org.xmlpull.v1.XmlPullParserException;
import android.util.TypedValue;
/**
* @author Dmitry Skiba
*
* Binary xml files parser.
*
* Parser has only two states:
* (1) Operational state, which parser obtains after first successful call
* to next() and retains until open(), close(), or failed call to next().
* (2) Closed state, which parser obtains after open(), close(), or failed
* call to next(). In this state methods return invalid values or throw exceptions.
*
* TODO:
* * check all methods in closed state
*
*/
public class AXmlResourceParser implements XmlResourceParser {
public AXmlResourceParser() {
resetEventInfo();
}
public void open(InputStream stream) {
close();
if (stream!=null) {
m_reader=new IntReader(stream,false);
}
}
public void close() {
if (!m_operational) {
return;
}
m_operational=false;
m_reader.close();
m_reader=null;
m_strings=null;
m_resourceIDs=null;
m_namespaces.reset();
resetEventInfo();
}
/////////////////////////////////// iteration
public int next() throws XmlPullParserException,IOException {
if (m_reader==null) {
throw new XmlPullParserException("Parser is not opened.",this,null);
}
try {
doNext();
return m_event;
}
catch (IOException e) {
close();
throw e;
}
}
public int nextToken() throws XmlPullParserException,IOException {
return next();
}
public int nextTag() throws XmlPullParserException,IOException {
int eventType=next();
if (eventType==TEXT && isWhitespace()) {
eventType=next();
}
if (eventType!=START_TAG && eventType!=END_TAG) {
throw new XmlPullParserException("Expected start or end tag.",this,null);
}
return eventType;
}
public String nextText() throws XmlPullParserException,IOException {
if(getEventType()!=START_TAG) {
throw new XmlPullParserException("Parser must be on START_TAG to read next text.",this,null);
}
int eventType=next();
if (eventType==TEXT) {
String result=getText();
eventType=next();
if (eventType!=END_TAG) {
throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.",this,null);
}
return result;
} else if (eventType==END_TAG) {
return "";
} else {
throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.",this,null);
}
}
public void require(int type,String namespace,String name) throws XmlPullParserException,IOException {
if (type!=getEventType() ||
(namespace!=null && !namespace.equals(getNamespace())) ||
(name!=null && !name.equals(getName())))
{
throw new XmlPullParserException(TYPES[type]+" is expected.",this,null);
}
}
public int getDepth() {
return m_namespaces.getDepth()-1;
}
public int getEventType() throws XmlPullParserException {
return m_event;
}
public int getLineNumber() {
return m_lineNumber;
}
public String getName() {
if (m_name==-1 || (m_event!=START_TAG && m_event!=END_TAG)) {
return null;
}
return m_strings.getString(m_name);
}
public String getText() {
if (m_name==-1 || m_event!=TEXT) {
return null;
}
return m_strings.getString(m_name);
}
public char[] getTextCharacters(int[] holderForStartAndLength) {
String text=getText();
if (text==null) {
return null;
}
holderForStartAndLength[0]=0;
holderForStartAndLength[1]=text.length();
char[] chars=new char[text.length()];
text.getChars(0,text.length(),chars,0);
return chars;
}
public String getNamespace() {
return m_strings.getString(m_namespaceUri);
}
public String getPrefix() {
int prefix=m_namespaces.findPrefix(m_namespaceUri);
return m_strings.getString(prefix);
}
public String getPositionDescription() {
return "XML line #"+getLineNumber();
}
public int getNamespaceCount(int depth) throws XmlPullParserException {
return m_namespaces.getAccumulatedCount(depth);
}
public String getNamespacePrefix(int pos) throws XmlPullParserException {
int prefix=m_namespaces.getPrefix(pos);
return m_strings.getString(prefix);
}
public String getNamespaceUri(int pos) throws XmlPullParserException {
int uri=m_namespaces.getUri(pos);
return m_strings.getString(uri);
}
/////////////////////////////////// attributes
public String getClassAttribute() {
if (m_classAttribute==-1) {
return null;
}
int offset=getAttributeOffset(m_classAttribute);
int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
}
public String getIdAttribute() {
if (m_idAttribute==-1) {
return null;
}
int offset=getAttributeOffset(m_idAttribute);
int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
}
public int getIdAttributeResourceValue(int defaultValue) {
if (m_idAttribute==-1) {
return defaultValue;
}
int offset=getAttributeOffset(m_idAttribute);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType!=TypedValue.TYPE_REFERENCE) {
return defaultValue;
}
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
public int getStyleAttribute() {
if (m_styleAttribute==-1) {
return 0;
}
int offset=getAttributeOffset(m_styleAttribute);
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
public int getAttributeCount() {
if (m_event!=START_TAG) {
return -1;
}
return m_attributes.length/ATTRIBUTE_LENGHT;
}
public String getAttributeNamespace(int index) {
int offset=getAttributeOffset(index);
int namespace=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI];
if (namespace==-1) {
return "";
}
return m_strings.getString(namespace);
}
public String getAttributePrefix(int index) {
int offset=getAttributeOffset(index);
int uri=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI];
int prefix=m_namespaces.findPrefix(uri);
if (prefix==-1) {
return "";
}
return m_strings.getString(prefix);
}
public String getAttributeName(int index) {
int offset=getAttributeOffset(index);
int name=m_attributes[offset+ATTRIBUTE_IX_NAME];
if (name==-1) {
return "";
}
return m_strings.getString(name);
}
public int getAttributeNameResource(int index) {
int offset=getAttributeOffset(index);
int name=m_attributes[offset+ATTRIBUTE_IX_NAME];
if (m_resourceIDs==null ||
name<0 || name>=m_resourceIDs.length)
{
return 0;
}
return m_resourceIDs[name];
}
public int getAttributeValueType(int index) {
int offset=getAttributeOffset(index);
return m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
}
public int getAttributeValueData(int index) {
int offset=getAttributeOffset(index);
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
public String getAttributeValue(int index) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType==TypedValue.TYPE_STRING) {
int valueString=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(valueString);
}
int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
return "";//TypedValue.coerceToString(valueType,valueData);
}
public boolean getAttributeBooleanValue(int index,boolean defaultValue) {
return getAttributeIntValue(index,defaultValue?1:0)!=0;
}
public float getAttributeFloatValue(int index,float defaultValue) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType==TypedValue.TYPE_FLOAT) {
int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
return Float.intBitsToFloat(valueData);
}
return defaultValue;
}
public int getAttributeIntValue(int index,int defaultValue) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType>=TypedValue.TYPE_FIRST_INT &&
valueType<=TypedValue.TYPE_LAST_INT)
{
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
public int getAttributeUnsignedIntValue(int index,int defaultValue) {
return getAttributeIntValue(index,defaultValue);
}
public int getAttributeResourceValue(int index,int defaultValue) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType==TypedValue.TYPE_REFERENCE) {
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
public String getAttributeValue(String namespace,String attribute) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return null;
}
return getAttributeValue(index);
}
public boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeBooleanValue(index,defaultValue);
}
public float getAttributeFloatValue(String namespace,String attribute,float defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeFloatValue(index,defaultValue);
}
public int getAttributeIntValue(String namespace,String attribute,int defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeIntValue(index,defaultValue);
}
public int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeUnsignedIntValue(index,defaultValue);
}
public int getAttributeResourceValue(String namespace,String attribute,int defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeResourceValue(index,defaultValue);
}
public int getAttributeListValue(int index,String[] options,int defaultValue) {
// TODO implement
return 0;
}
public int getAttributeListValue(String namespace,String attribute,String[] options,int defaultValue) {
// TODO implement
return 0;
}
public String getAttributeType(int index) {
return "CDATA";
}
public boolean isAttributeDefault(int index) {
return false;
}
/////////////////////////////////// dummies
public void setInput(InputStream stream,String inputEncoding) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public void setInput(Reader reader) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public String getInputEncoding() {
return null;
}
public int getColumnNumber() {
return -1;
}
public boolean isEmptyElementTag() throws XmlPullParserException {
return false;
}
public boolean isWhitespace() throws XmlPullParserException {
return false;
}
public void defineEntityReplacementText(String entityName,String replacementText) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public String getNamespace(String prefix) {
throw new RuntimeException(E_NOT_SUPPORTED);
}
public Object getProperty(String name) {
return null;
}
public void setProperty(String name,Object value) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public boolean getFeature(String feature) {
return false;
}
public void setFeature(String name,boolean value) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
///////////////////////////////////////////// implementation
/**
* Namespace stack, holds prefix+uri pairs, as well as
* depth information.
* All information is stored in one int[] array.
* Array consists of depth frames:
* Data=DepthFrame*;
* DepthFrame=Count+[Prefix+Uri]*+Count;
* Count='count of Prefix+Uri pairs';
* Yes, count is stored twice, to enable bottom-up traversal.
* increaseDepth adds depth frame, decreaseDepth removes it.
* push/pop operations operate only in current depth frame.
* decreaseDepth removes any remaining (not pop'ed) namespace pairs.
* findXXX methods search all depth frames starting
* from the last namespace pair of current depth frame.
* All functions that operate with int, use -1 as 'invalid value'.
*
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
*
*/
private static final class NamespaceStack {
public NamespaceStack() {
m_data=new int[32];
}
public final void reset() {
m_dataLength=0;
m_count=0;
m_depth=0;
}
public final int getTotalCount() {
return m_count;
}
public final int getCurrentCount() {
if (m_dataLength==0) {
return 0;
}
int offset=m_dataLength-1;
return m_data[offset];
}
public final int getAccumulatedCount(int depth) {
if (m_dataLength==0 || depth<0) {
return 0;
}
if (depth>m_depth) {
depth=m_depth;
}
int accumulatedCount=0;
int offset=0;
for (;depth!=0;--depth) {
int count=m_data[offset];
accumulatedCount+=count;
offset+=(2+count*2);
}
return accumulatedCount;
}
public final void push(int prefix,int uri) {
if (m_depth==0) {
increaseDepth();
}
ensureDataCapacity(2);
int offset=m_dataLength-1;
int count=m_data[offset];
m_data[offset-1-count*2]=count+1;
m_data[offset]=prefix;
m_data[offset+1]=uri;
m_data[offset+2]=count+1;
m_dataLength+=2;
m_count+=1;
}
public final boolean pop(int prefix,int uri) {
if (m_dataLength==0) {
return false;
}
int offset=m_dataLength-1;
int count=m_data[offset];
for (int i=0,o=offset-2;i!=count;++i,o-=2) {
if (m_data[o]!=prefix || m_data[o+1]!=uri) {
continue;
}
count-=1;
if (i==0) {
m_data[o]=count;
o-=(1+count*2);
m_data[o]=count;
} else {
m_data[offset]=count;
offset-=(1+2+count*2);
m_data[offset]=count;
System.arraycopy(
m_data,o+2,
m_data,o,
m_dataLength-o);
}
m_dataLength-=2;
m_count-=1;
return true;
}
return false;
}
public final boolean pop() {
if (m_dataLength==0) {
return false;
}
int offset=m_dataLength-1;
int count=m_data[offset];
if (count==0) {
return false;
}
count-=1;
offset-=2;
m_data[offset]=count;
offset-=(1+count*2);
m_data[offset]=count;
m_dataLength-=2;
m_count-=1;
return true;
}
public final int getPrefix(int index) {
return get(index,true);
}
public final int getUri(int index) {
return get(index,false);
}
public final int findPrefix(int uri) {
return find(uri,false);
}
public final int findUri(int prefix) {
return find(prefix,true);
}
public final int getDepth() {
return m_depth;
}
public final void increaseDepth() {
ensureDataCapacity(2);
int offset=m_dataLength;
m_data[offset]=0;
m_data[offset+1]=0;
m_dataLength+=2;
m_depth+=1;
}
public final void decreaseDepth() {
if (m_dataLength==0) {
return;
}
int offset=m_dataLength-1;
int count=m_data[offset];
if ((offset-1-count*2)==0) {
return;
}
m_dataLength-=2+count*2;
m_count-=count;
m_depth-=1;
}
private void ensureDataCapacity(int capacity) {
int available=(m_data.length-m_dataLength);
if (available>capacity) {
return;
}
int newLength=(m_data.length+available)*2;
int[] newData=new int[newLength];
System.arraycopy(m_data,0,newData,0,m_dataLength);
m_data=newData;
}
private final int find(int prefixOrUri,boolean prefix) {
if (m_dataLength==0) {
return -1;
}
int offset=m_dataLength-1;
for (int i=m_depth;i!=0;--i) {
int count=m_data[offset];
offset-=2;
for (;count!=0;--count) {
if (prefix) {
if (m_data[offset]==prefixOrUri) {
return m_data[offset+1];
}
} else {
if (m_data[offset+1]==prefixOrUri) {
return m_data[offset];
}
}
offset-=2;
}
}
return -1;
}
private final int get(int index,boolean prefix) {
if (m_dataLength==0 || index<0) {
return -1;
}
int offset=0;
for (int i=m_depth;i!=0;--i) {
int count=m_data[offset];
if (index>=count) {
index-=count;
offset+=(2+count*2);
continue;
}
offset+=(1+index*2);
if (!prefix) {
offset+=1;
}
return m_data[offset];
}
return -1;
}
private int[] m_data;
private int m_dataLength;
private int m_count;
private int m_depth;
}
/////////////////////////////////// package-visible
// final void fetchAttributes(int[] styleableIDs,TypedArray result) {
// result.resetIndices();
// if (m_attributes==null || m_resourceIDs==null) {
// return;
// }
// boolean needStrings=false;
// for (int i=0,e=styleableIDs.length;i!=e;++i) {
// int id=styleableIDs[i];
// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) {
// int name=m_attributes[o+ATTRIBUTE_IX_NAME];
// if (name>=m_resourceIDs.length ||
// m_resourceIDs[name]!=id)
// {
// continue;
// }
// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE];
// int valueData;
// int assetCookie;
// if (valueType==TypedValue.TYPE_STRING) {
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING];
// assetCookie=-1;
// needStrings=true;
// } else {
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA];
// assetCookie=0;
// }
// result.addValue(i,valueType,valueData,assetCookie,id,0);
// }
// }
// if (needStrings) {
// result.setStrings(m_strings);
// }
// }
final StringBlock getStrings() {
return m_strings;
}
///////////////////////////////////
private final int getAttributeOffset(int index) {
if (m_event!=START_TAG) {
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
}
int offset=index*5;
if (offset>=m_attributes.length) {
throw new IndexOutOfBoundsException("Invalid attribute index ("+index+").");
}
return offset;
}
private final int findAttribute(String namespace,String attribute) {
if (m_strings==null || attribute==null) {
return -1;
}
int name=m_strings.find(attribute);
if (name==-1) {
return -1;
}
int uri=(namespace!=null)?
m_strings.find(namespace):
-1;
for (int o=0;o!=m_attributes.length;++o) {
if (name==m_attributes[o+ATTRIBUTE_IX_NAME] &&
(uri==-1 || uri==m_attributes[o+ATTRIBUTE_IX_NAMESPACE_URI]))
{
return o/ATTRIBUTE_LENGHT;
}
}
return -1;
}
private final void resetEventInfo() {
m_event=-1;
m_lineNumber=-1;
m_name=-1;
m_namespaceUri=-1;
m_attributes=null;
m_idAttribute=-1;
m_classAttribute=-1;
m_styleAttribute=-1;
}
private final void doNext() throws IOException {
// Delayed initialization.
if (m_strings==null) {
ChunkUtil.readCheckType(m_reader,CHUNK_AXML_FILE);
/*chunkSize*/m_reader.skipInt();
m_strings=StringBlock.read(m_reader);
m_namespaces.increaseDepth();
m_operational=true;
}
if (m_event==END_DOCUMENT) {
return;
}
int event=m_event;
resetEventInfo();
while (true) {
if (m_decreaseDepth) {
m_decreaseDepth=false;
m_namespaces.decreaseDepth();
}
// Fake END_DOCUMENT event.
if (event==END_TAG &&
m_namespaces.getDepth()==1 &&
m_namespaces.getCurrentCount()==0)
{
m_event=END_DOCUMENT;
break;
}
int chunkType;
if (event==START_DOCUMENT) {
// Fake event, see CHUNK_XML_START_TAG handler.
chunkType=CHUNK_XML_START_TAG;
} else {
chunkType=m_reader.readInt();
}
if (chunkType==CHUNK_RESOURCEIDS) {
int chunkSize=m_reader.readInt();
if (chunkSize<8 || (chunkSize%4)!=0) {
throw new IOException("Invalid resource ids size ("+chunkSize+").");
}
m_resourceIDs=m_reader.readIntArray(chunkSize/4-2);
continue;
}
if (chunkTypeCHUNK_XML_LAST) {
throw new IOException("Invalid chunk type ("+chunkType+").");
}
// Fake START_DOCUMENT event.
if (chunkType==CHUNK_XML_START_TAG && event==-1) {
m_event=START_DOCUMENT;
break;
}
// Common header.
/*chunkSize*/m_reader.skipInt();
int lineNumber=m_reader.readInt();
/*0xFFFFFFFF*/m_reader.skipInt();
if (chunkType==CHUNK_XML_START_NAMESPACE ||
chunkType==CHUNK_XML_END_NAMESPACE)
{
if (chunkType==CHUNK_XML_START_NAMESPACE) {
int prefix=m_reader.readInt();
int uri=m_reader.readInt();
m_namespaces.push(prefix,uri);
} else {
/*prefix*/m_reader.skipInt();
/*uri*/m_reader.skipInt();
m_namespaces.pop();
}
continue;
}
m_lineNumber=lineNumber;
if (chunkType==CHUNK_XML_START_TAG) {
m_namespaceUri=m_reader.readInt();
m_name=m_reader.readInt();
/*flags?*/m_reader.skipInt();
int attributeCount=m_reader.readInt();
m_idAttribute=(attributeCount>>>16)-1;
attributeCount&=0xFFFF;
m_classAttribute=m_reader.readInt();
m_styleAttribute=(m_classAttribute>>>16)-1;
m_classAttribute=(m_classAttribute & 0xFFFF)-1;
m_attributes=m_reader.readIntArray(attributeCount*ATTRIBUTE_LENGHT);
for (int i=ATTRIBUTE_IX_VALUE_TYPE;i>>24);
i+=ATTRIBUTE_LENGHT;
}
m_namespaces.increaseDepth();
m_event=START_TAG;
break;
}
if (chunkType==CHUNK_XML_END_TAG) {
m_namespaceUri=m_reader.readInt();
m_name=m_reader.readInt();
m_event=END_TAG;
m_decreaseDepth=true;
break;
}
if (chunkType==CHUNK_XML_TEXT) {
m_name=m_reader.readInt();
/*?*/m_reader.skipInt();
/*?*/m_reader.skipInt();
m_event=TEXT;
break;
}
}
}
/////////////////////////////////// data
/*
* All values are essentially indices, e.g. m_name is
* an index of name in m_strings.
*/
private IntReader m_reader;
private boolean m_operational=false;
private StringBlock m_strings;
private int[] m_resourceIDs;
private NamespaceStack m_namespaces=new NamespaceStack();
private boolean m_decreaseDepth;
private int m_event;
private int m_lineNumber;
private int m_name;
private int m_namespaceUri;
private int[] m_attributes;
private int m_idAttribute;
private int m_classAttribute;
private int m_styleAttribute;
private static final String
E_NOT_SUPPORTED ="Method is not supported.";
private static final int
ATTRIBUTE_IX_NAMESPACE_URI =0,
ATTRIBUTE_IX_NAME =1,
ATTRIBUTE_IX_VALUE_STRING =2,
ATTRIBUTE_IX_VALUE_TYPE =3,
ATTRIBUTE_IX_VALUE_DATA =4,
ATTRIBUTE_LENGHT =5;
private static final int
CHUNK_AXML_FILE =0x00080003,
CHUNK_RESOURCEIDS =0x00080180,
CHUNK_XML_FIRST =0x00100100,
CHUNK_XML_START_NAMESPACE =0x00100100,
CHUNK_XML_END_NAMESPACE =0x00100101,
CHUNK_XML_START_TAG =0x00100102,
CHUNK_XML_END_TAG =0x00100103,
CHUNK_XML_TEXT =0x00100104,
CHUNK_XML_LAST =0x00100104;
}
LibScout-2.3.2/src/android/content/res/ChunkUtil.java 0000664 0000000 0000000 00000002025 13424313624 0022466 0 ustar 00root root 0000000 0000000 /*
* Copyright 2008 Android4ME
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.res;
import java.io.IOException;
/**
* @author Dmitry Skiba
*
*/
class ChunkUtil {
public static final void readCheckType(IntReader reader,int expectedType) throws IOException {
int type=reader.readInt();
if (type!=expectedType) {
throw new IOException(
"Expected chunk of type 0x"+Integer.toHexString(expectedType)+
", read 0x"+Integer.toHexString(type)+".");
}
}
}
LibScout-2.3.2/src/android/content/res/IntReader.java 0000664 0000000 0000000 00000006663 13424313624 0022451 0 ustar 00root root 0000000 0000000 /*
* Copyright 2008 Android4ME
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.res;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Dmitry Skiba
*
* Simple helper class that allows reading of integers.
*
* TODO:
* * implement buffering
*
*/
public final class IntReader {
public IntReader() {
}
public IntReader(InputStream stream,boolean bigEndian) {
reset(stream,bigEndian);
}
public final void reset(InputStream stream,boolean bigEndian) {
m_stream=stream;
m_bigEndian=bigEndian;
m_position=0;
}
public final void close() {
if (m_stream==null) {
return;
}
try {
m_stream.close();
}
catch (IOException e) {
}
reset(null,false);
}
public final InputStream getStream() {
return m_stream;
}
public final boolean isBigEndian() {
return m_bigEndian;
}
public final void setBigEndian(boolean bigEndian) {
m_bigEndian=bigEndian;
}
public final int readByte() throws IOException {
return readInt(1);
}
public final int readShort() throws IOException {
return readInt(2);
}
public final int readInt() throws IOException {
return readInt(4);
}
public final int readInt(int length) throws IOException {
if (length<0 || length>4) {
throw new IllegalArgumentException();
}
int result=0;
if (m_bigEndian) {
for (int i=(length-1)*8;i>=0;i-=8) {
int b=m_stream.read();
if (b==-1) {
throw new EOFException();
}
m_position+=1;
result|=(b<0;length-=1) {
array[offset++]=readInt();
}
}
public final byte[] readByteArray(int length) throws IOException {
byte[] array=new byte[length];
int read=m_stream.read(array);
m_position+=read;
if (read!=length) {
throw new EOFException();
}
return array;
}
public final void skip(int bytes) throws IOException {
if (bytes<=0) {
return;
}
long skipped=m_stream.skip(bytes);
m_position+=skipped;
if (skipped!=bytes) {
throw new EOFException();
}
}
public final void skipInt() throws IOException {
skip(4);
}
public final int available() throws IOException {
return m_stream.available();
}
public final int getPosition() {
return m_position;
}
/////////////////////////////////// data
private InputStream m_stream;
private boolean m_bigEndian;
private int m_position;
}
LibScout-2.3.2/src/android/content/res/StringBlock.java 0000664 0000000 0000000 00000013317 13424313624 0023007 0 ustar 00root root 0000000 0000000 /*
* Copyright 2008 Android4ME
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.res;
import java.io.IOException;
/**
* @author Dmitry Skiba
*
* Block of strings, used in binary xml and arsc.
*
* TODO:
* - implement get()
*
*/
public class StringBlock {
/**
* Reads whole (including chunk type) string block from stream.
* Stream must be at the chunk type.
*/
public static StringBlock read(IntReader reader) throws IOException {
ChunkUtil.readCheckType(reader,CHUNK_TYPE);
int chunkSize=reader.readInt();
int stringCount=reader.readInt();
int styleOffsetCount=reader.readInt();
/*?*/reader.readInt();
int stringsOffset=reader.readInt();
int stylesOffset=reader.readInt();
StringBlock block=new StringBlock();
block.m_stringOffsets=reader.readIntArray(stringCount);
if (styleOffsetCount!=0) {
block.m_styleOffsets=reader.readIntArray(styleOffsetCount);
}
{
int size=((stylesOffset==0)?chunkSize:stylesOffset)-stringsOffset;
if ((size%4)!=0) {
throw new IOException("String data size is not multiple of 4 ("+size+").");
}
block.m_strings=reader.readIntArray(size/4);
}
if (stylesOffset!=0) {
int size=(chunkSize-stylesOffset);
if ((size%4)!=0) {
throw new IOException("Style data size is not multiple of 4 ("+size+").");
}
block.m_styles=reader.readIntArray(size/4);
}
return block;
}
/**
* Returns number of strings in block.
*/
public int getCount() {
return m_stringOffsets!=null?
m_stringOffsets.length:
0;
}
/**
* Returns raw string (without any styling information) at specified index.
*/
public String getString(int index) {
if (index<0 ||
m_stringOffsets==null ||
index>=m_stringOffsets.length)
{
return null;
}
int offset=m_stringOffsets[index];
int length=getShort(m_strings,offset);
StringBuilder result=new StringBuilder(length);
for (;length!=0;length-=1) {
offset+=2;
result.append((char)getShort(m_strings,offset));
}
return result.toString();
}
/**
* Not yet implemented.
*
* Returns string with style information (if any).
*/
public CharSequence get(int index) {
return getString(index);
}
/**
* Returns string with style tags (html-like).
*/
public String getHTML(int index) {
String raw=getString(index);
if (raw==null) {
return raw;
}
int[] style=getStyle(index);
if (style==null) {
return raw;
}
StringBuilder html=new StringBuilder(raw.length()+32);
int offset=0;
while (true) {
int i=-1;
for (int j=0;j!=style.length;j+=3) {
if (style[j+1]==-1) {
continue;
}
if (i==-1 || style[i+1]>style[j+1]) {
i=j;
}
}
int start=((i!=-1)?style[i+1]:raw.length());
for (int j=0;j!=style.length;j+=3) {
int end=style[j+2];
if (end==-1 || end>=start) {
continue;
}
if (offset<=end) {
html.append(raw,offset,end+1);
offset=end+1;
}
style[j+2]=-1;
html.append('<');
html.append('/');
html.append(getString(style[j]));
html.append('>');
}
if (offset');
style[i+1]=-1;
}
return html.toString();
}
/**
* Finds index of the string.
* Returns -1 if the string was not found.
*/
public int find(String string) {
if (string==null) {
return -1;
}
for (int i=0;i!=m_stringOffsets.length;++i) {
int offset=m_stringOffsets[i];
int length=getShort(m_strings,offset);
if (length!=string.length()) {
continue;
}
int j=0;
for (;j!=length;++j) {
offset+=2;
if (string.charAt(j)!=getShort(m_strings,offset)) {
break;
}
}
if (j==length) {
return i;
}
}
return -1;
}
///////////////////////////////////////////// implementation
private StringBlock() {
}
/**
* Returns style information - array of int triplets,
* where in each triplet:
* * first int is index of tag name ('b','i', etc.)
* * second int is tag start index in string
* * third int is tag end index in string
*/
private int[] getStyle(int index) {
if (m_styleOffsets==null || m_styles==null ||
index>=m_styleOffsets.length)
{
return null;
}
int offset=m_styleOffsets[index]/4;
int style[];
{
int count=0;
for (int i=offset;i>> 16);
}
}
private int[] m_stringOffsets;
private int[] m_strings;
private int[] m_styleOffsets;
private int[] m_styles;
private static final int CHUNK_TYPE=0x001C0001;
}
LibScout-2.3.2/src/com/ 0000775 0000000 0000000 00000000000 13424313624 0014611 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/com/googlecode/ 0000775 0000000 0000000 00000000000 13424313624 0016720 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/com/googlecode/dex2jar/ 0000775 0000000 0000000 00000000000 13424313624 0020257 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/com/googlecode/dex2jar/reader/ 0000775 0000000 0000000 00000000000 13424313624 0021521 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/ 0000775 0000000 0000000 00000000000 13424313624 0022130 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/ArrayDataIn.java 0000664 0000000 0000000 00000006117 13424313624 0025137 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2009-2012 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.dex2jar.reader.io;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Stack;
/**
*
* @author Panxiaobo
* @version $Rev: 9fd8005bbaa4 $
*/
public abstract class ArrayDataIn extends ByteArrayInputStream implements DataIn {
private Stack stack = new Stack();
public ArrayDataIn(byte[] data) {
super(data);
}
@Override
public int readShortx() {
return (short) readUShortx();
}
@Override
public int readIntx() {
return readUIntx();
}
@Override
public int getCurrentPosition() {
return super.pos;
}
@Override
public void move(int absOffset) {
super.pos = absOffset;
}
@Override
public void pop() {
super.pos = stack.pop();
}
@Override
public void push() {
stack.push(super.pos);
}
@Override
public void pushMove(int absOffset) {
this.push();
this.move(absOffset);
}
@Override
public byte[] readBytes(int size) {
byte[] data = new byte[size];
try {
super.read(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
return data;
}
@Override
public long readLeb128() {
int bitpos = 0;
long vln = 0L;
do {
int inp = readUByte();
vln |= ((long) (inp & 0x7F)) << bitpos;
bitpos += 7;
if ((inp & 0x80) == 0) {
break;
}
} while (true);
if (((1L << (bitpos - 1)) & vln) != 0) {
vln -= (1L << bitpos);
}
return vln;
}
@Override
public long readULeb128() {
long value = 0;
int count = 0;
int b = readUByte();
while ((b & 0x80) != 0) {
value |= (b & 0x7f) << count;
count += 7;
b = readUByte();
}
value |= (b & 0x7f) << count;
return value;
}
@Override
public void skip(int bytes) {
super.skip(bytes);
}
@Override
public int readByte() {
return (byte) readUByte();
}
@Override
public int readUByte() {
if (super.pos >= super.count) {
throw new RuntimeException("EOF");
}
return super.read();
}
}
LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/DataIn.java 0000664 0000000 0000000 00000003054 13424313624 0024135 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2009-2012 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.dex2jar.reader.io;
/**
* 输入流
*
* @author Panxiaobo
* @version $Rev: 3b20152caede $
*/
public interface DataIn {
/**
* 获取当前位置
*
* @return
*/
int getCurrentPosition();
void move(int absOffset);
void pop();
void push();
/**
* equals to
*
*
* push();
* move(absOffset);
*
*
* @see #push()
* @see #move(int)
* @param absOffset
*/
void pushMove(int absOffset);
/**
*
*/
int readByte();
byte[] readBytes(int size);
int readIntx();
int readUIntx();
int readShortx();
int readUShortx();
long readLeb128();
/**
* @return
*/
int readUByte();
long readULeb128();
/**
* @param i
*/
void skip(int bytes);
}
LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/DataOut.java 0000664 0000000 0000000 00000000462 13424313624 0024336 0 ustar 00root root 0000000 0000000 package com.googlecode.dex2jar.reader.io;
import java.io.IOException;
public interface DataOut {
void writeBytes(byte[] bs) throws IOException;
void writeByte(int b) throws IOException;
void writeInt(int i) throws IOException;
void writeShort(int i) throws IOException;
}
LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/LeArrayDataIn.java 0000664 0000000 0000000 00000002165 13424313624 0025417 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2009-2012 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.dex2jar.reader.io;
/**
* @see DexFileReader#ENDIAN_CONSTANT
* @author Panxiaobo
*
*/
public class LeArrayDataIn extends ArrayDataIn implements DataIn {
public LeArrayDataIn(byte[] data) {
super(data);
}
@Override
public int readUShortx() {
return readUByte() | (readUByte() << 8);
}
@Override
public int readUIntx() {
return readUByte() | (readUByte() << 8) | (readUByte() << 16) | (readUByte() << 24);
}
}
LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/LeDataOut.java 0000664 0000000 0000000 00000001437 13424313624 0024622 0 ustar 00root root 0000000 0000000 package com.googlecode.dex2jar.reader.io;
import java.io.IOException;
import java.io.OutputStream;
public class LeDataOut implements DataOut {
public LeDataOut(OutputStream os) {
super();
this.os = os;
}
private OutputStream os;
@Override
public void writeByte(int v) throws IOException {
os.write(v);
}
@Override
public void writeShort(int v) throws IOException {
os.write(v);
os.write(v >> 8);
}
@Override
public void writeInt(int v) throws IOException {
os.write(v);
os.write(v >> 8);
os.write(v >> 16);
os.write(v >>> 24);
}
@Override
public void writeBytes(byte[] bs)throws IOException {
os.write(bs);
}
}
LibScout-2.3.2/src/de/ 0000775 0000000 0000000 00000000000 13424313624 0014423 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/ 0000775 0000000 0000000 00000000000 13424313624 0015672 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/ 0000775 0000000 0000000 00000000000 13424313624 0016471 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/TplCLI.java 0000664 0000000 0000000 00000046165 13424313624 0020437 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.infsec.tpl.config.LibScoutConfig;
import de.infsec.tpl.modules.libapi.LibraryApiAnalysis;
import de.infsec.tpl.modules.libmatch.LibraryIdentifier;
import de.infsec.tpl.modules.libprofiler.LibraryProfiler;
import de.infsec.tpl.modules.updatability.LibraryUpdatability;
import de.infsec.tpl.profile.Profile;
import de.infsec.tpl.stats.AppStats;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.profile.LibProfile;
public class TplCLI {
private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.TplCLI.class);
private static Options options;
public static class CliArgs {
static final String ARG_OPMODE = "o";
static final String ARGL_OPMODE = "opmode";
static final String ARG_CONFIG = "c";
public static final String ARGL_CONFIG = "libscout-conf";
static final String ARG_ANDROID_LIB = "a";
static final String ARGL_ANDROID_LIB = "android-sdk";
static final String ARG_LOG_DIR = "d";
static final String ARGL_LOG_DIR = "log-dir";
static final String ARG_STATS_DIR = "s";
static final String ARGL_STATS_DIR = "stats-dir";
static final String ARG_JSON_DIR = "j";
static final String ARGL_JSON_DIR = "json-dir";
static final String ARG_PROFILES_DIR = "p";
static final String ARGL_PROFILES_DIR = "profiles-dir";
static final String ARG_MUTE = "m";
static final String ARGL_MUTE = "mute";
static final String ARG_NO_PARTIAL_MATCHING = "n";
static final String ARGL_NO_PARTIAL_MATCHING = "no-partial-matching";
static final String ARG_LIB_USAGE_ANALYSIS = "u";
static final String ARGL_LIB_USAGE_ANALYSIS = "lib-usage-analysis";
static final String ARG_LIB_DESCRIPTION = "x";
static final String ARGL_LIB_DESCRIPTION = "library-description";
static final String ARG_LIB_VERBOSE_PROFILES = "v";
static final String ARGL_LIB_VERBOSE_PROFILES = "verbose-profiles";
static final String ARG_LIB_DEPENDENCY_ANALYSIS = "da";
static final String ARGL_LIB_DEPENDENCY_ANALYSIS = "lib-dependency-analysis";
static final String ARG_LIB_API_COMPAT_DIR = "l";
static final String ARGL_LIB_API_COMPAT_DIR = "lib-api-compat-dir";
}
private static ArrayList inputFiles;
private static File libraryDescription = null;
public static void main(String[] args) {
// parse command line arguments
parseCL(args);
List profiles = null;
LibraryUpdatability libUp = null;
try {
// parse LibScout.toml (args from CLI take precedence)
LibScoutConfig.loadConfig();
// sanity check for required options that can be set from both CLI/config file
checkRequiredOptions();
// initialize logback
initLogging();
LibScoutConfig.whoAmI();
/*
* one time data loading
*/
if (LibScoutConfig.opMatch() || LibScoutConfig.opUpdatability())
profiles = Profile.loadLibraryProfiles(LibScoutConfig.profilesDir);
if (LibScoutConfig.opUpdatability())
libUp = new LibraryUpdatability(LibScoutConfig.libApiCompatDir);
} catch (ParseException e) {
logger.error("Error: " + e.getMessage());
usage();
}
/*
* choose mode of operation
*/
// process input files, either library files or apps
for (File inputFile: inputFiles) {
try {
if (LibScoutConfig.opMatch()) {
LibraryIdentifier.run(inputFile, profiles, LibScoutConfig.runLibUsageAnalysis);
} else if (LibScoutConfig.opUpdatability()) {
AppStats stats = LibraryIdentifier.run(inputFile, profiles, true);
libUp.checkUpdatability(stats);
} else if (LibScoutConfig.opProfile()) {
LibraryProfiler.extractFingerPrints(inputFile, libraryDescription);
} else if (LibScoutConfig.opLibApiAnalysis()) {
LibraryApiAnalysis.run(inputFile);
}
} catch (Throwable t) {
logger.error("[FATAL " + (t instanceof Exception? "EXCEPTION" : "ERROR") + "] analysis aborted: " + t.getMessage());
logger.error(Utils.stacktrace2Str(t));
}
}
}
private static void parseCL(String[] args) {
try {
CommandLineParser parser = new BasicParser();
CommandLine cmd = parser.parse(setupOptions(), args);
// parse mode of operation
if (cmd.hasOption(CliArgs.ARG_OPMODE)) {
try {
LibScoutConfig.opmode = LibScoutConfig.OpMode.valueOf(cmd.getOptionValue(CliArgs.ARG_OPMODE).toUpperCase());
} catch (IllegalArgumentException e) {
throw new ParseException(Utils.stacktrace2Str(e));
}
} else
usage();
/*
* Logging options (apply to all modes, default settings: console logging, logdir="./logs")
* -m, disable logging (takes precedence over -d)
* -d [logdir], if provided without argument output is logged to default dir, otherwise to the provided dir
*/
if (cmd.hasOption(CliArgs.ARG_MUTE)) {
LibScoutConfig.logType = LibScoutConfig.LogType.NONE;
}
else if (cmd.hasOption(CliArgs.ARG_LOG_DIR)) {
LibScoutConfig.logType = LibScoutConfig.LogType.FILE;
if (cmd.getOptionValue(CliArgs.ARG_LOG_DIR) != null) { // we have a log dir
File logDir = new File(cmd.getOptionValue(CliArgs.ARG_LOG_DIR));
if (logDir.exists() && !logDir.isDirectory())
throw new ParseException("Log directory " + logDir + " already exists and is not a directory");
LibScoutConfig.logDir = logDir;
}
}
// path to Android SDK jar
if (checkOptionalUse(cmd, CliArgs.ARG_ANDROID_LIB, LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS, LibScoutConfig.OpMode.UPDATABILITY)) {
LibScoutConfig.pathToAndroidJar = new File(cmd.getOptionValue(CliArgs.ARG_ANDROID_LIB));
LibScoutConfig.checkIfValidFile(cmd.getOptionValue(CliArgs.ARG_ANDROID_LIB));
}
// path to LibScout.toml
if (checkOptionalUse(cmd, CliArgs.ARG_CONFIG, LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS, LibScoutConfig.OpMode.UPDATABILITY)) {
LibScoutConfig.libScoutConfigFileName = cmd.getOptionValue(CliArgs.ARG_CONFIG);
LibScoutConfig.checkIfValidFile(LibScoutConfig.libScoutConfigFileName);
}
// profiles dir option, if provided without argument output is written to default dir
if (checkOptionalUse(cmd, CliArgs.ARG_PROFILES_DIR, LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.UPDATABILITY)) {
File profilesDir = new File(cmd.getOptionValue(CliArgs.ARG_PROFILES_DIR));
if (profilesDir.exists() && !profilesDir.isDirectory())
throw new ParseException("Profiles directory " + profilesDir + " already exists and is not a directory");
LibScoutConfig.profilesDir = profilesDir;
}
// disable partial matching (full lib matching only)
if (checkOptionalUse(cmd, CliArgs.ARG_NO_PARTIAL_MATCHING, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.UPDATABILITY)) {
LibScoutConfig.noPartialMatching = true;
}
// run library usage analysis (for full matches only)
if (checkOptionalUse(cmd, CliArgs.ARG_LIB_USAGE_ANALYSIS, LibScoutConfig.OpMode.MATCH)) {
LibScoutConfig.runLibUsageAnalysis = true;
}
// provide library description file
if (checkRequiredUse(cmd, CliArgs.ARG_LIB_DESCRIPTION, LibScoutConfig.OpMode.PROFILE)) {
File libraryDescriptionFile = new File(cmd.getOptionValue(CliArgs.ARG_LIB_DESCRIPTION));
if (libraryDescriptionFile.exists() && libraryDescriptionFile.isDirectory())
throw new ParseException("Library description (" + libraryDescriptionFile + ") must not be a directory");
libraryDescription = libraryDescriptionFile;
}
// generate verbose library profiles?
if (checkOptionalUse(cmd, CliArgs.ARG_LIB_VERBOSE_PROFILES, LibScoutConfig.OpMode.PROFILE)) {
LibScoutConfig.genVerboseProfiles = true;
}
// enable library dependency analysis
if (checkOptionalUse(cmd, CliArgs.ARG_LIB_DEPENDENCY_ANALYSIS, LibScoutConfig.OpMode.LIB_API_ANALYSIS)) {
LibScoutConfig.libDependencyAnalysis = true;
}
// enable/disable generation of stats with optional stats directory
if (checkOptionalUse(cmd, CliArgs.ARG_STATS_DIR, LibScoutConfig.OpMode.MATCH)) {
LibScoutConfig.generateStats = true;
if (cmd.getOptionValue(CliArgs.ARG_STATS_DIR) != null) { // stats dir provided?
File statsDir = new File(cmd.getOptionValue(CliArgs.ARG_STATS_DIR));
if (statsDir.exists() && !statsDir.isDirectory())
throw new ParseException("Stats directory " + statsDir + " already exists and is not a directory");
LibScoutConfig.statsDir = statsDir;
}
}
// enable/disable generation of json output
if (checkOptionalUse(cmd, CliArgs.ARG_JSON_DIR, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS, LibScoutConfig.OpMode.UPDATABILITY)) {
LibScoutConfig.generateJSON = true;
if (cmd.getOptionValue(CliArgs.ARG_JSON_DIR) != null) { // json dir provided?
File jsonDir = new File(cmd.getOptionValue(CliArgs.ARG_JSON_DIR));
if (jsonDir.exists() && !jsonDir.isDirectory())
throw new ParseException("JSON directory " + jsonDir + " already exists and is not a directory");
LibScoutConfig.jsonDir = jsonDir;
}
}
// provide directory to lib api compat files (generated with api-analysis mode)
if (checkRequiredUse(cmd, CliArgs.ARG_LIB_API_COMPAT_DIR, LibScoutConfig.OpMode.UPDATABILITY)) {
File apiCompatDir = new File(cmd.getOptionValue(CliArgs.ARG_LIB_API_COMPAT_DIR));
if (!apiCompatDir.isDirectory())
throw new ParseException(apiCompatDir + " is not a directory");
LibScoutConfig.libApiCompatDir = apiCompatDir;
}
/*
* process lib|app arguments
* - in profile mode pass *one* library (since it is linked to lib description file)
* - in match mode pass one application file or one directory (including apks)
*/
inputFiles = new ArrayList();
if (LibScoutConfig.opLibApiAnalysis()) {
// we require a directory including library packages/descriptions
for (String path: cmd.getArgs()) {
File dir = new File(path);
if (dir.isDirectory())
inputFiles.add(dir);
}
if (inputFiles.isEmpty()) {
throw new ParseException("You have to provide at least one directory that includes a library package and description");
}
} else {
String[] fileExts = LibScoutConfig.opMatch() || LibScoutConfig.opUpdatability() ? new String[]{"apk"} : new String[]{"jar", "aar"};
for (String inputFile : cmd.getArgs()) {
File arg = new File(inputFile);
if (arg.isDirectory()) {
inputFiles.addAll(Utils.collectFiles(arg, fileExts));
} else if (arg.isFile()) {
if (arg.getName().endsWith("." + fileExts[0]))
inputFiles.add(arg);
else if (fileExts.length > 1 && arg.getName().endsWith("." + fileExts[1]))
inputFiles.add(arg);
else
throw new ParseException("File " + arg.getName() + " is no valid ." + Utils.join(Arrays.asList(fileExts), "/") + " file");
} else {
throw new ParseException("Argument " + inputFile + " is no valid file or directory!");
}
}
if (inputFiles.isEmpty()) {
if (LibScoutConfig.opProfile())
throw new ParseException("No libraries (jar|aar files) found to profile in " + cmd.getArgList());
else
throw new ParseException("No apk files found in " + cmd.getArgList());
} else if (inputFiles.size() > 1 && LibScoutConfig.opProfile())
throw new ParseException("You have to provide a path to a single library file or a directory incl. a single lib file");
}
} catch (ParseException e) {
System.err.println("Command line parsing failed:\n " + e.getMessage() + "\n");
usage();
} catch (Exception e) {
System.err.println("Error occurred during argument processing:\n" + e.getMessage());
}
}
private static boolean checkRequiredUse(CommandLine cmd, String option, LibScoutConfig.OpMode... modes) throws ParseException {
if (!Arrays.asList(modes).contains(LibScoutConfig.opmode))
return false;
if (!cmd.hasOption(option))
throw new ParseException("Required CLI Option " + option + " is missing in mode " + LibScoutConfig.opmode);
return true;
}
private static boolean checkOptionalUse(CommandLine cmd, String option, LibScoutConfig.OpMode... modes) {
if (!Arrays.asList(modes).contains(LibScoutConfig.opmode))
return false;
return cmd.hasOption(option);
}
/**
* Checks whether required option (for current mode) is either provided via CLI or config file
*/
private static void checkRequiredOptions() throws ParseException {
try {
LibScoutConfig.checkIfValidFile(LibScoutConfig.log4jConfigFileName);
} catch (ParseException e) {
throw new ParseException("Could not find the log4j config file logback.xml . Please add the path in the LibScout.toml config");
}
// android-sdk.jar
if (Arrays.asList(LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS).contains(LibScoutConfig.opmode) &&
LibScoutConfig.pathToAndroidJar == null) {
throw new ParseException("Required option " + CliArgs.ARGL_ANDROID_LIB + " is neither provided via command line nor config file");
}
}
@SuppressWarnings("static-access")
private static Options setupOptions() {
options = new Options();
options.addOption(OptionBuilder.withArgName("value")
.hasArgs(1)
.isRequired(true)
.withLongOpt(CliArgs.ARGL_OPMODE)
.withDescription("mode of operation, one of [" + LibScoutConfig.OpMode.getOpModeString() + "]")
.create(CliArgs.ARG_OPMODE));
options.addOption(OptionBuilder.withArgName("file")
.hasArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_CONFIG)
.withDescription("path to LibScout's config file, defaults to \"" + LibScoutConfig.libScoutConfigFileName + "\"")
.create(CliArgs.ARG_CONFIG));
options.addOption(OptionBuilder.withArgName("file")
.hasArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_ANDROID_LIB)
.withDescription("path to SDK android.jar")
.create(CliArgs.ARG_ANDROID_LIB));
options.addOption(OptionBuilder.withArgName("directory")
.hasOptionalArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_LOG_DIR)
.withDescription("path to store the logfile(s), defaults to \"" + LibScoutConfig.logDir + "\"")
.create(CliArgs.ARG_LOG_DIR));
options.addOption(OptionBuilder.withArgName("directory")
.hasOptionalArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_STATS_DIR)
.withDescription("path to app stat(s), defaults to \"" + LibScoutConfig.statsDir + "\"")
.create(CliArgs.ARG_STATS_DIR));
options.addOption(OptionBuilder.withArgName("directory")
.hasOptionalArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_JSON_DIR)
.withDescription("path to json output directory, defaults to \"" + LibScoutConfig.jsonDir + "\"")
.create(CliArgs.ARG_JSON_DIR));
options.addOption(OptionBuilder.withArgName("value")
.isRequired(false)
.withLongOpt(CliArgs.ARGL_MUTE)
.withDescription("disable file and console logging, takes precedence over -d")
.create(CliArgs.ARG_MUTE));
options.addOption(OptionBuilder.withArgName("directory")
.hasArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_PROFILES_DIR)
.withDescription("path to library profiles, defaults to \"" + LibScoutConfig.profilesDir + "\"")
.create(CliArgs.ARG_PROFILES_DIR));
options.addOption(OptionBuilder.withArgName("value")
.isRequired(false)
.withLongOpt(CliArgs.ARGL_LIB_VERBOSE_PROFILES)
.withDescription("enable verbose profiling (trace + pubonly)")
.create(CliArgs.ARG_LIB_VERBOSE_PROFILES));
options.addOption(OptionBuilder.withArgName("value")
.isRequired(false)
.withLongOpt(CliArgs.ARGL_NO_PARTIAL_MATCHING)
.withDescription("disables partial matching (full matching only)")
.create(CliArgs.ARG_NO_PARTIAL_MATCHING));
options.addOption(OptionBuilder.withArgName("value")
.isRequired(false)
.withLongOpt(CliArgs.ARGL_LIB_USAGE_ANALYSIS)
.withDescription("Enables library usage analysis (for full matches only)")
.create(CliArgs.ARG_LIB_USAGE_ANALYSIS));
options.addOption(OptionBuilder.withArgName("file")
.hasArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_LIB_DESCRIPTION)
.withDescription("xml file to describe the library")
.create(CliArgs.ARG_LIB_DESCRIPTION));
options.addOption(OptionBuilder.withArgName("value")
.isRequired(false)
.withLongOpt(CliArgs.ARGL_LIB_DEPENDENCY_ANALYSIS)
.withDescription("enable analysis of secondary library dependencies")
.create(CliArgs.ARG_LIB_DEPENDENCY_ANALYSIS));
options.addOption(OptionBuilder.withArgName("directory")
.hasArgs(1)
.isRequired(false)
.withLongOpt(CliArgs.ARGL_LIB_API_COMPAT_DIR)
.withDescription("path to library api compatibility data files")
.create(CliArgs.ARG_LIB_API_COMPAT_DIR));
return options;
}
private static void usage() {
// automatically generate the help statement
HelpFormatter formatter = new HelpFormatter();
String helpMsg = LibScoutConfig.OpMode.getUsageMessage(LibScoutConfig.opmode);
formatter.printHelp(helpMsg, options);
System.exit(1);
}
private static void initLogging() {
LoggerContext context = (LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
context.reset(); // clear any previous configuration
configurator.doConfigure(LibScoutConfig.log4jConfigFileName);
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
switch (LibScoutConfig.logType) {
case CONSOLE:
rootLogger.detachAppender("FILE");
break;
case FILE:
rootLogger.detachAppender("CONSOLE");
break;
case NONE:
rootLogger.detachAndStopAllAppenders();
break;
}
} catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
}
}
LibScout-2.3.2/src/de/infsec/tpl/config/ 0000775 0000000 0000000 00000000000 13424313624 0017736 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/config/LibScoutConfig.java 0000664 0000000 0000000 00000014167 13424313624 0023464 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.config;
import de.infsec.tpl.TplCLI;
import de.infsec.tpl.utils.Utils;
import net.consensys.cava.toml.Toml;
import net.consensys.cava.toml.TomlParseResult;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;
public class LibScoutConfig {
private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.config.LibScoutConfig.class);
public static final String TOOLNAME = "LibScout";
public static final String TOOLVERSION = "2.3.2";
// modes of operations
public enum OpMode {
// generate library profiles from original lib SDKs and descriptions
PROFILE("profile", "-x path_to_lib_desc [options] path_to_lib(jar|aar)"),
// match lib profiles in provided apps
MATCH("match", "[options] path_to_app(dir)"),
// analyzes library api stability (api additions, removals, changes)
LIB_API_ANALYSIS("lib_api_analysis", "path_to_lib_sdks"),
// infer library usage in apps and check to which extent detected libs can be updated
UPDATABILITY( "updatability", "[options] -l path_to_lib_api_compat path_to_app(dir)");
public String name;
public String usageMsg;
OpMode(String name, String usageMsg) {
this.name = name;
this.usageMsg = usageMsg;
}
public static String getUsageMessage(OpMode op) {
return op == null? getToolUsageMsg() : TOOLNAME + " --opmode " + op.name + " " + op.usageMsg;
}
public static String getToolUsageMsg() {
return TOOLNAME + " --opmode <" + getOpModeString() + "> [options]";
}
public static String getOpModeString() {
return Utils.join(Arrays.stream(OpMode.values()).map(op -> op.name.toLowerCase()).collect(Collectors.toList()), "|");
}
}
public static OpMode opmode = null;
public static boolean opMatch() { return OpMode.MATCH.equals(opmode); }
public static boolean opProfile() { return OpMode.PROFILE.equals(opmode); }
public static boolean opLibApiAnalysis() { return OpMode.LIB_API_ANALYSIS.equals(opmode); }
public static boolean opUpdatability() { return OpMode.UPDATABILITY.equals(opmode); }
// config files
public static String libScoutConfigFileName = "./config/LibScout.toml";
public static String log4jConfigFileName = "./config/logback.xml";
public static File pathToAndroidJar;
public static boolean noPartialMatching = false;
public static boolean runLibUsageAnalysis = false;
public static boolean genVerboseProfiles = false; // generate lib profiles with TRACE + PubOnly
public static boolean libDependencyAnalysis = false;
public enum LogType { NONE, CONSOLE, FILE }
public static LogType logType = LogType.CONSOLE;
public static File logDir = new File("./logs");
public static boolean generateStats = false;
public static File statsDir = new File("./stats");
public static boolean generateJSON = false;
public static File jsonDir = new File("./json");
public static File profilesDir = new File("./profiles");
public static File libApiCompatDir = null;
// package tree
public static class PckgTree {
public static boolean useAsciiRendering = false;
}
// reporting (logs, json)
public static class Reporting {
// upon detection, print/hide comments from library description
public static boolean showComments = false;
}
public static void whoAmI() {
logger.info("This is " + TOOLNAME + " " + TOOLVERSION);
}
public static boolean loadConfig() throws ParseException {
File libScoutConfigFile;
try {
libScoutConfigFile = checkIfValidFile(libScoutConfigFileName);
} catch (ParseException e) {
throw new ParseException("Could not find the config file LibScout.toml . Please provide it via the --" + TplCLI.CliArgs.ARGL_CONFIG + " switch");
}
try {
Path confPath = Paths.get(libScoutConfigFile.toURI());
TomlParseResult conf = Toml.parse(confPath);
if (conf.hasErrors()) {
logger.warn("Error while parsing config file:");
conf.errors().forEach(e -> logger.info(Utils.indent() + "line: " + e.position().line() + ": " + e.toString()));
return false;
} else {
for (String k: conf.dottedKeySet()) {
parseConfig(k, conf.get(k));
}
}
} catch (IOException e) {
logger.warn("Error while parsing config file: " + Utils.stacktrace2Str(e));
return false;
}
return true;
}
private static void parseConfig(String key, Object value) throws ParseException {
try {
//logger.debug("Parse config key : " + key + " value: " + value);
if ("logging.log4j_config_file".equals(key)) {
File f = checkIfValidFile((String) value);
log4jConfigFileName = f.getAbsolutePath();
} else if ("sdk.android_sdk_jar".equals(key)) {
if (pathToAndroidJar == null) // CLI takes precedence
pathToAndroidJar = checkIfValidFile((String) value);
} else if ("packageTree.ascii_rendering".equals(key)) {
PckgTree.useAsciiRendering = (Boolean) value;
} else if ("reporting.show_comments".equals(key)) {
Reporting.showComments = (Boolean) value;
} else
logger.warn("Found unknown config key: " + key);
} catch (ParseException e) {
throw new ParseException("Could not parse config option " + key + " : " + e.getMessage());
}
}
public static File checkIfValidFile(String fileName) throws ParseException {
File f = new File(fileName);
if (f.exists() && f.isFile())
return f;
else
throw new ParseException("No valid file: " + fileName);
}
}
LibScout-2.3.2/src/de/infsec/tpl/exceptions/ 0000775 0000000 0000000 00000000000 13424313624 0020652 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/exceptions/MethodNotFoundException.java 0000664 0000000 0000000 00000001532 13424313624 0026272 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.exceptions;
public class MethodNotFoundException extends Exception {
private static final long serialVersionUID = -5657700822870573562L;
public MethodNotFoundException(String message) {
super(message);
}
} LibScout-2.3.2/src/de/infsec/tpl/hash/ 0000775 0000000 0000000 00000000000 13424313624 0017414 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hash/AccessFlags.java 0000664 0000000 0000000 00000007606 13424313624 0022446 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hash;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.ibm.wala.classLoader.IMethod;
import de.infsec.tpl.utils.Utils;
public enum AccessFlags {
NO_FLAG (0x0, "no-flag"),
PUBLIC (0x1, "public"),
PRIVATE (0x2, "private"),
PROTECTED (0x4, "protected"),
PACKAGE_PROTECTED (0x8, "package-protected");
private int value;
private String accessFlagName;
//cache the array of all AccessFlags, because .values() allocates a new array for every call
private final static AccessFlags[] allFlags;
private final static List validFlagValues;
private static HashMap accessFlagsByName;
static {
allFlags = AccessFlags.values();
validFlagValues = new ArrayList();
for (AccessFlags flag: allFlags)
validFlagValues.add(flag.getValue());
accessFlagsByName = new HashMap();
for (AccessFlags accessFlag: allFlags) {
accessFlagsByName.put(accessFlag.accessFlagName, accessFlag);
}
}
private AccessFlags(int value, String accessFlagName) {
this.value = value;
this.accessFlagName = accessFlagName;
}
private static String flags2Str(AccessFlags[] accessFlags) {
int size = 0;
for (AccessFlags accessFlag: accessFlags) {
size += accessFlag.toString().length() + 1;
}
StringBuilder sb = new StringBuilder(size);
for (AccessFlags accessFlag: accessFlags) {
sb.append(accessFlag.toString());
sb.append(" ");
}
if (accessFlags.length > 0) {
sb.delete(sb.length() - 1, sb.length());
}
return sb.toString();
}
public static boolean isValidFlag(int code) {
return validFlagValues.contains(code);
}
public static String flags2Str(int code) {
List matchedFlags = new ArrayList();
for (AccessFlags flag: allFlags) {
if ((code & flag.value) != 0x0) {
matchedFlags.add(flag.accessFlagName + "(" + flag.value + ")");
}
}
return Utils.join(matchedFlags, ",");
}
public static AccessFlags getAccessFlag(String accessFlag) {
return accessFlagsByName.get(accessFlag);
}
public int getValue() {
return value;
}
public String toString() {
return accessFlagName;
}
public static int getAccessFlagFilter(AccessFlags... flags) {
int filter = NO_FLAG.getValue();
if (flags != null) {
for (AccessFlags flag: flags) {
if (!AccessFlags.isValidFlag(flag.getValue())) continue;
filter |= flag.getValue();
}
}
return filter;
}
public static int getPublicOnlyFilter() {
return getAccessFlagFilter(AccessFlags.PRIVATE, AccessFlags.PACKAGE_PROTECTED, AccessFlags.PROTECTED);
}
public static int getMethodAccessCode(IMethod m) {
int res = 0x0;
if (m == null)
return res;
if (m.isPublic()) {
res |= AccessFlags.PUBLIC.getValue();
} else if (m.isProtected()) {
res |= AccessFlags.PROTECTED.getValue();
} else if (m.isPrivate()) {
res |= AccessFlags.PRIVATE.getValue();
} else {
res |= AccessFlags.PACKAGE_PROTECTED.getValue();
}
return res;
}
}
LibScout-2.3.2/src/de/infsec/tpl/hash/Hash.java 0000664 0000000 0000000 00000002740 13424313624 0021145 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hash;
import java.util.Arrays;
import java.util.Comparator;
@Deprecated
public abstract class Hash implements IHash {
public static boolean equals(byte[] hash1, byte[] hash2) {
return Arrays.equals(hash1, hash2);
}
public static String hash2Str(byte[] hash) {
String format = "%" + (hash.length*2) + "x";
return String.format(format, new java.math.BigInteger(1, hash));
}
// Lexicographical comparator for byte arrays
public class ByteArrayComparator implements Comparator {
@Override
public int compare(byte[] left, byte[] right) {
for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) {
int a = (left[i] & 0xff);
int b = (right[j] & 0xff);
if (a != b) {
return a - b;
}
}
return left.length - right.length;
}
}
}
LibScout-2.3.2/src/de/infsec/tpl/hash/HashCode.java 0000664 0000000 0000000 00000002441 13424313624 0021736 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hash;
public class HashCode extends Hash {
public HashCode() {}
@Override
public byte[] hash(String str) {
int value = str.hashCode();
return new byte[] {
(byte)(value >>> 24),
(byte)(value >>> 16),
(byte)(value >>> 8),
(byte) value};
}
@Override
public byte[] hash(byte[] b) {
String str = new String(b);
return hash(str);
}
// adapted from String.hashCode(), generates a 64bit hash
/* public static long hash(String string) {
long h = 1125899906842597L; // prime
int len = string.length();
for (int i = 0; i < len; i++) {
h = 31*h + string.charAt(i);
}
return h;
}*/
}
LibScout-2.3.2/src/de/infsec/tpl/hash/HashImpl.java 0000664 0000000 0000000 00000002635 13424313624 0021772 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hash;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashImpl extends Hash {
private final MessageDigest digest;
/**
* Creates a new hash implementation object
* @param algorithm e.g. MD5 or SHA-256
* @throws NoSuchAlgorithmException
*/
public HashImpl(String algorithm) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance(algorithm); /* e.g. MD5, SHA-256 */
}
@Override
public byte[] hash(String str) {
try {
digest.update(str.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
digest.update(str.getBytes());
}
return digest.digest();
}
@Override
public byte[] hash(byte[] b) {
digest.update(b);
return digest.digest();
}
}
LibScout-2.3.2/src/de/infsec/tpl/hash/HashTreeOLD.java 0000664 0000000 0000000 00000070257 13424313624 0022334 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hash;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.Descriptor;
import de.infsec.tpl.hash.Hash.ByteArrayComparator;
import de.infsec.tpl.pkg.PackageTree;
import de.infsec.tpl.pkg.PackageUtils;
import de.infsec.tpl.profile.ProfileMatch.MatchLevel;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.WalaUtils;
/**
* The main data structure for (library/app) profiles
* @author ederr
*
*/
@Deprecated
public class HashTreeOLD implements Serializable {
private static final long serialVersionUID = 8890771073564530924L;
private static final Logger logger = LoggerFactory.getLogger(HashTreeOLD.class);
private Config config = new Config();
public enum HTREE_BUILD_VERBOSENESS {
MINIMAL /* Root and Package hashes/names only */,
NORMAL /* Root/Package/Class hashes including package names (DEFAULT) */,
DEBUG /* Root/Package/Class hashes including package/class names */,
TRACE /* Root/Package/Class/Method hashes including all names/signatures */
}
/**
* Build config for HashTreeOLD
*/
public class Config implements Serializable {
private static final long serialVersionUID = -8693957635226365553L;
// if true, filters duplicate method hashes, i.e. methods that have the same fuzzy descriptor
// this introduces some kind of fuzziness as we abstract from the concrete number of certain descriptor
public boolean filterDups = false;
// exclude methods while hashing whose access specifier (see {@link AccessFlags}) matches the filter value
public int accessFlagsFilter = AccessFlags.NO_FLAG.getValue();
// if true, inner classes are not considered during hashing
public boolean filterInnerClasses = false;
// the hash algorithm used for hashing
public HashAlgorithm hashAlgorithm = HashAlgorithm.MD5;
public HTREE_BUILD_VERBOSENESS buildVerboseness = HTREE_BUILD_VERBOSENESS.NORMAL;
public Config() {}
public Config(boolean filterDups, boolean filterInnerClasses) {
this.filterDups = filterDups;
this.filterInnerClasses = filterInnerClasses;
}
public Config(boolean filterDups, boolean filterInnerClasses, HashAlgorithm hashAlgorithm) {
this(filterDups, filterInnerClasses);
this.hashAlgorithm = hashAlgorithm;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Config))
return false;
Config c = (Config) obj;
return c.filterDups == this.filterDups &&
c.accessFlagsFilter == this.accessFlagsFilter &&
c.filterInnerClasses == this.filterInnerClasses &&
c.hashAlgorithm.equals(this.hashAlgorithm);
}
@Override
public int hashCode() {
return 10000 * (this.filterDups? 1 : 0) + 1000 * this.accessFlagsFilter + 100 * (this.filterInnerClasses? 1 : 0) + hashAlgorithm.value.hashCode();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[Config]");
sb.append("filterDups? " + this.filterDups);
sb.append(" | accessFlagsFilter? [" + this.accessFlagsFilter + "] " + AccessFlags.flags2Str(this.accessFlagsFilter));
sb.append(" | filterInnerClasses: " + this.filterInnerClasses);
sb.append(" | hash-algo: "+ this.hashAlgorithm);
sb.append(" | verboseness: "+ this.buildVerboseness);
return sb.toString();
}
}
private Node rootNode;
public class Node implements Serializable {
private static final long serialVersionUID = 8649289911402320347L;
public byte[] hash;
public List childs;
public Node(byte[] hash) {
this.hash = hash;
this.childs = new ArrayList();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node))
return false;
return Hash.equals(((Node) obj).hash, this.hash);
}
@Override
public int hashCode() {
return hash.hashCode() + childs.size();
}
@Override
public String toString() {
return Hash.hash2Str(this.hash);
}
public int numberOfChilds() {
return this.childs.size();
}
public void debug() {}
public String getStats() {
StringBuilder sb = new StringBuilder();
int pNodes = 0;
int cNodes = 0;
int mNodes = 0;
LinkedList worklist = new LinkedList();
worklist.add(this);
Node curNode;
while (!worklist.isEmpty()) {
curNode = worklist.poll();
worklist.addAll(curNode.childs);
for (Node n: curNode.childs) {
if (n instanceof PackageNode)
pNodes++;
else if (n instanceof ClassNode)
cNodes++;
else if (n instanceof MethodNode)
mNodes++;
}
}
sb.append("Node stats:\n");
sb.append(Utils.INDENT + "- contains " + mNodes + " method hashes.\n");
sb.append(Utils.INDENT + "- contains " + cNodes + " clazz hashes.\n");
sb.append(Utils.INDENT + "- contains " + pNodes + " package hashes.");
return sb.toString();
}
public boolean isLeaf() {
return childs.isEmpty();
}
}
public class NodeComparator implements Comparator {
private ByteArrayComparator comp;
public NodeComparator() throws NoSuchAlgorithmException {
IHash hashFunc = new HashImpl(config.hashAlgorithm.toString());
comp = ((Hash) hashFunc).new ByteArrayComparator();
}
@Override
public int compare(Node n0, Node n1) {
return comp.compare(n0.hash, n1.hash);
}
}
public class PackageNode extends Node implements Serializable {
private static final long serialVersionUID = -2824664777266635012L;
public String packageName;
public PackageNode(byte[] hash, String packageName) {
super(hash);
this.packageName = packageName;
}
@Override
public void debug() {
logger.info("Debug PackageNode: " + packageName + " (childs: " + childs.size() + ", " + Hash.hash2Str(hash) + ")");
for (Node n: this.childs) {
ClassNode cn = (ClassNode) n;
logger.info(Utils.INDENT + "- " + cn.clazzName + " :: " + cn.numberOfChilds() + " :: " + Hash.hash2Str(cn.hash));
// cn.debug();
}
}
public List getClassNodes() {
return this.childs;
}
public List getMethodNodes() {
ArrayList result = new ArrayList();
for (Node n: this.childs) {
result.addAll(((ClassNode) n).getMethodNodes());
}
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PackageNode))
return false;
return Hash.equals(((PackageNode) obj).hash, this.hash);
}
@Override
public String toString() {
return "PNode(" + packageName + ")";
}
}
public class ClassNode extends Node implements Serializable {
private static final long serialVersionUID = 4538829579264140006L;
public String clazzName;
public ClassNode(byte[] hash, String clazzName) {
super(hash);
this.clazzName = clazzName;
}
public List getMethodNodes() {
return this.childs;
}
@Override
public void debug() {
//logger.info("Debug ClassNode: " + clazzName + " (childs: " + childs.size() + ", " + Hash.hash2Human(hash) + ")");
for (Node n: this.childs) {
MethodNode mn = (MethodNode) n;
logger.info(Utils.INDENT2 + "- " + mn.signature + " :: " + Hash.hash2Str(mn.hash));
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ClassNode))
return false;
return Hash.equals(((ClassNode) obj).hash, this.hash);
}
@Override
public String toString() {
return "CNode(" + clazzName + ")";
}
}
public class MethodNode extends Node implements Serializable {
private static final long serialVersionUID = -1147942448831557142L;
public String signature;
public MethodNode(byte[] hash, String signature) {
super(hash);
this.signature = signature;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MethodNode))
return false;
return Hash.equals(((MethodNode) obj).hash, this.hash);
}
@Override
public String toString() {
return "MNode(" + signature + ")";
}
}
public enum HashAlgorithm {
MD5("MD5"), SHA1("SHA-1"), SHA256("SHA-256");
private String value;
HashAlgorithm(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
};
public HashTreeOLD() {
this.config = new Config();
}
public HashTreeOLD(boolean filterDups, boolean filterInnerClasses, HashAlgorithm algorithm) throws NoSuchAlgorithmException {
this.config = new Config(filterDups, filterInnerClasses, algorithm);
}
/*
* Setter methods
*/
/**
* Sets the method access flag filter
* @param flags an array of {@link AccessFlags} (only private, package-protected, protected, and public). Unset the filter by providing a null argument.
*/
public void setAccessFlagFilter(AccessFlags... flags) {
config.accessFlagsFilter = AccessFlags.getAccessFlagFilter(flags);
}
/**
* Filter any method but public methods for hashing
*/
public void setPublicOnlyFilter() {
setAccessFlagFilter(AccessFlags.PRIVATE, AccessFlags.PACKAGE_PROTECTED, AccessFlags.PROTECTED);
}
public void setPrivateMethodsFilter() {
setAccessFlagFilter(AccessFlags.PRIVATE);
}
public void setFilterDups(final boolean filterDups) {
config.filterDups = filterDups;
}
public void setFilterInnerClasses(final boolean filterInnerClasses) {
config.filterInnerClasses = filterInnerClasses;
}
public void setHashAlgorithm(final HashAlgorithm algorithm) {
config.hashAlgorithm = algorithm;
}
public void setBuildVerboseness(final HTREE_BUILD_VERBOSENESS v) {
config.buildVerboseness = v;
}
public boolean hasDefaultConfig() {
return !this.config.filterDups && !this.config.filterInnerClasses && this.config.accessFlagsFilter == 0x0;
}
/*
* Getter methods
*/
public Node getRootNode() {
return this.rootNode;
}
public byte[] getRootHash() {
return this.rootNode.hash;
}
public Config getConfig() {
return this.config;
}
public Collection getPackageNodes() {
return this.getRootNode().childs;
}
public int getNumberOfPackages() {
return rootNode.numberOfChilds();
}
public int getNumberOfClasses() {
int cCount = 0;
for (Node pNode: rootNode.childs)
cCount += getNumberOfClasses((PackageNode) pNode);
return cCount;
}
public int getNumberOfClasses(PackageNode pNode) {
return pNode.numberOfChilds();
}
public int getNumberOfMethods(PackageNode pNode) {
int mCount = 0;
for (Node cNode: pNode.childs)
mCount += cNode.numberOfChilds();
return mCount;
}
public int getNumberOfMethods() {
int mCount = 0;
for (Node pNode: rootNode.childs)
mCount += getNumberOfMethods((PackageNode) pNode);
return mCount;
}
public List getAllMethodSignatures() {
List signatures = new ArrayList();
for (Node pNode: rootNode.childs) {
for (Node cNode: pNode.childs) {
for (Node mNode: cNode.childs) {
signatures.add(((MethodNode) mNode).signature);
}
}
}
Collections.sort(signatures);
return signatures;
}
public int getNumberOfHashesByLevel(MatchLevel lvl) {
switch(lvl) {
case CLASS:
return getNumberOfClasses();
case METHOD:
return getNumberOfMethods();
case PACKAGE:
return getNumberOfPackages();
}
return -1;
}
public void printConfig() {
logger.info(config.toString());
}
public boolean matchesConfig(HashTreeOLD hTree) {
return this.config.equals(hTree.getConfig());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HashTreeOLD))
return false;
HashTreeOLD ht = (HashTreeOLD) obj;
if (!ht.config.equals(this.config))
return false;
return Hash.equals(this.getRootHash(), ht.getRootHash());
}
/**
* Generates a HashTreeOLD for every class loaded via application classLoader
* @throws NoSuchAlgorithmException
*/
public void generate(IClassHierarchy cha) throws NoSuchAlgorithmException {
logger.debug("Generate library hash tree..");
if (logger.isDebugEnabled())
printConfig();
IHash hashFunc = new HashImpl(config.hashAlgorithm.toString());
NodeComparator comp = new NodeComparator();
int classHashCount = 0;
int methodHashCount = 0;
// create map package name -> list of clazzNodes
HashMap> packageMap = new HashMap>();
for (IClass clazz: cha) {
if (WalaUtils.isAppClass(clazz)){
// inner class filter
if (config.filterInnerClasses && WalaUtils.isInnerClass(clazz)) {
continue;
}
// duplicate method filter
Collection methodNodes = config.filterDups? new TreeSet(comp) : new ArrayList();
Collection extends IMethod> methods = clazz.getDeclaredMethods();
// filter methods by access flag
if (config.accessFlagsFilter != AccessFlags.NO_FLAG.getValue()) {
methods = methods.stream()
.filter(m -> { int code = AccessFlags.getMethodAccessCode(m); return code > 0 && (code & config.accessFlagsFilter) == 0x0; }) // if predicate is true, keep in list
.collect(Collectors.toCollection(ArrayList::new));
}
for (IMethod m: methods) {
// normalize java|dex bytecode by skipping compiler-generated methods
if (m.isBridge() || m.isSynthetic()) {
continue;
}
String normalizedDesc = normalizeAnonymousInnerClassConstructor(m);
byte[] hash = hashFunc.hash(normalizedDesc != null? normalizedDesc : getFuzzyDescriptor(m));
methodNodes.add(new MethodNode(hash, m.getSignature()));
}
// normalization (if we have no methods, either because there are none or due to our filter properties, skip this class)
if (methodNodes.isEmpty()) {
logger.trace(Utils.INDENT + ">> No methods found for clazz: " + WalaUtils.simpleName(clazz) + " [SKIP]");
continue;
}
if (!config.filterDups)
Collections.sort((List) methodNodes, comp); // sort but do not filter dups
methodHashCount += methodNodes.size();
classHashCount++;
byte[] clazzHash = hash(methodNodes, hashFunc);
String classIdentifier = config.buildVerboseness == HTREE_BUILD_VERBOSENESS.DEBUG || config.buildVerboseness == HTREE_BUILD_VERBOSENESS.TRACE? WalaUtils.simpleName(clazz) : "";
ClassNode clazzNode = new ClassNode(clazzHash, classIdentifier);
// only store method hashes if configured (space vs accuracy)
clazzNode.childs = config.buildVerboseness == HTREE_BUILD_VERBOSENESS.TRACE? new ArrayList(methodNodes) : new ArrayList();
String pckgName = PackageUtils.getPackageName(clazz);
if (!packageMap.containsKey(pckgName)) {
packageMap.put(pckgName, config.filterDups? new TreeSet(comp) : new ArrayList());
}
packageMap.get(pckgName).add(clazzNode);
}
}
Collection packageNodes = config.filterDups? new TreeSet(comp) : new ArrayList();
for (String pckgName: new TreeSet(packageMap.keySet())) {
if (!config.filterDups)
Collections.sort((List) packageMap.get(pckgName), comp); // sort but do not filter dups
byte[] packageHash = hash(packageMap.get(pckgName), hashFunc);
PackageNode n = new PackageNode(packageHash, pckgName);
if (!config.buildVerboseness.equals(HTREE_BUILD_VERBOSENESS.MINIMAL)) // do not add class nodes in min verboseness
n.childs.addAll(packageMap.get(pckgName));
packageNodes.add(n);
}
logger.debug(Utils.INDENT + "- generated " + methodHashCount + " method hashes.");
logger.debug(Utils.INDENT + "- generated " + classHashCount + " clazz hashes.");
logger.debug(Utils.INDENT + "- generated " + packageNodes.size() + " package hashes.");
// generate library hash
if (!config.filterDups)
Collections.sort((List) packageNodes, comp); // sort but do not filter dups
byte[] libraryHash = hash(packageNodes, hashFunc);
rootNode = new Node(libraryHash);
rootNode.childs.addAll(packageNodes);
logger.debug(Utils.INDENT + "=> Library Hash: " + Hash.hash2Str(libraryHash));
}
public Node getSubTreeByPackage(PackageTree ptree) throws NoSuchAlgorithmException {
String rootPackage = ptree.getRootPackage();
// Since we have a flattened tree (in terms of package nodes, we collect all package nodes that
// equal or start with the rootPackage, then create and return a new rootnode with the collected package nodes
// as child
NodeComparator comp = new NodeComparator();
IHash hashFunc = new HashImpl(config.hashAlgorithm.toString());
Collection childs = config.filterDups? new TreeSet(comp) : new ArrayList();
for (Node n: rootNode.childs) {
PackageNode pn = (PackageNode) n;
if (pn.packageName.startsWith(rootPackage))
childs.add(pn);
}
if (!config.filterDups)
Collections.sort((List) childs, comp); // sort but do not filter dups
// generate new root node
if (!childs.isEmpty()) {
Node rootNode = new Node(hash(childs, hashFunc));
rootNode.childs.addAll(childs);
return rootNode;
} else
return null; // no matching node found
}
public Node generateRootNode(Collection pnodes) throws NoSuchAlgorithmException {
// Since we have a flattened tree (in terms of package nodes, we collect all package nodes that
// equal or start with the rootPackage, then create and return a new rootnode with the collected package nodes
// as child
NodeComparator comp = new NodeComparator();
IHash hashFunc = new HashImpl(config.hashAlgorithm.toString());
Collection childs = config.filterDups? new TreeSet(comp) : new ArrayList();
childs.addAll(pnodes);
if (!config.filterDups)
Collections.sort((List) childs, comp); // sort but do not filter dups
// generate new root node
if (childs.isEmpty()) {
logger.warn("[generateRootNode] no childs - return empy rootNode");
}
Node rootNode = new Node(hash(childs, hashFunc));
rootNode.childs.addAll(childs);
return rootNode;
}
/**
* Generic hash function that takes a list of hashes, concatenates and hashes them
* @param nodes a collection of input {@link Node}
* @param hashFunc a hash function
* @return a hash
*/
public static byte[] hash(Collection extends Node> nodes, final IHash hashFunc) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
for (Node node: nodes)
outputStream.write(node.hash);
} catch (IOException e) {
e.printStackTrace();
}
byte[] arr = outputStream.toByteArray();
return hashFunc.hash(arr);
}
/**
* This normalization of constructors of anonymous inner classes is due to the fact that
* (in some cases) the dx compiler adds the superclass of the enclosing class as second parameter,
* while the javac does not. This results in different hashes for this classes which implies that
* this library can't be matched exactly although it is the same version. Therefore, this
* normalization will skip this second argument during generation of the fuzzy descriptor.
*
* See example code: method createAdapter() in
* https://github.com/facebook/facebook-android-sdk/blob/f6c4e4c1062bcc74e5a25b3243f6bee2e32d949e/facebook/src/com/facebook/widget/PlacePickerFragment.java
*
* In the disassembled dex the constructor can look like this:
*
* method constructor (Lcom/facebook/widget/PlacePickerFragment;Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V
* .locals 0
* .param p3, "$anonymous0" # Landroid/content/Context;
*
* iput-object p1, p0, Lcom/facebook/widget/PlacePickerFragment$1;->this$0:Lcom/facebook/widget/PlacePickerFragment;
* invoke-direct {p0, p2, p3}, Lcom/facebook/widget/PickerFragment$PickerFragmentAdapter;->(Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V
* return-void
* .end method
*
*
* while the decompiled class file looks as follows:
*
* PlacePickerFragment$1(PlacePickerFragment paramPlacePickerFragment, Context x0) {
* super(paramPlacePickerFragment, x0);
* }
*
*
* @param m the {@link IMethod} to normalize
* @return null if this normalization does not apply, otherwise the normalized fuzzy descriptor {@see getFuzzyDescriptor}
*/
private static String normalizeAnonymousInnerClassConstructor(IMethod m) {
if (WalaUtils.isAnonymousInnerInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 1) {
// this can be anything -> normalize constructor to (X)V
logger.trace("[normalizeAnonymousInnerClassConstructor] found anonymous inner inner class constructor: "+ m.getSignature());
return "(X)V";
}
// check if we have an anonymous inner class constructor with a sufficient number of arguments
if (WalaUtils.isAnonymousInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 2) {
logger.trace("[normalizeAnonymousInnerClassConstructor] method: " + m.getSignature());
logger.trace(Utils.INDENT + "descriptor: " + m.getDescriptor() + " # params: " + m.getNumberOfParameters());
String enclosingClazzName = WalaUtils.simpleName(m.getDeclaringClass());
enclosingClazzName = enclosingClazzName.substring(0, enclosingClazzName.lastIndexOf('$'));
// check if both argument types are custom types
for (int i : new Integer[]{1, 2}) {
if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(1));
boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct);
if (!isAppClazz)
return null;
} else
return null;
}
IClass superClazz = null;
try {
IClass ic = WalaUtils.lookupClass(m.getClassHierarchy(), enclosingClazzName);
superClazz = ic.getSuperclass();
} catch (ClassNotFoundException e) {
// class lookup can also fail for lambdas, e.g. if superclass is kotlin.jvm.internal.Lambda
// we then default to fuzzy descriptor
logger.trace("Could not lookup " + enclosingClazzName + " in bytecode normalization");
return null;
}
String argType1 = Utils.convertToFullClassName(m.getParameterType(1).getName().toString());
String argType2 = Utils.convertToFullClassName(m.getParameterType(2).getName().toString());
// now check whether this normalization needs to be applied
if (argType1.equals(enclosingClazzName) &&
argType2.equals(WalaUtils.simpleName(superClazz))) {
StringBuilder sb = new StringBuilder("(");
// param0 is the object (for non-static calls), param1 the first arg to be skipped (doesn't matter
// if whether we skip param1 or param2 since both are replaced by placeholder value)
for (int i = 2; i < m.getNumberOfParameters(); i++) {
if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i));
boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct);
sb.append(isAppClazz ? customTypeReplacement : m.getParameterType(i).getName().toString());
} else
sb.append(m.getParameterType(i).getName().toString());
}
sb.append(")V");
logger.trace(Utils.INDENT + "> bytecode normalization applied to " + m.getSignature() + " fuzzy desc: " + sb.toString());
return sb.toString();
}
}
return null;
}
private static final String customTypeReplacement = "X";
/**
* A {@link Descriptor} only describes input arg types + return type, e.g.
* The Descriptor of AdVideoView.onError(Landroid/media/MediaPlayer;II)Z is (Landroid/media/MediaPlayerII)Z
* In order to produce a fuzzy (robust against identifier-renaming) descriptor we replace each custom type by a fixed
* replacement, e.g. we receive a descriptor like (XII)Z
* Note: library dependencies, i.e. lib A depends on lib B are not a problem. If we analyze lib A without loading lib B,
* any type of lib B will be loaded with the Application classloader but will _not_ be in the classhierarchy.
* @param m {@link IMethod}
* @return a fuzzy descriptor
*/
private static String getFuzzyDescriptor(IMethod m) {
logger.trace("[getFuzzyDescriptor]");
logger.trace("- signature: " + m.getSignature());
logger.trace("- descriptor: " + m.getDescriptor().toString());
StringBuilder sb = new StringBuilder("(");
for (int i = (m.isStatic()? 0 : 1) ; i < m.getNumberOfParameters(); i++) {
boolean isAppClazz = false;
if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i));
isAppClazz = ct == null || WalaUtils.isAppClass(ct);
sb.append(isAppClazz? customTypeReplacement : m.getParameterType(i).getName().toString());
} else
sb.append(m.getParameterType(i).getName().toString());
//logger.trace(LogConfig.INDENT + "- param ref: " + m.getParameterType(i).getName().toString() + (isAppClazz? " -> " + customTypeReplacement : ""));
}
//logger.trace("");
sb.append(")");
if (m.getReturnType().getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getReturnType());
sb.append(ct == null || WalaUtils.isAppClass(ct)? customTypeReplacement : m.getReturnType().getName().toString());
} else
sb.append(m.getReturnType().getName().toString());
logger.trace("-> new type: " + sb.toString());
return sb.toString();
}
public static HashTreeOLD getTreeByConfig(Collection treeList, Config config) {
for (HashTreeOLD lht: treeList)
if (lht.getConfig().equals(config))
return lht;
return null;
}
public static HashTreeOLD getTreeByConfig(Collection treeList, boolean filterDups, int accessFlagFilter, boolean filterInnerClasses) {
for (HashTreeOLD lht: treeList) {
Config cfg = lht.getConfig();
if (cfg.filterDups == filterDups && cfg.accessFlagsFilter == accessFlagFilter && cfg.filterInnerClasses == filterInnerClasses)
return lht;
}
return null;
}
public static List toPackageNode(Collection col) {
List res = new ArrayList();
for (Node n: col) {
if (n instanceof PackageNode)
res.add((PackageNode) n);
}
return res;
}
public static void debug_compareNodes(PackageNode libNode, PackageNode appNode) {
logger.info("Compare packageNodes: [LIB] " + libNode.packageName + " vs [APP] " + appNode.packageName);
for (Node n: libNode.childs) {
if (!appNode.childs.contains(n)) {
logger.info(" - LibNode:");
n.debug();
Node an = debug_getNodeByName(appNode.childs, ((ClassNode) n).clazzName);
if (an != null) {
logger.info(" - AppNode:");
an.debug();
}
}
}
}
private static Node debug_getNodeByHash(Collection col, byte[] hash) {
if (col != null) {
for (Iterator it = col.iterator(); it.hasNext(); ) {
Node n = it.next();
if (Hash.equals(n.hash, hash))
return n;
}
}
return null;
}
private static Node debug_getNodeByName(Collection col, String clazzName) {
if (col != null) {
for (Iterator it = col.iterator(); it.hasNext(); ) {
Node n = it.next();
if (n instanceof ClassNode && (((ClassNode) n).clazzName.equals(clazzName)))
return n;
}
}
return null;
}
}
LibScout-2.3.2/src/de/infsec/tpl/hash/IHash.java 0000664 0000000 0000000 00000001344 13424313624 0021255 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hash;
public interface IHash {
public byte[] hash(String str);
public byte[] hash(byte[] b);
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/ 0000775 0000000 0000000 00000000000 13424313624 0020274 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hashtree/HashTree.java 0000664 0000000 0000000 00000014734 13424313624 0022653 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.hashtree;
import com.google.common.hash.Hasher;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import de.infsec.tpl.hash.AccessFlags;
import de.infsec.tpl.hashtree.comp.clazz.DefaultClassNodeComp;
import de.infsec.tpl.hashtree.comp.clazz.IClassNodeComp;
import de.infsec.tpl.hashtree.comp.method.IMethodNodeComp;
import de.infsec.tpl.hashtree.comp.method.SignatureMethodNodeComp;
import de.infsec.tpl.hashtree.comp.pckg.DefaultPackageNodeComp;
import de.infsec.tpl.hashtree.comp.pckg.IPackageNodeComp;
import de.infsec.tpl.hashtree.node.*;
import de.infsec.tpl.pkg.PackageUtils;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.WalaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
/*
* props:
* single/multi-version
*
* common functionality::
* - stats
* - debug?
* - generate?? depends on data
*
* abstract/interface Node
* props:: single vs multi-version nodes
*
*/
/**
* A Merkle hash tree implementation used to efficiently
* identify code reuse in apps through library code
*/
public class HashTree implements Serializable {
private static final long serialVersionUID = 8890771073564531337L;
transient private static final Logger logger = LoggerFactory.getLogger(HashTree.class);
transient final IPackageNodeComp pnComp;
transient final IClassNodeComp cnComp;
transient final IMethodNodeComp mnComp;
private Node rootNode;
protected TreeConfig config = new TreeConfig();
public HashTree() {
this(new DefaultPackageNodeComp(), new DefaultClassNodeComp(), new SignatureMethodNodeComp());
}
public HashTree(IPackageNodeComp pnComp, IClassNodeComp cnComp, IMethodNodeComp mnComp) {
this.pnComp = pnComp;
this.cnComp = cnComp;
this.mnComp = mnComp;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HashTree))
return false;
return Arrays.equals(this.getRootHash(), ((HashTree) obj).getRootHash());
}
public static Node compNode(Collection extends Node> nodes, boolean prune, Hasher hasher) {
nodes.stream().sorted(HashUtils.comp).forEach(n -> hasher.putBytes(n.hash));
Node n = new Node(hasher.hash().asBytes());
if (!prune)
n.childs.addAll(nodes);
return n;
}
public void generate(IClassHierarchy cha) {
logger.debug("Generate hash tree..");
int classHashCount = 0;
int methodHashCount = 0;
// create map package name -> set of clazzNodes
HashMap> packageMap = new HashMap<>();
for (IClass clazz: cha) {
if (WalaUtils.isAppClass(clazz)) {
Collection extends IMethod> methods = clazz.getDeclaredMethods();
// filter methods by access flag
if (config.accessFlagsFilter != AccessFlags.NO_FLAG) {
methods = methods.stream()
.filter(m -> { int code = AccessFlags.getMethodAccessCode(m); return code > 0 && (code & config.accessFlagsFilter.getValue()) == 0x0; }) // if predicate is true, keep in list
.collect(Collectors.toCollection(ArrayList::new));
}
List methodNodes = methods.stream()
.filter(m -> !(m.isBridge() || m.isSynthetic())) // normalize java|dex bytecode by skipping compiler-generated methods
.map(m -> mnComp.comp(m, config))
.sorted(HashUtils.comp) // sort but do not filter dups
.collect(Collectors.toList());
// normalize - skip classes with no methods
if (methodNodes.isEmpty()) {
logger.trace(Utils.INDENT + ">> No methods found for clazz: " + WalaUtils.simpleName(clazz));
continue;
}
// update stats
methodHashCount += methodNodes.size();
classHashCount++;
ClassNode clazzNode = cnComp.comp(methodNodes, clazz, config);
// keep track on classes per package
String pckgName = PackageUtils.getPackageName(clazz);
if (!packageMap.containsKey(pckgName)) {
packageMap.put(pckgName, new ArrayList<>());
}
packageMap.get(pckgName).add(clazzNode);
}
}
packageMap.values().forEach(l -> l.sort(HashUtils.comp)); // sort class nodes
List packageNodes = packageMap.keySet().stream()
.map(p -> pnComp.comp(packageMap.get(p), p, cha, config))
.sorted(HashUtils.comp)
.collect(Collectors.toList());
// generate root
rootNode = compNode(packageNodes, false, config.getHasher());
logger.debug(Utils.INDENT + "- generated " + packageNodes.size() + " package hashes.");
logger.debug(Utils.INDENT + "- generated " + classHashCount + " clazz hashes.");
logger.debug(Utils.INDENT + "- generated " + methodHashCount + " method hashes.");
logger.debug(Utils.INDENT + "=> Library Hash: " + HashUtils.hash2Str(rootNode.hash));
}
/*
* Getter methods
*/
public Node getRootNode() {
return this.rootNode;
}
public byte[] getRootHash() {
return this.rootNode.hash;
}
public TreeConfig getConfig() {
return this.config;
}
public void setConfig(TreeConfig config) {
this.config = config;
}
public static List toPackageNode(Collection col) {
return col.stream()
.filter(n -> n instanceof PackageNode)
.map(n -> (PackageNode) n).collect(Collectors.toList());
}
public Collection getPackageNodes() {
return this.getRootNode().childs.stream().map(pn -> (PackageNode) pn).collect(Collectors.toList());
}
public int getNumberOfPackages() {
return rootNode.numberOfChilds();
}
public int getNumberOfClasses() {
return rootNode.childs.stream().mapToInt(pn -> pn.childs.size()).sum();
}
public int getNumberOfClasses(PackageNode pn) {
return pn.numberOfChilds();
}
public int getNumberOfMethods(PackageNode pn) {
return pn.childs.stream().mapToInt(cn -> cn.childs.size()).sum();
}
public int getNumberOfMethods() {
return rootNode.childs.stream()
.map(pn -> pn.childs)
.flatMap(Collection::stream)
.mapToInt(cn -> cn.childs.size()).sum();
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/HashUtils.java 0000664 0000000 0000000 00000001745 13424313624 0023052 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree;
import de.infsec.tpl.hashtree.node.Node;
import java.util.Comparator;
public class HashUtils {
public static String hash2Str(byte[] hash) {
String format = "%" + (hash.length*2) + "x";
return String.format(format, new java.math.BigInteger(1, hash));
}
public static final NodeComparator comp = new NodeComparator();
public static class NodeComparator implements Comparator {
public NodeComparator() {}
private int compare(byte[] left, byte[] right) {
for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) {
int a = (left[i] & 0xff);
int b = (right[j] & 0xff);
if (a != b) {
return a - b;
}
}
return left.length - right.length;
}
@Override
public int compare(Node n0, Node n1) {
return compare(n0.hash, n1.hash);
}
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/TreeConfig.java 0000664 0000000 0000000 00000002764 13424313624 0023175 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import de.infsec.tpl.hash.AccessFlags;
import de.infsec.tpl.utils.Utils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class TreeConfig implements Serializable {
private static final long serialVersionUID = 1190771073563431337L;
public HashFunction hf = Hashing.md5();
public AccessFlags accessFlagsFilter = AccessFlags.NO_FLAG;
// verboseness
public boolean keepPackageNames = true;
public boolean keepClassNames = false;
public boolean keepMethodSignatures = false;
// node pruning
public boolean pruneClasses = false;
public boolean pruneMethods = true;
public Hasher getHasher() {
return hf.newHasher();
}
@Override
public String toString() {
List l = new ArrayList<>();
if (keepPackageNames) l.add("PN");
if (keepClassNames) l.add("CN");
if (keepMethodSignatures) l.add("MSIG");
String keep = l.isEmpty()? "" : Utils.join(l, "|");
l = new ArrayList<>();
if (pruneClasses) l.add("CN");
if (pruneMethods) l.add("MSIG");
String prune = l.isEmpty()? "" : Utils.join(l, "|");
return hf.toString()
+ " | Flags: " + accessFlagsFilter
+ (keep.isEmpty()? "" : " | Keep: " + keep)
+ (prune.isEmpty()? "" : " | Prune: " + prune);
}
} LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/ 0000775 0000000 0000000 00000000000 13424313624 0021232 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/clazz/ 0000775 0000000 0000000 00000000000 13424313624 0022355 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/clazz/DefaultClassNodeComp.java 0000664 0000000 0000000 00000001577 13424313624 0027231 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.comp.clazz;
import com.ibm.wala.classLoader.IClass;
import de.infsec.tpl.hashtree.HashTree;
import de.infsec.tpl.hashtree.TreeConfig;
import de.infsec.tpl.hashtree.node.ClassNode;
import de.infsec.tpl.hashtree.node.Node;
import de.infsec.tpl.utils.WalaUtils;
import java.util.ArrayList;
import java.util.Collection;
public class DefaultClassNodeComp implements IClassNodeComp {
@Override
public ClassNode comp(Collection extends Node> methodNodes, IClass clazz, TreeConfig config) {
String className = config.keepClassNames ? WalaUtils.simpleName(clazz) : "";
// default behaviour, just create hash from child nodes
ClassNode cn = new ClassNode(HashTree.compNode(methodNodes, true, config.getHasher()).hash, className);
if (!config.pruneMethods) cn.childs = new ArrayList<>(methodNodes);
return cn;
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/clazz/IClassNodeComp.java 0000664 0000000 0000000 00000000565 13424313624 0026031 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.comp.clazz;
import com.ibm.wala.classLoader.IClass;
import de.infsec.tpl.hashtree.TreeConfig;
import de.infsec.tpl.hashtree.node.ClassNode;
import de.infsec.tpl.hashtree.node.Node;
import java.util.Collection;
public interface IClassNodeComp {
ClassNode comp(Collection extends Node> methodNodes, IClass clazz, TreeConfig config);
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/method/ 0000775 0000000 0000000 00000000000 13424313624 0022512 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/method/IMethodNodeComp.java 0000664 0000000 0000000 00000000410 13424313624 0026326 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.comp.method;
import com.ibm.wala.classLoader.IMethod;
import de.infsec.tpl.hashtree.TreeConfig;
import de.infsec.tpl.hashtree.node.MethodNode;
public interface IMethodNodeComp {
MethodNode comp(IMethod m, TreeConfig config);
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/method/SignatureMethodNodeComp.java 0000664 0000000 0000000 00000021250 13424313624 0030104 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.comp.method;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.Descriptor;
import de.infsec.tpl.hashtree.HashTree;
import de.infsec.tpl.hashtree.TreeConfig;
import de.infsec.tpl.hashtree.node.MethodNode;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.WalaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SignatureMethodNodeComp implements IMethodNodeComp {
private static final Logger logger = LoggerFactory.getLogger(HashTree.class);
@Override
public MethodNode comp(IMethod m, TreeConfig config) {
String desc = normalizeAnonymousInnerClassConstructor(m);
if (desc == null)
desc = getFuzzyDescriptor(m);
String signature = config.keepMethodSignatures? m.getSignature() : "";
return new MethodNode(config.getHasher().putBytes(desc.getBytes()).hash().asBytes(), signature);
}
/**
* This normalization of constructors of anonymous inner classes is due to the fact that
* (in some cases) the dx compiler adds the superclass of the enclosing class as second parameter,
* while the javac does not. This results in different hashes for this classes which implies that
* this library can't be matched exactly although it is the same version. Therefore, this
* normalization will skip this second argument during generation of the fuzzy descriptor.
*
* See example code: method createAdapter() in
* https://github.com/facebook/facebook-android-sdk/blob/f6c4e4c1062bcc74e5a25b3243f6bee2e32d949e/facebook/src/com/facebook/widget/PlacePickerFragment.java
*
* In the disassembled dex the constructor can look like this:
*
* method constructor (Lcom/facebook/widget/PlacePickerFragment;Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V
* .locals 0
* .param p3, "$anonymous0" # Landroid/content/Context;
*
* iput-object p1, p0, Lcom/facebook/widget/PlacePickerFragment$1;->this$0:Lcom/facebook/widget/PlacePickerFragment;
* invoke-direct {p0, p2, p3}, Lcom/facebook/widget/PickerFragment$PickerFragmentAdapter;->(Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V
* return-void
* .end method
*
*
* while the decompiled class file looks as follows:
*
* PlacePickerFragment$1(PlacePickerFragment paramPlacePickerFragment, Context x0) {
* super(paramPlacePickerFragment, x0);
* }
*
*
* @param m the {@link IMethod} to normalize
* @return null if this normalization does not apply, otherwise the normalized fuzzy descriptor {@see getFuzzyDescriptor}
*/
private static String normalizeAnonymousInnerClassConstructor(IMethod m) {
if (WalaUtils.isAnonymousInnerInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 1) {
// this can be anything -> normalize constructor to (X)V
logger.trace("[normalizeAnonymousInnerClassConstructor] found anonymous inner inner class constructor: "+ m.getSignature());
return "(X)V";
}
// check if we have an anonymous inner class constructor with a sufficient number of arguments
if (WalaUtils.isAnonymousInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 2) {
logger.trace("[normalizeAnonymousInnerClassConstructor] method: " + m.getSignature());
logger.trace(Utils.INDENT + "descriptor: " + m.getDescriptor() + " # params: " + m.getNumberOfParameters());
String enclosingClazzName = WalaUtils.simpleName(m.getDeclaringClass());
enclosingClazzName = enclosingClazzName.substring(0, enclosingClazzName.lastIndexOf('$'));
// check if both argument types are custom types
for (int i : new Integer[]{1, 2}) {
if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(1));
boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct);
if (!isAppClazz)
return null;
} else
return null;
}
IClass superClazz = null;
try {
IClass ic = WalaUtils.lookupClass(m.getClassHierarchy(), enclosingClazzName);
superClazz = ic.getSuperclass();
} catch (ClassNotFoundException e) {
// class lookup can also fail for lambdas, e.g. if superclass is kotlin.jvm.internal.Lambda
// we then default to fuzzy descriptor
logger.trace("Could not lookup " + enclosingClazzName + " in bytecode normalization");
return null;
}
String argType1 = Utils.convertToFullClassName(m.getParameterType(1).getName().toString());
String argType2 = Utils.convertToFullClassName(m.getParameterType(2).getName().toString());
// now check whether this normalization needs to be applied
if (argType1.equals(enclosingClazzName) &&
argType2.equals(WalaUtils.simpleName(superClazz))) {
StringBuilder sb = new StringBuilder("(");
// param0 is the object (for non-static calls), param1 the first arg to be skipped (doesn't matter
// if whether we skip param1 or param2 since both are replaced by placeholder value)
for (int i = 2; i < m.getNumberOfParameters(); i++) {
if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i));
boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct);
sb.append(isAppClazz ? customTypeReplacement : m.getParameterType(i).getName().toString());
} else
sb.append(m.getParameterType(i).getName().toString());
}
sb.append(")V");
logger.trace(Utils.INDENT + "> bytecode normalization applied to " + m.getSignature() + " fuzzy desc: " + sb.toString());
return sb.toString();
}
}
return null;
}
private static final String customTypeReplacement = "X";
/**
* A {@link Descriptor} only describes input arg types + return type, e.g.
* The Descriptor of AdVideoView.onError(Landroid/media/MediaPlayer;II)Z is (Landroid/media/MediaPlayerII)Z
* In order to produce a fuzzy (robust against identifier-renaming) descriptor we replace each custom type by a fixed
* replacement, e.g. we receive a descriptor like (XII)Z
* Note: library dependencies, i.e. lib A depends on lib B are not a problem. If we analyze lib A without loading lib B,
* any type of lib B will be loaded with the Application classloader but will _not_ be in the classhierarchy.
* @param m {@link IMethod}
* @return a fuzzy descriptor
*/
private static String getFuzzyDescriptor(IMethod m) {
logger.trace("[getFuzzyDescriptor]");
logger.trace("- signature: " + m.getSignature());
logger.trace("- descriptor: " + m.getDescriptor().toString());
StringBuilder sb = new StringBuilder("(");
for (int i = (m.isStatic()? 0 : 1) ; i < m.getNumberOfParameters(); i++) {
boolean isAppClazz = false;
if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i));
isAppClazz = ct == null || WalaUtils.isAppClass(ct);
sb.append(isAppClazz? customTypeReplacement : m.getParameterType(i).getName().toString());
} else
sb.append(m.getParameterType(i).getName().toString());
//logger.trace(LogConfig.INDENT + "- param ref: " + m.getParameterType(i).getName().toString() + (isAppClazz? " -> " + customTypeReplacement : ""));
}
//logger.trace("");
sb.append(")");
if (m.getReturnType().getClassLoader().equals(ClassLoaderReference.Application)) {
IClass ct = m.getClassHierarchy().lookupClass(m.getReturnType());
sb.append(ct == null || WalaUtils.isAppClass(ct)? customTypeReplacement : m.getReturnType().getName().toString());
} else
sb.append(m.getReturnType().getName().toString());
logger.trace("-> new type: " + sb.toString());
return sb.toString();
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/pckg/ 0000775 0000000 0000000 00000000000 13424313624 0022156 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/pckg/DefaultPackageNodeComp.java 0000664 0000000 0000000 00000001731 13424313624 0027310 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.comp.pckg;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import de.infsec.tpl.hashtree.HashTree;
import de.infsec.tpl.hashtree.TreeConfig;
import de.infsec.tpl.hashtree.node.Node;
import de.infsec.tpl.hashtree.node.PackageNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
public class DefaultPackageNodeComp implements IPackageNodeComp {
private static final Logger logger = LoggerFactory.getLogger(HashTree.class);
@Override
public PackageNode comp(Collection extends Node> classNodes, String packageName, IClassHierarchy cha, TreeConfig config) {
// default behaviour, just create hash from child nodes
PackageNode pn = new PackageNode(HashTree.compNode(classNodes, false, config.getHasher()).hash, (config.keepPackageNames? packageName : ""));
if (!config.pruneClasses) pn.childs = new ArrayList<>(classNodes);
return pn;
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/pckg/IPackageNodeComp.java 0000664 0000000 0000000 00000000631 13424313624 0026112 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.comp.pckg;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import de.infsec.tpl.hashtree.TreeConfig;
import de.infsec.tpl.hashtree.node.Node;
import de.infsec.tpl.hashtree.node.PackageNode;
import java.util.Collection;
public interface IPackageNodeComp {
PackageNode comp(Collection extends Node> classNodes, String packageName, IClassHierarchy cha, TreeConfig config);
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/ 0000775 0000000 0000000 00000000000 13424313624 0021221 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/ClassNode.java 0000664 0000000 0000000 00000002356 13424313624 0023745 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.node;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ClassNode extends Node implements Serializable {
private static final long serialVersionUID = 7790771073564531337L;
public String clazzName;
public ClassNode(byte[] hash, String clazzName) {
super(hash);
this.clazzName = clazzName;
}
public List getMethodNodes() {
return this.childs.stream().map(mn -> (MethodNode) mn).collect(Collectors.toList());
}
/*
@Override
public void debug() {
//logger.info("Debug ClassNode: " + clazzName + " (childs: " + childs.size() + ", " + Hash.hash2Human(hash) + ")");
for (Node n: this.childs) {
HashTreeOLD.MethodNode mn = (HashTreeOLD.MethodNode) n;
logger.info(Utils.INDENT2 + "- " + mn.signature + " :: " + Hash.hash2Str(mn.hash));
}
}*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ClassNode))
return false;
return Arrays.equals(((Node) obj).hash, this.hash);
}
@Override
public String toString() {
return "CNode(" + clazzName + ")";
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/MethodNode.java 0000664 0000000 0000000 00000001227 13424313624 0024114 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.node;
import java.io.Serializable;
import java.util.Arrays;
public class MethodNode extends Node implements Serializable {
private static final long serialVersionUID = 5590771073564531337L;
public String signature;
public MethodNode(byte[] hash, String signature) {
super(hash);
this.signature = signature;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MethodNode))
return false;
return Arrays.equals(((Node) obj).hash, this.hash);
}
@Override
public String toString() {
return "MNode(" + signature + ")";
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/Node.java 0000664 0000000 0000000 00000002212 13424313624 0022746 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.node;
import de.infsec.tpl.hashtree.HashUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TreeSet;
public class Node implements Serializable {
private static final long serialVersionUID = 6690771073564531337L;
public byte[] hash;
public List childs;
public TreeSet versions;
public Node(byte[] hash) {
this.hash = hash;
this.childs = new ArrayList<>();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node))
return false;
return Arrays.equals(((Node) obj).hash, this.hash);
}
@Override
public int hashCode() {
return Arrays.hashCode(hash) + childs.size();
}
@Override
public String toString() {
return HashUtils.hash2Str(this.hash);
}
public int numberOfChilds() {
return this.childs.size();
}
public boolean isLeaf() {
return childs.isEmpty();
}
public boolean isMultiVersionNode() {
return versions != null && !versions.isEmpty();
}
}
LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/PackageNode.java 0000664 0000000 0000000 00000003141 13424313624 0024224 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.hashtree.node;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class PackageNode extends Node implements Serializable {
private static final long serialVersionUID = 3390771073564531337L;
public String packageName;
public PackageNode(byte[] hash, String packageName) {
super(hash);
this.packageName = packageName;
}
/* @Override
public void debug() {
logger.info("Debug PackageNode: " + packageName + " (childs: " + childs.size() + ", " + Hash.hash2Str(hash) + ")");
for (Node n: this.childs) {
HashTreeOLD.ClassNode cn = (HashTreeOLD.ClassNode) n;
logger.info(Utils.INDENT + "- " + cn.clazzName + " :: " + cn.numberOfChilds() + " :: " + Hash.hash2Str(cn.hash));
// cn.debug();
}
}
*/
public List getClassNodes() {
return this.childs.stream()
.map(mn -> (ClassNode) mn)
.collect(Collectors.toList());
}
// TODO
public List getMethodNodes() {
return this.childs.stream()
.map(cn -> cn.childs)
.flatMap(Collection::stream)
.map(mn -> ((MethodNode) mn))
.collect(Collectors.toList());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PackageNode))
return false;
return Arrays.equals(((Node) obj).hash, this.hash);
}
@Override
public String toString() {
return "PNode(" + packageName + ")";
}
}
LibScout-2.3.2/src/de/infsec/tpl/manifest/ 0000775 0000000 0000000 00000000000 13424313624 0020277 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/manifest/IManifestHandler.java 0000664 0000000 0000000 00000001640 13424313624 0024320 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.manifest;
import java.io.InputStream;
/**
* Interface to work on Manifest files
*/
public interface IManifestHandler {
/**
* Process Android manifest files
* @param stream Input stream of manifest file
*/
public void handleManifest(InputStream stream);
}
LibScout-2.3.2/src/de/infsec/tpl/manifest/ProcessManifest.java 0000664 0000000 0000000 00000026326 13424313624 0024260 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.manifest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParser;
import android.content.res.AXmlResourceParser;
import pxb.android.axml.AXMLPrinter;
public class ProcessManifest implements Serializable {
private static final long serialVersionUID = -6763632946511685516L;
private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.manifest.ProcessManifest.class);
// TODO: make a ParsedManifest class with those values
private Set entryPointsClasses = new HashSet();
private String packageName = "";
private int versionCode = 0;
private int minSdkVersion = 1; // if not explicitly set, defaults to 1
private int targetSdkVersion = 1; // if not explicitly set, defaults to minSdkValue
private String sharedUserId = "";
private String applicationName = "";
private Set permissions = new TreeSet();
private Set libDependencies = new HashSet();
public final String MANIFEST_FILENAME = "AndroidManifest.xml";
/**
* Opens the given apk file and provides the given handler with a stream for
* accessing the contained android manifest file
* @param apk The apk file to process
* @param handler The handler for processing the apk file
*
* @author Steven Arzt
* @author Erik Derr
*/
private void handleAndroidManifestFile(String apk, IManifestHandler handler) {
File apkF = new File(apk);
if (!apkF.exists())
throw new RuntimeException("file '" + apk + "' does not exist!");
boolean found = false;
try {
ZipFile archive = null;
try {
archive = new ZipFile(apkF);
Enumeration> entries = archive.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String entryName = entry.getName();
// We are dealing with the Android manifest
if (entryName.equals(MANIFEST_FILENAME)) {
found = true;
handler.handleManifest(archive.getInputStream(entry));
break;
}
}
}
finally {
if (archive != null)
archive.close();
}
}
catch (Exception e) {
throw new RuntimeException("Error when looking for manifest in apk: " + e);
}
if (!found)
throw new RuntimeException("No manifest file found in apk");
}
public void loadManifestFile(String apk) {
handleAndroidManifestFile(apk, new IManifestHandler() {
@Override
public void handleManifest(InputStream stream) {
loadClassesFromBinaryManifest(stream);
}
});
}
// TODO TODO: parse meta data
protected void loadClassesFromBinaryManifest(InputStream manifestIS) {
try {
AXmlResourceParser parser = new AXmlResourceParser();
parser.open(manifestIS);
int type = -1;
boolean applicationEnabled = true;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
switch (type) {
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
String tagName = parser.getName();
if (tagName.equals("manifest")) {
this.packageName = getAttributeValue(parser, "package");
this.sharedUserId = getAttributeValue(parser, "sharedUserId");
try {
this.versionCode = Integer.parseInt(getAttributeValue(parser, "versionCode"));
} catch (NumberFormatException e) {
logger.warn("Could not parse versionCode: " + getAttributeValue(parser, "versionCode"));
}
// TODO parse shareduser id label if we have a string parser
} else if (tagName.equals("activity")
|| tagName.equals("receiver")
|| tagName.equals("service")
|| tagName.equals("provider")) {
// We ignore disabled activities
if (!applicationEnabled)
continue;
String attrValue = getAttributeValue(parser, "enabled");
if (attrValue != null && attrValue.equals("false"))
continue;
// Get the class name
attrValue = getAttributeValue(parser, "name");
entryPointsClasses.add(expandClassName(attrValue));
}
else if (tagName.equals("uses-permission")) {
String permissionName = getAttributeValue(parser, "name");
// We probably don't want to do this in some cases, so leave it
// to the user
// permissionName = permissionName.substring(permissionName.lastIndexOf(".") + 1);
this.permissions.add(permissionName);
}
else if (tagName.equals("uses-library")) {
String libraryName = getAttributeValue(parser, "name");
this.libDependencies.add(libraryName);
}
else if (tagName.equals("uses-sdk")) {
try {
this.minSdkVersion = Integer.parseInt(getAttributeValue(parser, "minSdkVersion"));
} catch (NumberFormatException e) {
logger.warn("Could not parse minSdkVersion: " + getAttributeValue(parser, "minSdkVersion"));
}
try {
this.targetSdkVersion = Integer.parseInt(getAttributeValue(parser, "targetSdkVersion"));
} catch (NumberFormatException e) { /* targetSdkValue is optional */ }
}
else if (tagName.equals("application")) {
// Check whether the application is disabled
String attrValue = getAttributeValue(parser, "enabled");
applicationEnabled = (attrValue == null || !attrValue.equals("false"));
// Get the application name which is also the fully-qualified
// name of the custom application object
this.applicationName = getAttributeValue(parser, "name");
if (this.applicationName != null && !this.applicationName.isEmpty())
this.entryPointsClasses.add(expandClassName(this.applicationName));
}
break;
case XmlPullParser.END_TAG:
break;
case XmlPullParser.TEXT:
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Generates a full class name from a short class name by appending the
* globally-defined package when necessary
* @param className The class name to expand
* @return The expanded class name for the given short name
*/
private String expandClassName(String className) {
if (className.startsWith(".")) {
return this.packageName + className;
} else if (!className.contains(".")) { // if only the classname is present without leading dot, Android's manifest parser safely expands the class name as if there was a leading dot
return this.packageName + "." + className;
} else {
return className;
}
}
private String getAttributeValue(AXmlResourceParser parser, String attributeName) {
for (int i = 0; i < parser.getAttributeCount(); i++)
if (parser.getAttributeName(i).equals(attributeName))
return AXMLPrinter.getAttributeValue(parser, i);
return "";
}
protected void loadClassesFromTextManifest(InputStream manifestIS) {
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = db.parse(manifestIS);
Element rootElement = doc.getDocumentElement();
this.packageName = rootElement.getAttribute("package");
NodeList appsElement = rootElement.getElementsByTagName("application");
if (appsElement.getLength() > 1)
throw new RuntimeException("More than one application tag in manifest");
for (int appIdx = 0; appIdx < appsElement.getLength(); appIdx++) {
Element appElement = (Element) appsElement.item(appIdx);
this.applicationName = appElement.getAttribute("android:name");
if (this.applicationName != null && !this.applicationName.isEmpty())
this.entryPointsClasses.add(expandClassName(this.applicationName));
NodeList activities = appElement.getElementsByTagName("activity");
NodeList receivers = appElement.getElementsByTagName("receiver");
NodeList services = appElement.getElementsByTagName("service");
for (int i = 0; i < activities.getLength(); i++) {
Element activity = (Element) activities.item(i);
loadManifestEntry(activity, "android.app.Activity", this.packageName);
}
for (int i = 0; i < receivers.getLength(); i++) {
Element receiver = (Element) receivers.item(i);
loadManifestEntry(receiver, "android.content.BroadcastReceiver", this.packageName);
}
for (int i = 0; i < services.getLength(); i++) {
Element service = (Element) services.item(i);
loadManifestEntry(service, "android.app.Service", this.packageName);
}
NodeList permissions = appElement.getElementsByTagName("uses-permission");
for (int i = 0; i < permissions.getLength(); i++) {
Element permission = (Element) permissions.item(i);
this.permissions.add(permission.getAttribute("android:name"));
}
}
}
catch (IOException ex) {
logger.error("Could not parse manifest: " + ex.getMessage());
ex.printStackTrace();
} catch (ParserConfigurationException ex) {
logger.error("Could not parse manifest: " + ex.getMessage());
ex.printStackTrace();
} catch (SAXException ex) {
logger.error("Could not parse manifest: " + ex.getMessage());
ex.printStackTrace();
}
}
private void loadManifestEntry(Element activity, String baseClass, String packageName) {
if (activity.getAttribute("android:enabled").equals("false"))
return;
String className = activity.getAttribute("android:name");
entryPointsClasses.add(expandClassName(className));
}
public Set getEntryPointClasses() {
return this.entryPointsClasses;
}
public String getApplicationName() {
return this.applicationName;
}
public Set getPermissions() {
return this.permissions;
}
public String getPackageName() {
return this.packageName;
}
public int getVersionCode() {
return this.versionCode;
}
public String getSharedUserId() {
return this.sharedUserId;
}
public Set getLibraryDependencies() {
return this.libDependencies;
}
public int getMinSdkVersion() { return this.minSdkVersion; }
public int getTargetSdkVersion() { return this.targetSdkVersion > 1? this.targetSdkVersion : this.minSdkVersion; }
}
LibScout-2.3.2/src/de/infsec/tpl/modules/ 0000775 0000000 0000000 00000000000 13424313624 0020141 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/ 0000775 0000000 0000000 00000000000 13424313624 0021401 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/DependencyAnalysis.java 0000664 0000000 0000000 00000020743 13424313624 0026034 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.modules.libapi;
import com.github.zafarkhaja.semver.Version;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.shrikeCT.InvalidClassFileException;
import de.infsec.tpl.pkg.PackageTree;
import de.infsec.tpl.pkg.PackageUtils;
import de.infsec.tpl.stats.Exportable;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.WalaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* Feature: Tests for nested/secondary library dependencies, i.e. libraries depending on other libraries.
* The analysis is performed for every documented API of every lib version via reachability analysis
*/
public class DependencyAnalysis {
private static final Logger logger = LoggerFactory.getLogger(DependencyAnalysis.class);
/**
* Secondary lib dependencies for every version and documented API
*/
class LibDependencies implements Exportable {
Version version;
// documented APIs to set of APIs from dependencies
Map> api2Dependencies;
LibDependencies(Version version, Map> api2Dependencies) {
this.version = version;
this.api2Dependencies = api2Dependencies;
}
@Override
public DependencyAnalysis.LibDependencies.Export export() {
return new Export(this);
}
public class Export {
String version;
Map> api2Dependencies = new HashMap>();
public Export(LibDependencies deps) {
this.version = deps.version.toString();
for (IMethod m: deps.api2Dependencies.keySet())
this.api2Dependencies.put(m.getSignature(), deps.api2Dependencies.get(m).stream().map(c -> c.getDeclaredTarget().getSignature()).collect(Collectors.toSet()));
}
}
}
// need to infer expected and actual semver
protected Map run(LibApiStats stats) {
Map version2Deps = new TreeMap();
for (Version version: stats.getVersions()) {
logger.info(Utils.INDENT + "- version: " + version);
Map> api2Dependencies = analyzeDependencies(stats.getDocumentedAPIs(version));
version2Deps.put(version, new LibDependencies(version, api2Dependencies));
}
// debug collect all dependencies (APIs) per lib (incl. all versions)
Set signatures = new TreeSet();
version2Deps.values().
forEach(ld -> {
ld.api2Dependencies.values()
.forEach(set -> {
set.forEach(csf -> signatures.add(PackageUtils.getPackageName(csf.getDeclaredTarget().getSignature())));
})
;}
);
logger.info(Utils.INDENT + "-> Dependencies of " + stats.libName);
signatures.forEach(s -> logger.info(Utils.INDENT2 + "- dep: " + s));
PackageTree pt = PackageTree.make(signatures);
pt.print(true);
/// TODO show empty packages + non-empty on depth == 1
return version2Deps;
}
private Map> analyzeDependencies(Set pubApis) {
HashMap> secDeps = new HashMap>();
// perform method reachability analysis for every pubAPI
for (IMethod docApi: pubApis) {
logger.debug("-> check API: " + docApi.getSignature());
LinkedList queue = new LinkedList();
queue.push(docApi);
// types to methods signatures in which they are used
Set unresolvedCalls = new HashSet();
Set visited = new HashSet();
// Check method invocations
try {
while (!queue.isEmpty()) {
IMethod m = queue.poll();
if (!visited.add(m.getSignature()))
continue;
for (CallSiteReference csf : com.ibm.wala.classLoader.CodeScanner.getCallSites(m)) {
IClass c = m.getClassHierarchy().lookupClass(csf.getDeclaredTarget().getDeclaringClass());
IMethod inv = m.getClassHierarchy().resolveMethod(csf.getDeclaredTarget());
if (inv == null) {
// inherited final methods can not be looked up or (abstract) interface methods inherited from another interface
// workaround, we check if the class is part of the CHA
if (c == null) {
logger.trace(" ## unresolved call: " + csf.getDeclaredTarget().getSignature() + (csf.isInterface() ? " [Interface]" : ""));
unresolvedCalls.add(csf);
}
} else {
if (c != null && WalaUtils.isAppClass(c))
queue.push(inv);
}
}
}
} catch (InvalidClassFileException e) {
logger.error(Utils.stacktrace2Str(e));
}
if (!unresolvedCalls.isEmpty()) {
secDeps.put(docApi, unresolvedCalls);
}
}
// Results
secDeps.keySet()
.forEach(m -> {
logger.debug("- Method: " + m.getSignature());
secDeps.get(m).stream()
.map(c -> c.getDeclaredTarget().getSignature())
.sorted().distinct()
.forEach(s -> logger.debug(" - dep: " + s));
});
logger.info(Utils.INDENT2 + "- " + secDeps.size() + "/" + pubApis.size() + " APIs with secondary dependencies");
return secDeps;
}
/* private void printSTATS(){
logger.info("# Processed libs: " + libName2Stats.size());
logger.info(" - w/o secondary dependencies: " + libName2Stats.values().stream().filter(s -> s.version2Dependencies.isEmpty()).count());
libName2Stats.values().stream().filter(s -> s.version2Dependencies.isEmpty()).forEach(st -> logger.error(Utils.INDENT + "- lib: " + st.libName));
Set rootPackages = new TreeSet();
libName2Stats.values().stream().map(s -> s.rootPackages).forEach(rootPackages::addAll);
// rootPackages.forEach(rp -> logger.error("-- root pckg: " + rp));
logger.info(" - with secondary dependencies: " + libName2Stats.values().stream().filter(s -> s.version2Dependencies.size() > 0).count());
libName2Stats.values().forEach(s -> {
s.version2Dependencies.keySet().forEach(v -> {
Set prunedDeps = new TreeSet();
for (String dep: s.version2Dependencies.get(v)) {
boolean added = false;
for (String rp: rootPackages) {
if (dep.startsWith(rp)) {
added = true;
prunedDeps.add(rp);
break;
}
}
if (!added) prunedDeps.add(dep);
};
s.version2Dependencies.put(v, prunedDeps);
});
});
for (LibApiStats stats: libName2Stats.values()) {
if (!stats.version2Dependencies.isEmpty()) {
logger.info(Utils.INDENT + "- lib: " + stats.libName);
// per lib package dep
Set perLibDep = new TreeSet();
stats.version2Dependencies.values().forEach(perLibDep::addAll);
perLibDep.forEach(dep -> logger.info(Utils.INDENT2 + "- dep: " + dep));
*/
// per version package dep
/*for (String v: stats.version2Dependencies.keySet().stream().sorted().collect(Collectors.toList())) {
logger.info(" - version: " + v);
for (String dep: stats.version2Dependencies.get(v))
logger.info(" -- dep: " + dep);
}*/
/* }
}
}*/
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/JvmMethodAccessFlags.java 0000664 0000000 0000000 00000010502 13424313624 0026236 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.modules.libapi;
import com.ibm.wala.classLoader.IMethod;
import de.infsec.tpl.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Spec taken from https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
*/
public enum JvmMethodAccessFlags {
NO_FLAG (0x0000, "no-flag"),
PUBLIC (0x0001, "public"),
PRIVATE (0x0002, "private"),
PROTECTED (0x0004, "protected"),
STATIC (0x0008, "static"),
FINAL (0x0010, "final"),
ABSTRACT (0x0400, "abstract"),
PACKAGE_PROTECTED (0x2000, "package-proteced");
private int value;
private String accessFlagName;
//cache the array of all AccessFlags, because .values() allocates a new array for every call
private final static JvmMethodAccessFlags[] allFlags;
private final static List validFlagValues;
private static HashMap accessFlagsByName;
static {
allFlags = JvmMethodAccessFlags.values();
validFlagValues = new ArrayList();
for (JvmMethodAccessFlags flag: allFlags)
validFlagValues.add(flag.getValue());
accessFlagsByName = new HashMap();
for (JvmMethodAccessFlags accessFlag: allFlags) {
accessFlagsByName.put(accessFlag.accessFlagName, accessFlag);
}
}
private JvmMethodAccessFlags(int value, String accessFlagName) {
this.value = value;
this.accessFlagName = accessFlagName;
}
private static String flags2Str(JvmMethodAccessFlags[] accessFlags) {
int size = 0;
for (JvmMethodAccessFlags accessFlag: accessFlags) {
size += accessFlag.toString().length() + 1;
}
StringBuilder sb = new StringBuilder(size);
for (JvmMethodAccessFlags accessFlag: accessFlags) {
sb.append(accessFlag.toString());
sb.append(" ");
}
if (accessFlags.length > 0) {
sb.delete(sb.length() - 1, sb.length());
}
return sb.toString();
}
public static boolean isValidFlag(int code) {
return validFlagValues.contains(code);
}
public static String flags2Str(int code) {
List matchedFlags = new ArrayList();
for (JvmMethodAccessFlags flag: allFlags) {
if ((code & flag.value) != 0x0) {
matchedFlags.add(flag.accessFlagName + "(" + flag.value + ")");
}
}
return Utils.join(matchedFlags, ",");
}
public static String flags2Str(IMethod m) {
return flags2Str(getMethodAccessCode(m));
}
public static JvmMethodAccessFlags getAccessFlag(String accessFlag) {
return accessFlagsByName.get(accessFlag);
}
public int getValue() {
return value;
}
public String toString() {
return accessFlagName;
}
public static int getAccessFlagFilter(JvmMethodAccessFlags... flags) {
int filter = NO_FLAG.getValue();
if (flags != null) {
for (JvmMethodAccessFlags flag: flags) {
if (!JvmMethodAccessFlags.isValidFlag(flag.getValue())) continue;
filter |= flag.getValue();
}
}
return filter;
}
public static int getPublicOnlyFilter() {
return getAccessFlagFilter(JvmMethodAccessFlags.PRIVATE, JvmMethodAccessFlags.PROTECTED, JvmMethodAccessFlags.STATIC,
JvmMethodAccessFlags.FINAL, JvmMethodAccessFlags.ABSTRACT, JvmMethodAccessFlags.PACKAGE_PROTECTED);
}
public static int getMethodAccessCode(IMethod m) {
int res = 0x0;
if (m == null)
return res;
if (m.isPublic()) {
res |= JvmMethodAccessFlags.PUBLIC.getValue();
} else if (m.isProtected()) {
res |= JvmMethodAccessFlags.PROTECTED.getValue();
} else if (m.isPrivate()) {
res |= JvmMethodAccessFlags.PRIVATE.getValue();
} else {
res |= JvmMethodAccessFlags.PACKAGE_PROTECTED.getValue();
}
if (m.isStatic())
res |= JvmMethodAccessFlags.STATIC.getValue();
if (m.isFinal())
res |= JvmMethodAccessFlags.FINAL.getValue();
if (m.isAbstract())
res |= JvmMethodAccessFlags.ABSTRACT.getValue();
return res;
}
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/LibApiComparator.java 0000664 0000000 0000000 00000042702 13424313624 0025441 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.modules.libapi;
import com.github.zafarkhaja.semver.Version;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import de.infsec.tpl.stats.Exportable;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.VersionWrapper;
import de.infsec.tpl.utils.WalaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* Feature: Tests the relationship/compatibility of documented API sets of consecutive versions
*/
public class LibApiComparator {
private static final Logger logger = LoggerFactory.getLogger(LibApiComparator.class);
private Map version2ApiDiff;
/**
* API Diff Statistics compared to predecessor version
*/
class ApiDiff implements Exportable {
Version v;
int apiCount;
VersionWrapper.SEMVER expectedSemver = null;
VersionWrapper.SEMVER actualSemver = null;
Set removed = new HashSet();
Set added = new HashSet();
Map> alternatives = new HashMap>();
ApiDiff(Version v, int apiCount) {
this.v = v;
this.apiCount = apiCount;
}
public void print(boolean verbose) {
boolean diff = false;
boolean critical = false;
if (expectedSemver != null) {
diff = !expectedSemver.equals(actualSemver);
critical = (expectedSemver.equals(VersionWrapper.SEMVER.PATCH) || expectedSemver.equals(VersionWrapper.SEMVER.MINOR)) &&
actualSemver.equals(VersionWrapper.SEMVER.MAJOR);
}
// version, #docAPI, apiCountDiff expSemver, actSemver, diff?, critical?
logger.info(String.format("%10s %10d %-15s %8s %8s %10s %15s",
v.toString(),
apiCount,
added.size() > 0 || removed.size() > 0? " (+" + added.size() + "/-" + removed.size() + ")" : "",
expectedSemver != null? expectedSemver.toString() : "---",
actualSemver != null? actualSemver.toString() : "---",
diff? "[DIFF]" : "",
critical? "[CRITICAL]" : ""));
if (verbose) {
final int LIMIT = 15; // print max number of methods
removed.stream().map(IMethod::getSignature).sorted().limit(LIMIT).forEach(m -> logger.info(Utils.INDENT2 + "- removed: " + m));
for (IMethod m: alternatives.keySet()) {
logger.info(Utils.INDENT2 + "Alternatives for " + m.getSignature());
alternatives.get(m).stream().map(IMethod::getSignature).sorted().forEach(s -> logger.info(Utils.indent(3) + "> alt: " + s));
}
added.stream().map(IMethod::getSignature).sorted().limit(LIMIT).forEach(m -> logger.info(Utils.INDENT2 + "+ added: " + m));
}
}
@Override
public Export export() {
return new Export(this);
}
public class Export {
String version;
int apiCount;
int apiAdditionsCount;
int apiDeletionsCount;
Set apiAdditions = new TreeSet();
Set apiDeletions = new TreeSet();
String expectedSemver;
String actualSemver;
Map> alternatives = new TreeMap>();
public Export(ApiDiff diff, boolean verbose) {
version = diff.v.toString();
apiCount = diff.apiCount;
apiAdditionsCount = diff.added.size();
apiDeletionsCount = diff.removed.size();
actualSemver = diff.actualSemver == null? "" : diff.actualSemver.name();
expectedSemver = diff.expectedSemver == null? "" : diff.expectedSemver.name();
for (IMethod m: diff.alternatives.keySet()) {
Set apis = diff.alternatives.get(m).stream().map(IMethod::getSignature).collect(Collectors.toSet());
alternatives.put(m.getSignature(), apis);
}
if (verbose) {
apiAdditions = diff.added.stream().map(IMethod::getSignature).sorted().collect(Collectors.toSet());
apiDeletions = diff.removed.stream().map(IMethod::getSignature).sorted().collect(Collectors.toSet());
}
}
public Export(ApiDiff diff) {
this(diff, false);
}
}
}
// need to infer expected and actual semver
protected Map run(LibApiStats stats) {
version2ApiDiff = new TreeMap();
Map> version2Api = generatePerVersionApiSet(stats);
for (Version v: version2Api.keySet()) {
version2ApiDiff.put(v, new ApiDiff(v, version2Api.get(v).size()));
}
// infer expected/actual semver
inferExpectedSemver();
inferActualSemver(version2Api);
inferAlternativeAPIs(version2Api);
// if (logger.isDebugEnabled()) {
logger.info("======================================");
logger.info("== Library: " + stats.libName + " ==");
logger.info("======================================");
printStats();
// }
return version2ApiDiff;
}
private void printStats() {
logger.info(String.format("%10s %10s %-15s %10s %10s %10s %15s",
"Version", "#docAPI", " apiDiff", "expSemver", "actSemver", " diff? ", " critical? "));
logger.info("---------------------------------------------------------------------------------------------");
for (Version v: version2ApiDiff.keySet()) {
version2ApiDiff.get(v).print(false);//true);//false);
}
logger.info("---------------------------------------------------------------------------------------------");
}
void inferExpectedSemver() {
Iterator it = version2ApiDiff.keySet().iterator();
Version old = it.next();
while (it.hasNext()) {
Version cur = it.next();
VersionWrapper.SEMVER sem = VersionWrapper.getExpectedSemver(old,cur);
version2ApiDiff.get(cur).expectedSemver = sem;
logger.debug(Utils.INDENT2 + "Expected SemVer:: " + old.toString() + " : " + cur.toString() + " -> " + sem.name());
old = cur;
}
}
Map> generatePerVersionApiSet(LibApiStats stats) {
Map> version2Api = new TreeMap>();
for (Version v: stats.getVersions()) {
Set apis = new HashSet();
for (IMethod api: stats.api2Versions.keySet()) {
if (stats.api2Versions.get(api).contains(v))
apis.add(api);
}
version2Api.put(v, apis);
}
return version2Api;
}
void inferActualSemver(Map> version2Api) {
Iterator it = version2ApiDiff.keySet().iterator();
Version v0 = it.next();
while (it.hasNext()) {
Version v1 = it.next();
VersionWrapper.SEMVER sem = compareApis(version2Api.get(v0), version2Api.get(v1));
version2ApiDiff.get(v1).actualSemver = sem;
// determine added/removed APIs
if (!sem.equals(VersionWrapper.SEMVER.PATCH)) {
Set removed = new HashSet(version2Api.get(v0));
removed.removeAll(version2Api.get(v1));
version2ApiDiff.get(v1).removed = removed;
Set added = new HashSet(version2Api.get(v1));
added.removeAll(version2Api.get(v0));
version2ApiDiff.get(v1).added = added;
}
logger.debug(Utils.INDENT2 + "Actual SemVer:: " + v0.toString() + " : " + v1.toString() + " -> " + sem.name());
v0 = v1;
}
}
/*
* Check for each major version (actual semver) for alternative
* APIs for each removed API
* NOTE: This is getting really slow if there are 10k+ doc APIs and more than
* 1k+ API removals between adjacent versions
*/
void inferAlternativeAPIs(Map> version2Api) {
for (Version v: version2ApiDiff.keySet()) {
ApiDiff diff = version2ApiDiff.get(v);
if (diff.actualSemver != null && diff.actualSemver.equals(VersionWrapper.SEMVER.MAJOR)) {
for (IMethod m: diff.removed) {
Set alternatives = version2Api.get(v).parallelStream()
.filter(api -> !diff.removed.contains(api)) // exclude removed apis
.filter(api -> isAlternativeApi(m, api))
.collect(Collectors.toSet());
// if we have three or more alternatives (e.g. renamed methods with one argument)
// the suggestions will probably be wrong -> do not store any alternatives
if (alternatives.size() <= 2)
diff.alternatives.put(m, alternatives);
}
}
}
}
/**
* Check for alternative APIs in case an API is no longer available in new library version
* Tests include
* 1. whether only the method name was renamed (same descriptor)
* 2. whether method name/return type are the same but one or more argument types have been generalized
* (e.g. ArrayList to List|Collection)
* 3. Same method one new argument was prepended/appended
* 4. Same method same arguments, different return type (e.g. String -> String[])
* @param target
* @param test
* @return
*/
protected static boolean isAlternativeApi(IMethod target, IMethod test) {
// Test2
if (isApiCompatible(target, test))
return true;
// check whether both APIs reside in the same code Package/Class
if (! WalaUtils.simpleName(target.getDeclaringClass()).equals(WalaUtils.simpleName(test.getDeclaringClass())))
return false;
// check for changes in access specifier
if (JvmMethodAccessFlags.getMethodAccessCode(target) != JvmMethodAccessFlags.getMethodAccessCode(test)) {
logger.trace("Access Flags incompatible: old: " + JvmMethodAccessFlags.flags2Str(target) + " new: " + JvmMethodAccessFlags.flags2Str(test));
return false;
}
// Test1: check whether method was renamed (with same descriptor)
// Since this is very fuzzy, we further require
// - constructors can't be an alternative to non-constructors
// - at least one argument (still fuzzy for methods with one primitive/String arg)
// TODO: at least one non-framework arg || at least two prim/framework args
if (! WalaUtils.getName(target).equals(WalaUtils.getName(test))) {
int numberOfArgs = target.getNumberOfParameters() - (target.isStatic()? 0 : 1);
return target.getDescriptor().toString().equals(test.getDescriptor().toString()) &&
numberOfArgs > 0 &&
(!WalaUtils.getName(target).equals("")) &&
(!WalaUtils.getName(test).equals(""));
}
// Test3: introduction of new argument at first/last position
if (WalaUtils.getName(target).equals(WalaUtils.getName(test)) && // same method name
(target.getReturnType().toString().equals(test.getReturnType().toString())) && // same return type
(target.getNumberOfParameters() == test.getNumberOfParameters()-1)) { // one more arg
// check if new arg was prepended
boolean check = true;
for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) {
if (!target.getParameterType(i).getName().toString().equals(test.getParameterType(i+1).getName().toString())) {
check = false;
break;
}
}
if (check) return true; // prepended
// check if new arg was appended
check = true;
for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) {
if (!target.getParameterType(i).getName().toString().equals(test.getParameterType(i).getName().toString())) {
check = false;
break;
}
}
if (check) return true; // appended
}
// Test4: same arg list: different return type
if (WalaUtils.getName(target).equals(WalaUtils.getName(test)) && // same method name
(!target.getReturnType().toString().equals(test.getReturnType().toString())) && // different return type
(target.getNumberOfParameters() == test.getNumberOfParameters())) { // same number of args
// check that all arg types are equal
boolean equal = true;
for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) {
if (!target.getParameterType(i).getName().toString().equals(test.getParameterType(i).getName().toString())) {
equal = false;
break;
}
}
if (equal) return true;
}
return false;
}
/**
* Check whether two API are compatible, i.e. no code changes (calls to APIs) have to be made by app developers
* This checks whether method name/return type are the same but one or more argument types have been generalized
* (e.g. ArrayList to List|Collection
*/
protected static boolean isApiCompatible(IMethod target, IMethod test) {
// check whether both APIs reside in the same code Package/Class
if (! WalaUtils.simpleName(target.getDeclaringClass()).equals(WalaUtils.simpleName(test.getDeclaringClass())))
return false;
// check for changes in access specifier
if (JvmMethodAccessFlags.getMethodAccessCode(target) != JvmMethodAccessFlags.getMethodAccessCode(test)) {
logger.trace("Access Flags incompatible: old: " + JvmMethodAccessFlags.flags2Str(target) + " new: " + JvmMethodAccessFlags.flags2Str(test));
return false;
}
// check whether method name changed
if (! WalaUtils.getName(target).equals(WalaUtils.getName(test))) {
return false;
}
// if method name changed, check whether non primitive arguments were generified
// - first check wether return types are the same
if (! target.getReturnType().toString().equals(test.getReturnType().toString()))
return false;
// - check argument types for generalization, i.e. when a ArrayList argument was changed to List
if (target.getNumberOfParameters() == test.getNumberOfParameters()) {
for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) {
if (!target.getParameterType(i).toString().equals(test.getParameterType(i).toString())) {
// skip primitive types
if (test.getParameterType(i).isPrimitiveType()) return false;
// check if test argument type is supertype
IClass paramTestClazz = test.getDeclaringClass().getClassHierarchy().lookupClass(test.getParameterType(i));
// could be null because it's a type of a different library (sub-dependency) that is not part of the cha
if (paramTestClazz == null) {
logger.warn("Could not lookup superclazz (maybe sub-dependency):");
logger.warn(Utils.INDENT + "target param type: " + target.getParameterType(i).getName().toString());
logger.warn(Utils.INDENT + "test param type : " + test.getParameterType(i).getName().toString());
return false;
}
List superClazzes = WalaUtils.getSuperClasses(paramTestClazz);
boolean found = false;
for (IClass ic: superClazzes) {
if (WalaUtils.simpleName(target.getDeclaringClass()).equals(WalaUtils.simpleName(ic))) {
found = true;
break;
}
}
if (!found)
return false; // incompatible type replacement in test API
}
}
return true; // all arguments are the same
}
return false;
}
// IMethod test as IMethod objects stem from different IClassHierarchies
protected static boolean equals(IMethod m1, IMethod m2) {
return m1.getSignature().equals(m2.getSignature()) &&
JvmMethodAccessFlags.getMethodAccessCode(m1) == JvmMethodAccessFlags.getMethodAccessCode(m2);
}
protected static VersionWrapper.SEMVER compareApis(Set s0, Set s1) {
if (s1.containsAll(s0)) {
if (s0.size() == s1.size()) // exact match
return VersionWrapper.SEMVER.PATCH;
else if (s1.size() > s0.size()) // contains complete set of APIs and additions -> backwards-compatible
return VersionWrapper.SEMVER.MINOR;
}
return VersionWrapper.SEMVER.MAJOR;
}
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/LibApiStats.java 0000664 0000000 0000000 00000005571 13424313624 0024433 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.modules.libapi;
import com.github.zafarkhaja.semver.Version;
import com.ibm.wala.classLoader.IMethod;
import de.infsec.tpl.config.LibScoutConfig;
import de.infsec.tpl.stats.Exportable;
import de.infsec.tpl.utils.VersionWrapper;
import java.util.*;
import java.util.stream.Collectors;
/**
* Container class to store library API changes across versions
*/
public class LibApiStats implements Exportable {
public String libName;
// set of version strings
private Set versions;
// maps documented API signatures to list of library versions including them
public Map> api2Versions;
public Map version2Diff;
public Map version2Deps;
public class Export {
public String libName;
public List versions;
public List apiDiffs;
public List libDeps;
// maps documented API signatures to list of library versions including them
public Map> api2Versions;
public Export(LibApiStats stats) {
this.libName = stats.libName;
this.versions = stats.versions.stream().map(Version::toString).collect(Collectors.toList());
this.apiDiffs = stats.version2Diff.values().stream().map(LibApiComparator.ApiDiff::export).collect(Collectors.toList());
if (LibScoutConfig.libDependencyAnalysis)
this.libDeps = stats.version2Deps.values().stream().map(DependencyAnalysis.LibDependencies::export).collect(Collectors.toList());
this.api2Versions = new HashMap<>();
for (IMethod m: stats.api2Versions.keySet()) {
this.api2Versions.put(m.getSignature(), stats.api2Versions.get(m).stream().map(Version::toString).collect(Collectors.toList()));
}
}
}
@Override
public Export export() {
return new Export(this);
}
public LibApiStats(String libName) {
this.libName = libName;
this.api2Versions = new HashMap<>();
this.versions = new TreeSet<>();
}
/*
* Update functions
*/
public void addVersion(String version) {
versions.add(VersionWrapper.valueOf(version));
}
public Set getVersions() {
return versions;
}
void setDocumentedAPIs(String version, Set docAPIs) {
for (IMethod api: docAPIs) {
if (!api2Versions.containsKey(api))
api2Versions.put(api, new TreeSet());
api2Versions.get(api).add(VersionWrapper.valueOf(version));
}
}
Set getDocumentedAPIs(Version version) {
return api2Versions.keySet().stream().filter(m -> api2Versions.get(m).contains(version)).collect(Collectors.toSet());
}
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/LibraryApiAnalysis.java 0000664 0000000 0000000 00000014523 13424313624 0026013 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.modules.libapi;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.cha.ClassHierarchy;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.ClassLoaderReference;
import de.infsec.tpl.TplCLI;
import de.infsec.tpl.config.LibScoutConfig;
import de.infsec.tpl.profile.LibraryDescription;
import de.infsec.tpl.utils.AarFile;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.xml.XMLParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.jar.JarFile;
/**
* = Library API Analysis =
*
* - Extracts the public (documented) API from original library code packages
* - Checks API stability across versions for the same library (expected/actual semantic versioning)
* - Searches for API alternatives in case of removed APIs
* - Results of type {@link LibApiStats.Export} are written to {@link TplCLI.CliArgs#ARG_JSON_DIR}
*/
public class LibraryApiAnalysis {
private static final Logger logger = LoggerFactory.getLogger(LibraryApiAnalysis.class);
// Mapping from ->
private Map meta2Code = new HashMap();
private Map libName2Stats = new HashMap();
public static void run(File libDir) {
new LibraryApiAnalysis(libDir);
}
private LibraryApiAnalysis(File libDir) {
locateLibrarySDKs(libDir);
parseLibrarySDKs(true);
if (LibScoutConfig.libDependencyAnalysis)
analyzeSecondaryDependencies();
analyzeLibraryAPIs();
// write stats to disk
libName2Stats.values().forEach(s -> writeLibData(s));
}
private void analyzeLibraryAPIs() {
LibApiComparator comp = new LibApiComparator();
for (LibApiStats lib: libName2Stats.values()) {
logger.info("- Analyze lib APIs: " + lib.libName);
lib.version2Diff = comp.run(lib);
}
}
private void analyzeSecondaryDependencies() {
DependencyAnalysis dp = new DependencyAnalysis();
for (LibApiStats lib: libName2Stats.values()) {
logger.info("- Analyze secondary deps: " + lib.libName);
lib.version2Deps = dp.run(lib);
}
}
private void writeLibData(LibApiStats stats) {
// output results in json format
File jsonOutputFile = new File(LibScoutConfig.jsonDir + File.separator + "libApis" + File.separator + stats.libName + ".json");
try {
Utils.obj2JsonFile(jsonOutputFile, stats.export());
logger.info("Results for library: " + stats.libName + " written to " + jsonOutputFile);
} catch (IOException e) {
logger.warn("Could not write json results: " + Utils.stacktrace2Str(e));
}
}
private void parseLibrarySDKs(boolean skipBeta) {
for (File libXML : meta2Code.keySet()) {
try {
LibraryDescription ld = XMLParser.readLibraryXML(libXML);
if (ld.version.matches(".*[a-zA-Z-]+.*") && skipBeta) { // skip alpha/beta/rc ..
logger.info("Skip lib: " + ld.name + " version: " + ld.version);
continue;
}
logger.info("- Parse lib: " + ld.name + " version: " + ld.version);
// if stats file not existing add new one
if (!libName2Stats.containsKey(ld.name))
libName2Stats.put(ld.name, new LibApiStats(ld.name));
libName2Stats.get(ld.name).addVersion(ld.version);
// extract public documented API
IClassHierarchy cha = createClassHierarchy(meta2Code.get(libXML));
Set docAPIs = PublicInterfaceExtractor.getDocumentedPublicInterface(cha);
libName2Stats.get(ld.name).setDocumentedAPIs(ld.version, docAPIs);
logger.info(Utils.INDENT + "- " + docAPIs.size() + " documented public APIs");
} catch (Exception e) {
logger.warn(Utils.stacktrace2Str(e));
}
}
}
private IClassHierarchy createClassHierarchy(File libCodeFile) throws ClassHierarchyException, IOException, ClassNotFoundException {
// create analysis scope and generate class hierarchy
final AnalysisScope scope = AnalysisScope.createJavaAnalysisScope();
JarFile jf = libCodeFile.getName().endsWith(".aar")? new AarFile(libCodeFile).getJarFile() : new JarFile((libCodeFile));
scope.addToScope(ClassLoaderReference.Application, jf);
scope.addToScope(ClassLoaderReference.Primordial, new JarFile(LibScoutConfig.pathToAndroidJar));
IClassHierarchy cha = ClassHierarchyFactory.makeWithRoot(scope);
// cleanup tmp files if library input was an .aar file
if (libCodeFile.getName().endsWith(".aar")) {
File tmpJar = new File(jf.getName());
tmpJar.delete();
logger.trace(Utils.indent() + "tmp jar-file deleted at " + tmpJar.getName());
}
return cha;
}
/**
* Find library descriptor files and their associated code package on disk (recursively)
* @param basePath
*/
private void locateLibrarySDKs(File basePath) {
long s = System.currentTimeMillis();
// recursively search for library.xml file and their associated code packages (.jar|.aar) within the same directory
try {
for (File f : Utils.collectFiles(basePath, new String[]{"xml"})) {
if (f.getName().equals("library.xml")) {
logger.trace("Found lib.xml: " + f.getName() + " parent: " + f.getParent() );
// grep the associated code package (TODO: prefer aar|jar?)
for (File j : Utils.collectFiles(f.getParentFile(), new String[]{"jar", "aar"})) {
logger.trace(" -> Found code pckg: " + j);
meta2Code.put(f,j);
break;
}
} else
logger.trace("Found something different: " + f);
}
} catch (Exception e) {
logger.warn(Utils.stacktrace2Str(e));
}
}
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/PublicInterfaceExtractor.java 0000664 0000000 0000000 00000016165 13424313624 0027210 0 ustar 00root root 0000000 0000000 package de.infsec.tpl.modules.libapi;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import de.infsec.tpl.hash.AccessFlags;
import de.infsec.tpl.pkg.PackageUtils;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.WalaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* Extracts the public API and documented public API (to be used by client) from a library.jar
* Filters ProGuard obfuscated methods/classes (identifier renaming)
*
* Related info:
* - http://wiki.eclipse.org/Evolving_Java-based_APIs
* -> javadoc parsing for pre-/post-condition changes in same APIs (aka semantic changes)
*
*/
public class PublicInterfaceExtractor {
private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.modules.libapi.PublicInterfaceExtractor.class);
public static Set getPublicInterface(IClassHierarchy cha) {
int classCount = 0; // how many distinct classes have public methods
HashSet pubMethods = new HashSet();
for (IClass clazz: cha) {
if (!WalaUtils.isAppClass(clazz)) continue;
Collection extends IMethod> methods = clazz.getDeclaredMethods();
// filter anything but public and non-compiler generated methods
methods = methods.stream()
.filter(m -> {
int code = AccessFlags.getMethodAccessCode(m);
return code > 0 && (code & AccessFlags.getPublicOnlyFilter()) == 0x0;
}) // if predicate is true, keep in list
.filter(m -> !(m.isBridge() || m.isSynthetic())) // filter compiler-generated methods
.collect(Collectors.toCollection(ArrayList::new));
if (!methods.isEmpty()) classCount++;
pubMethods.addAll(methods);
}
logger.debug("[getPublicInterface] Retrieved " + pubMethods.size() + " public methods from " + classCount + " distinct classes");
return pubMethods;
}
/**
* Try to programmatically infer the documented public library interface, i.e., the public methods
* that the app developer is supposed to use.
* This filter obfuscated names (id-renaming via ProGuard), public method of anonymous inner-classes and
* public methods in internal packages (based on keywords)
* @param cha
* @return
*/
public static Set getDocumentedPublicInterface(IClassHierarchy cha) {
Set methods = getPublicInterface(cha);
int pubApiCount = methods.size();
methods = methods.stream()
.filter(m -> !WalaUtils.isAnonymousInnerClass(m.getDeclaringClass())) // filter pub methods in anonymous inner classes
.filter(m -> !isSignatureRenamed(m)) // filter obfuscated names
.filter(m -> !isLibInternalMethod(m)) // is lib internal based on keywords
.collect(Collectors.toCollection(HashSet::new));
ArrayList list = new ArrayList(methods);
list.sort(Comparator.comparing(IMethod::getSignature));
// list.forEach(m -> logger.debug(Utils.INDENT + "[DocAPI] " + m.getSignature() + (m.getDeclaringClass().isInterface()? " [IS_IF_METHOD] " : " ")));
logger.debug("[getDocumentedPublicInterface] Retrieved " + list.size() + " doc public APIs (filtered: " + (pubApiCount-list.size()) + " public APIs)");
// TODO: entrypoint analysis with code?
// filter other? find lib entry points (calls within the lib?)
// -> if pub method is never called, likely to be doc api (or thread->start -> run)
return methods;
}
/**
* Check if package name includes a subpackage named 'internal'
* @param m
* @return
*/
private static boolean isLibInternalMethod(IMethod m) {
// check a part of the package contains 'internal'
List pckgFragments = PackageUtils.parsePackage(m.getDeclaringClass());
for (String frag: pckgFragments) {
if ("internal".equals(frag)) {
return true;
}
}
return false;
}
private static boolean isSignatureRenamed(IMethod m) {
logger.trace(Utils.INDENT2 + "- Check for renaming: " + m.getSignature());
// check if method name has been renamed
boolean renamed = isIdentifierRenamed(m.getReference().getName().toString());
if (renamed) {
logger.trace(Utils.indent(3) + "-> method name " + m.getReference().getName().toString() + " is renamed");
return true;
}
// check whether the class name has been renamed (e.g. when method is something like <[cl]init> or toString()
// in case we have (a) named inner-class(es), we check whether all classes were renamed
List clazz = new ArrayList();
String[] cz = WalaUtils.getClassName(m.getDeclaringClass()).split("\\$");
clazz.addAll(Arrays.asList(cz));
renamed = false;
for (String c: clazz) {
renamed = isIdentifierRenamed(c);
}
if (renamed) {
logger.trace(Utils.indent(3) + "-> class name " + WalaUtils.getClassName(m.getDeclaringClass()) + " is renamed");
return true;
}
// TODO: probably not necessary (unlikely that method/class is not renamed but packagename
// check whether a subset of the package name has been renamed
renamed = false;
List pckgFragments = PackageUtils.parsePackage(m.getDeclaringClass());
// logger.debug(" pckg fragments: " + Utils.join(pckgFragments, ","));
for (String frag: pckgFragments) {
boolean fragRenamed = isIdentifierRenamed(frag);
// logger.debug(" frag " + frag + " renamed: " + fragRenamed + " currentRenamed: " + renamed);
if (renamed && !fragRenamed)
// non-obfuscated package fragments must not follow obfuscated ones
return false;
renamed = fragRenamed;
}
return renamed;
}
/**
* filter proguard-obfuscated methods and subpackage identifier
* (created with its SimpleNameFactory and SpecialNameFactory)
*
* Limit detection to 2-char names to avoid false positives (non-obfuscated method names such as "foo")
* For mixedCaseNames this is 56^2 = 2704 combinations (e.g. aR, Bm, mB,..)
* For lowerCaseNames this is 26^2 = 676 combinations (e.g. aa, cd, ea,..)
* The number of combinations is per-package. Default Android Studio settings are lowercase only.
* In addition, ProGuard might append an underscore '_' to resolve ambiguous names (that were created during obfuscation)
*/
private static boolean isIdentifierRenamed(String str) {
if (str.length() == 3 && str.charAt(2) == '_')
str = str.substring(0,1); // strip underscore
if (str.length() == 1 || str.length() == 2) {
return str.matches("[a-zA-Z]{1,2}");
}
return false;
}
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libmatch/ 0000775 0000000 0000000 00000000000 13424313624 0021724 5 ustar 00root root 0000000 0000000 LibScout-2.3.2/src/de/infsec/tpl/modules/libmatch/LibCodeUsage.java 0000664 0000000 0000000 00000021017 13424313624 0025056 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.modules.libmatch;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.CodeScanner;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.shrikeCT.InvalidClassFileException;
import de.infsec.tpl.hash.AccessFlags;
import de.infsec.tpl.profile.ProfileMatch;
import de.infsec.tpl.utils.Utils;
import de.infsec.tpl.utils.WalaUtils;
/**
* Library Code Usage Analysis
* Checks which library code is used by the application in terms of API calls
* To this end, for each full match (partial matches are excluded) it is checked which calls are used within
* the code base that does not belong the identified library root package name.
*/
public class LibCodeUsage {
private static final Logger logger = LoggerFactory.getLogger(LibCodeUsage.class);
// TODO
// how to deal with libs with ambiguous root pckg (currently excluded)
// -> need to check all matched package name for such libs
public static void checkUsage(final IClassHierarchy cha, final List results) {
logger.info("");
logger.info("== Check lib usage ==");
long starttime = System.currentTimeMillis();
// get unique libraries (multiple lib versions with an exact match have the same API, thus checking only one of them suffices)
final HashMap lib2Profile = new HashMap(); // libname -> profile
final HashMap rootPckg2Lib = new HashMap(); // root package -> libname
for (ProfileMatch pm: results) {
if (!pm.doAllConfigsMatch()) continue;
String libName = pm.lib.description.name;
if (!lib2Profile.containsKey(libName)) {
lib2Profile.put(libName, pm);
}
String rootPckg = pm.getMatchedPackageTree().getRootPackage();
// TODO: currently exclude libs with ambiguous root packages
if (!LibraryIdentifier.ambiguousRootPackages.contains(rootPckg) && !rootPckg2Lib.containsKey(rootPckg)) {
rootPckg2Lib.put(rootPckg, libName);
}
}
// shortcut - if there are no lib matches there is no need to walk over the bytecode
if (lib2Profile.isEmpty()) {
logger.info(Utils.INDENT + ">> lib code usage analysis done - No libraries matched to scan for (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - starttime) + ")");
return;
}
// scan code once
// library root package -> signature, access specifier
Map> usedMethods = new TreeMap>();
Map> unresolvableMethods = new TreeMap>();
// iterate all classes
for (IClass clazz: cha) {
if (!WalaUtils.isAppClass(clazz)) continue;
// iterate all methods
for (IMethod im: clazz.getDeclaredMethods()) {
if (im.isAbstract() || im.isNative()) continue;
try {
// iterate all call sites
for (CallSiteReference csr: CodeScanner.getCallSites(im)) {
for (String rootPckg: rootPckg2Lib.keySet()) {
if (csr.getDeclaredTarget().getSignature().startsWith(rootPckg) && !WalaUtils.simpleName(im.getDeclaringClass()).startsWith(rootPckg)) {
//TODO how to store? extend DB? flag whether usage was found in app dev code (based on (fractions of) package name) or other lib code? we could also classify by using matched lib packages
// TODO: add additional heuristic to check for app package name levels
boolean usedInApp = !rootPckg2Lib.keySet().contains(WalaUtils.simpleName(im.getDeclaringClass()));
// check access specifier for target method
IClass targetClazz = cha.lookupClass(csr.getDeclaredTarget().getDeclaringClass());
if (targetClazz == null) { // no lookup possible - dead / legacy code?
logger.debug(Utils.INDENT + "Unresolvable class for lib method in use: " + csr.getDeclaredTarget().getSignature());
if (!unresolvableMethods.containsKey(rootPckg2Lib.get(rootPckg)))
unresolvableMethods.put(rootPckg2Lib.get(rootPckg), new TreeSet());
unresolvableMethods.get(rootPckg2Lib.get(rootPckg)).add(csr.getDeclaredTarget().getSignature());
break;
}
IMethod targetMethod = targetClazz.getMethod(csr.getDeclaredTarget().getSelector());
if (targetMethod == null) { // e.g. if clazz is interface without declared methods
targetMethod = WalaUtils.resolveMethod(clazz, csr);
}
int accessSpecifier = AccessFlags.getMethodAccessCode(targetMethod);
String accessSpec = AccessFlags.flags2Str(accessSpecifier);
logger.trace(Utils.INDENT + "- method in use (in " + (usedInApp? "app" : "lib") +"): " + csr.getDeclaredTarget().getSignature() + " in bm: "+ im.getSignature() + " access: " + accessSpec);
String normalizedSig = csr.getDeclaredTarget().getSignature();
// normalize signature if lib root package does not match app lib root package (e.g. due to id renaming)
if (!rootPckg.equals(lib2Profile.get(rootPckg2Lib.get(rootPckg)).lib.packageTree.getRootPackage())) {
// replace package name in dot notation
String r = rootPckg.replaceAll("\\.", "\\\\.");
String rx = lib2Profile.get(rootPckg2Lib.get(rootPckg)).lib.packageTree.getRootPackage().replaceAll("\\.", "\\\\.");
// replace arguments in / notation
String r2 = rootPckg.replaceAll("\\.", "/");
String rx2 = lib2Profile.get(rootPckg2Lib.get(rootPckg)).lib.packageTree.getRootPackage().replaceAll("\\.", "/");
normalizedSig = csr.getDeclaredTarget().getSignature().replaceAll(r,rx).replaceAll(r2,rx2);
}
// update usedMethods
if (!usedMethods.containsKey(rootPckg2Lib.get(rootPckg)))
usedMethods.put(rootPckg2Lib.get(rootPckg), new TreeMap());
usedMethods.get(rootPckg2Lib.get(rootPckg)).put(normalizedSig, accessSpecifier);
}
}
}
} catch (InvalidClassFileException e) {
logger.error(Utils.stacktrace2Str(e));
}
}
}
// debug output
for (String lib: usedMethods.keySet()) {
ProfileMatch pm = lib2Profile.get(lib);
String libRootPckg = pm.lib.packageTree.getRootPackage();
// TODO: currently unresolvable methods and access spec are not stored!
// store used methods
pm.usedLibMethods = new TreeSet(usedMethods.get(lib).keySet());
logger.info("- check lib: " + pm.lib.getLibIdentifier() + " root package: " + libRootPckg + (!libRootPckg.equals(pm.getMatchedPackageTree().getRootPackage())? " vs matched root package: " + pm.getMatchedPackageTree().getRootPackage() : ""));
// retrieve number of unique lib classes used
Set uniqueClazzes = new HashSet();
for (String sig: usedMethods.get(lib).keySet()) {
uniqueClazzes.add(Utils.getFullClassName(sig));
}
logger.info(" (found " + usedMethods.get(lib).size() + " unique library methods and " + uniqueClazzes.size() + " unique lib clazzes");
for (String sig: usedMethods.get(lib).keySet()) {
logger.debug(Utils.INDENT + "- method in use: " + sig + " " + AccessFlags.flags2Str(usedMethods.get(lib).get(sig)));
}
// This can happen if a library contains multiple sub-libraries like OrmLite-[android,core,jdbc] and only a subset is profiled.
if (unresolvableMethods.containsKey(lib)) {
logger.info("");
for (String sig: unresolvableMethods.get(lib))
logger.debug(Utils.INDENT + "!! unresolvable library method in use by app: " + sig);
}
logger.info("");
}
logger.info(Utils.INDENT + ">> lib code usage analysis done (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - starttime) + ")");
}
}
LibScout-2.3.2/src/de/infsec/tpl/modules/libmatch/LibraryIdentifier.java 0000664 0000000 0000000 00000104454 13424313624 0026206 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package de.infsec.tpl.modules.libmatch;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import de.infsec.tpl.config.LibScoutConfig;
import de.infsec.tpl.hashtree.HashTree;
import de.infsec.tpl.hashtree.node.Node;
import de.infsec.tpl.hashtree.node.PackageNode;
import de.infsec.tpl.utils.WalaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.ibm.wala.dalvik.util.AndroidAnalysisScope;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import de.infsec.tpl.manifest.ProcessManifest;
import de.infsec.tpl.hash.Hash;
import de.infsec.tpl.pkg.PackageTree;
import de.infsec.tpl.pkg.PackageUtils;
import de.infsec.tpl.pkg.PackageUtils.RELATIONSHIP;
import de.infsec.tpl.profile.AppProfile;
import de.infsec.tpl.profile.LibProfile;
import de.infsec.tpl.profile.ProfileMatch;
import de.infsec.tpl.profile.ProfileMatch.HTreeMatch;
import de.infsec.tpl.profile.ProfileMatch.MatchLevel;
import de.infsec.tpl.stats.AppStats;
import de.infsec.tpl.stats.SerializableAppStats;
import de.infsec.tpl.utils.ApkUtils;
import de.infsec.tpl.utils.Pair;
import de.infsec.tpl.utils.Utils;
public class LibraryIdentifier {
private static final Logger logger = LoggerFactory.getLogger(LibraryIdentifier.class);
private IClassHierarchy cha;
private Map uniqueLibraries; // unique library name -> highest version
private AppStats stats;
private static final String FILE_EXT_SERIALIZED = ".data";
private static final String FILE_EXT_JSON = ".json";
public static Set ambiguousRootPackages = new TreeSet() {
private static final long serialVersionUID = 7951760067476257884L;
{
add("com.google"); add("com.google.android");
add("com.google.android.gms"); add("android.support");
}};
public static AppStats run(File appFile, List profiles, boolean libUsageAnalysis) throws ClassHierarchyException, NoSuchAlgorithmException, IOException {
LibraryIdentifier libid = new LibraryIdentifier(appFile);
return libid.identifyLibraries(profiles, libUsageAnalysis);
}
private LibraryIdentifier(File appFile) {
this.stats = new AppStats(appFile);
// set identifier for logging
String logIdentifier = LibScoutConfig.logDir.getAbsolutePath() + File.separator;
logIdentifier += appFile.getName().replaceAll("\\.jar", "").replaceAll("\\.apk", "").replaceAll("\\.aar", "");
MDC.put("appPath", logIdentifier);
}
private void createClassHierarchy() throws IOException, ClassHierarchyException {
long s = System.currentTimeMillis();
// check if we have a multi-dex file
stats.isMultiDex = ApkUtils.isMultiDexApk(stats.appFile);
if (stats.isMultiDex)
logger.info("Multi-dex apk detected - Code is merged to single class hierarchy!");
// create analysis scope and generate class hierarchy
// we do not need additional libraries like support libraries,
// as they are statically linked in the app code.
final AnalysisScope scope = AndroidAnalysisScope.setUpAndroidAnalysisScope(new File(stats.appFile.getAbsolutePath()).toURI(), null /* no exclusions */, null /* we always pass an android lib */, LibScoutConfig.pathToAndroidJar.toURI());
cha = ClassHierarchyFactory.makeWithRoot(scope);
logger.info("Generated class hierarchy (in " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - s) + ")");
WalaUtils.getChaStats(cha);
}
private AppStats identifyLibraries(List profiles, boolean libUsageAnalysis) throws NoSuchAlgorithmException, IOException, ClassHierarchyException {
long starttime = System.currentTimeMillis();
logger.info("Process app: " + stats.appFile.getName());
// parse AndroidManifest.xml
stats.manifest = parseManifest(stats.appFile);
// check stat file /package-level1/package-level2/appName_appVersionCode.data
String statsFileName = stats.appFile.getName().replaceAll("\\.jar", "").replaceAll("\\.apk", "").replaceAll("\\.aar", "") + "_" + stats.manifest.getVersionCode(); // without file suffix
File statsSubDir = PackageUtils.packageToPath(stats.manifest.getPackageName());
File statsFile = new File(LibScoutConfig.statsDir + File.separator + statsSubDir + File.separator + statsFileName + FILE_EXT_SERIALIZED);
// if stat file already exists for this app, return
if (LibScoutConfig.generateStats && statsFile.exists()) {
logger.info(Utils.INDENT + "Stat file " + statsFile + " already exists - ABORT!");
return null;
}
stats.profiles = profiles;
uniqueLibraries = LibProfile.getUniqueLibraries(profiles);
logger.info("Found " + uniqueLibraries.size() + " unique libraries in " + profiles.size() + " library profiles");
// create CHA
createClassHierarchy();
// generate app package tree and hash trees
AppProfile appProfile = AppProfile.create(cha);
stats.pTree = appProfile.packageTree;
stats.appHashTrees = appProfile.hashTrees;
// fast scan (heuristic) - check if lib root package is in app
logger.info("= Scan for library root packages (heuristic) =");
for (LibProfile profile: profiles) {
// check if library root package is present in app (for validation purposes)
String rootPackage = profile.packageTree.getRootPackage();
// In some edge case the automatic root package extraction gives us a generic package that could match multiple different libraries.
// In these cases it is better to ignore them instead of getting a lot of false matches
if (rootPackage == null || ambiguousRootPackages.contains(rootPackage)) continue;
boolean match = appProfile.packageTree.containsPackage(rootPackage);
if (match && !stats.packageOnlyMatches.containsKey(profile.description.name)) {
stats.packageOnlyMatches.put(profile.description.name, rootPackage);
logger.info(Utils.INDENT + "- Found lib root package " + rootPackage + " (" + profile.description.name + ")");
}
}
logger.info("");
// check app against all loaded profiles (exact + partial matching)
long s = System.currentTimeMillis();
logger.info("= Match profiles =");
List results = new ArrayList();
for (LibProfile profile: profiles) {
logger.debug("- Match Library: " + profile);
logger.trace("Lib PackageTree:");
if (logger.isTraceEnabled()) {
profile.packageTree.print(true);
logger.trace("");
}
// check if this is the most current library version
profile.setIsDeprecatedLib(!uniqueLibraries.get(profile.description.name).equals(profile.description.version));
// compute similarity scores for each hash tree
ProfileMatch pm = partialMatchForTrees(cha, appProfile, profile, MatchLevel.CLASS);
results.add(pm);
// do we have a one-to-one copy of the library?
if (pm.doAllConfigsMatch()) {
logger.debug(Utils.INDENT + "- all configs match!");
logger.debug(Utils.INDENT + "- re-obfuscated library? " + pm.isLibObfuscated());
logger.debug("");
}
}
logger.info(Utils.INDENT + ">> profile matching done (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - s) + ")");
stats.pMatches = results;
printResults(results);
// run library API usage analysis for full matches only
if (libUsageAnalysis)
LibCodeUsage.checkUsage(cha, results);
logger.info("");
stats.processingTime = System.currentTimeMillis() - starttime;
// write app results to json
if (LibScoutConfig.generateJSON) {
File jsonFile = new File(LibScoutConfig.jsonDir + File.separator + statsSubDir + File.separator + statsFileName + FILE_EXT_JSON);
Utils.obj2JsonFile(jsonFile, stats);
logger.info("Write app stats to JSON (dir: " + LibScoutConfig.jsonDir + ")");
}
// serialize appstats to disk
if (LibScoutConfig.generateStats) {
if (!stats.pMatches.isEmpty()) {
logger.info("Serialize app stats to disk (dir: " + LibScoutConfig.statsDir + ")");
Utils.object2Disk(statsFile, new SerializableAppStats(stats)); // TODO mv from java serialization to protobufs or remove w/o replacement
}
}
logger.info("App processing time: " + Utils.millisecondsToFormattedTime(stats.processingTime));
return stats;
}
/**
* Compute similarity scores for all provided {@link HashTree}.
* @param cha the {@link IClassHierarchy}
* @param appProfile the {@link AppProfile}
* @param libProfile the {@link LibProfile}
* @param lvl the level of matching to be applied (currently either Package or Class level)
* @return a {@link ProfileMatch} for the provided library profile
* @throws NoSuchAlgorithmException
*/
public ProfileMatch partialMatchForTrees(final IClassHierarchy cha, final AppProfile appProfile, final LibProfile libProfile, final MatchLevel lvl) throws NoSuchAlgorithmException {
ProfileMatch pMatch = new ProfileMatch(libProfile);
logger.trace("Partial match of lib: " + libProfile);
// check if library root package is present in app (for validation purposes)
String rootPackage = libProfile.packageTree.getRootPackage();
pMatch.libRootPackagePresent = rootPackage == null? false : appProfile.packageTree.containsPackage(rootPackage);
logger.trace(Utils.INDENT + "Library root package " + rootPackage + " is " + (pMatch.libRootPackagePresent? "" : "not ") + " present in app!");
// calculate scores for each hash tree
for (HashTree tree : appProfile.hashTrees) {
partialMatch(cha, pMatch, tree, appProfile.packageTree, libProfile, lvl);
}
if (logger.isTraceEnabled())
pMatch.printResults(3);
return pMatch;
}
/**
* Multi-step approach to compute the similarity score between a given library and an application.
* We first check for a full match by comparing the package hashes of the library with the ones from the application.
* If there is no full match we compute a candidate list for each app package, compute partitions (potential root packages) and
* determine the maximum over all partitions.
*
* @param cha the {@IClassHierarchy}
* @param pMatch a {@ProfileMatch} in which the result is stored as side-effect
* @param appHashTree the generated {@link HashTree}
* @param appTree the application {@link PackageTree}
* @param lib the {@link LibProfile} to match against
* @param lvl the level of matching to be applied (currently either Package or Class level)
* @throws NoSuchAlgorithmException
*/
public void partialMatch(final IClassHierarchy cha, final ProfileMatch pMatch, final HashTree appHashTree, final PackageTree appTree, final LibProfile lib, final MatchLevel lvl) throws NoSuchAlgorithmException {
// retrieve hash tree with same config from profile
HashTree libHashTree = pMatch.lib.hashTrees.get(0); // TODO mult?
HTreeMatch match = pMatch.createResult(appHashTree.getConfig());
logger.debug(Utils.INDENT + "- partial match for config: " + appHashTree.getConfig());
if (libHashTree == null) {
logger.error("Could not find lib hash tree for config: " + appHashTree.getConfig());
return;
}
/*
* step 0. shortcut - check if library fully matches by comparing the package hashes
*/
logger.debug(Utils.INDENT2 + "# step 0: check if lib fully matches");
if (appHashTree.getPackageNodes().containsAll(libHashTree.getPackageNodes())) {
logger.debug(Utils.indent(3) + "-> All package hashes (" + libHashTree.getPackageNodes().size() + ") of library match!");
// update results
match.simScore = ProfileMatch.MATCH_HTREE_FULL;
List matchingNodes = new ArrayList<>(appHashTree.getPackageNodes());
matchingNodes.retainAll(libHashTree.getPackageNodes());
match.matchingNodes = matchingNodes;
pMatch.addResult(match);
return;
}
// abort if partial matching has been disabled via cli-option
if (LibScoutConfig.noPartialMatching) {
logger.debug(Utils.INDENT2 + "Partial matching disabled - [SKIP]");
match.simScore = ProfileMatch.MATCH_HTREE_NONE;
pMatch.addResult(match);
return;
}
// we do not perform partial matching for libs that have multiple lib root packages
logger.debug(Utils.INDENT2 + "# step 0.5: Check root package: " + lib.packageTree.getRootPackage());
if (lib.packageTree.getRootPackage() == null) {
logger.debug(Utils.indent(3) + "-> No partial matching performed due to multiple lib root packages");
match.simScore = ProfileMatch.MATCH_HTREE_NO_ROOT_PCKG;
pMatch.addResult(match);
return;
}
/*
* step 1. compute candidate list
*
* Candidate list of each library package sorted by similarity score, i.e.
* lp1 ∶ ap1 (0.95), ap2 (0.84), ap3 (0.75)
* lp3 ∶ ap6 (0.91), ap4 (0.60)
* lp2 ∶ ap7 (0.85), ap9 (0.82)
*/
logger.debug(Utils.INDENT2 + "# step 1: compute candidate list");
long time = System.currentTimeMillis();
HashMap>> candidateList = new HashMap>>();
for (Node lp: libHashTree.getPackageNodes()) {
ArrayList> clist = new ArrayList>(); // candidate list for lp
for (Node ap: appHashTree.getPackageNodes()) {
// filter application packages that start with declared manifest app package name
// TODO: unfortunately most app packages do only partially match the manifest package name. This means to match more app packages
// we would have to test partially (but: this could lead to false positives if we have libs from the same developer)
if (((PackageNode) ap).packageName.startsWith(stats.manifest.getPackageName()))
continue;
float score = calcNodeSimScore(lp, ap);
if (score > ProfileMatch.MIN_CLAZZ_SCORE) {
// update candidate list
clist.add(new Pair(ap, score));
}
}
Collections.sort(clist, new SimScoreComparator()); // sort candidate list by simScore
candidateList.put(lp, clist);
}
// sort tree map by highest candidate values
TreeSet>>> sortedCList = new TreeSet>>>(new CandidateListComparator());
sortedCList.addAll(candidateList.entrySet());
// debug print (sorted candidate list)
if (logger.isTraceEnabled()) {
logger.trace(Utils.indent(3) + "Sorted candidate list (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - time) + "):");
for (Map.Entry>> entry: sortedCList) {
logger.trace(Utils.indent(4) + entry.getKey() + " : " + Utils.join(entry.getValue(), ", "));
}
}
/*
* step 2. get potential root packages -- partitions (skip package name of apk)
* Note: This will _not_ work if app developer manually renamed the library packages (e.g. prefixed lib packages with app package)
*/
logger.debug(Utils.INDENT2 +"# step 2: determine partitions");
time = System.currentTimeMillis();
String libraryRootPackage = pMatch.lib.packageTree.getRootPackage();
int libPDepth = PackageUtils.packageDepth(libraryRootPackage);
// retrieve potential app root packages of depth libPDepth
Set appRootPackages = new TreeSet();
for (Node ap: appHashTree.getPackageNodes()) {
PackageNode apn = (PackageNode) ap;
String pRootPackage = PackageUtils.getSubPackageOfDepth(apn.packageName, libPDepth);
if (pRootPackage != null)
appRootPackages.add(pRootPackage);
}
logger.trace(Utils.indent(3) + "# partitions(" + appRootPackages.size() + "): " + appRootPackages);
appRootPackages = getPartitionsByRootPackage(appRootPackages, lib.packageTree.getRootPackage());
logger.debug(Utils.indent(3) + "# unique partitions(" + appRootPackages.size() + "): " + appRootPackages);
logger.debug(Utils.indent(4) + "- step 2 processing time: " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - time));
/*
* step 3. compute maximum score for each partition (including selected app packages)
* a score is a mapping of root package -> pair < score, nodes with individual package scores >
*/
logger.debug(Utils.INDENT2 + "# step 3: partitions scores");
// pre-compute library package relationships
// TODO: could be computed even earlier (pmatchForTrees)
List libPackageRel = computePackageRelationships(sortedCList);
HashMap>>> scores = new HashMap>>>();
for (String rootPackage: appRootPackages) {
time = System.currentTimeMillis();
Pair>> partSimScore = calcPartitionSimScore(rootPackage, sortedCList, libPackageRel);
if (partSimScore != null) {
logger.debug(Utils.indent(4) + "-> partition: " + rootPackage + " sim score: " + partSimScore);
scores.put(rootPackage, partSimScore);
}
logger.debug(Utils.indent(5) + "- processing time: " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - time));
}
/*
* step 4. chose overall maximum (with a minimum threshold of @ProfileMatch.MIN_PARTIAL_MATCHING_SCORE)
*/
logger.debug(Utils.INDENT2 + "# step 4: compute overall maximum score");
if (scores.isEmpty()) {
logger.debug("No partial matching for " + pMatch.lib.getLibIdentifier() + "\n");
// update results
match.simScore = ProfileMatch.MATCH_HTREE_NONE;
pMatch.addResult(match);
} else {
logger.debug(Utils.indent(3) + scores.size() + " results for partial matching of lib " + pMatch.lib.getLibIdentifier() + " (" + appHashTree.getConfig() + ")");
// get best score over partitions
String rootPckg = "";
float highScore = 0f;
for (String root: scores.keySet()) {
logger.debug(Utils.indent(4) + "- rootPckg: " + root + " score: " + scores.get(root).first());
if (scores.get(root).first() > highScore) {
highScore = scores.get(root).first();
rootPckg = root;
}
}
logger.debug(Utils.INDENT + "=> maximum partial matching score: " + highScore + " (partition: " + rootPckg + ")\n");
match.rootPackage = rootPckg;
match.simScore = highScore;
List matchingNodes = new ArrayList();
for (Pair p: scores.get(rootPckg).second()) {
if (p != null)
matchingNodes.add(p.first());
}
match.matchingNodes = HashTree.toPackageNode(matchingNodes);
pMatch.addResult(match);
}
}
// TODO: cumbersome
private static List computePackageRelationships(TreeSet>>> candidateList) {
// retrieve ordered list of library packages from the current candidate list
ArrayList libraryPackageNodes = new ArrayList();
for (Map.Entry>> entry: candidateList) {
libraryPackageNodes.add((PackageNode) entry.getKey());
}
// compute package relationship for adjacent packages (used as additional app package filter in getCombinations)
ArrayList result = new ArrayList();
for (int i = 0; i < libraryPackageNodes.size()-1; i++) {
result.add(PackageUtils.testRelationship(libraryPackageNodes.get(i).packageName, libraryPackageNodes.get(i+1).packageName));
}
return result;
}
private Pair>> calcPartitionSimScore(final String rootPackage, final TreeSet>>> candidateList, final List libPackageRel) {
logger.trace(Utils.indent(3) + "- Calculate sim score for partition: " + rootPackage);
// create view on candidate list (filter app packages that do not start with rootPackage and app packages
// that have a different depth as the lib package)
// TODO: maybe could be improved by using a bitmask
ArrayList>> cList = new ArrayList>>();
int packagesWithCandidates = 0;
for (Iterator>>> it = candidateList.iterator(); it.hasNext(); ) {
Map.Entry>> pckgCandidates = it.next();
ArrayList> filteredCandidates = new ArrayList>();
for (Pair candidate: pckgCandidates.getValue()) {
PackageNode pn = (PackageNode) candidate.first();
// check if candidate package starts with root package and has the same package depth
if (pn.packageName.startsWith(rootPackage) && PackageUtils.packageDepth(((PackageNode) pckgCandidates.getKey()).packageName) == PackageUtils.packageDepth(pn.packageName))
filteredCandidates.add(candidate);
}
if (!filteredCandidates.isEmpty()) {
packagesWithCandidates++;
}
cList.add(filteredCandidates);
}
// stop if less than half of lib packages have no candidate
if ((((float) packagesWithCandidates / (float) candidateList.size()) < 0.5f)) {
logger.trace(Utils.indent(4) + "Only " + packagesWithCandidates + " / " + candidateList.size() + " lib packages for partition: " + rootPackage + " have candidates - [SKIP]");
return null;
}
// debug print (sorted+filtered candidate list)
if (logger.isTraceEnabled()) {
logger.trace(Utils.indent(4) + "Sorted + filtered (package name) candidate list:");
int i = 0;
for (Map.Entry