diff --git a/app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# b/app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# deleted file mode 100644 index 4f34ea06a..000000000 --- a/app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# +++ /dev/null @@ -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. - * - * WARNING: 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 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 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: - *
  • in the downloaded jar
  • - *
  • in the index XML
  • - *
  • stored in the local database
  • - * 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!"); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# b/app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# deleted file mode 100644 index 9aed07bcd..000000000 --- a/app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# +++ /dev/null @@ -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 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 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 = "

    " + str + "

    "; - 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; - } -} diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2d6cb5fac..8045bbac2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,7 +6,7 @@ #ffdd2c00 #ffdd2c00 - #ff0d47a1 + #9e1010 #ff042570 #ff8ab000 #ff222222 diff --git a/app/src/main/res/values/default_repos.xml b/app/src/main/res/values/default_repos.xml index 3a895f579..6547c7ef0 100644 --- a/app/src/main/res/values/default_repos.xml +++ b/app/src/main/res/values/default_repos.xml @@ -4,13 +4,12 @@ - F-Droid + BelMarket - https://f-droid.org/repo + https://shop.belmarket.ir/ - 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. + + مخزن رسمی بِل‌مارکت ساخته شده از آخرین نسخهٔ سورس‌کد نرم‌افزار‌ها 13 @@ -22,71 +21,28 @@ ignore - 3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef + 308204f1308202d9a003020102020430b0c5af300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d536572766572301e170d3136303832323035313132355a170d3434303130383035313132355a30293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d53657276657230820222300d06092a864886f70d01010105000382020f003082020a028202010099c822a51e759fbcc83561cd415b99b679de2521c679b5929ba9ed5fd85ebf8c7b22567b1c9b44a8c863ad066f1df2b78ef99bb0e3fbc6f904a5cc146d3fd1a06fe696c04aab97dac037956743b9e26f32a4fa4280e23cadd1042f17caad7837af946307c369cf3bbef4cf9ae5ff4fff06979fc7229464d030ea7f6f0bd720c8b84c8f398cf3cb750a788a6b0a0a6954f2b47a325ee30be3062645f41dedc67ce95614626d625b0072fd8560e856f83da5cb3d65efe048b86ddbd9c828e316bc554ec7cb9d2608f1d71aa1ebc18d08525675627b53ec2966e8eae4e79a659b196bce20721b5652b7ce1c0e2130d5ddb1a52718652721fc67ebfbfa6b54dfaca64bb7d5c02fcdbdcd5cc912f4b751db2573545b5d3b67f6338d2513b6b1f12054591d6b3fb4f22c6aadfe50a4077ed9c92ac99dbca9c4380a86cb534ce6a315a30f6b65b6b8dd0aa2df6b416b868323c05929c24324024933160ca620931099de010a308196811106d958cbae02554af2a2b2883e56753a975e2ac2ae351923a80871d445495f9e86f40d308eb1000bf173264ca9ed774caf0d7c6ab48512b7cffe8c20982d196cd04c285c06a6dccb1b7d26c79bbb4603f297c3fc128776751cfa97de6af4ef90266e81c667ede0a8a8feb9ba1645e517c7f98695a85ef773d618d35c07ec597c64c1c461510db7da8172ed2769ce92dc2658aa00f6e02a28f30203010001a321301f301d0603551d0e041604145e653e4723c8cbbb83b615a95728266944a0a396300d06092a864886f70d01010b050003820201006589602a0ffa7131f21d57a5354b3e22f988852508332cd05dec3673c9ce955834829aec6ae1f58304e8cef7d5707e8340c5d370f640b1b6b5c2c46d3aace0f1568d1951148b76205cd72c66870449c3e75d2ce748463d459b568f3cf8d4c1719c11a3e0d039105f2c6750821954be5c40887af6ca800a39b77a4db1f2038c9f98cc1c5a5382ff136e9a708c17333d43400fc0f14f2ebc56442565aa95e84e224dcb665faedc92be8c9449c594668cef78018bae5c86f57ed00926afeada8e0a42bbc02ee4dc069b92f43b4679288a755401e1d733d1146abcc0574d39cd4c057f1edb23d17411d6495712b604f9e88073ffab6bb05c73e685b5d47eab1bcbb433719cc36b3bb024c993a3da85334774bac4ba18963f237dcf5d2ecd663700b76352b5347206bcf1635135c9694cf9ce8d75b37642792b08be56e3b13dde797a6642368a2203e0dd013f533edd73816c8bc61aea1f121c3bb210571cac50005b4b234d6d30d5e48b6292b0e5774d23da036639ef1bc4070fb33461f07892bcf3fbb5d8ba5b8e23a945774da3d618d1b6ba59e262bf1f5b71dbce11da59cf05e979a1bf4d62805c9c2a88cb7155075d14c9264b29ec122b76cff78e88ab9035b5a4540c0f3cc2339fc2ec5928f5ffa3a17f6bc6fdcc92ac89b55d29525206c74a9281ab91da24f3db5c46882476caa8dc6eed9a22624c25907990b48953b730bf - F-Droid Archive + ‌Belmarket Binary repo - https://f-droid.org/archive + https://bin.belmarket.ir/ - The archive repository of the F-Droid client. This contains older versions of - applications from the main repository. + + مخزن دودویی بِل مارکت، ساخته شده توسط فایل‌های نصبی سیستم‌عامل - 13 + 12 - 0 + 1 2 ignore - 3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef - - - - Guardian Project - - https://guardianproject.info/fdroid/repo - - 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. - - - 13 - - 0 - - 3 - - ignore - - - 308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f - - - - Guardian Project Archive - - https://guardianproject.info/fdroid/archive - - The official repository of The Guardian Project apps for use with F-Droid client. This - contains older versions of applications from the main repository. - - - 13 - - 0 - - 4 - - ignore - - - 308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f + 308204f1308202d9a0030201020204297435a4300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d536572766572301e170d3136313031373131303834355a170d3434303330343131303834355a30293110300e060355040b1307462d44726f6964311530130603550403130c4275696c642d53657276657230820222300d06092a864886f70d01010105000382020f003082020a02820201009441ca19cbb4a9d6021bc5c91bee5da803807d12aaeb7e8ce3b6ae701e681702c1a96b9e9c4c7c8988d681f30fab152dea265d00bc9dd45b187c3eee1755091d8ed37a4a19ee4687d6dc3c675f2132c8e116a3b9e2c7b60f38ded458496702c19963bd0673dc710c63f952f17fd43bee9ff9cb4e1f84585e8abdd379ce2564044a049abd31108bb7b06ce3e136a222be42aa85ace041c12c92b3737d4767d7e313a559127165e3b49fed77fcdbe29485e82ff9cd71b3b36735bf747969406574e8f93343d0e388230b8aeba945e524b5158b3582ed4d336f2d42f1b079c34a40b9c35e125b7fc95585b74072add11c41663b966bb1eeac025cd31e911f8eb955c7c6bd2e5eee701bc8b91843037372dcd3c2509c9431d28b9d76924556d2cda7383a37b3d54d7db2db634a8e5fde89fdcba1ddcc57ba25050ed7a5591fb1031e02457147203c546765555684d2b1f1a865b1fa874e8e2aeaa16c83db389cf85a2fe1bebfde0f50fd309d69f52514b6ef63c26918a14f9d993eb01ea133228103bdab3a66b31e55754f8cdb753ed620074fcfdf1e9fcac279faad671425e3afdd2c8a27a15d65c1f7bc2fc708e9643ae06898b165c1a1881c649ac2894e79653c5a33a6d8276847010caa747fe1995b35c791b3540b4ce713f99a515aa30c813066d4ae60ce9e473855429f3f0973ef26b4047ee7df70696ed9cbd0bd4f196f7f0203010001a321301f301d0603551d0e0416041445a91de5179148e2c0e3a8e89b7a48de1d872e3d300d06092a864886f70d01010b050003820201001f76cdd43617b464b7dab3e3eb942ad2d01c0ade31853c26893d553d8c3028f385e8e68c9da508389c15892ff5d492b203c1637660d59ac55149fa43c19bcef24ea8111661c63b444f414ccba6ad6d114b2ef489087d715aa91ce57d512d89be392af4d0ba7006ee5c8497bf676d3d3e85cc097c24bcbeabcf32fdd225d2df10f3f53432a60aa51b974fb978eefaaee9ea8f0d5b08ef539531c07f7284df1e6f6fef2b60fb415c4292ceb2ac0ed71fa36a4c075f8b86021660e8194c0ecd00ca65195312c9d76da6232d137c350a912977e4ba5fef8e5212108ea76084b1b02b1e686d9839e3c35745c24bbe19ce1b886bf8b70aa82556cf977ac16d7bc3b2eec5ca2d63f07600a534274a45a156639228b43d46b3547983ea76c6e912c9be85dea68b4cb5227814c50dcc38e289d08ad5d96c997591a949ac9f21883a57ad2f22bcf9ac39b88de831f301827a12db0f051a6cafa97b88aca0abcf86002296a593f27eb5b30f25afacfa4ccf7e53095f70929fadbe84330bb15e491974b8163e34b14c2c0e1ff9afa27466a0f17e74d53108e7fe7c3a2a6122f470df69e68da1fcd6bfd9ef7bd7e5b4511c22786c49ab7c638ec246a41a54f646815bfc29a0a9e6e32f90960ffa0c52b1fee2c8ff0429bb18d7f148a49c34f9d118e9761c9b1a09d3b5debb5b2e2e1cb3c772c2687035fc9c4aef796b0eb45c5d4fa62c787f12