1 /*
2  * Created on Jun 6, 2004
3  *
4  * Paros and its related class files.
5  *
6  * Paros is an HTTP/HTTPS proxy for assessing web application security.
7  * Copyright (C) 2003-2004 Chinotec Technologies Company
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the Clarified Artistic License
11  * as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * Clarified Artistic License for more details.
17  *
18  * You should have received a copy of the Clarified Artistic License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21  */
22 // ZAP: 2011/08/03 Cope with unexpected values in config file
23 // ZAP: 2012/04/23 Added @Override annotation to the appropriate method and removed
24 // unnecessary cast.
25 // ZAP: 2012/11/15 Issue 416: Normalise how multiple related options are managed
26 // throughout ZAP and enhance the usability of some options.
27 // ZAP: 2013/01/04 Added portsForSslTunneling parameter with method
28 // isPortDemandingSslTunnel() to indicate HTTP CONNECT behavior.
29 // ZAP: 2013/01/30 Issue 478: Allow to choose to send ZAP's managed cookies on
30 // a single Cookie request header and set it as the default
31 // ZAP: 2013/12/13 Issue 939: ZAP should accept SSL connections on non-standard ports automatically
32 // ZAP: 2014/03/23 Issue 416: Normalise how multiple related options are managed throughout ZAP
33 // and enhance the usability of some options
34 // ZAP: 2014/03/23 Issue 968: Allow to choose the enabled SSL/TLS protocols
35 // ZAP: 2014/03/23 Issue 1100: Annotate option methods that shouldn't be exposed in the ZAP API
36 // ZAP: 2041/08/14 Issue 1305: Outgoing proxy is disabled when updating from old versions
37 // ZAP: 2016/08/08 Issue 2742: Allow for override/customization of Java's "networkaddress.cache.ttl"
38 // value
39 // ZAP: 2017/01/11 Exclude some options from the API (manually handled to return correct values).
40 // ZAP: 2017/04/14 Validate that the SSL/TLS versions persisted can be set/used.
41 // ZAP: 2017/05/02 Added option key to enable / disable HTTP State
42 // ZAP: 2017/05/15 Ensure HttpState is non-null when HTTP State is enabled.
43 // ZAP: 2017/06/19 Do not allow to set negative timeout values and expose the default value.
44 // ZAP: 2017/09/26 Use helper methods to read the configurations.
45 // ZAP: 2018/02/14 Remove unnecessary boxing / unboxing
46 // ZAP: 2018/08/10 Set the default user agent to HttpRequestHeader (Issue 4846).
47 // ZAP: 2019/06/01 Normalise line endings.
48 // ZAP: 2019/06/05 Normalise format/style.
49 // ZAP: 2020/01/02 Updated default user agent
50 // ZAP: 2020/04/20 Allow to configure the SOCKS proxy (Issue 29).
51 // ZAP: 2020/11/26 Use Log4j 2 classes for logging.
52 // ZAP: 2021/10/06 Updated default user agent
53 package org.parosproxy.paros.network;
54 
55 import java.net.PasswordAuthentication;
56 import java.security.Security;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.List;
61 import java.util.Objects;
62 import java.util.regex.Pattern;
63 import org.apache.commons.configuration.HierarchicalConfiguration;
64 import org.apache.commons.httpclient.HttpState;
65 import org.apache.logging.log4j.LogManager;
66 import org.apache.logging.log4j.Logger;
67 import org.parosproxy.paros.common.AbstractParam;
68 import org.zaproxy.zap.extension.api.ZapApiIgnore;
69 import org.zaproxy.zap.network.DomainMatcher;
70 import org.zaproxy.zap.network.SocksProxy;
71 
72 public class ConnectionParam extends AbstractParam {
73 
74     // ZAP: Added logger
75     private static Logger log = LogManager.getLogger(ConnectionParam.class);
76 
77     private static final String CONNECTION_BASE_KEY = "connection";
78 
79     private static final String USE_PROXY_CHAIN_KEY = CONNECTION_BASE_KEY + ".proxyChain.enabled";
80     private static final String PROXY_CHAIN_NAME = CONNECTION_BASE_KEY + ".proxyChain.hostName";
81     private static final String PROXY_CHAIN_PORT = CONNECTION_BASE_KEY + ".proxyChain.port";
82     private static final String USE_PROXY_CHAIN_AUTH_KEY =
83             CONNECTION_BASE_KEY + ".proxyChain.authEnabled";
84     private static final String PROXY_CHAIN_REALM = CONNECTION_BASE_KEY + ".proxyChain.realm";
85     private static final String PROXY_CHAIN_USER_NAME =
86             CONNECTION_BASE_KEY + ".proxyChain.userName";
87     private static final String PROXY_CHAIN_PASSWORD = CONNECTION_BASE_KEY + ".proxyChain.password";
88 
89     private static final String PROXY_EXCLUDED_DOMAIN_KEY =
90             CONNECTION_BASE_KEY + ".proxyChain.exclusions";
91     private static final String ALL_PROXY_EXCLUDED_DOMAINS_KEY =
92             PROXY_EXCLUDED_DOMAIN_KEY + ".exclusion";
93     private static final String PROXY_EXCLUDED_DOMAIN_VALUE_KEY = "name";
94     private static final String PROXY_EXCLUDED_DOMAIN_REGEX_KEY = "regex";
95     private static final String PROXY_EXCLUDED_DOMAIN_ENABLED_KEY = "enabled";
96     private static final String CONFIRM_REMOVE_EXCLUDED_DOMAIN =
97             CONNECTION_BASE_KEY + ".proxyChain.confirmRemoveExcludedDomain";
98 
99     private static final String SECURITY_PROTOCOLS_ENABLED =
100             CONNECTION_BASE_KEY + ".securityProtocolsEnabled";
101     private static final String SECURITY_PROTOCOL_ELEMENT_KEY = "protocol";
102     private static final String ALL_SECURITY_PROTOCOLS_ENABLED_KEY =
103             SECURITY_PROTOCOLS_ENABLED + "." + SECURITY_PROTOCOL_ELEMENT_KEY;
104 
105     // ZAP: Added prompt option and timeout
106     private static final String PROXY_CHAIN_PROMPT = CONNECTION_BASE_KEY + ".proxyChain.prompt";
107     private static final String TIMEOUT_IN_SECS = CONNECTION_BASE_KEY + ".timeoutInSecs";
108     private static final String SINGLE_COOKIE_REQUEST_HEADER =
109             CONNECTION_BASE_KEY + ".singleCookieRequestHeader";
110     private static final String HTTP_STATE_ENABLED = CONNECTION_BASE_KEY + ".httpStateEnabled";
111     public static final String DEFAULT_USER_AGENT = CONNECTION_BASE_KEY + ".defaultUserAgent";
112 
113     public static final String DEFAULT_DEFAULT_USER_AGENT =
114             "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0";
115 
116     /** The security property for TTL of successful DNS queries. */
117     private static final String DNS_TTL_SUCCESSFUL_QUERIES_SECURITY_PROPERTY =
118             "networkaddress.cache.ttl";
119 
120     /**
121      * The default TTL (in seconds) of successful DNS queries.
122      *
123      * @since 2.6.0
124      */
125     public static final int DNS_DEFAULT_TTL_SUCCESSFUL_QUERIES = 30;
126 
127     /** The configuration key for TTL of successful DNS queries. */
128     private static final String DNS_TTL_SUCCESSFUL_QUERIES_KEY =
129             CONNECTION_BASE_KEY + ".dnsTtlSuccessfulQueries";
130 
131     /**
132      * The default connection timeout (in seconds).
133      *
134      * @since 2.7.0
135      */
136     public static final int DEFAULT_TIMEOUT = 20;
137 
138     private static final String SOCKS_PROXY_BASE_KEY = CONNECTION_BASE_KEY + ".socksProxy.";
139     private static final String USE_SOCKS_PROXY_KEY = SOCKS_PROXY_BASE_KEY + "enabled";
140     private static final String SOCKS_PROXY_HOST_KEY = SOCKS_PROXY_BASE_KEY + "host";
141     private static final String SOCKS_PROXY_PORT_KEY = SOCKS_PROXY_BASE_KEY + "port";
142     private static final String SOCKS_PROXY_VERSION_KEY = SOCKS_PROXY_BASE_KEY + "version";
143     private static final String SOCKS_PROXY_DNS_KEY = SOCKS_PROXY_BASE_KEY + "dns";
144     private static final String SOCKS_PROXY_USERNAME_KEY = SOCKS_PROXY_BASE_KEY + "username";
145     private static final String SOCKS_PROXY_PASSWORD_KEY = SOCKS_PROXY_BASE_KEY + "password";
146 
147     /**
148      * The default SOCKS proxy configuration.
149      *
150      * @since 2.10.0
151      */
152     public static final SocksProxy DEFAULT_SOCKS_PROXY = new SocksProxy("localhost", 1080);
153 
154     /**
155      * Pattern with loopback names and addresses that should be always resolved (when creating the
156      * {@link java.net.InetSocketAddress}).
157      *
158      * <p>Same pattern used by default proxy selector.
159      *
160      * @see #shouldResolveRemoteHostname(String)
161      */
162     private static final Pattern LOOPBACK_PATTERN =
163             Pattern.compile("\\Qlocalhost\\E|\\Q127.\\E.*|\\Q[::1]\\E|\\Q0.0.0.0\\E|\\Q[::0]\\E");
164 
165     private boolean useProxyChain;
166     private String proxyChainName = "";
167     private int proxyChainPort = 8080;
168     private boolean confirmRemoveProxyExcludeDomain = true;
169     private boolean useProxyChainAuth;
170     private String proxyChainRealm = "";
171     private String proxyChainUserName = "";
172     private String proxyChainPassword = "";
173 
174     private boolean useSocksProxy;
175     private SocksProxy socksProxy = DEFAULT_SOCKS_PROXY;
176     private PasswordAuthentication socksProxyPasswordAuth =
177             new PasswordAuthentication("", new char[0]);
178 
179     private HttpState httpState = null;
180     private boolean httpStateEnabled = false;
181     private List<DomainMatcher> proxyExcludedDomains = new ArrayList<>(0);
182     private List<DomainMatcher> proxyExcludedDomainsEnabled = new ArrayList<>(0);
183 
184     private String[] securityProtocolsEnabled;
185 
186     // ZAP: Added prompt option and timeout
187     private boolean proxyChainPrompt = false;
188     private int timeoutInSecs = DEFAULT_TIMEOUT;
189 
190     private boolean singleCookieRequestHeader = true;
191     private String defaultUserAgent = "";
192 
193     /** The TTL (in seconds) of successful DNS queries. */
194     private int dnsTtlSuccessfulQueries = DNS_DEFAULT_TTL_SUCCESSFUL_QUERIES;
195 
196     /** @return Returns the httpStateEnabled. */
isHttpStateEnabled()197     public boolean isHttpStateEnabled() {
198         return httpStateEnabled;
199     }
200     /** @param httpStateEnabled The httpStateEnabled to set. */
setHttpStateEnabled(boolean httpStateEnabled)201     public void setHttpStateEnabled(boolean httpStateEnabled) {
202         setHttpStateEnabledImpl(httpStateEnabled);
203         getConfig().setProperty(HTTP_STATE_ENABLED, this.httpStateEnabled);
204     }
205 
setHttpStateEnabledImpl(boolean httpStateEnabled)206     private void setHttpStateEnabledImpl(boolean httpStateEnabled) {
207         this.httpStateEnabled = httpStateEnabled;
208         if (this.httpStateEnabled) {
209             httpState = new HttpState();
210         } else {
211             httpState = null;
212         }
213     }
214 
ConnectionParam()215     public ConnectionParam() {}
216 
217     @Override
parse()218     protected void parse() {
219         updateOptions();
220 
221         dnsTtlSuccessfulQueries =
222                 getInt(DNS_TTL_SUCCESSFUL_QUERIES_KEY, DNS_DEFAULT_TTL_SUCCESSFUL_QUERIES);
223         Security.setProperty(
224                 DNS_TTL_SUCCESSFUL_QUERIES_SECURITY_PROPERTY,
225                 Integer.toString(dnsTtlSuccessfulQueries));
226 
227         useProxyChain = getBoolean(USE_PROXY_CHAIN_KEY, false);
228         useProxyChainAuth = getBoolean(USE_PROXY_CHAIN_AUTH_KEY, false);
229 
230         setProxyChainName(getString(PROXY_CHAIN_NAME, ""));
231         setProxyChainPort(getInt(PROXY_CHAIN_PORT, 8080));
232 
233         loadProxyExcludedDomains();
234         this.confirmRemoveProxyExcludeDomain = getBoolean(CONFIRM_REMOVE_EXCLUDED_DOMAIN, true);
235 
236         setProxyChainRealm(getString(PROXY_CHAIN_REALM, ""));
237         setProxyChainUserName(getString(PROXY_CHAIN_USER_NAME, ""));
238 
239         try {
240             // ZAP: Added prompt option
241             if (getConfig().getProperty(PROXY_CHAIN_PROMPT) instanceof String
242                     && ((String) getConfig().getProperty(PROXY_CHAIN_PROMPT)).isEmpty()) {
243                 // In 1.2.0 the default for this field was empty, which causes a crash in 1.3.*
244                 setProxyChainPrompt(false);
245             } else if (getBoolean(PROXY_CHAIN_PROMPT, false)) {
246                 setProxyChainPrompt(true);
247             } else {
248                 setProxyChainPrompt(false);
249                 setProxyChainPassword(getString(PROXY_CHAIN_PASSWORD, ""));
250             }
251         } catch (Exception e) {
252             // ZAP: Log exceptions
253             log.error(e.getMessage(), e);
254         }
255 
256         setTimeoutInSecsImpl(getInt(TIMEOUT_IN_SECS, DEFAULT_TIMEOUT));
257 
258         this.singleCookieRequestHeader = getBoolean(SINGLE_COOKIE_REQUEST_HEADER, true);
259 
260         setHttpStateEnabledImpl(getBoolean(HTTP_STATE_ENABLED, false));
261 
262         this.defaultUserAgent = getString(DEFAULT_USER_AGENT, DEFAULT_DEFAULT_USER_AGENT);
263         HttpRequestHeader.setDefaultUserAgent(defaultUserAgent);
264 
265         loadSecurityProtocolsEnabled();
266 
267         parseSocksProxyOptions();
268     }
269 
updateOptions()270     private void updateOptions() {
271         final String oldKey = CONNECTION_BASE_KEY + "sslConnectPorts";
272         if (getConfig().containsKey(oldKey)) {
273             getConfig().clearProperty(oldKey);
274         }
275 
276         final String oldSkipNameKey = CONNECTION_BASE_KEY + ".proxyChain.skipName";
277         if (getConfig().containsKey(oldSkipNameKey)) {
278             migrateOldSkipNameOption(getConfig().getString(oldSkipNameKey, ""));
279             getConfig().clearProperty(oldSkipNameKey);
280         }
281 
282         if (!getConfig().containsKey(USE_PROXY_CHAIN_KEY)) {
283             String proxyName = getConfig().getString(PROXY_CHAIN_NAME, "");
284             if (!proxyName.isEmpty()) {
285                 getConfig().setProperty(USE_PROXY_CHAIN_KEY, Boolean.TRUE);
286             }
287         }
288 
289         if (!getConfig().containsKey(USE_PROXY_CHAIN_AUTH_KEY)) {
290             String proxyUserName = getConfig().getString(PROXY_CHAIN_USER_NAME, "");
291             if (!proxyUserName.isEmpty()) {
292                 getConfig().setProperty(USE_PROXY_CHAIN_AUTH_KEY, Boolean.TRUE);
293             }
294         }
295     }
296 
migrateOldSkipNameOption(String skipNames)297     private void migrateOldSkipNameOption(String skipNames) {
298         List<DomainMatcher> excludedDomains = convertOldSkipNameOption(skipNames);
299 
300         if (!excludedDomains.isEmpty()) {
301             setProxyExcludedDomains(excludedDomains);
302         }
303     }
304 
convertOldSkipNameOption(String skipNames)305     private static List<DomainMatcher> convertOldSkipNameOption(String skipNames) {
306         if (skipNames == null || skipNames.isEmpty()) {
307             return Collections.emptyList();
308         }
309 
310         ArrayList<DomainMatcher> excludedDomains = new ArrayList<>();
311         String[] names = skipNames.split(";");
312         for (String name : names) {
313             String excludedDomain = name.trim();
314             if (!excludedDomain.isEmpty()) {
315                 if (excludedDomain.contains("*")) {
316                     excludedDomain = excludedDomain.replace(".", "\\.").replace("*", ".*?");
317                     try {
318                         Pattern pattern = Pattern.compile(excludedDomain, Pattern.CASE_INSENSITIVE);
319                         excludedDomains.add(new DomainMatcher(pattern));
320                     } catch (IllegalArgumentException e) {
321                         log.error("Failed to migrate the excluded domain name: " + name, e);
322                     }
323                 } else {
324                     excludedDomains.add(new DomainMatcher(excludedDomain));
325                 }
326             }
327         }
328         excludedDomains.trimToSize();
329         return excludedDomains;
330     }
331 
332     /**
333      * Tells whether or not the outgoing connections should use the proxy set.
334      *
335      * @return {@code true} if outgoing connections should use the proxy set, {@code false}
336      *     otherwise.
337      * @since 2.3.0
338      * @see #setUseProxyChain(boolean)
339      */
isUseProxyChain()340     public boolean isUseProxyChain() {
341         return useProxyChain;
342     }
343 
344     /**
345      * Sets whether or not the outgoing connections should use the proxy set.
346      *
347      * <p><strong>Note:</strong> The call to this method has no effect if set to use the proxy but
348      * the proxy was not previously configured.
349      *
350      * @param useProxyChain {@code true} if outgoing connections should use the proxy set, {@code
351      *     false} otherwise.
352      * @since 2.3.0
353      * @see #isUseProxyChain()
354      * @see #setProxyChainName(String)
355      * @see #setProxyChainPort(int)
356      */
setUseProxyChain(boolean useProxyChain)357     public void setUseProxyChain(boolean useProxyChain) {
358         if (useProxyChain && (getProxyChainName() == null || getProxyChainName().isEmpty())) {
359             return;
360         }
361 
362         this.useProxyChain = useProxyChain;
363         getConfig().setProperty(USE_PROXY_CHAIN_KEY, this.useProxyChain);
364     }
365 
366     /**
367      * Returns the name of the outgoing proxy. The returned name is never {@code null}.
368      *
369      * @return the name of the outgoing proxy, never {@code null}.
370      * @see #isUseProxyChain()
371      * @see #setProxyChainName(String)
372      */
getProxyChainName()373     public String getProxyChainName() {
374         return proxyChainName;
375     }
376 
377     /**
378      * Sets the name of the outgoing proxy. If empty the use of the outgoing proxy will be disabled.
379      *
380      * <p><strong>Note:</strong> The call to this method has no effect if the given {@code
381      * proxyChainName} is {@code null}.
382      *
383      * @param proxyChainName the name of the outgoing proxy
384      * @see #getProxyChainName()
385      * @see #setUseProxyChain(boolean)
386      */
setProxyChainName(String proxyChainName)387     public void setProxyChainName(String proxyChainName) {
388         if (proxyChainName == null) {
389             return;
390         }
391         this.proxyChainName = proxyChainName.trim();
392         if (proxyChainName.isEmpty()) {
393             setUseProxyChain(false);
394         }
395         getConfig().setProperty(PROXY_CHAIN_NAME, this.proxyChainName);
396     }
397 
getProxyChainPort()398     public int getProxyChainPort() {
399         return proxyChainPort;
400     }
401 
setProxyChainPort(int proxyChainPort)402     public void setProxyChainPort(int proxyChainPort) {
403         this.proxyChainPort = proxyChainPort;
404         getConfig().setProperty(PROXY_CHAIN_PORT, Integer.toString(this.proxyChainPort));
405     }
406 
407     /**
408      * @deprecated (2.3.0) Replaced by {@link #getProxyExcludedDomains()} and {@link
409      *     #getProxyExcludedDomainsEnabled()}. <strong>Note:</strong> Newer regular expression
410      *     excluded domains will not be returned by this method.
411      */
412     @Deprecated
413     @ZapApiIgnore
414     @SuppressWarnings({"javadoc"})
getProxyChainSkipName()415     public String getProxyChainSkipName() {
416         StringBuilder skipNamesStringBuilder = new StringBuilder("");
417         for (DomainMatcher excludedDomain : proxyExcludedDomains) {
418             if (!excludedDomain.isRegex()) {
419                 skipNamesStringBuilder.append(excludedDomain.getValue()).append(';');
420             }
421         }
422         return skipNamesStringBuilder.toString();
423     }
424 
425     /** @deprecated (2.3.0) Replaced by {@link #setProxyExcludedDomains(List)}. */
426     @Deprecated
427     @SuppressWarnings({"javadoc"})
setProxyChainSkipName(String proxyChainSkipName)428     public void setProxyChainSkipName(String proxyChainSkipName) {
429         setProxyExcludedDomains(convertOldSkipNameOption(proxyChainSkipName));
430     }
431 
432     /**
433      * Tells whether or not the outgoing connections should use the proxy authentication credentials
434      * set.
435      *
436      * @return {@code true} if outgoing connections should use the proxy authentication credentials
437      *     set, {@code false} otherwise.
438      * @since 2.3.0
439      * @see #isUseProxyChain()
440      * @see #setUseProxyChainAuth(boolean)
441      */
isUseProxyChainAuth()442     public boolean isUseProxyChainAuth() {
443         return useProxyChainAuth;
444     }
445 
446     /**
447      * Sets whether or not the outgoing connections should use the proxy authentication credentials
448      * set.
449      *
450      * <p><strong>Note:</strong> The call to this method has no effect if set to use the credentials
451      * but the credentials were not previously set.
452      *
453      * @param useProxyChainAuth {@code true} if outgoing connections should use the proxy
454      *     authentication credentials set, {@code false} otherwise.
455      * @since 2.3.0
456      * @see #isUseProxyChainAuth()
457      * @see #setUseProxyChain(boolean)
458      * @see #setProxyChainUserName(String)
459      * @see #setProxyChainPassword(String)
460      * @see #setProxyChainRealm(String)
461      */
setUseProxyChainAuth(boolean useProxyChainAuth)462     public void setUseProxyChainAuth(boolean useProxyChainAuth) {
463         if (useProxyChainAuth
464                 && (getProxyChainUserName() == null || getProxyChainUserName().isEmpty())) {
465             return;
466         }
467 
468         this.useProxyChainAuth = useProxyChainAuth;
469         getConfig().setProperty(USE_PROXY_CHAIN_AUTH_KEY, this.useProxyChainAuth);
470     }
471 
getProxyChainRealm()472     public String getProxyChainRealm() {
473         return proxyChainRealm;
474     }
475 
setProxyChainRealm(String proxyChainRealm)476     public void setProxyChainRealm(String proxyChainRealm) {
477         this.proxyChainRealm = proxyChainRealm.trim();
478         getConfig().setProperty(PROXY_CHAIN_REALM, this.proxyChainRealm);
479     }
480 
getProxyChainUserName()481     public String getProxyChainUserName() {
482         return proxyChainUserName;
483     }
484 
setProxyChainUserName(String proxyChainUserName)485     public void setProxyChainUserName(String proxyChainUserName) {
486         this.proxyChainUserName = proxyChainUserName.trim();
487         getConfig().setProperty(PROXY_CHAIN_USER_NAME, this.proxyChainUserName);
488     }
489 
getProxyChainPassword()490     public String getProxyChainPassword() {
491         return proxyChainPassword.trim();
492     }
493 
setProxyChainPassword(String proxyChainPassword)494     public void setProxyChainPassword(String proxyChainPassword) {
495         this.proxyChainPassword = proxyChainPassword;
496         getConfig().setProperty(PROXY_CHAIN_PASSWORD, this.proxyChainPassword);
497     }
498 
499     // ZAP: Added setProxyChainPassword(String, boolean) method
setProxyChainPassword(String proxyChainPassword, boolean save)500     public void setProxyChainPassword(String proxyChainPassword, boolean save) {
501         if (save) {
502             this.setProxyChainPassword(proxyChainPassword);
503         } else {
504             this.proxyChainPassword = proxyChainPassword;
505         }
506     }
507 
508     // ZAP: Added prompt option
setProxyChainPrompt(boolean proxyPrompt)509     public void setProxyChainPrompt(boolean proxyPrompt) {
510         this.proxyChainPrompt = proxyPrompt;
511         getConfig().setProperty(PROXY_CHAIN_PROMPT, this.proxyChainPrompt);
512     }
513 
isProxyChainPrompt()514     public boolean isProxyChainPrompt() {
515         return this.proxyChainPrompt;
516     }
517 
518     /**
519      * Tells whether or not the given {@code domainName} should be excluded from the outgoing proxy.
520      *
521      * @param domainName the domain to be checked
522      * @return {@code true} if the given {@code domainName} should be excluded, {@code false}
523      *     otherwise.
524      * @since 2.3.0
525      */
isDomainExcludedFromProxy(String domainName)526     private boolean isDomainExcludedFromProxy(String domainName) {
527         if (domainName == null || domainName.isEmpty()) {
528             return false;
529         }
530 
531         for (DomainMatcher excludedDomain : proxyExcludedDomainsEnabled) {
532             if (excludedDomain.matches(domainName)) {
533                 return true;
534             }
535         }
536         return false;
537     }
538 
539     /**
540      * Check if given host name need to send using proxy.
541      *
542      * @param hostName host name to be checked.
543      * @return true = need to send via proxy.
544      */
isUseProxy(String hostName)545     public boolean isUseProxy(String hostName) {
546         if (!isUseProxyChain() || isDomainExcludedFromProxy(hostName)) {
547             return false;
548         } else {
549             return true;
550         }
551     }
552 
553     /** @return Returns the httpState. */
getHttpState()554     public HttpState getHttpState() {
555         return httpState;
556     }
557     /** @param httpState The httpState to set. */
setHttpState(HttpState httpState)558     public void setHttpState(HttpState httpState) {
559         this.httpState = httpState;
560     }
561 
getTimeoutInSecs()562     public int getTimeoutInSecs() {
563         return timeoutInSecs;
564     }
565 
setTimeoutInSecs(int timeoutInSecs)566     public void setTimeoutInSecs(int timeoutInSecs) {
567         setTimeoutInSecsImpl(timeoutInSecs);
568         getConfig().setProperty(TIMEOUT_IN_SECS, this.timeoutInSecs);
569     }
570 
setTimeoutInSecsImpl(int timeoutInSecs)571     private void setTimeoutInSecsImpl(int timeoutInSecs) {
572         if (timeoutInSecs < 0) {
573             this.timeoutInSecs = 0;
574             return;
575         }
576 
577         this.timeoutInSecs = timeoutInSecs;
578     }
579 
580     /**
581      * Tells whether the cookies should be set on a single "Cookie" request header or multiple
582      * "Cookie" request headers, when sending an HTTP request to the server.
583      *
584      * @return {@code true} if the cookies should be set on a single request header, {@code false}
585      *     otherwise
586      */
isSingleCookieRequestHeader()587     public boolean isSingleCookieRequestHeader() {
588         return this.singleCookieRequestHeader;
589     }
590 
591     /**
592      * Sets whether the cookies should be set on a single "Cookie" request header or multiple
593      * "Cookie" request headers, when sending an HTTP request to the server.
594      *
595      * @param singleCookieRequestHeader {@code true} if the cookies should be set on a single
596      *     request header, {@code false} otherwise
597      */
setSingleCookieRequestHeader(boolean singleCookieRequestHeader)598     public void setSingleCookieRequestHeader(boolean singleCookieRequestHeader) {
599         this.singleCookieRequestHeader = singleCookieRequestHeader;
600         getConfig().setProperty(SINGLE_COOKIE_REQUEST_HEADER, singleCookieRequestHeader);
601     }
602 
603     /**
604      * Returns the domains excluded from the outgoing proxy.
605      *
606      * @return the domains excluded from the outgoing proxy.
607      * @since 2.3.0
608      * @see #isUseProxy(String)
609      * @see #getProxyExcludedDomainsEnabled()
610      * @see #setProxyExcludedDomains(List)
611      */
612     @ZapApiIgnore
getProxyExcludedDomains()613     public List<DomainMatcher> getProxyExcludedDomains() {
614         return proxyExcludedDomains;
615     }
616 
617     /**
618      * Returns the, enabled, domains excluded from the outgoing proxy.
619      *
620      * @return the enabled domains excluded from the outgoing proxy.
621      * @since 2.3.0
622      * @see #isUseProxy(String)
623      * @see #getProxyExcludedDomains()
624      * @see #setProxyExcludedDomains(List)
625      */
626     @ZapApiIgnore
getProxyExcludedDomainsEnabled()627     public List<DomainMatcher> getProxyExcludedDomainsEnabled() {
628         return proxyExcludedDomainsEnabled;
629     }
630 
631     /**
632      * Sets the domains that will be excluded from the outgoing proxy.
633      *
634      * @param proxyExcludedDomains the domains that will be excluded.
635      * @since 2.3.0
636      * @see #getProxyExcludedDomains()
637      * @see #getProxyExcludedDomainsEnabled()
638      */
setProxyExcludedDomains(List<DomainMatcher> proxyExcludedDomains)639     public void setProxyExcludedDomains(List<DomainMatcher> proxyExcludedDomains) {
640         if (proxyExcludedDomains == null || proxyExcludedDomains.isEmpty()) {
641             ((HierarchicalConfiguration) getConfig()).clearTree(ALL_PROXY_EXCLUDED_DOMAINS_KEY);
642 
643             this.proxyExcludedDomains = Collections.emptyList();
644             this.proxyExcludedDomainsEnabled = Collections.emptyList();
645             return;
646         }
647 
648         this.proxyExcludedDomains = new ArrayList<>(proxyExcludedDomains);
649 
650         ((HierarchicalConfiguration) getConfig()).clearTree(ALL_PROXY_EXCLUDED_DOMAINS_KEY);
651 
652         int size = proxyExcludedDomains.size();
653         ArrayList<DomainMatcher> enabledExcludedDomains = new ArrayList<>(size);
654         for (int i = 0; i < size; ++i) {
655             String elementBaseKey = ALL_PROXY_EXCLUDED_DOMAINS_KEY + "(" + i + ").";
656             DomainMatcher excludedDomain = proxyExcludedDomains.get(i);
657 
658             getConfig()
659                     .setProperty(
660                             elementBaseKey + PROXY_EXCLUDED_DOMAIN_VALUE_KEY,
661                             excludedDomain.getValue());
662             getConfig()
663                     .setProperty(
664                             elementBaseKey + PROXY_EXCLUDED_DOMAIN_REGEX_KEY,
665                             excludedDomain.isRegex());
666             getConfig()
667                     .setProperty(
668                             elementBaseKey + PROXY_EXCLUDED_DOMAIN_ENABLED_KEY,
669                             excludedDomain.isEnabled());
670 
671             if (excludedDomain.isEnabled()) {
672                 enabledExcludedDomains.add(excludedDomain);
673             }
674         }
675 
676         enabledExcludedDomains.trimToSize();
677         this.proxyExcludedDomainsEnabled = enabledExcludedDomains;
678     }
679 
loadProxyExcludedDomains()680     private void loadProxyExcludedDomains() {
681         List<HierarchicalConfiguration> fields =
682                 ((HierarchicalConfiguration) getConfig())
683                         .configurationsAt(ALL_PROXY_EXCLUDED_DOMAINS_KEY);
684         this.proxyExcludedDomains = new ArrayList<>(fields.size());
685         ArrayList<DomainMatcher> excludedDomainsEnabled = new ArrayList<>(fields.size());
686         for (HierarchicalConfiguration sub : fields) {
687             String value = sub.getString(PROXY_EXCLUDED_DOMAIN_VALUE_KEY, "");
688             if (value.isEmpty()) {
689                 log.warn(
690                         "Failed to read an outgoing proxy excluded domain entry, required value is empty.");
691                 continue;
692             }
693 
694             DomainMatcher excludedDomain = null;
695             boolean regex = sub.getBoolean(PROXY_EXCLUDED_DOMAIN_REGEX_KEY, false);
696             if (regex) {
697                 try {
698                     Pattern pattern = DomainMatcher.createPattern(value);
699                     excludedDomain = new DomainMatcher(pattern);
700                 } catch (IllegalArgumentException e) {
701                     log.error(
702                             "Failed to read an outgoing proxy excluded domain entry with regex: "
703                                     + value,
704                             e);
705                 }
706             } else {
707                 excludedDomain = new DomainMatcher(value);
708             }
709 
710             if (excludedDomain != null) {
711                 excludedDomain.setEnabled(sub.getBoolean(PROXY_EXCLUDED_DOMAIN_ENABLED_KEY, true));
712 
713                 proxyExcludedDomains.add(excludedDomain);
714 
715                 if (excludedDomain.isEnabled()) {
716                     excludedDomainsEnabled.add(excludedDomain);
717                 }
718             }
719         }
720 
721         excludedDomainsEnabled.trimToSize();
722         this.proxyExcludedDomainsEnabled = excludedDomainsEnabled;
723     }
724 
725     /**
726      * Tells whether or not the remotion of a proxy exclusion needs confirmation.
727      *
728      * @return {@code true} if the remotion needs confirmation, {@code false} otherwise.
729      * @since 2.3.0
730      */
731     @ZapApiIgnore
isConfirmRemoveProxyExcludedDomain()732     public boolean isConfirmRemoveProxyExcludedDomain() {
733         return this.confirmRemoveProxyExcludeDomain;
734     }
735 
736     /**
737      * Sets whether or not the remotion of a proxy exclusion needs confirmation.
738      *
739      * @param confirmRemove {@code true} if the remotion needs confirmation, {@code false}
740      *     otherwise.
741      * @since 2.3.0
742      */
743     @ZapApiIgnore
setConfirmRemoveProxyExcludedDomain(boolean confirmRemove)744     public void setConfirmRemoveProxyExcludedDomain(boolean confirmRemove) {
745         this.confirmRemoveProxyExcludeDomain = confirmRemove;
746         getConfig().setProperty(CONFIRM_REMOVE_EXCLUDED_DOMAIN, confirmRemoveProxyExcludeDomain);
747     }
748 
749     /**
750      * Returns the security protocols enabled (SSL/TLS) for outgoing connections.
751      *
752      * @return the security protocols enabled for outgoing connections.
753      * @since 2.3.0
754      */
755     @ZapApiIgnore
getSecurityProtocolsEnabled()756     public String[] getSecurityProtocolsEnabled() {
757         return Arrays.copyOf(securityProtocolsEnabled, securityProtocolsEnabled.length);
758     }
759 
760     /**
761      * Sets the security protocols enabled (SSL/TLS) for outgoing connections.
762      *
763      * <p>The call has no effect if the given array is null or empty.
764      *
765      * @param enabledProtocols the security protocols enabled (SSL/TLS) for outgoing connections.
766      * @throws IllegalArgumentException if at least one of the {@code enabledProtocols} is {@code
767      *     null} or empty.
768      * @since 2.3.0
769      */
setSecurityProtocolsEnabled(String[] enabledProtocols)770     public void setSecurityProtocolsEnabled(String[] enabledProtocols) {
771         if (enabledProtocols == null || enabledProtocols.length == 0) {
772             return;
773         }
774         for (int i = 0; i < enabledProtocols.length; i++) {
775             if (enabledProtocols[i] == null || enabledProtocols[i].isEmpty()) {
776                 throw new IllegalArgumentException(
777                         "The parameter enabledProtocols must not contain null or empty elements.");
778             }
779         }
780 
781         ((HierarchicalConfiguration) getConfig()).clearTree(ALL_SECURITY_PROTOCOLS_ENABLED_KEY);
782 
783         for (int i = 0; i < enabledProtocols.length; ++i) {
784             String elementBaseKey = ALL_SECURITY_PROTOCOLS_ENABLED_KEY + "(" + i + ")";
785             getConfig().setProperty(elementBaseKey, enabledProtocols[i]);
786         }
787 
788         this.securityProtocolsEnabled = Arrays.copyOf(enabledProtocols, enabledProtocols.length);
789         setClientEnabledProtocols();
790     }
791 
loadSecurityProtocolsEnabled()792     private void loadSecurityProtocolsEnabled() {
793         List<Object> protocols = getConfig().getList(ALL_SECURITY_PROTOCOLS_ENABLED_KEY);
794         if (protocols.size() != 0) {
795             securityProtocolsEnabled = new String[protocols.size()];
796             securityProtocolsEnabled = protocols.toArray(securityProtocolsEnabled);
797             setClientEnabledProtocols();
798         } else {
799             setSecurityProtocolsEnabled(SSLConnector.getClientEnabledProtocols());
800         }
801     }
802 
setClientEnabledProtocols()803     private void setClientEnabledProtocols() {
804         try {
805             SSLConnector.setClientEnabledProtocols(securityProtocolsEnabled);
806         } catch (IllegalArgumentException e) {
807             log.warn(
808                     "Failed to set persisted protocols "
809                             + Arrays.toString(securityProtocolsEnabled)
810                             + " falling back to "
811                             + Arrays.toString(SSLConnector.getFailSafeProtocols())
812                             + " caused by: "
813                             + e.getMessage());
814             securityProtocolsEnabled = SSLConnector.getFailSafeProtocols();
815             SSLConnector.setClientEnabledProtocols(securityProtocolsEnabled);
816         }
817     }
818 
getDefaultUserAgent()819     public String getDefaultUserAgent() {
820         return this.defaultUserAgent;
821     }
822 
setDefaultUserAgent(String defaultUserAgent)823     public void setDefaultUserAgent(String defaultUserAgent) {
824         this.defaultUserAgent = defaultUserAgent;
825         HttpRequestHeader.setDefaultUserAgent(defaultUserAgent);
826         getConfig().setProperty(DEFAULT_USER_AGENT, defaultUserAgent);
827     }
828 
829     /**
830      * Gets the TTL (in seconds) of successful DNS queries.
831      *
832      * @return the TTL in seconds
833      * @since 2.6.0
834      * @see #setDnsTtlSuccessfulQueries(int)
835      */
getDnsTtlSuccessfulQueries()836     public int getDnsTtlSuccessfulQueries() {
837         return dnsTtlSuccessfulQueries;
838     }
839 
840     /**
841      * Sets the TTL (in seconds) of successful DNS queries.
842      *
843      * <p>Some values have special meaning:
844      *
845      * <ul>
846      *   <li>Negative number, cache forever;
847      *   <li>Zero, disables caching;
848      *   <li>Positive number, the number of seconds the successful DNS queries will be cached.
849      * </ul>
850      *
851      * @param ttl the TTL in seconds
852      * @since 2.6.0
853      * @see #getDnsTtlSuccessfulQueries()
854      */
setDnsTtlSuccessfulQueries(int ttl)855     public void setDnsTtlSuccessfulQueries(int ttl) {
856         if (dnsTtlSuccessfulQueries == ttl) {
857             return;
858         }
859 
860         dnsTtlSuccessfulQueries = ttl;
861         getConfig().setProperty(DNS_TTL_SUCCESSFUL_QUERIES_KEY, ttl);
862     }
863 
parseSocksProxyOptions()864     private void parseSocksProxyOptions() {
865         String host = System.getProperty("socksProxyHost");
866         int port;
867         String version;
868         boolean useDns = getBoolean(SOCKS_PROXY_DNS_KEY, DEFAULT_SOCKS_PROXY.isUseDns());
869 
870         if (host != null && !host.isEmpty()) {
871             port = parseSocksPort(System.getProperty("socksProxyPort"));
872             version = System.getProperty("socksProxyVersion");
873 
874             useSocksProxy = true;
875         } else {
876             host = getString(SOCKS_PROXY_HOST_KEY, DEFAULT_SOCKS_PROXY.getHost());
877             port = parseSocksPort(getConfig().getString(SOCKS_PROXY_PORT_KEY));
878             version =
879                     getString(
880                             SOCKS_PROXY_VERSION_KEY,
881                             String.valueOf(DEFAULT_SOCKS_PROXY.getVersion().number()));
882 
883             useSocksProxy = getBoolean(USE_SOCKS_PROXY_KEY, false);
884         }
885 
886         socksProxy = new SocksProxy(host, port, SocksProxy.Version.from(version), useDns);
887         if (useSocksProxy) {
888             apply(socksProxy);
889         }
890 
891         socksProxyPasswordAuth =
892                 new PasswordAuthentication(
893                         getString(SOCKS_PROXY_USERNAME_KEY, ""),
894                         getString(SOCKS_PROXY_PASSWORD_KEY, "").toCharArray());
895     }
896 
parseSocksPort(String value)897     private static int parseSocksPort(String value) {
898         if (value == null || value.isEmpty()) {
899             return DEFAULT_SOCKS_PROXY.getPort();
900         }
901 
902         int port;
903         try {
904             port = Integer.parseInt(value);
905         } catch (NumberFormatException e) {
906             log.warn("Failed to parse the SOCKS port: " + value, e);
907             return DEFAULT_SOCKS_PROXY.getPort();
908         }
909 
910         if (port > 0 && port <= 65535) {
911             return port;
912         }
913 
914         log.warn("Invalid SOCKS port: " + value);
915         return DEFAULT_SOCKS_PROXY.getPort();
916     }
917 
918     /**
919      * Applies the given SOCKS proxy configuration to the SOCKS system properties.
920      *
921      * <p>If the SOCKS proxy is not in use (i.e. {@link #useSocksProxy} is {@code false}) the system
922      * properties are cleared.
923      *
924      * @param socksProxy the SOCKS proxy to apply.
925      */
apply(SocksProxy socksProxy)926     private void apply(SocksProxy socksProxy) {
927         String host = "";
928         String port = "";
929         String version = "";
930         if (useSocksProxy) {
931             host = socksProxy.getHost();
932             port = Integer.toString(socksProxy.getPort());
933             version = Integer.toString(socksProxy.getVersion().number());
934         }
935         System.setProperty("socksProxyHost", host);
936         System.setProperty("socksProxyPort", port);
937         System.setProperty("socksProxyVersion", version);
938     }
939 
940     /**
941      * Tells whether or not the given hostname should be resolved.
942      *
943      * <p>The names should not be resolved when ZAP is configured to use a SOCKSv5 proxy and rely on
944      * it for resolution.
945      *
946      * <p><strong>Note:</strong> Not part of the public API.
947      *
948      * @param hostname the name to check.
949      * @return {@code true} if the given {@code hostname} should be resolved, {@code false}
950      *     otherwise.
951      */
952     @ZapApiIgnore
shouldResolveRemoteHostname(String hostname)953     public boolean shouldResolveRemoteHostname(String hostname) {
954         if (!useSocksProxy
955                 || !socksProxy.isUseDns()
956                 || socksProxy.getVersion() != SocksProxy.Version.SOCKS5) {
957             return true;
958         }
959         return LOOPBACK_PATTERN.matcher(hostname).matches();
960     }
961 
962     /**
963      * Tells whether or not the outgoing connections should use the SOCKS proxy.
964      *
965      * @return {@code true} if outgoing connections should use the SOCKS proxy, {@code false}
966      *     otherwise.
967      * @since 2.10.0
968      * @see #setUseSocksProxy(boolean)
969      */
isUseSocksProxy()970     public boolean isUseSocksProxy() {
971         return useSocksProxy;
972     }
973 
974     /**
975      * Sets whether or not the outgoing connections should use the SOCKS proxy.
976      *
977      * @param useSocksProxy {@code true} if outgoing connections should use the SOCKS proxy, {@code
978      *     false} otherwise.
979      * @since 2.10.0
980      * @see #isUseSocksProxy()
981      * @see #setSocksProxy(SocksProxy)
982      */
setUseSocksProxy(boolean useSocksProxy)983     public void setUseSocksProxy(boolean useSocksProxy) {
984         if (this.useSocksProxy == useSocksProxy) {
985             return;
986         }
987 
988         this.useSocksProxy = useSocksProxy;
989 
990         getConfig().setProperty(USE_SOCKS_PROXY_KEY, useSocksProxy);
991 
992         apply(socksProxy);
993     }
994 
995     /**
996      * Gets the SOCKS proxy for outgoing connections.
997      *
998      * @return the SOCKS proxy, never {@code null}.
999      * @since 2.10.0
1000      * @see #isUseSocksProxy()
1001      * @see #setSocksProxy(SocksProxy)
1002      */
1003     @ZapApiIgnore
getSocksProxy()1004     public SocksProxy getSocksProxy() {
1005         return socksProxy;
1006     }
1007 
1008     /**
1009      * Sets the SOCKS proxy for outgoing connections.
1010      *
1011      * @param socksProxy the SOCKS proxy.
1012      * @throws NullPointerException if the given {@code socksProxy} is {@code null}.
1013      * @since 2.10.0
1014      * @see #getSocksProxy()
1015      * @see #setUseSocksProxy(boolean)
1016      */
setSocksProxy(SocksProxy socksProxy)1017     public void setSocksProxy(SocksProxy socksProxy) {
1018         if (this.socksProxy.equals(socksProxy)) {
1019             return;
1020         }
1021 
1022         this.socksProxy = Objects.requireNonNull(socksProxy);
1023 
1024         getConfig().setProperty(SOCKS_PROXY_HOST_KEY, socksProxy.getHost());
1025         getConfig().setProperty(SOCKS_PROXY_PORT_KEY, socksProxy.getPort());
1026         getConfig().setProperty(SOCKS_PROXY_VERSION_KEY, socksProxy.getVersion().number());
1027         getConfig().setProperty(SOCKS_PROXY_DNS_KEY, socksProxy.isUseDns());
1028 
1029         if (useSocksProxy) {
1030             apply(socksProxy);
1031         }
1032     }
1033 
1034     /**
1035      * Gets the SOCKS proxy password authentication.
1036      *
1037      * @return the SOCKS proxy password authentication, never {@code null}.
1038      * @since 2.10.0
1039      * @see #isUseSocksProxy()
1040      * @see #setSocksProxyPasswordAuth(PasswordAuthentication)
1041      */
1042     @ZapApiIgnore
getSocksProxyPasswordAuth()1043     public PasswordAuthentication getSocksProxyPasswordAuth() {
1044         return socksProxyPasswordAuth;
1045     }
1046 
1047     /**
1048      * Sets the SOCKS proxy password authentication.
1049      *
1050      * @param passwordAuth the password authentication.
1051      * @throws NullPointerException if the given {@code passwordAuth} is {@code null}.
1052      * @since 2.10.0
1053      * @see #getSocksProxyPasswordAuth()
1054      */
setSocksProxyPasswordAuth(PasswordAuthentication passwordAuth)1055     public void setSocksProxyPasswordAuth(PasswordAuthentication passwordAuth) {
1056         this.socksProxyPasswordAuth = Objects.requireNonNull(passwordAuth);
1057 
1058         getConfig().setProperty(SOCKS_PROXY_USERNAME_KEY, passwordAuth.getUserName());
1059         getConfig().setProperty(SOCKS_PROXY_PASSWORD_KEY, new String(passwordAuth.getPassword()));
1060     }
1061 }
1062