diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index a5c6506391aa82cd5572dfb2f87f3a96a5b48698..d7718890eae9fe5ef8c1603e7d1caaa755feca04 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -25,6 +25,7 @@ import net.i2p.app.ClientAppManager; import net.i2p.app.Outproxy; import net.i2p.client.I2PSession; import net.i2p.client.LookupResult; +import net.i2p.client.naming.NamingService; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketOptions; @@ -664,7 +665,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn host = null; break; } - } else if(hostLowerCase.endsWith(".i2p")) { + } else if(NamingService.isI2PHost(hostLowerCase)) { // Destination gets the hostname destination = host; // Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure @@ -1279,7 +1280,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } } else if("i2p".equals(host)) { clientDest = null; - } else if (destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) { + } else if (NamingService.isB32Host(destination)) { int len = destination.length(); if (len < 60 || (len >= 61 && len <= 63)) { // 8-59 or 61-63 chars, this won't work @@ -1289,7 +1290,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn } catch (IOException ioe) {} return; } - if (len >= 64) { + if (NamingService.isBlindedHost(destination)) { // catch b33 errors before session lookup try { BlindData bd = Blinding.decode(_context, destination); @@ -1363,7 +1364,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN); } else if(ahelperPresent) { header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN); - } else if(destination.length() >= 60 && destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) { + } else if (NamingService.isB32Host(destination)) { header = getErrorPage("nols", ERR_DESTINATION_UNKNOWN); extraMessage = _t("Destination lease set not found"); } else { @@ -1728,7 +1729,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn if(host == null) { return null; } - if (host.toLowerCase(Locale.US).endsWith(".b32.i2p")) { + if (NamingService.isB32Host(host)) { return host; } Destination dest = _context.namingService().lookup(host); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java index a9f3b2179f4654dca290e6a3e446b29da1c9a963..96e02bf16398be00ee520644c7cb505dfcd87bf7 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java @@ -16,6 +16,7 @@ import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; +import net.i2p.client.naming.NamingService; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.Destination; @@ -134,7 +135,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase { int colon = proxy.indexOf(':'); if (colon > 0) host = host.substring(0, colon); - if (host.endsWith(".i2p")) { + if (NamingService.isI2PHost(host)) { proxyList.add(proxy); } else { String m = "Non-i2p SOCKS outproxy: " + proxy; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java index f3496d77832d1060ca110e3c75754248c0add2b2..ef7d0b6cc4655e20a1ce79dc7e4d5373bdf68227 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java @@ -21,6 +21,7 @@ import net.i2p.I2PException; import net.i2p.app.ClientApp; import net.i2p.app.ClientAppManager; import net.i2p.app.Outproxy; +import net.i2p.client.naming.NamingService; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.DataFormatException; @@ -211,7 +212,7 @@ class SOCKS4aServer extends SOCKSServer { try { String hostLowerCase = connHostName.toLowerCase(Locale.US); - if (hostLowerCase.endsWith(".i2p")) { + if (NamingService.isI2PHost(hostLowerCase)) { Destination dest = _context.namingService().lookup(connHostName); if (dest == null) { try { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java index 7a35e77db5da4ce1d720f5a76fabf3db461fddac..71e6841a7ec3e2a651e69c35630e4afc28792111 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java @@ -26,6 +26,7 @@ import net.i2p.I2PException; import net.i2p.app.ClientApp; import net.i2p.app.ClientAppManager; import net.i2p.app.Outproxy; +import net.i2p.client.naming.NamingService; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.DataFormatException; @@ -459,7 +460,7 @@ class SOCKS5Server extends SOCKSServer { try { String hostLowerCase = connHostName.toLowerCase(Locale.US); - if (hostLowerCase.endsWith(".i2p")) { + if (NamingService.isI2PHost(hostLowerCase)) { // Let's not do a new Dest for every request, huh? //I2PSocketManager sm = I2PSocketManagerFactory.createManager(); //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null); diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java index c584dcb28cb4bcfb41642a69dacd86c61ca2f89c..0479aba18270a0ac1eb27fbedbd854c91df60815 100644 --- a/core/java/src/net/i2p/client/naming/NamingService.java +++ b/core/java/src/net/i2p/client/naming/NamingService.java @@ -13,6 +13,7 @@ import java.lang.reflect.Constructor; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -739,6 +740,61 @@ public abstract class NamingService { //// End new API for multiple Destinations + /** + * Is this an i2p hostname? + * + * ref: https://www.rfc-editor.org/rfc/rfc9476.html + * + * @param hostname non-null + * @return true if hostname (case insensitive) ends with .i2p or .i2p.alt + * @since 0.9.66 + */ + public static boolean isI2PHost(String hostname) { + String lc = hostname.toLowerCase(Locale.US); + return lc.endsWith(".i2p") || lc.endsWith(".i2p.alt"); + } + + /** + * Is this a b32 hostname? + * Does NOT validate the base32 part except for length. + * + * ref: https://www.rfc-editor.org/rfc/rfc9476.html + * + * @param hostname non-null + * @return true if hostname (case insensitive) ends with .b32.i2p or .b32.i2p.alt + * and is long enough. May or may not be blinded. + * @since 0.9.66 + */ + public static boolean isB32Host(String hostname) { + int len = hostname.length(); + if (len < 60) + return false; + String lc = hostname.toLowerCase(Locale.US); + return lc.endsWith(".b32.i2p") || + (len >= 64 && lc.endsWith(".b32.i2p.alt")); + } + + /** + * Is this a blinded ("b33") hostname? + * Does NOT validate the base32 part except for length. + * See Blinding.decode() for full validation. + * + * ref: https://www.rfc-editor.org/rfc/rfc9476.html + * + * @param hostname non-null + * @return true if hostname (case insensitive) ends with .b32.i2p or .b32.i2p.alt + * and is long enough. + * @since 0.9.66 + */ + public static boolean isBlindedHost(String hostname) { + int len = hostname.length(); + if (len < 64) + return false; + String lc = hostname.toLowerCase(Locale.US); + return lc.endsWith(".b32.i2p") || + (len >= 68 && lc.endsWith(".b32.i2p.alt")); + } + /** * WARNING - for use by I2PAppContext only - others must use * I2PAppContext.namingService()