Skip to content

Commit 4eb1d69

Browse files
authored
Merge pull request #6674 from thc202/selenium/sm-fx-bin
selenium: prevent use of no Firefox binary
2 parents 8988832 + c001a13 commit 4eb1d69

3 files changed

Lines changed: 234 additions & 2 deletions

File tree

addOns/selenium/selenium.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ zapAddOn {
3535
}
3636
}
3737

38+
spotless {
39+
java {
40+
target(
41+
fileTree(projectDir) {
42+
include("src/**/*.java")
43+
exclude("src/main/java/org/zaproxy/zap/extension/selenium/internal/FirefoxBinary.java")
44+
},
45+
)
46+
}
47+
}
48+
3849
dependencies {
3950
compileOnly(libs.log4j.core)
4051

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.zaproxy.zap.extension.selenium.internal;
19+
20+
import static java.util.stream.Collectors.toList;
21+
import static org.openqa.selenium.Platform.MAC;
22+
import static org.openqa.selenium.Platform.UNIX;
23+
import static org.openqa.selenium.Platform.WINDOWS;
24+
25+
import java.io.File;
26+
import java.io.IOException;
27+
import java.nio.file.Files;
28+
import java.nio.file.Path;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.Optional;
33+
import java.util.stream.Collectors;
34+
import java.util.stream.Stream;
35+
import org.openqa.selenium.Platform;
36+
import org.openqa.selenium.WebDriverException;
37+
import org.openqa.selenium.firefox.FirefoxDriver;
38+
import org.openqa.selenium.os.ExecutableFinder;
39+
40+
/**
41+
* A wrapper around Firefox's binary. This allows us to locate the binary in a portable way.
42+
*/
43+
public class FirefoxBinary {
44+
45+
private final String executable;
46+
47+
public FirefoxBinary() {
48+
String systemBinary = locateFirefoxBinaryFromSystemProperty();
49+
if (systemBinary != null) {
50+
executable = systemBinary;
51+
return;
52+
}
53+
54+
String platformBinary = locateFirefoxBinariesFromPlatform().findFirst().orElse(null);
55+
if (platformBinary != null) {
56+
executable = platformBinary;
57+
return;
58+
}
59+
60+
throw new WebDriverException(
61+
"Cannot find firefox binary in PATH. "
62+
+ "Make sure firefox is installed. OS appears to be: "
63+
+ Platform.getCurrent());
64+
}
65+
66+
public String getFile() {
67+
return executable;
68+
}
69+
70+
/**
71+
* Locates the firefox binary from a system property. Will throw an exception if the binary cannot
72+
* be found.
73+
*/
74+
static String locateFirefoxBinaryFromSystemProperty() {
75+
String binaryName = System.getProperty(FirefoxDriver.SystemProperty.BROWSER_BINARY);
76+
if (binaryName == null) return null;
77+
78+
File binary = new File(binaryName);
79+
if (binary.exists() && !binary.isDirectory()) return binary.getAbsolutePath();
80+
81+
Platform current = Platform.getCurrent();
82+
if (current.is(WINDOWS)) {
83+
if (!binaryName.endsWith(".exe")) {
84+
binaryName += ".exe";
85+
}
86+
87+
} else if (current.is(MAC)) {
88+
if (!binaryName.endsWith(".app")) {
89+
binaryName += ".app";
90+
}
91+
binaryName += "/Contents/MacOS/firefox";
92+
}
93+
94+
binary = new File(binaryName);
95+
if (binary.exists()) return binary.getAbsolutePath();
96+
97+
throw new WebDriverException(
98+
String.format(
99+
"'%s' property set, but unable to locate the requested binary: %s",
100+
FirefoxDriver.SystemProperty.BROWSER_BINARY, binaryName));
101+
}
102+
103+
/** Locates the firefox binary by platform. */
104+
private static Stream<String> locateFirefoxBinariesFromPlatform() {
105+
List<String> executables = new ArrayList<>();
106+
107+
Platform current = Platform.getCurrent();
108+
if (current.is(WINDOWS)) {
109+
executables.addAll(
110+
Stream.of(
111+
"Mozilla Firefox\\firefox.exe",
112+
"Firefox Developer Edition\\firefox.exe",
113+
"Nightly\\firefox.exe")
114+
.map(FirefoxBinary::getPathsInProgramFiles)
115+
.flatMap(List::stream)
116+
.map(File::new)
117+
.filter(File::exists)
118+
.map(File::getAbsolutePath)
119+
.collect(toList()));
120+
121+
} else if (current.is(MAC)) {
122+
// system
123+
File binary = new File("/Applications/Firefox.app/Contents/MacOS/firefox");
124+
if (binary.exists()) {
125+
executables.add(binary.getAbsolutePath());
126+
}
127+
128+
// user home
129+
binary = new File(System.getProperty("user.home") + binary.getAbsolutePath());
130+
if (binary.exists()) {
131+
executables.add(binary.getAbsolutePath());
132+
}
133+
134+
} else if (current.is(UNIX)) {
135+
String systemFirefoxBin = new ExecutableFinder().find("firefox");
136+
if (systemFirefoxBin != null) {
137+
executables.add(new File(systemFirefoxBin).getAbsolutePath());
138+
}
139+
}
140+
141+
String systemFirefox = new ExecutableFinder().find("firefox");
142+
if (systemFirefox != null) {
143+
Path firefoxPath = new File(systemFirefox).toPath();
144+
if (Files.isSymbolicLink(firefoxPath)) {
145+
try {
146+
Path realPath = firefoxPath.toRealPath();
147+
File file = realPath.getParent().resolve("firefox").toFile();
148+
if (file.exists()) {
149+
executables.add(file.getAbsolutePath());
150+
}
151+
} catch (IOException e) {
152+
// ignore this path
153+
}
154+
155+
} else {
156+
executables.add(new File(systemFirefox).getAbsolutePath());
157+
}
158+
}
159+
160+
return executables.stream();
161+
}
162+
163+
private static List<String> getPathsInProgramFiles(final String childPath) {
164+
return Stream.of(getProgramFilesPath(), getProgramFiles86Path())
165+
.map(parent -> new File(parent, childPath).getAbsolutePath())
166+
.collect(Collectors.toList());
167+
}
168+
169+
/**
170+
* Returns the path to the Windows Program Files. On non-English versions, this is not necessarily
171+
* "C:\Program Files".
172+
*
173+
* @return the path to the Windows Program Files
174+
*/
175+
private static String getProgramFilesPath() {
176+
return getEnvVarPath("ProgramFiles", "C:\\Program Files").replace(" (x86)", "");
177+
}
178+
179+
private static String getProgramFiles86Path() {
180+
return getEnvVarPath("ProgramFiles(x86)", "C:\\Program Files (x86)");
181+
}
182+
183+
private static String getEnvVarPath(final String envVar, final String defaultValue) {
184+
return getEnvVarIgnoreCase(envVar)
185+
.map(File::new)
186+
.filter(File::exists)
187+
.map(File::getAbsolutePath)
188+
.orElseGet(() -> new File(defaultValue).getAbsolutePath());
189+
}
190+
191+
private static Optional<String> getEnvVarIgnoreCase(String var) {
192+
return System.getenv().entrySet().stream()
193+
.filter(e -> e.getKey().equalsIgnoreCase(var))
194+
.findFirst()
195+
.map(Map.Entry::getValue);
196+
}
197+
}

addOns/selenium/src/main/java/org/zaproxy/zap/extension/selenium/internal/FirefoxProfileManager.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
import java.util.stream.Collectors;
3232
import org.apache.logging.log4j.LogManager;
3333
import org.apache.logging.log4j.Logger;
34+
import org.openqa.selenium.WebDriverException;
3435
import org.openqa.selenium.manager.SeleniumManager;
3536
import org.openqa.selenium.manager.SeleniumManagerOutput;
37+
import org.openqa.selenium.manager.SeleniumManagerOutput.Result;
3638
import org.parosproxy.paros.Constant;
3739
import org.zaproxy.zap.extension.selenium.ProfileManager;
3840
import org.zaproxy.zap.utils.Stats;
@@ -146,10 +148,19 @@ public Path getOrCreateProfile(String profileName) throws IOException {
146148
.getBinaryPaths(
147149
List.of("--offline", "--avoid-stats", "--browser", "firefox"));
148150
if (smOutput.getCode() != 0) {
149-
LOGGER.debug("Executed SeleniumManager with exit code: {}", smOutput.getCode());
151+
LOGGER.debug(
152+
"Executed SeleniumManager with exit code: {} Message: {}",
153+
smOutput.getCode(),
154+
smOutput.getMessage());
150155
return null;
151156
}
152-
String path = smOutput.getBrowserPath();
157+
158+
String path = getBrowserPath(smOutput);
159+
if (path == null || path.isEmpty()) {
160+
LOGGER.warn("Unable to find Firefox binary through SeleniumManager.");
161+
return null;
162+
}
163+
153164
String[] args = {path, "-headless", "-CreateProfile", profileName};
154165
LOGGER.debug("Creating profile with: {}", () -> Arrays.toString(args));
155166

@@ -174,4 +185,17 @@ public Path getOrCreateProfile(String profileName) throws IOException {
174185
}
175186
return profileDir;
176187
}
188+
189+
private static String getBrowserPath(Result smOutput) {
190+
String path = smOutput.getBrowserPath();
191+
if (path != null && !path.isEmpty()) {
192+
return path;
193+
}
194+
try {
195+
return new FirefoxBinary().getFile();
196+
} catch (WebDriverException ignore) {
197+
// Nothing to do, fallback attempt.
198+
}
199+
return null;
200+
}
177201
}

0 commit comments

Comments
 (0)