From 6112cc5a38c1bb5d8be9d4c90c8879261e60fac6 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Thu, 6 Nov 2014 17:45:06 +0000
Subject: [PATCH] i2psnark:   - Add new opentrackers, remove welterde   -
 Support multiple default opentrackers   - Don't link to opentrackers at the
 top

---
 .../src/org/klomp/snark/I2PSnarkUtil.java     | 28 +++++++++++----
 .../src/org/klomp/snark/SnarkManager.java     | 35 +++++++++++++++----
 .../src/org/klomp/snark/TrackerClient.java    |  6 ++--
 .../org/klomp/snark/web/I2PSnarkServlet.java  |  7 ++--
 installer/resources/hosts.txt                 |  2 ++
 5 files changed, 61 insertions(+), 17 deletions(-)

diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index d1038534f9..a7467a1e3f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -3,12 +3,15 @@ package org.klomp.snark;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+
 import net.i2p.I2PAppContext;
 import net.i2p.I2PException;
 import net.i2p.client.I2PSession;
@@ -71,7 +74,6 @@ public class I2PSnarkUtil {
     private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
     public static final int DEFAULT_STARTUP_DELAY = 3;
     public static final boolean DEFAULT_USE_OPENTRACKERS = true;
-    public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
     public static final int DEFAULT_MAX_UP_BW = 8;  //KBps
     public static final int MAX_CONNECTIONS = 16; // per torrent
     public static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
@@ -99,8 +101,7 @@ public class I2PSnarkUtil {
         _maxConnections = MAX_CONNECTIONS;
         _startupDelay = DEFAULT_STARTUP_DELAY;
         _shouldUseOT = DEFAULT_USE_OPENTRACKERS;
-        // FIXME split if default has more than one
-        _openTrackers = Collections.singletonList(DEFAULT_OPENTRACKERS);
+        _openTrackers = Collections.emptyList();
         _shouldUseDHT = DEFAULT_USE_DHT;
         // This is used for both announce replies and .torrent file downloads,
         // so it must be available even if not connected to I2CP.
@@ -565,12 +566,12 @@ public class I2PSnarkUtil {
         return rv;
     }
     
-    /** @param ot non-null */
+    /** @param ot non-null list of announce URLs */
     public void setOpenTrackers(List<String> ot) { 
         _openTrackers = ot;
     }
 
-    /** List of open trackers to use as backups
+    /** List of open tracker announce URLs to use as backups
      *  @return non-null, possibly unmodifiable, empty if disabled
      */
     public List<String> getOpenTrackers() { 
@@ -580,7 +581,22 @@ public class I2PSnarkUtil {
     }
 
     /**
-     *  List of open trackers to use as backups even if disabled
+     *  Is this announce URL probably for an open tracker?
+     *
+     *  @since 0.9.17
+     */
+    public boolean isKnownOpenTracker(String url) { 
+        try {
+           URL u = new URL(url);
+           String host = u.getHost();
+           return host != null && SnarkManager.KNOWN_OPENTRACKERS.contains(host);
+        } catch (MalformedURLException mue) {
+           return false;
+        }
+    }
+
+    /**
+     *  List of open tracker announce URLs to use as backups even if disabled
      *  @return non-null
      *  @since 0.9.4
      */
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index 947cc996e5..8344469bc8 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -27,6 +27,7 @@ import java.util.concurrent.LinkedBlockingQueue;
 
 import net.i2p.I2PAppContext;
 import net.i2p.app.ClientAppManager;
+import net.i2p.crypto.SigType;
 import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
 import net.i2p.update.*;
@@ -144,17 +145,39 @@ public class SnarkManager implements CompleteListener {
 //       , "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/"
 //       , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
        "Postman", "http://tracker2.postman.i2p/announce.php=http://tracker2.postman.i2p/"
-       ,"Welterde", "http://tracker.welterde.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
+//       ,"Welterde", "http://tracker.welterde.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
        ,"Diftracker", "http://diftracker.i2p/announce.php=http://diftracker.i2p/"
 //       , "CRSTRACK", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/"
 //       ,"Exotrack", "http://blbgywsjubw3d2zih2giokakhe3o2cko7jtte4risb3hohbcoyva.b32.i2p/announce.php=http://exotrack.i2p/"
+       ,"DgTrack", "http://w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p/a=http://opentracker.dg2.i2p/"
+       // The following is ECDSA_SHA256_P256
+       ,"TheBland", "http://s5ikrdyjwbcgxmqetxb3nyheizftms7euacuub2hic7defkh3xhq.b32.i2p/a=http://tracker.thebland.i2p/stats?mode=peer"
     };
     
+    /** URL. This is our equivalent to router.utorrent.com for bootstrap */
+    public static final String DEFAULT_BACKUP_TRACKER = "http://opentracker.dg2.i2p/a";
+
+    /** URLs, comma-separated. Used for "announce to open trackers also" */
+    private static final String DEFAULT_OPENTRACKERS = DEFAULT_BACKUP_TRACKER +
+                                                       (SigType.ECDSA_SHA256_P256.isAvailable() ? ",http://tracker.thebland.i2p/a" : "");
+
     public static final Set<String> DEFAULT_TRACKER_ANNOUNCES;
 
+    /** host names for config form */
+    public static final Set<String> KNOWN_OPENTRACKERS = new HashSet(Arrays.asList(new String[] {
+        "tracker.welterde.i2p", "cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p",
+        "opentracker.dg2.i2p", "w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p",
+        "tracker.thebland.i2p", "s5ikrdyjwbcgxmqetxb3nyheizftms7euacuub2hic7defkh3xhq.b32.i2p",
+        "psi.i2p", "avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p",
+        "opentracker.psi.i2p", "vmow3h54yljn7zvzbqepdddt5fmygijujycod2q6yznpy2rrzuwa.b32.i2p",
+        "tracker.killyourtv.i2p", "5mpvzxfbd4rtped3c7ln4ddw52e7i7t56s36ztky4ustxtxrjdpa.b32.i2p"
+    }));
+
     static {
         Set<String> ann = new HashSet();
         for (int i = 1; i < DEFAULT_TRACKERS.length; i += 2) {
+            if (DEFAULT_TRACKERS[i-1].equals("TheBland") && !SigType.ECDSA_SHA256_P256.isAvailable())
+                continue;
             String urls[] = DEFAULT_TRACKERS[i].split("=", 2);
             ann.add(urls[0]);
         }
@@ -657,9 +680,7 @@ public class SnarkManager implements CompleteListener {
         _util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
         _util.setStartupDelay(getInt(PROP_STARTUP_DELAY, DEFAULT_STARTUP_DELAY));
         _util.setFilesPublic(areFilesPublic());
-        String ot = _config.getProperty(PROP_OPENTRACKERS);
-        if (ot != null)
-            _util.setOpenTrackers(getOpenTrackers());
+        _util.setOpenTrackers(getListConfig(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS));
         String useOT = _config.getProperty(PROP_USE_OPENTRACKERS);
         boolean bOT = useOT == null || Boolean.parseBoolean(useOT);
         _util.setUseOpenTrackers(bOT);
@@ -959,7 +980,7 @@ public class SnarkManager implements CompleteListener {
     private List<String> getOpenTrackers() {
         if (!_util.shouldUseOpenTrackers())
             return Collections.emptyList();
-        return getListConfig(PROP_OPENTRACKERS, I2PSnarkUtil.DEFAULT_OPENTRACKERS);
+        return getListConfig(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS);
     }
     
     /**
@@ -977,7 +998,7 @@ public class SnarkManager implements CompleteListener {
     public void saveOpenTrackers(List<String> ot) {
         setListConfig(PROP_OPENTRACKERS, ot);
         if (ot == null)
-            ot = Collections.singletonList(I2PSnarkUtil.DEFAULT_OPENTRACKERS);
+            ot = getListConfig(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS);
         _util.setOpenTrackers(ot);
         addMessage(_("Open Tracker list changed - torrent restart required to take effect."));
         saveConfig();
@@ -2071,6 +2092,8 @@ public class SnarkManager implements CompleteListener {
         _trackerMap.clear();
         for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2) {
             String name = DEFAULT_TRACKERS[i];
+            if (name.equals("TheBland") && !SigType.ECDSA_SHA256_P256.isAvailable())
+                continue;
             String urls[] = DEFAULT_TRACKERS[i+1].split("=", 2);
             String url2 = urls.length > 1 ? urls[1] : null;
             _trackerMap.put(name, new Tracker(name, urls[0], url2));
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index c2320de77e..4b141e02da 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -74,8 +74,6 @@ public class TrackerClient implements Runnable {
   private static final String NOT_REGISTERED_2  = "torrent not found";    // diftracker
   private static final String NOT_REGISTERED_3  = "torrent unauthorised"; // vuze
   private static final String ERROR_GOT_HTML  = "received html";             // fake return
-  /** this is our equivalent to router.utorrent.com for bootstrap */
-  private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a";
 
   private final static int SLEEP = 5; // 5 minutes.
   private final static int DELAY_MIN = 2000; // 2 secs.
@@ -344,7 +342,9 @@ public class TrackerClient implements Runnable {
                 _log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
         }
         if (backupTrackers.isEmpty()) {
-            backupTrackers.add(new TCTracker(DEFAULT_BACKUP_TRACKER, false));
+            backupTrackers.add(new TCTracker(SnarkManager.DEFAULT_BACKUP_TRACKER, false));
+        } else if (trackers.size() > 1) {
+            Collections.shuffle(backupTrackers, _util.getContext().random());
         }
     }
     this.completed = coordinator.getLeft() == 0;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index a1daeb4e47..338b8e5b6f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -329,6 +329,8 @@ public class I2PSnarkServlet extends BasicServlet {
             for (Tracker t : sortedTrackers) {
                 if (t.baseURL == null || !t.baseURL.startsWith("http"))
                     continue;
+                if (_manager.util().isKnownOpenTracker(t.announceURL))
+                    continue;
                 out.write(" <a href=\"" + t.baseURL + "\" class=\"snarkRefresh\" target=\"_blank\">" + t.name + "</a>");
             }
         }
@@ -2305,6 +2307,7 @@ public class I2PSnarkServlet extends BasicServlet {
             String announceURL = t.announceURL.replace("&#61;", "=");
             boolean isOpen = openTrackers.contains(t.announceURL);
             boolean isPrivate = privateTrackers.contains(t.announceURL);
+            boolean isKnownOpen = _manager.util().isKnownOpenTracker(t.announceURL);
             buf.append("<tr><td><input type=\"checkbox\" class=\"optbox\" name=\"delete_")
                .append(name).append("\" title=\"").append(_("Delete")).append("\">" +
                        "</td><td>").append(name)
@@ -2313,7 +2316,7 @@ public class I2PSnarkServlet extends BasicServlet {
                .append(announceURL).append("\"");
             if (!(isOpen || isPrivate))
                 buf.append(" checked=\"checked\"");
-            else if (t.announceURL.equals("http://tracker.welterde.i2p/a"))
+            else if (isKnownOpen)
                 buf.append(" disabled=\"disabled\"");
             buf.append(">" +
                        "</td><td><input type=\"radio\" class=\"optbox\" value=\"1\" name=\"ttype_")
@@ -2329,7 +2332,7 @@ public class I2PSnarkServlet extends BasicServlet {
             if (isPrivate) {
                 buf.append(" checked=\"checked\"");
             } else {
-                if (SnarkManager.DEFAULT_TRACKER_ANNOUNCES.contains(t.announceURL))
+                if (isKnownOpen)
                     buf.append(" disabled=\"disabled\"");
             }
             buf.append(">" +
diff --git a/installer/resources/hosts.txt b/installer/resources/hosts.txt
index 662b4995c6..46017c9c4d 100644
--- a/installer/resources/hosts.txt
+++ b/installer/resources/hosts.txt
@@ -358,3 +358,5 @@ jisko.i2p=YSXAtZN7atC3kAoMLFr6vRcEYf3oUpBonrePtfzt6ydtXIqt501dUqwDXVzKP2Hkmg2Zg9
 hiddengate.i2p=2BJrMAUHLQBc0CoRnkVpFfkx9dp74qpe3TVlLiXe-RuQGwBNztoYN7WjfmRHF5~KZ-KUEKhA3BbAFuWksGMOi7mV0hUD8Egr5ZG03-ekysSiKmVj3mfMv-GfPTdq5W-OdXK87q1Z~KV-e4Qwq6OFV8VREF81jDeMo4agdV0~388WNaQUjS-Jyh7wInh1XOaENFzsLSBPfbgZMIUEoCe5HiTaXLDTxRnDdLPgkvNfOEV9vT3rCgX2lhd8xVL48q4OObjlZ6DLdQ~O4kSIZoPX~9FWDKPB9nCVCa2zIkbDhBqN7p-r~HSPBUM2nUBB2gwuyYtpiLz3ukqEUGV20VAVZ35RqL-pWl4wAMqy2iRwFcgYnM8tvPGFmJM0lLyPlZdyRSU86QkvvVMUrZYXcylg4uP0WXPeNIP4nXOj5ZR7s9gP3AL4xW4Dh2YqPoCaWWt8J2LZugX7QSHpFVEdWZtxnppD1zDmIsbpm-UAUqdgviTIcFPyhxHaEGMUZ7Gid2FqAAAA
 i2pdocs.str4d.i2p=CRayYfxrPBELUQ4tYi723g8atsyHwkR0--N8fXggNqJgHsnrKxle0ovH5qj6aqUGToay4ktJEz~opIV2lF605gdGE9rSrPONOU0v737~gzjmLpJoyW03yOhr6lGvTxTKWjgx5xRHctt11DEMlMufY4Q72~EZETE4k5E45V3YqSWGyYIzxdpzzw9zvMe9TaLZTs0ZYFgXRMIu1o5IzTkfK2FfAMNfHMcRESHHK8GcF0TkzZFnvOhtgr~Fywq5B7VMYzwq6a8ws5bPjSjQWbJqX8NR~NZl7gxSMwdlZlcICVFOo-uW9Yna2YfhzM-A5SOx60EsL26YX1VXh1zQ3Rsg2lNXbSPI4dLb9taGWxpZS914L-N3Htgih7VM-Zl1QYQGuNV-9LaEEUQKRPCeKdyG14Yry4YOVDItMFE5CHIDaoFcpD24jPE~0aHmVk~RXOIzVhmtVj~HcpBXWxT4lpIO2V3kzA-ZTvo5I0ollbP5Ep3s0Cj~EfikBhUL6zHsWVC0AAAA
 mtn.i2p-projekt.i2p=I6Ha4I7rgR0JyExQnN~4sZz5CfRqdN7o-2t1i2YOExd2CxC4MHHz3nJ5KgBIqeNa0dZbugwMEYMWneXAf8nuy9RKUrjcDvGLqScuU8QCpG-e7UDR8lNregfaOwTuypBQiPh9nrc86Zc2JXyTv0eG3T5Yl1tydqs3qEz5w1vn64uRCrGLEMsnGMIZ4aXexseL6McW9ObIQ0qOIfRG9kc~3~bpD9UM4G04BXPZ1Ao5MUjGoQyxlaNCt1DrvAYcrRArzvv0QGyPjb6HpHLlK2UIoR6SumRuNYpEXCaqbXEnEicx1kB5Pyhcwm0cyaMCx-ZaUU-F1~23VurE41xNMbq4GwXtev4bVP4ycHGHjH-XYJmKrqnesIEp7InSAWfm0gdDHuy9377nSIyAhGLLFlWKtzrZPiKm57SN4~BZSQIF6CI4JutGa0x9EaO8hvzFgaYKgwS~a7-YuZNfx4VHEF2J34DrsJo4qetdPTWTKX1o-ztMP6f7DpWjp4JTKb36YstvAAAA
+tracker.thebland.i2p=gzBtMSRcMD6b36PmPCQWZhh30fYm2Ww2r4tRSref4N2T4~cnXK3DjJOuJwao2jRK4bZwX2Rkyjw849xrFMqaR3SdPe3-K61B~Kr9Uo1KLdm3~oahOWFmCaIlipPs-i3jdTT~721YUcYB09n4PGrDq5KZSOOBlLZKulJficO58QRUlDpva4OCCRrX9EUCoAavOciKpvKtnGwl6AiPFu8WnmEeGQ861vjdirjfkHWNp3gj9IjGuxJNcgyHi51BWYZM6il~LJTcbA4zuZn~qudHIx9uzUtO-t08yzSRrmfVwVVVru6-~BBX0ipADi9UGZjyB-PJEKKjizUPxSp2OCmiOlQ2iXpKs2j8yfjHJbn-eWKpIh4jfpNigy6AbDfzFivkvm8lt8CleYf-p3~SHdqIL0iEaacxi5BAU4Baj5yS818kPQP4hEEMMtq4WnKjl4IW64swXSg1wlVBTiKDJzzQGK20jySBuPxhEbd6sfAeirzn585g5EqeV8DLqsMfe5pZBQAEAAEAAA==
+opentracker.dg2.i2p=WSUjGXhqHr7TsBizCO0qV~Kk7G1-suPPSSyMs4AnLQ8wRlWWZ~9rl7AzS0tFG4Dbbl8Te0wtZmQeMhcartbJ3TY-TBnviFqA8zP-sEQf5UK0BA5zzrtpB7NnUo-65B61cVbqG51-p9cJZ~Crre0LEZm5bJs8P3J~oH3b3BJ0YXtuv0ccBgj~OAf1g8ZrHr431XLq8WPRkzAVEIDhFdiSCYAz8XTArNN7OnPUUCjZcw92Oqf9wajg0eXqnThDFbrCx54h0UKM7sVDqRnoXbuGVLTPVvmIOnwuwZrn4X60GMLW38Dg-38qSfJL61FIbn5HfK-VTjCOuC16PtvJAPS1qUBWa-Y3j3aGK3BpHQnlOl-XNB~tJU0GBzRvEnOPFbtqXw38LyTCsvXcY31C~sNC~jedATUfPSZK-UBeN6RkR5BQiXBV-YkzUvTM4s~oXXgw9WFe9DdEWP-XR9~2G1Qe-GkcRAKUXmTAzVnRjlHEDR0lLMfxDwe3OfZuBzM9Gda9AAAA
-- 
GitLab