001/**
002 *
003 * Copyright 2019-2020 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 */
017package org.jxmpp.strings.testframework;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Set;
025
026import org.jxmpp.stringprep.XmppStringprep;
027
028public class KnownFailures {
029
030        private static final Set<XmppStringprepStringCoupling> VALID_JIDS_WHITELIST = new HashSet<>();
031        private static final Set<XmppStringprepStringCoupling> INVALID_JIDS_WHITELIST = new HashSet<>();
032
033        static {
034                // ICU4J
035                whitelistValidJid(XmppStringPrepper.ICU4J, "fußball@example.com");
036                whitelistValidJid(XmppStringPrepper.ICU4J, "user@example.org/🍺");
037
038                whitelistInvalidJid(XmppStringPrepper.ICU4J, "♚@example.com");
039                whitelistInvalidJid(XmppStringPrepper.ICU4J, "henry\u2163@example.com");
040                whitelistInvalidJid(XmppStringPrepper.ICU4J, "user@@host/resource");
041                whitelistInvalidJid(XmppStringPrepper.ICU4J, "user@@host");
042                whitelistInvalidJid(XmppStringPrepper.ICU4J, "user@@");
043                whitelistInvalidJid(XmppStringPrepper.ICU4J, "username@example.org@example.org");
044
045                // LIBIDN
046                whitelistValidJid(XmppStringPrepper.LIBIDN, "fußball@example.com");
047                whitelistValidJid(XmppStringPrepper.LIBIDN, "user@example.org/🍺");
048
049                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "♚@example.com");
050                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "henry\u2163@example.com");
051                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "user@@host/resource");
052                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "user@@host");
053                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "user@@");
054                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "xsf@muc.xmpp.org/؜x");
055                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "username@example.org@example.org");
056
057
058                // ROCKS_XMPP_PRECIS
059                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "ς@example.com");
060                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "user@[2001:638:a000:4134::ffff:40]");
061                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "user@[2001:638:a000:4134::ffff:40%eno1]");
062                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "user@averylongdomainpartisstillvalideventhoughitexceedsthesixtyfourbytelimitofdnslabels");
063
064
065                // SIMPLE
066                whitelistValidJid(XmppStringPrepper.SIMPLE, "ς@example.com");
067
068                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "♚@example.com");
069                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "henry\u2163@example.com");
070                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "user@@host/resource");
071                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "user@@host");
072                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "user@@");
073                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "xsf@muc.xmpp.org/؜x");
074                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "username@example.org@example.org");
075
076        }
077
078        /**
079         * Check a XMPP Strings Testframeworkresult against the list of known failures (whitelist).
080         *
081         * @param result the result of the XMPP Strings Testframework.
082         * @return the result of whitelist check.
083         */
084        public static Result checkAgainstWhitelist(StringsTestframework.Result result) {
085                List<ValidJidTestresult.Failed> validJidFailedTestresults = new ArrayList<>(result.validJidFailedTestresults);
086                List<InvalidJidTestresult.Failed> invalidJidFailedTestresults = new ArrayList<>(result.invalidJidFailedTestresults);
087
088                Set<XmppStringprepStringCoupling> validJidsWhitelist, invalidJidsWhitelist;
089                synchronized (VALID_JIDS_WHITELIST) {
090                        validJidsWhitelist = new HashSet<>(VALID_JIDS_WHITELIST);
091                }
092                synchronized (INVALID_JIDS_WHITELIST) {
093                        invalidJidsWhitelist = new HashSet<>(INVALID_JIDS_WHITELIST);
094                }
095
096                {
097                        Iterator<ValidJidTestresult.Failed> it = validJidFailedTestresults.iterator();
098                        while (it.hasNext()) {
099                                ValidJidTestresult.Failed failed = it.next();
100                                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(
101                                                failed.xmppStringPrepper.xmppStringprepClass, failed.validJid.unnormalizedJid);
102                                boolean wasWhitelisted = validJidsWhitelist.remove(coupling);
103                                if (wasWhitelisted) {
104                                        it.remove();
105                                }
106                        }
107                }
108
109                {
110                        Iterator<InvalidJidTestresult.Failed> it = invalidJidFailedTestresults.iterator();
111                        while (it.hasNext()) {
112                                InvalidJidTestresult.Failed failed = it.next();
113                                String invalidJid = failed.invalidJid.invalidJid;
114                                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(
115                                                failed.xmppStringPrepper.xmppStringprepClass, invalidJid);
116                                boolean wasWhitelisted = invalidJidsWhitelist.remove(coupling);
117                                if (wasWhitelisted) {
118                                        it.remove();
119                                }
120                        }
121                }
122
123                return new Result(validJidFailedTestresults, invalidJidFailedTestresults, validJidsWhitelist, invalidJidsWhitelist);
124        }
125
126        /**
127         * Whitelist the given valid JID and the used XMPP String prepper.
128         *
129         * @param xmppStringPrepper the used XMPP String prepper.
130         * @param validJid the valid JID.
131         */
132        public static void whitelistValidJid(XmppStringPrepper xmppStringPrepper, String validJid) {
133                whitelistValidJid(xmppStringPrepper.xmppStringprepClass, validJid);
134        }
135
136        /**
137         * Whitelist the given valid JID and the used XMPP String prepper.
138         *
139         * @param xmppStringprepClass the class of the used XMPP String prepper.
140         * @param validJid the valid JID.
141         */
142        public static void whitelistValidJid(Class<? extends XmppStringprep> xmppStringprepClass, String validJid) {
143                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(xmppStringprepClass, validJid);
144                final boolean newCoupling;
145                synchronized (VALID_JIDS_WHITELIST) {
146                        newCoupling = VALID_JIDS_WHITELIST.add(coupling);
147                }
148                if (!newCoupling) {
149                        throw new IllegalArgumentException(coupling + " is already whitelisted for valid JIDs");
150                }
151        }
152
153        /**
154         * Whitelist the given invalid JID and the used XMPP String prepper.
155         *
156         * @param xmppStringPrepper the used XMPP String prepper.
157         * @param invalidJid the invalid JID.
158         */
159        public static void whitelistInvalidJid(XmppStringPrepper xmppStringPrepper, String invalidJid) {
160                whitelistInvalidJid(xmppStringPrepper.xmppStringprepClass, invalidJid);
161        }
162
163        /**
164         * Whitelist the given invalid JID and the used XMPP String prepper.
165         *
166         * @param xmppStringprepClass the class of the used XMPP String prepper.
167         * @param invalidJid the invalid JID.
168         */
169        public static void whitelistInvalidJid(Class<? extends XmppStringprep> xmppStringprepClass, String invalidJid) {
170                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(xmppStringprepClass, invalidJid);
171                final boolean newCoupling;
172                synchronized (INVALID_JIDS_WHITELIST) {
173                        newCoupling = INVALID_JIDS_WHITELIST.add(coupling);
174                }
175                if (!newCoupling) {
176                        throw new IllegalArgumentException(coupling + " is already whitelisted for invalid JIDs");
177                }
178        }
179
180        public static class Result {
181                public final boolean noUnknownFailures;
182                public final List<ValidJidTestresult.Failed> remainingValidJidFailedTestresults;
183                public final List<InvalidJidTestresult.Failed> remainingInvalidJidFailedTestresults;
184
185                public final Set<XmppStringprepStringCoupling> remainingValidJidsWhitelist, remainingInvalidJidsWhitelist;
186
187                private Result(List<ValidJidTestresult.Failed> remainingValidJidFailedTestresults,
188                                List<InvalidJidTestresult.Failed> remainingInvalidJidFailedTestresults,
189                                Set<XmppStringprepStringCoupling> remainingValidJidsWhitelist,
190                                Set<XmppStringprepStringCoupling> remainingInvalidJidsWhitelist) {
191                        noUnknownFailures = remainingValidJidFailedTestresults.isEmpty()
192                                        && remainingInvalidJidFailedTestresults.isEmpty() && remainingValidJidsWhitelist.isEmpty()
193                                        && remainingInvalidJidsWhitelist.isEmpty();
194
195                        this.remainingValidJidFailedTestresults = Collections.unmodifiableList(remainingValidJidFailedTestresults);
196                        this.remainingInvalidJidFailedTestresults = Collections.unmodifiableList(remainingInvalidJidFailedTestresults);
197                        this.remainingValidJidsWhitelist = Collections.unmodifiableSet(remainingValidJidsWhitelist);
198                        this.remainingInvalidJidsWhitelist = Collections.unmodifiableSet(remainingInvalidJidsWhitelist);
199                }
200
201                @Override
202                public String toString() {
203                        StringBuilder sb = new StringBuilder();
204                        sb.append(
205                                        "XMPP Strings Testframework Known Failure Report\n" +
206                                        "===============================================\n");
207                        if (!remainingValidJidFailedTestresults.isEmpty()) {
208                                sb.append(
209                                                "Remaining Valid JID failed results:\n" +
210                                                "-----------------------------------\n"
211                                );
212                                for (ValidJidTestresult.Failed failed : remainingValidJidFailedTestresults) {
213                                        sb.append(failed).append('\n');
214                                }
215                        }
216                        if (!remainingValidJidsWhitelist.isEmpty()) {
217                                sb.append(
218                                                "Remaining Valid JID failed whitelist entries:\n" +
219                                                "---------------------------------------------\n"
220                                );
221                                for (XmppStringprepStringCoupling coupling : remainingValidJidsWhitelist) {
222                                        sb.append(coupling).append('\n');
223                                }
224                        }
225                        if (!remainingInvalidJidFailedTestresults.isEmpty()) {
226                                sb.append(
227                                                "Remaining Invalid JID failed results:\n" +
228                                                "-----------------------------------\n"
229                                );
230                                for (InvalidJidTestresult.Failed failed : remainingInvalidJidFailedTestresults) {
231                                        sb.append(failed).append('\n');
232                                }
233                        }
234                        if (!remainingInvalidJidsWhitelist.isEmpty()) {
235                                sb.append(
236                                                "Remaining Invalid JID failed whitelist entries:\n" +
237                                                "---------------------------------------------\n"
238                                );
239                                for (XmppStringprepStringCoupling coupling : remainingInvalidJidsWhitelist) {
240                                        sb.append(coupling).append('\n');
241                                }
242                        }
243                        return sb.toString();
244                }
245        }
246
247        private static class XmppStringprepStringCoupling {
248                public final Class<? extends XmppStringprep> xmppStringprepClass;
249                public final String string;
250
251                private XmppStringprepStringCoupling(Class<? extends XmppStringprep> xmppStringprepClass, String string) {
252                        this.xmppStringprepClass = xmppStringprepClass;
253                        this.string = string;
254                }
255
256                @Override
257                public int hashCode() {
258                        int result = 17;
259                        result = 31 * result + xmppStringprepClass.hashCode();
260                        result = 31 * result + string.hashCode();
261                        return result;
262                }
263
264                @Override
265                public boolean equals(Object other) {
266                        if (this == other) {
267                                return true;
268                        }
269
270                        if (!(other instanceof XmppStringprepStringCoupling)) {
271                                return false;
272                        }
273
274                        XmppStringprepStringCoupling lhs = (XmppStringprepStringCoupling) other;
275                        return xmppStringprepClass.equals(lhs.xmppStringprepClass) && string.equals(lhs.string);
276                }
277
278                @Override
279                public String toString() {
280                        return xmppStringprepClass.getName() + " '" + string + "\'";
281                }
282        }
283
284}