001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2018-2022 Florian Schmaus.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smack;
019
020import java.net.MalformedURLException;
021import java.net.URL;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028
029import javax.net.ssl.HostnameVerifier;
030
031import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
032import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
033import org.jivesoftware.smack.compression.XMPPInputOutputStream;
034import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory;
035import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
036import org.jivesoftware.smack.parsing.ExceptionThrowingCallback;
037import org.jivesoftware.smack.parsing.ExceptionThrowingCallbackWithHint;
038import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
039import org.jivesoftware.smack.util.Objects;
040
041/**
042 * Represents the configuration of Smack. The configuration is used for:
043 * <ul>
044 *      <li> Initializing classes by loading them at start-up.
045 *      <li> Getting the current Smack version.
046 *      <li> Getting and setting global library behavior, such as the period of time
047 *          to wait for replies to packets from the server. Note: setting these values
048 *          via the API will override settings in the configuration file.
049 * </ul>
050 *
051 * Configuration settings are stored in org.jivesoftware.smack/smack-config.xml.
052 *
053 * @author Gaston Dombiak
054 */
055public final class SmackConfiguration {
056
057    public static final String SMACK_URL_STRING = "https://igniterealtime.org/projects/smack";
058
059    public static final URL SMACK_URL;
060
061    static {
062        try {
063            SMACK_URL = new URL(SMACK_URL_STRING);
064        } catch (MalformedURLException e) {
065            throw new IllegalStateException(e);
066        }
067    }
068
069    private static int defaultPacketReplyTimeout = 5000;
070    private static int packetCollectorSize = 5000;
071
072    private static List<String> defaultMechs = new ArrayList<>();
073
074    static Set<String> disabledSmackClasses = new HashSet<>();
075
076    static final List<XMPPInputOutputStream> compressionHandlers = new ArrayList<>(2);
077
078    static boolean smackInitialized = false;
079
080    /**
081     * Value that indicates whether debugging is enabled. When enabled, a debug
082     * window will appear for each new connection that will contain the following
083     * information:<ul>
084     * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
085     * <li> Server Traffic -- raw XML traffic sent by the server to the client.
086     * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
087     * </ul>
088     * Debugging can be enabled by setting this field to true, or by setting the Java system
089     * property <code>smack.debugEnabled</code> to true. The system property can be set on the
090     * command line such as "java SomeApp -Dsmack.debugEnabled=true".
091     */
092    public static boolean DEBUG = false;
093
094    private static SmackDebuggerFactory DEFAULT_DEBUGGER_FACTORY = ReflectionDebuggerFactory.INSTANCE;
095
096    /**
097     * The default parsing exception callback is {@link ExceptionThrowingCallback} which will
098     * throw an exception and therefore disconnect the active connection.
099     */
100    private static ParsingExceptionCallback defaultCallback = new ExceptionThrowingCallbackWithHint();
101
102    private static HostnameVerifier defaultHostnameVerififer;
103
104    /**
105     * Returns the Smack version information, eg "1.3.0".
106     *
107     * @return the Smack version information.
108     * @deprecated use {@link Smack#getVersion()} instead.
109     */
110    @Deprecated
111    // TODO: Remove in Smack 4.6
112    public static String getVersion() {
113        return SmackInitialization.SMACK_VERSION;
114    }
115
116    /**
117     * Returns the number of milliseconds to wait for a response from
118     * the server. The default value is 5000 ms.
119     *
120     * @return the milliseconds to wait for a response from the server
121     */
122    public static int getDefaultReplyTimeout() {
123        // The timeout value must be greater than 0 otherwise we will answer the default value
124        if (defaultPacketReplyTimeout <= 0) {
125            defaultPacketReplyTimeout = 5000;
126        }
127        return defaultPacketReplyTimeout;
128    }
129
130    /**
131     * Sets the number of milliseconds to wait for a response from
132     * the server.
133     *
134     * @param timeout the milliseconds to wait for a response from the server
135     */
136    public static void setDefaultReplyTimeout(int timeout) {
137        if (timeout <= 0) {
138            throw new IllegalArgumentException();
139        }
140        defaultPacketReplyTimeout = timeout;
141    }
142
143    public static void setDefaultSmackDebuggerFactory(SmackDebuggerFactory debuggerFactory) {
144        DEFAULT_DEBUGGER_FACTORY = Objects.requireNonNull(debuggerFactory, "Debugger factory must not be null");
145    }
146
147    public static SmackDebuggerFactory getDefaultSmackDebuggerFactory() {
148        return DEFAULT_DEBUGGER_FACTORY;
149    }
150
151    /**
152     * Gets the default max size of a stanza collector before it will delete
153     * the older packets.
154     *
155     * @return The number of packets to queue before deleting older packets.
156     */
157    public static int getStanzaCollectorSize() {
158        return packetCollectorSize;
159    }
160
161    /**
162     * Sets the default max size of a stanza collector before it will delete
163     * the older packets.
164     *
165     * @param collectorSize the number of packets to queue before deleting older packets.
166     */
167    public static void setStanzaCollectorSize(int collectorSize) {
168        packetCollectorSize = collectorSize;
169    }
170
171    /**
172     * Add a SASL mechanism to the list to be used.
173     *
174     * @param mech the SASL mechanism to be added
175     */
176    public static void addSaslMech(String mech) {
177        if (!defaultMechs.contains(mech)) {
178            defaultMechs.add(mech);
179        }
180    }
181
182    /**
183     * Add a Collection of SASL mechanisms to the list to be used.
184     *
185     * @param mechs the Collection of SASL mechanisms to be added
186     */
187    public static void addSaslMechs(Collection<String> mechs) {
188        for (String mech : mechs) {
189            addSaslMech(mech);
190        }
191    }
192
193    /**
194     * Remove a SASL mechanism from the list to be used.
195     *
196     * @param mech the SASL mechanism to be removed
197     */
198    public static void removeSaslMech(String mech) {
199        defaultMechs.remove(mech);
200    }
201
202    /**
203     * Remove a Collection of SASL mechanisms to the list to be used.
204     *
205     * @param mechs the Collection of SASL mechanisms to be removed
206     */
207    public static void removeSaslMechs(Collection<String> mechs) {
208        defaultMechs.removeAll(mechs);
209    }
210
211    /**
212     * Returns the list of SASL mechanisms to be used. If a SASL mechanism is
213     * listed here it does not guarantee it will be used. The server may not
214     * support it, or it may not be implemented.
215     *
216     * @return the list of SASL mechanisms to be used.
217     */
218    public static List<String> getSaslMechs() {
219        return Collections.unmodifiableList(defaultMechs);
220    }
221
222    /**
223     * Set the default parsing exception callback for all newly created connections.
224     *
225     * @param callback TODO javadoc me please
226     * @see ParsingExceptionCallback
227     */
228    public static void setDefaultParsingExceptionCallback(ParsingExceptionCallback callback) {
229        defaultCallback = callback;
230    }
231
232    /**
233     * Returns the default parsing exception callback.
234     *
235     * @return the default parsing exception callback
236     * @see ParsingExceptionCallback
237     */
238    public static ParsingExceptionCallback getDefaultParsingExceptionCallback() {
239        return defaultCallback;
240    }
241
242    public static void addCompressionHandler(XMPPInputOutputStream xmppInputOutputStream) {
243        compressionHandlers.add(xmppInputOutputStream);
244    }
245
246    /**
247     * Get compression handlers.
248     *
249     * @return a list of compression handlers.
250     */
251    public static List<XMPPInputOutputStream> getCompressionHandlers() {
252        List<XMPPInputOutputStream> res = new ArrayList<>(compressionHandlers.size());
253        for (XMPPInputOutputStream ios : compressionHandlers) {
254            if (ios.isSupported()) {
255                res.add(ios);
256            }
257        }
258        return res;
259    }
260
261    /**
262     * Set the default HostnameVerifier that will be used by XMPP connections to verify the hostname
263     * of a TLS certificate. XMPP connections are able to overwrite this settings by supplying a
264     * HostnameVerifier in their ConnectionConfiguration with
265     * {@link ConnectionConfiguration.Builder#setHostnameVerifier(HostnameVerifier)}.
266     *
267     * @param verifier HostnameVerifier
268     */
269    public static void setDefaultHostnameVerifier(HostnameVerifier verifier) {
270        defaultHostnameVerififer = verifier;
271    }
272
273    /**
274     * Convenience method for {@link #addDisabledSmackClass(String)}.
275     *
276     * @param clz the Smack class to disable
277     */
278    public static void addDisabledSmackClass(Class<?> clz) {
279        addDisabledSmackClass(clz.getName());
280    }
281
282    /**
283     * Add a class to the disabled smack classes.
284     * <p>
285     * {@code className} can also be a package name, in this case, the entire
286     * package is disabled (but can be manually enabled).
287     * </p>
288     *
289     * @param className TODO javadoc me please
290     */
291    public static void addDisabledSmackClass(String className) {
292        disabledSmackClasses.add(className);
293    }
294
295    /**
296     * Add the given class names to the list of disabled Smack classes.
297     *
298     * @param classNames the Smack classes to disable.
299     * @see #addDisabledSmackClass(String)
300     */
301    public static void addDisabledSmackClasses(String... classNames) {
302        for (String className : classNames) {
303            addDisabledSmackClass(className);
304        }
305    }
306
307    public static boolean isDisabledSmackClass(String className) {
308        for (String disabledClassOrPackage : disabledSmackClasses) {
309            if (disabledClassOrPackage.equals(className)) {
310                return true;
311            }
312            int lastDotIndex = disabledClassOrPackage.lastIndexOf('.');
313            // Security check to avoid NPEs if someone entered 'foo.bar.'
314            if (disabledClassOrPackage.length() > lastDotIndex
315                            // disabledClassOrPackage is not an Class
316                            && !Character.isUpperCase(disabledClassOrPackage.charAt(lastDotIndex + 1))
317                            // classToLoad startsWith the package disabledClassOrPackage disables
318                            && className.startsWith(disabledClassOrPackage)) {
319                // Skip the class because the whole package was disabled
320                return true;
321            }
322        }
323        return false;
324    }
325
326    /**
327     * Check if Smack was successfully initialized.
328     *
329     * @return true if smack was initialized, false otherwise
330     */
331    public static boolean isSmackInitialized() {
332        return smackInitialized;
333    }
334
335    /**
336     * Get the default HostnameVerifier
337     *
338     * @return the default HostnameVerifier or <code>null</code> if none was set
339     */
340    static HostnameVerifier getDefaultHostnameVerifier() {
341        return defaultHostnameVerififer;
342    }
343
344    public enum UnknownIqRequestReplyMode {
345        doNotReply,
346        replyFeatureNotImplemented,
347        replyServiceUnavailable,
348    }
349
350    private static UnknownIqRequestReplyMode unknownIqRequestReplyMode = UnknownIqRequestReplyMode.replyFeatureNotImplemented;
351
352    public static UnknownIqRequestReplyMode getUnknownIqRequestReplyMode() {
353        return unknownIqRequestReplyMode;
354    }
355
356    public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
357        SmackConfiguration.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Must set mode");
358    }
359
360    private static final int defaultConcurrencyLevelLimit;
361
362    static {
363        int availableProcessors = Runtime.getRuntime().availableProcessors();
364        if (availableProcessors < 8) {
365            defaultConcurrencyLevelLimit = 8;
366        } else {
367            defaultConcurrencyLevelLimit = (int) (availableProcessors * 1.1);
368        }
369    }
370
371    public static int getDefaultConcurrencyLevelLimit() {
372        return defaultConcurrencyLevelLimit;
373    }
374
375    private static final Set<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>> KNOWN_MODULES = new HashSet<>();
376
377    public static boolean addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptor) {
378        synchronized (KNOWN_MODULES) {
379            return KNOWN_MODULES.add(moduleDescriptor);
380        }
381    }
382
383    public static void addAllKnownModulesTo(ModularXmppClientToServerConnectionConfiguration.Builder builder) {
384        synchronized (KNOWN_MODULES) {
385            for (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptor : KNOWN_MODULES) {
386                builder.addModule(moduleDescriptor);
387            }
388        }
389    }
390
391    /**
392     * If enabled, causes {@link AbstractXMPPConnection} to create a thread for every asynchronous send operation. This
393     * is meant to work-around a shortcoming of Smack 4.4, where certain send operations are not asynchronous even if
394     * they should be. This is an expert setting, do not toggle if you do not understand the consequences or have been
395     * told to do so. Note that it is expected that this will not be needed in future Smack versions.
396     *
397     * @since 4.4.6
398     */
399    public static boolean TRUELY_ASYNC_SENDS = false;
400}