diff --git a/.travis.yml b/.travis.yml index 51a7de72..acba3f55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: java before_install: - - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import - - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust + - | + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import + echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust + fi install: - mvn install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V diff --git a/conf.properties b/conf.properties index cbd837e5..3b4734b2 100644 --- a/conf.properties +++ b/conf.properties @@ -12,4 +12,9 @@ auth.password= ## Spotify authentication blob (BLOB) auth.blob= ## Cache enabled -cache.enabled=false \ No newline at end of file +cache.enabled=false +# Zeroconf +## Listen on all interfaces (overrides `zeroconf.interfaces`) +zeroconf.listenAll=true +## Listen on this interfaces (comma separated list of names) +zeroconf.interfaces= \ No newline at end of file diff --git a/core/src/main/java/xyz/gianlu/librespot/AbsConfiguration.java b/core/src/main/java/xyz/gianlu/librespot/AbsConfiguration.java index 775e6a14..5de8a763 100644 --- a/core/src/main/java/xyz/gianlu/librespot/AbsConfiguration.java +++ b/core/src/main/java/xyz/gianlu/librespot/AbsConfiguration.java @@ -3,13 +3,14 @@ import org.jetbrains.annotations.Nullable; import xyz.gianlu.librespot.core.AuthConfiguration; import xyz.gianlu.librespot.core.Session; +import xyz.gianlu.librespot.core.ZeroconfAuthenticator; import xyz.gianlu.librespot.player.CacheManager; import xyz.gianlu.librespot.player.Player; /** * @author Gianlu */ -public abstract class AbsConfiguration implements Player.PlayerConfiguration, CacheManager.CacheConfiguration, AuthConfiguration { +public abstract class AbsConfiguration implements Player.PlayerConfiguration, CacheManager.CacheConfiguration, AuthConfiguration, ZeroconfAuthenticator.Configuration { @Nullable public abstract String deviceName(); diff --git a/core/src/main/java/xyz/gianlu/librespot/DefaultConfiguration.java b/core/src/main/java/xyz/gianlu/librespot/DefaultConfiguration.java index 827f5841..c4be9a88 100644 --- a/core/src/main/java/xyz/gianlu/librespot/DefaultConfiguration.java +++ b/core/src/main/java/xyz/gianlu/librespot/DefaultConfiguration.java @@ -83,4 +83,14 @@ public Session.DeviceType deviceType() { public Strategy strategy() { return Strategy.ZEROCONF; } + + @Override + public boolean zeroconfListenAll() { + return true; + } + + @Override + public @NotNull String[] zeroconfInterfaces() { + return new String[0]; + } } diff --git a/core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java b/core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java index e8b3ec6d..8fd63abf 100644 --- a/core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java +++ b/core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java @@ -73,6 +73,14 @@ private > E getEnum(@NotNull Class clazz, @NotNull String k } } + @NotNull + private String[] getStringArray(@NotNull String key, @NotNull String[] fallback) { + String str = properties.getProperty(key, null); + if (str == null) return fallback; + else if ((str = str.trim()).isEmpty()) return new String[0]; + else return Utils.split(str, ','); + } + @Override public boolean cacheEnabled() { return getBoolean("cache.enabled", defaults.cacheEnabled()); @@ -133,4 +141,15 @@ public float normalisationPregain() { public Strategy strategy() { return getEnum(Strategy.class, "auth.strategy", defaults.strategy()); } + + @Override + public boolean zeroconfListenAll() { + return getBoolean("zeroconf.listenAll", defaults.zeroconfListenAll()); + } + + @NotNull + @Override + public String[] zeroconfInterfaces() { + return getStringArray("zeroconf.interfaces", defaults.zeroconfInterfaces()); + } } diff --git a/core/src/main/java/xyz/gianlu/librespot/core/Session.java b/core/src/main/java/xyz/gianlu/librespot/core/Session.java index 04711fc9..0a9a8d85 100644 --- a/core/src/main/java/xyz/gianlu/librespot/core/Session.java +++ b/core/src/main/java/xyz/gianlu/librespot/core/Session.java @@ -405,9 +405,12 @@ public static class Builder { private final Inner inner; private Authentication.LoginCredentials loginCredentials = null; private AuthConfiguration authConf; + private ZeroconfAuthenticator.Configuration zeroconfConf; public Builder(@NotNull DeviceType deviceType, @NotNull String deviceName, @NotNull AbsConfiguration configuration) { this.inner = new Inner(deviceType, deviceName, configuration); + this.authConf = configuration; + this.zeroconfConf = configuration; } public Builder(@NotNull AbsConfiguration configuration) { @@ -421,6 +424,7 @@ public Builder(@NotNull AbsConfiguration configuration) { this.inner = new Inner(deviceType, deviceName, configuration); this.authConf = configuration; + this.zeroconfConf = configuration; } public Builder facebook() throws IOException { @@ -433,7 +437,7 @@ public Builder facebook() throws IOException { } public Builder zeroconf() throws IOException { - try (ZeroconfAuthenticator authenticator = new ZeroconfAuthenticator(inner)) { + try (ZeroconfAuthenticator authenticator = new ZeroconfAuthenticator(inner, zeroconfConf)) { loginCredentials = authenticator.lockUntilCredentials(); return this; } catch (InterruptedException ex) { diff --git a/core/src/main/java/xyz/gianlu/librespot/core/ZeroconfAuthenticator.java b/core/src/main/java/xyz/gianlu/librespot/core/ZeroconfAuthenticator.java index 8b5c9dcd..a025ee1c 100644 --- a/core/src/main/java/xyz/gianlu/librespot/core/ZeroconfAuthenticator.java +++ b/core/src/main/java/xyz/gianlu/librespot/core/ZeroconfAuthenticator.java @@ -20,10 +20,7 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URLDecoder; +import java.net.*; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.util.*; @@ -65,7 +62,7 @@ public class ZeroconfAuthenticator implements Closeable { private final Session.Inner session; private final DiffieHellman keys; - ZeroconfAuthenticator(Session.Inner session) throws IOException { + ZeroconfAuthenticator(Session.Inner session, Configuration conf) throws IOException { this.session = session; this.keys = new DiffieHellman(session.random); this.mDnsService = new MulticastDNSService(); @@ -73,14 +70,54 @@ public class ZeroconfAuthenticator implements Closeable { int port = session.random.nextInt((MAX_PORT - MIN_PORT) + 1) + MIN_PORT; new Thread(this.runner = new HttpRunner(port)).start(); - ServiceInstance service = new ServiceInstance(new ServiceName("librespot._spotify-connect._tcp.local."), 0, 0, port, Name.fromString("local."), InetAddress.getAllByName("localhost"), "VERSION=1.0", "CPath=/"); + InetAddress[] bound; + if (conf.zeroconfListenAll()) { + bound = getAllInterfacesAddresses(); + } else { + String[] interfaces = conf.zeroconfInterfaces(); + if (interfaces.length == 0) { + bound = new InetAddress[]{InetAddress.getLoopbackAddress()}; + } else { + List list = new ArrayList<>(); + for (String str : interfaces) addAddressForInterfaceName(list, str); + bound = list.toArray(new InetAddress[0]); + } + } + + LOGGER.debug("Registering service on " + Arrays.toString(bound)); + + ServiceInstance service = new ServiceInstance(new ServiceName("librespot._spotify-connect._tcp.local."), 0, 0, port, Name.fromString("local."), bound, "VERSION=1.0", "CPath=/"); spotifyConnectService = mDnsService.register(service); if (spotifyConnectService == null) throw new IOException("Failed registering SpotifyConnect service!"); - LOGGER.info("SpotifyConnect service registered successfully!"); } + private static void addAddressForInterfaceName(List list, @NotNull String name) throws SocketException { + NetworkInterface nif = NetworkInterface.getByName(name); + if (nif == null) { + LOGGER.warn(String.format("Interface %s doesn't exists.", name)); + return; + } + + addAddressOfInterface(list, nif); + } + + private static void addAddressOfInterface(List list, @NotNull NetworkInterface nif) { + LOGGER.trace(String.format("Adding addresses of %s (displayName: %s)", nif.getName(), nif.getDisplayName())); + Enumeration ias = nif.getInetAddresses(); + while (ias.hasMoreElements()) + list.add(ias.nextElement()); + } + + @NotNull + private static InetAddress[] getAllInterfacesAddresses() throws SocketException { + List list = new ArrayList<>(); + Enumeration is = NetworkInterface.getNetworkInterfaces(); + while (is.hasMoreElements()) addAddressOfInterface(list, is.nextElement()); + return list.toArray(new InetAddress[0]); + } + @Override public void close() throws IOException { mDnsService.unregister(spotifyConnectService); @@ -194,6 +231,13 @@ Authentication.LoginCredentials lockUntilCredentials() throws InterruptedExcepti } } + public interface Configuration { + boolean zeroconfListenAll(); + + @NotNull + String[] zeroconfInterfaces(); + } + private class HttpRunner implements Runnable, Closeable { private final ServerSocket serverSocket; private volatile boolean shouldStop = false; @@ -297,4 +341,4 @@ public void close() throws IOException { serverSocket.close(); } } -} +} \ No newline at end of file