fix colors
This commit is contained in:
parent
8b2c0fb737
commit
e5ae455cdf
@ -1,397 +0,0 @@
|
||||
|
||||
|
||||
package org.belmarket.shop;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.belmarket.shop.data.Apk;
|
||||
import org.belmarket.shop.data.App;
|
||||
import org.belmarket.shop.data.Repo;
|
||||
import org.belmarket.shop.data.RepoPersister;
|
||||
import org.belmarket.shop.data.RepoProvider;
|
||||
import org.belmarket.shop.data.Schema.RepoTable;
|
||||
import org.belmarket.shop.net.Downloader;
|
||||
import org.belmarket.shop.net.DownloaderFactory;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
/**
|
||||
* Responsible for updating an individual repository. This will:
|
||||
* * Download the index.jar
|
||||
* * Verify that it is signed correctly and by the correct certificate
|
||||
* * Parse the index.xml from the .jar file
|
||||
* * Save the resulting repo, apps, and apks to the database.
|
||||
*
|
||||
* <b>WARNING</b>: this class is the central piece of the entire security model of
|
||||
* FDroid! Avoid modifying it when possible, if you absolutely must, be very,
|
||||
* very careful with the changes that you are making!
|
||||
*/
|
||||
public class RepoUpdater {
|
||||
|
||||
private static final String TAG = "RepoUpdater";
|
||||
|
||||
private final String indexUrl;
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final Repo repo;
|
||||
private boolean hasChanged;
|
||||
|
||||
@Nullable
|
||||
private ProgressListener downloadProgressListener;
|
||||
private ProgressListener committingProgressListener;
|
||||
private ProgressListener processXmlProgressListener;
|
||||
private String cacheTag;
|
||||
private X509Certificate signingCertFromJar;
|
||||
|
||||
@NonNull private final RepoPersister persister;
|
||||
|
||||
/**
|
||||
* Updates an app repo as read out of the database into a {@link Repo} instance.
|
||||
*
|
||||
* @param repo A {@link Repo} read out of the local database
|
||||
*/
|
||||
public RepoUpdater(@NonNull Context context, @NonNull Repo repo) {
|
||||
this.context = context;
|
||||
this.repo = repo;
|
||||
this.persister = new RepoPersister(context, repo);
|
||||
|
||||
String url = repo.address + "/index.jar";
|
||||
String versionName = Utils.getVersionName(context);
|
||||
if (versionName != null) {
|
||||
url += "?client_version=" + versionName;
|
||||
}
|
||||
this.indexUrl = url;
|
||||
}
|
||||
|
||||
public void setDownloadProgressListener(ProgressListener progressListener) {
|
||||
this.downloadProgressListener = progressListener;
|
||||
}
|
||||
|
||||
public void setProcessXmlProgressListener(ProgressListener progressListener) {
|
||||
this.processXmlProgressListener = progressListener;
|
||||
}
|
||||
|
||||
public void setCommittingProgressListener(ProgressListener progressListener) {
|
||||
this.committingProgressListener = progressListener;
|
||||
}
|
||||
|
||||
public boolean hasChanged() {
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
private Downloader downloadIndex() throws UpdateException {
|
||||
Downloader downloader = null;
|
||||
try {
|
||||
downloader = DownloaderFactory.create(context, indexUrl);
|
||||
downloader.setCacheTag(repo.lastetag);
|
||||
downloader.setListener(downloadProgressListener);
|
||||
downloader.download();
|
||||
|
||||
if (downloader.isCached()) {
|
||||
// The index is unchanged since we last read it. We just mark
|
||||
// everything that came from this repo as being updated.
|
||||
Utils.debugLog(TAG, "Repo index for " + indexUrl + " is up to date (by etag)");
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
if (downloader != null && downloader.outputFile != null) {
|
||||
if (!downloader.outputFile.delete()) {
|
||||
Log.w(TAG, "Couldn't delete file: " + downloader.outputFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
throw new UpdateException(repo, "Error getting index file", e);
|
||||
} catch (InterruptedException e) {
|
||||
// ignored if canceled, the local database just won't be updated
|
||||
e.printStackTrace();
|
||||
}
|
||||
return downloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* All repos are represented by a signed jar file, {@code index.jar}, which contains
|
||||
* a single file, {@code index.xml}. This takes the {@code index.jar}, verifies the
|
||||
* signature, then returns the unzipped {@code index.xml}.
|
||||
*
|
||||
* @throws UpdateException All error states will come from here.
|
||||
*/
|
||||
public void update() throws UpdateException {
|
||||
|
||||
final Downloader downloader = downloadIndex();
|
||||
hasChanged = downloader.hasChanged();
|
||||
|
||||
if (hasChanged) {
|
||||
// Don't worry about checking the status code for 200. If it was a
|
||||
// successful download, then we will have a file ready to use:
|
||||
cacheTag = downloader.getCacheTag();
|
||||
processDownloadedFile(downloader.outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
private ContentValues repoDetailsToSave;
|
||||
private String signingCertFromIndexXml;
|
||||
|
||||
private RepoXMLHandler.IndexReceiver createIndexReceiver() {
|
||||
return new RepoXMLHandler.IndexReceiver() {
|
||||
@Override
|
||||
public void receiveRepo(String name, String description, String signingCert, int maxAge, int version, long timestamp) {
|
||||
signingCertFromIndexXml = signingCert;
|
||||
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveApp(App app, List<Apk> packages) {
|
||||
try {
|
||||
persister.saveToDb(app, packages);
|
||||
} catch (UpdateException e) {
|
||||
throw new RuntimeException("Error while saving repo details to database.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void processDownloadedFile(File downloadedFile) throws UpdateException {
|
||||
InputStream indexInputStream = null;
|
||||
try {
|
||||
if (downloadedFile == null || !downloadedFile.exists()) {
|
||||
throw new UpdateException(repo, downloadedFile + " does not exist!");
|
||||
}
|
||||
|
||||
// Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes
|
||||
// breakage when verifying the signature of the downloaded .jar. For more
|
||||
// details, check out https://gitlab.com/fdroid/fdroidclient/issues/111.
|
||||
FDroidApp.disableSpongyCastleOnLollipop();
|
||||
|
||||
JarFile jarFile = new JarFile(downloadedFile, true);
|
||||
JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
|
||||
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
|
||||
processXmlProgressListener, new URL(repo.address), (int) indexEntry.getSize());
|
||||
|
||||
// Process the index...
|
||||
SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
final SAXParser parser = factory.newSAXParser();
|
||||
final XMLReader reader = parser.getXMLReader();
|
||||
final RepoXMLHandler repoXMLHandler = new RepoXMLHandler(repo, createIndexReceiver());
|
||||
reader.setContentHandler(repoXMLHandler);
|
||||
reader.parse(new InputSource(indexInputStream));
|
||||
|
||||
long timestamp = repoDetailsToSave.getAsLong(RepoTable.Cols.TIMESTAMP);
|
||||
if (timestamp < repo.timestamp) {
|
||||
throw new UpdateException(repo, "index.jar is older that current index! "
|
||||
+ timestamp + " < " + repo.timestamp);
|
||||
}
|
||||
|
||||
signingCertFromJar = getSigningCertFromJar(indexEntry);
|
||||
|
||||
// JarEntry can only read certificates after the file represented by that JarEntry
|
||||
// has been read completely, so verification cannot run until now...
|
||||
assertSigningCertFromXmlCorrect();
|
||||
commitToDb();
|
||||
} catch (SAXException | ParserConfigurationException | IOException e) {
|
||||
throw new UpdateException(repo, "Error parsing index", e);
|
||||
} finally {
|
||||
FDroidApp.enableSpongyCastleOnLollipop();
|
||||
Utils.closeQuietly(indexInputStream);
|
||||
if (downloadedFile != null) {
|
||||
if (!downloadedFile.delete()) {
|
||||
Log.w(TAG, "Couldn't delete file: " + downloadedFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void commitToDb() throws UpdateException {
|
||||
Log.i(TAG, "Repo signature verified, saving app metadata to database.");
|
||||
if (committingProgressListener != null) {
|
||||
try {
|
||||
//TODO this should be an event, not a progress listener
|
||||
committingProgressListener.onProgress(new URL(indexUrl), 0, -1);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
persister.commit(repoDetailsToSave);
|
||||
}
|
||||
|
||||
private void assertSigningCertFromXmlCorrect() throws SigningException {
|
||||
|
||||
// no signing cert read from database, this is the first use
|
||||
if (repo.signingCertificate == null) {
|
||||
verifyAndStoreTOFUCerts(signingCertFromIndexXml, signingCertFromJar);
|
||||
}
|
||||
verifyCerts(signingCertFromIndexXml, signingCertFromJar);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tracking data for the repo represented by this instance (index version, etag,
|
||||
* description, human-readable name, etc.
|
||||
*/
|
||||
private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge, int version, long timestamp) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
|
||||
|
||||
if (repo.lastetag == null || !repo.lastetag.equals(cacheTag)) {
|
||||
values.put(RepoTable.Cols.LAST_ETAG, cacheTag);
|
||||
}
|
||||
|
||||
if (version != -1 && version != repo.version) {
|
||||
Utils.debugLog(TAG, "Repo specified a new version: from " + repo.version + " to " + version);
|
||||
values.put(RepoTable.Cols.VERSION, version);
|
||||
}
|
||||
|
||||
if (maxAge != -1 && maxAge != repo.maxage) {
|
||||
Utils.debugLog(TAG, "Repo specified a new maximum age - updated");
|
||||
values.put(RepoTable.Cols.MAX_AGE, maxAge);
|
||||
}
|
||||
|
||||
if (description != null && !description.equals(repo.description)) {
|
||||
values.put(RepoTable.Cols.DESCRIPTION, description);
|
||||
}
|
||||
|
||||
if (name != null && !name.equals(repo.name)) {
|
||||
values.put(RepoTable.Cols.NAME, name);
|
||||
}
|
||||
|
||||
if (timestamp != repo.timestamp) {
|
||||
values.put(RepoTable.Cols.TIMESTAMP, timestamp);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static class UpdateException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -4492452418826132803L;
|
||||
public final Repo repo;
|
||||
|
||||
public UpdateException(Repo repo, String message) {
|
||||
super(message);
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
public UpdateException(Repo repo, String message, Exception cause) {
|
||||
super(message, cause);
|
||||
this.repo = repo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SigningException extends UpdateException {
|
||||
public SigningException(Repo repo, String message) {
|
||||
super(repo, "Repository was not signed correctly: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FDroid's index.jar is signed using a particular format and does not allow lots of
|
||||
* signing setups that would be valid for a regular jar. This validates those
|
||||
* restrictions.
|
||||
*/
|
||||
private X509Certificate getSigningCertFromJar(JarEntry jarEntry) throws SigningException {
|
||||
final CodeSigner[] codeSigners = jarEntry.getCodeSigners();
|
||||
if (codeSigners == null || codeSigners.length == 0) {
|
||||
throw new SigningException(repo, "No signature found in index");
|
||||
}
|
||||
/* we could in theory support more than 1, but as of now we do not */
|
||||
if (codeSigners.length > 1) {
|
||||
throw new SigningException(repo, "index.jar must be signed by a single code signer!");
|
||||
}
|
||||
List<? extends Certificate> certs = codeSigners[0].getSignerCertPath().getCertificates();
|
||||
if (certs.size() != 1) {
|
||||
throw new SigningException(repo, "index.jar code signers must only have a single certificate!");
|
||||
}
|
||||
return (X509Certificate) certs.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* A new repo can be added with or without the fingerprint of the signing
|
||||
* certificate. If no fingerprint is supplied, then do a pure TOFU and just
|
||||
* store the certificate as valid. If there is a fingerprint, then first
|
||||
* check that the signing certificate in the jar matches that fingerprint.
|
||||
*/
|
||||
private void verifyAndStoreTOFUCerts(String certFromIndexXml, X509Certificate rawCertFromJar)
|
||||
throws SigningException {
|
||||
if (repo.signingCertificate != null) {
|
||||
return; // there is a repo.signingCertificate already, nothing to TOFU
|
||||
}
|
||||
|
||||
/* The first time a repo is added, it can be added with the signing certificate's
|
||||
* fingerprint. In that case, check that fingerprint against what is
|
||||
* actually in the index.jar itself. If no fingerprint, just store the
|
||||
* signing certificate */
|
||||
if (repo.fingerprint != null) {
|
||||
String fingerprintFromIndexXml = Utils.calcFingerprint(certFromIndexXml);
|
||||
String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
|
||||
if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml)
|
||||
|| !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
|
||||
throw new SigningException(repo, "Supplied certificate fingerprint does not match!");
|
||||
}
|
||||
} // else - no info to check things are valid, so just Trust On First Use
|
||||
|
||||
Utils.debugLog(TAG, "Saving new signing certificate in the database for " + repo.address);
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), ""));
|
||||
values.put(RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
|
||||
RepoProvider.Helper.update(context, repo, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* FDroid works with three copies of the signing certificate:
|
||||
* <li>in the downloaded jar</li>
|
||||
* <li>in the index XML</li>
|
||||
* <li>stored in the local database</li>
|
||||
* It would work better removing the copy from the index XML, but it needs to stay
|
||||
* there for backwards compatibility since the old TOFU process requires it. Therefore,
|
||||
* since all three have to be present, all three are compared.
|
||||
*
|
||||
* @param certFromIndexXml the cert written into the header of the index XML
|
||||
* @param rawCertFromJar the {@link X509Certificate} embedded in the downloaded jar
|
||||
*/
|
||||
private void verifyCerts(String certFromIndexXml, X509Certificate rawCertFromJar) throws SigningException {
|
||||
// convert binary data to string version that is used in FDroid's database
|
||||
String certFromJar = Hasher.hex(rawCertFromJar);
|
||||
|
||||
// repo and repo.signingCertificate must be pre-loaded from the database
|
||||
if (TextUtils.isEmpty(repo.signingCertificate)
|
||||
|| TextUtils.isEmpty(certFromJar)
|
||||
|| TextUtils.isEmpty(certFromIndexXml)) {
|
||||
throw new SigningException(repo, "A empty repo or signing certificate is invalid!");
|
||||
}
|
||||
|
||||
// though its called repo.signingCertificate, its actually a X509 certificate
|
||||
if (repo.signingCertificate.equals(certFromJar)
|
||||
&& repo.signingCertificate.equals(certFromIndexXml)
|
||||
&& certFromIndexXml.equals(certFromJar)) {
|
||||
return; // we have a match!
|
||||
}
|
||||
throw new SigningException(repo, "Signing certificate does not match!");
|
||||
}
|
||||
|
||||
}
|
@ -1,307 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
|
||||
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.belmarket.shop;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.belmarket.shop.data.Apk;
|
||||
import org.belmarket.shop.data.App;
|
||||
import org.belmarket.shop.data.Repo;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parses the index.xml into Java data structures.
|
||||
*/
|
||||
public class RepoXMLHandler extends DefaultHandler {
|
||||
|
||||
// The repo we're processing.
|
||||
private final Repo repo;
|
||||
|
||||
private List<Apk> apksList = new ArrayList<>();
|
||||
|
||||
private App curapp;
|
||||
private Apk curapk;
|
||||
|
||||
private String currentApkHashType;
|
||||
|
||||
// After processing the XML, these will be -1 if the index didn't specify
|
||||
// them - otherwise it will be the value specified.
|
||||
private int repoMaxAge = -1;
|
||||
private int repoVersion;
|
||||
private long repoTimestamp;
|
||||
private String repoDescription;
|
||||
private String repoName;
|
||||
|
||||
// the X.509 signing certificate stored in the header of index.xml
|
||||
private String repoSigningCert;
|
||||
|
||||
private final StringBuilder curchars = new StringBuilder();
|
||||
|
||||
interface IndexReceiver {
|
||||
void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp);
|
||||
|
||||
void receiveApp(App app, List<Apk> packages);
|
||||
}
|
||||
|
||||
private final IndexReceiver receiver;
|
||||
|
||||
public RepoXMLHandler(Repo repo, @NonNull IndexReceiver receiver) {
|
||||
this.repo = repo;
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
curchars.append(ch, start, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName)
|
||||
throws SAXException {
|
||||
|
||||
if ("application".equals(localName) && curapp != null) {
|
||||
onApplicationParsed();
|
||||
} else if ("package".equals(localName) && curapk != null && curapp != null) {
|
||||
apksList.add(curapk);
|
||||
curapk = null;
|
||||
} else if ("repo".equals(localName)) {
|
||||
onRepoParsed();
|
||||
} else if (curchars.length() == 0) {
|
||||
// All options below require non-empty content
|
||||
return;
|
||||
}
|
||||
final String str = curchars.toString().trim();
|
||||
if (curapk != null) {
|
||||
switch (localName) {
|
||||
case "version":
|
||||
curapk.versionName = str;
|
||||
break;
|
||||
case "versioncode":
|
||||
curapk.versionCode = Utils.parseInt(str, -1);
|
||||
break;
|
||||
case "size":
|
||||
curapk.size = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "hash":
|
||||
if (currentApkHashType == null || "md5".equals(currentApkHashType)) {
|
||||
if (curapk.hash == null) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "SHA-256";
|
||||
}
|
||||
} else if ("sha256".equals(currentApkHashType)) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "SHA-256";
|
||||
}
|
||||
break;
|
||||
case "sig":
|
||||
curapk.sig = str;
|
||||
break;
|
||||
case "srcname":
|
||||
curapk.srcname = str;
|
||||
break;
|
||||
case "apkname":
|
||||
curapk.apkName = str;
|
||||
break;
|
||||
case "sdkver":
|
||||
curapk.minSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MIN_VALUE);
|
||||
break;
|
||||
case "targetSdkVersion":
|
||||
curapk.targetSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MIN_VALUE);
|
||||
break;
|
||||
case "maxsdkver":
|
||||
curapk.maxSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MAX_VALUE);
|
||||
if (curapk.maxSdkVersion == 0) {
|
||||
// before fc0df0dcf4dd0d5f13de82d7cd9254b2b48cb62d, this could be 0
|
||||
curapk.maxSdkVersion = Apk.SDK_VERSION_MAX_VALUE;
|
||||
}
|
||||
break;
|
||||
case "added":
|
||||
curapk.added = Utils.parseDate(str, null);
|
||||
break;
|
||||
case "permissions":
|
||||
curapk.permissions = Utils.parseCommaSeparatedString(str);
|
||||
break;
|
||||
case "features":
|
||||
curapk.features = Utils.parseCommaSeparatedString(str);
|
||||
break;
|
||||
case "nativecode":
|
||||
curapk.nativecode = Utils.parseCommaSeparatedString(str);
|
||||
break;
|
||||
}
|
||||
} else if (curapp != null) {
|
||||
switch (localName) {
|
||||
case "name":
|
||||
curapp.name = str;
|
||||
break;
|
||||
case "icon":
|
||||
curapp.icon = str;
|
||||
break;
|
||||
case "description":
|
||||
// This is the old-style description. We'll read it
|
||||
// if present, to support old repos, but in newer
|
||||
// repos it will get overwritten straight away!
|
||||
curapp.description = "<p>" + str + "</p>";
|
||||
break;
|
||||
case "desc":
|
||||
// New-style description.
|
||||
curapp.description = str;
|
||||
break;
|
||||
case "summary":
|
||||
curapp.summary = str;
|
||||
break;
|
||||
case "license":
|
||||
curapp.license = str;
|
||||
break;
|
||||
case "author":
|
||||
curapp.author = str;
|
||||
break;
|
||||
case "email":
|
||||
curapp.email = str;
|
||||
break;
|
||||
case "source":
|
||||
curapp.sourceURL = str;
|
||||
break;
|
||||
case "changelog":
|
||||
curapp.changelogURL = str;
|
||||
break;
|
||||
case "donate":
|
||||
curapp.donateURL = str;
|
||||
break;
|
||||
case "bitcoin":
|
||||
curapp.bitcoinAddr = str;
|
||||
break;
|
||||
case "litecoin":
|
||||
curapp.litecoinAddr = str;
|
||||
break;
|
||||
case "flattr":
|
||||
curapp.flattrID = str;
|
||||
break;
|
||||
case "web":
|
||||
curapp.webURL = str;
|
||||
break;
|
||||
case "tracker":
|
||||
curapp.trackerURL = str;
|
||||
break;
|
||||
case "added":
|
||||
curapp.added = Utils.parseDate(str, null);
|
||||
break;
|
||||
case "lastupdated":
|
||||
curapp.lastUpdated = Utils.parseDate(str, null);
|
||||
break;
|
||||
case "marketversion":
|
||||
curapp.upstreamVersionName = str;
|
||||
break;
|
||||
case "marketvercode":
|
||||
curapp.upstreamVersionCode = Utils.parseInt(str, -1);
|
||||
break;
|
||||
case "categories":
|
||||
curapp.categories = Utils.parseCommaSeparatedString(str);
|
||||
break;
|
||||
case "antifeatures":
|
||||
curapp.antiFeatures = Utils.parseCommaSeparatedString(str);
|
||||
break;
|
||||
case "requirements":
|
||||
curapp.requirements = Utils.parseCommaSeparatedString(str);
|
||||
break;
|
||||
}
|
||||
} else if ("description".equals(localName)) {
|
||||
repoDescription = cleanWhiteSpace(str);
|
||||
}
|
||||
}
|
||||
|
||||
private void onApplicationParsed() {
|
||||
receiver.receiveApp(curapp, apksList);
|
||||
curapp = null;
|
||||
apksList = new ArrayList<>();
|
||||
// If the app packageName is already present in this apps list, then it
|
||||
// means the same index file has a duplicate app, which should
|
||||
// not be allowed.
|
||||
// However, I'm thinking that it should be undefined behaviour,
|
||||
// because it is probably a bug in the fdroid server that made it
|
||||
// happen, and I don't *think* it will crash the client, because
|
||||
// the first app will insert, the second one will update the newly
|
||||
// inserted one.
|
||||
|
||||
//////#TODO
|
||||
|
||||
/*
|
||||
|
||||
sample this as a xml handlers
|
||||
should add screen shot id the project as it be
|
||||
and also there is no way to contact developer
|
||||
another is to create a place to collect userr data and vote for a package
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
private void onRepoParsed() {
|
||||
receiver.receiveRepo(repoName, repoDescription, repoSigningCert, repoMaxAge, repoVersion, repoTimestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attributes) throws SAXException {
|
||||
super.startElement(uri, localName, qName, attributes);
|
||||
|
||||
if ("repo".equals(localName)) {
|
||||
repoSigningCert = attributes.getValue("", "pubkey");
|
||||
repoMaxAge = Utils.parseInt(attributes.getValue("", "maxage"), -1);
|
||||
repoVersion = Utils.parseInt(attributes.getValue("", "version"), -1);
|
||||
repoName = cleanWhiteSpace(attributes.getValue("", "name"));
|
||||
repoDescription = cleanWhiteSpace(attributes.getValue("", "description"));
|
||||
repoTimestamp = parseLong(attributes.getValue("", "timestamp"), 0);
|
||||
} else if ("application".equals(localName) && curapp == null) {
|
||||
curapp = new App();
|
||||
curapp.packageName = attributes.getValue("", "id");
|
||||
} else if ("package".equals(localName) && curapp != null && curapk == null) {
|
||||
curapk = new Apk();
|
||||
curapk.packageName = curapp.packageName;
|
||||
curapk.repo = repo.getId();
|
||||
currentApkHashType = null;
|
||||
|
||||
} else if ("hash".equals(localName) && curapk != null) {
|
||||
currentApkHashType = attributes.getValue("", "type");
|
||||
}
|
||||
curchars.setLength(0);
|
||||
}
|
||||
|
||||
private static String cleanWhiteSpace(@Nullable String str) {
|
||||
return str == null ? null : str.replaceAll("\\s", " ");
|
||||
}
|
||||
|
||||
private static long parseLong(String str, long fallback) {
|
||||
if (str == null || str.length() == 0) {
|
||||
return fallback;
|
||||
}
|
||||
long result;
|
||||
try {
|
||||
result = Long.parseLong(str);
|
||||
} catch (NumberFormatException e) {
|
||||
result = fallback;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
<color name="red">#ffdd2c00</color>
|
||||
|
||||
<color name="fdroid_blue">#ffdd2c00</color>
|
||||
<color name="fdroid_blue_dark">#ff0d47a1</color>
|
||||
<color name="fdroid_blue_dark">#9e1010</color>
|
||||
<color name="fdroid_blue_darkest">#ff042570</color>
|
||||
<color name="fdroid_green">#ff8ab000</color>
|
||||
<color name="fdroid_night">#ff222222</color>
|
||||
|
@ -4,13 +4,12 @@
|
||||
<string-array name="default_repos">
|
||||
|
||||
<!-- name -->
|
||||
<item>F-Droid</item>
|
||||
<item>BelMarket</item>
|
||||
<!-- address -->
|
||||
<item>https://f-droid.org/repo</item>
|
||||
<item>https://shop.belmarket.ir/</item>
|
||||
<!-- description -->
|
||||
<item>The official F-Droid repository. Applications in this repository are mostly built
|
||||
directory from the source code. Some are official binaries built by the original
|
||||
application developers - these will be replaced by source-built versions over time.
|
||||
<item>
|
||||
مخزن رسمی بِلمارکت ساخته شده از آخرین نسخهٔ سورسکد نرمافزارها
|
||||
</item>
|
||||
<!-- version -->
|
||||
<item>13</item>
|
||||
@ -22,71 +21,28 @@
|
||||
<item>ignore</item>
|
||||
<!-- pubkey -->
|
||||
<item>
|
||||
3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
|
||||
308204f1308202d9a003020102020430b0c5af300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d536572766572301e170d3136303832323035313132355a170d3434303130383035313132355a30293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d53657276657230820222300d06092a864886f70d01010105000382020f003082020a028202010099c822a51e759fbcc83561cd415b99b679de2521c679b5929ba9ed5fd85ebf8c7b22567b1c9b44a8c863ad066f1df2b78ef99bb0e3fbc6f904a5cc146d3fd1a06fe696c04aab97dac037956743b9e26f32a4fa4280e23cadd1042f17caad7837af946307c369cf3bbef4cf9ae5ff4fff06979fc7229464d030ea7f6f0bd720c8b84c8f398cf3cb750a788a6b0a0a6954f2b47a325ee30be3062645f41dedc67ce95614626d625b0072fd8560e856f83da5cb3d65efe048b86ddbd9c828e316bc554ec7cb9d2608f1d71aa1ebc18d08525675627b53ec2966e8eae4e79a659b196bce20721b5652b7ce1c0e2130d5ddb1a52718652721fc67ebfbfa6b54dfaca64bb7d5c02fcdbdcd5cc912f4b751db2573545b5d3b67f6338d2513b6b1f12054591d6b3fb4f22c6aadfe50a4077ed9c92ac99dbca9c4380a86cb534ce6a315a30f6b65b6b8dd0aa2df6b416b868323c05929c24324024933160ca620931099de010a308196811106d958cbae02554af2a2b2883e56753a975e2ac2ae351923a80871d445495f9e86f40d308eb1000bf173264ca9ed774caf0d7c6ab48512b7cffe8c20982d196cd04c285c06a6dccb1b7d26c79bbb4603f297c3fc128776751cfa97de6af4ef90266e81c667ede0a8a8feb9ba1645e517c7f98695a85ef773d618d35c07ec597c64c1c461510db7da8172ed2769ce92dc2658aa00f6e02a28f30203010001a321301f301d0603551d0e041604145e653e4723c8cbbb83b615a95728266944a0a396300d06092a864886f70d01010b050003820201006589602a0ffa7131f21d57a5354b3e22f988852508332cd05dec3673c9ce955834829aec6ae1f58304e8cef7d5707e8340c5d370f640b1b6b5c2c46d3aace0f1568d1951148b76205cd72c66870449c3e75d2ce748463d459b568f3cf8d4c1719c11a3e0d039105f2c6750821954be5c40887af6ca800a39b77a4db1f2038c9f98cc1c5a5382ff136e9a708c17333d43400fc0f14f2ebc56442565aa95e84e224dcb665faedc92be8c9449c594668cef78018bae5c86f57ed00926afeada8e0a42bbc02ee4dc069b92f43b4679288a755401e1d733d1146abcc0574d39cd4c057f1edb23d17411d6495712b604f9e88073ffab6bb05c73e685b5d47eab1bcbb433719cc36b3bb024c993a3da85334774bac4ba18963f237dcf5d2ecd663700b76352b5347206bcf1635135c9694cf9ce8d75b37642792b08be56e3b13dde797a6642368a2203e0dd013f533edd73816c8bc61aea1f121c3bb210571cac50005b4b234d6d30d5e48b6292b0e5774d23da036639ef1bc4070fb33461f07892bcf3fbb5d8ba5b8e23a945774da3d618d1b6ba59e262bf1f5b71dbce11da59cf05e979a1bf4d62805c9c2a88cb7155075d14c9264b29ec122b76cff78e88ab9035b5a4540c0f3cc2339fc2ec5928f5ffa3a17f6bc6fdcc92ac89b55d29525206c74a9281ab91da24f3db5c46882476caa8dc6eed9a22624c25907990b48953b730bf
|
||||
</item>
|
||||
|
||||
<!-- name -->
|
||||
<item>F-Droid Archive</item>
|
||||
<item>Belmarket Binary repo</item>
|
||||
<!-- address -->
|
||||
<item>https://f-droid.org/archive</item>
|
||||
<item>https://bin.belmarket.ir/</item>
|
||||
<!-- description -->
|
||||
<item>The archive repository of the F-Droid client. This contains older versions of
|
||||
applications from the main repository.
|
||||
<item>
|
||||
مخزن دودویی بِل مارکت، ساخته شده توسط فایلهای نصبی سیستمعامل
|
||||
</item>
|
||||
<!-- version -->
|
||||
<item>13</item>
|
||||
<item>12</item>
|
||||
<!-- enabled -->
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<!-- priority -->
|
||||
<item>2</item>
|
||||
<!-- push requests -->
|
||||
<item>ignore</item>
|
||||
<!-- pubkey -->
|
||||
<item>
|
||||
3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
|
||||
</item>
|
||||
|
||||
<!-- name -->
|
||||
<item>Guardian Project</item>
|
||||
<!-- address -->
|
||||
<item>https://guardianproject.info/fdroid/repo</item>
|
||||
<!-- description -->
|
||||
<item>The official app repository of The Guardian Project. Applications in this repository
|
||||
are official binaries build by the original application developers and signed by the
|
||||
same key as the APKs that are released in the Google Play store.
|
||||
</item>
|
||||
<!-- version -->
|
||||
<item>13</item>
|
||||
<!-- enabled -->
|
||||
<item>0</item>
|
||||
<!-- priority -->
|
||||
<item>3</item>
|
||||
<!-- push requests -->
|
||||
<item>ignore</item>
|
||||
<!-- pubkey -->
|
||||
<item>
|
||||
308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f
|
||||
</item>
|
||||
|
||||
<!-- name -->
|
||||
<item>Guardian Project Archive</item>
|
||||
<!-- address -->
|
||||
<item>https://guardianproject.info/fdroid/archive</item>
|
||||
<!-- description -->
|
||||
<item>The official repository of The Guardian Project apps for use with F-Droid client. This
|
||||
contains older versions of applications from the main repository.
|
||||
</item>
|
||||
<!-- version -->
|
||||
<item>13</item>
|
||||
<!-- enabled -->
|
||||
<item>0</item>
|
||||
<!-- priority -->
|
||||
<item>4</item>
|
||||
<!-- push requests -->
|
||||
<item>ignore</item>
|
||||
<!-- pubkey -->
|
||||
<item>
|
||||
308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f
|
||||
308204f1308202d9a0030201020204297435a4300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d536572766572301e170d3136313031373131303834355a170d3434303330343131303834355a30293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d53657276657230820222300d06092a864886f70d01010105000382020f003082020a02820201009441ca19cbb4a9d6021bc5c91bee5da803807d12aaeb7e8ce3b6ae701e681702c1a96b9e9c4c7c8988d681f30fab152dea265d00bc9dd45b187c3eee1755091d8ed37a4a19ee4687d6dc3c675f2132c8e116a3b9e2c7b60f38ded458496702c19963bd0673dc710c63f952f17fd43bee9ff9cb4e1f84585e8abdd379ce2564044a049abd31108bb7b06ce3e136a222be42aa85ace041c12c92b3737d4767d7e313a559127165e3b49fed77fcdbe29485e82ff9cd71b3b36735bf747969406574e8f93343d0e388230b8aeba945e524b5158b3582ed4d336f2d42f1b079c34a40b9c35e125b7fc95585b74072add11c41663b966bb1eeac025cd31e911f8eb955c7c6bd2e5eee701bc8b91843037372dcd3c2509c9431d28b9d76924556d2cda7383a37b3d54d7db2db634a8e5fde89fdcba1ddcc57ba25050ed7a5591fb1031e02457147203c546765555684d2b1f1a865b1fa874e8e2aeaa16c83db389cf85a2fe1bebfde0f50fd309d69f52514b6ef63c26918a14f9d993eb01ea133228103bdab3a66b31e55754f8cdb753ed620074fcfdf1e9fcac279faad671425e3afdd2c8a27a15d65c1f7bc2fc708e9643ae06898b165c1a1881c649ac2894e79653c5a33a6d8276847010caa747fe1995b35c791b3540b4ce713f99a515aa30c813066d4ae60ce9e473855429f3f0973ef26b4047ee7df70696ed9cbd0bd4f196f7f0203010001a321301f301d0603551d0e0416041445a91de5179148e2c0e3a8e89b7a48de1d872e3d300d06092a864886f70d01010b050003820201001f76cdd43617b464b7dab3e3eb942ad2d01c0ade31853c26893d553d8c3028f385e8e68c9da508389c15892ff5d492b203c1637660d59ac55149fa43c19bcef24ea8111661c63b444f414ccba6ad6d114b2ef489087d715aa91ce57d512d89be392af4d0ba7006ee5c8497bf676d3d3e85cc097c24bcbeabcf32fdd225d2df10f3f53432a60aa51b974fb978eefaaee9ea8f0d5b08ef539531c07f7284df1e6f6fef2b60fb415c4292ceb2ac0ed71fa36a4c075f8b86021660e8194c0ecd00ca65195312c9d76da6232d137c350a912977e4ba5fef8e5212108ea76084b1b02b1e686d9839e3c35745c24bbe19ce1b886bf8b70aa82556cf977ac16d7bc3b2eec5ca2d63f07600a534274a45a156639228b43d46b3547983ea76c6e912c9be85dea68b4cb5227814c50dcc38e289d08ad5d96c997591a949ac9f21883a57ad2f22bcf9ac39b88de831f301827a12db0f051a6cafa97b88aca0abcf86002296a593f27eb5b30f25afacfa4ccf7e53095f70929fadbe84330bb15e491974b8163e34b14c2c0e1ff9afa27466a0f17e74d53108e7fe7c3a2a6122f470df69e68da1fcd6bfd9ef7bd7e5b4511c22786c49ab7c638ec246a41a54f646815bfc29a0a9e6e32f90960ffa0c52b1fee2c8ff0429bb18d7f148a49c34f9d118e9761c9b1a09d3b5debb5b2e2e1cb3c772c2687035fc9c4aef796b0eb45c5d4fa62c787f12
|
||||
</item>
|
||||
|
||||
</string-array>
|
||||
|
Loading…
x
Reference in New Issue
Block a user