fix colors

This commit is contained in:
numb95 2016-10-25 16:06:56 +03:30
parent 8b2c0fb737
commit e5ae455cdf
4 changed files with 13 additions and 761 deletions

View File

@ -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!");
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>