mirror of
https://github.com/f-droid/fdroidserver.git
synced 2025-11-09 08:50:28 +03:00
Merge branch 'port-to-osx-and-trusty' into 'master'
port to OSX and Ubuntu/trusty See merge request fdroid/fdroidserver!341
This commit is contained in:
commit
ca069d941d
13 changed files with 206 additions and 101 deletions
86
.travis.yml
86
.travis.yml
|
|
@ -3,42 +3,81 @@
|
||||||
language: java
|
language: java
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
|
||||||
- os: linux # this is really about OSX, Ubuntu is just bonus
|
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
language: android
|
language: android
|
||||||
sudo: required
|
|
||||||
# this doesn't actually work yet https://github.com/travis-ci/travis-ci/issues/5337
|
|
||||||
dist: trusty
|
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode8
|
osx_image: xcode9
|
||||||
env: ANDROID_HOME=/usr/local/opt/android-sdk
|
env: ANDROID_SDK_ROOT=/usr/local/share/android-sdk
|
||||||
|
env: ANDROID_HOME=/usr/local/share/android-sdk
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode7.3
|
||||||
|
env: ANDROID_SDK_ROOT=/usr/local/share/android-sdk
|
||||||
|
env: ANDROID_HOME=/usr/local/share/android-sdk
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode6.4
|
||||||
|
env: ANDROID_SDK_ROOT=/usr/local/share/android-sdk
|
||||||
|
env: ANDROID_HOME=/usr/local/share/android-sdk
|
||||||
|
|
||||||
licenses:
|
addons:
|
||||||
- 'android-sdk-preview-license-52d11cd2'
|
apt:
|
||||||
- 'android-sdk-license-.+'
|
sources:
|
||||||
|
- sourceline: 'ppa:fdroid/fdroidserver'
|
||||||
|
packages:
|
||||||
|
- bash
|
||||||
|
- dash
|
||||||
|
- pylint
|
||||||
|
- pep8
|
||||||
|
- python3-dev
|
||||||
|
- python3-pip
|
||||||
|
- python3-ruamel.yaml
|
||||||
|
- python3-setuptools
|
||||||
|
- python3.4-venv
|
||||||
|
- libjpeg-dev
|
||||||
|
- zlib1g-dev
|
||||||
|
- fdroidserver
|
||||||
|
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
- android-23 # required for `fdroid build` test
|
||||||
|
- build-tools-25.0.3 # required for `fdroid build` test
|
||||||
|
licenses:
|
||||||
|
- 'android-sdk-preview-.+'
|
||||||
|
- 'android-sdk-license-.+'
|
||||||
|
|
||||||
# the PPA is needed on Ubuntu 14.04 precise, and with python3, trusty too
|
# the PPA is needed on Ubuntu 14.04 precise, and with python3, trusty too
|
||||||
# the pip thing is a hack that can go away with trusty
|
# the pip thing is a hack that can go away with trusty
|
||||||
install:
|
install:
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||||
brew update > /dev/null;
|
brew update > /dev/null;
|
||||||
brew install android-sdk dash gnu-sed jpeg python3;
|
brew install dash bash python3 gradle jenv;
|
||||||
sudo pip3 install pep8 pyflakes pylint;
|
brew install gnu-sed --with-default-names;
|
||||||
|
brew cask reinstall java;
|
||||||
|
brew cask install android-sdk;
|
||||||
|
|
||||||
|
mkdir -p "$ANDROID_HOME/licenses";
|
||||||
|
echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license";
|
||||||
|
echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license";
|
||||||
|
echo y | $ANDROID_HOME/tools/bin/sdkmanager "platform-tools";
|
||||||
|
echo y | $ANDROID_HOME/tools/bin/sdkmanager "build-tools;25.0.2";
|
||||||
|
echo y | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-23";
|
||||||
|
|
||||||
sudo pip3 install -e .;
|
sudo pip3 install -e .;
|
||||||
sudo rm -rf fdroidserver.egg-info;
|
sudo rm -rf fdroidserver.egg-info;
|
||||||
echo y | android --verbose update sdk --no-ui --all --filter platform-tools,build-tools-25.0.2;
|
|
||||||
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
echo $PATH;
|
||||||
sudo add-apt-repository ppa:guardianproject/fdroidserver -y;
|
echo $JAVA_HOME;
|
||||||
sudo apt-get -q update -y;
|
jenv versions;
|
||||||
sudo apt-get -q install -y --no-install-recommends python3 python3-dev
|
/usr/libexec/java_home;
|
||||||
python3-git python3-pil python3-libcloud python3-logilab-astng
|
java -version;
|
||||||
python3-paramiko python3-pip python3-pyasn1 python3-pyasn1-modules
|
which java;
|
||||||
python3-requests python3-venv python3-yaml rsync
|
javac -version;
|
||||||
pyflakes pylint3 pep8 dash bash ruby libjpeg-dev zlib1g-dev;
|
which javac;
|
||||||
sudo pip3 install pylint;
|
jarsigner -help;
|
||||||
fi
|
which jarsigner;
|
||||||
|
keytool -help;
|
||||||
|
which keytool;
|
||||||
|
fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cd tests
|
- cd tests
|
||||||
|
|
@ -46,5 +85,4 @@ script:
|
||||||
|
|
||||||
after_failure:
|
after_failure:
|
||||||
- cd $TRAVIS_BUILD_DIR
|
- cd $TRAVIS_BUILD_DIR
|
||||||
- ls -lRa env
|
|
||||||
- ls -lR | curl -F 'clbin=<-' https://clbin.com
|
- ls -lR | curl -F 'clbin=<-' https://clbin.com
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ from distutils.util import strtobool
|
||||||
|
|
||||||
import fdroidserver.metadata
|
import fdroidserver.metadata
|
||||||
from fdroidserver import _
|
from fdroidserver import _
|
||||||
from fdroidserver.exception import FDroidException, VCSException, BuildException
|
from fdroidserver.exception import FDroidException, VCSException, BuildException, VerificationException
|
||||||
from .asynchronousfilereader import AsynchronousFileReader
|
from .asynchronousfilereader import AsynchronousFileReader
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1599,6 +1599,13 @@ class KnownApks:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
'''Load filename/date info about previously seen APKs
|
||||||
|
|
||||||
|
Since the appid and date strings both will never have spaces,
|
||||||
|
this is parsed as a list from the end to allow the filename to
|
||||||
|
have any combo of spaces.
|
||||||
|
'''
|
||||||
|
|
||||||
self.path = os.path.join('stats', 'known_apks.txt')
|
self.path = os.path.join('stats', 'known_apks.txt')
|
||||||
self.apks = {}
|
self.apks = {}
|
||||||
if os.path.isfile(self.path):
|
if os.path.isfile(self.path):
|
||||||
|
|
@ -1608,7 +1615,10 @@ class KnownApks:
|
||||||
if len(t) == 2:
|
if len(t) == 2:
|
||||||
self.apks[t[0]] = (t[1], None)
|
self.apks[t[0]] = (t[1], None)
|
||||||
else:
|
else:
|
||||||
self.apks[t[0]] = (t[1], datetime.strptime(t[2], '%Y-%m-%d'))
|
appid = t[-2]
|
||||||
|
date = datetime.strptime(t[-1], '%Y-%m-%d')
|
||||||
|
filename = line[0:line.rfind(appid) - 1]
|
||||||
|
self.apks[filename] = (appid, date)
|
||||||
self.changed = False
|
self.changed = False
|
||||||
|
|
||||||
def writeifchanged(self):
|
def writeifchanged(self):
|
||||||
|
|
@ -2077,24 +2087,41 @@ def verify_apks(signed_apk, unsigned_apk, tmp_dir):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def verify_apk_signature(apk, jar=False):
|
def verify_jar_signature(jar):
|
||||||
|
"""Verifies the signature of a given JAR file.
|
||||||
|
|
||||||
|
jarsigner is very shitty: unsigned JARs pass as "verified"! So
|
||||||
|
this has to turn on -strict then check for result 4, since this
|
||||||
|
does not expect the signature to be from a CA-signed certificate.
|
||||||
|
|
||||||
|
:raises: VerificationException() if the JAR's signature could not be verified
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if subprocess.call([config['jarsigner'], '-strict', '-verify', jar]) != 4:
|
||||||
|
raise VerificationException(_("The repository's index could not be verified."))
|
||||||
|
|
||||||
|
|
||||||
|
def verify_apk_signature(apk, min_sdk_version=None):
|
||||||
"""verify the signature on an APK
|
"""verify the signature on an APK
|
||||||
|
|
||||||
Try to use apksigner whenever possible since jarsigner is very
|
Try to use apksigner whenever possible since jarsigner is very
|
||||||
shitty: unsigned APKs pass as "verified"! So this has to turn on
|
shitty: unsigned APKs pass as "verified"! Warning, this does
|
||||||
-strict then check for result 4.
|
not work on JARs with apksigner >= 0.7 (build-tools 26.0.1)
|
||||||
|
|
||||||
You can set :param: jar to True if you want to use this method
|
|
||||||
to verify jar signatures.
|
|
||||||
"""
|
"""
|
||||||
if set_command_in_config('apksigner'):
|
if set_command_in_config('apksigner'):
|
||||||
args = [config['apksigner'], 'verify']
|
args = [config['apksigner'], 'verify']
|
||||||
if jar:
|
if min_sdk_version:
|
||||||
args += ['--min-sdk-version=1']
|
args += ['--min-sdk-version=' + min_sdk_version]
|
||||||
return subprocess.call(args + [apk]) == 0
|
return subprocess.call(args + [apk]) == 0
|
||||||
else:
|
else:
|
||||||
logging.warning("Using Java's jarsigner, not recommended for verifying APKs! Use apksigner")
|
logging.warning("Using Java's jarsigner, not recommended for verifying APKs! Use apksigner")
|
||||||
return subprocess.call([config['jarsigner'], '-strict', '-verify', apk]) == 4
|
try:
|
||||||
|
verify_jar_signature(apk)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def verify_old_apk_signature(apk):
|
def verify_old_apk_signature(apk):
|
||||||
|
|
@ -2325,7 +2352,7 @@ def write_to_config(thisconfig, key, value=None, config_file=None):
|
||||||
|
|
||||||
# load config file, create one if it doesn't exist
|
# load config file, create one if it doesn't exist
|
||||||
if not os.path.exists(cfg):
|
if not os.path.exists(cfg):
|
||||||
os.mknod(cfg)
|
open(cfg, 'a').close()
|
||||||
logging.info("Creating empty " + cfg)
|
logging.info("Creating empty " + cfg)
|
||||||
with open(cfg, 'r', encoding="utf-8") as f:
|
with open(cfg, 'r', encoding="utf-8") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
|
||||||
|
|
@ -632,7 +632,7 @@ def download_repo_index(url_str, etag=None, verify_fingerprint=True):
|
||||||
jar = zipfile.ZipFile(fp)
|
jar = zipfile.ZipFile(fp)
|
||||||
|
|
||||||
# verify that the JAR signature is valid
|
# verify that the JAR signature is valid
|
||||||
verify_jar_signature(fp.name)
|
common.verify_jar_signature(fp.name)
|
||||||
|
|
||||||
# get public key and its fingerprint from JAR
|
# get public key and its fingerprint from JAR
|
||||||
public_key, public_key_fingerprint = get_public_key_from_jar(jar)
|
public_key, public_key_fingerprint = get_public_key_from_jar(jar)
|
||||||
|
|
@ -652,16 +652,6 @@ def download_repo_index(url_str, etag=None, verify_fingerprint=True):
|
||||||
return index, new_etag
|
return index, new_etag
|
||||||
|
|
||||||
|
|
||||||
def verify_jar_signature(file):
|
|
||||||
"""
|
|
||||||
Verifies the signature of a given JAR file.
|
|
||||||
|
|
||||||
:raises: VerificationException() if the JAR's signature could not be verified
|
|
||||||
"""
|
|
||||||
if not common.verify_apk_signature(file, jar=True):
|
|
||||||
raise VerificationException(_("The repository's index could not be verified."))
|
|
||||||
|
|
||||||
|
|
||||||
def get_public_key_from_jar(jar):
|
def get_public_key_from_jar(jar):
|
||||||
"""
|
"""
|
||||||
Get the public key and its fingerprint from a JAR file.
|
Get the public key and its fingerprint from a JAR file.
|
||||||
|
|
|
||||||
|
|
@ -100,11 +100,21 @@ def main():
|
||||||
# make sure at least aapt is found, since this can't do anything without it
|
# make sure at least aapt is found, since this can't do anything without it
|
||||||
test_config['aapt'] = common.find_sdk_tools_cmd('aapt')
|
test_config['aapt'] = common.find_sdk_tools_cmd('aapt')
|
||||||
else:
|
else:
|
||||||
# if neither --android-home nor the default sdk_path exist, prompt the user
|
# if neither --android-home nor the default sdk_path
|
||||||
|
# exist, prompt the user using platform-specific default
|
||||||
default_sdk_path = '/opt/android-sdk'
|
default_sdk_path = '/opt/android-sdk'
|
||||||
if sys.platform == 'win32' or sys.platform == 'cygwin':
|
if sys.platform == 'win32' or sys.platform == 'cygwin':
|
||||||
default_sdk_path = os.path.join(os.getenv('USERPROFILE'),
|
p = os.path.join(os.getenv('USERPROFILE'),
|
||||||
'AppData', 'Local', 'Android', 'android-sdk')
|
'AppData', 'Local', 'Android', 'android-sdk')
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
# on OSX, Homebrew is common and has an easy path to detect
|
||||||
|
p = '/usr/local/opt/android-sdk'
|
||||||
|
else:
|
||||||
|
# if the Debian packages are installed, suggest them
|
||||||
|
p = '/usr/lib/android-sdk'
|
||||||
|
if os.path.exists(p):
|
||||||
|
default_sdk_path = p
|
||||||
|
|
||||||
while not options.no_prompt:
|
while not options.no_prompt:
|
||||||
try:
|
try:
|
||||||
s = input(_('Enter the path to the Android SDK (%s) here:\n> ') % default_sdk_path)
|
s = input(_('Enter the path to the Android SDK (%s) here:\n> ') % default_sdk_path)
|
||||||
|
|
|
||||||
|
|
@ -1197,16 +1197,6 @@ def process_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk=Fal
|
||||||
apk is the scanned apk information, and cachechanged is True if the apkcache got changed.
|
apk is the scanned apk information, and cachechanged is True if the apkcache got changed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ' ' in apkfilename:
|
|
||||||
if options.rename_apks:
|
|
||||||
newfilename = apkfilename.replace(' ', '_')
|
|
||||||
os.rename(os.path.join(repodir, apkfilename),
|
|
||||||
os.path.join(repodir, newfilename))
|
|
||||||
apkfilename = newfilename
|
|
||||||
else:
|
|
||||||
logging.critical("Spaces in filenames are not allowed.")
|
|
||||||
return True, None, False
|
|
||||||
|
|
||||||
apk = {}
|
apk = {}
|
||||||
apkfile = os.path.join(repodir, apkfilename)
|
apkfile = os.path.join(repodir, apkfilename)
|
||||||
|
|
||||||
|
|
|
||||||
22
tests/IsMD5Disabled.java
Normal file
22
tests/IsMD5Disabled.java
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
import java.security.Security;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class IsMD5Disabled {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
String daString = Security.getProperty("jdk.jar.disabledAlgorithms");
|
||||||
|
String[] algorithms = daString.trim().split(",");
|
||||||
|
boolean isMD5Disabled = true;
|
||||||
|
for (String alg : algorithms) {
|
||||||
|
if (alg.trim().toLowerCase(Locale.US).startsWith("md5")) {
|
||||||
|
isMD5Disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMD5Disabled) {
|
||||||
|
System.out.println("MD5 in jdk.jar.disabledAlgorithms: " + daString);
|
||||||
|
} else {
|
||||||
|
System.out.println("MD5 allowed for JAR signatures: " + daString);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,18 @@ export PATH=/usr/lib/jvm/java-8-openjdk-amd64/bin:$PATH
|
||||||
cd $WORKSPACE/tests
|
cd $WORKSPACE/tests
|
||||||
./run-tests $apksource
|
./run-tests $apksource
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------#
|
||||||
|
# find pyvenv, to support Ubuntu/trusty's python3.4-venv
|
||||||
|
|
||||||
|
if which pyvenv; then
|
||||||
|
pyvenv=pyvenv
|
||||||
|
elif which pyvenv-3.4; then
|
||||||
|
pyvenv=pyvenv-3.4
|
||||||
|
else
|
||||||
|
echo "pyvenv required to run this test suite!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------#
|
#------------------------------------------------------------------------------#
|
||||||
# test building the source tarball, then installing it
|
# test building the source tarball, then installing it
|
||||||
|
|
@ -54,7 +66,7 @@ cd $WORKSPACE
|
||||||
python3 setup.py sdist
|
python3 setup.py sdist
|
||||||
|
|
||||||
rm -rf $WORKSPACE/env
|
rm -rf $WORKSPACE/env
|
||||||
pyvenv $WORKSPACE/env
|
$pyvenv $WORKSPACE/env
|
||||||
. $WORKSPACE/env/bin/activate
|
. $WORKSPACE/env/bin/activate
|
||||||
# workaround https://github.com/pypa/setuptools/issues/937
|
# workaround https://github.com/pypa/setuptools/issues/937
|
||||||
pip3 install setuptools==33.1.1
|
pip3 install setuptools==33.1.1
|
||||||
|
|
@ -68,7 +80,7 @@ fdroid=$WORKSPACE/env/bin/fdroid $WORKSPACE/tests/run-tests $apksource
|
||||||
# test install using install direct from git repo
|
# test install using install direct from git repo
|
||||||
cd $WORKSPACE
|
cd $WORKSPACE
|
||||||
rm -rf $WORKSPACE/env
|
rm -rf $WORKSPACE/env
|
||||||
pyvenv $WORKSPACE/env
|
$pyvenv $WORKSPACE/env
|
||||||
. $WORKSPACE/env/bin/activate
|
. $WORKSPACE/env/bin/activate
|
||||||
# workaround https://github.com/pypa/setuptools/issues/937
|
# workaround https://github.com/pypa/setuptools/issues/937
|
||||||
pip3 install setuptools==33.1.1
|
pip3 install setuptools==33.1.1
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,14 @@ class IndexTest(unittest.TestCase):
|
||||||
source_dir = os.path.join(basedir, 'signindex')
|
source_dir = os.path.join(basedir, 'signindex')
|
||||||
for f in ('testy.jar', 'guardianproject.jar'):
|
for f in ('testy.jar', 'guardianproject.jar'):
|
||||||
testfile = os.path.join(source_dir, f)
|
testfile = os.path.join(source_dir, f)
|
||||||
fdroidserver.index.verify_jar_signature(testfile)
|
fdroidserver.common.verify_jar_signature(testfile)
|
||||||
|
|
||||||
def test_verify_jar_signature_fails(self):
|
def test_verify_jar_signature_fails(self):
|
||||||
basedir = os.path.dirname(__file__)
|
basedir = os.path.dirname(__file__)
|
||||||
source_dir = os.path.join(basedir, 'signindex')
|
source_dir = os.path.join(basedir, 'signindex')
|
||||||
testfile = os.path.join(source_dir, 'unsigned.jar')
|
testfile = os.path.join(source_dir, 'unsigned.jar')
|
||||||
with self.assertRaises(fdroidserver.index.VerificationException):
|
with self.assertRaises(fdroidserver.index.VerificationException):
|
||||||
fdroidserver.index.verify_jar_signature(testfile)
|
fdroidserver.common.verify_jar_signature(testfile)
|
||||||
|
|
||||||
def test_get_public_key_from_jar_succeeds(self):
|
def test_get_public_key_from_jar_succeeds(self):
|
||||||
basedir = os.path.dirname(__file__)
|
basedir = os.path.dirname(__file__)
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@
|
||||||
<package>
|
<package>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
<versioncode>100</versioncode>
|
<versioncode>100</versioncode>
|
||||||
<apkname>urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk</apkname>
|
<apkname>urzip-Sergey Vasilyevich Rakhmaninov; Серге́й Васи́льевич Рахма́нинов, IPA: [sʲɪrˈɡʲej rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·瓦西里耶维奇·拉赫玛.apk尼诺夫 .apk</apkname>
|
||||||
<hash type="sha256">15c0ec72c74a3791f42cdb43c57df0fb11a4dbb656851bbb8cf05b26a8372789</hash>
|
<hash type="sha256">15c0ec72c74a3791f42cdb43c57df0fb11a4dbb656851bbb8cf05b26a8372789</hash>
|
||||||
<size>11471</size>
|
<size>11471</size>
|
||||||
<sdkver>4</sdkver>
|
<sdkver>4</sdkver>
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -8,14 +8,14 @@ echo_header() {
|
||||||
|
|
||||||
copy_apks_into_repo() {
|
copy_apks_into_repo() {
|
||||||
set +x
|
set +x
|
||||||
for f in `find $APKDIR -name '*.apk' | grep -F -v -e unaligned -e unsigned -e badsig -e badcert`; do
|
find $APKDIR -type f -name '*.apk' -print0 | while IFS= read -r -d '' f; do
|
||||||
name=$(basename $(dirname `dirname $f`))
|
echo $f | grep -F -v -e unaligned -e unsigned -e badsig -e badcert -e bad-unicode || continue
|
||||||
apk=`$aapt dump badging "$f" | sed -n "s,^package: name='\(.*\)' versionCode='\([0-9][0-9]*\)' .*,\1_\2.apk,p"`
|
apk=`$aapt dump badging "$f" | sed -n "s,^package: name='\(.*\)' versionCode='\([0-9][0-9]*\)' .*,\1_\2.apk,p"`
|
||||||
test $f -nt repo/$apk && rm -f repo/$apk # delete existing if $f is newer
|
test "$f" -nt repo/$apk && rm -f repo/$apk # delete existing if $f is newer
|
||||||
if [ ! -e repo/$apk ] && [ ! -e archive/$apk ]; then
|
if [ ! -e repo/$apk ] && [ ! -e archive/$apk ]; then
|
||||||
echo "$f --> repo/$apk"
|
echo "$f --> repo/$apk"
|
||||||
ln $f $1/repo/$apk || \
|
ln "$f" $1/repo/$apk || \
|
||||||
rsync -axv $f $1/repo/$apk # rsync if hard link is not possible
|
rsync -axv "$f" $1/repo/$apk # rsync if hard link is not possible
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
set -x
|
set -x
|
||||||
|
|
@ -46,6 +46,11 @@ have_git_2_3() {
|
||||||
python3 -c "import sys; from distutils.version import LooseVersion as V; sys.exit(V(sys.argv[3]) < V('2.3'))" `git --version`
|
python3 -c "import sys; from distutils.version import LooseVersion as V; sys.exit(V(sys.argv[3]) < V('2.3'))" `git --version`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_MD5_disabled() {
|
||||||
|
javac $WORKSPACE/tests/IsMD5Disabled.java && java -cp $WORKSPACE/tests IsMD5Disabled
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
#------------------------------------------------------------------------------#
|
#------------------------------------------------------------------------------#
|
||||||
# "main"
|
# "main"
|
||||||
|
|
||||||
|
|
@ -91,6 +96,13 @@ if [ -z $python ]; then
|
||||||
python=python3
|
python=python3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# try to use GNU sed on OSX/BSD cuz BSD sed sucks
|
||||||
|
if which gsed; then
|
||||||
|
sed=gsed
|
||||||
|
else
|
||||||
|
sed=sed
|
||||||
|
fi
|
||||||
|
|
||||||
set -x # show each command as it is executed
|
set -x # show each command as it is executed
|
||||||
|
|
||||||
#------------------------------------------------------------------------------#
|
#------------------------------------------------------------------------------#
|
||||||
|
|
@ -154,10 +166,10 @@ REPOROOT=`create_test_dir`
|
||||||
cd $REPOROOT
|
cd $REPOROOT
|
||||||
|
|
||||||
$fdroid init
|
$fdroid init
|
||||||
sed -i.tmp 's,^ *repo_description.*,repo_description = """获取已安装在您的设备上的应用的,' config.py
|
$sed -i.tmp 's,^ *repo_description.*,repo_description = """获取已安装在您的设备上的应用的,' config.py
|
||||||
echo "mirrors = ('https://foo.bar/fdroid', 'http://secret.onion/fdroid')" >> config.py
|
echo "mirrors = ('https://foo.bar/fdroid', 'http://secret.onion/fdroid')" >> config.py
|
||||||
mkdir metadata
|
mkdir metadata
|
||||||
cp $WORKSPACE/tests/urzip.apk repo/
|
cp $WORKSPACE/tests/urzip.apk $WORKSPACE/tests/bad-unicode*.apk repo/
|
||||||
cp $WORKSPACE/tests/metadata/info.guardianproject.urzip.yml metadata/
|
cp $WORKSPACE/tests/metadata/info.guardianproject.urzip.yml metadata/
|
||||||
|
|
||||||
$fdroid readmeta
|
$fdroid readmeta
|
||||||
|
|
@ -165,23 +177,23 @@ $fdroid update
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------#
|
#------------------------------------------------------------------------------#
|
||||||
echo_header 'run `fdroid build` in fresh git checkout from import.TestCase'
|
echo_header 'run "fdroid build" in fresh git checkout from import.TestCase'
|
||||||
|
|
||||||
cd $WORKSPACE/tests/tmp/importer
|
cd $WORKSPACE/tests/tmp/importer
|
||||||
git remote update -p
|
git remote update -p
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
# stick with known working commit, in case future commits break things for this code
|
# stick with known working commit, in case future commits break things for this code
|
||||||
git reset --hard cecf00c08aec56ae7a5eba444150c4d1ae868814
|
git reset --hard 985aa135524ab7dd1e70335fd47b22fa628b81b3
|
||||||
if [ -d $ANDROID_HOME/platforms/android-23 ]; then
|
if [ -d $ANDROID_HOME/platforms/android-23 ]; then
|
||||||
echo "build_tools = '`ls -1 $ANDROID_HOME/build-tools/ | sort -n | tail -1`'" > config.py
|
echo "build_tools = '`ls -1 $ANDROID_HOME/build-tools/ | sort -n | tail -1`'" > config.py
|
||||||
echo "force_build_tools = True" >> config.py
|
echo "force_build_tools = True" >> config.py
|
||||||
$fdroid build --verbose org.fdroid.ci.test.app:300
|
$fdroid build --verbose org.fdroid.ci.test.app:300
|
||||||
else
|
else
|
||||||
echo 'WARNING: Skipping `fdroid build` test since android-23 is missing!'
|
echo 'WARNING: Skipping "fdroid build" test since android-23 is missing!'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#------------------------------------------------------------------------------#
|
#------------------------------------------------------------------------------#
|
||||||
echo_header 'copy git import and run `fdroid scanner` on it'
|
echo_header 'copy git import and run "fdroid scanner" on it'
|
||||||
|
|
||||||
REPOROOT=`create_test_dir`
|
REPOROOT=`create_test_dir`
|
||||||
cd $REPOROOT
|
cd $REPOROOT
|
||||||
|
|
@ -228,16 +240,19 @@ test -e repo/index-v1.jar
|
||||||
grep -F '<application id=' repo/index.xml > /dev/null
|
grep -F '<application id=' repo/index.xml > /dev/null
|
||||||
grep -F '<install packageName=' repo/index.xml > /dev/null
|
grep -F '<install packageName=' repo/index.xml > /dev/null
|
||||||
grep -F '<uninstall packageName=' repo/index.xml > /dev/null
|
grep -F '<uninstall packageName=' repo/index.xml > /dev/null
|
||||||
$fdroid gpgsign --verbose
|
# OSX tests are run on Travis-CI, and gpg fails to launch gpg-agent there
|
||||||
$fdroid gpgsign --verbose
|
if [ "$TRAVIS_OS_NAME" != "osx" ]; then
|
||||||
test -e repo/obb.mainpatch.current_1619.apk.asc
|
$fdroid gpgsign --verbose
|
||||||
test -e repo/obb.main.twoversions_1101617_src.tar.gz.asc
|
$fdroid gpgsign --verbose
|
||||||
! test -e repo/obb.mainpatch.current_1619.apk.asc.asc
|
test -e repo/obb.mainpatch.current_1619.apk.asc
|
||||||
! test -e repo/obb.main.twoversions_1101617_src.tar.gz.asc.asc
|
test -e repo/obb.main.twoversions_1101617_src.tar.gz.asc
|
||||||
! test -e repo/index.xml.asc
|
! test -e repo/obb.mainpatch.current_1619.apk.asc.asc
|
||||||
|
! test -e repo/obb.main.twoversions_1101617_src.tar.gz.asc.asc
|
||||||
|
! test -e repo/index.xml.asc
|
||||||
|
fi
|
||||||
|
|
||||||
# we can't easily reproduce the timestamps for things, so just hardcode them
|
# we can't easily reproduce the timestamps for things, so just hardcode them
|
||||||
sed -i --expression='s,timestamp="[0-9]*",timestamp="1480431575",' repo/index.xml
|
$sed -i.tmp -e 's,timestamp="[0-9]*",timestamp="1480431575",' repo/index.xml
|
||||||
diff -uw $WORKSPACE/tests/repo/index.xml repo/index.xml
|
diff -uw $WORKSPACE/tests/repo/index.xml repo/index.xml
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -251,21 +266,22 @@ $fdroid init --keystore keystore.jks --repo-keyalias=sova
|
||||||
echo 'keystorepass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
echo 'keystorepass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
||||||
echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
||||||
echo "accepted_formats = ['txt']" >> config.py
|
echo "accepted_formats = ['txt']" >> config.py
|
||||||
sed -i '/allow_disabled_algorithms/d' config.py
|
$sed -i.tmp '/allow_disabled_algorithms/d' config.py
|
||||||
test -d metadata || mkdir metadata
|
test -d metadata || mkdir metadata
|
||||||
cp $WORKSPACE/tests/metadata/*.txt metadata/
|
cp $WORKSPACE/tests/metadata/*.txt metadata/
|
||||||
echo 'Summary:good test version of urzip' > metadata/info.guardianproject.urzip.txt
|
echo 'Summary:good test version of urzip' > metadata/info.guardianproject.urzip.txt
|
||||||
echo 'Summary:good MD5 sig, which is disabled algorithm' > metadata/org.bitbucket.tickytacky.mirrormirror.txt
|
echo 'Summary:good MD5 sig, which is disabled algorithm' > metadata/org.bitbucket.tickytacky.mirrormirror.txt
|
||||||
sed -i '/Archive Policy:/d' metadata/*.txt
|
$sed -i.tmp '/Archive Policy:/d' metadata/*.txt
|
||||||
test -d repo || mkdir repo
|
test -d repo || mkdir repo
|
||||||
cp $WORKSPACE/tests/urzip.apk \
|
cp $WORKSPACE/tests/urzip.apk \
|
||||||
$WORKSPACE/tests/org.bitbucket.tickytacky.mirrormirror_[0-9].apk \
|
$WORKSPACE/tests/org.bitbucket.tickytacky.mirrormirror_[0-9].apk \
|
||||||
$WORKSPACE/tests/repo/com.politedroid_[0-9].apk \
|
$WORKSPACE/tests/repo/com.politedroid_[0-9].apk \
|
||||||
$WORKSPACE/tests/repo/obb.main.twoversions_110161[357].apk \
|
$WORKSPACE/tests/repo/obb.main.twoversions_110161[357].apk \
|
||||||
repo/
|
repo/
|
||||||
sed -i 's,archive_older = [0-9],archive_older = 3,' config.py
|
$sed -i.tmp 's,archive_older = [0-9],archive_older = 3,' config.py
|
||||||
|
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
|
echo "This will fail when jarsigner allows MD5 for APK signatures"
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 5
|
test `grep '<package>' archive/index.xml | wc -l` -eq 5
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 7
|
test `grep '<package>' repo/index.xml | wc -l` -eq 7
|
||||||
|
|
||||||
|
|
@ -284,7 +300,7 @@ test -d metadata || mkdir metadata
|
||||||
cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/
|
cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/
|
||||||
test -d repo || mkdir repo
|
test -d repo || mkdir repo
|
||||||
cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk repo/
|
cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk repo/
|
||||||
sed -i 's,archive_older = [0-9],archive_older = 3,' config.py
|
$sed -i.tmp 's,archive_older = [0-9],archive_older = 3,' config.py
|
||||||
|
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 0
|
test `grep '<package>' archive/index.xml | wc -l` -eq 0
|
||||||
|
|
@ -299,7 +315,7 @@ test -e repo/com.politedroid_5.apk
|
||||||
test -e repo/com.politedroid_6.apk
|
test -e repo/com.politedroid_6.apk
|
||||||
|
|
||||||
echo "enable one app in the repo"
|
echo "enable one app in the repo"
|
||||||
sed -i 's,^Archive Policy:4,Archive Policy:1,' metadata/com.politedroid.txt
|
$sed -i.tmp 's,^Archive Policy:4,Archive Policy:1,' metadata/com.politedroid.txt
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 3
|
test `grep '<package>' archive/index.xml | wc -l` -eq 3
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
||||||
|
|
@ -313,7 +329,7 @@ test -e archive/com.politedroid_5.apk
|
||||||
test -e repo/com.politedroid_6.apk
|
test -e repo/com.politedroid_6.apk
|
||||||
|
|
||||||
echo "remove all apps from the repo"
|
echo "remove all apps from the repo"
|
||||||
sed -i 's,^Archive Policy:1,Archive Policy:0,' metadata/com.politedroid.txt
|
$sed -i.tmp 's,^Archive Policy:1,Archive Policy:0,' metadata/com.politedroid.txt
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 4
|
test `grep '<package>' archive/index.xml | wc -l` -eq 4
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 0
|
test `grep '<package>' repo/index.xml | wc -l` -eq 0
|
||||||
|
|
@ -328,7 +344,7 @@ test -e archive/com.politedroid_6.apk
|
||||||
! test -e repo/com.politedroid_6.apk
|
! test -e repo/com.politedroid_6.apk
|
||||||
|
|
||||||
echo "move back one from archive to the repo"
|
echo "move back one from archive to the repo"
|
||||||
sed -i 's,^Archive Policy:0,Archive Policy:1,' metadata/com.politedroid.txt
|
$sed -i.tmp 's,^Archive Policy:0,Archive Policy:1,' metadata/com.politedroid.txt
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 3
|
test `grep '<package>' archive/index.xml | wc -l` -eq 3
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
||||||
|
|
@ -356,10 +372,10 @@ echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
||||||
echo "accepted_formats = ['txt']" >> config.py
|
echo "accepted_formats = ['txt']" >> config.py
|
||||||
test -d metadata || mkdir metadata
|
test -d metadata || mkdir metadata
|
||||||
cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/
|
cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/
|
||||||
sed -i '/Archive Policy:/d' metadata/com.politedroid.txt
|
$sed -i.tmp '/Archive Policy:/d' metadata/com.politedroid.txt
|
||||||
test -d repo || mkdir repo
|
test -d repo || mkdir repo
|
||||||
cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk repo/
|
cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk repo/
|
||||||
sed -i 's,archive_older = [0-9],archive_older = 3,' config.py
|
$sed -i.tmp 's,archive_older = [0-9],archive_older = 3,' config.py
|
||||||
|
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 1
|
test `grep '<package>' archive/index.xml | wc -l` -eq 1
|
||||||
|
|
@ -373,7 +389,7 @@ test -e repo/com.politedroid_4.apk
|
||||||
test -e repo/com.politedroid_5.apk
|
test -e repo/com.politedroid_5.apk
|
||||||
test -e repo/com.politedroid_6.apk
|
test -e repo/com.politedroid_6.apk
|
||||||
|
|
||||||
sed -i 's,archive_older = 3,archive_older = 1,' config.py
|
$sed -i.tmp 's,archive_older = 3,archive_older = 1,' config.py
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 3
|
test `grep '<package>' archive/index.xml | wc -l` -eq 3
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
||||||
|
|
@ -387,7 +403,7 @@ test -e archive/com.politedroid_5.apk
|
||||||
test -e repo/com.politedroid_6.apk
|
test -e repo/com.politedroid_6.apk
|
||||||
|
|
||||||
# disabling deletes from the archive
|
# disabling deletes from the archive
|
||||||
sed -i 's/Build:1.3,4/Build:1.3,4\n disable=testing deletion/' metadata/com.politedroid.txt
|
$sed -i.tmp 's/Build:1.3,4/Build:1.3,4\n disable=testing deletion/' metadata/com.politedroid.txt
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 2
|
test `grep '<package>' archive/index.xml | wc -l` -eq 2
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
||||||
|
|
@ -401,7 +417,7 @@ test -e archive/com.politedroid_5.apk
|
||||||
test -e repo/com.politedroid_6.apk
|
test -e repo/com.politedroid_6.apk
|
||||||
|
|
||||||
# disabling deletes from the repo, and promotes one from the archive
|
# disabling deletes from the repo, and promotes one from the archive
|
||||||
sed -i 's/Build:1.5,6/Build:1.5,6\n disable=testing deletion/' metadata/com.politedroid.txt
|
$sed -i.tmp 's/Build:1.5,6/Build:1.5,6\n disable=testing deletion/' metadata/com.politedroid.txt
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 1
|
test `grep '<package>' archive/index.xml | wc -l` -eq 1
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
test `grep '<package>' repo/index.xml | wc -l` -eq 1
|
||||||
|
|
@ -424,12 +440,12 @@ echo 'keystorepass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.p
|
||||||
echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
echo 'keypass = "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI="' >> config.py
|
||||||
echo "accepted_formats = ['txt']" >> config.py
|
echo "accepted_formats = ['txt']" >> config.py
|
||||||
echo 'allow_disabled_algorithms = True' >> config.py
|
echo 'allow_disabled_algorithms = True' >> config.py
|
||||||
sed -i 's,archive_older = [0-9],archive_older = 3,' config.py
|
$sed -i.tmp 's,archive_older = [0-9],archive_older = 3,' config.py
|
||||||
test -d metadata || mkdir metadata
|
test -d metadata || mkdir metadata
|
||||||
cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/
|
cp $WORKSPACE/tests/metadata/com.politedroid.txt metadata/
|
||||||
echo 'Summary:good test version of urzip' > metadata/info.guardianproject.urzip.txt
|
echo 'Summary:good test version of urzip' > metadata/info.guardianproject.urzip.txt
|
||||||
echo 'Summary:good MD5 sig, disabled algorithm' > metadata/org.bitbucket.tickytacky.mirrormirror.txt
|
echo 'Summary:good MD5 sig, disabled algorithm' > metadata/org.bitbucket.tickytacky.mirrormirror.txt
|
||||||
sed -i '/Archive Policy:/d' metadata/*.txt
|
$sed -i.tmp '/Archive Policy:/d' metadata/*.txt
|
||||||
test -d repo || mkdir repo
|
test -d repo || mkdir repo
|
||||||
cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk \
|
cp $WORKSPACE/tests/repo/com.politedroid_[0-9].apk \
|
||||||
$WORKSPACE/tests/org.bitbucket.tickytacky.mirrormirror_[0-9].apk \
|
$WORKSPACE/tests/org.bitbucket.tickytacky.mirrormirror_[0-9].apk \
|
||||||
|
|
@ -459,7 +475,7 @@ test -e repo/org.bitbucket.tickytacky.mirrormirror_3.apk
|
||||||
test -e repo/org.bitbucket.tickytacky.mirrormirror_4.apk
|
test -e repo/org.bitbucket.tickytacky.mirrormirror_4.apk
|
||||||
test -e archive/urzip-badsig.apk
|
test -e archive/urzip-badsig.apk
|
||||||
|
|
||||||
sed -i '/allow_disabled_algorithms/d' config.py
|
$sed -i.tmp '/allow_disabled_algorithms/d' config.py
|
||||||
$fdroid update --pretty --nosign
|
$fdroid update --pretty --nosign
|
||||||
test `grep '<package>' archive/index.xml | wc -l` -eq 5
|
test `grep '<package>' archive/index.xml | wc -l` -eq 5
|
||||||
test `grep '<package>' repo/index.xml | wc -l` -eq 3
|
test `grep '<package>' repo/index.xml | wc -l` -eq 3
|
||||||
|
|
@ -971,7 +987,7 @@ test -e tmp/apkcache
|
||||||
grep -F '<application id=' repo/index.xml > /dev/null
|
grep -F '<application id=' repo/index.xml > /dev/null
|
||||||
|
|
||||||
# now set fake repo_keyalias
|
# now set fake repo_keyalias
|
||||||
sed -i.tmp 's,^ *repo_keyalias.*,repo_keyalias = "fake",' $REPOROOT/config.py
|
$sed -i.tmp 's,^ *repo_keyalias.*,repo_keyalias = "fake",' $REPOROOT/config.py
|
||||||
set +e
|
set +e
|
||||||
$fdroid update
|
$fdroid update
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ obb.main.twoversions_1101615.apk obb.main.twoversions 2016-01-01
|
||||||
obb.main.twoversions_1101617.apk obb.main.twoversions 2016-06-20
|
obb.main.twoversions_1101617.apk obb.main.twoversions 2016-06-20
|
||||||
obb.mainpatch.current_1619.apk obb.mainpatch.current 2016-04-23
|
obb.mainpatch.current_1619.apk obb.mainpatch.current 2016-04-23
|
||||||
obb.mainpatch.current_1619_another-release-key.apk obb.mainpatch.current 2017-06-01
|
obb.mainpatch.current_1619_another-release-key.apk obb.mainpatch.current 2017-06-01
|
||||||
urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk info.guardianproject.urzip 2016-06-23
|
urzip-Sergey Vasilyevich Rakhmaninov; Серге́й Васи́льевич Рахма́нинов, IPA: [sʲɪrˈɡʲej rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·瓦西里耶维奇·拉赫玛.apk尼诺夫 .apk info.guardianproject.urzip 2016-06-23
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue