Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -43,7 +45,6 @@

import static io.appium.java_client.internal.Strings.isNullOrEmpty;
import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS;
import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP6_ADDRESS;
import static java.util.Locale.ROOT;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
Expand All @@ -55,7 +56,6 @@
*/
public final class AppiumDriverLocalService implements Closeable {

private static final String URL_MASK = "http://%s:%d/";
private static final Logger LOG = LoggerFactory.getLogger(AppiumDriverLocalService.class);
private static final Pattern LOGGER_CONTEXT_PATTERN = Pattern.compile("^(\\[debug\\] )?\\[(.+?)\\]");
private static final String APPIUM_SERVICE_SLF4J_LOGGER_PREFIX = "appium.service";
Expand Down Expand Up @@ -83,7 +83,8 @@ public final class AppiumDriverLocalService implements Closeable {
this.nodeJSArgs = nodeJSArgs;
this.nodeJSEnvironment = nodeJSEnvironment;
this.startupTimeout = startupTimeout;
this.url = new URL(String.format(URL_MASK, ipAddress, nodeJSPort));
// URL encloses an IPv6 host in square brackets
this.url = new URL("http", ipAddress, nodeJSPort, "/");
}

/**
Expand Down Expand Up @@ -122,9 +123,8 @@ private static URL addSuffix(URL url, String suffix) {
}

@SneakyThrows
@SuppressWarnings("SameParameterValue")
private static URL replaceHost(URL source, String oldHost, String newHost) {
return new URL(source.toString().replaceFirst(oldHost, newHost));
private static URL replaceHost(URL source, String newHost) {
return new URL(source.getProtocol(), newHost, source.getPort(), source.getFile());
}

/**
Expand Down Expand Up @@ -167,18 +167,34 @@ private boolean ping(Duration timeout) throws InterruptedException {
return availabilityChecker.waitUntilAvailable(statusUrl, timeout);
}

private URL fixBroadcastAddresses(URL url) {
static URL fixBroadcastAddresses(URL url) {
var host = url.getHost();
// The operating system will block direct access to the universal broadcast IP address
if (host.equals(BROADCAST_IP4_ADDRESS)) {
return replaceHost(url, BROADCAST_IP4_ADDRESS, "127.0.0.1");
return replaceHost(url, "127.0.0.1");
}
if (host.equals(BROADCAST_IP6_ADDRESS)) {
return replaceHost(url, BROADCAST_IP6_ADDRESS, "::1");
if (isIpv6AnyAddress(host)) {
return replaceHost(url, "::1");
}
return url;
}

/**
* Checks for any form of the IPv6 unspecified address, such as {@code ::}, {@code ::0}
* or {@code 0:0:0:0:0:0:0:0}.
*/
private static boolean isIpv6AnyAddress(String host) {
if (!host.contains(":")) {
return false;
}
try {
// An IPv6 literal is parsed without a name lookup
return InetAddress.getByName(host).isAnyLocalAddress();
} catch (UnknownHostException e) {
return false;
}
}

/**
* Starts the defined appium server.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ public AppiumServiceBuilder withAppiumJS(File appiumJS) {

/**
* Sets the IP address the server listens on.
* An IPv6 address can be given with or without square brackets.
*
* @param ipAddress the IP address
* @return the self-reference.
Expand Down Expand Up @@ -362,6 +363,9 @@ List<String> createArgs() {

if (isNullOrEmpty(ipAddress)) {
ipAddress = BROADCAST_IP4_ADDRESS;
} else {
// Appium expects an IPv6 address without square brackets
ipAddress = stripBrackets(ipAddress);
}
argList.add("--address");
argList.add(ipAddress);
Expand Down Expand Up @@ -393,6 +397,17 @@ List<String> createArgs() {
return Collections.unmodifiableList(argList);
}

/**
* Removes the square brackets around an IPv6 literal.
*
* @param address an IP address or host name, with or without square brackets
* @return the address without enclosing square brackets
*/
private static String stripBrackets(String address) {
return address.length() > 1 && address.startsWith("[") && address.endsWith("]")
? address.substring(1, address.length() - 1)
: address;
}

/**
* Sets which Node.js the builder will use.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -52,6 +54,68 @@ void buildsServiceWithGivenPortAndAddress() throws IOException {
assertFalse(service.isRunning());
}

@Test
Comment thread
mykola-mokhnach marked this conversation as resolved.
Resolved
void buildsServiceWithIpv6Address() throws IOException {
var builder = newBuilder().usingPort(4777).withIPAddress("::1");

assertEquals(List.of(tempDir.resolve("main.js").toString(), "--port", "4777", "--address", "::1"),
builder.createArgs());
assertEquals("http://[::1]:4777/", builder.build().getUrl().toString());
}

@Test
void buildsServiceWithIpv6BroadcastAddress() throws IOException {
var service = newBuilder().usingPort(4777)
.withIPAddress(AppiumServiceBuilder.BROADCAST_IP6_ADDRESS).build();

assertEquals("http://[::]:4777/", service.getUrl().toString());
}

@Test
void buildsServiceWithBracketedIpv6Address() throws IOException {
var builder = newBuilder().usingPort(4777).withIPAddress("[::1]");

assertEquals(List.of(tempDir.resolve("main.js").toString(), "--port", "4777", "--address", "::1"),
builder.createArgs());
assertEquals("http://[::1]:4777/", builder.build().getUrl().toString());
}

@Test
void buildsServiceWithIpv6AddressWithZoneId() throws IOException {
var builder = newBuilder().usingPort(4777).withIPAddress("fe80::1%en0");

assertEquals(List.of(tempDir.resolve("main.js").toString(), "--port", "4777", "--address", "fe80::1%en0"),
builder.createArgs());
assertEquals("http://[fe80::1%en0]:4777/", builder.build().getUrl().toString());
}

@ParameterizedTest
@ValueSource(strings = {"::", "::0", "0:0:0:0:0:0:0:0"})
void pingsIpv6LoopbackForIpv6BroadcastAddress(String address) throws IOException {
var service = newBuilder().usingPort(4777).withIPAddress(address)
.withArgument(io.appium.java_client.service.local.flags.GeneralServerFlag.BASEPATH, "/wd/hub")
.build();

assertEquals("http://[::1]:4777/wd/hub/",
AppiumDriverLocalService.fixBroadcastAddresses(service.getUrl()).toString());
}

@Test
void pingsIpv4LoopbackForIpv4BroadcastAddress() throws IOException {
var service = newBuilder().usingPort(4777).withIPAddress(AppiumServiceBuilder.BROADCAST_IP4_ADDRESS).build();

assertEquals("http://127.0.0.1:4777/",
AppiumDriverLocalService.fixBroadcastAddresses(service.getUrl()).toString());
}

@ParameterizedTest
@ValueSource(strings = {"::1", "127.0.0.1", "localhost"})
void pingsGivenAddressWhenItIsNotABroadcastAddress(String address) throws IOException {
var url = newBuilder().usingPort(4777).withIPAddress(address).build().getUrl();

assertEquals(url.toString(), AppiumDriverLocalService.fixBroadcastAddresses(url).toString());
}

@Test
void appliesBasePath() throws IOException {
var service = newBuilder().usingPort(4777).withIPAddress("127.0.0.1")
Expand Down