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="red">#ffdd2c00</color>
|
||||||
|
|
||||||
<color name="fdroid_blue">#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_blue_darkest">#ff042570</color>
|
||||||
<color name="fdroid_green">#ff8ab000</color>
|
<color name="fdroid_green">#ff8ab000</color>
|
||||||
<color name="fdroid_night">#ff222222</color>
|
<color name="fdroid_night">#ff222222</color>
|
||||||
|
@ -4,13 +4,12 @@
|
|||||||
<string-array name="default_repos">
|
<string-array name="default_repos">
|
||||||
|
|
||||||
<!-- name -->
|
<!-- name -->
|
||||||
<item>F-Droid</item>
|
<item>BelMarket</item>
|
||||||
<!-- address -->
|
<!-- address -->
|
||||||
<item>https://f-droid.org/repo</item>
|
<item>https://shop.belmarket.ir/</item>
|
||||||
<!-- description -->
|
<!-- description -->
|
||||||
<item>The official F-Droid repository. Applications in this repository are mostly built
|
<item>
|
||||||
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 -->
|
<!-- version -->
|
||||||
<item>13</item>
|
<item>13</item>
|
||||||
@ -22,71 +21,28 @@
|
|||||||
<item>ignore</item>
|
<item>ignore</item>
|
||||||
<!-- pubkey -->
|
<!-- pubkey -->
|
||||||
<item>
|
<item>
|
||||||
3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
|
308204f1308202d9a003020102020430b0c5af300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d536572766572301e170d3136303832323035313132355a170d3434303130383035313132355a30293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d53657276657230820222300d06092a864886f70d01010105000382020f003082020a028202010099c822a51e759fbcc83561cd415b99b679de2521c679b5929ba9ed5fd85ebf8c7b22567b1c9b44a8c863ad066f1df2b78ef99bb0e3fbc6f904a5cc146d3fd1a06fe696c04aab97dac037956743b9e26f32a4fa4280e23cadd1042f17caad7837af946307c369cf3bbef4cf9ae5ff4fff06979fc7229464d030ea7f6f0bd720c8b84c8f398cf3cb750a788a6b0a0a6954f2b47a325ee30be3062645f41dedc67ce95614626d625b0072fd8560e856f83da5cb3d65efe048b86ddbd9c828e316bc554ec7cb9d2608f1d71aa1ebc18d08525675627b53ec2966e8eae4e79a659b196bce20721b5652b7ce1c0e2130d5ddb1a52718652721fc67ebfbfa6b54dfaca64bb7d5c02fcdbdcd5cc912f4b751db2573545b5d3b67f6338d2513b6b1f12054591d6b3fb4f22c6aadfe50a4077ed9c92ac99dbca9c4380a86cb534ce6a315a30f6b65b6b8dd0aa2df6b416b868323c05929c24324024933160ca620931099de010a308196811106d958cbae02554af2a2b2883e56753a975e2ac2ae351923a80871d445495f9e86f40d308eb1000bf173264ca9ed774caf0d7c6ab48512b7cffe8c20982d196cd04c285c06a6dccb1b7d26c79bbb4603f297c3fc128776751cfa97de6af4ef90266e81c667ede0a8a8feb9ba1645e517c7f98695a85ef773d618d35c07ec597c64c1c461510db7da8172ed2769ce92dc2658aa00f6e02a28f30203010001a321301f301d0603551d0e041604145e653e4723c8cbbb83b615a95728266944a0a396300d06092a864886f70d01010b050003820201006589602a0ffa7131f21d57a5354b3e22f988852508332cd05dec3673c9ce955834829aec6ae1f58304e8cef7d5707e8340c5d370f640b1b6b5c2c46d3aace0f1568d1951148b76205cd72c66870449c3e75d2ce748463d459b568f3cf8d4c1719c11a3e0d039105f2c6750821954be5c40887af6ca800a39b77a4db1f2038c9f98cc1c5a5382ff136e9a708c17333d43400fc0f14f2ebc56442565aa95e84e224dcb665faedc92be8c9449c594668cef78018bae5c86f57ed00926afeada8e0a42bbc02ee4dc069b92f43b4679288a755401e1d733d1146abcc0574d39cd4c057f1edb23d17411d6495712b604f9e88073ffab6bb05c73e685b5d47eab1bcbb433719cc36b3bb024c993a3da85334774bac4ba18963f237dcf5d2ecd663700b76352b5347206bcf1635135c9694cf9ce8d75b37642792b08be56e3b13dde797a6642368a2203e0dd013f533edd73816c8bc61aea1f121c3bb210571cac50005b4b234d6d30d5e48b6292b0e5774d23da036639ef1bc4070fb33461f07892bcf3fbb5d8ba5b8e23a945774da3d618d1b6ba59e262bf1f5b71dbce11da59cf05e979a1bf4d62805c9c2a88cb7155075d14c9264b29ec122b76cff78e88ab9035b5a4540c0f3cc2339fc2ec5928f5ffa3a17f6bc6fdcc92ac89b55d29525206c74a9281ab91da24f3db5c46882476caa8dc6eed9a22624c25907990b48953b730bf
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<!-- name -->
|
<!-- name -->
|
||||||
<item>F-Droid Archive</item>
|
<item>Belmarket Binary repo</item>
|
||||||
<!-- address -->
|
<!-- address -->
|
||||||
<item>https://f-droid.org/archive</item>
|
<item>https://bin.belmarket.ir/</item>
|
||||||
<!-- description -->
|
<!-- description -->
|
||||||
<item>The archive repository of the F-Droid client. This contains older versions of
|
<item>
|
||||||
applications from the main repository.
|
مخزن دودویی بِل مارکت، ساخته شده توسط فایلهای نصبی سیستمعامل
|
||||||
</item>
|
</item>
|
||||||
<!-- version -->
|
<!-- version -->
|
||||||
<item>13</item>
|
<item>12</item>
|
||||||
<!-- enabled -->
|
<!-- enabled -->
|
||||||
<item>0</item>
|
<item>1</item>
|
||||||
<!-- priority -->
|
<!-- priority -->
|
||||||
<item>2</item>
|
<item>2</item>
|
||||||
<!-- push requests -->
|
<!-- push requests -->
|
||||||
<item>ignore</item>
|
<item>ignore</item>
|
||||||
<!-- pubkey -->
|
<!-- pubkey -->
|
||||||
<item>
|
<item>
|
||||||
3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
|
308204f1308202d9a0030201020204297435a4300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d536572766572301e170d3136313031373131303834355a170d3434303330343131303834355a30293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d53657276657230820222300d06092a864886f70d01010105000382020f003082020a02820201009441ca19cbb4a9d6021bc5c91bee5da803807d12aaeb7e8ce3b6ae701e681702c1a96b9e9c4c7c8988d681f30fab152dea265d00bc9dd45b187c3eee1755091d8ed37a4a19ee4687d6dc3c675f2132c8e116a3b9e2c7b60f38ded458496702c19963bd0673dc710c63f952f17fd43bee9ff9cb4e1f84585e8abdd379ce2564044a049abd31108bb7b06ce3e136a222be42aa85ace041c12c92b3737d4767d7e313a559127165e3b49fed77fcdbe29485e82ff9cd71b3b36735bf747969406574e8f93343d0e388230b8aeba945e524b5158b3582ed4d336f2d42f1b079c34a40b9c35e125b7fc95585b74072add11c41663b966bb1eeac025cd31e911f8eb955c7c6bd2e5eee701bc8b91843037372dcd3c2509c9431d28b9d76924556d2cda7383a37b3d54d7db2db634a8e5fde89fdcba1ddcc57ba25050ed7a5591fb1031e02457147203c546765555684d2b1f1a865b1fa874e8e2aeaa16c83db389cf85a2fe1bebfde0f50fd309d69f52514b6ef63c26918a14f9d993eb01ea133228103bdab3a66b31e55754f8cdb753ed620074fcfdf1e9fcac279faad671425e3afdd2c8a27a15d65c1f7bc2fc708e9643ae06898b165c1a1881c649ac2894e79653c5a33a6d8276847010caa747fe1995b35c791b3540b4ce713f99a515aa30c813066d4ae60ce9e473855429f3f0973ef26b4047ee7df70696ed9cbd0bd4f196f7f0203010001a321301f301d0603551d0e0416041445a91de5179148e2c0e3a8e89b7a48de1d872e3d300d06092a864886f70d01010b050003820201001f76cdd43617b464b7dab3e3eb942ad2d01c0ade31853c26893d553d8c3028f385e8e68c9da508389c15892ff5d492b203c1637660d59ac55149fa43c19bcef24ea8111661c63b444f414ccba6ad6d114b2ef489087d715aa91ce57d512d89be392af4d0ba7006ee5c8497bf676d3d3e85cc097c24bcbeabcf32fdd225d2df10f3f53432a60aa51b974fb978eefaaee9ea8f0d5b08ef539531c07f7284df1e6f6fef2b60fb415c4292ceb2ac0ed71fa36a4c075f8b86021660e8194c0ecd00ca65195312c9d76da6232d137c350a912977e4ba5fef8e5212108ea76084b1b02b1e686d9839e3c35745c24bbe19ce1b886bf8b70aa82556cf977ac16d7bc3b2eec5ca2d63f07600a534274a45a156639228b43d46b3547983ea76c6e912c9be85dea68b4cb5227814c50dcc38e289d08ad5d96c997591a949ac9f21883a57ad2f22bcf9ac39b88de831f301827a12db0f051a6cafa97b88aca0abcf86002296a593f27eb5b30f25afacfa4ccf7e53095f70929fadbe84330bb15e491974b8163e34b14c2c0e1ff9afa27466a0f17e74d53108e7fe7c3a2a6122f470df69e68da1fcd6bfd9ef7bd7e5b4511c22786c49ab7c638ec246a41a54f646815bfc29a0a9e6e32f90960ffa0c52b1fee2c8ff0429bb18d7f148a49c34f9d118e9761c9b1a09d3b5debb5b2e2e1cb3c772c2687035fc9c4aef796b0eb45c5d4fa62c787f12
|
||||||
</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
|
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
</string-array>
|
</string-array>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user